packages feed

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 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