From 2eb40736519d80ad21ea9118eba0f76ded94c356 Mon Sep 17 00:00:00 2001 From: Imuli Date: Tue, 26 May 2015 18:15:46 -0400 Subject: [PATCH 1/4] read multiple files: options --- hledger/Hledger/Cli/Main.hs | 2 +- hledger/Hledger/Cli/Options.hs | 12 +++++++----- hledger/Hledger/Cli/Utils.hs | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/hledger/Hledger/Cli/Main.hs b/hledger/Hledger/Cli/Main.hs index 0bb0eee2e..b3667f0b7 100644 --- a/hledger/Hledger/Cli/Main.hs +++ b/hledger/Hledger/Cli/Main.hs @@ -256,7 +256,7 @@ main = do -- internal commands | cmd == "activity" = withJournalDo opts histogram `orShowHelp` activitymode - | cmd == "add" = (journalFilePathFromOpts opts >>= ensureJournalFileExists >> withJournalDo opts add) `orShowHelp` addmode + | cmd == "add" = (journalFilePathFromOpts opts >>= (ensureJournalFileExists . head) >> withJournalDo opts add) `orShowHelp` addmode | cmd == "accounts" = withJournalDo opts accounts `orShowHelp` accountsmode | cmd == "balance" = withJournalDo opts balance `orShowHelp` balancemode | cmd == "balancesheet" = withJournalDo opts balancesheet `orShowHelp` balancesheetmode diff --git a/hledger/Hledger/Cli/Options.hs b/hledger/Hledger/Cli/Options.hs index 9f121c15c..d514e4606 100644 --- a/hledger/Hledger/Cli/Options.hs +++ b/hledger/Hledger/Cli/Options.hs @@ -250,7 +250,7 @@ s `withAliases` as = s ++ " (" ++ intercalate ", " as ++ ")" data CliOpts = CliOpts { rawopts_ :: RawOpts ,command_ :: String - ,file_ :: Maybe FilePath + ,file_ :: [FilePath] ,rules_file_ :: Maybe FilePath ,output_file_ :: Maybe FilePath ,output_format_ :: Maybe String @@ -311,7 +311,7 @@ rawOptsToCliOpts rawopts = do return defcliopts { rawopts_ = rawopts ,command_ = stringopt "command" rawopts - ,file_ = maybestringopt "file" rawopts + ,file_ = map stripquotes $ listofstringopt "file" rawopts ,rules_file_ = maybestringopt "rules-file" rawopts ,output_file_ = maybestringopt "output-file" rawopts ,output_format_ = maybestringopt "output-format" rawopts @@ -368,12 +368,14 @@ aliasesFromOpts = map (\a -> fromparse $ runParser accountaliasp () ("--alias "+ -- | Get the (tilde-expanded, absolute) journal file path from -- 1. options, 2. an environment variable, or 3. the default. -journalFilePathFromOpts :: CliOpts -> IO String +journalFilePathFromOpts :: CliOpts -> IO [String] journalFilePathFromOpts opts = do f <- defaultJournalPath d <- getCurrentDirectory - expandPath d $ fromMaybe f $ file_ opts - + mapM (expandPath d) $ ifEmpty (file_ opts) [f] + where + ifEmpty [] d = d + ifEmpty l _ = l -- | Get the expanded, absolute output file path from options, -- or the default (-, meaning stdout). diff --git a/hledger/Hledger/Cli/Utils.hs b/hledger/Hledger/Cli/Utils.hs index 735050e9d..d70152263 100644 --- a/hledger/Hledger/Cli/Utils.hs +++ b/hledger/Hledger/Cli/Utils.hs @@ -68,7 +68,7 @@ withJournalDo opts cmd = do -- to let the add command work. rulespath <- rulesFilePathFromOpts opts journalpath <- journalFilePathFromOpts opts - ej <- readJournalFile Nothing rulespath (not $ ignore_assertions_ opts) journalpath + ej <- readJournalFile Nothing rulespath (not $ ignore_assertions_ opts) (head journalpath) either error' (cmd opts . journalApplyAliases (aliasesFromOpts opts)) ej -- | Write some output to stdout or to a file selected by --output-file. From 3f15b8052022d3faa476e597267771c4d9a9b7b4 Mon Sep 17 00:00:00 2001 From: Imuli Date: Tue, 26 May 2015 22:34:03 -0400 Subject: [PATCH 2/4] read multiple files: internals --- hledger-lib/Hledger/Read.hs | 26 +++++++++++++++++--------- hledger/Hledger/Cli/Utils.hs | 2 +- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/hledger-lib/Hledger/Read.hs b/hledger-lib/Hledger/Read.hs index 555d34ce9..88ca87a83 100644 --- a/hledger-lib/Hledger/Read.hs +++ b/hledger-lib/Hledger/Read.hs @@ -15,6 +15,7 @@ module Hledger.Read ( readJournal, readJournal', readJournalFile, + readJournalFiles, requireJournalFileExists, ensureJournalFileExists, -- * Parsers used elsewhere @@ -39,7 +40,7 @@ import System.Directory (doesFileExist, getHomeDirectory) import System.Environment (getEnv) import System.Exit (exitFailure) import System.FilePath (()) -import System.IO (IOMode(..), withFile, stdin, stderr, hSetNewlineMode, universalNewlineMode) +import System.IO (IOMode(..), openFile, stdin, stderr, hSetNewlineMode, universalNewlineMode) import Test.HUnit import Text.Printf @@ -51,7 +52,7 @@ import Hledger.Read.TimelogReader as TimelogReader import Hledger.Read.CsvReader as CsvReader import Hledger.Utils import Prelude hiding (getContents, writeFile) -import Hledger.Utils.UTF8IOCompat (getContents, hGetContents, writeFile) +import Hledger.Utils.UTF8IOCompat (hGetContents, writeFile) journalEnvVar = "LEDGER_FILE" @@ -163,17 +164,24 @@ readersForPathAndData (f,s) = filter (\r -> (rDetector r) f s) readers -- conversion of that format. Also there is a flag specifying whether -- to check or ignore balance assertions in the journal. readJournalFile :: Maybe StorageFormat -> Maybe FilePath -> Bool -> FilePath -> IO (Either String Journal) -readJournalFile format rulesfile assrt "-" = do - hSetNewlineMode stdin universalNewlineMode - getContents >>= readJournal format rulesfile assrt (Just "-") -readJournalFile format rulesfile assrt f = do - requireJournalFileExists f - withFile f ReadMode $ \h -> do +readJournalFile format rulesfile assrt f = readJournalFiles format rulesfile assrt [f] + +readJournalFiles :: Maybe StorageFormat -> Maybe FilePath -> Bool -> [FilePath] -> IO (Either String Journal) +readJournalFiles format rulesfile assrt f = do + contents <- fmap concat $ mapM readFileAnyNewline f + readJournal format rulesfile assrt (listToMaybe f) contents + where + readFileAnyNewline f = do + requireJournalFileExists f + h <- fileHandle f hSetNewlineMode h universalNewlineMode - hGetContents h >>= readJournal format rulesfile assrt (Just f) + hGetContents h + fileHandle "-" = return stdin + fileHandle f = openFile f ReadMode -- | If the specified journal file does not exist, give a helpful error and quit. requireJournalFileExists :: FilePath -> IO () +requireJournalFileExists "-" = return () requireJournalFileExists f = do exists <- doesFileExist f when (not exists) $ do diff --git a/hledger/Hledger/Cli/Utils.hs b/hledger/Hledger/Cli/Utils.hs index d70152263..7f4bd27bd 100644 --- a/hledger/Hledger/Cli/Utils.hs +++ b/hledger/Hledger/Cli/Utils.hs @@ -68,7 +68,7 @@ withJournalDo opts cmd = do -- to let the add command work. rulespath <- rulesFilePathFromOpts opts journalpath <- journalFilePathFromOpts opts - ej <- readJournalFile Nothing rulespath (not $ ignore_assertions_ opts) (head journalpath) + ej <- readJournalFiles Nothing rulespath (not $ ignore_assertions_ opts) journalpath either error' (cmd opts . journalApplyAliases (aliasesFromOpts opts)) ej -- | Write some output to stdout or to a file selected by --output-file. From a7ba436116868670df1db3a7e979e1e140b3c52f Mon Sep 17 00:00:00 2001 From: Imuli Date: Thu, 28 May 2015 11:45:10 -0400 Subject: [PATCH 3/4] read multiple files: tests --- data/alias.journal | 2 ++ data/business.journal | 4 ++++ data/personal.journal | 4 ++++ tests/cli/alias.journal | 1 + tests/cli/business.journal | 1 + tests/cli/multiple-files.test | 39 +++++++++++++++++++++++++++++++++++ tests/cli/personal.journal | 1 + 7 files changed, 52 insertions(+) create mode 100644 data/alias.journal create mode 100644 data/business.journal create mode 100644 data/personal.journal create mode 120000 tests/cli/alias.journal create mode 120000 tests/cli/business.journal create mode 100644 tests/cli/multiple-files.test create mode 120000 tests/cli/personal.journal diff --git a/data/alias.journal b/data/alias.journal new file mode 100644 index 000000000..43e453198 --- /dev/null +++ b/data/alias.journal @@ -0,0 +1,2 @@ +alias expenses = equity:draw:personal +alias assets = assets:personal diff --git a/data/business.journal b/data/business.journal new file mode 100644 index 000000000..39287f58c --- /dev/null +++ b/data/business.journal @@ -0,0 +1,4 @@ +2014/1/1 + expenses:office supplies $1 + assets:business checking + diff --git a/data/personal.journal b/data/personal.journal new file mode 100644 index 000000000..ca0892bd8 --- /dev/null +++ b/data/personal.journal @@ -0,0 +1,4 @@ +2014/1/2 + expenses:food $1 + assets:cash + diff --git a/tests/cli/alias.journal b/tests/cli/alias.journal new file mode 120000 index 000000000..1b7f296b0 --- /dev/null +++ b/tests/cli/alias.journal @@ -0,0 +1 @@ +../../data/alias.journal \ No newline at end of file diff --git a/tests/cli/business.journal b/tests/cli/business.journal new file mode 120000 index 000000000..d8ae20f52 --- /dev/null +++ b/tests/cli/business.journal @@ -0,0 +1 @@ +../../data/business.journal \ No newline at end of file diff --git a/tests/cli/multiple-files.test b/tests/cli/multiple-files.test new file mode 100644 index 000000000..d981f8075 --- /dev/null +++ b/tests/cli/multiple-files.test @@ -0,0 +1,39 @@ +# 1. all data files on the command line should be read +hledgerdev inc -f personal.journal -f business.journal +>>> +Income Statement + +Revenues: +-------------------- + 0 + +Expenses: + $2 expenses + $1 food + $1 office supplies +-------------------- + $2 + +Total: +-------------------- + $2 +>>>2 +>>>=0 + +# 2. aliases in files should only apply to later files +hledgerdev print -f personal.journal -f business.journal -f alias.journal -f personal.journal +>>> +2014/01/01 + expenses:office supplies $1 + assets:business checking $-1 + +2014/01/02 + expenses:food $1 + assets:cash $-1 + +2014/01/02 + equity:draw:personal:food $1 + assets:personal:cash $-1 + +>>>2 +>>>=0 diff --git a/tests/cli/personal.journal b/tests/cli/personal.journal new file mode 120000 index 000000000..9e94c310a --- /dev/null +++ b/tests/cli/personal.journal @@ -0,0 +1 @@ +../../data/personal.journal \ No newline at end of file From 0eb9f49a6e5ce2033f15a2ee0323f29bcb0524ef Mon Sep 17 00:00:00 2001 From: Imuli Date: Thu, 28 May 2015 12:42:22 -0400 Subject: [PATCH 4/4] doc: manual: read multiple files --- doc/manual.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/manual.md b/doc/manual.md index b8f935348..3fe348a2c 100644 --- a/doc/manual.md +++ b/doc/manual.md @@ -1060,6 +1060,12 @@ With the `--depth N` option, commands like [account](#account), [balance](#balan and [register](#register) will show only the uppermost accounts in the account tree, down to level N. Use this when you want a summary with less detail. +### Multiple files + +One may specify the `--file FILE` option multiple times. This is equivalent to +concatenating the files to standard input and passing `--file -`, except that +the add command functions normally and adds entries to the first specified file. + ## Queries One of hledger's strengths is being able to quickly report on precise subsets of your data.\