hledger 0.13 → 0.14
raw patch · 15 files changed
+1155/−1242 lines, 15 filesdep ~hledger-libdep ~processPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: hledger-lib, process
API changes (from Hackage documentation)
- Hledger.Cli.Commands: tests_Hledger_Commands :: Test
- Hledger.Cli.Register: tests_Register :: Test
- Hledger.Cli.Tests: journalWithAmounts :: [String] -> Journal
- Hledger.Cli.Tests: tests :: Test
+ Hledger.Cli: tests_Hledger_Cli :: Test
+ Hledger.Cli.Add: PostingState :: Journal -> (AccountName -> Bool) -> Bool -> Maybe [Posting] -> PostingState
+ Hledger.Cli.Add: data PostingState
+ Hledger.Cli.Add: psAccept :: PostingState -> AccountName -> Bool
+ Hledger.Cli.Add: psHistory :: PostingState -> Maybe [Posting]
+ Hledger.Cli.Add: psJournal :: PostingState -> Journal
+ Hledger.Cli.Add: psSuggestHistoricalAmount :: PostingState -> Bool
+ Hledger.Cli.Add: runInteractionDefault :: InputT IO a -> IO a
+ Hledger.Cli.Balance: tests_Hledger_Cli_Balance :: Test
+ Hledger.Cli.Convert: accountField :: CsvRules -> Maybe FieldPosition
+ Hledger.Cli.Convert: dateFormat :: CsvRules -> Maybe String
+ Hledger.Cli.Convert: effectiveDateField :: CsvRules -> Maybe FieldPosition
+ Hledger.Cli.Register: tests_Hledger_Cli_Register :: Test
+ Hledger.Cli.Utils: TestList :: [Test] -> Test
+ Hledger.Cli.Utils: data Test :: *
- Hledger.Cli.Add: getAndAddTransactions :: Journal -> [Opt] -> [String] -> Day -> InputT IO ()
+ Hledger.Cli.Add: getAndAddTransactions :: Journal -> [Opt] -> [String] -> Day -> IO ()
- Hledger.Cli.Add: getPostings :: JournalContext -> (AccountName -> Bool) -> Maybe [Posting] -> [Posting] -> InputT IO [Posting]
+ Hledger.Cli.Add: getPostings :: PostingState -> [Posting] -> IO [Posting]
- Hledger.Cli.Add: getTransaction :: Journal -> [Opt] -> [String] -> Day -> InputT IO (Transaction, Day)
+ Hledger.Cli.Add: getTransaction :: Journal -> [Opt] -> [String] -> Day -> IO (Transaction, Day)
- Hledger.Cli.Convert: CsvRules :: Maybe FieldPosition -> Maybe FieldPosition -> Maybe FieldPosition -> Maybe FieldPosition -> Maybe FieldPosition -> Maybe FieldPosition -> Maybe String -> AccountName -> [AccountRule] -> CsvRules
+ Hledger.Cli.Convert: CsvRules :: Maybe FieldPosition -> Maybe String -> Maybe FieldPosition -> Maybe FieldPosition -> Maybe FieldPosition -> Maybe FieldPosition -> Maybe FieldPosition -> Maybe String -> Maybe FieldPosition -> Maybe FieldPosition -> AccountName -> [AccountRule] -> CsvRules
- Hledger.Cli.Convert: normaliseDate :: String -> String
+ Hledger.Cli.Convert: normaliseDate :: Maybe String -> String -> String
Files
- Hledger/Cli.hs +909/−0
- Hledger/Cli/Add.hs +45/−23
- Hledger/Cli/Balance.hs +6/−4
- Hledger/Cli/Commands.hs +0/−40
- Hledger/Cli/Convert.hs +50/−9
- Hledger/Cli/Histogram.hs +7/−9
- Hledger/Cli/Main.hs +3/−9
- Hledger/Cli/Options.hs +31/−8
- Hledger/Cli/Print.hs +3/−5
- Hledger/Cli/Register.hs +47/−12
- Hledger/Cli/Stats.hs +5/−6
- Hledger/Cli/Tests.hs +35/−1102
- Hledger/Cli/Utils.hs +1/−0
- Hledger/Cli/Version.hs +1/−1
- hledger.cabal +12/−14
+ Hledger/Cli.hs view
@@ -0,0 +1,909 @@+{-| +Hledger.Cli re-exports the options, utilities and commands provided by the+hledger command-line program.+-}++module Hledger.Cli (+ module Hledger.Cli.Add,+ module Hledger.Cli.Balance,+ module Hledger.Cli.Convert,+ module Hledger.Cli.Histogram,+ module Hledger.Cli.Print,+ module Hledger.Cli.Register,+ module Hledger.Cli.Stats,+ module Hledger.Cli.Options,+ module Hledger.Cli.Utils,+ tests_Hledger_Cli+ )+where+import Hledger.Cli.Add+import Hledger.Cli.Balance+import Hledger.Cli.Convert+import Hledger.Cli.Histogram+import Hledger.Cli.Print+import Hledger.Cli.Register+import Hledger.Cli.Stats+import Hledger.Cli.Options+import Hledger.Cli.Utils+++import qualified Data.Map as Map+import System.Time (ClockTime(TOD))++import Hledger.Data -- including testing utils in Hledger.Data.Utils+import Hledger.Read+import Hledger.Read.JournalReader (someamount)+++-- | hledger and hledger-lib's unit tests aggregated from all modules+-- plus some more which are easier to define here for now.+-- tests_Hledger_Cli1 :: Test+tests_Hledger_Cli = TestList+ [+ tests_Hledger_Data+ ,tests_Hledger_Read+ -- ,tests_Hledger_Cli_Add+ -- ,tests_Hledger_Cli_Balance+ ,tests_Hledger_Cli_Convert+ -- ,tests_Hledger_Cli_Histogram+ ,tests_Hledger_Cli_Options+ -- ,tests_Hledger_Cli_Print+ ,tests_Hledger_Cli_Register+ -- ,tests_Hledger_Cli_Stats+++ ,"account directive" ~:+ let sameParse str1 str2 = do j1 <- readJournal Nothing str1 >>= either error' return+ j2 <- readJournal Nothing str2 >>= either error' return+ j1 `is` j2{filereadtime=filereadtime j1, files=files j1, jContext=jContext j1}+ in TestList+ [+ "account directive 1" ~: sameParse + "2008/12/07 One\n test:from $-1\n test:to $1\n"+ "!account test\n2008/12/07 One\n from $-1\n to $1\n"++ ,"account directive 2" ~: sameParse + "2008/12/07 One\n test:foo:from $-1\n test:foo:to $1\n"+ "!account test\n!account foo\n2008/12/07 One\n from $-1\n to $1\n"++ ,"account directive 3" ~: sameParse + "2008/12/07 One\n test:from $-1\n test:to $1\n"+ "!account test\n!account foo\n!end\n2008/12/07 One\n from $-1\n to $1\n"++ ,"account directive 4" ~: sameParse + ("2008/12/07 One\n alpha $-1\n beta $1\n" +++ "!account outer\n2008/12/07 Two\n aigh $-2\n bee $2\n" +++ "!account inner\n2008/12/07 Three\n gamma $-3\n delta $3\n" +++ "!end\n2008/12/07 Four\n why $-4\n zed $4\n" +++ "!end\n2008/12/07 Five\n foo $-5\n bar $5\n"+ )+ ("2008/12/07 One\n alpha $-1\n beta $1\n" +++ "2008/12/07 Two\n outer:aigh $-2\n outer:bee $2\n" +++ "2008/12/07 Three\n outer:inner:gamma $-3\n outer:inner:delta $3\n" +++ "2008/12/07 Four\n outer:why $-4\n outer:zed $4\n" +++ "2008/12/07 Five\n foo $-5\n bar $5\n"+ )+ ]++ ,"ledgerAccountNames" ~:+ ledgerAccountNames ledger7 `is`+ ["assets","assets:cash","assets:checking","assets:saving","equity","equity:opening balances",+ "expenses","expenses:food","expenses:food:dining","expenses:phone","expenses:vacation",+ "liabilities","liabilities:credit cards","liabilities:credit cards:discover"]++ ,"balance report tests" ~:+ let (opts,args) `gives` es = do + l <- samplejournalwithopts opts args+ t <- getCurrentLocalTime+ balanceReportAsText opts (balanceReport opts (optsToFilterSpec opts args t) l) `is` unlines es+ in TestList+ [++ "balance report with no args" ~:+ ([], []) `gives`+ [" $-1 assets"+ ," $1 bank:saving"+ ," $-2 cash"+ ," $2 expenses"+ ," $1 food"+ ," $1 supplies"+ ," $-2 income"+ ," $-1 gifts"+ ," $-1 salary"+ ," $1 liabilities:debts"+ ,"--------------------"+ ," 0"+ ]++ ,"balance report can be limited with --depth" ~:+ ([Depth "1"], []) `gives`+ [" $-1 assets"+ ," $2 expenses"+ ," $-2 income"+ ," $1 liabilities"+ ,"--------------------"+ ," 0"+ ]+ + ,"balance report with account pattern o" ~:+ ([SubTotal], ["o"]) `gives`+ [" $1 expenses:food"+ ," $-2 income"+ ," $-1 gifts"+ ," $-1 salary"+ ,"--------------------"+ ," $-1"+ ]++ ,"balance report with account pattern o and --depth 1" ~:+ ([Depth "1"], ["o"]) `gives`+ [" $1 expenses"+ ," $-2 income"+ ,"--------------------"+ ," $-1"+ ]++ ,"balance report with account pattern a" ~:+ ([], ["a"]) `gives`+ [" $-1 assets"+ ," $1 bank:saving"+ ," $-2 cash"+ ," $-1 income:salary"+ ," $1 liabilities:debts"+ ,"--------------------"+ ," $-1"+ ]++ ,"balance report with account pattern e" ~:+ ([], ["e"]) `gives`+ [" $-1 assets"+ ," $1 bank:saving"+ ," $-2 cash"+ ," $2 expenses"+ ," $1 food"+ ," $1 supplies"+ ," $-2 income"+ ," $-1 gifts"+ ," $-1 salary"+ ," $1 liabilities:debts"+ ,"--------------------"+ ," 0"+ ]++ ,"balance report with unmatched parent of two matched subaccounts" ~: + ([], ["cash","saving"]) `gives`+ [" $-1 assets"+ ," $1 bank:saving"+ ," $-2 cash"+ ,"--------------------"+ ," $-1"+ ]++ ,"balance report with multi-part account name" ~: + ([], ["expenses:food"]) `gives`+ [" $1 expenses:food"+ ,"--------------------"+ ," $1"+ ]++ ,"balance report with negative account pattern" ~:+ ([], ["not:assets"]) `gives`+ [" $2 expenses"+ ," $1 food"+ ," $1 supplies"+ ," $-2 income"+ ," $-1 gifts"+ ," $-1 salary"+ ," $1 liabilities:debts"+ ,"--------------------"+ ," $1"+ ]++ ,"balance report negative account pattern always matches full name" ~: + ([], ["not:e"]) `gives`+ ["--------------------"+ ," 0"+ ]++ ,"balance report negative patterns affect totals" ~: + ([], ["expenses","not:food"]) `gives`+ [" $1 expenses:supplies"+ ,"--------------------"+ ," $1"+ ]++ ,"balance report with -E shows zero-balance accounts" ~:+ ([SubTotal,Empty], ["assets"]) `gives`+ [" $-1 assets"+ ," $1 bank"+ ," 0 checking"+ ," $1 saving"+ ," $-2 cash"+ ,"--------------------"+ ," $-1"+ ]++ ,"balance report with cost basis" ~: do+ j <- (readJournal Nothing $ unlines+ [""+ ,"2008/1/1 test "+ ," a:b 10h @ $50"+ ," c:d "+ ]) >>= either error' return+ let j' = journalCanonicaliseAmounts $ journalConvertAmountsToCost j -- enable cost basis adjustment+ balanceReportAsText [] (balanceReport [] nullfilterspec j') `is`+ unlines+ [" $500 a:b"+ ," $-500 c:d"+ ,"--------------------"+ ," 0"+ ]++ ,"balance report elides zero-balance root account(s)" ~: do+ l <- readJournalWithOpts []+ (unlines+ ["2008/1/1 one"+ ," test:a 1"+ ," test:b"+ ])+ balanceReportAsText [] (balanceReport [] nullfilterspec l) `is`+ unlines+ [" 1 test:a"+ ," -1 test:b"+ ,"--------------------"+ ," 0"+ ]++ ]++ ,"journalCanonicaliseAmounts" ~:+ "use the greatest precision" ~:+ (map precision $ journalAmountAndPriceCommodities $ journalCanonicaliseAmounts $ journalWithAmounts ["1","2.00"]) `is` [2,2]++ ,"commodities" ~:+ Map.elems (commodities ledger7) `is` [Commodity {symbol="$", side=L, spaced=False, decimalpoint='.', precision=2, separator=',', separatorpositions=[]}]++ -- don't know what this should do+ -- ,"elideAccountName" ~: do+ -- (elideAccountName 50 "aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa"+ -- `is` "aa:aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa")+ -- (elideAccountName 20 "aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa"+ -- `is` "aa:aa:aaaaaaaaaaaaaa")++ ,"default year" ~: do+ rl <- readJournal Nothing defaultyear_journal_str >>= either error' return+ tdate (head $ jtxns rl) `is` fromGregorian 2009 1 1+ return ()++ ,"print report tests" ~: TestList+ [++ "print expenses" ~:+ do + let args = ["expenses"]+ l <- samplejournalwithopts [] args+ t <- getCurrentLocalTime+ showTransactions (optsToFilterSpec [] args t) l `is` unlines+ ["2008/06/03 * eat & shop"+ ," expenses:food $1"+ ," expenses:supplies $1"+ ," assets:cash $-2"+ ,""+ ]++ , "print report with depth arg" ~:+ do + l <- samplejournal+ t <- getCurrentLocalTime+ showTransactions (optsToFilterSpec [Depth "2"] [] t) l `is` unlines+ ["2008/01/01 income"+ ," income:salary $-1"+ ,""+ ,"2008/06/01 gift"+ ," income:gifts $-1"+ ,""+ ,"2008/06/03 * eat & shop"+ ," expenses:food $1"+ ," expenses:supplies $1"+ ," assets:cash $-2"+ ,""+ ,"2008/12/31 * pay off"+ ," liabilities:debts $1"+ ,""+ ]++ ]++ ,"register report tests" ~:+ let registerdates = filter (not . null) . map (strip . take 10) . lines+ in+ TestList+ [++ "register report with no args" ~:+ do + l <- samplejournal+ (registerReportAsText [] $ registerReport [] (optsToFilterSpec [] [] t1) l) `is` unlines+ ["2008/01/01 income assets:bank:checking $1 $1"+ ," income:salary $-1 0"+ ,"2008/06/01 gift assets:bank:checking $1 $1"+ ," income:gifts $-1 0"+ ,"2008/06/02 save assets:bank:saving $1 $1"+ ," assets:bank:checking $-1 0"+ ,"2008/06/03 eat & shop expenses:food $1 $1"+ ," expenses:supplies $1 $2"+ ," assets:cash $-2 0"+ ,"2008/12/31 pay off liabilities:debts $1 $1"+ ," assets:bank:checking $-1 0"+ ]++ ,"register report with cleared option" ~:+ do + let opts = [Cleared]+ l <- readJournalWithOpts opts sample_journal_str+ (registerReportAsText opts $ registerReport opts (optsToFilterSpec opts [] t1) l) `is` unlines+ ["2008/06/03 eat & shop expenses:food $1 $1"+ ," expenses:supplies $1 $2"+ ," assets:cash $-2 0"+ ,"2008/12/31 pay off liabilities:debts $1 $1"+ ," assets:bank:checking $-1 0"+ ]++ ,"register report with uncleared option" ~:+ do + let opts = [UnCleared]+ l <- readJournalWithOpts opts sample_journal_str+ (registerReportAsText opts $ registerReport opts (optsToFilterSpec opts [] t1) l) `is` unlines+ ["2008/01/01 income assets:bank:checking $1 $1"+ ," income:salary $-1 0"+ ,"2008/06/01 gift assets:bank:checking $1 $1"+ ," income:gifts $-1 0"+ ,"2008/06/02 save assets:bank:saving $1 $1"+ ," assets:bank:checking $-1 0"+ ]++ ,"register report sorts by date" ~:+ do + l <- readJournalWithOpts [] $ unlines+ ["2008/02/02 a"+ ," b 1"+ ," c"+ ,""+ ,"2008/01/01 d"+ ," e 1"+ ," f"+ ]+ registerdates (registerReportAsText [] $ registerReport [] (optsToFilterSpec [] [] t1) l) `is` ["2008/01/01","2008/02/02"]++ ,"register report with account pattern" ~:+ do+ l <- samplejournal+ (registerReportAsText [] $ registerReport [] (optsToFilterSpec [] ["cash"] t1) l) `is` unlines+ ["2008/06/03 eat & shop assets:cash $-2 $-2"+ ]++ ,"register report with account pattern, case insensitive" ~:+ do + l <- samplejournal+ (registerReportAsText [] $ registerReport [] (optsToFilterSpec [] ["cAsH"] t1) l) `is` unlines+ ["2008/06/03 eat & shop assets:cash $-2 $-2"+ ]++ ,"register report with display expression" ~:+ do + l <- samplejournal+ let gives displayexpr = + (registerdates (registerReportAsText opts $ registerReport opts (optsToFilterSpec opts [] t1) l) `is`)+ where opts = [Display displayexpr]+ "d<[2008/6/2]" `gives` ["2008/01/01","2008/06/01"]+ "d<=[2008/6/2]" `gives` ["2008/01/01","2008/06/01","2008/06/02"]+ "d=[2008/6/2]" `gives` ["2008/06/02"]+ "d>=[2008/6/2]" `gives` ["2008/06/02","2008/06/03","2008/12/31"]+ "d>[2008/6/2]" `gives` ["2008/06/03","2008/12/31"]++ ,"register report with period expression" ~:+ do + l <- samplejournal+ let periodexpr `gives` dates = do+ l' <- samplejournalwithopts opts []+ registerdates (registerReportAsText opts $ registerReport opts (optsToFilterSpec opts [] t1) l') `is` dates+ where opts = [Period periodexpr]+ "" `gives` ["2008/01/01","2008/06/01","2008/06/02","2008/06/03","2008/12/31"]+ "2008" `gives` ["2008/01/01","2008/06/01","2008/06/02","2008/06/03","2008/12/31"]+ "2007" `gives` []+ "june" `gives` ["2008/06/01","2008/06/02","2008/06/03"]+ "monthly" `gives` ["2008/01/01","2008/06/01","2008/12/01"]+ "quarterly" `gives` ["2008/01/01","2008/04/01","2008/10/01"]+ let opts = [Period "yearly"]+ (registerReportAsText opts $ registerReport opts (optsToFilterSpec opts [] t1) l) `is` unlines+ ["2008/01/01 - 2008/12/31 assets:bank:saving $1 $1"+ ," assets:cash $-2 $-1"+ ," expenses:food $1 0"+ ," expenses:supplies $1 $1"+ ," income:gifts $-1 0"+ ," income:salary $-1 $-1"+ ," liabilities:debts $1 0"+ ]+ let opts = [Period "quarterly"]+ registerdates (registerReportAsText opts $ registerReport opts (optsToFilterSpec opts [] t1) l) `is` ["2008/01/01","2008/04/01","2008/10/01"]+ let opts = [Period "quarterly",Empty]+ registerdates (registerReportAsText opts $ registerReport opts (optsToFilterSpec opts [] t1) l) `is` ["2008/01/01","2008/04/01","2008/07/01","2008/10/01"]++ ]++ , "register report with depth arg" ~:+ do + l <- samplejournal+ let opts = [Depth "2"]+ (registerReportAsText opts $ registerReport opts (optsToFilterSpec opts [] t1) l) `is` unlines+ ["2008/01/01 income assets:bank $1 $1"+ ," income:salary $-1 0"+ ,"2008/06/01 gift assets:bank $1 $1"+ ," income:gifts $-1 0"+ ,"2008/06/02 save assets:bank $1 $1"+ ," assets:bank $-1 0"+ ,"2008/06/03 eat & shop expenses:food $1 $1"+ ," expenses:supplies $1 $2"+ ," assets:cash $-2 0"+ ,"2008/12/31 pay off liabilities:debts $1 $1"+ ," assets:bank $-1 0"+ ]++ ,"show dollars" ~: show (dollars 1) ~?= "$1.00"++ ,"show hours" ~: show (hours 1) ~?= "1.0h"++ ,"unicode in balance layout" ~: do+ l <- readJournalWithOpts []+ "2009/01/01 * медвежья шкура\n расходы:покупки 100\n актив:наличные\n"+ balanceReportAsText [] (balanceReport [] (optsToFilterSpec [] [] t1) l) `is` unlines+ [" -100 актив:наличные"+ ," 100 расходы:покупки"+ ,"--------------------"+ ," 0"+ ]++ ,"unicode in register layout" ~: do+ l <- readJournalWithOpts []+ "2009/01/01 * медвежья шкура\n расходы:покупки 100\n актив:наличные\n"+ (registerReportAsText [] $ registerReport [] (optsToFilterSpec [] [] t1) l) `is` unlines+ ["2009/01/01 медвежья шкура расходы:покупки 100 100"+ ," актив:наличные -100 0"]++ ,"subAccounts" ~: do+ l <- liftM (journalToLedger nullfilterspec) samplejournal+ let a = ledgerAccount l "assets"+ map aname (ledgerSubAccounts l a) `is` ["assets:bank","assets:cash"]++ ]++ +-- fixtures/test data++t1 = LocalTime date1 midday where date1 = parsedate "2008/11/26"++samplejournal = readJournalWithOpts [] sample_journal_str+samplejournalwithopts opts _ = readJournalWithOpts opts sample_journal_str++sample_journal_str = unlines+ ["; A sample journal file."+ ,";"+ ,"; Sets up this account tree:"+ ,"; assets"+ ,"; bank"+ ,"; checking"+ ,"; saving"+ ,"; cash"+ ,"; expenses"+ ,"; food"+ ,"; supplies"+ ,"; income"+ ,"; gifts"+ ,"; salary"+ ,"; liabilities"+ ,"; debts"+ ,""+ ,"2008/01/01 income"+ ," assets:bank:checking $1"+ ," income:salary"+ ,""+ ,"2008/06/01 gift"+ ," assets:bank:checking $1"+ ," income:gifts"+ ,""+ ,"2008/06/02 save"+ ," assets:bank:saving $1"+ ," assets:bank:checking"+ ,""+ ,"2008/06/03 * eat & shop"+ ," expenses:food $1"+ ," expenses:supplies $1"+ ," assets:cash"+ ,""+ ,"2008/12/31 * pay off"+ ," liabilities:debts $1"+ ," assets:bank:checking"+ ,""+ ,""+ ,";final comment"+ ]++defaultyear_journal_str = unlines+ ["Y2009"+ ,""+ ,"01/01 A"+ ," a $1"+ ," b"+ ]++-- write_sample_journal = writeFile "sample.journal" sample_journal_str++-- entry2_str = unlines+-- ["2007/01/27 * joes diner"+-- ," expenses:food:dining $10.00"+-- ," expenses:gifts $10.00"+-- ," assets:checking $-20.00"+-- ,""+-- ]++-- entry3_str = unlines+-- ["2007/01/01 * opening balance"+-- ," assets:cash $4.82"+-- ," equity:opening balances"+-- ,""+-- ,"2007/01/01 * opening balance"+-- ," assets:cash $4.82"+-- ," equity:opening balances"+-- ,""+-- ,"2007/01/28 coopportunity"+-- ," expenses:food:groceries $47.18"+-- ," assets:checking"+-- ,""+-- ]++-- periodic_entry1_str = unlines+-- ["~ monthly from 2007/2/2"+-- ," assets:saving $200.00"+-- ," assets:checking"+-- ,""+-- ]++-- periodic_entry2_str = unlines+-- ["~ monthly from 2007/2/2"+-- ," assets:saving $200.00 ;auto savings"+-- ," assets:checking"+-- ,""+-- ]++-- periodic_entry3_str = unlines+-- ["~ monthly from 2007/01/01"+-- ," assets:cash $4.82"+-- ," equity:opening balances"+-- ,""+-- ,"~ monthly from 2007/01/01"+-- ," assets:cash $4.82"+-- ," equity:opening balances"+-- ,""+-- ]++-- journal1_str = unlines+-- [""+-- ,"2007/01/27 * joes diner"+-- ," expenses:food:dining $10.00"+-- ," expenses:gifts $10.00"+-- ," assets:checking $-20.00"+-- ,""+-- ,""+-- ,"2007/01/28 coopportunity"+-- ," expenses:food:groceries $47.18"+-- ," assets:checking $-47.18"+-- ,""+-- ,""+-- ]++-- journal2_str = unlines+-- [";comment"+-- ,"2007/01/27 * joes diner"+-- ," expenses:food:dining $10.00"+-- ," assets:checking $-47.18"+-- ,""+-- ]++-- journal3_str = unlines+-- ["2007/01/27 * joes diner"+-- ," expenses:food:dining $10.00"+-- ,";intra-entry comment"+-- ," assets:checking $-47.18"+-- ,""+-- ]++-- journal4_str = unlines+-- ["!include \"somefile\""+-- ,"2007/01/27 * joes diner"+-- ," expenses:food:dining $10.00"+-- ," assets:checking $-47.18"+-- ,""+-- ]++-- journal5_str = ""++-- journal6_str = unlines+-- ["~ monthly from 2007/1/21"+-- ," expenses:entertainment $16.23 ;netflix"+-- ," assets:checking"+-- ,""+-- ,"; 2007/01/01 * opening balance"+-- ,"; assets:saving $200.04"+-- ,"; equity:opening balances "+-- ,""+-- ]++-- journal7_str = unlines+-- ["2007/01/01 * opening balance"+-- ," assets:cash $4.82"+-- ," equity:opening balances "+-- ,""+-- ,"2007/01/01 * opening balance"+-- ," income:interest $-4.82"+-- ," equity:opening balances "+-- ,""+-- ,"2007/01/02 * ayres suites"+-- ," expenses:vacation $179.92"+-- ," assets:checking "+-- ,""+-- ,"2007/01/02 * auto transfer to savings"+-- ," assets:saving $200.00"+-- ," assets:checking "+-- ,""+-- ,"2007/01/03 * poquito mas"+-- ," expenses:food:dining $4.82"+-- ," assets:cash "+-- ,""+-- ,"2007/01/03 * verizon"+-- ," expenses:phone $95.11"+-- ," assets:checking "+-- ,""+-- ,"2007/01/03 * discover"+-- ," liabilities:credit cards:discover $80.00"+-- ," assets:checking "+-- ,""+-- ,"2007/01/04 * blue cross"+-- ," expenses:health:insurance $90.00"+-- ," assets:checking "+-- ,""+-- ,"2007/01/05 * village market liquor"+-- ," expenses:food:dining $6.48"+-- ," assets:checking "+-- ,""+-- ]++journal7 = Journal+ [] + [] + [+ txnTieKnot $ Transaction {+ tdate=parsedate "2007/01/01",+ teffectivedate=Nothing,+ tstatus=False,+ tcode="*",+ tdescription="opening balance",+ tcomment="",+ tmetadata=[],+ tpostings=[+ Posting {+ pstatus=False,+ paccount="assets:cash",+ pamount=(Mixed [dollars 4.82]),+ pcomment="",+ ptype=RegularPosting,+ pmetadata=[],+ ptransaction=Nothing+ },+ Posting {+ pstatus=False,+ paccount="equity:opening balances",+ pamount=(Mixed [dollars (-4.82)]),+ pcomment="",+ ptype=RegularPosting,+ pmetadata=[],+ ptransaction=Nothing+ }+ ],+ tpreceding_comment_lines=""+ }+ ,+ txnTieKnot $ Transaction {+ tdate=parsedate "2007/02/01",+ teffectivedate=Nothing,+ tstatus=False,+ tcode="*",+ tdescription="ayres suites",+ tcomment="",+ tmetadata=[],+ tpostings=[+ Posting {+ pstatus=False,+ paccount="expenses:vacation",+ pamount=(Mixed [dollars 179.92]),+ pcomment="",+ ptype=RegularPosting,+ pmetadata=[],+ ptransaction=Nothing+ },+ Posting {+ pstatus=False,+ paccount="assets:checking",+ pamount=(Mixed [dollars (-179.92)]),+ pcomment="",+ ptype=RegularPosting,+ pmetadata=[],+ ptransaction=Nothing+ }+ ],+ tpreceding_comment_lines=""+ }+ ,+ txnTieKnot $ Transaction {+ tdate=parsedate "2007/01/02",+ teffectivedate=Nothing,+ tstatus=False,+ tcode="*",+ tdescription="auto transfer to savings",+ tcomment="",+ tmetadata=[],+ tpostings=[+ Posting {+ pstatus=False,+ paccount="assets:saving",+ pamount=(Mixed [dollars 200]),+ pcomment="",+ ptype=RegularPosting,+ pmetadata=[],+ ptransaction=Nothing+ },+ Posting {+ pstatus=False,+ paccount="assets:checking",+ pamount=(Mixed [dollars (-200)]),+ pcomment="",+ ptype=RegularPosting,+ pmetadata=[],+ ptransaction=Nothing+ }+ ],+ tpreceding_comment_lines=""+ }+ ,+ txnTieKnot $ Transaction {+ tdate=parsedate "2007/01/03",+ teffectivedate=Nothing,+ tstatus=False,+ tcode="*",+ tdescription="poquito mas",+ tcomment="",+ tmetadata=[],+ tpostings=[+ Posting {+ pstatus=False,+ paccount="expenses:food:dining",+ pamount=(Mixed [dollars 4.82]),+ pcomment="",+ ptype=RegularPosting,+ pmetadata=[],+ ptransaction=Nothing+ },+ Posting {+ pstatus=False,+ paccount="assets:cash",+ pamount=(Mixed [dollars (-4.82)]),+ pcomment="",+ ptype=RegularPosting,+ pmetadata=[],+ ptransaction=Nothing+ }+ ],+ tpreceding_comment_lines=""+ }+ ,+ txnTieKnot $ Transaction {+ tdate=parsedate "2007/01/03",+ teffectivedate=Nothing,+ tstatus=False,+ tcode="*",+ tdescription="verizon",+ tcomment="",+ tmetadata=[],+ tpostings=[+ Posting {+ pstatus=False,+ paccount="expenses:phone",+ pamount=(Mixed [dollars 95.11]),+ pcomment="",+ ptype=RegularPosting,+ pmetadata=[],+ ptransaction=Nothing+ },+ Posting {+ pstatus=False,+ paccount="assets:checking",+ pamount=(Mixed [dollars (-95.11)]),+ pcomment="",+ ptype=RegularPosting,+ pmetadata=[],+ ptransaction=Nothing+ }+ ],+ tpreceding_comment_lines=""+ }+ ,+ txnTieKnot $ Transaction {+ tdate=parsedate "2007/01/03",+ teffectivedate=Nothing,+ tstatus=False,+ tcode="*",+ tdescription="discover",+ tcomment="",+ tmetadata=[],+ tpostings=[+ Posting {+ pstatus=False,+ paccount="liabilities:credit cards:discover",+ pamount=(Mixed [dollars 80]),+ pcomment="",+ ptype=RegularPosting,+ pmetadata=[],+ ptransaction=Nothing+ },+ Posting {+ pstatus=False,+ paccount="assets:checking",+ pamount=(Mixed [dollars (-80)]),+ pcomment="",+ ptype=RegularPosting,+ pmetadata=[],+ ptransaction=Nothing+ }+ ],+ tpreceding_comment_lines=""+ }+ ]+ []+ []+ ""+ nullctx+ []+ (TOD 0 0)++ledger7 = journalToLedger nullfilterspec journal7++-- journal8_str = unlines+-- ["2008/1/1 test "+-- ," a:b 10h @ $40"+-- ," c:d "+-- ,""+-- ]++-- timelogentry1_str = "i 2007/03/11 16:19:00 hledger\n"+-- timelogentry1 = TimeLogEntry In (parsedatetime "2007/03/11 16:19:00") "hledger"++-- timelogentry2_str = "o 2007/03/11 16:30:00\n"+-- timelogentry2 = TimeLogEntry Out (parsedatetime "2007/03/11 16:30:00") ""++-- a1 = Mixed [(hours 1){price=Just $ Mixed [Amount (comm "$") 10 Nothing]}]+-- a2 = Mixed [(hours 2){price=Just $ Mixed [Amount (comm "EUR") 10 Nothing]}]+-- a3 = Mixed $ amounts a1 ++ amounts a2++journalWithAmounts :: [String] -> Journal+journalWithAmounts as =+ Journal+ []+ []+ [t | a <- as, let t = nulltransaction{tdescription=a,tpostings=[nullposting{pamount=parse a,ptransaction=Just t}]}]+ []+ []+ ""+ nullctx+ []+ (TOD 0 0)+ where parse = fromparse . parseWithCtx nullctx someamount+
Hledger/Cli/Add.hs view
@@ -3,6 +3,10 @@ A history-aware add command to help with data entry. +Note: this might not be sensible, but add has some aspirations of being+both user-fiendly and pipeable/scriptable and for this reason+informational messages are mostly written to stderr rather than stdout.+ -} module Hledger.Cli.Add@@ -11,13 +15,10 @@ import Hledger.Read.JournalReader (someamount) import Hledger.Cli.Options import Hledger.Cli.Register (registerReport, registerReportAsText)-#if __GLASGOW_HASKELL__ <= 610-import Prelude hiding (putStr, putStrLn, getLine, appendFile)-import System.IO.UTF8-import System.IO ( stderr )-#else+import Prelude hiding (putStr, putStrLn, appendFile)+import Hledger.Data.UTF8 (putStr, putStrLn, appendFile)+ import System.IO ( stderr, hPutStrLn, hPutStr )-#endif import System.IO.Error import Text.ParserCombinators.Parsec import Hledger.Cli.Utils (readJournalWithOpts)@@ -30,6 +31,14 @@ import Safe (headMay) import Control.Exception (throw) +{- | Information used as the basis for suggested account names, amounts,+ etc in add prompt+-}+data PostingState = PostingState {+ psJournal :: Journal,+ psAccept :: AccountName -> Bool,+ psSuggestHistoricalAmount :: Bool,+ psHistory :: Maybe [Posting]} -- | Read transactions from the terminal, prompting for each field, -- and append them to the journal file. If the journal came from stdin, this@@ -43,7 +52,7 @@ ++"To complete a transaction, enter . when prompted for an account.\n" ++"To quit, press control-d or control-c." today <- getCurrentDay- runInteraction j (getAndAddTransactions j opts args today)+ getAndAddTransactions j opts args today `catch` (\e -> unless (isEOFError e) $ ioError e) where f = journalFilePath j @@ -51,22 +60,22 @@ -- validating, displaying and appending them to the journal file, until -- end of input (then raise an EOF exception). Any command-line arguments -- are used as the first transaction's description.-getAndAddTransactions :: Journal -> [Opt] -> [String] -> Day -> InputT IO ()+getAndAddTransactions :: Journal -> [Opt] -> [String] -> Day -> IO () getAndAddTransactions j opts args defaultDate = do (t, d) <- getTransaction j opts args defaultDate- j <- liftIO $ journalAddTransaction j opts t+ j <- journalAddTransaction j opts t getAndAddTransactions j opts args d -- | Read a transaction from the command line, with history-aware prompting. getTransaction :: Journal -> [Opt] -> [String] -> Day- -> InputT IO (Transaction,Day)+ -> IO (Transaction,Day) getTransaction j opts args defaultDate = do- today <- liftIO getCurrentDay- datestr <- askFor "date" + today <- getCurrentDay+ datestr <- runInteractionDefault $ askFor "date" (Just $ showDate defaultDate) (Just $ \s -> null s || isRight (parse (smartdate >> many spacenonewline >> eof) "" $ lowercase s))- description <- askFor "description" (Just "") Nothing+ description <- runInteractionDefault $ askFor "description" (Just "") Nothing let historymatches = transactionsSimilarTo j args description bestmatch | null historymatches = Nothing | otherwise = Just $ snd $ head historymatches@@ -78,7 +87,7 @@ else True where (ant,_,_,_) = groupPostings $ journalPostings j getpostingsandvalidate = do- ps <- getPostings (jContext j) accept bestmatchpostings []+ ps <- getPostings (PostingState j accept True bestmatchpostings) [] let t = nulltransaction{tdate=date ,tstatus=False ,tdescription=description@@ -97,14 +106,14 @@ -- fragile -- | Read postings from the command line until . is entered, using any -- provided historical postings and the journal context to guess defaults.-getPostings :: JournalContext -> (AccountName -> Bool) -> Maybe [Posting] -> [Posting] -> InputT IO [Posting]-getPostings ctx accept historicalps enteredps = do+getPostings :: PostingState -> [Posting] -> IO [Posting]+getPostings st enteredps = do let bestmatch | isNothing historicalps = Nothing | n <= length ps = Just $ ps !! (n-1) | otherwise = Nothing where Just ps = historicalps defaultaccount = maybe Nothing (Just . showacctname) bestmatch- account <- askFor (printf "account %d" n) defaultaccount (Just accept)+ account <- runInteraction j $ askFor (printf "account %d" n) defaultaccount (Just accept) if account=="." then return enteredps else do@@ -114,17 +123,18 @@ | n <= length ps = Just $ ps !! (n-1) | otherwise = Nothing where Just ps = historicalps'- defaultamountstr | isJust bestmatch' = Just historicalamountstr+ defaultamountstr | isJust bestmatch' && suggesthistorical = Just historicalamountstr | n > 1 = Just balancingamountstr | otherwise = Nothing where- historicalamountstr = showMixedAmountWithPrecision maxprecision $ pamount $ fromJust bestmatch'- balancingamountstr = showMixedAmountWithPrecision maxprecision $ negate $ sumMixedAmountsPreservingHighestPrecision $ map pamount enteredrealps- amountstr <- askFor (printf "amount %d" n) defaultamountstr validateamount+ -- force a decimal point in the output in case there's a+ -- digit group separator that would be mistaken for one+ historicalamountstr = showMixedAmountWithPrecision maxprecisionwithpoint $ pamount $ fromJust bestmatch'+ balancingamountstr = showMixedAmountWithPrecision maxprecisionwithpoint $ negate $ sumMixedAmountsPreservingHighestPrecision $ map pamount enteredrealps+ amountstr <- runInteractionDefault $ askFor (printf "amount %d" n) defaultamountstr validateamount let amount = fromparse $ runParser (someamount <|> return missingamt) ctx "" amountstr amount' = fromparse $ runParser (someamount <|> return missingamt) nullctx "" amountstr defaultamtused = Just (showMixedAmount amount) == defaultamountstr- historicalps'' = if defaultamtused then historicalps' else Nothing commodityadded | c == cwithnodef = Nothing | otherwise = c where c = maybemixedamountcommodity amount@@ -133,10 +143,18 @@ p = nullposting{paccount=stripbrackets account, pamount=amount, ptype=postingtype account}+ st' = if defaultamtused then st+ else st{psHistory = historicalps',+ psSuggestHistoricalAmount = False} when (isJust commodityadded) $ liftIO $ hPutStrLn stderr $ printf "using default commodity (%s)" (symbol $ fromJust commodityadded)- getPostings ctx accept historicalps'' $ enteredps ++ [p]+ getPostings st' (enteredps ++ [p]) where+ j = psJournal st+ historicalps = psHistory st+ ctx = jContext j+ accept = psAccept st+ suggesthistorical = psSuggestHistoricalAmount st n = length enteredps + 1 enteredrealps = filter isReal enteredps showacctname p = showAccountName Nothing (ptype p) $ paccount p@@ -234,6 +252,10 @@ runInteraction j m = do let cc = completionCache j runInputT (setComplete (accountCompletion cc) defaultSettings) m++runInteractionDefault :: InputT IO a -> IO a+runInteractionDefault m = do+ runInputT (setComplete noCompletion defaultSettings) m -- A precomputed list of all accounts previously entered into the journal. type CompletionCache = [AccountName]
Hledger/Cli/Balance.hs view
@@ -101,6 +101,7 @@ ,balance ,balanceReport ,balanceReportAsText+ ,tests_Hledger_Cli_Balance -- ,tests_Balance ) where import Hledger.Data.Utils@@ -110,10 +111,8 @@ import Hledger.Data.Posting import Hledger.Data.Ledger import Hledger.Cli.Options-#if __GLASGOW_HASKELL__ <= 610-import Prelude hiding ( putStr )-import System.IO.UTF8-#endif+import Prelude hiding (putStr)+import Hledger.Data.UTF8 (putStr) -- | A balance report is a chart of accounts with balances, and their grand total.@@ -211,3 +210,6 @@ isInterestingTree = treeany (isInteresting opts l . aname) subtrees = map (fromJust . ledgerAccountTreeAt l) $ ledgerSubAccounts l $ ledgerAccount l a +tests_Hledger_Cli_Balance = TestList+ [+ ]
− Hledger/Cli/Commands.hs
@@ -1,40 +0,0 @@-{-# LANGUAGE CPP #-}-{-| --The Commands package defines all the commands offered by the hledger-application, like \"register\" and \"balance\". This module exports all-the commands; you can also import individual modules if you prefer.---}--module Hledger.Cli.Commands (- module Hledger.Cli.Add,- module Hledger.Cli.Balance,- module Hledger.Cli.Convert,- module Hledger.Cli.Histogram,- module Hledger.Cli.Print,- module Hledger.Cli.Register,- module Hledger.Cli.Stats,- tests_Hledger_Commands- )-where-import Hledger.Cli.Add-import Hledger.Cli.Balance-import Hledger.Cli.Convert-import Hledger.Cli.Histogram-import Hledger.Cli.Print-import Hledger.Cli.Register-import Hledger.Cli.Stats-import Test.HUnit (Test(TestList))---tests_Hledger_Commands = TestList- [--- Hledger.Cli.Add.tests_Add--- ,Hledger.Cli.Balance.tests_Balance- Hledger.Cli.Convert.tests_Convert--- ,Hledger.Cli.Histogram.tests_Histogram--- ,Hledger.Cli.Print.tests_Print- ,Hledger.Cli.Register.tests_Register--- ,Hledger.Cli.Stats.tests_Stats- ]
Hledger/Cli/Convert.hs view
@@ -10,7 +10,7 @@ import Hledger.Data.Utils (strip, spacenonewline, restofline, parseWithCtx, assertParse, assertParseEqual, error') import Hledger.Read.JournalReader (someamount,ledgeraccountname) import Hledger.Data.Journal (nullctx)-import Hledger.Data.Amount (nullmixedamt)+import Hledger.Data.Amount (nullmixedamt, costOfMixedAmount) import Safe (atDef, maximumDef) import System.IO (stderr) import Text.CSV (parseCSVFromFile, printCSV)@@ -35,24 +35,30 @@ -} data CsvRules = CsvRules { dateField :: Maybe FieldPosition,+ dateFormat :: Maybe String, statusField :: Maybe FieldPosition, codeField :: Maybe FieldPosition, descriptionField :: Maybe FieldPosition, amountField :: Maybe FieldPosition, currencyField :: Maybe FieldPosition, baseCurrency :: Maybe String,+ accountField :: Maybe FieldPosition,+ effectiveDateField :: Maybe FieldPosition, baseAccount :: AccountName, accountRules :: [AccountRule] } deriving (Show, Eq) nullrules = CsvRules { dateField=Nothing,+ dateFormat=Nothing, statusField=Nothing, codeField=Nothing, descriptionField=Nothing, amountField=Nothing, currencyField=Nothing, baseCurrency=Nothing,+ accountField=Nothing,+ effectiveDateField=Nothing, baseAccount="unknown", accountRules=[] }@@ -109,6 +115,8 @@ ,descriptionField r ,amountField r ,currencyField r+ ,accountField r+ ,effectiveDateField r ] rulesFileFor :: FilePath -> FilePath@@ -165,11 +173,14 @@ definitions = do choice' [ datefield+ ,dateformat ,statusfield ,codefield ,descriptionfield ,amountfield ,currencyfield+ ,accountfield+ ,effectivedatefield ,basecurrency ,baseaccount ,commentline@@ -183,6 +194,20 @@ r <- getState setState r{dateField=readMay v} +effectivedatefield = do+ string "effective-date-field"+ many1 spacenonewline+ v <- restofline+ r <- getState+ setState r{effectiveDateField=readMay v}++dateformat = do+ string "date-format"+ many1 spacenonewline+ v <- restofline+ r <- getState+ setState r{dateFormat=Just v}+ codefield = do string "code-field" many1 spacenonewline@@ -218,6 +243,14 @@ r <- getState setState r{currencyField=readMay v} +accountfield = do+ string "account-field"+ many1 spacenonewline+ v <- restofline+ r <- getState+ setState r{accountField=readMay v}++ basecurrency = do string "currency" many1 spacenonewline@@ -271,12 +304,15 @@ transactionFromCsvRecord :: CsvRules -> CsvRecord -> Transaction transactionFromCsvRecord rules fields = let - date = parsedate $ normaliseDate $ maybe "1900/1/1" (atDef "" fields) (dateField rules)+ date = parsedate $ normaliseDate (dateFormat rules) $ maybe "1900/1/1" (atDef "" fields) (dateField rules)+ effectivedate = do idx <- effectiveDateField rules+ return $ parsedate $ normaliseDate (dateFormat rules) $ (atDef "" fields) idx status = maybe False (null . strip . (atDef "" fields)) (statusField rules) code = maybe "" (atDef "" fields) (codeField rules) desc = maybe "" (atDef "" fields) (descriptionField rules) comment = "" precomment = ""+ baseacc = maybe (baseAccount rules) (atDef "" fields) (accountField rules) amountstr = maybe "" (atDef "" fields) (amountField rules) amountstr' = strnegate amountstr where strnegate ('-':s) = s strnegate s = '-':s@@ -284,12 +320,15 @@ amountstr'' = currency ++ amountstr' amountparse = runParser someamount nullctx "" amountstr'' amount = either (const nullmixedamt) id amountparse+ -- Using costOfMixedAmount here to allow complex costs like "10 GBP @@ 15 USD".+ -- Aim is to have "10 GBP @@ 15 USD" applied to account "acct", but have "-15USD" applied to "baseacct"+ baseamount = costOfMixedAmount amount unknownacct | (readDef 0 amountstr' :: Double) < 0 = "income:unknown" | otherwise = "expenses:unknown" (acct,newdesc) = identify (accountRules rules) unknownacct desc t = Transaction { tdate=date,- teffectivedate=Nothing,+ teffectivedate=effectivedate, tstatus=status, tcode=code, tdescription=newdesc,@@ -308,8 +347,8 @@ }, Posting { pstatus=False,- paccount=baseAccount rules,- pamount=(-amount),+ paccount=baseacc,+ pamount=(-baseamount), pcomment="", ptype=RegularPosting, pmetadata=[],@@ -320,9 +359,11 @@ in t -- | Convert some date string with unknown format to YYYY/MM/DD.-normaliseDate :: String -> String-normaliseDate s = maybe "0000/00/00" showDate $- firstJust+normaliseDate :: Maybe String -- ^ User-supplied date format: this should be tried in preference to all others+ -> String -> String+normaliseDate mb_user_format s = maybe "0000/00/00" showDate $+ firstJust $+ (maybe id (\user_format -> (parseTime defaultTimeLocale user_format s :)) mb_user_format) $ [parseTime defaultTimeLocale "%Y/%m/%e" s -- can't parse a month without leading 0, try adding one ,parseTime defaultTimeLocale "%Y/%m/%e" (take 5 s ++ "0" ++ drop 5 s)@@ -350,7 +391,7 @@ caseinsensitive = ("(?i)"++) -tests_Convert = TestList [+tests_Hledger_Cli_Convert = TestList [ "convert rules parsing: empty file" ~: do -- let assertMixedAmountParse parseresult mixedamount =
Hledger/Cli/Histogram.hs view
@@ -9,10 +9,8 @@ where import Hledger.Data import Hledger.Cli.Options-#if __GLASGOW_HASKELL__ <= 610-import Prelude hiding ( putStr )-import System.IO.UTF8-#endif+import Prelude hiding (putStr)+import Hledger.Data.UTF8 (putStr) barchar = '*'@@ -25,14 +23,14 @@ putStr $ showHistogram opts (optsToFilterSpec opts args t) j showHistogram :: [Opt] -> FilterSpec -> Journal -> String-showHistogram opts filterspec j = concatMap (printDayWith countBar) dayps+showHistogram opts filterspec j = concatMap (printDayWith countBar) spanps where i = intervalFromOpts opts- interval | i == NoInterval = Daily+ interval | i == NoInterval = Days 1 | otherwise = i- fullspan = journalDateSpan j- days = filter (DateSpan Nothing Nothing /=) $ splitSpan interval fullspan- dayps = [(s, filter (isPostingInDateSpan s) ps) | s <- days]+ span = datespan filterspec `orDatesFrom` journalDateSpan j+ spans = filter (DateSpan Nothing Nothing /=) $ splitSpan interval span+ spanps = [(s, filter (isPostingInDateSpan s) ps) | s <- spans] -- same as Register -- should count transactions, not postings ? ps = sortBy (comparing postingDate) $ filterempties $ filter matchapats $ filterdepth $ journalPostings j
Hledger/Cli/Main.hs view
@@ -1,7 +1,6 @@-{-# LANGUAGE CPP #-} {-| hledger - a ledger-compatible accounting tool.-Copyright (c) 2007-2010 Simon Michael <simon@joyful.com>+Copyright (c) 2007-2011 Simon Michael <simon@joyful.com> Released under GPL version 3 or later. hledger is a partial haskell clone of John Wiegley's "ledger". It@@ -40,16 +39,11 @@ module Hledger.Cli.Main where -#if __GLASGOW_HASKELL__ <= 610 import Prelude hiding (putStr, putStrLn)-import System.IO.UTF8-#endif-+import Hledger.Data.UTF8 (putStr, putStrLn) import Hledger.Data-import Hledger.Cli.Commands-import Hledger.Cli.Options+import Hledger.Cli import Hledger.Cli.Tests-import Hledger.Cli.Utils (withJournalDo) import Hledger.Cli.Version (progversionstr, binaryfilename) main :: IO ()
Hledger/Cli/Options.hs view
@@ -1,11 +1,9 @@-{-# LANGUAGE CPP #-} {-| Command-line options for the application. -} module Hledger.Cli.Options where-import Codec.Binary.UTF8.String (decodeString) import System.Console.GetOpt import System.Environment @@ -148,7 +146,7 @@ -- provided usage string. parseArgumentsWith :: [OptDescr Opt] -> IO ([Opt], [String]) parseArgumentsWith options = do- rawargs <- map decodeString `fmap` getArgs+ rawargs <- map fromPlatformString `fmap` getArgs let (opts,args,errs) = getOpt Permute options rawargs opts' <- fixOptDates opts let opts'' = if Debug `elem` opts' then Verbose:opts' else opts'@@ -199,11 +197,11 @@ ((p:_), _) -> case parsePeriodExpr (parsedate "0001/01/01") p of Right (i, _) -> i Left e -> parseerror e- (_, (DailyOpt:_)) -> Daily- (_, (WeeklyOpt:_)) -> Weekly- (_, (MonthlyOpt:_)) -> Monthly- (_, (QuarterlyOpt:_)) -> Quarterly- (_, (YearlyOpt:_)) -> Yearly+ (_, (DailyOpt:_)) -> Days 1+ (_, (WeeklyOpt:_)) -> Weeks 1+ (_, (MonthlyOpt:_)) -> Months 1+ (_, (QuarterlyOpt:_)) -> Quarters 1+ (_, (YearlyOpt:_)) -> Years 1 (_, _) -> NoInterval where periodopts = reverse $ optValuesForConstructor Period opts@@ -295,3 +293,28 @@ -- where -- listtomaybe [] = Nothing -- listtomaybe vs = Just $ last vs++tests_Hledger_Cli_Options = TestList+ [+ "dateSpanFromOpts" ~: do+ let todaysdate = parsedate "2008/11/26"+ let gives = is . show . dateSpanFromOpts todaysdate+ [] `gives` "DateSpan Nothing Nothing"+ [Begin "2008", End "2009"] `gives` "DateSpan (Just 2008-01-01) (Just 2009-01-01)"+ [Period "in 2008"] `gives` "DateSpan (Just 2008-01-01) (Just 2009-01-01)"+ [Begin "2005", End "2007",Period "in 2008"] `gives` "DateSpan (Just 2008-01-01) (Just 2009-01-01)"++ ,"intervalFromOpts" ~: do+ let gives = is . intervalFromOpts+ [] `gives` NoInterval+ [DailyOpt] `gives` Days 1+ [WeeklyOpt] `gives` Weeks 1+ [MonthlyOpt] `gives` Months 1+ [QuarterlyOpt] `gives` Quarters 1+ [YearlyOpt] `gives` Years 1+ [Period "weekly"] `gives` Weeks 1+ [Period "monthly"] `gives` Months 1+ [Period "quarterly"] `gives` Quarters 1+ [WeeklyOpt, Period "yearly"] `gives` Years 1++ ]
Hledger/Cli/Print.hs view
@@ -12,12 +12,10 @@ ,journalReport ,showTransactions ) where-import Hledger.Data import Hledger.Cli.Options-#if __GLASGOW_HASKELL__ <= 610-import Prelude hiding ( putStr )-import System.IO.UTF8-#endif+import Hledger.Data+import Prelude hiding (putStr)+import Hledger.Data.UTF8 (putStr) -- | A "journal report" is just a list of transactions.
Hledger/Cli/Register.hs view
@@ -12,19 +12,18 @@ ,registerReport ,registerReportAsText ,showPostingWithBalanceForVty- ,tests_Register+ ,tests_Hledger_Cli_Register ) where import Safe (headMay, lastMay)-import Hledger.Data-import Hledger.Cli.Options-#if __GLASGOW_HASKELL__ <= 610-import Prelude hiding ( putStr )-import System.IO.UTF8-#endif import Text.ParserCombinators.Parsec +import Hledger.Cli.Options+import Hledger.Data+import Prelude hiding (putStr)+import Hledger.Data.UTF8 (putStr) + -- | A register report is a list of postings to an account or set of -- accounts, with a running total. Postings may be actual postings, or -- virtual postings aggregated over a reporting interval.@@ -198,10 +197,46 @@ depthClipPosting (Just d) p@Posting{paccount=a} = p{paccount=clipAccountName d a} -tests_Register :: Test-tests_Register = TestList [+tests_Hledger_Cli_Register :: Test+tests_Hledger_Cli_Register = TestList+ [ - "summarisePostingsByInterval" ~: do- summarisePostingsByInterval Quarterly Nothing False (DateSpan Nothing Nothing) [] ~?= []+ "summarisePostingsByInterval" ~: do+ summarisePostingsByInterval (Quarters 1) Nothing False (DateSpan Nothing Nothing) [] ~?= [] - ]+ -- ,"summarisePostingsInDateSpan" ~: do+ -- let gives (b,e,depth,showempty,ps) =+ -- (summarisePostingsInDateSpan (mkdatespan b e) depth showempty ps `is`)+ -- let ps =+ -- [+ -- nullposting{lpdescription="desc",lpaccount="expenses:food:groceries",lpamount=Mixed [dollars 1]}+ -- ,nullposting{lpdescription="desc",lpaccount="expenses:food:dining", lpamount=Mixed [dollars 2]}+ -- ,nullposting{lpdescription="desc",lpaccount="expenses:food", lpamount=Mixed [dollars 4]}+ -- ,nullposting{lpdescription="desc",lpaccount="expenses:food:dining", lpamount=Mixed [dollars 8]}+ -- ]+ -- ("2008/01/01","2009/01/01",0,9999,False,[]) `gives` + -- []+ -- ("2008/01/01","2009/01/01",0,9999,True,[]) `gives` + -- [+ -- nullposting{lpdate=parsedate "2008/01/01",lpdescription="- 2008/12/31"}+ -- ]+ -- ("2008/01/01","2009/01/01",0,9999,False,ts) `gives` + -- [+ -- nullposting{lpdate=parsedate "2008/01/01",lpdescription="- 2008/12/31",lpaccount="expenses:food", lpamount=Mixed [dollars 4]}+ -- ,nullposting{lpdate=parsedate "2008/01/01",lpdescription="- 2008/12/31",lpaccount="expenses:food:dining", lpamount=Mixed [dollars 10]}+ -- ,nullposting{lpdate=parsedate "2008/01/01",lpdescription="- 2008/12/31",lpaccount="expenses:food:groceries",lpamount=Mixed [dollars 1]}+ -- ]+ -- ("2008/01/01","2009/01/01",0,2,False,ts) `gives` + -- [+ -- nullposting{lpdate=parsedate "2008/01/01",lpdescription="- 2008/12/31",lpaccount="expenses:food",lpamount=Mixed [dollars 15]}+ -- ]+ -- ("2008/01/01","2009/01/01",0,1,False,ts) `gives` + -- [+ -- nullposting{lpdate=parsedate "2008/01/01",lpdescription="- 2008/12/31",lpaccount="expenses",lpamount=Mixed [dollars 15]}+ -- ]+ -- ("2008/01/01","2009/01/01",0,0,False,ts) `gives` + -- [+ -- nullposting{lpdate=parsedate "2008/01/01",lpdescription="- 2008/12/31",lpaccount="",lpamount=Mixed [dollars 15]}+ -- ]++ ]
Hledger/Cli/Stats.hs view
@@ -7,13 +7,12 @@ module Hledger.Cli.Stats where-import Hledger.Data-import Hledger.Cli.Options import qualified Data.Map as Map-#if __GLASGOW_HASKELL__ <= 610-import Prelude hiding ( putStr )-import System.IO.UTF8-#endif++import Hledger.Cli.Options+import Hledger.Data+import Prelude hiding (putStr)+import Hledger.Data.UTF8 (putStr) -- like Register.summarisePostings
Hledger/Cli/Tests.hs view
@@ -10,1106 +10,39 @@ by .test files in the tests\/ subdirectory. These can be run by doing @make functest@ in the hledger source tree. -hledger's doctests are shell tests defined in literal blocks in haddock-documentation in the source, run by doing @make doctest@ in the hledger-source tree. are They hardly used, but here is an example:--@-$ bin/hledger -f data/sample.journal balance o- $1 expenses:food- $-2 income- $-1 gifts- $-1 salary---------------------- $-1-@---}--module Hledger.Cli.Tests-where-import qualified Data.Map as Map-import System.Exit (exitFailure, exitWith, ExitCode(ExitSuccess)) -- base 3 compatible-import System.Time (ClockTime(TOD))--import Hledger.Data -- including testing utils in Hledger.Data.Utils-import Hledger.Read (readJournal)-import Hledger.Read.JournalReader (someamount)-import Hledger.Cli.Commands-import Hledger.Cli.Options-import Hledger.Cli.Utils----- | Run unit tests.-runtests :: [Opt] -> [String] -> IO ()-runtests _ args = do- (counts,_) <- liftM (flip (,) 0) $ runTestTT ts- if errors counts > 0 || (failures counts > 0)- then exitFailure- else exitWith ExitSuccess- where- ts = TestList $ filter matchname $ tflatten tests -- show flat test names- -- ts = tfilter matchname $ TestList tests -- show hierarchical test names- matchname = matchpats args . tname---- | unit tests, augmenting the ones defined in each module. Where that is--- inconvenient due to import cycles or whatever, we define them here.-tests :: Test-tests = TestList [- tests_Hledger_Data,- tests_Hledger_Commands,-- "account directive" ~:- let sameParse str1 str2 = do j1 <- readJournal Nothing str1 >>= either error' return- j2 <- readJournal Nothing str2 >>= either error' return- j1 `is` j2{filereadtime=filereadtime j1, files=files j1, jContext=jContext j1}- in TestList- [- "account directive 1" ~: sameParse - "2008/12/07 One\n test:from $-1\n test:to $1\n"- "!account test\n2008/12/07 One\n from $-1\n to $1\n"-- ,"account directive 2" ~: sameParse - "2008/12/07 One\n test:foo:from $-1\n test:foo:to $1\n"- "!account test\n!account foo\n2008/12/07 One\n from $-1\n to $1\n"-- ,"account directive 3" ~: sameParse - "2008/12/07 One\n test:from $-1\n test:to $1\n"- "!account test\n!account foo\n!end\n2008/12/07 One\n from $-1\n to $1\n"-- ,"account directive 4" ~: sameParse - ("2008/12/07 One\n alpha $-1\n beta $1\n" ++- "!account outer\n2008/12/07 Two\n aigh $-2\n bee $2\n" ++- "!account inner\n2008/12/07 Three\n gamma $-3\n delta $3\n" ++- "!end\n2008/12/07 Four\n why $-4\n zed $4\n" ++- "!end\n2008/12/07 Five\n foo $-5\n bar $5\n"- )- ("2008/12/07 One\n alpha $-1\n beta $1\n" ++- "2008/12/07 Two\n outer:aigh $-2\n outer:bee $2\n" ++- "2008/12/07 Three\n outer:inner:gamma $-3\n outer:inner:delta $3\n" ++- "2008/12/07 Four\n outer:why $-4\n outer:zed $4\n" ++- "2008/12/07 Five\n foo $-5\n bar $5\n"- )- ]-- ,"accountnames" ~:- accountnames ledger7 `is`- ["assets","assets:cash","assets:checking","assets:saving","equity","equity:opening balances",- "expenses","expenses:food","expenses:food:dining","expenses:phone","expenses:vacation",- "liabilities","liabilities:credit cards","liabilities:credit cards:discover"]-- ,"accountNameTreeFrom" ~: do- accountNameTreeFrom ["a"] `is` Node "top" [Node "a" []]- accountNameTreeFrom ["a","b"] `is` Node "top" [Node "a" [], Node "b" []]- accountNameTreeFrom ["a","a:b"] `is` Node "top" [Node "a" [Node "a:b" []]]- accountNameTreeFrom ["a:b:c"] `is` Node "top" [Node "a" [Node "a:b" [Node "a:b:c" []]]]-- ,"balance report tests" ~:- let (opts,args) `gives` es = do - l <- samplejournalwithopts opts args- t <- getCurrentLocalTime- balanceReportAsText opts (balanceReport opts (optsToFilterSpec opts args t) l) `is` unlines es- in TestList- [-- "balance report with no args" ~:- ([], []) `gives`- [" $-1 assets"- ," $1 bank:saving"- ," $-2 cash"- ," $2 expenses"- ," $1 food"- ," $1 supplies"- ," $-2 income"- ," $-1 gifts"- ," $-1 salary"- ," $1 liabilities:debts"- ,"--------------------"- ," $0"- ]-- ,"balance report can be limited with --depth" ~:- ([Depth "1"], []) `gives`- [" $-1 assets"- ," $2 expenses"- ," $-2 income"- ," $1 liabilities"- ,"--------------------"- ," $0"- ]- - ,"balance report with account pattern o" ~:- ([SubTotal], ["o"]) `gives`- [" $1 expenses:food"- ," $-2 income"- ," $-1 gifts"- ," $-1 salary"- ,"--------------------"- ," $-1"- ]-- ,"balance report with account pattern o and --depth 1" ~:- ([Depth "1"], ["o"]) `gives`- [" $1 expenses"- ," $-2 income"- ,"--------------------"- ," $-1"- ]-- ,"balance report with account pattern a" ~:- ([], ["a"]) `gives`- [" $-1 assets"- ," $1 bank:saving"- ," $-2 cash"- ," $-1 income:salary"- ," $1 liabilities:debts"- ,"--------------------"- ," $-1"- ]-- ,"balance report with account pattern e" ~:- ([], ["e"]) `gives`- [" $-1 assets"- ," $1 bank:saving"- ," $-2 cash"- ," $2 expenses"- ," $1 food"- ," $1 supplies"- ," $-2 income"- ," $-1 gifts"- ," $-1 salary"- ," $1 liabilities:debts"- ,"--------------------"- ," $0"- ]-- ,"balance report with unmatched parent of two matched subaccounts" ~: - ([], ["cash","saving"]) `gives`- [" $-1 assets"- ," $1 bank:saving"- ," $-2 cash"- ,"--------------------"- ," $-1"- ]-- ,"balance report with multi-part account name" ~: - ([], ["expenses:food"]) `gives`- [" $1 expenses:food"- ,"--------------------"- ," $1"- ]-- ,"balance report with negative account pattern" ~:- ([], ["not:assets"]) `gives`- [" $2 expenses"- ," $1 food"- ," $1 supplies"- ," $-2 income"- ," $-1 gifts"- ," $-1 salary"- ," $1 liabilities:debts"- ,"--------------------"- ," $1"- ]-- ,"balance report negative account pattern always matches full name" ~: - ([], ["not:e"]) `gives`- ["--------------------"- ," 0"- ]-- ,"balance report negative patterns affect totals" ~: - ([], ["expenses","not:food"]) `gives`- [" $1 expenses:supplies"- ,"--------------------"- ," $1"- ]-- ,"balance report with -E shows zero-balance accounts" ~:- ([SubTotal,Empty], ["assets"]) `gives`- [" $-1 assets"- ," $1 bank"- ," $0 checking"- ," $1 saving"- ," $-2 cash"- ,"--------------------"- ," $-1"- ]-- ,"balance report with cost basis" ~: do- j <- (readJournal Nothing $ unlines- [""- ,"2008/1/1 test "- ," a:b 10h @ $50"- ," c:d "- ]) >>= either error' return- let j' = journalCanonicaliseAmounts $ journalConvertAmountsToCost j -- enable cost basis adjustment- balanceReportAsText [] (balanceReport [] nullfilterspec j') `is`- unlines- [" $500 a:b"- ," $-500 c:d"- ,"--------------------"- ," $0"- ]-- ,"balance report elides zero-balance root account(s)" ~: do- l <- readJournalWithOpts []- (unlines- ["2008/1/1 one"- ," test:a 1"- ," test:b"- ])- balanceReportAsText [] (balanceReport [] nullfilterspec l) `is`- unlines- [" 1 test:a"- ," -1 test:b"- ,"--------------------"- ," 0"- ]-- ]-- ,"balanceTransaction" ~: do- assertBool "detect unbalanced entry, sign error"- (isLeft $ balanceTransaction Nothing- (Transaction (parsedate "2007/01/28") Nothing False "" "test" "" []- [Posting False "a" (Mixed [dollars 1]) "" RegularPosting [] Nothing, - Posting False "b" (Mixed [dollars 1]) "" RegularPosting [] Nothing- ] ""))- assertBool "detect unbalanced entry, multiple missing amounts"- (isLeft $ balanceTransaction Nothing- (Transaction (parsedate "2007/01/28") Nothing False "" "test" "" []- [Posting False "a" missingamt "" RegularPosting [] Nothing, - Posting False "b" missingamt "" RegularPosting [] Nothing- ] ""))- let e = balanceTransaction Nothing (Transaction (parsedate "2007/01/28") Nothing False "" "test" "" []- [Posting False "a" (Mixed [dollars 1]) "" RegularPosting [] Nothing, - Posting False "b" missingamt "" RegularPosting [] Nothing- ] "")- assertBool "one missing amount should be ok" (isRight e)- assertEqual "balancing amount is added" - (Mixed [dollars (-1)])- (case e of- Right e' -> (pamount $ last $ tpostings e')- Left _ -> error' "should not happen")-- ,"journalCanonicaliseAmounts" ~:- "use the greatest precision" ~:- (map precision $ journalAmountAndPriceCommodities $ journalCanonicaliseAmounts $ journalWithAmounts ["1","2.00"]) `is` [2,2]-- ,"commodities" ~:- Map.elems (commodities ledger7) `is` [Commodity {symbol="$", side=L, spaced=False, comma=False, precision=2}]-- ,"dateSpanFromOpts" ~: do- let todaysdate = parsedate "2008/11/26"- let gives = is . show . dateSpanFromOpts todaysdate- [] `gives` "DateSpan Nothing Nothing"- [Begin "2008", End "2009"] `gives` "DateSpan (Just 2008-01-01) (Just 2009-01-01)"- [Period "in 2008"] `gives` "DateSpan (Just 2008-01-01) (Just 2009-01-01)"- [Begin "2005", End "2007",Period "in 2008"] `gives` "DateSpan (Just 2008-01-01) (Just 2009-01-01)"-- -- don't know what this should do- -- ,"elideAccountName" ~: do- -- (elideAccountName 50 "aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa"- -- `is` "aa:aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa")- -- (elideAccountName 20 "aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa"- -- `is` "aa:aa:aaaaaaaaaaaaaa")-- ,"expandAccountNames" ~:- expandAccountNames ["assets:cash","assets:checking","expenses:vacation"] `is`- ["assets","assets:cash","assets:checking","expenses","expenses:vacation"]-- ,"intervalFromOpts" ~: do- let gives = is . intervalFromOpts- [] `gives` NoInterval- [DailyOpt] `gives` Daily- [WeeklyOpt] `gives` Weekly- [MonthlyOpt] `gives` Monthly- [QuarterlyOpt] `gives` Quarterly- [YearlyOpt] `gives` Yearly- [Period "weekly"] `gives` Weekly- [Period "monthly"] `gives` Monthly- [Period "quarterly"] `gives` Quarterly- [WeeklyOpt, Period "yearly"] `gives` Yearly-- ,"isAccountNamePrefixOf" ~: do- "assets" `isAccountNamePrefixOf` "assets" `is` False- "assets" `isAccountNamePrefixOf` "assets:bank" `is` True- "assets" `isAccountNamePrefixOf` "assets:bank:checking" `is` True- "my assets" `isAccountNamePrefixOf` "assets:bank" `is` False-- ,"isTransactionBalanced" ~: do- let t = Transaction (parsedate "2009/01/01") Nothing False "" "a" "" []- [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting [] (Just t)- ,Posting False "c" (Mixed [dollars (-1.00)]) "" RegularPosting [] (Just t)- ] ""- assertBool "detect balanced" (isTransactionBalanced Nothing t)- let t = Transaction (parsedate "2009/01/01") Nothing False "" "a" "" []- [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting [] (Just t)- ,Posting False "c" (Mixed [dollars (-1.01)]) "" RegularPosting [] (Just t)- ] ""- assertBool "detect unbalanced" (not $ isTransactionBalanced Nothing t)- let t = Transaction (parsedate "2009/01/01") Nothing False "" "a" "" []- [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting [] (Just t)- ] ""- assertBool "detect unbalanced, one posting" (not $ isTransactionBalanced Nothing t)- let t = Transaction (parsedate "2009/01/01") Nothing False "" "a" "" []- [Posting False "b" (Mixed [dollars 0]) "" RegularPosting [] (Just t)- ] ""- assertBool "one zero posting is considered balanced for now" (isTransactionBalanced Nothing t)- let t = Transaction (parsedate "2009/01/01") Nothing False "" "a" "" []- [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting [] (Just t)- ,Posting False "c" (Mixed [dollars (-1.00)]) "" RegularPosting [] (Just t)- ,Posting False "d" (Mixed [dollars 100]) "" VirtualPosting [] (Just t)- ] ""- assertBool "virtual postings don't need to balance" (isTransactionBalanced Nothing t)- let t = Transaction (parsedate "2009/01/01") Nothing False "" "a" "" []- [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting [] (Just t)- ,Posting False "c" (Mixed [dollars (-1.00)]) "" RegularPosting [] (Just t)- ,Posting False "d" (Mixed [dollars 100]) "" BalancedVirtualPosting [] (Just t)- ] ""- assertBool "balanced virtual postings need to balance among themselves" (not $ isTransactionBalanced Nothing t)- let t = Transaction (parsedate "2009/01/01") Nothing False "" "a" "" []- [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting [] (Just t)- ,Posting False "c" (Mixed [dollars (-1.00)]) "" RegularPosting [] (Just t)- ,Posting False "d" (Mixed [dollars 100]) "" BalancedVirtualPosting [] (Just t)- ,Posting False "e" (Mixed [dollars (-100)]) "" BalancedVirtualPosting [] (Just t)- ] ""- assertBool "balanced virtual postings need to balance among themselves (2)" (isTransactionBalanced Nothing t)-- ,"isSubAccountNameOf" ~: do- "assets" `isSubAccountNameOf` "assets" `is` False- "assets:bank" `isSubAccountNameOf` "assets" `is` True- "assets:bank:checking" `isSubAccountNameOf` "assets" `is` False- "assets:bank" `isSubAccountNameOf` "my assets" `is` False-- ,"default year" ~: do- rl <- readJournal Nothing defaultyear_journal_str >>= either error' return- tdate (head $ jtxns rl) `is` fromGregorian 2009 1 1- return ()-- ,"normaliseMixedAmount" ~: do- normaliseMixedAmount (Mixed []) ~?= Mixed [nullamt]-- ,"parsedate" ~: do- parsedate "2008/02/03" `is` parsetimewith "%Y/%m/%d" "2008/02/03" date1- parsedate "2008-02-03" `is` parsetimewith "%Y/%m/%d" "2008/02/03" date1-- ,"period expressions" ~: do- let todaysdate = parsedate "2008/11/26"- let str `gives` result = show (parsewith (periodexpr todaysdate) str) `is` ("Right " ++ result)- "from aug to oct" `gives` "(NoInterval,DateSpan (Just 2008-08-01) (Just 2008-10-01))"- "aug to oct" `gives` "(NoInterval,DateSpan (Just 2008-08-01) (Just 2008-10-01))"- "every day from aug to oct" `gives` "(Daily,DateSpan (Just 2008-08-01) (Just 2008-10-01))"- "daily from aug" `gives` "(Daily,DateSpan (Just 2008-08-01) Nothing)"- "every week to 2009" `gives` "(Weekly,DateSpan Nothing (Just 2009-01-01))"-- ,"print report tests" ~: TestList- [-- "print expenses" ~:- do - let args = ["expenses"]- l <- samplejournalwithopts [] args- t <- getCurrentLocalTime- showTransactions (optsToFilterSpec [] args t) l `is` unlines- ["2008/06/03 * eat & shop"- ," expenses:food $1"- ," expenses:supplies $1"- ," assets:cash $-2"- ,""- ]-- , "print report with depth arg" ~:- do - l <- samplejournal- t <- getCurrentLocalTime- showTransactions (optsToFilterSpec [Depth "2"] [] t) l `is` unlines- ["2008/01/01 income"- ," income:salary $-1"- ,""- ,"2008/06/01 gift"- ," income:gifts $-1"- ,""- ,"2008/06/03 * eat & shop"- ," expenses:food $1"- ," expenses:supplies $1"- ," assets:cash $-2"- ,""- ,"2008/12/31 * pay off"- ," liabilities:debts $1"- ,""- ]-- ]-- ,"punctuatethousands 1" ~: punctuatethousands "" `is` ""-- ,"punctuatethousands 2" ~: punctuatethousands "1234567.8901" `is` "1,234,567.8901"-- ,"punctuatethousands 3" ~: punctuatethousands "-100" `is` "-100"-- ,"register report tests" ~:- let registerdates = filter (not . null) . map (strip . take 10) . lines- in- TestList- [-- "register report with no args" ~:- do - l <- samplejournal- (registerReportAsText [] $ registerReport [] (optsToFilterSpec [] [] t1) l) `is` unlines- ["2008/01/01 income assets:bank:checking $1 $1"- ," income:salary $-1 0"- ,"2008/06/01 gift assets:bank:checking $1 $1"- ," income:gifts $-1 0"- ,"2008/06/02 save assets:bank:saving $1 $1"- ," assets:bank:checking $-1 0"- ,"2008/06/03 eat & shop expenses:food $1 $1"- ," expenses:supplies $1 $2"- ," assets:cash $-2 0"- ,"2008/12/31 pay off liabilities:debts $1 $1"- ," assets:bank:checking $-1 0"- ]-- ,"register report with cleared option" ~:- do - let opts = [Cleared]- l <- readJournalWithOpts opts sample_journal_str- (registerReportAsText opts $ registerReport opts (optsToFilterSpec opts [] t1) l) `is` unlines- ["2008/06/03 eat & shop expenses:food $1 $1"- ," expenses:supplies $1 $2"- ," assets:cash $-2 0"- ,"2008/12/31 pay off liabilities:debts $1 $1"- ," assets:bank:checking $-1 0"- ]-- ,"register report with uncleared option" ~:- do - let opts = [UnCleared]- l <- readJournalWithOpts opts sample_journal_str- (registerReportAsText opts $ registerReport opts (optsToFilterSpec opts [] t1) l) `is` unlines- ["2008/01/01 income assets:bank:checking $1 $1"- ," income:salary $-1 0"- ,"2008/06/01 gift assets:bank:checking $1 $1"- ," income:gifts $-1 0"- ,"2008/06/02 save assets:bank:saving $1 $1"- ," assets:bank:checking $-1 0"- ]-- ,"register report sorts by date" ~:- do - l <- readJournalWithOpts [] $ unlines- ["2008/02/02 a"- ," b 1"- ," c"- ,""- ,"2008/01/01 d"- ," e 1"- ," f"- ]- registerdates (registerReportAsText [] $ registerReport [] (optsToFilterSpec [] [] t1) l) `is` ["2008/01/01","2008/02/02"]-- ,"register report with account pattern" ~:- do- l <- samplejournal- (registerReportAsText [] $ registerReport [] (optsToFilterSpec [] ["cash"] t1) l) `is` unlines- ["2008/06/03 eat & shop assets:cash $-2 $-2"- ]-- ,"register report with account pattern, case insensitive" ~:- do - l <- samplejournal- (registerReportAsText [] $ registerReport [] (optsToFilterSpec [] ["cAsH"] t1) l) `is` unlines- ["2008/06/03 eat & shop assets:cash $-2 $-2"- ]-- ,"register report with display expression" ~:- do - l <- samplejournal- let gives displayexpr = - (registerdates (registerReportAsText opts $ registerReport opts (optsToFilterSpec opts [] t1) l) `is`)- where opts = [Display displayexpr]- "d<[2008/6/2]" `gives` ["2008/01/01","2008/06/01"]- "d<=[2008/6/2]" `gives` ["2008/01/01","2008/06/01","2008/06/02"]- "d=[2008/6/2]" `gives` ["2008/06/02"]- "d>=[2008/6/2]" `gives` ["2008/06/02","2008/06/03","2008/12/31"]- "d>[2008/6/2]" `gives` ["2008/06/03","2008/12/31"]-- ,"register report with period expression" ~:- do - l <- samplejournal- let periodexpr `gives` dates = do- l' <- samplejournalwithopts opts []- registerdates (registerReportAsText opts $ registerReport opts (optsToFilterSpec opts [] t1) l') `is` dates- where opts = [Period periodexpr]- "" `gives` ["2008/01/01","2008/06/01","2008/06/02","2008/06/03","2008/12/31"]- "2008" `gives` ["2008/01/01","2008/06/01","2008/06/02","2008/06/03","2008/12/31"]- "2007" `gives` []- "june" `gives` ["2008/06/01","2008/06/02","2008/06/03"]- "monthly" `gives` ["2008/01/01","2008/06/01","2008/12/01"]- "quarterly" `gives` ["2008/01/01","2008/04/01","2008/10/01"]- let opts = [Period "yearly"]- (registerReportAsText opts $ registerReport opts (optsToFilterSpec opts [] t1) l) `is` unlines- ["2008/01/01 - 2008/12/31 assets:bank:saving $1 $1"- ," assets:cash $-2 $-1"- ," expenses:food $1 0"- ," expenses:supplies $1 $1"- ," income:gifts $-1 0"- ," income:salary $-1 $-1"- ," liabilities:debts $1 0"- ]- let opts = [Period "quarterly"]- registerdates (registerReportAsText opts $ registerReport opts (optsToFilterSpec opts [] t1) l) `is` ["2008/01/01","2008/04/01","2008/10/01"]- let opts = [Period "quarterly",Empty]- registerdates (registerReportAsText opts $ registerReport opts (optsToFilterSpec opts [] t1) l) `is` ["2008/01/01","2008/04/01","2008/07/01","2008/10/01"]-- ]-- , "register report with depth arg" ~:- do - l <- samplejournal- let opts = [Depth "2"]- (registerReportAsText opts $ registerReport opts (optsToFilterSpec opts [] t1) l) `is` unlines- ["2008/01/01 income assets:bank $1 $1"- ," income:salary $-1 0"- ,"2008/06/01 gift assets:bank $1 $1"- ," income:gifts $-1 0"- ,"2008/06/02 save assets:bank $1 $1"- ," assets:bank $-1 0"- ,"2008/06/03 eat & shop expenses:food $1 $1"- ," expenses:supplies $1 $2"- ," assets:cash $-2 0"- ,"2008/12/31 pay off liabilities:debts $1 $1"- ," assets:bank $-1 0"- ]-- ,"show dollars" ~: show (dollars 1) ~?= "$1.00"-- ,"show hours" ~: show (hours 1) ~?= "1.0h"-- ,"unicode in balance layout" ~: do- l <- readJournalWithOpts []- "2009/01/01 * медвежья шкура\n расходы:покупки 100\n актив:наличные\n"- balanceReportAsText [] (balanceReport [] (optsToFilterSpec [] [] t1) l) `is` unlines- [" -100 актив:наличные"- ," 100 расходы:покупки"- ,"--------------------"- ," 0"- ]-- ,"unicode in register layout" ~: do- l <- readJournalWithOpts []- "2009/01/01 * медвежья шкура\n расходы:покупки 100\n актив:наличные\n"- (registerReportAsText [] $ registerReport [] (optsToFilterSpec [] [] t1) l) `is` unlines- ["2009/01/01 медвежья шкура расходы:покупки 100 100"- ," актив:наличные -100 0"]-- ,"fixSmartDateStr" ~: do- let gives = is . fixSmartDateStr (parsedate "2008/11/26")- "1999-12-02" `gives` "1999/12/02"- "1999.12.02" `gives` "1999/12/02"- "1999/3/2" `gives` "1999/03/02"- "19990302" `gives` "1999/03/02"- "2008/2" `gives` "2008/02/01"- "0020/2" `gives` "0020/02/01"- "1000" `gives` "1000/01/01"- "4/2" `gives` "2008/04/02"- "2" `gives` "2008/11/02"- "January" `gives` "2008/01/01"- "feb" `gives` "2008/02/01"- "today" `gives` "2008/11/26"- "yesterday" `gives` "2008/11/25"- "tomorrow" `gives` "2008/11/27"- "this day" `gives` "2008/11/26"- "last day" `gives` "2008/11/25"- "next day" `gives` "2008/11/27"- "this week" `gives` "2008/11/24" -- last monday- "last week" `gives` "2008/11/17" -- previous monday- "next week" `gives` "2008/12/01" -- next monday- "this month" `gives` "2008/11/01"- "last month" `gives` "2008/10/01"- "next month" `gives` "2008/12/01"- "this quarter" `gives` "2008/10/01"- "last quarter" `gives` "2008/07/01"- "next quarter" `gives` "2009/01/01"- "this year" `gives` "2008/01/01"- "last year" `gives` "2007/01/01"- "next year" `gives` "2009/01/01"--- "last wed" `gives` "2008/11/19"--- "next friday" `gives` "2008/11/28"--- "next january" `gives` "2009/01/01"-- ,"subAccounts" ~: do- l <- liftM (journalToLedger nullfilterspec) samplejournal- let a = ledgerAccount l "assets"- map aname (ledgerSubAccounts l a) `is` ["assets:bank","assets:cash"]-- -- ,"summarisePostingsInDateSpan" ~: do- -- let gives (b,e,depth,showempty,ps) =- -- (summarisePostingsInDateSpan (mkdatespan b e) depth showempty ps `is`)- -- let ps =- -- [- -- nullposting{lpdescription="desc",lpaccount="expenses:food:groceries",lpamount=Mixed [dollars 1]}- -- ,nullposting{lpdescription="desc",lpaccount="expenses:food:dining", lpamount=Mixed [dollars 2]}- -- ,nullposting{lpdescription="desc",lpaccount="expenses:food", lpamount=Mixed [dollars 4]}- -- ,nullposting{lpdescription="desc",lpaccount="expenses:food:dining", lpamount=Mixed [dollars 8]}- -- ]- -- ("2008/01/01","2009/01/01",0,9999,False,[]) `gives` - -- []- -- ("2008/01/01","2009/01/01",0,9999,True,[]) `gives` - -- [- -- nullposting{lpdate=parsedate "2008/01/01",lpdescription="- 2008/12/31"}- -- ]- -- ("2008/01/01","2009/01/01",0,9999,False,ts) `gives` - -- [- -- nullposting{lpdate=parsedate "2008/01/01",lpdescription="- 2008/12/31",lpaccount="expenses:food", lpamount=Mixed [dollars 4]}- -- ,nullposting{lpdate=parsedate "2008/01/01",lpdescription="- 2008/12/31",lpaccount="expenses:food:dining", lpamount=Mixed [dollars 10]}- -- ,nullposting{lpdate=parsedate "2008/01/01",lpdescription="- 2008/12/31",lpaccount="expenses:food:groceries",lpamount=Mixed [dollars 1]}- -- ]- -- ("2008/01/01","2009/01/01",0,2,False,ts) `gives` - -- [- -- nullposting{lpdate=parsedate "2008/01/01",lpdescription="- 2008/12/31",lpaccount="expenses:food",lpamount=Mixed [dollars 15]}- -- ]- -- ("2008/01/01","2009/01/01",0,1,False,ts) `gives` - -- [- -- nullposting{lpdate=parsedate "2008/01/01",lpdescription="- 2008/12/31",lpaccount="expenses",lpamount=Mixed [dollars 15]}- -- ]- -- ("2008/01/01","2009/01/01",0,0,False,ts) `gives` - -- [- -- nullposting{lpdate=parsedate "2008/01/01",lpdescription="- 2008/12/31",lpaccount="",lpamount=Mixed [dollars 15]}- -- ]-- ]-- --- fixtures/test data--date1 = parsedate "2008/11/26"-t1 = LocalTime date1 midday--samplejournal = readJournalWithOpts [] sample_journal_str-samplejournalwithopts opts _ = readJournalWithOpts opts sample_journal_str--sample_journal_str = unlines- ["; A sample journal file."- ,";"- ,"; Sets up this account tree:"- ,"; assets"- ,"; bank"- ,"; checking"- ,"; saving"- ,"; cash"- ,"; expenses"- ,"; food"- ,"; supplies"- ,"; income"- ,"; gifts"- ,"; salary"- ,"; liabilities"- ,"; debts"- ,""- ,"2008/01/01 income"- ," assets:bank:checking $1"- ," income:salary"- ,""- ,"2008/06/01 gift"- ," assets:bank:checking $1"- ," income:gifts"- ,""- ,"2008/06/02 save"- ," assets:bank:saving $1"- ," assets:bank:checking"- ,""- ,"2008/06/03 * eat & shop"- ," expenses:food $1"- ," expenses:supplies $1"- ," assets:cash"- ,""- ,"2008/12/31 * pay off"- ," liabilities:debts $1"- ," assets:bank:checking"- ,""- ,""- ,";final comment"- ]--defaultyear_journal_str = unlines- ["Y2009"- ,""- ,"01/01 A"- ," a $1"- ," b"- ]--write_sample_journal = writeFile "sample.journal" sample_journal_str--entry2_str = unlines- ["2007/01/27 * joes diner"- ," expenses:food:dining $10.00"- ," expenses:gifts $10.00"- ," assets:checking $-20.00"- ,""- ]--entry3_str = unlines- ["2007/01/01 * opening balance"- ," assets:cash $4.82"- ," equity:opening balances"- ,""- ,"2007/01/01 * opening balance"- ," assets:cash $4.82"- ," equity:opening balances"- ,""- ,"2007/01/28 coopportunity"- ," expenses:food:groceries $47.18"- ," assets:checking"- ,""- ]--periodic_entry1_str = unlines- ["~ monthly from 2007/2/2"- ," assets:saving $200.00"- ," assets:checking"- ,""- ]--periodic_entry2_str = unlines- ["~ monthly from 2007/2/2"- ," assets:saving $200.00 ;auto savings"- ," assets:checking"- ,""- ]--periodic_entry3_str = unlines- ["~ monthly from 2007/01/01"- ," assets:cash $4.82"- ," equity:opening balances"- ,""- ,"~ monthly from 2007/01/01"- ," assets:cash $4.82"- ," equity:opening balances"- ,""- ]--journal1_str = unlines- [""- ,"2007/01/27 * joes diner"- ," expenses:food:dining $10.00"- ," expenses:gifts $10.00"- ," assets:checking $-20.00"- ,""- ,""- ,"2007/01/28 coopportunity"- ," expenses:food:groceries $47.18"- ," assets:checking $-47.18"- ,""- ,""- ]--journal2_str = unlines- [";comment"- ,"2007/01/27 * joes diner"- ," expenses:food:dining $10.00"- ," assets:checking $-47.18"- ,""- ]--journal3_str = unlines- ["2007/01/27 * joes diner"- ," expenses:food:dining $10.00"- ,";intra-entry comment"- ," assets:checking $-47.18"- ,""- ]--journal4_str = unlines- ["!include \"somefile\""- ,"2007/01/27 * joes diner"- ," expenses:food:dining $10.00"- ," assets:checking $-47.18"- ,""- ]--journal5_str = ""--journal6_str = unlines- ["~ monthly from 2007/1/21"- ," expenses:entertainment $16.23 ;netflix"- ," assets:checking"- ,""- ,"; 2007/01/01 * opening balance"- ,"; assets:saving $200.04"- ,"; equity:opening balances "- ,""- ]--journal7_str = unlines- ["2007/01/01 * opening balance"- ," assets:cash $4.82"- ," equity:opening balances "- ,""- ,"2007/01/01 * opening balance"- ," income:interest $-4.82"- ," equity:opening balances "- ,""- ,"2007/01/02 * ayres suites"- ," expenses:vacation $179.92"- ," assets:checking "- ,""- ,"2007/01/02 * auto transfer to savings"- ," assets:saving $200.00"- ," assets:checking "- ,""- ,"2007/01/03 * poquito mas"- ," expenses:food:dining $4.82"- ," assets:cash "- ,""- ,"2007/01/03 * verizon"- ," expenses:phone $95.11"- ," assets:checking "- ,""- ,"2007/01/03 * discover"- ," liabilities:credit cards:discover $80.00"- ," assets:checking "- ,""- ,"2007/01/04 * blue cross"- ," expenses:health:insurance $90.00"- ," assets:checking "- ,""- ,"2007/01/05 * village market liquor"- ," expenses:food:dining $6.48"- ," assets:checking "- ,""- ]--journal7 = Journal- [] - [] - [- txnTieKnot $ Transaction {- tdate=parsedate "2007/01/01",- teffectivedate=Nothing,- tstatus=False,- tcode="*",- tdescription="opening balance",- tcomment="",- tmetadata=[],- tpostings=[- Posting {- pstatus=False,- paccount="assets:cash",- pamount=(Mixed [dollars 4.82]),- pcomment="",- ptype=RegularPosting,- pmetadata=[],- ptransaction=Nothing- },- Posting {- pstatus=False,- paccount="equity:opening balances",- pamount=(Mixed [dollars (-4.82)]),- pcomment="",- ptype=RegularPosting,- pmetadata=[],- ptransaction=Nothing- }- ],- tpreceding_comment_lines=""- }- ,- txnTieKnot $ Transaction {- tdate=parsedate "2007/02/01",- teffectivedate=Nothing,- tstatus=False,- tcode="*",- tdescription="ayres suites",- tcomment="",- tmetadata=[],- tpostings=[- Posting {- pstatus=False,- paccount="expenses:vacation",- pamount=(Mixed [dollars 179.92]),- pcomment="",- ptype=RegularPosting,- pmetadata=[],- ptransaction=Nothing- },- Posting {- pstatus=False,- paccount="assets:checking",- pamount=(Mixed [dollars (-179.92)]),- pcomment="",- ptype=RegularPosting,- pmetadata=[],- ptransaction=Nothing- }- ],- tpreceding_comment_lines=""- }- ,- txnTieKnot $ Transaction {- tdate=parsedate "2007/01/02",- teffectivedate=Nothing,- tstatus=False,- tcode="*",- tdescription="auto transfer to savings",- tcomment="",- tmetadata=[],- tpostings=[- Posting {- pstatus=False,- paccount="assets:saving",- pamount=(Mixed [dollars 200]),- pcomment="",- ptype=RegularPosting,- pmetadata=[],- ptransaction=Nothing- },- Posting {- pstatus=False,- paccount="assets:checking",- pamount=(Mixed [dollars (-200)]),- pcomment="",- ptype=RegularPosting,- pmetadata=[],- ptransaction=Nothing- }- ],- tpreceding_comment_lines=""- }- ,- txnTieKnot $ Transaction {- tdate=parsedate "2007/01/03",- teffectivedate=Nothing,- tstatus=False,- tcode="*",- tdescription="poquito mas",- tcomment="",- tmetadata=[],- tpostings=[- Posting {- pstatus=False,- paccount="expenses:food:dining",- pamount=(Mixed [dollars 4.82]),- pcomment="",- ptype=RegularPosting,- pmetadata=[],- ptransaction=Nothing- },- Posting {- pstatus=False,- paccount="assets:cash",- pamount=(Mixed [dollars (-4.82)]),- pcomment="",- ptype=RegularPosting,- pmetadata=[],- ptransaction=Nothing- }- ],- tpreceding_comment_lines=""- }- ,- txnTieKnot $ Transaction {- tdate=parsedate "2007/01/03",- teffectivedate=Nothing,- tstatus=False,- tcode="*",- tdescription="verizon",- tcomment="",- tmetadata=[],- tpostings=[- Posting {- pstatus=False,- paccount="expenses:phone",- pamount=(Mixed [dollars 95.11]),- pcomment="",- ptype=RegularPosting,- pmetadata=[],- ptransaction=Nothing- },- Posting {- pstatus=False,- paccount="assets:checking",- pamount=(Mixed [dollars (-95.11)]),- pcomment="",- ptype=RegularPosting,- pmetadata=[],- ptransaction=Nothing- }- ],- tpreceding_comment_lines=""- }- ,- txnTieKnot $ Transaction {- tdate=parsedate "2007/01/03",- teffectivedate=Nothing,- tstatus=False,- tcode="*",- tdescription="discover",- tcomment="",- tmetadata=[],- tpostings=[- Posting {- pstatus=False,- paccount="liabilities:credit cards:discover",- pamount=(Mixed [dollars 80]),- pcomment="",- ptype=RegularPosting,- pmetadata=[],- ptransaction=Nothing- },- Posting {- pstatus=False,- paccount="assets:checking",- pamount=(Mixed [dollars (-80)]),- pcomment="",- ptype=RegularPosting,- pmetadata=[],- ptransaction=Nothing- }- ],- tpreceding_comment_lines=""- }- ]- []- []- ""- nullctx- []- (TOD 0 0)--ledger7 = journalToLedger nullfilterspec journal7--journal8_str = unlines- ["2008/1/1 test "- ," a:b 10h @ $40"- ," c:d "- ,""- ]--timelogentry1_str = "i 2007/03/11 16:19:00 hledger\n"-timelogentry1 = TimeLogEntry In (parsedatetime "2007/03/11 16:19:00") "hledger"--timelogentry2_str = "o 2007/03/11 16:30:00\n"-timelogentry2 = TimeLogEntry Out (parsedatetime "2007/03/11 16:30:00") ""--a1 = Mixed [(hours 1){price=Just $ Mixed [Amount (comm "$") 10 Nothing]}]-a2 = Mixed [(hours 2){price=Just $ Mixed [Amount (comm "EUR") 10 Nothing]}]-a3 = Mixed $ amounts a1 ++ amounts a2--journalWithAmounts :: [String] -> Journal-journalWithAmounts as =- Journal- []- []- [t | a <- as, let t = nulltransaction{tdescription=a,tpostings=[nullposting{pamount=parse a,ptransaction=Just t}]}]- []- []- ""- nullctx- []- (TOD 0 0)- where parse = fromparse . parseWithCtx nullctx someamount+hledger's doctests are shell commands with expected output in literal+blocks in the haddock documentation, run by doing @make doctest@ in the+hledger source tree. They are hardly used, but here is an example:++@+$ bin/hledger -f data/sample.journal balance o+ $1 expenses:food+ $-2 income+ $-1 gifts+ $-1 salary+--------------------+ $-1+@++-}++module Hledger.Cli.Tests+where+import System.Exit (exitFailure, exitWith, ExitCode(ExitSuccess)) -- base 3 compatible++import Hledger.Data -- including testing utils in Hledger.Data.Utils+import Hledger.Cli+++-- | Run unit tests.+runtests :: [Opt] -> [String] -> IO ()+runtests _ args = do+ (counts,_) <- liftM (flip (,) 0) $ runTestTT ts+ if errors counts > 0 || (failures counts > 0)+ then exitFailure+ else exitWith ExitSuccess+ where+ ts = TestList $ filter matchname $ tflatten tests_Hledger_Cli -- show flat test names+ -- ts = tfilter matchname $ TestList tests -- show hierarchical test names+ matchname = matchpats args . tname
Hledger/Cli/Utils.hs view
@@ -19,6 +19,7 @@ writeFileWithBackup, writeFileWithBackupIfChanged, readFileStrictly,+ Test(TestList), ) where import Hledger.Data
Hledger/Cli/Version.hs view
@@ -17,7 +17,7 @@ -- version and PATCHLEVEL are set by the make process -version = "0.13.0"+version = "0.14.0" #ifdef PATCHLEVEL patchlevel = "." ++ show PATCHLEVEL -- must be numeric !
hledger.cabal view
@@ -1,15 +1,13 @@ name: hledger-version: 0.13+version: 0.14 category: Finance-synopsis: A command-line double-entry accounting tool.+synopsis: A robust command-line accounting tool with a simple human-editable data format, similar to ledger. description:- hledger reads a plain text general journal or time log- describing your transactions and displays precise- balance and register reports on the console.- It is a remix, in haskell, of John Wiegley's excellent c++- ledger. hledger aims to be a practical, accessible tool- for end users and a useful library for finance-minded- haskell programmers.+ hledger is a haskell port and friendly fork of John Wiegley's ledger accounting tool.+ This package provides the main hledger command-line tool; see the other hledger-* packages for web and curses interfaces and chart generation.+ hledger aims to be a reliable, practical financial reporting tool for day-to-day use, and also a useful library for building financial apps in haskell.+ Given a plain text file describing transactions, of money or any other commodity, hledger will print the chart of accounts, account balances, or transactions you're interested in.+ It can also help you add transactions to the journal file, or convert CSV data from your bank. license: GPL license-file: LICENSE@@ -41,7 +39,7 @@ Hledger.Cli.Tests Hledger.Cli.Utils Hledger.Cli.Version- Hledger.Cli.Commands+ Hledger.Cli Hledger.Cli.Add Hledger.Cli.Balance Hledger.Cli.Convert@@ -50,7 +48,7 @@ Hledger.Cli.Register Hledger.Cli.Stats build-depends:- hledger-lib == 0.13+ hledger-lib == 0.14 ,HUnit ,base >= 3 && < 5 ,containers@@ -61,7 +59,7 @@ ,old-locale ,old-time ,parsec- ,process >= 1.0.1.4 && < 1.1+ ,process ,regexpr >= 0.5.1 ,safe >= 0.2 ,split == 0.1.*@@ -80,7 +78,7 @@ Hledger.Cli.Tests Hledger.Cli.Utils Hledger.Cli.Version- Hledger.Cli.Commands+ Hledger.Cli Hledger.Cli.Add Hledger.Cli.Balance Hledger.Cli.Convert@@ -89,7 +87,7 @@ Hledger.Cli.Register Hledger.Cli.Stats build-depends:- hledger-lib == 0.13+ hledger-lib == 0.14 ,HUnit ,base >= 3 && < 5 ,containers