Allow separator to be overridden from command line

This also fixes that parsing separators on the command line and the
rules file was not handled the same way.
This commit is contained in:
Aleksandar Dimitrov 2020-01-02 19:09:52 +01:00 committed by Simon Michael
parent b524a088c7
commit 02d9205af7
3 changed files with 58 additions and 16 deletions

View File

@ -50,6 +50,7 @@ module Hledger.Read.Common (
getAccountAliases,
clearAccountAliases,
journalAddFile,
getSpecialSeparators,
-- * parsers
-- ** transaction bits
@ -162,7 +163,7 @@ data InputOpts = InputOpts {
mformat_ :: Maybe StorageFormat -- ^ a file/storage format to try, unless overridden
-- by a filename prefix. Nothing means try all.
,mrules_file_ :: Maybe FilePath -- ^ a conversion rules file to use (when reading CSV)
,separator_ :: Char -- ^ the separator to use (when reading CSV)
,separator_ :: Maybe Char -- ^ the separator to use (when reading CSV)
,aliases_ :: [String] -- ^ account name aliases to apply
,anon_ :: Bool -- ^ do light anonymisation/obfuscation of the data
,ignore_assertions_ :: Bool -- ^ don't check balance assertions
@ -175,14 +176,14 @@ data InputOpts = InputOpts {
instance Default InputOpts where def = definputopts
definputopts :: InputOpts
definputopts = InputOpts def def ',' def def def def True def def
definputopts = InputOpts def def def def def def def True def def
rawOptsToInputOpts :: RawOpts -> InputOpts
rawOptsToInputOpts rawopts = InputOpts{
-- files_ = listofstringopt "file" rawopts
mformat_ = Nothing
,mrules_file_ = maybestringopt "rules-file" rawopts
,separator_ = fromMaybe ',' (maybecharopt "separator" rawopts)
,separator_ = maybestringopt "separator" rawopts >>= getSpecialSeparators
,aliases_ = listofstringopt "alias" rawopts
,anon_ = boolopt "anon" rawopts
,ignore_assertions_ = boolopt "ignore-assertions" rawopts
@ -194,6 +195,14 @@ rawOptsToInputOpts rawopts = InputOpts{
--- * parsing utilities
-- | Parse special separator names TAB and SPACE, or return the first
-- character. Return Nothing on empty string
getSpecialSeparators :: String -> Maybe Char
getSpecialSeparators "SPACE" = Just ' '
getSpecialSeparators "TAB" = Just '\t'
getSpecialSeparators (x:_) = Just x
getSpecialSeparators [] = Nothing
-- | Run a text parser in the identity monad. See also: parseWithState.
runTextParser, rtp
:: TextParser Identity a -> Text -> Either (ParseErrorBundle Text CustomErr) a

View File

@ -71,7 +71,7 @@ import Text.Printf (printf)
import Hledger.Data
import Hledger.Utils
import Hledger.Read.Common (Reader(..),InputOpts(..),amountp, statusp, genericSourcePos, finaliseJournal)
import Hledger.Read.Common (Reader(..),InputOpts(..),amountp, statusp, genericSourcePos, getSpecialSeparators, finaliseJournal)
type CSV = [Record]
@ -104,13 +104,13 @@ parse iopts f t = do
-- better preemptively reverse them once more. XXX inefficient
pj' = journalReverse pj
getSeparatorFromRules :: Char -> CsvRules -> Char
getSeparatorFromRules defaultSeparator rules =
maybe defaultSeparator id (getSeparator <$> getDirective "separator" rules)
where getSeparator :: String -> Char
getSeparator "SPACE" = ' '
getSeparator "TAB" = '\t'
getSeparator x = head x
-- | Decide which separator to get.
-- If the external separator is provided, take it. Otherwise, look at the rules. Finally, return ','.
getSeparator :: Maybe Char -> CsvRules -> Char
getSeparator externalSeparator rules = head $
catMaybes [ externalSeparator
, getDirective "separator" rules >>= getSpecialSeparators
, Just ',']
-- | Read a Journal from the given CSV data (and filename, used for error
-- messages), or return an error. Proceed as follows:
@ -123,9 +123,9 @@ getSeparatorFromRules defaultSeparator rules =
-- 4. if the rules file didn't exist, create it with the default rules and filename
-- 5. return the transactions as a Journal
-- @
readJournalFromCsv :: Char -> Maybe FilePath -> FilePath -> Text -> IO (Either String Journal)
readJournalFromCsv :: Maybe Char -> Maybe FilePath -> FilePath -> Text -> IO (Either String Journal)
readJournalFromCsv _ Nothing "-" _ = return $ Left "please use --rules-file when reading CSV from stdin"
readJournalFromCsv separator mrulesfile csvfile csvdata =
readJournalFromCsv commandLineSeparator mrulesfile csvfile csvdata =
handle (\(e::IOException) -> return $ Left $ show e) $ do
-- make and throw an IO exception.. which we catch and convert to an Either above ?
@ -156,7 +156,7 @@ readJournalFromCsv separator mrulesfile csvfile csvdata =
records <- (either throwerr id .
dbg2 "validateCsv" . validateCsv rules skiplines .
dbg2 "parseCsv")
`fmap` parseCsv (getSeparatorFromRules separator rules) parsecfilename csvdata
`fmap` parseCsv (getSeparator commandLineSeparator rules) parsecfilename csvdata
dbg1IO "first 3 csv records" $ take 3 records
-- identify header lines

View File

@ -527,7 +527,7 @@ $ ./hledger-csv
>=0
# 25. specify alternative delimiter in rules
# 25. specify alternative whitespace delimiter in rules
<
2009/10/01 Flubber Co 50 123
@ -539,7 +539,40 @@ separator TAB
$ ./hledger-csv
2009/10/01 Flubber Co
(assets:myacct) $50 = $123
(assets:myacct) $50 = $123
>=0
# 26. specify char delimiter in rules
<
2009/10/01;Flubber Co;50;123
RULES
fields date, description, amount, balance
currency $
account1 (assets:myacct)
separator ;
$ ./hledger-csv
2009/10/01 Flubber Co
(assets:myacct) $50 = $123
>=0
# 27. command line delimiter overrides configuration file
<
2009/10/01 Flubber Co 50 123
RULES
fields date, description, amount, balance
currency $
account1 (assets:myacct)
separator ;
$ ./hledger-csv --separator 'TAB'
2009/10/01 Flubber Co
(assets:myacct) $50 = $123
>=0
## 25. A single unbalanced posting with number other than 1 also should not generate a balancing posting.