From eff1d3f1a5aeaa09048319b20391e958ac2a8b94 Mon Sep 17 00:00:00 2001 From: Simon Michael Date: Sat, 3 Aug 2013 20:47:43 -0700 Subject: [PATCH] csv reader: add the `include` directive, useful for factoring out common rules used with multiple CSV files --- MANUAL.md | 6 +++++- hledger-lib/Hledger/Read/CsvReader.hs | 17 +++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/MANUAL.md b/MANUAL.md index f7256819e..b0c8768f8 100644 --- a/MANUAL.md +++ b/MANUAL.md @@ -549,8 +549,12 @@ The following kinds of rule can appear in any order: %-m/%-d/%Y %Y-%h-%d +**include** *RULESFILE* +: Include another rules file at this point. Useful for common rules shared across multiple CSV files. + Typically you'll keep one rules file for each account which you -download as CSV. For an example, see [How to read CSV files](CSV.html). +download as CSV. For an example, see [How to read CSV +files](CSV.html). Other notes: diff --git a/hledger-lib/Hledger/Read/CsvReader.hs b/hledger-lib/Hledger/Read/CsvReader.hs index db95a71e7..0176bd1b8 100644 --- a/hledger-lib/Hledger/Read/CsvReader.hs +++ b/hledger-lib/Hledger/Read/CsvReader.hs @@ -22,7 +22,7 @@ import Control.Exception hiding (try) import Control.Monad import Control.Monad.Error -- import Test.HUnit -import Data.Char (toLower, isDigit) +import Data.Char (toLower, isDigit, isSpace) import Data.List import Data.Maybe import Data.Ord @@ -319,7 +319,7 @@ getDirective directivename = lookup directivename . rdirectives parseRulesFile :: FilePath -> IO (Either ParseError CsvRules) parseRulesFile f = do - s <- readFile' f + s <- readFile' f >>= expandIncludes let rules = parseCsvRules f s return $ case rules of Left e -> Left e @@ -329,6 +329,19 @@ parseRulesFile f = do where toParseError s = newErrorMessage (Message s) (initialPos "") +-- | Pre-parse csv rules to interpolate included files, recursively. +-- This is a cheap hack to avoid rewriting the existing parser. +expandIncludes :: String -> IO String +expandIncludes s = do + let (ls,rest) = break (isPrefixOf "include") $ lines s + case rest of + [] -> return $ unlines ls + (('i':'n':'c':'l':'u':'d':'e':f):ls') -> do + let f' = dropWhile isSpace f + included <- readFile f' >>= expandIncludes + return $ unlines [unlines ls, included, unlines ls'] + ls' -> return $ unlines $ ls ++ ls' -- should never get here + parseCsvRules :: FilePath -> String -> Either ParseError CsvRules -- parseCsvRules rulesfile s = runParser csvrulesfile nullrules{baseAccount=takeBaseName rulesfile} rulesfile s parseCsvRules rulesfile s =