lib: csv: refactor, allow writing different rules text
This commit is contained in:
		
							parent
							
								
									9cfb7bf6af
								
							
						
					
					
						commit
						ea1f19c71e
					
				| @ -20,6 +20,8 @@ module Hledger.Read.CsvReader ( | |||||||
|   -- rules, |   -- rules, | ||||||
|   rulesFileFor, |   rulesFileFor, | ||||||
|   parseRulesFile, |   parseRulesFile, | ||||||
|  |   parseAndValidateCsvRules, | ||||||
|  |   expandIncludes, | ||||||
|   transactionFromCsvRecord, |   transactionFromCsvRecord, | ||||||
|   -- * Tests |   -- * Tests | ||||||
|   tests_Hledger_Read_CsvReader |   tests_Hledger_Read_CsvReader | ||||||
| @ -100,11 +102,13 @@ readJournalFromCsv mrulesfile csvfile csvdata = | |||||||
|   -- parse rules |   -- parse rules | ||||||
|   let rulesfile = fromMaybe (rulesFileFor csvfile) mrulesfile |   let rulesfile = fromMaybe (rulesFileFor csvfile) mrulesfile | ||||||
|   rulesfileexists <- doesFileExist rulesfile |   rulesfileexists <- doesFileExist rulesfile | ||||||
|   when rulesfileexists $ hPrintf stderr "using conversion rules file %s\n" rulesfile |   rulestext <- | ||||||
|   rules <- |  | ||||||
|     if rulesfileexists |     if rulesfileexists | ||||||
|     then liftIO (runExceptT $ parseRulesFile rulesfile) >>= either throwerr return |     then do | ||||||
|     else return defaultRules |       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 |   dbg2IO "rules" rules | ||||||
| 
 | 
 | ||||||
|   -- apply skip directive |   -- apply skip directive | ||||||
| @ -114,7 +118,7 @@ readJournalFromCsv mrulesfile csvfile csvdata = | |||||||
|           oneorerror s  = readDef (throwerr $ "could not parse skip value: " ++ show s) s |           oneorerror s  = readDef (throwerr $ "could not parse skip value: " ++ show s) s | ||||||
| 
 | 
 | ||||||
|   -- parse csv |   -- 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 |   let parsecfilename = if csvfile == "-" then "(stdin)" else csvfile | ||||||
|   records <- (either throwerr id . |   records <- (either throwerr id . | ||||||
|               dbg2 "validateCsv" . validateCsv skip . |               dbg2 "validateCsv" . validateCsv skip . | ||||||
| @ -144,7 +148,7 @@ readJournalFromCsv mrulesfile csvfile csvdata = | |||||||
| 
 | 
 | ||||||
|   when (not rulesfileexists) $ do |   when (not rulesfileexists) $ do | ||||||
|     hPrintf stderr "created default conversion rules file %s, edit this for better results\n" rulesfile |     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'} |   return $ Right nulljournal{jtxns=sortBy (comparing tdate) txns'} | ||||||
| 
 | 
 | ||||||
| @ -219,13 +223,6 @@ defaultRulesText csvfile = T.pack $ unlines | |||||||
|   ," account2 assets:bank:savings\n" |   ," 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 | -- Conversion rules parsing | ||||||
| 
 | 
 | ||||||
| @ -351,23 +348,18 @@ getDirective directivename = lookup directivename . rdirectives | |||||||
| instance ShowErrorComponent String where | instance ShowErrorComponent String where | ||||||
|   showErrorComponent = id |   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 :: FilePath -> ExceptT String IO CsvRules | ||||||
| parseRulesFile f = do | parseRulesFile f =  | ||||||
|   s <- liftIO $ (readFile' f >>= expandIncludes (takeDirectory f)) |   liftIO (readFile' f >>= expandIncludes (takeDirectory f)) >>= parseAndValidateCsvRules 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. | -- | Look for hledger rules file-style include directives in this text, | ||||||
| -- This is a cheap hack to avoid rewriting the existing parser. | -- 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 :: FilePath -> T.Text -> IO T.Text | ||||||
| expandIncludes basedir content = do | expandIncludes basedir content = do | ||||||
|   let (ls,rest) = break (T.isPrefixOf "include") $ T.lines content |   let (ls,rest) = break (T.isPrefixOf "include") $ T.lines content | ||||||
| @ -380,6 +372,23 @@ expandIncludes basedir content = do | |||||||
|       return $ T.unlines [T.unlines ls, included, T.unlines ls'] |       return $ T.unlines [T.unlines ls, included, T.unlines ls'] | ||||||
|     ls' -> return $ T.unlines $ ls ++ ls'   -- should never get here |     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 :: FilePath -> T.Text -> Either (ParseError Char Dec) CsvRules | ||||||
| -- parseCsvRules rulesfile s = runParser csvrulesfile nullrules{baseAccount=takeBaseName rulesfile} rulesfile s | -- parseCsvRules rulesfile s = runParser csvrulesfile nullrules{baseAccount=takeBaseName rulesfile} rulesfile s | ||||||
| parseCsvRules rulesfile s = | parseCsvRules rulesfile s = | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user