packages feed

hledger 0.17 → 0.18

raw patch · 18 files changed

+460/−1219 lines, 18 filesdep +shakespeare-textdep +textdep −csvdep ~hledger-libPVP ok

version bump matches the API change (PVP)

Dependencies added: shakespeare-text, text

Dependencies removed: csv

Dependency ranges changed: hledger-lib

API changes (from Hackage documentation)

- Hledger.Cli.Convert: CsvRules :: Maybe FieldPosition -> Maybe String -> Maybe FieldPosition -> Maybe FieldPosition -> [FormatString] -> Maybe FieldPosition -> Maybe FieldPosition -> Maybe FieldPosition -> Maybe FieldPosition -> Maybe String -> Maybe FieldPosition -> Maybe FieldPosition -> Maybe FieldPosition -> AccountName -> [AccountRule] -> CsvRules
- Hledger.Cli.Convert: account2Field :: CsvRules -> Maybe FieldPosition
- Hledger.Cli.Convert: accountField :: CsvRules -> Maybe FieldPosition
- Hledger.Cli.Convert: accountRules :: CsvRules -> [AccountRule]
- Hledger.Cli.Convert: accountrule :: GenParser Char CsvRules AccountRule
- Hledger.Cli.Convert: amountField :: CsvRules -> Maybe FieldPosition
- Hledger.Cli.Convert: amountInField :: CsvRules -> Maybe FieldPosition
- Hledger.Cli.Convert: amountOutField :: CsvRules -> Maybe FieldPosition
- Hledger.Cli.Convert: baseAccount :: CsvRules -> AccountName
- Hledger.Cli.Convert: baseCurrency :: CsvRules -> Maybe String
- Hledger.Cli.Convert: codeField :: CsvRules -> Maybe FieldPosition
- Hledger.Cli.Convert: convert :: CliOpts -> IO ()
- Hledger.Cli.Convert: csvrulesfile :: GenParser Char CsvRules CsvRules
- Hledger.Cli.Convert: currencyField :: CsvRules -> Maybe FieldPosition
- Hledger.Cli.Convert: data CsvRules
- Hledger.Cli.Convert: dateField :: CsvRules -> Maybe FieldPosition
- Hledger.Cli.Convert: dateFormat :: CsvRules -> Maybe String
- Hledger.Cli.Convert: definitions :: GenParser Char CsvRules ()
- Hledger.Cli.Convert: descriptionField :: CsvRules -> [FormatString]
- Hledger.Cli.Convert: descriptionFieldValue :: GenParser Char st [FormatString]
- Hledger.Cli.Convert: effectiveDateField :: CsvRules -> Maybe FieldPosition
- Hledger.Cli.Convert: formatD :: CsvRecord -> Bool -> Maybe Int -> Maybe Int -> Field -> String
- Hledger.Cli.Convert: formatDescription :: CsvRecord -> [FormatString] -> String
- Hledger.Cli.Convert: getAmount :: CsvRules -> CsvRecord -> String
- Hledger.Cli.Convert: identify :: [AccountRule] -> String -> String -> (String, String)
- Hledger.Cli.Convert: initialRulesFileContent :: String
- Hledger.Cli.Convert: instance Eq CsvRules
- Hledger.Cli.Convert: instance Show CsvRules
- Hledger.Cli.Convert: maxFieldIndex :: CsvRules -> Int
- Hledger.Cli.Convert: normaliseDate :: Maybe String -> String -> String
- Hledger.Cli.Convert: parseCsv :: FilePath -> IO (Either ParseError CSV)
- Hledger.Cli.Convert: parseCsvRules :: FilePath -> String -> Either ParseError CsvRules
- Hledger.Cli.Convert: parseCsvRulesFile :: FilePath -> IO (Either ParseError CsvRules)
- Hledger.Cli.Convert: rulesFileFor :: CliOpts -> FilePath -> FilePath
- Hledger.Cli.Convert: statusField :: CsvRules -> Maybe FieldPosition
- Hledger.Cli.Convert: transactionFromCsvRecord :: CsvRules -> CsvRecord -> Transaction
- Hledger.Cli.Convert: type AccountRule = ([(String, Maybe String)], AccountName)
- Hledger.Cli.Convert: type CsvRecord = [String]
- Hledger.Cli.Convert: type FieldPosition = Int
- Hledger.Cli.Convert: validateRules :: CsvRules -> Maybe String
- Hledger.Cli.Format: Account :: Field
- Hledger.Cli.Format: DefaultDate :: Field
- Hledger.Cli.Format: DepthSpacer :: Field
- Hledger.Cli.Format: Description :: Field
- Hledger.Cli.Format: FieldNo :: Int -> Field
- Hledger.Cli.Format: FormatField :: Bool -> (Maybe Int) -> (Maybe Int) -> Field -> FormatString
- Hledger.Cli.Format: FormatLiteral :: String -> FormatString
- Hledger.Cli.Format: Total :: Field
- Hledger.Cli.Format: data Field
- Hledger.Cli.Format: data FormatString
- Hledger.Cli.Format: formatStrings :: GenParser Char st [FormatString]
- Hledger.Cli.Format: formatValue :: Bool -> Maybe Int -> Maybe Int -> String -> String
- Hledger.Cli.Format: instance Eq Field
- Hledger.Cli.Format: instance Eq FormatString
- Hledger.Cli.Format: instance Show Field
- Hledger.Cli.Format: instance Show FormatString
- Hledger.Cli.Format: parseFormatString :: String -> Either String [FormatString]
- Hledger.Cli.Format: tests :: Test
- Hledger.Cli.Tests: runTestsOrExit :: CliOpts -> IO ()
- Hledger.Cli.Tests: runtests :: CliOpts -> IO ()
- Hledger.Cli.Tests: runtests' :: Num b => CliOpts -> IO (Counts, b)
- Hledger.Cli.Utils: readJournal' :: String -> IO Journal
+ Hledger.Cli.Add: letterPairs :: [t] -> [[t]]
+ Hledger.Cli.Add: wordLetterPairs :: String -> [[Char]]
+ Hledger.Cli.Balancesheet: balancesheet :: CliOpts -> Journal -> IO ()
+ Hledger.Cli.Balancesheet: tests_Hledger_Cli_Balancesheet :: Test
+ Hledger.Cli.Cashflow: cashflow :: CliOpts -> Journal -> IO ()
+ Hledger.Cli.Cashflow: tests_Hledger_Cli_Cashflow :: Test
+ Hledger.Cli.Histogram: barchar :: Char
+ Hledger.Cli.Histogram: countBar :: [a] -> [Char]
+ Hledger.Cli.Histogram: printDayWith :: (PrintfType t, PrintfArg t1) => (t2 -> t1) -> (DateSpan, t2) -> t
+ Hledger.Cli.Incomestatement: incomestatement :: CliOpts -> Journal -> IO ()
+ Hledger.Cli.Incomestatement: tests_Hledger_Cli_Incomestatement :: Test
+ Hledger.Cli.Options: accountsmode :: Mode RawOpts
+ Hledger.Cli.Options: activitymode :: Mode RawOpts
+ Hledger.Cli.Options: addmode :: Mode RawOpts
+ Hledger.Cli.Options: addonargsflag :: Arg [([Char], [Char])]
+ Hledger.Cli.Options: addonmode :: Name -> Mode RawOpts
+ Hledger.Cli.Options: balancesheetmode :: Mode RawOpts
+ Hledger.Cli.Options: boolopt :: String -> RawOpts -> Bool
+ Hledger.Cli.Options: cashflowmode :: Mode RawOpts
+ Hledger.Cli.Options: commandargsflag :: Arg [([Char], [Char])]
+ Hledger.Cli.Options: commandmode :: [Name] -> Mode RawOpts
+ Hledger.Cli.Options: convertmode :: Mode RawOpts
+ Hledger.Cli.Options: decodeRawOpts :: [(t, SystemString)] -> [(t, String)]
+ Hledger.Cli.Options: defcliopts :: CliOpts
+ Hledger.Cli.Options: entriesmode :: Mode RawOpts
+ Hledger.Cli.Options: fileflags :: [Flag [([Char], [Char])]]
+ Hledger.Cli.Options: generalflags1 :: [Flag [([Char], [Char])]]
+ Hledger.Cli.Options: generalflags2 :: [Flag [([Char], [Char])]]
+ Hledger.Cli.Options: generalflags3 :: [Flag [([Char], [Char])]]
+ Hledger.Cli.Options: generalflagstitle :: [Char]
+ Hledger.Cli.Options: getDirectoryContentsSafe :: FilePath -> IO [FilePath]
+ Hledger.Cli.Options: getEnvSafe :: String -> IO String
+ Hledger.Cli.Options: help_postscript :: [a]
+ Hledger.Cli.Options: helpflags :: [Flag [([Char], [Char])]]
+ Hledger.Cli.Options: incomestatementmode :: Mode RawOpts
+ Hledger.Cli.Options: intopt :: String -> RawOpts -> Int
+ Hledger.Cli.Options: listofstringopt :: Eq a => a -> [(a, t)] -> [t]
+ Hledger.Cli.Options: mainargsflag :: Arg [([Char], [Char])]
+ Hledger.Cli.Options: mainmode :: [Name] -> Mode RawOpts
+ Hledger.Cli.Options: maybestringopt :: Eq a => a -> [(a, String)] -> Maybe String
+ Hledger.Cli.Options: optserror :: [Char] -> c
+ Hledger.Cli.Options: postingsmode :: Mode RawOpts
+ Hledger.Cli.Options: rearrangeForCmdArgs :: [[Char]] -> [[Char]]
+ Hledger.Cli.Options: reportflags :: [Flag [([Char], [Char])]]
+ Hledger.Cli.Options: rulesFilePathFromOpts :: CliOpts -> Maybe FilePath
+ Hledger.Cli.Options: setboolopt :: t -> [(t, [Char])] -> [(t, [Char])]
+ Hledger.Cli.Options: setopt :: t -> [Char] -> [(t, [Char])] -> [(t, [Char])]
+ Hledger.Cli.Options: showModeHelp :: Mode a -> String
+ Hledger.Cli.Options: statsmode :: Mode RawOpts
+ Hledger.Cli.Options: stringopt :: Eq a => a -> [(a, String)] -> [Char]
+ Hledger.Cli.Options: testmode :: Mode RawOpts
+ Hledger.Cli.Options: tests_Hledger_Cli_Options :: Test
+ Hledger.Cli.Options: transactionsmode :: Mode RawOpts
+ Hledger.Cli.Print: tests_Hledger_Cli_Print :: Test
+ Hledger.Cli.Tests: flatTests :: CliOpts -> Test
+ Hledger.Cli.Tests: hierarchicalTests :: CliOpts -> Test
+ Hledger.Cli.Tests: runTests :: CliOpts -> IO Counts
+ Hledger.Cli.Tests: runTestsTillFailure :: CliOpts -> IO (Maybe String)
+ Hledger.Cli.Tests: test' :: CliOpts -> IO ()
- Hledger.Cli.Add: transactionsSimilarTo :: Journal -> [String] -> String -> [(Double, Transaction)]
+ Hledger.Cli.Add: transactionsSimilarTo :: Journal -> Query -> String -> [(Double, Transaction)]
- Hledger.Cli.Histogram: showHistogram :: ReportOpts -> FilterSpec -> Journal -> String
+ Hledger.Cli.Histogram: showHistogram :: ReportOpts -> Query -> Journal -> String
- Hledger.Cli.Options: CliOpts :: RawOpts -> String -> Maybe FilePath -> [String] -> Bool -> Bool -> Maybe FilePath -> ReportOpts -> CliOpts
+ Hledger.Cli.Options: CliOpts :: RawOpts -> String -> Maybe FilePath -> Maybe FilePath -> [String] -> Bool -> Bool -> ReportOpts -> CliOpts
- Hledger.Cli.Print: showTransactions :: ReportOpts -> FilterSpec -> Journal -> String
+ Hledger.Cli.Print: showTransactions :: ReportOpts -> Query -> Journal -> String

Files

Hledger/Cli.hs view
@@ -1,13 +1,19 @@ {-| -Hledger.Cli re-exports the options, utilities and commands provided by the-hledger command-line program.++Hledger.Cli re-exports the options, utilities and commands provided by+the hledger command-line program. This module also aggregates the+built-in unit tests defined throughout hledger and hledger-lib, and+adds some more which are easier to define here.+ -}  module Hledger.Cli (                      module Hledger.Cli.Add,                      module Hledger.Cli.Balance,-                     module Hledger.Cli.Convert,+                     module Hledger.Cli.Balancesheet,+                     module Hledger.Cli.Cashflow,                      module Hledger.Cli.Histogram,+                     module Hledger.Cli.Incomestatement,                      module Hledger.Cli.Print,                      module Hledger.Cli.Register,                      module Hledger.Cli.Stats,@@ -17,7 +23,6 @@                      tests_Hledger_Cli               ) where-import Control.Monad import qualified Data.Map as Map import Data.Time.Calendar import System.Time (ClockTime(TOD))@@ -26,8 +31,10 @@ import Hledger import Hledger.Cli.Add import Hledger.Cli.Balance-import Hledger.Cli.Convert+import Hledger.Cli.Balancesheet+import Hledger.Cli.Cashflow import Hledger.Cli.Histogram+import Hledger.Cli.Incomestatement import Hledger.Cli.Print import Hledger.Cli.Register import Hledger.Cli.Stats@@ -35,17 +42,17 @@ import Hledger.Cli.Utils import Hledger.Cli.Version --- | hledger and hledger-lib's unit tests aggregated from all modules--- plus some more which are easier to define here for now.+ tests_Hledger_Cli :: Test tests_Hledger_Cli = TestList  [-    tests_Hledger_Data-   ,tests_Hledger_Read+    tests_Hledger    -- ,tests_Hledger_Cli_Add    -- ,tests_Hledger_Cli_Balance-   ,tests_Hledger_Cli_Convert+   ,tests_Hledger_Cli_Balancesheet+   ,tests_Hledger_Cli_Cashflow    -- ,tests_Hledger_Cli_Histogram+   ,tests_Hledger_Cli_Incomestatement    ,tests_Hledger_Cli_Options    -- ,tests_Hledger_Cli_Print    ,tests_Hledger_Cli_Register@@ -53,8 +60,8 @@      ,"account directive" ~:-   let sameParse str1 str2 = do j1 <- readJournal Nothing str1 >>= either error' return-                                j2 <- readJournal Nothing str2 >>= either error' return+   let sameParse str1 str2 = do j1 <- readJournal Nothing Nothing Nothing str1 >>= either error' return+                                j2 <- readJournal Nothing Nothing Nothing str2 >>= either error' return                                 j1 `is` j2{filereadtime=filereadtime j1, files=files j1, jContext=jContext j1}    in TestList    [@@ -85,7 +92,7 @@                            )     ,"account directive should preserve \"virtual\" posting type" ~: do-      j <- readJournal Nothing "!account test\n2008/12/07 One\n  (from)  $-1\n  (to)  $1\n" >>= either error' return+      j <- readJournal Nothing Nothing Nothing "!account test\n2008/12/07 One\n  (from)  $-1\n  (to)  $1\n" >>= either error' return       let p = head $ tpostings $ head $ jtxns j       assertBool "" $ (paccount p) == "test:from"       assertBool "" $ (ptype p) == VirtualPosting@@ -93,7 +100,7 @@    ]     ,"account aliases" ~: do-      Right j <- readJournal Nothing "!alias expenses = equity:draw:personal\n1/1\n (expenses:food)  1\n"+      j <- readJournal Nothing Nothing Nothing "!alias expenses = equity:draw:personal\n1/1\n (expenses:food)  1\n" >>= either error' return       let p = head $ tpostings $ head $ jtxns j       assertBool "" $ paccount p == "equity:draw:personal:food" @@ -103,160 +110,12 @@       "expenses","expenses:food","expenses:food:dining","expenses:phone","expenses:vacation",       "liabilities","liabilities:credit cards","liabilities:credit cards:discover"] -  ,"balance report tests" ~:-   let opts `gives` es = do-        j <- samplejournal-        d <- getCurrentDay-        accountsReportAsText opts (accountsReport opts (optsToFilterSpec opts d) j) `is` es-   in TestList-   [-    "balance report with no args" ~:-    defreportopts `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" ~:-    defreportopts{depth_=Just 1} `gives`-    ["                 $-1  assets"-    ,"                  $2  expenses"-    ,"                 $-2  income"-    ,"                  $1  liabilities"-    ,"--------------------"-    ,"                   0"-    ]-    -   ,"balance report with account pattern o" ~:-    defreportopts{patterns_=["o"]} `gives`-    ["                  $1  expenses:food"-    ,"                 $-2  income"-    ,"                 $-1    gifts"-    ,"                 $-1    salary"-    ,"--------------------"-    ,"                 $-1"-    ]--   ,"balance report with account pattern o and --depth 1" ~:-    defreportopts{patterns_=["o"],depth_=Just 1} `gives`-    ["                  $1  expenses"-    ,"                 $-2  income"-    ,"--------------------"-    ,"                 $-1"-    ]--   ,"balance report with account pattern a" ~:-    defreportopts{patterns_=["a"]} `gives`-    ["                 $-1  assets"-    ,"                  $1    bank:saving"-    ,"                 $-2    cash"-    ,"                 $-1  income:salary"-    ,"                  $1  liabilities:debts"-    ,"--------------------"-    ,"                 $-1"-    ]--   ,"balance report with account pattern e" ~:-    defreportopts{patterns_=["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" ~: -    defreportopts{patterns_=["cash","saving"]} `gives`-    ["                 $-1  assets"-    ,"                  $1    bank:saving"-    ,"                 $-2    cash"-    ,"--------------------"-    ,"                 $-1"-    ]--   ,"balance report with multi-part account name" ~: -    defreportopts{patterns_=["expenses:food"]} `gives`-    ["                  $1  expenses:food"-    ,"--------------------"-    ,"                  $1"-    ]--   ,"balance report with negative account pattern" ~:-    defreportopts{patterns_=["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" ~: -    defreportopts{patterns_=["not:e"]} `gives`-    ["--------------------"-    ,"                   0"-    ]--   ,"balance report negative patterns affect totals" ~: -    defreportopts{patterns_=["expenses","not:food"]} `gives`-    ["                  $1  expenses:supplies"-    ,"--------------------"-    ,"                  $1"-    ]--   ,"balance report with -E shows zero-balance accounts" ~:-    defreportopts{patterns_=["assets"],empty_=True} `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-      accountsReportAsText defreportopts (accountsReport defreportopts nullfilterspec j') `is`-        ["                $500  a:b"-        ,"               $-500  c:d"-        ,"--------------------"-        ,"                   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=[]}]+    Map.elems (ledgerCommodities ledger7) `is` [Commodity {symbol="$", side=L, spaced=False, decimalpoint='.', precision=2, separator=',', separatorpositions=[]}]    -- don't know what this should do   -- ,"elideAccountName" ~: do@@ -266,215 +125,17 @@   --     `is` "aa:aa:aaaaaaaaaaaaaa")    ,"default year" ~: do-    j <- readJournal Nothing defaultyear_journal_str >>= either error' return+    j <- readJournal Nothing Nothing Nothing defaultyear_journal_str >>= either error' return     tdate (head $ jtxns j) `is` fromGregorian 2009 1 1     return () -  ,"print report tests" ~: TestList-  [--   "print expenses" ~:-   do -    let opts = defreportopts{patterns_=["expenses"]}-    j <- samplejournal-    d <- getCurrentDay-    showTransactions opts (optsToFilterSpec opts d) j `is` unlines-     ["2008/06/03 * eat & shop"-     ,"    expenses:food                $1"-     ,"    expenses:supplies            $1"-     ,"    assets:cash                 $-2"-     ,""-     ]--  , "print report with depth arg" ~:-   do -    let opts = defreportopts{depth_=Just 2}-    j <- samplejournal-    d <- getCurrentDay-    showTransactions opts (optsToFilterSpec opts d) j `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 -    j <- samplejournal-    let opts = defreportopts-    (postingsReportAsText opts $ postingsReport opts (optsToFilterSpec opts date1) j) `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 = defreportopts{cleared_=True}-    j <- readJournal' sample_journal_str-    (postingsReportAsText opts $ postingsReport opts (optsToFilterSpec opts date1) j) `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 = defreportopts{uncleared_=True}-    j <- readJournal' sample_journal_str-    (postingsReportAsText opts $ postingsReport opts (optsToFilterSpec opts date1) j) `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 -    j <- readJournal' $ unlines-        ["2008/02/02 a"-        ,"  b  1"-        ,"  c"-        ,""-        ,"2008/01/01 d"-        ,"  e  1"-        ,"  f"-        ]-    let opts = defreportopts-    registerdates (postingsReportAsText opts $ postingsReport opts (optsToFilterSpec opts date1) j) `is` ["2008/01/01","2008/02/02"]--  ,"register report with account pattern" ~:-   do-    j <- samplejournal-    let opts = defreportopts{patterns_=["cash"]}-    (postingsReportAsText opts $ postingsReport opts (optsToFilterSpec opts date1) j) `is` unlines-     ["2008/06/03 eat & shop           assets:cash                     $-2          $-2"-     ]--  ,"register report with account pattern, case insensitive" ~:-   do -    j <- samplejournal-    let opts = defreportopts{patterns_=["cAsH"]}-    (postingsReportAsText opts $ postingsReport opts (optsToFilterSpec opts date1) j) `is` unlines-     ["2008/06/03 eat & shop           assets:cash                     $-2          $-2"-     ]--  ,"register report with display expression" ~:-   do -    j <- samplejournal-    let gives displayexpr = -            (registerdates (postingsReportAsText opts $ postingsReport opts (optsToFilterSpec opts date1) j) `is`)-                where opts = defreportopts{display_=Just 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 -    j <- samplejournal-    let periodexpr `gives` dates = do-          j' <- samplejournal-          registerdates (postingsReportAsText opts $ postingsReport opts (optsToFilterSpec opts date1) j') `is` dates-              where opts = defreportopts{period_=maybePeriod date1 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 = defreportopts{period_=maybePeriod date1 "yearly"}-    (postingsReportAsText opts $ postingsReport opts (optsToFilterSpec opts date1) j) `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 = defreportopts{period_=maybePeriod date1 "quarterly"}-    registerdates (postingsReportAsText opts $ postingsReport opts (optsToFilterSpec opts date1) j) `is` ["2008/01/01","2008/04/01","2008/10/01"]-    let opts = defreportopts{period_=maybePeriod date1 "quarterly",empty_=True}-    registerdates (postingsReportAsText opts $ postingsReport opts (optsToFilterSpec opts date1) j) `is` ["2008/01/01","2008/04/01","2008/07/01","2008/10/01"]--  ]--  , "register report with depth arg" ~:-   do -    j <- samplejournal-    let opts = defreportopts{depth_=Just 2}-    (postingsReportAsText opts $ postingsReport opts (optsToFilterSpec opts date1) j) `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-    j <- readJournal'-      "2009/01/01 * медвежья шкура\n  расходы:покупки  100\n  актив:наличные\n"-    let opts = defreportopts-    accountsReportAsText opts (accountsReport opts (optsToFilterSpec opts date1) j) `is`-      ["                -100  актив:наличные"-      ,"                 100  расходы:покупки"-      ,"--------------------"-      ,"                   0"-      ]+  ,"show dollars" ~: showAmount (dollars 1) ~?= "$1.00" -  ,"unicode in register layout" ~: do-    j <- readJournal'-      "2009/01/01 * медвежья шкура\n  расходы:покупки  100\n  актив:наличные\n"-    let opts = defreportopts-    (postingsReportAsText opts $ postingsReport opts (optsToFilterSpec opts date1) j) `is` unlines-      ["2009/01/01 медвежья шкура       расходы:покупки                 100          100"-      ,"                                актив:наличные                 -100            0"]+  ,"show hours" ~: showAmount (hours 1) ~?= "1.0h"    ,"subAccounts" ~: do-    l <- liftM (journalToLedger nullfilterspec) samplejournal-    let a = ledgerAccount l "assets"+    let l = journalToLedger Any samplejournal+        a = ledgerAccount l "assets"     map aname (ledgerSubAccounts l a) `is` ["assets:bank","assets:cash"]   ]@@ -482,9 +143,10 @@    -- fixtures/test data -date1 = parsedate "2008/11/26"+-- date1 = parsedate "2008/11/26" -- t1 = LocalTime date1 midday +{- samplejournal = readJournal' sample_journal_str  sample_journal_str = unlines@@ -529,6 +191,7 @@  ,""  ,";final comment"  ]+-}  defaultyear_journal_str = unlines  ["Y2009"@@ -690,7 +353,7 @@              tcode="*",              tdescription="opening balance",              tcomment="",-             tmetadata=[],+             ttags=[],              tpostings=[               Posting {                 pstatus=False,@@ -698,7 +361,7 @@                 pamount=(Mixed [dollars 4.82]),                 pcomment="",                 ptype=RegularPosting,-                pmetadata=[],+                ptags=[],                 ptransaction=Nothing               },               Posting {@@ -707,7 +370,7 @@                 pamount=(Mixed [dollars (-4.82)]),                 pcomment="",                 ptype=RegularPosting,-                pmetadata=[],+                ptags=[],                 ptransaction=Nothing               }              ],@@ -721,7 +384,7 @@              tcode="*",              tdescription="ayres suites",              tcomment="",-             tmetadata=[],+             ttags=[],              tpostings=[               Posting {                 pstatus=False,@@ -729,7 +392,7 @@                 pamount=(Mixed [dollars 179.92]),                 pcomment="",                 ptype=RegularPosting,-                pmetadata=[],+                ptags=[],                 ptransaction=Nothing               },               Posting {@@ -738,7 +401,7 @@                 pamount=(Mixed [dollars (-179.92)]),                 pcomment="",                 ptype=RegularPosting,-                pmetadata=[],+                ptags=[],                 ptransaction=Nothing               }              ],@@ -752,7 +415,7 @@              tcode="*",              tdescription="auto transfer to savings",              tcomment="",-             tmetadata=[],+             ttags=[],              tpostings=[               Posting {                 pstatus=False,@@ -760,7 +423,7 @@                 pamount=(Mixed [dollars 200]),                 pcomment="",                 ptype=RegularPosting,-                pmetadata=[],+                ptags=[],                 ptransaction=Nothing               },               Posting {@@ -769,7 +432,7 @@                 pamount=(Mixed [dollars (-200)]),                 pcomment="",                 ptype=RegularPosting,-                pmetadata=[],+                ptags=[],                 ptransaction=Nothing               }              ],@@ -783,7 +446,7 @@              tcode="*",              tdescription="poquito mas",              tcomment="",-             tmetadata=[],+             ttags=[],              tpostings=[               Posting {                 pstatus=False,@@ -791,7 +454,7 @@                 pamount=(Mixed [dollars 4.82]),                 pcomment="",                 ptype=RegularPosting,-                pmetadata=[],+                ptags=[],                 ptransaction=Nothing               },               Posting {@@ -800,7 +463,7 @@                 pamount=(Mixed [dollars (-4.82)]),                 pcomment="",                 ptype=RegularPosting,-                pmetadata=[],+                ptags=[],                 ptransaction=Nothing               }              ],@@ -814,7 +477,7 @@              tcode="*",              tdescription="verizon",              tcomment="",-             tmetadata=[],+             ttags=[],              tpostings=[               Posting {                 pstatus=False,@@ -822,7 +485,7 @@                 pamount=(Mixed [dollars 95.11]),                 pcomment="",                 ptype=RegularPosting,-                pmetadata=[],+                ptags=[],                 ptransaction=Nothing               },               Posting {@@ -831,7 +494,7 @@                 pamount=(Mixed [dollars (-95.11)]),                 pcomment="",                 ptype=RegularPosting,-                pmetadata=[],+                ptags=[],                 ptransaction=Nothing               }              ],@@ -845,7 +508,7 @@              tcode="*",              tdescription="discover",              tcomment="",-             tmetadata=[],+             ttags=[],              tpostings=[               Posting {                 pstatus=False,@@ -853,7 +516,7 @@                 pamount=(Mixed [dollars 80]),                 pcomment="",                 ptype=RegularPosting,-                pmetadata=[],+                ptags=[],                 ptransaction=Nothing               },               Posting {@@ -862,7 +525,7 @@                 pamount=(Mixed [dollars (-80)]),                 pcomment="",                 ptype=RegularPosting,-                pmetadata=[],+                ptags=[],                 ptransaction=Nothing               }              ],@@ -876,7 +539,7 @@           []           (TOD 0 0) -ledger7 = journalToLedger nullfilterspec journal7+ledger7 = journalToLedger Any journal7  -- journal8_str = unlines --  ["2008/1/1 test           "@@ -907,4 +570,4 @@         nullctx         []         (TOD 0 0)-    where parse = fromparse . parseWithCtx nullctx someamount+    where parse = fromparse . parseWithCtx nullctx amount
Hledger/Cli/Add.hs view
@@ -10,7 +10,7 @@  module Hledger.Cli.Add where-import Control.Exception (throw)+import Control.Exception as C import Control.Monad import Control.Monad.Trans (liftIO) import Data.Char (toUpper)@@ -29,10 +29,9 @@  import Hledger import Prelude hiding (putStr, putStrLn, appendFile)-import Hledger.Utils.UTF8 (putStr, putStrLn, appendFile)+import Hledger.Utils.UTF8IOCompat (putStr, putStrLn, appendFile) import Hledger.Cli.Options import Hledger.Cli.Register (postingsReportAsText)-import Hledger.Cli.Utils   {- | Information used as the basis for suggested account names, amounts,@@ -57,7 +56,7 @@     ++"To stop adding transactions, enter . at a date prompt, or control-d/control-c."   today <- getCurrentDay   getAndAddTransactions j opts today-        `catch` (\e -> unless (isEOFError e) $ ioError e)+        `C.catch` (\e -> unless (isEOFError e) $ ioError e)       where f = journalFilePath j  -- | Read a number of transactions from the command line, prompting,@@ -82,7 +81,7 @@                          || isRight (parse (smartdate >> many spacenonewline >> eof) "" $ lowercase s))   when (datestr == ".") $ ioError $ mkIOError eofErrorType "" Nothing Nothing   description <- runInteractionDefault $ askFor "description" (Just "") Nothing-  let historymatches = transactionsSimilarTo j (patterns_ $ reportopts_ opts) description+  let historymatches = transactionsSimilarTo j (queryFromOpts today $ reportopts_ opts) description       bestmatch | null historymatches = Nothing                 | otherwise = Just $ snd $ head historymatches       bestmatchpostings = maybe Nothing (Just . tpostings) bestmatch@@ -150,16 +149,16 @@                 -- I think 1 or 4, whichever would show the most decimal places                 p = maxprecisionwithpoint       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+      let a  = fromparse $ runParser (amount <|> return missingmixedamt) ctx     "" amountstr+          a' = fromparse $ runParser (amount <|> return missingmixedamt) nullctx "" amountstr+          defaultamtused = Just (showMixedAmount a) == defaultamountstr           commodityadded | c == cwithnodef = Nothing                          | otherwise       = c-              where c          = maybemixedamountcommodity amount-                    cwithnodef = maybemixedamountcommodity amount'+              where c          = maybemixedamountcommodity a+                    cwithnodef = maybemixedamountcommodity a'                     maybemixedamountcommodity = maybe Nothing (Just . commodity) . headMay . amounts           p = nullposting{paccount=stripbrackets account,-                          pamount=amount,+                          pamount=a,                           ptype=postingtype account}           st' = if defaultamtused then st                    else st{psHistory = historicalps',@@ -181,7 +180,7 @@       postingtype _ = RegularPosting       stripbrackets = dropWhile (`elem` "([") . reverse . dropWhile (`elem` "])") . reverse       validateamount = Just $ \s -> (null s && not (null enteredrealps))-                                   || isRight (runParser (someamount>>many spacenonewline>>eof) ctx "" s)+                                   || isRight (runParser (amount>>many spacenonewline>>eof) ctx "" s)  -- | Prompt for and read a string value, optionally with a default value -- and a validator. A validator causes the prompt to repeat until the@@ -198,7 +197,7 @@     Nothing -> return input     where         showdef s = " [" ++ s ++ "]"-        eofErr = throw $ mkIOError eofErrorType "end of input" Nothing Nothing+        eofErr = C.throw $ mkIOError eofErrorType "end of input" Nothing Nothing  -- | Append this transaction to the journal's file, and to the journal's -- transaction list.@@ -229,7 +228,7 @@ registerFromString s = do   d <- getCurrentDay   j <- readJournal' s-  return $ postingsReportAsText opts $ postingsReport opts (optsToFilterSpec opts d) j+  return $ postingsReportAsText opts $ postingsReport opts (queryFromOpts d opts) j       where opts = defreportopts{empty_=True}  -- | Return a similarity measure, from 0 to 1, for two strings.@@ -257,14 +256,14 @@           t' = simplify t           simplify = filter (not . (`elem` "0123456789")) -transactionsSimilarTo :: Journal -> [String] -> String -> [(Double,Transaction)]-transactionsSimilarTo j apats s =+transactionsSimilarTo :: Journal -> Query -> String -> [(Double,Transaction)]+transactionsSimilarTo j q s =     sortBy compareRelevanceAndRecency                $ filter ((> threshold).fst)                [(compareDescriptions s $ tdescription t, t) | t <- ts]     where       compareRelevanceAndRecency (n1,t1) (n2,t2) = compare (n2,tdate t2) (n1,tdate t1)-      ts = jtxns $ filterJournalTransactionsByAccount apats j+      ts = filter (q `matchesTransaction`) $ jtxns j       threshold = 0  runInteraction :: Journal -> InputT IO a -> IO a
Hledger/Cli/Balance.hs view
@@ -105,9 +105,8 @@  import Hledger import Prelude hiding (putStr)-import Hledger.Utils.UTF8 (putStr)-import Hledger.Cli.Format-import qualified Hledger.Cli.Format as Format+import Hledger.Utils.UTF8IOCompat (putStr)+import Hledger.Data.FormatStrings import Hledger.Cli.Options  @@ -117,7 +116,7 @@   d <- getCurrentDay   let lines = case formatFromOpts ropts of             Left err -> [err]-            Right _ -> accountsReportAsText ropts $ accountsReport ropts (optsToFilterSpec ropts d) j+            Right _ -> accountsReportAsText ropts $ accountsReport ropts (queryFromOpts d ropts) j   putStr $ unlines lines  -- | Render a balance report as plain text suitable for console output.@@ -134,6 +133,20 @@                 ,padleft 20 $ showMixedAmountWithoutPrice total                 ] +tests_accountsReportAsText = [+  "accountsReportAsText" ~: do+  -- "unicode in balance layout" ~: do+    j <- readJournal'+      "2009/01/01 * медвежья шкура\n  расходы:покупки  100\n  актив:наличные\n"+    let opts = defreportopts+    accountsReportAsText opts (accountsReport opts (queryFromOpts (parsedate "2008/11/26") opts) j) `is`+      ["                -100  актив:наличные"+      ,"                 100  расходы:покупки"+      ,"--------------------"+      ,"                   0"+      ]+ ]+ {- This implementation turned out to be a bit convoluted but implements the following algorithm for formatting: @@ -170,15 +183,14 @@          FormatLiteral l -> l          FormatField ljust min max field  -> formatField opts accountName depth amount ljust min max field -formatField :: ReportOpts -> Maybe AccountName -> Int -> Amount -> Bool -> Maybe Int -> Maybe Int -> Field -> String+formatField :: ReportOpts -> Maybe AccountName -> Int -> Amount -> Bool -> Maybe Int -> Maybe Int -> HledgerFormatField -> String formatField opts accountName depth total ljust min max field = case field of-        Format.Account     -> formatValue ljust min max $ maybe "" (accountNameDrop (drop_ opts)) accountName-        Format.DepthSpacer -> case min of+        AccountField     -> formatValue ljust min max $ maybe "" (accountNameDrop (drop_ opts)) accountName+        DepthSpacerField -> case min of                                Just m  -> formatValue ljust Nothing max $ replicate (depth * m) ' '                                Nothing -> formatValue ljust Nothing max $ replicate depth ' '-        Format.Total       -> formatValue ljust min max $ showAmountWithoutPrice total+        TotalField       -> formatValue ljust min max $ showAmountWithoutPrice total         _                  -> ""  tests_Hledger_Cli_Balance = TestList- [- ]+  tests_accountsReportAsText
+ Hledger/Cli/Balancesheet.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE QuasiQuotes, RecordWildCards #-}+{-|++The @balancesheet@ command prints a simple balance sheet.++-}++module Hledger.Cli.Balancesheet (+  balancesheet+ ,tests_Hledger_Cli_Balancesheet+) where++import qualified Data.Text.Lazy.IO as LT+import Test.HUnit+import Text.Shakespeare.Text++import Hledger+import Hledger.Cli.Options+import Hledger.Cli.Balance+++-- | Print a simple balance sheet.+balancesheet :: CliOpts -> Journal -> IO ()+balancesheet CliOpts{reportopts_=ropts} j = do+  -- let lines = case formatFromOpts ropts of Left err, Right ...+  d <- getCurrentDay+  let q = queryFromOpts d (withoutBeginDate ropts)+      assetreport@(_,assets)          = accountsReport ropts (And [q, journalAssetAccountQuery j]) j+      liabilityreport@(_,liabilities) = accountsReport ropts (And [q, journalLiabilityAccountQuery j]) j+      equityreport@(_,equity)         = accountsReport ropts (And [q, journalEquityAccountQuery j]) j+      total = assets + liabilities + equity+  LT.putStr $ [lt|Balance Sheet++Assets:+#{unlines $ accountsReportAsText ropts assetreport}+Liabilities:+#{unlines $ accountsReportAsText ropts liabilityreport}+Equity:+#{unlines $ accountsReportAsText ropts equityreport}++Total:+--------------------+#{padleft 20 $ showMixedAmountWithoutPrice total}+|]++withoutBeginDate :: ReportOpts -> ReportOpts+withoutBeginDate ropts@ReportOpts{..} = ropts{begin_=Nothing, period_=p}+  where p = case period_ of Nothing -> Nothing+                            Just (i, DateSpan _ e) -> Just (i, DateSpan Nothing e)++tests_Hledger_Cli_Balancesheet = TestList+ [+ ]
+ Hledger/Cli/Cashflow.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE QuasiQuotes, RecordWildCards #-}+{-|++The @cashflow@ command prints a simplified cashflow statement.  It just+shows the change in all "cash" accounts for the period (without the+traditional segmentation into operating, investing, and financing+cash flows.)++-}++module Hledger.Cli.Cashflow (+  cashflow+ ,tests_Hledger_Cli_Cashflow+) where++import qualified Data.Text.Lazy.IO as LT+import Test.HUnit+import Text.Shakespeare.Text++import Hledger+import Hledger.Cli.Options+import Hledger.Cli.Balance+++-- | Print a simple cashflow statement.+cashflow :: CliOpts -> Journal -> IO ()+cashflow CliOpts{reportopts_=ropts} j = do+  -- let lines = case formatFromOpts ropts of Left err, Right ...+  d <- getCurrentDay+  let q = queryFromOpts d (withoutBeginDate ropts)+      cashreport@(_,total) = accountsReport ropts (And [q, journalCashAccountQuery j]) j+      -- operatingreport@(_,operating) = accountsReport ropts (And [q, journalOperatingAccountMatcher j]) j+      -- investingreport@(_,investing) = accountsReport ropts (And [q, journalInvestingAccountMatcher j]) j+      -- financingreport@(_,financing) = accountsReport ropts (And [q, journalFinancingAccountMatcher j]) j+      -- total = operating + investing + financing+  LT.putStr $ [lt|Cashflow Statement++Cash flows:+#{unlines $ accountsReportAsText ropts cashreport}++Total:+--------------------+#{padleft 20 $ showMixedAmountWithoutPrice total}+|]++withoutBeginDate :: ReportOpts -> ReportOpts+withoutBeginDate ropts@ReportOpts{..} = ropts{begin_=Nothing, period_=p}+  where p = case period_ of Nothing -> Nothing+                            Just (i, DateSpan _ e) -> Just (i, DateSpan Nothing e)++tests_Hledger_Cli_Cashflow = TestList+ [+ ]
− Hledger/Cli/Convert.hs
@@ -1,546 +0,0 @@-{-|-Convert account data in CSV format (eg downloaded from a bank) to journal-format, and print it on stdout. See the manual for more details.--}--module Hledger.Cli.Convert where-import Control.Monad (when, guard, liftM)-import Data.List-import Data.Maybe-import Data.Ord-import Data.Time.Format (parseTime)-import Safe-import System.Directory (doesFileExist)-import System.Exit (exitFailure)-import System.FilePath (takeBaseName, replaceExtension)-import System.IO (stderr)-import System.Locale (defaultTimeLocale)-import Test.HUnit-import Text.CSV (parseCSV, parseCSVFromFile, CSV)-import Text.ParserCombinators.Parsec-import Text.Printf (hPrintf)--import Prelude hiding (getContents)-import Hledger.Utils.UTF8 (getContents)-import Hledger-import Hledger.Cli.Format-import qualified Hledger.Cli.Format as Format-import Hledger.Cli.Options-import Hledger.Cli.Version--{- |-A set of data definitions and account-matching patterns sufficient to-convert a particular CSV data file into meaningful journal transactions. See above.--}-data CsvRules = CsvRules {-      dateField :: Maybe FieldPosition,-      dateFormat :: Maybe String,-      statusField :: Maybe FieldPosition,-      codeField :: Maybe FieldPosition,-      descriptionField :: [FormatString],-      amountField :: Maybe FieldPosition,-      amountInField :: Maybe FieldPosition,-      amountOutField :: Maybe FieldPosition,-      currencyField :: Maybe FieldPosition,-      baseCurrency :: Maybe String,-      accountField :: Maybe FieldPosition,-      account2Field :: Maybe FieldPosition,-      effectiveDateField :: Maybe FieldPosition,-      baseAccount :: AccountName,-      accountRules :: [AccountRule]-} deriving (Show, Eq)--nullrules = CsvRules {-      dateField=Nothing,-      dateFormat=Nothing,-      statusField=Nothing,-      codeField=Nothing,-      descriptionField=[],-      amountField=Nothing,-      amountInField=Nothing,-      amountOutField=Nothing,-      currencyField=Nothing,-      baseCurrency=Nothing,-      accountField=Nothing,-      account2Field=Nothing,-      effectiveDateField=Nothing,-      baseAccount="unknown",-      accountRules=[]-}--type FieldPosition = Int--type AccountRule = (-   [(String, Maybe String)] -- list of regex match patterns with optional replacements-  ,AccountName              -- account name to use for a transaction matching this rule-  )--type CsvRecord = [String]----- | Read the CSV file named as an argument and print equivalent journal transactions,--- using/creating a .rules file.-convert :: CliOpts -> IO ()-convert opts = do-  let csvfile = case headDef "" $ patterns_ $ reportopts_ opts of-                  "" -> "-"-                  s -> s-      usingStdin = csvfile == "-"-      rulesFileSpecified = isJust $ rules_file_ opts-      rulesfile = rulesFileFor opts csvfile-  when (usingStdin && (not rulesFileSpecified)) $ error' "please use --rules-file to specify a rules file when converting stdin"-  csvparse <- parseCsv csvfile-  let records = case csvparse of-                  Left e -> error' $ show e-                  Right rs -> filter (/= [""]) rs-  exists <- doesFileExist rulesfile-  if (not exists) then do-                  hPrintf stderr "creating conversion rules file %s, edit this file for better results\n" rulesfile-                  writeFile rulesfile initialRulesFileContent-   else-      hPrintf stderr "using conversion rules file %s\n" rulesfile-  rules <- liftM (either (error'.show) id) $ parseCsvRulesFile rulesfile-  let invalid = validateRules rules-  when (debug_ opts) $ hPrintf stderr "rules: %s\n" (show rules)-  when (isJust invalid) $ error (fromJust invalid)-  let requiredfields = max 2 (maxFieldIndex rules + 1)-      badrecords = take 1 $ filter ((< requiredfields).length) records-  if null badrecords-   then do-     mapM_ (putStr . show) $ sortBy (comparing tdate) $ map (transactionFromCsvRecord rules) records-   else do-     hPrintf stderr (unlines [-                      "Warning, at least one CSV record does not contain a field referenced by the"-                     ,"conversion rules file, or has less than two fields. Are you converting a"-                     ,"valid CSV file ? First bad record:\n%s"-                     ]) (show $ head badrecords)-     exitFailure--parseCsv :: FilePath -> IO (Either ParseError CSV)-parseCsv path =-  case path of-    "-" -> liftM (parseCSV "(stdin)") getContents-    p   -> parseCSVFromFile p---- | The highest (0-based) field index referenced in the field--- definitions, or -1 if no fields are defined.-maxFieldIndex :: CsvRules -> Int-maxFieldIndex r = maximumDef (-1) $ catMaybes [-                   dateField r-                  ,statusField r-                  ,codeField r-                  ,amountField r-                  ,amountInField r-                  ,amountOutField r-                  ,currencyField r-                  ,accountField r-                  ,account2Field r-                  ,effectiveDateField r-                  ]--rulesFileFor :: CliOpts -> FilePath -> FilePath-rulesFileFor CliOpts{rules_file_=Just f} _ = f-rulesFileFor CliOpts{rules_file_=Nothing} csvfile = replaceExtension csvfile ".rules"--initialRulesFileContent :: String-initialRulesFileContent =-    "# csv conversion rules file generated by " ++ prognameandversion ++ "\n" ++-    "# Add rules to this file for more accurate conversion, see\n"++-    "# http://hledger.org/MANUAL.html#convert\n" ++-    "\n" ++-    "base-account assets:bank:checking\n" ++-    "date-field 0\n" ++-    "description-field 4\n" ++-    "amount-field 1\n" ++-    "base-currency $\n" ++-    "\n" ++-    "# account-assigning rules\n" ++-    "\n" ++-    "SPECTRUM\n" ++-    "expenses:health:gym\n" ++-    "\n" ++-    "ITUNES\n" ++-    "BLKBSTR=BLOCKBUSTER\n" ++-    "expenses:entertainment\n" ++-    "\n" ++-    "(TO|FROM) SAVINGS\n" ++-    "assets:bank:savings\n"--validateRules :: CsvRules -> Maybe String-validateRules rules = let-    hasAmount = isJust $ amountField rules-    hasIn = isJust $ amountInField rules-    hasOut = isJust $ amountOutField rules-  in case (hasAmount, hasIn, hasOut) of-    (True, True, _) -> Just "Don't specify amount-in-field when specifying amount-field"-    (True, _, True) -> Just "Don't specify amount-out-field when specifying amount-field"-    (_, False, True) -> Just "Please specify amount-in-field when specifying amount-out-field"-    (_, True, False) -> Just "Please specify amount-out-field when specifying amount-in-field"-    (False, False, False) -> Just "Please specify either amount-field, or amount-in-field and amount-out-field"-    _ -> Nothing---- rules file parser--parseCsvRulesFile :: FilePath -> IO (Either ParseError CsvRules)-parseCsvRulesFile f = do-  s <- readFile f-  return $ parseCsvRules f s--parseCsvRules :: FilePath -> String -> Either ParseError CsvRules-parseCsvRules rulesfile s = runParser csvrulesfile nullrules{baseAccount=takeBaseName rulesfile} rulesfile s--csvrulesfile :: GenParser Char CsvRules CsvRules-csvrulesfile = do-  many blankorcommentline-  many definitions-  r <- getState-  ars <- many accountrule-  many blankorcommentline-  eof-  return r{accountRules=ars}--definitions :: GenParser Char CsvRules ()-definitions = do-  choice' [-    datefield-   ,dateformat-   ,statusfield-   ,codefield-   ,descriptionfield-   ,amountfield-   ,amountinfield-   ,amountoutfield-   ,currencyfield-   ,accountfield-   ,account2field-   ,effectivedatefield-   ,basecurrency-   ,baseaccount-   ,commentline-   ] <?> "definition"-  return ()--datefield = do-  string "date-field"-  many1 spacenonewline-  v <- restofline-  updateState (\r -> r{dateField=readMay v})--effectivedatefield = do-  string "effective-date-field"-  many1 spacenonewline-  v <- restofline-  updateState (\r -> r{effectiveDateField=readMay v})--dateformat = do-  string "date-format"-  many1 spacenonewline-  v <- restofline-  updateState (\r -> r{dateFormat=Just v})--codefield = do-  string "code-field"-  many1 spacenonewline-  v <- restofline-  updateState (\r -> r{codeField=readMay v})--statusfield = do-  string "status-field"-  many1 spacenonewline-  v <- restofline-  updateState (\r -> r{statusField=readMay v})--descriptionFieldValue :: GenParser Char st [FormatString]-descriptionFieldValue = do---      try (fieldNo <* spacenonewline)-      try fieldNo-  <|> formatStrings-  where-    fieldNo = many1 digit >>= \x -> return [FormatField False Nothing Nothing $ FieldNo $ read x]--descriptionfield = do-  string "description-field"-  many1 spacenonewline-  formatS <- descriptionFieldValue-  restofline-  updateState (\x -> x{descriptionField=formatS})--amountfield = do-  string "amount-field"-  many1 spacenonewline-  v <- restofline-  x <- updateState (\r -> r{amountField=readMay v})-  return x--amountinfield = do-  choice [string "amount-in-field", string "in-field"]-  many1 spacenonewline-  v <- restofline-  updateState (\r -> r{amountInField=readMay v})--amountoutfield = do-  choice [string "amount-out-field", string "out-field"]-  many1 spacenonewline-  v <- restofline-  updateState (\r -> r{amountOutField=readMay v})--currencyfield = do-  string "currency-field"-  many1 spacenonewline-  v <- restofline-  updateState (\r -> r{currencyField=readMay v})--accountfield = do-  string "account-field"-  many1 spacenonewline-  v <- restofline-  updateState (\r -> r{accountField=readMay v})--account2field = do-  string "account2-field"-  many1 spacenonewline-  v <- restofline-  updateState (\r -> r{account2Field=readMay v})--basecurrency = do-  choice [string "base-currency", string "currency"]-  many1 spacenonewline-  v <- restofline-  updateState (\r -> r{baseCurrency=Just v})--baseaccount = do-  string "base-account"-  many1 spacenonewline-  v <- ledgeraccountname-  optional newline-  updateState (\r -> r{baseAccount=v})--accountrule :: GenParser Char CsvRules AccountRule-accountrule = do-  many blankorcommentline-  pats <- many1 matchreplacepattern-  guard $ length pats >= 2-  let pats' = init pats-      acct = either (fail.show) id $ runParser ledgeraccountname () "" $ fst $ last pats-  many blankorcommentline-  return (pats',acct)- <?> "account rule"--blanklines = many1 blankline--blankline = many spacenonewline >> newline >> return () <?> "blank line"--commentchar = oneOf ";#"--commentline = many spacenonewline >> commentchar >> restofline >> return () <?> "comment line"--blankorcommentline = choice' [blankline, commentline]--matchreplacepattern = do-  notFollowedBy commentchar-  matchpat <- many1 (noneOf "=\n")-  replpat <- optionMaybe $ do {char '='; many $ noneOf "\n"}-  newline-  return (matchpat,replpat)---- csv record conversion-formatD :: CsvRecord -> Bool -> Maybe Int -> Maybe Int -> Field -> String-formatD record leftJustified min max f = case f of -  FieldNo n       -> maybe "" show $ atMay record n-  -- Some of these might in theory in read from fields-  Format.Account  -> ""-  DepthSpacer     -> ""-  Total           -> ""-  DefaultDate     -> ""-  Description     -> ""- where-   show = formatValue leftJustified min max--formatDescription :: CsvRecord -> [FormatString] -> String-formatDescription _ [] = ""-formatDescription record (f:fs) = s ++ (formatDescription record fs)-  where s = case f of-                FormatLiteral l -> l-                FormatField leftJustified min max field  -> formatD record leftJustified min max field--transactionFromCsvRecord :: CsvRules -> CsvRecord -> Transaction-transactionFromCsvRecord rules fields =-  let -      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 = formatDescription fields (descriptionField rules)-      comment = ""-      precomment = ""-      baseacc = maybe (baseAccount rules) (atDef "" fields) (accountField rules)-      amountstr = getAmount rules fields-      amountstr' = strnegate amountstr where strnegate ('-':s) = s-                                             strnegate s = '-':s-      currency = maybe (fromMaybe "" $ baseCurrency rules) (atDef "" fields) (currencyField rules)-      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-      acct = maybe acct' (atDef "" fields) (account2Field rules)-      t = Transaction {-              tdate=date,-              teffectivedate=effectivedate,-              tstatus=status,-              tcode=code,-              tdescription=newdesc,-              tcomment=comment,-              tpreceding_comment_lines=precomment,-              tmetadata=[],-              tpostings=[-                   Posting {-                     pstatus=False,-                     paccount=acct,-                     pamount=amount,-                     pcomment="",-                     ptype=RegularPosting,-                     pmetadata=[],-                     ptransaction=Just t-                   },-                   Posting {-                     pstatus=False,-                     paccount=baseacc,-                     pamount=(-baseamount),-                     pcomment="",-                     ptype=RegularPosting,-                     pmetadata=[],-                     ptransaction=Just t-                   }-                  ]-            }-  in t---- | Convert some date string with unknown format to YYYY/MM/DD.-normaliseDate :: Maybe String -- ^ User-supplied date format: this should be tried in preference to all others-              -> String -> String-normaliseDate mb_user_format s =-    let parsewith = flip (parseTime defaultTimeLocale) s in-    maybe (error' $ "could not parse \""++s++"\" as a date, consider adding a date-format directive or upgrading")-          showDate $-          firstJust $ (map parsewith $-                       maybe [] (:[]) mb_user_format-                       -- the - modifier requires time-1.2.0.5, released-                       -- in 2011/5, so for now we emulate it for wider-                       -- compatibility.  time < 1.2.0.5 also has a buggy-                       -- %y which we don't do anything about.-                       -- ++ [-                       -- "%Y/%m/%d"-                       -- ,"%Y/%-m/%-d"-                       -- ,"%Y-%m-%d"-                       -- ,"%Y-%-m-%-d"-                       -- ,"%m/%d/%Y"-                       -- ,"%-m/%-d/%Y"-                       -- ,"%m-%d-%Y"-                       -- ,"%-m-%-d-%Y"-                       -- ]-                      )-                      ++ [-                       parseTime defaultTimeLocale "%Y/%m/%e" s-                      ,parseTime defaultTimeLocale "%Y-%m-%e" s-                      ,parseTime defaultTimeLocale "%m/%e/%Y" s-                      ,parseTime defaultTimeLocale "%m-%e-%Y" s-                      ,parseTime defaultTimeLocale "%Y/%m/%e" (take 5 s ++ "0" ++ drop 5 s)-                      ,parseTime defaultTimeLocale "%Y-%m-%e" (take 5 s ++ "0" ++ drop 5 s)-                      ,parseTime defaultTimeLocale "%m/%e/%Y" ('0':s)-                      ,parseTime defaultTimeLocale "%m-%e-%Y" ('0':s)-                      ]---- | Apply account matching rules to a transaction description to obtain--- the most appropriate account and a new description.-identify :: [AccountRule] -> String -> String -> (String,String)-identify rules defacct desc | null matchingrules = (defacct,desc)-                            | otherwise = (acct,newdesc)-    where-      matchingrules = filter ismatch rules :: [AccountRule]-          where ismatch = any ((`regexMatchesCI` desc) . fst) . fst-      (prs,acct) = head matchingrules-      p_ms_r = filter (\(_,m,_) -> m) $ map (\(p,r) -> (p, p `regexMatchesCI` desc, r)) prs-      (p,_,r) = head p_ms_r-      newdesc = case r of Just repl -> regexReplaceCI p repl desc-                          Nothing   -> desc--caseinsensitive = ("(?i)"++)--getAmount :: CsvRules -> CsvRecord -> String-getAmount rules fields = case amountField rules of-  Just f  -> maybe "" (atDef "" fields) $ Just f-  Nothing ->-    case (i, o) of-      (x, "") -> x-      ("", x) -> "-"++x-      p -> error' $ "using amount-in-field and amount-out-field, found a value in both fields: "++show p-    where-      i = maybe "" (atDef "" fields) (amountInField rules)-      o = maybe "" (atDef "" fields) (amountOutField rules)--tests_Hledger_Cli_Convert = TestList (test_parser ++ test_description_parsing)--test_description_parsing = [-      "description-field 1" ~: assertParseDescription "description-field 1\n" [FormatField False Nothing Nothing (FieldNo 1)]-    , "description-field 1 " ~: assertParseDescription "description-field 1 \n" [FormatField False Nothing Nothing (FieldNo 1)]-    , "description-field %(1)" ~: assertParseDescription "description-field %(1)\n" [FormatField False Nothing Nothing (FieldNo 1)]-    , "description-field %(1)/$(2)" ~: assertParseDescription "description-field %(1)/%(2)\n" [-          FormatField False Nothing Nothing (FieldNo 1)-        , FormatLiteral "/"-        , FormatField False Nothing Nothing (FieldNo 2)-        ]-    ]-  where-    assertParseDescription string expected = do assertParseEqual (parseDescription string) (nullrules {descriptionField = expected})-    parseDescription :: String -> Either ParseError CsvRules-    parseDescription x = runParser descriptionfieldWrapper nullrules "(unknown)" x-    descriptionfieldWrapper :: GenParser Char CsvRules CsvRules-    descriptionfieldWrapper = do-      descriptionfield-      r <- getState-      return r--test_parser =  [--   "convert rules parsing: empty file" ~: do-     -- let assertMixedAmountParse parseresult mixedamount =-     --         (either (const "parse error") showMixedAmountDebug parseresult) ~?= (showMixedAmountDebug mixedamount)-    assertParseEqual (parseCsvRules "unknown" "") nullrules--  ,"convert rules parsing: accountrule" ~: do-     assertParseEqual (parseWithCtx nullrules accountrule "A\na\n") -- leading blank line required-                 ([("A",Nothing)], "a")--  ,"convert rules parsing: trailing comments" ~: do-     assertParse (parseWithCtx nullrules csvrulesfile "A\na\n# \n#\n")--  ,"convert rules parsing: trailing blank lines" ~: do-     assertParse (parseWithCtx nullrules csvrulesfile "A\na\n\n  \n")--  -- not supported-  -- ,"convert rules parsing: no final newline" ~: do-  --    assertParse (parseWithCtx nullrules csvrulesfile "A\na")-  --    assertParse (parseWithCtx nullrules csvrulesfile "A\na\n# \n#")-  --    assertParse (parseWithCtx nullrules csvrulesfile "A\na\n\n  ")--                 -- (nullrules{-                 --   -- dateField=Maybe FieldPosition,-                 --   -- statusField=Maybe FieldPosition,-                 --   -- codeField=Maybe FieldPosition,-                 --   -- descriptionField=Maybe FieldPosition,-                 --   -- amountField=Maybe FieldPosition,-                 --   -- currencyField=Maybe FieldPosition,-                 --   -- baseCurrency=Maybe String,-                 --   -- baseAccount=AccountName,-                 --   accountRules=[-                 --        ([("A",Nothing)], "a")-                 --       ]-                 --  })--  ]
− Hledger/Cli/Format.hs
@@ -1,133 +0,0 @@-module Hledger.Cli.Format (-          parseFormatString-        , formatStrings-        , formatValue-        , FormatString(..)-        , Field(..)-        , tests-        ) where--import Numeric-import Data.Char (isPrint)-import Data.Maybe-import Test.HUnit-import Text.ParserCombinators.Parsec-import Text.Printf---data Field =-    Account-  | DefaultDate-  | Description-  | Total-  | DepthSpacer-  | FieldNo Int-    deriving (Show, Eq)--data FormatString =-    FormatLiteral String-  | FormatField Bool        -- Left justified ?-                (Maybe Int) -- Min width-                (Maybe Int) -- Max width-                Field       -- Field-  deriving (Show, Eq)--formatValue :: Bool -> Maybe Int -> Maybe Int -> String -> String-formatValue leftJustified min max value = printf formatS value-    where-      l = if leftJustified then "-" else ""-      min' = maybe "" show min-      max' = maybe "" (\i -> "." ++ (show i)) max-      formatS = "%" ++ l ++ min' ++ max' ++ "s"--parseFormatString :: String -> Either String [FormatString]-parseFormatString input = case (runParser formatStrings () "(unknown)") input of-    Left y -> Left $ show y-    Right x -> Right x--{--Parsers--}--field :: GenParser Char st Field-field = do-        try (string "account" >> return Account)-    <|> try (string "depth_spacer" >> return DepthSpacer)-    <|> try (string "date" >> return Description)-    <|> try (string "description" >> return Description)-    <|> try (string "total" >> return Total)-    <|> try (many1 digit >>= (\s -> return $ FieldNo $ read s))--formatField :: GenParser Char st FormatString-formatField = do-    char '%'-    leftJustified <- optionMaybe (char '-')-    minWidth <- optionMaybe (many1 $ digit)-    maxWidth <- optionMaybe (do char '.'; many1 $ digit) -- TODO: Can this be (char '1') *> (many1 digit)-    char '('-    f <- field-    char ')'-    return $ FormatField (isJust leftJustified) (parseDec minWidth) (parseDec maxWidth) f-    where-      parseDec s = case s of-        Just text -> Just m where ((m,_):_) = readDec text-        _ -> Nothing--formatLiteral :: GenParser Char st FormatString-formatLiteral = do-    s <- many1 c-    return $ FormatLiteral s-    where-      isPrintableButNotPercentage x = isPrint x && (not $ x == '%')-      c =     (satisfy isPrintableButNotPercentage <?> "printable character")-          <|> try (string "%%" >> return '%')--formatString :: GenParser Char st FormatString-formatString =-        formatField-    <|> formatLiteral--formatStrings :: GenParser Char st [FormatString]-formatStrings = many formatString--testFormat :: FormatString -> String -> String -> Assertion-testFormat fs value expected = assertEqual name expected actual-    where-        (name, actual) = case fs of-            FormatLiteral l -> ("literal", formatValue False Nothing Nothing l)-            FormatField leftJustify min max _ -> ("field", formatValue leftJustify min max value)--testParser :: String -> [FormatString] -> Assertion-testParser s expected = case (parseFormatString s) of-    Left  error -> assertFailure $ show error-    Right actual -> assertEqual ("Input: " ++ s) expected actual--tests = test [ formattingTests ++ parserTests ]--formattingTests = [-      testFormat (FormatLiteral " ")                                ""            " "-    , testFormat (FormatField False Nothing Nothing Description)    "description" "description"-    , testFormat (FormatField False (Just 20) Nothing Description)  "description" "         description"-    , testFormat (FormatField False Nothing (Just 20) Description)  "description" "description"-    , testFormat (FormatField True Nothing (Just 20) Description)   "description" "description"-    , testFormat (FormatField True (Just 20) Nothing Description)   "description" "description         "-    , testFormat (FormatField True (Just 20) (Just 20) Description) "description" "description         "-    , testFormat (FormatField True Nothing (Just 3) Description)    "description" "des"-    ]--parserTests = [-      testParser ""                             []-    , testParser "D"                            [FormatLiteral "D"]-    , testParser "%(date)"                      [FormatField False Nothing Nothing Description]-    , testParser "%(total)"                     [FormatField False Nothing Nothing Total]-    , testParser "Hello %(date)!"               [FormatLiteral "Hello ", FormatField False Nothing Nothing Description, FormatLiteral "!"]-    , testParser "%-(date)"                     [FormatField True Nothing Nothing Description]-    , testParser "%20(date)"                    [FormatField False (Just 20) Nothing Description]-    , testParser "%.10(date)"                   [FormatField False Nothing (Just 10) Description]-    , testParser "%20.10(date)"                 [FormatField False (Just 20) (Just 10) Description]-    , testParser "%20(account) %.10(total)\n"   [ FormatField False (Just 20) Nothing Account-                                                , FormatLiteral " "-                                                , FormatField False Nothing (Just 10) Total-                                                , FormatLiteral "\n"-                                                ]-  ]
Hledger/Cli/Histogram.hs view
@@ -14,8 +14,9 @@ import Hledger.Cli.Options import Hledger.Data import Hledger.Reports+import Hledger.Query import Prelude hiding (putStr)-import Hledger.Utils.UTF8 (putStr)+import Hledger.Utils.UTF8IOCompat (putStr)   barchar = '*'@@ -23,30 +24,26 @@ -- | Print a histogram of some statistic per reporting interval, such as -- number of postings per day. histogram :: CliOpts -> Journal -> IO ()-histogram CliOpts{reportopts_=reportopts_} j = do+histogram CliOpts{reportopts_=ropts} j = do   d <- getCurrentDay-  putStr $ showHistogram reportopts_ (optsToFilterSpec reportopts_ d) j+  putStr $ showHistogram ropts (queryFromOpts d ropts) j -showHistogram :: ReportOpts -> FilterSpec -> Journal -> String-showHistogram opts filterspec j = concatMap (printDayWith countBar) spanps+showHistogram :: ReportOpts -> Query -> Journal -> String+showHistogram opts q j = concatMap (printDayWith countBar) spanps     where       i = intervalFromOpts opts       interval | i == NoInterval = Days 1                | otherwise = i-      span = datespan filterspec `orDatesFrom` journalDateSpan j+      span = queryDateSpan (effective_ opts) q `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+      -- ps = sortBy (comparing postingDate) $ filterempties $ filter matchapats $ filterdepth $ journalPostings j+      ps = sortBy (comparing postingDate) $ filterempties $ filter (q `matchesPosting`) $ journalPostings j       filterempties-          | empty_ opts = id+          | queryEmpty q = id           | otherwise = filter (not . isZeroMixedAmount . pamount)-      matchapats = matchpats apats . paccount-      apats = acctpats filterspec-      filterdepth | interval == NoInterval = filter (\p -> accountNameLevel (paccount p) <= depth)-                  | otherwise = id-      depth = fromMaybe 99999 $ depth_ opts  printDayWith f (DateSpan b _, ts) = printf "%s %s\n" (show $ fromJust b) (f ts) 
+ Hledger/Cli/Incomestatement.hs view
@@ -0,0 +1,44 @@+{-# LANGUAGE QuasiQuotes, TemplateHaskell, OverloadedStrings #-}+{-|++The @incomestatement@ command prints a simple income statement (profit & loss) report.++-}++module Hledger.Cli.Incomestatement (+  incomestatement+ ,tests_Hledger_Cli_Incomestatement+) where++import qualified Data.Text.Lazy.IO as LT+import Test.HUnit+import Text.Shakespeare.Text++import Hledger+import Hledger.Cli.Options+import Hledger.Cli.Balance++-- | Print a simple income statement.+incomestatement :: CliOpts -> Journal -> IO ()+incomestatement CliOpts{reportopts_=ropts} j = do+  d <- getCurrentDay+  let q = queryFromOpts d ropts+      incomereport@(_,income)    = accountsReport ropts (And [q, journalIncomeAccountQuery j]) j+      expensereport@(_,expenses) = accountsReport ropts (And [q, journalExpenseAccountQuery j]) j+      total = income + expenses+  LT.putStr $ [lt|Income Statement++Revenues:+#{unlines $ accountsReportAsText ropts incomereport}+Expenses:+#{unlines $ accountsReportAsText ropts expensereport}++Total:+--------------------+#{padleft 20 $ showMixedAmountWithoutPrice total}+|]++tests_Hledger_Cli_Incomestatement :: Test+tests_Hledger_Cli_Incomestatement = TestList+ [+ ]
Hledger/Cli/Main.hs view
@@ -19,7 +19,7 @@ or ghci:  > $ ghci hledger-> > j <- readJournalFile "data/sample.journal"+> > j <- readJournalFile Nothing Nothing "data/sample.journal" > > register [] ["income","expenses"] j > 2008/01/01 income               income:salary                   $-1          $-1 > 2008/06/01 gift                 income:gifts                    $-1          $-2@@ -46,11 +46,13 @@ import System.Process import Text.Printf -import Hledger (ensureJournalFile)+import Hledger (ensureJournalFileExists) import Hledger.Cli.Add import Hledger.Cli.Balance-import Hledger.Cli.Convert+import Hledger.Cli.Balancesheet+import Hledger.Cli.Cashflow import Hledger.Cli.Histogram+import Hledger.Cli.Incomestatement import Hledger.Cli.Print import Hledger.Cli.Register import Hledger.Cli.Stats@@ -59,13 +61,21 @@ import Hledger.Cli.Utils import Hledger.Cli.Version import Hledger.Utils+import Hledger.Reports+import Hledger.Data.Dates  main :: IO () main = do   args <- getArgs   addons <- getHledgerAddonCommands   opts <- getHledgerCliOpts addons-  when (debug_ opts) $ printf "%s\n" prognameandversion >> printf "opts: %s\n" (show opts)+  when (debug_ opts) $ do+    printf "%s\n" prognameandversion+    printf "args: %s\n" (show args)+    printf "opts: %s\n" (show opts)+    d <- getCurrentDay+    printf "query: %s\n" (show $ queryFromOpts d $ reportopts_ opts)+   run' opts addons args     where       run' opts@CliOpts{command_=cmd} addons args@@ -73,17 +83,20 @@        | (null matchedaddon) && "version" `in_` (rawopts_ opts)         = putStrLn prognameandversion        | (null matchedaddon) && "binary-filename" `in_` (rawopts_ opts) = putStrLn $ binaryfilename progname        | null cmd                                        = putStr $ showModeHelp mainmode'-       | cmd `isPrefixOf` "add"                          = showModeHelpOr addmode      $ journalFilePathFromOpts opts >>= ensureJournalFile >> withJournalDo opts add-       | cmd `isPrefixOf` "convert"                      = showModeHelpOr convertmode  $ convert opts-       | cmd `isPrefixOf` "test"                         = showModeHelpOr testmode     $ runtests opts+       | cmd `isPrefixOf` "add"                          = showModeHelpOr addmode      $ journalFilePathFromOpts opts >>= ensureJournalFileExists >> withJournalDo opts add+       | cmd `isPrefixOf` "test"                         = showModeHelpOr testmode     $ test' opts        | any (cmd `isPrefixOf`) ["accounts","balance"]   = showModeHelpOr accountsmode $ withJournalDo opts balance        | any (cmd `isPrefixOf`) ["entries","print"]      = showModeHelpOr entriesmode  $ withJournalDo opts print'        | any (cmd `isPrefixOf`) ["postings","register"]  = showModeHelpOr postingsmode $ withJournalDo opts register        | any (cmd `isPrefixOf`) ["activity","histogram"] = showModeHelpOr activitymode $ withJournalDo opts histogram+       | any (cmd `isPrefixOf`) ["incomestatement","is"] = showModeHelpOr incomestatementmode $ withJournalDo opts incomestatement+       | any (cmd `isPrefixOf`) ["balancesheet","bs"]    = showModeHelpOr balancesheetmode $ withJournalDo opts balancesheet+       | any (cmd `isPrefixOf`) ["cashflow","cf"]        = showModeHelpOr cashflowmode $ withJournalDo opts cashflow        | cmd `isPrefixOf` "stats"                        = showModeHelpOr statsmode    $ withJournalDo opts stats        | not (null matchedaddon)                           = do                                                              when (debug_ opts) $ printf "running %s\n" shellcmd                                                              system shellcmd >>= exitWith+       | cmd == "convert"                                = optserror ("convert is no longer needed, just use -f FILE.csv") >> exitFailure        | otherwise                                       = optserror ("command "++cmd++" is not recognized") >> exitFailure        where         mainmode' = mainmode addons
Hledger/Cli/Options.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TemplateHaskell, ScopedTypeVariables #-} {-|  Command-line options for the hledger program, and option-parsing utilities.@@ -7,6 +7,7 @@  module Hledger.Cli.Options where+import Control.Exception as C import Data.List import Data.List.Split import Data.Maybe@@ -22,12 +23,12 @@ import Text.Printf  import Hledger-import Hledger.Cli.Format as Format+import Hledger.Data.FormatStrings as Format import Hledger.Cli.Version   -- 1. cmdargs mode and flag definitions, for the main and subcommand modes.--- Flag values are parsed initially to simple RawOpts to permit reuse.+-- Flag values are parsed initially to a simple association list to allow reuse.  type RawOpts = [(String,String)] @@ -59,11 +60,11 @@      groupUnnamed = [      ]     ,groupHidden = [+        convertmode      ]     ,groupNamed = [       ("Misc commands", [         addmode-       ,convertmode        ,testmode        ])      ,("\nReport commands", [@@ -72,6 +73,9 @@        ,postingsmode        -- ,transactionsmode        ,activitymode+       ,incomestatementmode+       ,balancesheetmode+       ,cashflowmode        ,statsmode        ])      ]@@ -80,6 +84,19 @@     }  } +-- backwards compatibility - allow cmdargs to recognise this command so we can detect and warn+convertmode = (commandmode ["convert"]) {+  modeValue = [("command","convert")]+ ,modeHelp = ""+ ,modeArgs = ([], Just $ flagArg (\s opts -> Right $ setopt "args" s opts) "[CSVFILE]")+ ,modeGroupFlags = Group {+     groupUnnamed = []+    ,groupHidden = []+    ,groupNamed = []+    }+ }+--+ addonmode name = defmode {   modeNames = [name]  ,modeHelp = printf "[-- OPTIONS]   run the %s-%s program" progname name@@ -107,6 +124,7 @@  fileflags = [   flagReq ["file","f"]  (\s opts -> Right $ setopt "file" s opts) "FILE" "use a different journal file; - means stdin"+ ,flagReq ["rules-file"]  (\s opts -> Right $ setopt "rules-file" s opts) "RULESFILE" "conversion rules for CSV (default: FILE.rules)"  ,flagReq ["alias"]  (\s opts -> Right $ setopt "alias" s opts)  "ACCT=ALIAS" "display ACCT's name as ALIAS in reports"  ] @@ -154,19 +172,6 @@     }  } -convertmode = (commandmode ["convert"]) {-  modeValue = [("command","convert")]- ,modeHelp = "show the specified CSV file as hledger journal entries"- ,modeArgs = ([], Just $ flagArg (\s opts -> Right $ setopt "args" s opts) "[CSVFILE]")- ,modeGroupFlags = Group {-     groupUnnamed = [-      flagReq ["rules-file"]  (\s opts -> Right $ setopt "rules-file" s opts) "FILE" "rules file to use (default: CSVFILE.rules)"-     ]-    ,groupHidden = []-    ,groupNamed = [(generalflagstitle, generalflags3)]-    }- }- testmode = (commandmode ["test"]) {   modeHelp = "run self-tests, or just the ones matching REGEXPS"  ,modeArgs = ([], Just $ flagArg (\s opts -> Right $ setopt "args" s opts) "[REGEXPS]")@@ -177,7 +182,7 @@     }  } -accountsmode = (commandmode ["balance","accounts"]) {+accountsmode = (commandmode ["balance","bal","accounts"]) {   modeHelp = "(or accounts) show matched accounts and their balances"  ,modeArgs = ([], Just commandargsflag)  ,modeGroupFlags = Group {@@ -234,6 +239,36 @@     }  } +incomestatementmode = (commandmode ["incomestatement","is"]) {+  modeHelp = "show a standard income statement"+ ,modeArgs = ([], Just commandargsflag)+ ,modeGroupFlags = Group {+     groupUnnamed = []+    ,groupHidden = []+    ,groupNamed = [(generalflagstitle, generalflags1)]+    }+ }++balancesheetmode = (commandmode ["balancesheet","bs"]) {+  modeHelp = "show a standard balance sheet"+ ,modeArgs = ([], Just commandargsflag)+ ,modeGroupFlags = Group {+     groupUnnamed = []+    ,groupHidden = []+    ,groupNamed = [(generalflagstitle, generalflags1)]+    }+ }++cashflowmode = (commandmode ["cashflow","cf"]) {+  modeHelp = "show a simple cashflow statement"+ ,modeArgs = ([], Just commandargsflag)+ ,modeGroupFlags = Group {+     groupUnnamed = []+    ,groupHidden = []+    ,groupNamed = [(generalflagstitle, generalflags1)]+    }+ }+ statsmode = (commandmode ["stats"]) {   modeHelp = "show quick statistics for a journal (or part of it)"  ,modeArgs = ([], Just commandargsflag)@@ -253,10 +288,10 @@      rawopts_         :: RawOpts     ,command_         :: String     ,file_            :: Maybe FilePath+    ,rules_file_      :: Maybe FilePath     ,alias_           :: [String]     ,debug_           :: Bool     ,no_new_accounts_ :: Bool           -- add-    ,rules_file_      :: Maybe FilePath -- convert     ,reportopts_      :: ReportOpts  } deriving (Show) @@ -282,10 +317,10 @@               rawopts_         = rawopts              ,command_         = stringopt "command" rawopts              ,file_            = maybestringopt "file" rawopts-             ,alias_           = listofstringopt "alias" rawopts+             ,rules_file_      = maybestringopt "rules-file" rawopts+             ,alias_           = map stripquotes $ listofstringopt "alias" rawopts              ,debug_           = boolopt "debug" rawopts              ,no_new_accounts_ = boolopt "no-new-accounts" rawopts -- add-             ,rules_file_      = maybestringopt "rules-file" rawopts -- convert              ,reportopts_ = defreportopts {                              begin_     = maybesmartdateopt d "begin" rawopts                             ,end_       = maybesmartdateopt d "end" rawopts@@ -308,7 +343,7 @@                             ,quarterly_ = boolopt "quarterly" rawopts                             ,yearly_    = boolopt "yearly" rawopts                             ,format_    = maybestringopt "format" rawopts-                            ,patterns_  = listofstringopt "args" rawopts+                            ,query_     = unwords $ listofstringopt "args" rawopts                             }              } @@ -336,11 +371,11 @@     where       hledgerprog = string progname >> char '-' >> many1 (letter <|> char '-') >> eof -getEnvSafe v = getEnv v `catch` (\_ -> return "")-getDirectoryContentsSafe d = getDirectoryContents d `catch` (\_ -> return [])+getEnvSafe v = getEnv v `C.catch` (\(_::C.IOException) -> return "")+getDirectoryContentsSafe d = getDirectoryContents d `C.catch` (\(_::C.IOException) -> return [])  -- | Convert possibly encoded option values to regular unicode strings.-decodeRawOpts = map (\(name,val) -> (name, fromPlatformString val))+decodeRawOpts = map (\(name,val) -> (name, fromSystemString val))  -- A hacky workaround for http://code.google.com/p/ndmitchell/issues/detail?id=470 : -- we'd like to permit options before COMMAND as well as after it.@@ -364,7 +399,7 @@  stringopt name = fromMaybe "" . maybestringopt name -listofstringopt name rawopts = [stripquotes v | (n,v) <- rawopts, n==name]+listofstringopt name rawopts = [v | (k,v) <- rawopts, k==name]  maybeintopt :: String -> RawOpts -> Maybe Int maybeintopt name rawopts =@@ -415,21 +450,31 @@ -- | Default line format for balance report: "%20(total)  %2(depth_spacer)%-(account)" defaultBalanceFormatString :: [FormatString] defaultBalanceFormatString = [-      FormatField False (Just 20) Nothing Total+      FormatField False (Just 20) Nothing TotalField     , FormatLiteral "  "-    , FormatField True (Just 2) Nothing DepthSpacer-    , FormatField True Nothing Nothing Format.Account+    , FormatField True (Just 2) Nothing DepthSpacerField+    , FormatField True Nothing Nothing AccountField     ]  -- | Get the journal file path from options, an environment variable, or a default.--- If the path contains a literal tilde raise an error to avoid confusion.+-- If the path contains a literal tilde raise an error to avoid confusion. XXX journalFilePathFromOpts :: CliOpts -> IO String journalFilePathFromOpts opts = do-  f <- myJournalPath+  f <- defaultJournalPath   let f' = fromMaybe f $ file_ opts   if '~' `elem` f'    then error' $ printf "~ in the journal file path is not supported, please adjust (%s)" f'    else return f'++-- | Get the rules file path from options, if any.+-- If the path contains a literal tilde raise an error to avoid confusion. XXX+rulesFilePathFromOpts :: CliOpts -> Maybe FilePath+rulesFilePathFromOpts opts =+  case rules_file_ opts of+    Nothing -> Nothing+    Just f -> if '~' `elem` f+               then error' $ printf "~ in file paths is not supported, please adjust (%s)" f+               else Just f  aliasesFromOpts :: CliOpts -> [(AccountName,AccountName)] aliasesFromOpts = map parseAlias . alias_
Hledger/Cli/Print.hs view
@@ -7,23 +7,67 @@ module Hledger.Cli.Print (   print'  ,showTransactions+ ,tests_Hledger_Cli_Print ) where import Data.List+import Test.HUnit  import Hledger import Prelude hiding (putStr)-import Hledger.Utils.UTF8 (putStr)+import Hledger.Utils.UTF8IOCompat (putStr) import Hledger.Cli.Options  -- | Print journal transactions in standard format. print' :: CliOpts -> Journal -> IO () print' CliOpts{reportopts_=ropts} j = do   d <- getCurrentDay-  putStr $ showTransactions ropts (optsToFilterSpec ropts d) j+  putStr $ showTransactions ropts (queryFromOpts d ropts) j -showTransactions :: ReportOpts -> FilterSpec -> Journal -> String-showTransactions opts fspec j = entriesReportAsText opts fspec $ entriesReport opts fspec j+showTransactions :: ReportOpts -> Query -> Journal -> String+showTransactions opts q j = entriesReportAsText opts q $ entriesReport opts q j -entriesReportAsText :: ReportOpts -> FilterSpec -> EntriesReport -> String+tests_showTransactions = [+  "showTransactions" ~: do++   -- "print expenses" ~:+   do +    let opts = defreportopts{query_="expenses"}+    d <- getCurrentDay+    showTransactions opts (queryFromOpts d opts) samplejournal `is` unlines+     ["2008/06/03 * eat & shop"+     ,"    expenses:food                $1"+     ,"    expenses:supplies            $1"+     ,"    assets:cash                 $-2"+     ,""+     ]++  -- , "print report with depth arg" ~:+   do +    let opts = defreportopts{depth_=Just 2}+    d <- getCurrentDay+    showTransactions opts (queryFromOpts d opts) samplejournal `is` unlines+      ["2008/01/01 income"+      ,"    assets:bank:checking            $1"+      ,"    income:salary                  $-1"+      ,""+      ,"2008/06/01 gift"+      ,"    assets:bank:checking            $1"+      ,"    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"+      ,"    assets:bank:checking           $-1"+      ,""+      ]+ ]++entriesReportAsText :: ReportOpts -> Query -> EntriesReport -> String entriesReportAsText _ _ items = concatMap showTransactionUnelided items +tests_Hledger_Cli_Print = TestList+  tests_showTransactions
Hledger/Cli/Register.hs view
@@ -18,7 +18,7 @@  import Hledger import Prelude hiding (putStr)-import Hledger.Utils.UTF8 (putStr)+import Hledger.Utils.UTF8IOCompat (putStr) import Hledger.Cli.Options  @@ -26,12 +26,23 @@ register :: CliOpts -> Journal -> IO () register CliOpts{reportopts_=ropts} j = do   d <- getCurrentDay-  putStr $ postingsReportAsText ropts $ postingsReport ropts (optsToFilterSpec ropts d) j+  putStr $ postingsReportAsText ropts $ postingsReport ropts (queryFromOpts d ropts) j  -- | Render a register report as plain text suitable for console output. postingsReportAsText :: ReportOpts -> PostingsReport -> String postingsReportAsText opts = unlines . map (postingsReportItemAsText opts) . snd +tests_postingsReportAsText = [+  "postingsReportAsText" ~: do+  -- "unicode in register layout" ~: do+    j <- readJournal'+      "2009/01/01 * медвежья шкура\n  расходы:покупки  100\n  актив:наличные\n"+    let opts = defreportopts+    (postingsReportAsText opts $ postingsReport opts (queryFromOpts (parsedate "2008/11/26") opts) j) `is` unlines+      ["2009/01/01 медвежья шкура       расходы:покупки                 100          100"+      ,"                                актив:наличные                 -100            0"]+ ]+ -- | Render one register report line item as plain text. Eg: -- @ -- date (10)  description (20)     account (22)            amount (11)  balance (12)@@ -59,6 +70,4 @@  tests_Hledger_Cli_Register :: Test tests_Hledger_Cli_Register = TestList- [-- ]+  tests_postingsReportAsText
Hledger/Cli/Stats.hs view
@@ -16,7 +16,7 @@ import Hledger import Hledger.Cli.Options import Prelude hiding (putStr)-import Hledger.Utils.UTF8 (putStr)+import Hledger.Utils.UTF8IOCompat (putStr)   -- like Register.summarisePostings@@ -24,9 +24,9 @@ stats :: CliOpts -> Journal -> IO () stats CliOpts{reportopts_=reportopts_} j = do   d <- getCurrentDay-  let filterspec = optsToFilterSpec reportopts_ d-      l = journalToLedger filterspec j-      reportspan = (ledgerDateSpan l) `orDatesFrom` (datespan filterspec)+  let q = queryFromOpts d reportopts_+      l = journalToLedger q j+      reportspan = (ledgerDateSpan l) `orDatesFrom` (queryDateSpan False q)       intervalspans = splitSpan (intervalFromOpts reportopts_) reportspan       showstats = showLedgerStats l d       s = intercalate "\n" $ map showstats intervalspans@@ -40,7 +40,7 @@       w1 = maximum $ map (length . fst) stats       w2 = maximum $ map (length . show . snd) stats       stats = [-         ("Journal file", journalFilePath $ journal l)+         ("Journal file", journalFilePath $ ledgerJournal l)         ,("Transactions span", printf "%s to %s (%d days)" (start span) (end span) days)         ,("Last transaction", maybe "none" show lastdate ++ showelapsed lastelapsed)         ,("Transactions", printf "%d (%0.1f per day)" tnum txnrate)@@ -55,7 +55,7 @@       -- Days since last transaction : %(recentelapsed)s        ]            where-             ts = sortBy (comparing tdate) $ filter (spanContainsDate span . tdate) $ jtxns $ journal l+             ts = sortBy (comparing tdate) $ filter (spanContainsDate span . tdate) $ jtxns $ ledgerJournal l              as = nub $ map paccount $ concatMap tpostings ts              cs = Map.keys $ canonicaliseCommodities $ nub $ map commodity $ concatMap amounts $ map pamount $ concatMap tpostings ts              lastdate | null ts = Nothing
Hledger/Cli/Tests.hs view
@@ -1,28 +1,6 @@ {- | -This module contains hledger's unit tests. These are built in to hledger,-and can be run at any time by doing @hledger test@ (or, with a few more-options, by doing @make unittest@ in the hledger source tree.)--Other kinds of tests:--hledger's functional tests are a set of shell/command-line tests defined-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 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-@+A simple test runner for hledger's built-in unit tests.  -} @@ -37,22 +15,27 @@   -- | Run unit tests and exit with success or failure.-runtests :: CliOpts -> IO ()-runtests opts = do-  (hunitcounts,_) <- runtests' opts-  if errors hunitcounts > 0 || (failures hunitcounts > 0)+test' :: CliOpts -> IO ()+test' opts = do+  results <- runTests opts+  if errors results > 0 || failures results > 0    then exitFailure    else exitWith ExitSuccess --- | Run unit tests and exit on failure.-runTestsOrExit :: CliOpts -> IO ()-runTestsOrExit opts = do-  (hunitcounts,_) <- runtests' opts-  when (errors hunitcounts > 0 || (failures hunitcounts > 0)) $ exitFailure+-- | Run all or just the matched unit tests and return their HUnit result counts.+runTests :: CliOpts -> IO Counts+runTests = liftM (fst . flip (,) 0) . runTestTT . flatTests -runtests' :: Num b => CliOpts -> IO (Counts, b)-runtests' opts = liftM (flip (,) 0) $ runTestTT ts-    where-      ts = TestList $ filter matchname $ tflatten tests_Hledger_Cli  -- show flat test names-      -- ts = tfilter matchname $ TestList tests -- show hierarchical test names-      matchname = matchpats (patterns_ $ reportopts_ opts) . tname+-- | Run all or just the matched unit tests until the first failure or+-- error, returning the name of the problem test if any.+runTestsTillFailure :: CliOpts -> IO (Maybe String)+runTestsTillFailure _ = undefined -- do+  -- let ts = flatTests opts+  --     results = liftM (fst . flip (,) 0) $ runTestTT $+  --     firstproblem = find (\counts -> )++-- | All or pattern-matched tests, as a flat list to show simple names.+flatTests opts = TestList $ filter (matchesAccount (queryFromOpts nulldate $ reportopts_ opts) . testName) $ flattenTests tests_Hledger_Cli++-- | All or pattern-matched tests, in the original suites to show hierarchical names.+hierarchicalTests opts = filterTests (matchesAccount (queryFromOpts nulldate $ reportopts_ opts) . testName) tests_Hledger_Cli
Hledger/Cli/Utils.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE ScopedTypeVariables #-} {-|  Utilities for top-level modules and ghci. See also Hledger.Read and@@ -8,7 +9,6 @@ module Hledger.Cli.Utils     (      withJournalDo,-     readJournal',      journalReload,      journalReloadIfChanged,      journalFileIsNewer,@@ -21,7 +21,7 @@      Test(TestList),     ) where-import Control.Exception+import Control.Exception as C import Data.List import Data.Maybe import Safe (readMay)@@ -48,20 +48,16 @@   -- We kludgily read the file before parsing to grab the full text, unless   -- it's stdin, or it doesn't exist and we are adding. We read it strictly   -- to let the add command work.-  journalFilePathFromOpts opts >>= readJournalFile Nothing >>=+  journalFilePathFromOpts opts >>= readJournalFile Nothing (rulesFilePathFromOpts opts) >>=     either error' (cmd opts . journalApplyAliases (aliasesFromOpts opts))  -- -- | Get a journal from the given string and options, or throw an error. -- readJournalWithOpts :: CliOpts -> String -> IO Journal--- readJournalWithOpts opts s = readJournal Nothing s >>= either error' return---- | Get a journal from the given string, or throw an error.-readJournal' :: String -> IO Journal-readJournal' s = readJournal Nothing s >>= either error' return+-- readJournalWithOpts opts s = readJournal Nothing Nothing Nothing s >>= either error' return  -- | Re-read a journal from its data file, or return an error string. journalReload :: Journal -> IO (Either String Journal)-journalReload j = readJournalFile Nothing $ journalFilePath j+journalReload j = readJournalFile Nothing Nothing $ journalFilePath j  -- | Re-read a journal from its data file mostly, only if the file has -- changed since last read (or if there is no file, ie data read from@@ -100,7 +96,7 @@ fileModificationTime :: FilePath -> IO ClockTime fileModificationTime f     | null f = getClockTime-    | otherwise = getModificationTime f `Prelude.catch` \_ -> getClockTime+    | otherwise = getModificationTime f `C.catch` \(_::C.IOException) -> getClockTime  -- | Attempt to open a web browser on the given url, all platforms. openBrowserOn :: String -> IO ExitCode@@ -140,7 +136,7 @@ writeFileWithBackup f t = backUpFile f >> writeFile f t  readFileStrictly :: FilePath -> IO String-readFileStrictly f = readFile f >>= \s -> Control.Exception.evaluate (length s) >> return s+readFileStrictly f = readFile f >>= \s -> C.evaluate (length s) >> return s  -- | Back up this file with a (incrementing) numbered suffix, or give an error. backUpFile :: FilePath -> IO ()
Hledger/Cli/Version.hs view
@@ -19,8 +19,13 @@  -- package name and version from the cabal file progname, version, prognameandversion :: String+#if HADDOCK+progname = ""+version  = ""+#else progname = $(packageVariable (pkgName . package)) version  = $(packageVariable (pkgVersion . package))+#endif prognameandversion = progname ++ " " ++ version  -- developer build version strings include PATCHLEVEL (number of
hledger.cabal view
@@ -1,5 +1,5 @@ name:           hledger-version: 0.17+version: 0.18 category:       Finance synopsis:       The main command-line interface for the hledger accounting tool. description:@@ -17,7 +17,7 @@ homepage:       http://hledger.org bug-reports:    http://code.google.com/p/hledger/issues stability:      beta-tested-with:    GHC==7.0, GHC==7.2+tested-with:    GHC==7.0.4, GHC==7.2.2, GHC==7.4.1 cabal-version:  >= 1.8 build-type:     Simple -- data-dir:       data@@ -47,26 +47,26 @@   exposed-modules:                   Hledger.Cli                   Hledger.Cli.Main-                  Hledger.Cli.Format                   Hledger.Cli.Options                   Hledger.Cli.Tests                   Hledger.Cli.Utils                   Hledger.Cli.Version                   Hledger.Cli.Add                   Hledger.Cli.Balance-                  Hledger.Cli.Convert+                  Hledger.Cli.Balancesheet+                  Hledger.Cli.Cashflow                   Hledger.Cli.Histogram+                  Hledger.Cli.Incomestatement                   Hledger.Cli.Print                   Hledger.Cli.Register                   Hledger.Cli.Stats   -- should be the same as below   build-depends:-                  hledger-lib == 0.17+                  hledger-lib == 0.18                  ,base >= 3 && < 5                  ,cabal-file-th                  ,containers                  ,cmdargs >= 0.9.1   && < 0.10-                 ,csv                  ,directory                  ,filepath                  ,haskeline == 0.6.*@@ -78,7 +78,9 @@                  ,process                  ,regexpr >= 0.5.1                  ,safe >= 0.2+                 ,shakespeare-text == 1.0.*                  ,split == 0.1.*+                 ,text == 0.11.*                  ,time                  ,utf8-string >= 0.3.5 && < 0.4 @@ -95,15 +97,16 @@   other-modules:                   Hledger.Cli                   Hledger.Cli.Main-                  Hledger.Cli.Format                   Hledger.Cli.Options                   Hledger.Cli.Tests                   Hledger.Cli.Utils                   Hledger.Cli.Version                   Hledger.Cli.Add                   Hledger.Cli.Balance-                  Hledger.Cli.Convert+                  Hledger.Cli.Balancesheet+                  Hledger.Cli.Cashflow                   Hledger.Cli.Histogram+                  Hledger.Cli.Incomestatement                   Hledger.Cli.Print                   Hledger.Cli.Register                   Hledger.Cli.Stats@@ -114,12 +117,11 @@        ghc-options:   -threaded  -- should be the same as above   build-depends:-                  hledger-lib == 0.17+                  hledger-lib == 0.18                  ,base >= 3 && < 5                  ,cabal-file-th                  ,containers                  ,cmdargs >= 0.9.1   && < 0.10-                 ,csv                  ,directory                  ,filepath                  ,haskeline == 0.6.*@@ -131,6 +133,8 @@                  ,process                  ,regexpr >= 0.5.1                  ,safe >= 0.2+                 ,shakespeare-text == 1.0.*                  ,split == 0.1.*+                 ,text == 0.11.*                  ,time                  ,utf8-string >= 0.3.5 && < 0.4