1 {- | 2 hledger's test suite. Most tests are HUnit-based, and defined in the 3 @tests@ list below. These tests are built in to hledger and can be run at 4 any time with @hledger test@. 5 6 In addition, we have tests in doctest format, which can be run with @make 7 doctest@ in the hledger source tree. These have some advantages: 8 9 - easier to read and write than hunit, for functional/shell tests 10 11 - easier to read multi-line output from failing tests 12 13 - can also appear in, and test, docs 14 15 and disadvantages: 16 17 - not included in hledger's built-in tests 18 19 - not platform independent 20 21 Here are the hledger doctests (some may reappear in other modules as 22 examples): 23 24 Run a few with c++ ledger first: 25 26 @ 27 $ ledger -f sample.ledger balance 28 $-1 assets 29 $1 bank:saving 30 $-2 cash 31 $2 expenses 32 $1 food 33 $1 supplies 34 $-2 income 35 $-1 gifts 36 $-1 salary 37 $1 liabilities:debts 38 @ 39 40 @ 41 $ ledger -f sample.ledger balance o 42 $1 expenses:food 43 $-2 income 44 $-1 gifts 45 $-1 salary 46 -------------------- 47 $-1 48 @ 49 50 Then hledger: 51 52 @ 53 $ hledger -f sample.ledger balance 54 $-1 assets 55 $1 bank:saving 56 $-2 cash 57 $2 expenses 58 $1 food 59 $1 supplies 60 $-2 income 61 $-1 gifts 62 $-1 salary 63 $1 liabilities:debts 64 @ 65 66 @ 67 $ hledger -f sample.ledger balance o 68 $1 expenses:food 69 $-2 income 70 $-1 gifts 71 $-1 salary 72 -------------------- 73 $-1 74 @ 75 76 @ 77 $ hledger -f sample.ledger balance --depth 1 78 $-1 assets 79 $2 expenses 80 $-2 income 81 $1 liabilities 82 @ 83 -} 84 {- 85 @ 86 $ printf "2009/1/1 a\n b 1.1\n c -1\n" | runhaskell hledger.hs -f- reg 2>&1 ; true 87 hledger.hs: could not balance this transaction, amounts do not add up to zero: 88 2009/01/01 a 89 b 1.1 90 c -1 91 92 93 @ 94 95 @ 96 $ printf "2009/1/1 x\n (virtual) 100\n a 1\n b\n" | runhaskell hledger.hs -f- print 2>&1 ; true 97 2009/01/01 x 98 (virtual) 100 99 a 1 100 b 101 102 @ 103 104 Unicode input/output tests 105 106 -- layout of the balance command with unicode names 107 @ 108 $ printf "2009-01-01 проверка\n τράπεζα 10 руб\n नकद\n" | hledger -f - bal 109 10 руб τράπεζα 110 -10 руб नकद 111 @ 112 113 -- layout of the register command with unicode names 114 @ 115 $ printf "2009-01-01 проверка\n τράπεζα 10 руб\n नकद\n" | hledger -f - reg 116 2009/01/01 проверка τράπεζα 10 руб 10 руб 117 नकद -10 руб 0 118 @ 119 120 -- layout of the print command with unicode names 121 @ 122 $ printf "2009-01-01 проверка\n счёт:первый 1\n счёт:второй\n" | hledger -f - print 123 2009/01/01 проверка 124 счёт:первый 1 125 счёт:второй 126 127 @ 128 129 -- search for unicode account names 130 @ 131 $ printf "2009-01-01 проверка\n τράπεζα 10 руб\n नकद\n" | hledger -f - reg τράπ 132 2009/01/01 проверка τράπεζα 10 руб 10 руб 133 @ 134 135 -- search for unicode descriptions (should choose only the first entry) 136 @ 137 $ printf "2009-01-01 аура (cyrillic letters)\n bank 10\n cash\n2010-01-01 aypa (roman letters)\n bank 20\n cash\n" | hledger -f - reg desc:аура 138 2009/01/01 аура (cyrillic let.. bank 10 10 139 cash -10 0 140 @ 141 142 -- error message with unicode in ledger 143 -- not implemented yet 144 --@ 145 $ printf "2009-01-01 broken entry\n дебит 1\n кредит -2\n" | hledger -f - 2>&1 ; true 146 hledger: could not balance this transaction, amounts do not add up to zero: 147 2009/01/01 broken entry 148 дебит 1 149 кредит -2 150 151 152 --@ 153 154 @ 155 $ printf "2009-01-01 x\n a 2\n b (b) b -1\n c\n" | hledger -f - print 2>&1; true 156 2009/01/01 x 157 a 2 158 b (b) b -1 159 c 160 161 @ 162 163 -} 164 -- other test tools: 165 -- http://hackage.haskell.org/cgi-bin/hackage-scripts/package/test-framework 166 -- http://hackage.haskell.org/cgi-bin/hackage-scripts/package/HTF 167 168 module Tests 169 where 170 import qualified Data.Map as Map 171 import Data.Time.Format 172 import Locale (defaultTimeLocale) 173 import Text.ParserCombinators.Parsec 174 import Test.HUnit 175 import Test.HUnit.Tools (assertRaises, runVerboseTests) 176 177 import Commands.All 178 import Ledger 179 import Options 180 import Utils 181 182 183 runtests opts args = runner flattests 184 where 185 runner | (Verbose `elem` opts) = runVerboseTests 186 | otherwise = \t -> runTestTT t >>= return . (flip (,) 0) 187 flattests = TestList $ filter matchname $ concatMap tflatten tests 188 deeptests = tfilter matchname $ TestList tests 189 matchname = matchpats args . tname 190 191 -- | Get a Test's label, or the empty string. 192 tname :: Test -> String 193 tname (TestLabel n _) = n 194 tname _ = "" 195 196 -- | Flatten a Test containing TestLists into a list of single tests. 197 tflatten :: Test -> [Test] 198 tflatten (TestLabel _ t@(TestList _)) = tflatten t 199 tflatten (TestList ts) = concatMap tflatten ts 200 tflatten t = [t] 201 202 -- | Filter TestLists in a Test, recursively, preserving the structure. 203 tfilter :: (Test -> Bool) -> Test -> Test 204 tfilter p (TestLabel l ts) = TestLabel l (tfilter p ts) 205 tfilter p (TestList ts) = TestList $ filter (any p . tflatten) $ map (tfilter p) ts 206 tfilter _ t = t 207 208 -- | Simple way to assert something is some expected value, with no label. 209 is :: (Eq a, Show a) => a -> a -> Assertion 210 a `is` e = assertEqual "" e a 211 212 -- | Assert a parse result is some expected value, or print a parse error. 213 parseis :: (Show a, Eq a) => (Either ParseError a) -> a -> Assertion 214 parse `parseis` expected = either printParseError (`is` expected) parse 215 216 parseWithCtx :: GenParser Char LedgerFileCtx a -> String -> Either ParseError a 217 parseWithCtx p ts = runParser p emptyCtx "" ts 218 219 ------------------------------------------------------------------------------ 220 -- | Tests for any function or topic. Mostly ordered by test name. 221 tests :: [Test] 222 tests = [ 223 224 "account directive" ~: 225 let sameParse str1 str2 = do l1 <- rawLedgerFromString str1 226 l2 <- rawLedgerFromString str2 227 l1 `is` l2 228 in TestList 229 [ 230 "account directive 1" ~: sameParse 231 "2008/12/07 One\n test:from $-1\n test:to $1\n" 232 "!account test\n2008/12/07 One\n from $-1\n to $1\n" 233 234 ,"account directive 2" ~: sameParse 235 "2008/12/07 One\n test:foo:from $-1\n test:foo:to $1\n" 236 "!account test\n!account foo\n2008/12/07 One\n from $-1\n to $1\n" 237 238 ,"account directive 3" ~: sameParse 239 "2008/12/07 One\n test:from $-1\n test:to $1\n" 240 "!account test\n!account foo\n!end\n2008/12/07 One\n from $-1\n to $1\n" 241 242 ,"account directive 4" ~: sameParse 243 ("2008/12/07 One\n alpha $-1\n beta $1\n" ++ 244 "!account outer\n2008/12/07 Two\n aigh $-2\n bee $2\n" ++ 245 "!account inner\n2008/12/07 Three\n gamma $-3\n delta $3\n" ++ 246 "!end\n2008/12/07 Four\n why $-4\n zed $4\n" ++ 247 "!end\n2008/12/07 Five\n foo $-5\n bar $5\n" 248 ) 249 ("2008/12/07 One\n alpha $-1\n beta $1\n" ++ 250 "2008/12/07 Two\n outer:aigh $-2\n outer:bee $2\n" ++ 251 "2008/12/07 Three\n outer:inner:gamma $-3\n outer:inner:delta $3\n" ++ 252 "2008/12/07 Four\n outer:why $-4\n outer:zed $4\n" ++ 253 "2008/12/07 Five\n foo $-5\n bar $5\n" 254 ) 255 ] 256 257 ,"accountnames" ~: do 258 accountnames ledger7 `is` 259 ["assets","assets:cash","assets:checking","assets:saving","equity","equity:opening balances", 260 "expenses","expenses:food","expenses:food:dining","expenses:phone","expenses:vacation", 261 "liabilities","liabilities:credit cards","liabilities:credit cards:discover"] 262 263 ,"accountNameTreeFrom" ~: do 264 accountNameTreeFrom ["a"] `is` Node "top" [Node "a" []] 265 accountNameTreeFrom ["a","b"] `is` Node "top" [Node "a" [], Node "b" []] 266 accountNameTreeFrom ["a","a:b"] `is` Node "top" [Node "a" [Node "a:b" []]] 267 accountNameTreeFrom ["a:b:c"] `is` Node "top" [Node "a" [Node "a:b" [Node "a:b:c" []]]] 268 269 ,"amount arithmetic" ~: do 270 let a1 = dollars 1.23 271 let a2 = Amount (comm "$") (-1.23) Nothing 272 let a3 = Amount (comm "$") (-1.23) Nothing 273 (a1 + a2) `is` Amount (comm "$") 0 Nothing 274 (a1 + a3) `is` Amount (comm "$") 0 Nothing 275 (a2 + a3) `is` Amount (comm "$") (-2.46) Nothing 276 (a3 + a3) `is` Amount (comm "$") (-2.46) Nothing 277 (sum [a2,a3]) `is` Amount (comm "$") (-2.46) Nothing 278 (sum [a3,a3]) `is` Amount (comm "$") (-2.46) Nothing 279 (sum [a1,a2,a3,-a3]) `is` Amount (comm "$") 0 Nothing 280 281 ,"balance report tests" ~: 282 let (opts,args) `gives` es = do 283 l <- sampleledgerwithopts opts args 284 showBalanceReport opts args l `is` unlines es 285 in TestList 286 [ 287 288 "balance report with no args" ~: 289 ([], []) `gives` 290 [" $-1 assets" 291 ," $1 bank:saving" 292 ," $-2 cash" 293 ," $2 expenses" 294 ," $1 food" 295 ," $1 supplies" 296 ," $-2 income" 297 ," $-1 gifts" 298 ," $-1 salary" 299 ," $1 liabilities:debts" 300 ] 301 302 ,"balance report can be limited with --depth" ~: 303 ([Depth "1"], []) `gives` 304 [" $-1 assets" 305 ," $2 expenses" 306 ," $-2 income" 307 ," $1 liabilities" 308 ] 309 310 ,"balance report with account pattern o" ~: 311 ([SubTotal], ["o"]) `gives` 312 [" $1 expenses:food" 313 ," $-2 income" 314 ," $-1 gifts" 315 ," $-1 salary" 316 ,"--------------------" 317 ," $-1" 318 ] 319 320 ,"balance report with account pattern o and --depth 1" ~: 321 ([Depth "1"], ["o"]) `gives` 322 [" $1 expenses" 323 ," $-2 income" 324 ,"--------------------" 325 ," $-1" 326 ] 327 328 ,"balance report with account pattern a" ~: 329 ([], ["a"]) `gives` 330 [" $-1 assets" 331 ," $1 bank:saving" 332 ," $-2 cash" 333 ," $-1 income:salary" 334 ," $1 liabilities:debts" 335 ,"--------------------" 336 ," $-1" 337 ] 338 339 ,"balance report with account pattern e" ~: 340 ([], ["e"]) `gives` 341 [" $-1 assets" 342 ," $1 bank:saving" 343 ," $-2 cash" 344 ," $2 expenses" 345 ," $1 food" 346 ," $1 supplies" 347 ," $-2 income" 348 ," $-1 gifts" 349 ," $-1 salary" 350 ," $1 liabilities:debts" 351 ] 352 353 ,"balance report with unmatched parent of two matched subaccounts" ~: 354 ([], ["cash","saving"]) `gives` 355 [" $-1 assets" 356 ," $1 bank:saving" 357 ," $-2 cash" 358 ,"--------------------" 359 ," $-1" 360 ] 361 362 ,"balance report with multi-part account name" ~: 363 ([], ["expenses:food"]) `gives` 364 [" $1 expenses:food" 365 ,"--------------------" 366 ," $1" 367 ] 368 369 ,"balance report with negative account pattern" ~: 370 ([], ["not:assets"]) `gives` 371 [" $2 expenses" 372 ," $1 food" 373 ," $1 supplies" 374 ," $-2 income" 375 ," $-1 gifts" 376 ," $-1 salary" 377 ," $1 liabilities:debts" 378 ,"--------------------" 379 ," $1" 380 ] 381 382 ,"balance report negative account pattern always matches full name" ~: 383 ([], ["not:e"]) `gives` [] 384 385 ,"balance report negative patterns affect totals" ~: 386 ([], ["expenses","not:food"]) `gives` 387 [" $1 expenses:supplies" 388 ,"--------------------" 389 ," $1" 390 ] 391 392 ,"balance report with -E shows zero-balance accounts" ~: 393 ([SubTotal,Empty], ["assets"]) `gives` 394 [" $-1 assets" 395 ," $1 bank" 396 ," $0 checking" 397 ," $1 saving" 398 ," $-2 cash" 399 ,"--------------------" 400 ," $-1" 401 ] 402 403 ,"balance report with cost basis" ~: do 404 rl <- rawLedgerFromString $ unlines 405 ["" 406 ,"2008/1/1 test " 407 ," a:b 10h @ $50" 408 ," c:d " 409 ,"" 410 ] 411 let l = cacheLedger [] $ 412 filterRawLedger (DateSpan Nothing Nothing) [] Nothing False $ 413 canonicaliseAmounts True rl -- enable cost basis adjustment 414 showBalanceReport [] [] l `is` 415 unlines 416 [" $500 a:b" 417 ," $-500 c:d" 418 ] 419 420 ,"balance report elides zero-balance root account(s)" ~: do 421 l <- ledgerFromStringWithOpts [] [] sampletime 422 (unlines 423 ["2008/1/1 one" 424 ," test:a 1" 425 ," test:b" 426 ]) 427 showBalanceReport [] [] l `is` 428 unlines 429 [" 1 test:a" 430 ," -1 test:b" 431 ] 432 433 ] 434 435 ,"balanceLedgerTransaction" ~: do 436 assertBool "detect unbalanced entry, sign error" 437 (isLeft $ balanceLedgerTransaction 438 (LedgerTransaction (parsedate "2007/01/28") False "" "test" "" 439 [Posting False "a" (Mixed [dollars 1]) "" RegularPosting, 440 Posting False "b" (Mixed [dollars 1]) "" RegularPosting 441 ] "")) 442 assertBool "detect unbalanced entry, multiple missing amounts" 443 (isLeft $ balanceLedgerTransaction 444 (LedgerTransaction (parsedate "2007/01/28") False "" "test" "" 445 [Posting False "a" missingamt "" RegularPosting, 446 Posting False "b" missingamt "" RegularPosting 447 ] "")) 448 let e = balanceLedgerTransaction (LedgerTransaction (parsedate "2007/01/28") False "" "test" "" 449 [Posting False "a" (Mixed [dollars 1]) "" RegularPosting, 450 Posting False "b" missingamt "" RegularPosting 451 ] "") 452 assertBool "one missing amount should be ok" (isRight e) 453 assertEqual "balancing amount is added" 454 (Mixed [dollars (-1)]) 455 (case e of 456 Right e' -> (pamount $ last $ ltpostings e') 457 Left _ -> error "should not happen") 458 459 ,"cacheLedger" ~: do 460 (length $ Map.keys $ accountmap $ cacheLedger [] rawledger7) `is` 15 461 462 ,"canonicaliseAmounts" ~: 463 "use the greatest precision" ~: do 464 (rawLedgerPrecisions $ canonicaliseAmounts False $ rawLedgerWithAmounts ["1","2.00"]) `is` [2,2] 465 466 ,"commodities" ~: do 467 commodities ledger7 `is` [Commodity {symbol="$", side=L, spaced=False, comma=False, precision=2}] 468 469 ,"dateSpanFromOpts" ~: do 470 let todaysdate = parsedate "2008/11/26" 471 let opts `gives` spans = show (dateSpanFromOpts todaysdate opts) `is` spans 472 [] `gives` "DateSpan Nothing Nothing" 473 [Begin "2008", End "2009"] `gives` "DateSpan (Just 2008-01-01) (Just 2009-01-01)" 474 [Period "in 2008"] `gives` "DateSpan (Just 2008-01-01) (Just 2009-01-01)" 475 [Begin "2005", End "2007",Period "in 2008"] `gives` "DateSpan (Just 2008-01-01) (Just 2009-01-01)" 476 477 ,"entriesFromTimeLogEntries" ~: do 478 today <- getCurrentDay 479 now' <- getCurrentTime 480 tz <- getCurrentTimeZone 481 let now = utcToLocalTime tz now' 482 nowstr = showtime now 483 yesterday = prevday today 484 clockin t a = TimeLogEntry In t a 485 clockout t = TimeLogEntry Out t "" 486 mktime d s = LocalTime d $ fromMaybe midnight $ parseTime defaultTimeLocale "%H:%M:%S" s 487 showtime t = formatTime defaultTimeLocale "%H:%M" t 488 assertEntriesGiveStrings name es ss = assertEqual name ss (map ltdescription $ entriesFromTimeLogEntries now es) 489 490 assertEntriesGiveStrings "started yesterday, split session at midnight" 491 [clockin (mktime yesterday "23:00:00") ""] 492 ["23:00-23:59","00:00-"++nowstr] 493 assertEntriesGiveStrings "split multi-day sessions at each midnight" 494 [clockin (mktime (addDays (-2) today) "23:00:00") ""] 495 ["23:00-23:59","00:00-23:59","00:00-"++nowstr] 496 assertEntriesGiveStrings "auto-clock-out if needed" 497 [clockin (mktime today "00:00:00") ""] 498 ["00:00-"++nowstr] 499 let future = utcToLocalTime tz $ addUTCTime 100 now' 500 futurestr = showtime future 501 assertEntriesGiveStrings "use the clockin time for auto-clockout if it's in the future" 502 [clockin future ""] 503 [printf "%s-%s" futurestr futurestr] 504 505 ,"expandAccountNames" ~: do 506 expandAccountNames ["assets:cash","assets:checking","expenses:vacation"] `is` 507 ["assets","assets:cash","assets:checking","expenses","expenses:vacation"] 508 509 ,"intervalFromOpts" ~: do 510 let opts `gives` interval = intervalFromOpts opts `is` interval 511 [] `gives` NoInterval 512 [WeeklyOpt] `gives` Weekly 513 [MonthlyOpt] `gives` Monthly 514 [QuarterlyOpt] `gives` Quarterly 515 [YearlyOpt] `gives` Yearly 516 [Period "weekly"] `gives` Weekly 517 [Period "monthly"] `gives` Monthly 518 [Period "quarterly"] `gives` Quarterly 519 [WeeklyOpt, Period "yearly"] `gives` Yearly 520 521 ,"isAccountNamePrefixOf" ~: do 522 "assets" `isAccountNamePrefixOf` "assets" `is` False 523 "assets" `isAccountNamePrefixOf` "assets:bank" `is` True 524 "assets" `isAccountNamePrefixOf` "assets:bank:checking" `is` True 525 "my assets" `isAccountNamePrefixOf` "assets:bank" `is` False 526 527 ,"isLedgerTransactionBalanced" ~: do 528 assertBool "detect balanced" 529 (isLedgerTransactionBalanced 530 (LedgerTransaction (parsedate "2009/01/01") False "" "a" "" 531 [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting 532 ,Posting False "c" (Mixed [dollars (-1.00)]) "" RegularPosting 533 ] "")) 534 assertBool "detect unbalanced" 535 (not $ isLedgerTransactionBalanced 536 (LedgerTransaction (parsedate "2009/01/01") False "" "a" "" 537 [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting 538 ,Posting False "c" (Mixed [dollars (-1.01)]) "" RegularPosting 539 ] "")) 540 assertBool "detect unbalanced, one posting" 541 (not $ isLedgerTransactionBalanced 542 (LedgerTransaction (parsedate "2009/01/01") False "" "a" "" 543 [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting 544 ] "")) 545 assertBool "one zero posting is considered balanced for now" 546 (isLedgerTransactionBalanced 547 (LedgerTransaction (parsedate "2009/01/01") False "" "a" "" 548 [Posting False "b" (Mixed [dollars 0]) "" RegularPosting 549 ] "")) 550 assertBool "virtual postings don't need to balance" 551 (isLedgerTransactionBalanced 552 (LedgerTransaction (parsedate "2009/01/01") False "" "a" "" 553 [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting 554 ,Posting False "c" (Mixed [dollars (-1.00)]) "" RegularPosting 555 ,Posting False "d" (Mixed [dollars 100]) "" VirtualPosting 556 ] "")) 557 assertBool "balanced virtual postings need to balance among themselves" 558 (not $ isLedgerTransactionBalanced 559 (LedgerTransaction (parsedate "2009/01/01") False "" "a" "" 560 [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting 561 ,Posting False "c" (Mixed [dollars (-1.00)]) "" RegularPosting 562 ,Posting False "d" (Mixed [dollars 100]) "" BalancedVirtualPosting 563 ] "")) 564 assertBool "balanced virtual postings need to balance among themselves (2)" 565 (isLedgerTransactionBalanced 566 (LedgerTransaction (parsedate "2009/01/01") False "" "a" "" 567 [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting 568 ,Posting False "c" (Mixed [dollars (-1.00)]) "" RegularPosting 569 ,Posting False "d" (Mixed [dollars 100]) "" BalancedVirtualPosting 570 ,Posting False "e" (Mixed [dollars (-100)]) "" BalancedVirtualPosting 571 ] "")) 572 573 ,"isSubAccountNameOf" ~: do 574 "assets" `isSubAccountNameOf` "assets" `is` False 575 "assets:bank" `isSubAccountNameOf` "assets" `is` True 576 "assets:bank:checking" `isSubAccountNameOf` "assets" `is` False 577 "assets:bank" `isSubAccountNameOf` "my assets" `is` False 578 579 ,"default year" ~: do 580 rl <- rawLedgerFromString defaultyear_ledger_str 581 (ltdate $ head $ ledger_txns rl) `is` fromGregorian 2009 1 1 582 return () 583 584 ,"ledgerFile" ~: do 585 let now = getCurrentLocalTime 586 assertBool "ledgerFile should parse an empty file" $ (isRight $ parseWithCtx ledgerFile "") 587 r <- rawLedgerFromString "" -- don't know how to get it from ledgerFile 588 assertBool "ledgerFile parsing an empty file should give an empty ledger" $ null $ ledger_txns r 589 590 ,"ledgerHistoricalPrice" ~: do 591 parseWithCtx ledgerHistoricalPrice price1_str `parseis` price1 592 593 ,"ledgerTransaction" ~: do 594 parseWithCtx ledgerTransaction entry1_str `parseis` entry1 595 assertBool "ledgerTransaction should not parse just a date" 596 $ isLeft $ parseWithCtx ledgerTransaction "2009/1/1\n" 597 assertBool "ledgerTransaction should require some postings" 598 $ isLeft $ parseWithCtx ledgerTransaction "2009/1/1 a\n" 599 let t = parseWithCtx ledgerTransaction "2009/1/1 a ;comment\n b 1\n" 600 assertBool "ledgerTransaction should not include a comment in the description" 601 $ either (const False) ((== "a") . ltdescription) t 602 603 ,"ledgerposting" ~: do 604 parseWithCtx ledgerposting rawposting1_str `parseis` rawposting1 605 606 ,"parsedate" ~: do 607 parsedate "2008/02/03" `is` parsetimewith "%Y/%m/%d" "2008/02/03" sampledate 608 parsedate "2008-02-03" `is` parsetimewith "%Y/%m/%d" "2008/02/03" sampledate 609 610 ,"period expressions" ~: do 611 let todaysdate = parsedate "2008/11/26" 612 let str `gives` result = (show $ parsewith (periodexpr todaysdate) str) `is` ("Right "++result) 613 "from aug to oct" `gives` "(NoInterval,DateSpan (Just 2008-08-01) (Just 2008-10-01))" 614 "aug to oct" `gives` "(NoInterval,DateSpan (Just 2008-08-01) (Just 2008-10-01))" 615 "every day from aug to oct" `gives` "(Daily,DateSpan (Just 2008-08-01) (Just 2008-10-01))" 616 "daily from aug" `gives` "(Daily,DateSpan (Just 2008-08-01) Nothing)" 617 "every week to 2009" `gives` "(Weekly,DateSpan Nothing (Just 2009-01-01))" 618 619 ,"print report tests" ~: TestList 620 [ 621 622 "print expenses" ~: 623 do 624 let args = ["expenses"] 625 l <- sampleledgerwithopts [] args 626 showLedgerTransactions [] args l `is` unlines 627 ["2008/06/03 * eat & shop" 628 ," expenses:food $1" 629 ," expenses:supplies $1" 630 ," assets:cash" 631 ,"" 632 ] 633 634 , "print report with depth arg" ~: 635 do 636 l <- sampleledger 637 showLedgerTransactions [Depth "2"] [] l `is` unlines 638 ["2008/01/01 income" 639 ," income:salary $-1" 640 ,"" 641 ,"2008/06/01 gift" 642 ," income:gifts $-1" 643 ,"" 644 ,"2008/06/03 * eat & shop" 645 ," expenses:food $1" 646 ," expenses:supplies $1" 647 ," assets:cash" 648 ,"" 649 ,"2008/12/31 * pay off" 650 ," liabilities:debts $1" 651 ,"" 652 ] 653 654 ] 655 656 ,"punctuatethousands 1" ~: punctuatethousands "" `is` "" 657 658 ,"punctuatethousands 2" ~: punctuatethousands "1234567.8901" `is` "1,234,567.8901" 659 660 ,"punctuatethousands 3" ~: punctuatethousands "-100" `is` "-100" 661 662 ,"register report tests" ~: 663 let registerdates = filter (not . null) . map (strip . take 10) . lines 664 in 665 TestList 666 [ 667 668 "register report with no args" ~: 669 do 670 l <- sampleledger 671 showRegisterReport [] [] l `is` unlines 672 ["2008/01/01 income assets:bank:checking $1 $1" 673 ," income:salary $-1 0" 674 ,"2008/06/01 gift assets:bank:checking $1 $1" 675 ," income:gifts $-1 0" 676 ,"2008/06/02 save assets:bank:saving $1 $1" 677 ," assets:bank:checking $-1 0" 678 ,"2008/06/03 eat & shop expenses:food $1 $1" 679 ," expenses:supplies $1 $2" 680 ," assets:cash $-2 0" 681 ,"2008/12/31 pay off liabilities:debts $1 $1" 682 ," assets:bank:checking $-1 0" 683 ] 684 685 ,"register report with cleared arg" ~: 686 do 687 l <- ledgerFromStringWithOpts [Cleared] [] sampletime sample_ledger_str 688 showRegisterReport [Cleared] [] l `is` unlines 689 ["2008/06/03 eat & shop expenses:food $1 $1" 690 ," expenses:supplies $1 $2" 691 ," assets:cash $-2 0" 692 ,"2008/12/31 pay off liabilities:debts $1 $1" 693 ," assets:bank:checking $-1 0" 694 ] 695 696 ,"register report with uncleared arg" ~: 697 do 698 l <- ledgerFromStringWithOpts [UnCleared] [] sampletime sample_ledger_str 699 showRegisterReport [UnCleared] [] l `is` unlines 700 ["2008/01/01 income assets:bank:checking $1 $1" 701 ," income:salary $-1 0" 702 ,"2008/06/01 gift assets:bank:checking $1 $1" 703 ," income:gifts $-1 0" 704 ,"2008/06/02 save assets:bank:saving $1 $1" 705 ," assets:bank:checking $-1 0" 706 ] 707 708 ,"register report sorts by date" ~: 709 do 710 l <- ledgerFromStringWithOpts [] [] sampletime $ unlines 711 ["2008/02/02 a" 712 ," b 1" 713 ," c" 714 ,"" 715 ,"2008/01/01 d" 716 ," e 1" 717 ," f" 718 ] 719 registerdates (showRegisterReport [] [] l) `is` ["2008/01/01","2008/02/02"] 720 721 ,"register report with account pattern" ~: 722 do 723 l <- sampleledger 724 showRegisterReport [] ["cash"] l `is` unlines 725 ["2008/06/03 eat & shop assets:cash $-2 $-2" 726 ] 727 728 ,"register report with account pattern, case insensitive" ~: 729 do 730 l <- sampleledger 731 showRegisterReport [] ["cAsH"] l `is` unlines 732 ["2008/06/03 eat & shop assets:cash $-2 $-2" 733 ] 734 735 ,"register report with display expression" ~: 736 do 737 l <- sampleledger 738 let displayexpr `gives` dates = 739 registerdates (showRegisterReport [Display displayexpr] [] l) `is` dates 740 "d<[2008/6/2]" `gives` ["2008/01/01","2008/06/01"] 741 "d<=[2008/6/2]" `gives` ["2008/01/01","2008/06/01","2008/06/02"] 742 "d=[2008/6/2]" `gives` ["2008/06/02"] 743 "d>=[2008/6/2]" `gives` ["2008/06/02","2008/06/03","2008/12/31"] 744 "d>[2008/6/2]" `gives` ["2008/06/03","2008/12/31"] 745 746 ,"register report with period expression" ~: 747 do 748 l <- sampleledger 749 let periodexpr `gives` dates = do 750 lopts <- sampleledgerwithopts [Period periodexpr] [] 751 registerdates (showRegisterReport [Period periodexpr] [] lopts) `is` dates 752 "" `gives` ["2008/01/01","2008/06/01","2008/06/02","2008/06/03","2008/12/31"] 753 "2008" `gives` ["2008/01/01","2008/06/01","2008/06/02","2008/06/03","2008/12/31"] 754 "2007" `gives` [] 755 "june" `gives` ["2008/06/01","2008/06/02","2008/06/03"] 756 "monthly" `gives` ["2008/01/01","2008/06/01","2008/12/01"] 757 "quarterly" `gives` ["2008/01/01","2008/04/01","2008/10/01"] 758 showRegisterReport [Period "yearly"] [] l `is` unlines 759 ["2008/01/01 - 2008/12/31 assets:bank:saving $1 $1" 760 ," assets:cash $-2 $-1" 761 ," expenses:food $1 0" 762 ," expenses:supplies $1 $1" 763 ," income:gifts $-1 0" 764 ," income:salary $-1 $-1" 765 ," liabilities:debts $1 0" 766 ] 767 registerdates (showRegisterReport [Period "quarterly"] [] l) `is` ["2008/01/01","2008/04/01","2008/10/01"] 768 registerdates (showRegisterReport [Period "quarterly",Empty] [] l) `is` ["2008/01/01","2008/04/01","2008/07/01","2008/10/01"] 769 770 ] 771 772 , "register report with depth arg" ~: 773 do 774 l <- sampleledger 775 showRegisterReport [Depth "2"] [] l `is` unlines 776 ["2008/01/01 income income:salary $-1 $-1" 777 ,"2008/06/01 gift income:gifts $-1 $-2" 778 ,"2008/06/03 eat & shop expenses:food $1 $-1" 779 ," expenses:supplies $1 0" 780 ," assets:cash $-2 $-2" 781 ,"2008/12/31 pay off liabilities:debts $1 $-1" 782 ] 783 784 ,"show dollars" ~: show (dollars 1) ~?= "$1.00" 785 786 ,"show hours" ~: show (hours 1) ~?= "1.0h" 787 788 ,"showLedgerTransaction" ~: do 789 assertEqual "show a balanced transaction, eliding last amount" 790 (unlines 791 ["2007/01/28 coopportunity" 792 ," expenses:food:groceries $47.18" 793 ," assets:checking" 794 ,"" 795 ]) 796 (showLedgerTransaction 797 (LedgerTransaction (parsedate "2007/01/28") False "" "coopportunity" "" 798 [Posting False "expenses:food:groceries" (Mixed [dollars 47.18]) "" RegularPosting 799 ,Posting False "assets:checking" (Mixed [dollars (-47.18)]) "" RegularPosting 800 ] "")) 801 -- document some cases that arise in debug/testing: 802 assertEqual "show an unbalanced transaction, should not elide" 803 (unlines 804 ["2007/01/28 coopportunity" 805 ," expenses:food:groceries $47.18" 806 ," assets:checking $-47.19" 807 ,"" 808 ]) 809 (showLedgerTransaction 810 (LedgerTransaction (parsedate "2007/01/28") False "" "coopportunity" "" 811 [Posting False "expenses:food:groceries" (Mixed [dollars 47.18]) "" RegularPosting 812 ,Posting False "assets:checking" (Mixed [dollars (-47.19)]) "" RegularPosting 813 ] "")) 814 assertEqual "show an unbalanced transaction with one posting, should not elide" 815 (unlines 816 ["2007/01/28 coopportunity" 817 ," expenses:food:groceries $47.18" 818 ,"" 819 ]) 820 (showLedgerTransaction 821 (LedgerTransaction (parsedate "2007/01/28") False "" "coopportunity" "" 822 [Posting False "expenses:food:groceries" (Mixed [dollars 47.18]) "" RegularPosting 823 ] "")) 824 assertEqual "show a transaction with one posting and a missing amount" 825 (unlines 826 ["2007/01/28 coopportunity" 827 ," expenses:food:groceries " 828 ,"" 829 ]) 830 (showLedgerTransaction 831 (LedgerTransaction (parsedate "2007/01/28") False "" "coopportunity" "" 832 [Posting False "expenses:food:groceries" missingamt "" RegularPosting 833 ] "")) 834 835 ,"unicode in balance layout" ~: do 836 l <- ledgerFromStringWithOpts [] [] sampletime 837 "2009/01/01 * медвежья шкура\n расходы:покупки 100\n актив:наличные\n" 838 showBalanceReport [] [] l `is` unlines 839 [" -100 актив:наличные" 840 ," 100 расходы:покупки"] 841 842 ,"unicode in register layout" ~: do 843 l <- ledgerFromStringWithOpts [] [] sampletime 844 "2009/01/01 * медвежья шкура\n расходы:покупки 100\n актив:наличные\n" 845 showRegisterReport [] [] l `is` unlines 846 ["2009/01/01 медвежья шкура расходы:покупки 100 100" 847 ," актив:наличные -100 0"] 848 849 ,"smart dates" ~: do 850 let str `gives` datestr = fixSmartDateStr (parsedate "2008/11/26") str `is` datestr 851 "1999-12-02" `gives` "1999/12/02" 852 "1999.12.02" `gives` "1999/12/02" 853 "1999/3/2" `gives` "1999/03/02" 854 "19990302" `gives` "1999/03/02" 855 "2008/2" `gives` "2008/02/01" 856 "20/2" `gives` "0020/02/01" 857 "1000" `gives` "1000/01/01" 858 "4/2" `gives` "2008/04/02" 859 "2" `gives` "2008/11/02" 860 "January" `gives` "2008/01/01" 861 "feb" `gives` "2008/02/01" 862 "today" `gives` "2008/11/26" 863 "yesterday" `gives` "2008/11/25" 864 "tomorrow" `gives` "2008/11/27" 865 "this day" `gives` "2008/11/26" 866 "last day" `gives` "2008/11/25" 867 "next day" `gives` "2008/11/27" 868 "this week" `gives` "2008/11/24" -- last monday 869 "last week" `gives` "2008/11/17" -- previous monday 870 "next week" `gives` "2008/12/01" -- next monday 871 "this month" `gives` "2008/11/01" 872 "last month" `gives` "2008/10/01" 873 "next month" `gives` "2008/12/01" 874 "this quarter" `gives` "2008/10/01" 875 "last quarter" `gives` "2008/07/01" 876 "next quarter" `gives` "2009/01/01" 877 "this year" `gives` "2008/01/01" 878 "last year" `gives` "2007/01/01" 879 "next year" `gives` "2009/01/01" 880 -- "last wed" `gives` "2008/11/19" 881 -- "next friday" `gives` "2008/11/28" 882 -- "next january" `gives` "2009/01/01" 883 884 ,"splitSpan" ~: do 885 let (interval,span) `gives` spans = splitSpan interval span `is` spans 886 (NoInterval,mkdatespan "2008/01/01" "2009/01/01") `gives` 887 [mkdatespan "2008/01/01" "2009/01/01"] 888 (Quarterly,mkdatespan "2008/01/01" "2009/01/01") `gives` 889 [mkdatespan "2008/01/01" "2008/04/01" 890 ,mkdatespan "2008/04/01" "2008/07/01" 891 ,mkdatespan "2008/07/01" "2008/10/01" 892 ,mkdatespan "2008/10/01" "2009/01/01" 893 ] 894 (Quarterly,nulldatespan) `gives` 895 [nulldatespan] 896 (Daily,mkdatespan "2008/01/01" "2008/01/01") `gives` 897 [mkdatespan "2008/01/01" "2008/01/01"] 898 (Quarterly,mkdatespan "2008/01/01" "2008/01/01") `gives` 899 [mkdatespan "2008/01/01" "2008/01/01"] 900 901 ,"subAccounts" ~: do 902 l <- sampleledger 903 let a = ledgerAccount l "assets" 904 (map aname $ ledgerSubAccounts l a) `is` ["assets:bank","assets:cash"] 905 906 ,"summariseTransactionsInDateSpan" ~: do 907 let (b,e,tnum,depth,showempty,ts) `gives` summaryts = 908 summariseTransactionsInDateSpan (mkdatespan b e) tnum depth showempty ts `is` summaryts 909 let ts = 910 [ 911 nulltxn{tdescription="desc",taccount="expenses:food:groceries",tamount=Mixed [dollars 1]} 912 ,nulltxn{tdescription="desc",taccount="expenses:food:dining", tamount=Mixed [dollars 2]} 913 ,nulltxn{tdescription="desc",taccount="expenses:food", tamount=Mixed [dollars 4]} 914 ,nulltxn{tdescription="desc",taccount="expenses:food:dining", tamount=Mixed [dollars 8]} 915 ] 916 ("2008/01/01","2009/01/01",0,9999,False,[]) `gives` 917 [] 918 ("2008/01/01","2009/01/01",0,9999,True,[]) `gives` 919 [ 920 nulltxn{tdate=parsedate "2008/01/01",tdescription="- 2008/12/31"} 921 ] 922 ("2008/01/01","2009/01/01",0,9999,False,ts) `gives` 923 [ 924 nulltxn{tdate=parsedate "2008/01/01",tdescription="- 2008/12/31",taccount="expenses:food", tamount=Mixed [dollars 4]} 925 ,nulltxn{tdate=parsedate "2008/01/01",tdescription="- 2008/12/31",taccount="expenses:food:dining", tamount=Mixed [dollars 10]} 926 ,nulltxn{tdate=parsedate "2008/01/01",tdescription="- 2008/12/31",taccount="expenses:food:groceries",tamount=Mixed [dollars 1]} 927 ] 928 ("2008/01/01","2009/01/01",0,2,False,ts) `gives` 929 [ 930 nulltxn{tdate=parsedate "2008/01/01",tdescription="- 2008/12/31",taccount="expenses:food",tamount=Mixed [dollars 15]} 931 ] 932 ("2008/01/01","2009/01/01",0,1,False,ts) `gives` 933 [ 934 nulltxn{tdate=parsedate "2008/01/01",tdescription="- 2008/12/31",taccount="expenses",tamount=Mixed [dollars 15]} 935 ] 936 ("2008/01/01","2009/01/01",0,0,False,ts) `gives` 937 [ 938 nulltxn{tdate=parsedate "2008/01/01",tdescription="- 2008/12/31",taccount="",tamount=Mixed [dollars 15]} 939 ] 940 941 ,"postingamount" ~: do 942 parseWithCtx postingamount " $47.18" `parseis` Mixed [dollars 47.18] 943 parseWithCtx postingamount " $1." `parseis` 944 Mixed [Amount (Commodity {symbol="$",side=L,spaced=False,comma=False,precision=0}) 1 Nothing] 945 946 ] 947 948 949 ------------------------------------------------------------------------------ 950 -- test data 951 952 sampledate = parsedate "2008/11/26" 953 sampletime = LocalTime sampledate midday 954 sampleledger = ledgerFromStringWithOpts [] [] sampletime sample_ledger_str 955 sampleledgerwithopts opts args = ledgerFromStringWithOpts opts args sampletime sample_ledger_str 956 957 sample_ledger_str = unlines 958 ["; A sample ledger file." 959 ,";" 960 ,"; Sets up this account tree:" 961 ,"; assets" 962 ,"; bank" 963 ,"; checking" 964 ,"; saving" 965 ,"; cash" 966 ,"; expenses" 967 ,"; food" 968 ,"; supplies" 969 ,"; income" 970 ,"; gifts" 971 ,"; salary" 972 ,"; liabilities" 973 ,"; debts" 974 ,"" 975 ,"2008/01/01 income" 976 ," assets:bank:checking $1" 977 ," income:salary" 978 ,"" 979 ,"2008/06/01 gift" 980 ," assets:bank:checking $1" 981 ," income:gifts" 982 ,"" 983 ,"2008/06/02 save" 984 ," assets:bank:saving $1" 985 ," assets:bank:checking" 986 ,"" 987 ,"2008/06/03 * eat & shop" 988 ," expenses:food $1" 989 ," expenses:supplies $1" 990 ," assets:cash" 991 ,"" 992 ,"2008/12/31 * pay off" 993 ," liabilities:debts $1" 994 ," assets:bank:checking" 995 ,"" 996 ,"" 997 ,";final comment" 998 ] 999 1000 defaultyear_ledger_str = unlines 1001 ["Y2009" 1002 ,"" 1003 ,"01/01 A" 1004 ," a $1" 1005 ," b" 1006 ] 1007 1008 write_sample_ledger = writeFile "sample.ledger" sample_ledger_str 1009 1010 rawposting1_str = " expenses:food:dining $10.00\n" 1011 1012 rawposting1 = Posting False "expenses:food:dining" (Mixed [dollars 10]) "" RegularPosting 1013 1014 entry1_str = unlines 1015 ["2007/01/28 coopportunity" 1016 ," expenses:food:groceries $47.18" 1017 ," assets:checking" 1018 ,"" 1019 ] 1020 1021 entry1 = 1022 (LedgerTransaction (parsedate "2007/01/28") False "" "coopportunity" "" 1023 [Posting False "expenses:food:groceries" (Mixed [dollars 47.18]) "" RegularPosting, 1024 Posting False "assets:checking" (Mixed [dollars (-47.18)]) "" RegularPosting] "") 1025 1026 1027 entry2_str = unlines 1028 ["2007/01/27 * joes diner" 1029 ," expenses:food:dining $10.00" 1030 ," expenses:gifts $10.00" 1031 ," assets:checking $-20.00" 1032 ,"" 1033 ] 1034 1035 entry3_str = unlines 1036 ["2007/01/01 * opening balance" 1037 ," assets:cash $4.82" 1038 ," equity:opening balances" 1039 ,"" 1040 ,"2007/01/01 * opening balance" 1041 ," assets:cash $4.82" 1042 ," equity:opening balances" 1043 ,"" 1044 ,"2007/01/28 coopportunity" 1045 ," expenses:food:groceries $47.18" 1046 ," assets:checking" 1047 ,"" 1048 ] 1049 1050 periodic_entry1_str = unlines 1051 ["~ monthly from 2007/2/2" 1052 ," assets:saving $200.00" 1053 ," assets:checking" 1054 ,"" 1055 ] 1056 1057 periodic_entry2_str = unlines 1058 ["~ monthly from 2007/2/2" 1059 ," assets:saving $200.00 ;auto savings" 1060 ," assets:checking" 1061 ,"" 1062 ] 1063 1064 periodic_entry3_str = unlines 1065 ["~ monthly from 2007/01/01" 1066 ," assets:cash $4.82" 1067 ," equity:opening balances" 1068 ,"" 1069 ,"~ monthly from 2007/01/01" 1070 ," assets:cash $4.82" 1071 ," equity:opening balances" 1072 ,"" 1073 ] 1074 1075 ledger1_str = unlines 1076 ["" 1077 ,"2007/01/27 * joes diner" 1078 ," expenses:food:dining $10.00" 1079 ," expenses:gifts $10.00" 1080 ," assets:checking $-20.00" 1081 ,"" 1082 ,"" 1083 ,"2007/01/28 coopportunity" 1084 ," expenses:food:groceries $47.18" 1085 ," assets:checking $-47.18" 1086 ,"" 1087 ,"" 1088 ] 1089 1090 ledger2_str = unlines 1091 [";comment" 1092 ,"2007/01/27 * joes diner" 1093 ," expenses:food:dining $10.00" 1094 ," assets:checking $-47.18" 1095 ,"" 1096 ] 1097 1098 ledger3_str = unlines 1099 ["2007/01/27 * joes diner" 1100 ," expenses:food:dining $10.00" 1101 ,";intra-entry comment" 1102 ," assets:checking $-47.18" 1103 ,"" 1104 ] 1105 1106 ledger4_str = unlines 1107 ["!include \"somefile\"" 1108 ,"2007/01/27 * joes diner" 1109 ," expenses:food:dining $10.00" 1110 ," assets:checking $-47.18" 1111 ,"" 1112 ] 1113 1114 ledger5_str = "" 1115 1116 ledger6_str = unlines 1117 ["~ monthly from 2007/1/21" 1118 ," expenses:entertainment $16.23 ;netflix" 1119 ," assets:checking" 1120 ,"" 1121 ,"; 2007/01/01 * opening balance" 1122 ,"; assets:saving $200.04" 1123 ,"; equity:opening balances " 1124 ,"" 1125 ] 1126 1127 ledger7_str = unlines 1128 ["2007/01/01 * opening balance" 1129 ," assets:cash $4.82" 1130 ," equity:opening balances " 1131 ,"" 1132 ,"2007/01/01 * opening balance" 1133 ," income:interest $-4.82" 1134 ," equity:opening balances " 1135 ,"" 1136 ,"2007/01/02 * ayres suites" 1137 ," expenses:vacation $179.92" 1138 ," assets:checking " 1139 ,"" 1140 ,"2007/01/02 * auto transfer to savings" 1141 ," assets:saving $200.00" 1142 ," assets:checking " 1143 ,"" 1144 ,"2007/01/03 * poquito mas" 1145 ," expenses:food:dining $4.82" 1146 ," assets:cash " 1147 ,"" 1148 ,"2007/01/03 * verizon" 1149 ," expenses:phone $95.11" 1150 ," assets:checking " 1151 ,"" 1152 ,"2007/01/03 * discover" 1153 ," liabilities:credit cards:discover $80.00" 1154 ," assets:checking " 1155 ,"" 1156 ,"2007/01/04 * blue cross" 1157 ," expenses:health:insurance $90.00" 1158 ," assets:checking " 1159 ,"" 1160 ,"2007/01/05 * village market liquor" 1161 ," expenses:food:dining $6.48" 1162 ," assets:checking " 1163 ,"" 1164 ] 1165 1166 rawledger7 = RawLedger 1167 [] 1168 [] 1169 [ 1170 LedgerTransaction { 1171 ltdate= parsedate "2007/01/01", 1172 ltstatus=False, 1173 ltcode="*", 1174 ltdescription="opening balance", 1175 ltcomment="", 1176 ltpostings=[ 1177 Posting { 1178 pstatus=False, 1179 paccount="assets:cash", 1180 pamount=(Mixed [dollars 4.82]), 1181 pcomment="", 1182 ptype=RegularPosting 1183 }, 1184 Posting { 1185 pstatus=False, 1186 paccount="equity:opening balances", 1187 pamount=(Mixed [dollars (-4.82)]), 1188 pcomment="", 1189 ptype=RegularPosting 1190 } 1191 ], 1192 ltpreceding_comment_lines="" 1193 } 1194 , 1195 LedgerTransaction { 1196 ltdate= parsedate "2007/02/01", 1197 ltstatus=False, 1198 ltcode="*", 1199 ltdescription="ayres suites", 1200 ltcomment="", 1201 ltpostings=[ 1202 Posting { 1203 pstatus=False, 1204 paccount="expenses:vacation", 1205 pamount=(Mixed [dollars 179.92]), 1206 pcomment="", 1207 ptype=RegularPosting 1208 }, 1209 Posting { 1210 pstatus=False, 1211 paccount="assets:checking", 1212 pamount=(Mixed [dollars (-179.92)]), 1213 pcomment="", 1214 ptype=RegularPosting 1215 } 1216 ], 1217 ltpreceding_comment_lines="" 1218 } 1219 , 1220 LedgerTransaction { 1221 ltdate=parsedate "2007/01/02", 1222 ltstatus=False, 1223 ltcode="*", 1224 ltdescription="auto transfer to savings", 1225 ltcomment="", 1226 ltpostings=[ 1227 Posting { 1228 pstatus=False, 1229 paccount="assets:saving", 1230 pamount=(Mixed [dollars 200]), 1231 pcomment="", 1232 ptype=RegularPosting 1233 }, 1234 Posting { 1235 pstatus=False, 1236 paccount="assets:checking", 1237 pamount=(Mixed [dollars (-200)]), 1238 pcomment="", 1239 ptype=RegularPosting 1240 } 1241 ], 1242 ltpreceding_comment_lines="" 1243 } 1244 , 1245 LedgerTransaction { 1246 ltdate=parsedate "2007/01/03", 1247 ltstatus=False, 1248 ltcode="*", 1249 ltdescription="poquito mas", 1250 ltcomment="", 1251 ltpostings=[ 1252 Posting { 1253 pstatus=False, 1254 paccount="expenses:food:dining", 1255 pamount=(Mixed [dollars 4.82]), 1256 pcomment="", 1257 ptype=RegularPosting 1258 }, 1259 Posting { 1260 pstatus=False, 1261 paccount="assets:cash", 1262 pamount=(Mixed [dollars (-4.82)]), 1263 pcomment="", 1264 ptype=RegularPosting 1265 } 1266 ], 1267 ltpreceding_comment_lines="" 1268 } 1269 , 1270 LedgerTransaction { 1271 ltdate=parsedate "2007/01/03", 1272 ltstatus=False, 1273 ltcode="*", 1274 ltdescription="verizon", 1275 ltcomment="", 1276 ltpostings=[ 1277 Posting { 1278 pstatus=False, 1279 paccount="expenses:phone", 1280 pamount=(Mixed [dollars 95.11]), 1281 pcomment="", 1282 ptype=RegularPosting 1283 }, 1284 Posting { 1285 pstatus=False, 1286 paccount="assets:checking", 1287 pamount=(Mixed [dollars (-95.11)]), 1288 pcomment="", 1289 ptype=RegularPosting 1290 } 1291 ], 1292 ltpreceding_comment_lines="" 1293 } 1294 , 1295 LedgerTransaction { 1296 ltdate=parsedate "2007/01/03", 1297 ltstatus=False, 1298 ltcode="*", 1299 ltdescription="discover", 1300 ltcomment="", 1301 ltpostings=[ 1302 Posting { 1303 pstatus=False, 1304 paccount="liabilities:credit cards:discover", 1305 pamount=(Mixed [dollars 80]), 1306 pcomment="", 1307 ptype=RegularPosting 1308 }, 1309 Posting { 1310 pstatus=False, 1311 paccount="assets:checking", 1312 pamount=(Mixed [dollars (-80)]), 1313 pcomment="", 1314 ptype=RegularPosting 1315 } 1316 ], 1317 ltpreceding_comment_lines="" 1318 } 1319 ] 1320 [] 1321 [] 1322 "" 1323 "" 1324 1325 ledger7 = cacheLedger [] rawledger7 1326 1327 ledger8_str = unlines 1328 ["2008/1/1 test " 1329 ," a:b 10h @ $40" 1330 ," c:d " 1331 ,"" 1332 ] 1333 1334 timelogentry1_str = "i 2007/03/11 16:19:00 hledger\n" 1335 timelogentry1 = TimeLogEntry In (parsedatetime "2007/03/11 16:19:00") "hledger" 1336 1337 timelogentry2_str = "o 2007/03/11 16:30:00\n" 1338 timelogentry2 = TimeLogEntry Out (parsedatetime "2007/03/11 16:30:00") "" 1339 1340 price1_str = "P 2004/05/01 XYZ $55\n" 1341 price1 = HistoricalPrice (parsedate "2004/05/01") "XYZ" "$" 55 1342 1343 a1 = Mixed [(hours 1){price=Just $ Mixed [Amount (comm "$") 10 Nothing]}] 1344 a2 = Mixed [(hours 2){price=Just $ Mixed [Amount (comm "EUR") 10 Nothing]}] 1345 a3 = Mixed $ (amounts a1) ++ (amounts a2) 1346 1347 rawLedgerWithAmounts :: [String] -> RawLedger 1348 rawLedgerWithAmounts as = 1349 RawLedger 1350 [] 1351 [] 1352 [nullledgertxn{ltdescription=a,ltpostings=[nullrawposting{pamount=parse a}]} | a <- as] 1353 [] 1354 [] 1355 "" 1356 "" 1357 where parse = fromparse . parseWithCtx postingamount . (" "++) 1358