Skip to content

Commit

Permalink
lib: csv: refactor, allow writing different rules text
Browse files Browse the repository at this point in the history
  • Loading branch information
simonmichael committed Feb 6, 2017
1 parent 9cfb7bf commit ea1f19c
Showing 1 changed file with 38 additions and 29 deletions.
67 changes: 38 additions & 29 deletions hledger-lib/Hledger/Read/CsvReader.hs
Expand Up @@ -20,6 +20,8 @@ module Hledger.Read.CsvReader (
-- rules,
rulesFileFor,
parseRulesFile,
parseAndValidateCsvRules,
expandIncludes,
transactionFromCsvRecord,
-- * Tests
tests_Hledger_Read_CsvReader
Expand Down Expand Up @@ -100,11 +102,13 @@ readJournalFromCsv mrulesfile csvfile csvdata =
-- parse rules
let rulesfile = fromMaybe (rulesFileFor csvfile) mrulesfile
rulesfileexists <- doesFileExist rulesfile
when rulesfileexists $ hPrintf stderr "using conversion rules file %s\n" rulesfile
rules <-
rulestext <-
if rulesfileexists
then liftIO (runExceptT $ parseRulesFile rulesfile) >>= either throwerr return
else return defaultRules
then do
hPrintf stderr "using conversion rules file %s\n" rulesfile
liftIO $ (readFile' rulesfile >>= expandIncludes (takeDirectory rulesfile))
else return $ defaultRulesText rulesfile
rules <- liftIO (runExceptT $ parseAndValidateCsvRules rulesfile rulestext) >>= either throwerr return
dbg2IO "rules" rules

-- apply skip directive
Expand All @@ -114,7 +118,7 @@ readJournalFromCsv mrulesfile csvfile csvdata =
oneorerror s = readDef (throwerr $ "could not parse skip value: " ++ show s) s

-- parse csv
-- parsec seems to fail if you pass it "-" here
-- parsec seems to fail if you pass it "-" here XXX try again with megaparsec
let parsecfilename = if csvfile == "-" then "(stdin)" else csvfile
records <- (either throwerr id .
dbg2 "validateCsv" . validateCsv skip .
Expand Down Expand Up @@ -144,7 +148,7 @@ readJournalFromCsv mrulesfile csvfile csvdata =

when (not rulesfileexists) $ do
hPrintf stderr "created default conversion rules file %s, edit this for better results\n" rulesfile
writeFile rulesfile $ T.unpack $ defaultRulesText rulesfile
writeFile rulesfile $ T.unpack rulestext

return $ Right nulljournal{jtxns=sortBy (comparing tdate) txns'}

Expand Down Expand Up @@ -219,13 +223,6 @@ defaultRulesText csvfile = T.pack $ unlines
," account2 assets:bank:savings\n"
]

defaultRules :: CsvRules
defaultRules =
either
(error' "Could not parse the default CSV rules, this should not happen")
id
$ parseCsvRules "" $ defaultRulesText ""

--------------------------------------------------------------------------------
-- Conversion rules parsing

Expand Down Expand Up @@ -351,23 +348,18 @@ getDirective directivename = lookup directivename . rdirectives
instance ShowErrorComponent String where
showErrorComponent = id

-- | An error-throwing action that parses this file's content
-- as CSV conversion rules, interpolating any included files first,
-- and runs some extra validation checks.
parseRulesFile :: FilePath -> ExceptT String IO CsvRules
parseRulesFile f = do
s <- liftIO $ (readFile' f >>= expandIncludes (takeDirectory f))
let rules = parseCsvRules f s
case rules of
Left e -> ExceptT $ return $ Left $ parseErrorPretty e
Right r -> do
r_ <- liftIO $ runExceptT $ validateRules r
ExceptT $ case r_ of
Left e -> return $ Left $ parseErrorPretty $ toParseError e
Right r -> return $ Right r
where
toParseError :: forall s. Ord s => s -> ParseError Char s
toParseError s = (mempty :: ParseError Char s) { errorCustom = S.singleton s}

-- | Pre-parse csv rules to interpolate included files, recursively.
-- This is a cheap hack to avoid rewriting the existing parser.
parseRulesFile f =
liftIO (readFile' f >>= expandIncludes (takeDirectory f)) >>= parseAndValidateCsvRules f

-- | Look for hledger rules file-style include directives in this text,
-- and interpolate the included files, recursively.
-- Included file paths may be relative to the directory of the
-- provided file path.
-- This is a cheap hack to avoid rewriting the CSV rules parser.
expandIncludes :: FilePath -> T.Text -> IO T.Text
expandIncludes basedir content = do
let (ls,rest) = break (T.isPrefixOf "include") $ T.lines content
Expand All @@ -380,6 +372,23 @@ expandIncludes basedir content = do
return $ T.unlines [T.unlines ls, included, T.unlines ls']
ls' -> return $ T.unlines $ ls ++ ls' -- should never get here

-- | An error-throwing action that parses this text as CSV conversion rules
-- and runs some extra validation checks. The file path is for error messages.
parseAndValidateCsvRules :: FilePath -> T.Text -> ExceptT String IO CsvRules
parseAndValidateCsvRules rulesfile s = do
let rules = parseCsvRules rulesfile s
case rules of
Left e -> ExceptT $ return $ Left $ parseErrorPretty e
Right r -> do
r_ <- liftIO $ runExceptT $ validateRules r
ExceptT $ case r_ of
Left e -> return $ Left $ parseErrorPretty $ toParseError e
Right r -> return $ Right r
where
toParseError :: forall s. Ord s => s -> ParseError Char s
toParseError s = (mempty :: ParseError Char s) { errorCustom = S.singleton s}

-- | Parse this text as CSV conversion rules. The file path is for error messages.
parseCsvRules :: FilePath -> T.Text -> Either (ParseError Char Dec) CsvRules
-- parseCsvRules rulesfile s = runParser csvrulesfile nullrules{baseAccount=takeBaseName rulesfile} rulesfile s
parseCsvRules rulesfile s =
Expand Down

0 comments on commit ea1f19c

Please sign in to comment.