packages feed

hledger-lib 1.3.2 → 1.4

raw patch · 35 files changed

+1103/−569 lines, 35 filesdep −ghc-primdep −old-localedep ~ansi-terminaldep ~megaparsecdep ~parsecPVP ok

version bump matches the API change (PVP)

Dependencies removed: ghc-prim, old-locale

Dependency ranges changed: ansi-terminal, megaparsec, parsec, time

API changes (from Hackage documentation)

- Hledger.Data.Posting: postingAllImplicitTags :: Posting -> [Tag]
- Hledger.Data.StringFormat: tests :: Test
- Hledger.Reports.ReportOptions: instance Data.Default.Class.Default GHC.Types.Bool
+ Hledger.Data.Account: sortAccountTreeByAmount :: NormalBalance -> Account -> Account
+ Hledger.Data.Journal: journalPivot :: Text -> Journal -> Journal
+ Hledger.Data.StringFormat: tests_Hledger_Data_StringFormat :: Test
+ Hledger.Data.Types: NormalNegative :: NormalBalance
+ Hledger.Data.Types: NormalPositive :: NormalBalance
+ Hledger.Data.Types: data NormalBalance
+ Hledger.Data.Types: instance Data.Data.Data Hledger.Data.Types.NormalBalance
+ Hledger.Data.Types: instance GHC.Classes.Eq Hledger.Data.Types.NormalBalance
+ Hledger.Data.Types: instance GHC.Show.Show Hledger.Data.Types.NormalBalance
+ Hledger.Read: readJournalFilesWithOpts :: InputOpts -> [FilePath] -> IO (Either String Journal)
+ Hledger.Read.Common: InputOpts :: Maybe StorageFormat -> Maybe FilePath -> [String] -> Bool -> Bool -> Bool -> Bool -> String -> InputOpts
+ Hledger.Read.Common: [aliases_] :: InputOpts -> [String]
+ Hledger.Read.Common: [anon_] :: InputOpts -> Bool
+ Hledger.Read.Common: [ignore_assertions_] :: InputOpts -> Bool
+ Hledger.Read.Common: [mformat_] :: InputOpts -> Maybe StorageFormat
+ Hledger.Read.Common: [mrules_file_] :: InputOpts -> Maybe FilePath
+ Hledger.Read.Common: [new_] :: InputOpts -> Bool
+ Hledger.Read.Common: [new_save_] :: InputOpts -> Bool
+ Hledger.Read.Common: [pivot_] :: InputOpts -> String
+ Hledger.Read.Common: data InputOpts
+ Hledger.Read.Common: definputopts :: InputOpts
+ Hledger.Read.Common: instance Data.Data.Data Hledger.Read.Common.InputOpts
+ Hledger.Read.Common: instance Data.Default.Class.Default Hledger.Read.Common.InputOpts
+ Hledger.Read.Common: instance GHC.Show.Show Hledger.Read.Common.InputOpts
+ Hledger.Read.Common: rawOptsToInputOpts :: RawOpts -> InputOpts
+ Hledger.Reports.MultiBalanceReports: tests_Hledger_Reports_MultiBalanceReport :: Test
+ Hledger.Reports.ReportOptions: [normalbalance_] :: ReportOpts -> Maybe NormalBalance
+ Hledger.Reports.ReportOptions: [sort_amount_] :: ReportOpts -> Bool
+ Hledger.Utils: instance Data.Default.Class.Default GHC.Types.Bool
- Hledger.Reports.ReportOptions: ReportOpts :: Period -> Interval -> [Status] -> Bool -> Maybe Int -> Maybe DisplayExp -> Bool -> Bool -> Bool -> Bool -> Maybe FormatStr -> String -> Bool -> Bool -> BalanceType -> AccountListMode -> Int -> Bool -> Bool -> Bool -> Bool -> Bool -> ReportOpts
+ Hledger.Reports.ReportOptions: ReportOpts :: Period -> Interval -> [Status] -> Bool -> Maybe Int -> Maybe DisplayExp -> Bool -> Bool -> Bool -> Bool -> Maybe FormatStr -> String -> Bool -> Bool -> BalanceType -> AccountListMode -> Int -> Bool -> Bool -> Bool -> Bool -> Bool -> Maybe NormalBalance -> Bool -> ReportOpts

Files

CHANGES view
@@ -1,13 +1,44 @@ API-ish changes in the hledger-lib package. See also the hledger and project change logs (for user-visible changes). -# 1.3.2 (2017/9/6)+# 1.4 (2017/9/30) -* fix missing parsec dep breaking doctests with ghc 8.2 (fpco/stackage#2835)+* add readJournalFile[s]WithOpts, with simpler arguments and support+for detecting new transactions since the last read. +* query: add payee: and note: query terms, improve description/payee/note docs (Jakub Zárybnický, Simon Michael, #598, #608)++* journal, cli: make trailing whitespace significant in regex account aliases+Trailing whitespace in the replacement part of a regular expression+account alias is now significant. Eg, converting a parent account to+just an account name prefix: --alias '/:acct:/=:acct '++* timedot: allow a quantity of seconds, minutes, days, weeks, months+  or years to be logged as Ns, Nm, Nd, Nw, Nmo, Ny++* csv: switch the order of generated postings, so account1 is first.+This simplifies things and facilitates future improvements.++* csv: show the "creating/using rules file" message only with --debug++* csv: fix multiple includes in one rules file++* csv: add "newest-first" rule for more robust same-day ordering++* deps: allow ansi-terminal 0.7++* deps: add missing parsec lower bound, possibly related to #596, fpco/stackage#2835++* deps: drop oldtime flag, require time 1.5+++* deps: remove ghc < 7.6 support, remove obsolete CPP conditionals++* deps: fix test suite with ghc 8.2++ # 1.3.1 (2017/8/25) -* Fix a bug with -H showing nothing for empty periods (Nicholas Niro)+* Fix a bug with -H showing nothing for empty periods (#583, Nicholas Niro) This patch fixes a bug that happened when using the -H option on a period without any transaction. Previously, the behavior was no output at all even though it should have shown the previous ending balances
Hledger/Data/Account.hs view
@@ -11,7 +11,9 @@ where import Data.List import Data.Maybe+import Data.Ord import qualified Data.Map as M+import Data.Text (pack,unpack) import Safe (headMay, lookupJustDef) import Test.HUnit import Text.Printf@@ -26,7 +28,7 @@ -- deriving instance Show Account instance Show Account where     show Account{..} = printf "Account %s (boring:%s, postings:%d, ebalance:%s, ibalance:%s)"-                       aname+                       (pack $ regexReplace ":" "_" $ unpack aname)  -- hide : so pretty-show doesn't break line                        (if aboring then "y" else "n" :: String)                        anumpostings                        (showMixedAmount aebalance)@@ -182,6 +184,20 @@ filterAccounts p a     | p a       = a : concatMap (filterAccounts p) (asubs a)     | otherwise = concatMap (filterAccounts p) (asubs a)++-- | Sort each level of an account tree by inclusive amount,+-- so that the accounts with largest normal balances are listed first.  +-- The provided normal balance sign determines whether normal balances+-- are negative or positive.+sortAccountTreeByAmount :: NormalBalance -> Account -> Account+sortAccountTreeByAmount normalsign a+  | null $ asubs a = a+  | otherwise      = a{asubs=+                        sortBy (maybeflip $ comparing aibalance) $ +                        map (sortAccountTreeByAmount normalsign) $ asubs a}+  where+    maybeflip | normalsign==NormalNegative = id+              | otherwise                  = flip  -- | Search an account list by name. lookupAccount :: AccountName -> [Account] -> Maybe Account
Hledger/Data/Journal.hs view
@@ -20,6 +20,7 @@   commodityStylesFromAmounts,   journalConvertAmountsToCost,   journalFinalise,+  journalPivot,   -- * Filtering   filterJournalTransactions,   filterJournalPostings,@@ -293,9 +294,9 @@ -- | A query for Cash (-equivalent) accounts in this journal (ie, -- accounts which appear on the cashflow statement.)  This is currently -- hard-coded to be all the Asset accounts except for those containing the--- case-insensitive regex @(receivable|A/R)@.+-- case-insensitive regex @(receivable|:A/R|:fixed)@. journalCashAccountQuery  :: Journal -> Query-journalCashAccountQuery j = And [journalAssetAccountQuery j, Not $ Acct "(receivable|A/R)"]+journalCashAccountQuery j = And [journalAssetAccountQuery j, Not $ Acct "(receivable|:A/R|:fixed)"]  -- Various kinds of filtering on journals. We do it differently depending -- on the command.@@ -885,6 +886,32 @@                             ]} -- #endif +-- | Apply the pivot transformation to all postings in a journal,+-- replacing their account name by their value for the given field or tag.+journalPivot :: Text -> Journal -> Journal+journalPivot fieldortagname j = j{jtxns = map (transactionPivot fieldortagname) . jtxns $ j}++-- | Replace this transaction's postings' account names with the value+-- of the given field or tag, if any.+transactionPivot :: Text -> Transaction -> Transaction         +transactionPivot fieldortagname t = t{tpostings = map (postingPivot fieldortagname) . tpostings $ t}++-- | Replace this posting's account name with the value+-- of the given field or tag, if any, otherwise the empty string.+postingPivot :: Text -> Posting -> Posting         +postingPivot fieldortagname p = p{paccount = pivotedacct, porigin = Just $ originalPosting p}+  where+    pivotedacct+      | Just t <- ptransaction p, fieldortagname == "code"        = tcode t  +      | Just t <- ptransaction p, fieldortagname == "description" = tdescription t  +      | Just t <- ptransaction p, fieldortagname == "payee"       = transactionPayee t  +      | Just t <- ptransaction p, fieldortagname == "note"        = transactionNote t  +      | Just (_, value) <- postingFindTag fieldortagname p        = value+      | otherwise                                                 = ""++postingFindTag :: TagName -> Posting -> Maybe (TagName, TagValue)         +postingFindTag tagname p = find ((tagname==) . fst) $ postingAllTags p+ -- Misc helpers  -- | Check if a set of hledger account/description filter patterns matches the@@ -929,6 +956,10 @@ --     expenses:supplies  $1 --     assets:cash --+-- 2008/10/01 take a loan+--     assets:bank:checking $1+--     liabilities:debts    $-1+-- -- 2008/12/31 * pay off --     liabilities:debts  $1 --     assets:bank:checking@@ -1000,6 +1031,22 @@              tpostings=["expenses:food" `post` usd 1                        ,"expenses:supplies" `post` usd 1                        ,"assets:cash" `post` missingamt+                       ],+             tpreceding_comment_lines=""+           }+          ,+           txnTieKnot $ Transaction {+             tindex=0,+             tsourcepos=nullsourcepos,+             tdate=parsedate "2008/10/01",+             tdate2=Nothing,+             tstatus=Unmarked,+             tcode="",+             tdescription="take a loan",+             tcomment="",+             ttags=[],+             tpostings=["assets:bank:checking" `post` usd 1+                       ,"liabilities:debts" `post` usd (-1)                        ],              tpreceding_comment_lines=""            }
Hledger/Data/Ledger.hs view
@@ -93,8 +93,8 @@ tests_ledgerFromJournal = [  "ledgerFromJournal" ~: do   assertEqual "" (0) (length $ ledgerPostings $ ledgerFromJournal Any nulljournal)-  assertEqual "" (11) (length $ ledgerPostings $ ledgerFromJournal Any samplejournal)-  assertEqual "" (6) (length $ ledgerPostings $ ledgerFromJournal (Depth 2) samplejournal)+  assertEqual "" (13) (length $ ledgerPostings $ ledgerFromJournal Any samplejournal)+  assertEqual "" (7) (length $ ledgerPostings $ ledgerFromJournal (Depth 2) samplejournal)  ]  tests_Hledger_Data_Ledger = TestList $
Hledger/Data/Posting.hs view
@@ -25,7 +25,6 @@   hasAmount,   postingAllTags,   transactionAllTags,-  postingAllImplicitTags,   relatedPostings,   removePrices,   -- * date operations@@ -175,33 +174,22 @@                                Nothing -> Unmarked   | otherwise = s --- | Implicit tags for this transaction.-transactionImplicitTags :: Transaction -> [Tag]-transactionImplicitTags t = filter (not . T.null . snd) [("code", tcode t)-                                                        ,("description", tdescription t)-                                                        ,("payee", transactionPayee t)-                                                        ,("note", transactionNote t)-                                                        ]- transactionPayee :: Transaction -> Text transactionPayee = fst . payeeAndNoteFromDescription . tdescription  transactionNote :: Transaction -> Text-transactionNote = fst . payeeAndNoteFromDescription . tdescription+transactionNote = snd . payeeAndNoteFromDescription . tdescription  -- | Parse a transaction's description into payee and note (aka narration) fields, -- assuming a convention of separating these with | (like Beancount). -- Ie, everything up to the first | is the payee, everything after it is the note. -- When there's no |, payee == note == description. payeeAndNoteFromDescription :: Text -> (Text,Text)-payeeAndNoteFromDescription t = (textstrip p, textstrip $ T.tail n)+payeeAndNoteFromDescription t+  | T.null n = (t, t)+  | otherwise = (textstrip p, textstrip $ T.drop 1 n)   where-    (p,n) = T.breakOn "|" t---- | Tags for this posting including implicit and any inherited from its parent transaction.-postingAllImplicitTags :: Posting -> [Tag]-postingAllImplicitTags p = ptags p ++ maybe [] transactionTags (ptransaction p)-    where transactionTags t = ttags t ++ transactionImplicitTags t+    (p, n) = T.span (/= '|') t  -- | Tags for this posting including any inherited from its parent transaction. postingAllTags :: Posting -> [Tag]
Hledger/Data/StringFormat.hs view
@@ -10,7 +10,7 @@         , StringFormat(..)         , StringFormatComponent(..)         , ReportItemField(..)-        , tests+        , tests_Hledger_Data_StringFormat         ) where  import Prelude ()@@ -147,7 +147,7 @@     Left  error -> assertFailure $ show error     Right actual -> assertEqual ("Input: " ++ s) expected actual -tests = test [ formattingTests ++ parserTests ]+tests_Hledger_Data_StringFormat = test [ formattingTests ++ parserTests ]  formattingTests = [       testFormat (FormatLiteral " ")                                ""            " "
Hledger/Data/Types.hs view
@@ -356,6 +356,15 @@   aboring                   :: Bool           -- ^ used in the accounts report to label elidable parents   } deriving (Typeable, Data, Generic) +-- | Whether an account's balance is normally a positive number (in accounting terms,+-- normally a debit balance), as for asset and expense accounts, or a negative number+-- (in accounting terms, normally a credit balance), as for liability, equity and +-- income accounts. Cf https://en.wikipedia.org/wiki/Normal_balance .+data NormalBalance = +    NormalPositive -- ^ normally debit - assets, expenses...+  | NormalNegative -- ^ normally credit - liabilities, equity, income...+  deriving (Show, Data, Eq) + -- | A Ledger has the journal it derives from, and the accounts -- derived from that. Accounts are accessible both list-wise and -- tree-wise, since each one knows its parent and subs; the first
Hledger/Query.hs view
@@ -225,6 +225,8 @@     ,"amt"     ,"code"     ,"desc"+    ,"payee"+    ,"note"     ,"acct"     ,"date"     ,"date2"@@ -260,6 +262,8 @@     Right _ -> Left Any -- not:somequeryoption will be ignored parseQueryTerm _ (T.stripPrefix "code:" -> Just s) = Left $ Code $ T.unpack s parseQueryTerm _ (T.stripPrefix "desc:" -> Just s) = Left $ Desc $ T.unpack s+parseQueryTerm _ (T.stripPrefix "payee:" -> Just s) = Left $ Tag "payee" $ Just $ T.unpack s+parseQueryTerm _ (T.stripPrefix "note:" -> Just s) = Left $ Tag "note" $ Just $ T.unpack s parseQueryTerm _ (T.stripPrefix "acct:" -> Just s) = Left $ Acct $ T.unpack s parseQueryTerm d (T.stripPrefix "date2:" -> Just s) =         case parsePeriodExpr d s of Left e         -> error' $ "\"date2:"++T.unpack s++"\" gave a "++showDateParseError e@@ -294,6 +298,8 @@     "status:!" `gives` (Left $ StatusQ Pending)     "status:0" `gives` (Left $ StatusQ Unmarked)     "status:" `gives` (Left $ StatusQ Unmarked)+    "payee:x" `gives` (Left $ Tag "payee" (Just "x"))+    "note:x" `gives` (Left $ Tag "note" (Just "x"))     "real:1" `gives` (Left $ Real True)     "date:2008" `gives` (Left $ Date $ DateSpan (Just $ parsedate "2008/01/01") (Just $ parsedate "2009/01/01"))     "date:from 2012/5/17" `gives` (Left $ Date $ DateSpan (Just $ parsedate "2012/05/17") Nothing)@@ -684,8 +690,10 @@ -- matchesPosting (Empty True) Posting{pamount=a} = isZeroMixedAmount a matchesPosting (Empty _) _ = True matchesPosting (Sym r) Posting{pamount=Mixed as} = any (regexMatchesCI $ "^" ++ r ++ "$") $ map (T.unpack . acommodity) as-matchesPosting (Tag n v) p = not $ null $ matchedTags n v $ postingAllTags p--- matchesPosting _ _ = False+matchesPosting (Tag n v) p = case (n, v) of+  ("payee", Just v) -> maybe False (regexMatchesCI v . T.unpack . transactionPayee) $ ptransaction p+  ("note", Just v) -> maybe False (regexMatchesCI v . T.unpack . transactionNote) $ ptransaction p+  (n, v) -> matchesTags n v $ postingAllTags p  tests_matchesPosting = [    "matchesPosting" ~: do@@ -737,9 +745,10 @@ matchesTransaction (Empty _) _ = True matchesTransaction (Depth d) t = any (Depth d `matchesPosting`) $ tpostings t matchesTransaction q@(Sym _) t = any (q `matchesPosting`) $ tpostings t-matchesTransaction (Tag n v) t = not $ null $ matchedTags n v $ transactionAllTags t---- matchesTransaction _ _ = False+matchesTransaction (Tag n v) t = case (n, v) of+  ("payee", Just v) -> regexMatchesCI v . T.unpack . transactionPayee $ t+  ("note", Just v) -> regexMatchesCI v . T.unpack . transactionNote $ t+  (n, v) -> matchesTags n v $ transactionAllTags t  tests_matchesTransaction = [   "matchesTransaction" ~: do@@ -749,14 +758,16 @@    assertBool "" $ (Desc "x x") `matchesTransaction` nulltransaction{tdescription="x x"}    -- see posting for more tag tests    assertBool "" $ (Tag "foo" (Just "a")) `matchesTransaction` nulltransaction{ttags=[("foo","bar")]}+   assertBool "" $ (Tag "payee" (Just "payee")) `matchesTransaction` nulltransaction{tdescription="payee|note"}+   assertBool "" $ (Tag "note" (Just "note")) `matchesTransaction` nulltransaction{tdescription="payee|note"}    -- a tag match on a transaction also matches posting tags    assertBool "" $ (Tag "postingtag" Nothing) `matchesTransaction` nulltransaction{tpostings=[nullposting{ptags=[("postingtag","")]}]}  ]  -- | Filter a list of tags by matching against their names and -- optionally also their values.-matchedTags :: Regexp -> Maybe Regexp -> [Tag] -> [Tag]-matchedTags namepat valuepat tags = filter (match namepat valuepat) tags+matchesTags :: Regexp -> Maybe Regexp -> [Tag] -> Bool+matchesTags namepat valuepat = not . null . filter (match namepat valuepat)   where     match npat Nothing     (n,_) = regexMatchesCI npat (T.unpack n) -- XXX     match npat (Just vpat) (n,v) = regexMatchesCI npat (T.unpack n) && regexMatchesCI vpat (T.unpack v)
Hledger/Read.hs view
@@ -14,6 +14,7 @@   PrefixedFilePath,   defaultJournal,   defaultJournalPath,+  readJournalFilesWithOpts,   readJournalFiles,   readJournalFile,   requireJournalFileExists,@@ -41,18 +42,20 @@ import Control.Monad.Except import Data.List import Data.Maybe+import Data.Ord import Data.Text (Text) import qualified Data.Text as T+import Data.Time (Day) import Safe import System.Directory (doesFileExist, getHomeDirectory) import System.Environment (getEnv) import System.Exit (exitFailure)-import System.FilePath ((</>), takeExtension)-import System.IO (stderr)+import System.FilePath+import System.IO import Test.HUnit import Text.Printf -import Hledger.Data.Dates (getCurrentDay)+import Hledger.Data.Dates (getCurrentDay, parsedate, showDate) import Hledger.Data.Types import Hledger.Read.Common import qualified Hledger.Read.JournalReader   as JournalReader@@ -62,7 +65,6 @@ import qualified Hledger.Read.CsvReader       as CsvReader import Hledger.Utils import Prelude hiding (getContents, writeFile)-import Hledger.Utils.UTF8IOCompat (writeFile)   journalEnvVar           = "LEDGER_FILE"@@ -257,6 +259,111 @@     firstSuccessOrFirstError (e:_) []    = return $ Left e              -- none left, return first error     path' = fromMaybe "(string)" path ++--- New versions of readJournal* with easier arguments, and support for --new.++readJournalFilesWithOpts :: InputOpts -> [FilePath] -> IO (Either String Journal)+readJournalFilesWithOpts iopts =+  (right mconcat1 . sequence <$>) . mapM (readJournalFileWithOpts iopts)+  where+    mconcat1 :: Monoid t => [t] -> t+    mconcat1 [] = mempty+    mconcat1 x  = foldr1 mappend x++readJournalFileWithOpts :: InputOpts -> PrefixedFilePath -> IO (Either String Journal)+readJournalFileWithOpts iopts prefixedfile = do+  let +    (mfmt, f) = splitReaderPrefix prefixedfile+    iopts' = iopts{mformat_=firstJust [mfmt, mformat_ iopts]}+  requireJournalFileExists f+  t <- readFileOrStdinAnyLineEnding f+  ej <- readJournalWithOpts iopts' (Just f) t+  case ej of+    Left e  -> return $ Left e+    Right j | new_ iopts -> do+      ds <- previousLatestDates f+      let (newj, newds) = journalFilterSinceLatestDates ds j+      when (new_save_ iopts && not (null newds)) $ saveLatestDates newds f+      return $ Right newj+    Right j -> return $ Right j++-- A "LatestDates" is zero or more copies of the same date,+-- representing the latest transaction date read from a file,+-- and how many transactions there were on that date.+type LatestDates = [Day]++-- | Get all instances of the latest date in an unsorted list of dates.+-- Ie, if the latest date appears once, return it in a one-element list,+-- if it appears three times (anywhere), return three of it.+latestDates :: [Day] -> LatestDates+latestDates = headDef [] . take 1 . group . reverse . sort++-- | Remember that these transaction dates were the latest seen when+-- reading this journal file.+saveLatestDates :: LatestDates -> FilePath -> IO () +saveLatestDates dates f = writeFile (latestDatesFileFor f) $ unlines $ map showDate dates++-- | What were the latest transaction dates seen the last time this +-- journal file was read ? If there were multiple transactions on the+-- latest date, that number of dates is returned, otherwise just one.+-- Or none if no transactions were read, or if latest dates info is not +-- available for this file.+previousLatestDates :: FilePath -> IO LatestDates+previousLatestDates f = do+  let latestfile = latestDatesFileFor f+  exists <- doesFileExist latestfile+  if exists+  then map (parsedate . strip) . lines . strip . T.unpack <$> readFileStrictly latestfile+  else return []++-- | Where to save latest transaction dates for the given file path.+-- (.latest.FILE)+latestDatesFileFor :: FilePath -> FilePath+latestDatesFileFor f = dir </> ".latest" <.> fname+  where+    (dir, fname) = splitFileName f++readFileStrictly :: FilePath -> IO Text+readFileStrictly f = readFile' f >>= \t -> C.evaluate (T.length t) >> return t++-- | Given zero or more latest dates (all the same, representing the+-- latest previously seen transaction date, and how many transactions+-- were seen on that date), remove transactions with earlier dates+-- from the journal, and the same number of transactions on the+-- latest date, if any, leaving only transactions that we can assume+-- are newer. Also returns the new latest dates of the new journal.+journalFilterSinceLatestDates :: LatestDates -> Journal -> (Journal, LatestDates)+journalFilterSinceLatestDates [] j       = (j,  latestDates $ map tdate $ jtxns j)+journalFilterSinceLatestDates ds@(d:_) j = (j', ds')+  where+    samedateorlaterts     = filter ((>= d).tdate) $ jtxns j+    (samedatets, laterts) = span ((== d).tdate) $ sortBy (comparing tdate) samedateorlaterts+    newsamedatets         = drop (length ds) samedatets+    j'                    = j{jtxns=newsamedatets++laterts}+    ds'                   = latestDates $ map tdate $ samedatets++laterts++readJournalWithOpts :: InputOpts -> Maybe FilePath -> Text -> IO (Either String Journal)+readJournalWithOpts iopts mfile txt =+  tryReadersWithOpts iopts mfile specifiedorallreaders txt+  where+    specifiedorallreaders = maybe stablereaders (:[]) $ findReader (mformat_ iopts) mfile+    stablereaders = filter (not.rExperimental) readers++tryReadersWithOpts :: InputOpts -> Maybe FilePath -> [Reader] -> Text -> IO (Either String Journal)+tryReadersWithOpts iopts mpath readers txt = firstSuccessOrFirstError [] readers+  where+    firstSuccessOrFirstError :: [String] -> [Reader] -> IO (Either String Journal)+    firstSuccessOrFirstError [] []        = return $ Left "no readers found"+    firstSuccessOrFirstError errs (r:rs) = do+      dbg1IO "trying reader" (rFormat r)+      result <- (runExceptT . (rParser r) (mrules_file_ iopts) (not $ ignore_assertions_ iopts) path) txt+      dbg1IO "reader result" $ either id show result+      case result of Right j -> return $ Right j                        -- success!+                     Left e  -> firstSuccessOrFirstError (errs++[e]) rs -- keep trying+    firstSuccessOrFirstError (e:_) []    = return $ Left e              -- none left, return first error+    path = fromMaybe "(string)" mpath++---   -- tests
Hledger/Read/Common.hs view
@@ -13,7 +13,7 @@ -}  --- * module-{-# LANGUAGE CPP, RecordWildCards, NamedFieldPuns, NoMonoLocalBinds, ScopedTypeVariables, FlexibleContexts, TupleSections, OverloadedStrings #-}+{-# LANGUAGE CPP, DeriveDataTypeable, RecordWildCards, NamedFieldPuns, NoMonoLocalBinds, ScopedTypeVariables, FlexibleContexts, TupleSections, OverloadedStrings #-}  module Hledger.Read.Common where@@ -24,6 +24,8 @@ import Control.Monad.Except (ExceptT(..), runExceptT, throwError) --, catchError) import Control.Monad.State.Strict import Data.Char (isNumber)+import Data.Data+import Data.Default import Data.Functor.Identity import Data.List.Compat import Data.List.NonEmpty (NonEmpty(..))@@ -42,6 +44,39 @@ import Hledger.Utils  -- $setup++-- | Various options to use when reading journal files.+-- Similar to CliOptions.inputflags, simplifies the journal-reading functions.+data InputOpts = InputOpts {+     -- files_             :: [FilePath]+     mformat_           :: Maybe StorageFormat  -- ^ a file/storage format to try, unless overridden+                                                --   by a filename prefix. Nothing means try all.+    ,mrules_file_       :: Maybe FilePath       -- ^ a conversion rules file to use (when reading CSV)+    ,aliases_           :: [String]             -- ^ account name aliases to apply+    ,anon_              :: Bool                 -- ^ do light anonymisation/obfuscation of the data +    ,ignore_assertions_ :: Bool                 -- ^ don't check balance assertions+    ,new_               :: Bool                 -- ^ read only new transactions since this file was last read+    ,new_save_          :: Bool                 -- ^ save latest new transactions state for next time+    ,pivot_             :: String               -- ^ use the given field's value as the account name + } deriving (Show, Data) --, Typeable)++instance Default InputOpts where def = definputopts++definputopts :: InputOpts+definputopts = InputOpts def def def def def def True def++rawOptsToInputOpts :: RawOpts -> InputOpts+rawOptsToInputOpts rawopts = InputOpts{+   -- files_             = map (T.unpack . stripquotes . T.pack) $ listofstringopt "file" rawopts+   mformat_           = Nothing+  ,mrules_file_       = maybestringopt "rules-file" rawopts+  ,aliases_           = map (T.unpack . stripquotes . T.pack) $ listofstringopt "alias" rawopts+  ,anon_              = boolopt "anon" rawopts+  ,ignore_assertions_ = boolopt "ignore-assertions" rawopts+  ,new_               = boolopt "new" rawopts+  ,new_save_          = True+  ,pivot_             = stringopt "pivot" rawopts+  }  --- * parsing utils 
Hledger/Read/CsvReader.hs view
@@ -1,9 +1,9 @@-{-# LANGUAGE CPP #-} {-|  A reader for CSV data, using an extra rules file to help interpret the data.  -}+{-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-}@@ -40,6 +40,7 @@ import Data.Ord import Data.Text (Text) import qualified Data.Text as T+import qualified Data.Text.IO as T import Data.Time.Calendar (Day) #if MIN_VERSION_time(1,5,0) import Data.Time.Format (parseTimeM, defaultTimeLocale)@@ -50,12 +51,11 @@ import Safe import System.Directory (doesFileExist) import System.FilePath-import System.IO (stderr) import Test.HUnit hiding (State) import Text.CSV (parseCSV, CSV) import Text.Megaparsec.Compat hiding (parse) import qualified Text.Parsec as Parsec-import Text.Printf (hPrintf,printf)+import Text.Printf (printf)  import Hledger.Data import Hledger.Utils.UTF8IOCompat (getContents)@@ -103,7 +103,7 @@   rulestext <-     if rulesfileexists     then do-      hPrintf stderr "using conversion rules file %s\n" rulesfile+      dbg1IO "using conversion rules file" rulesfile       liftIO $ (readFile' rulesfile >>= expandIncludes (takeDirectory rulesfile))     else return $ defaultRulesText rulesfile   rules <- liftIO (runExceptT $ parseAndValidateCsvRules rulesfile rulestext) >>= either throwerr return @@ -141,17 +141,27 @@                    )                    (initialPos parsecfilename) records -  -- heuristic: if the records appear to have been in reverse date order,-  -- reverse them all as well as doing a txn date sort,-  -- so that same-day txns' original order is preserved-    txns' | length txns > 1 && tdate (head txns) > tdate (last txns) = reverse txns-          | otherwise = txns+    -- Ensure transactions are ordered chronologically.+    -- First, reverse them to get same-date transactions ordered chronologically,+    -- if the CSV records seem to be most-recent-first, ie if there's an explicit +    -- "newest-first" directive, or if there's more than one date and the first date+    -- is more recent than the last.+    txns' = +      (if newestfirst || mseemsnewestfirst == Just True then reverse else id) txns+      where+        newestfirst = dbg3 "newestfirst" $ isJust $ getDirective "newest-first" rules+        mseemsnewestfirst = dbg3 "mseemsnewestfirst" $  +          case nub $ map tdate txns of +            ds | length ds > 1 -> Just $ head ds > last ds +            _                  -> Nothing+    -- Second, sort by date.+    txns'' = sortBy (comparing tdate) txns'    when (not rulesfileexists) $ do-    hPrintf stderr "created default conversion rules file %s, edit this for better results\n" rulesfile+    dbg1IO "creating conversion rules file" rulesfile     writeFile rulesfile $ T.unpack rulestext -  return $ Right nulljournal{jtxns=sortBy (comparing tdate) txns'}+  return $ Right nulljournal{jtxns=txns''}  parseCsv :: FilePath -> String -> IO (Either Parsec.ParseError CSV) parseCsv path csvdata =@@ -210,6 +220,7 @@   ,"fields date, description, amount"   ,""   ,"#skip 1"+  ,"#newest-first"   ,""   ,"#date-format %-d/%-m/%Y"   ,"#date-format %-m/%-d/%Y"@@ -232,7 +243,7 @@  RULES: RULE* -RULE: ( FIELD-LIST | FIELD-ASSIGNMENT | CONDITIONAL-BLOCK | SKIP | DATE-FORMAT | COMMENT | BLANK ) NEWLINE+RULE: ( FIELD-LIST | FIELD-ASSIGNMENT | CONDITIONAL-BLOCK | SKIP | NEWEST-FIRST | DATE-FORMAT | COMMENT | BLANK ) NEWLINE  FIELD-LIST: fields SPACE FIELD-NAME ( SPACE? , SPACE? FIELD-NAME )* @@ -356,22 +367,18 @@ parseRulesFile f =    liftIO (readFile' f >>= expandIncludes (takeDirectory f)) >>= parseAndValidateCsvRules f --- | Look for hledger rules file-style include directives in this text,--- and interpolate the included files, recursively.--- Included file paths may be relative to the directory of the--- provided file path.+-- | Inline all files referenced by include directives in this hledger CSV rules text, recursively.+-- Included file paths may be relative to the directory of the provided file path. -- This is a cheap hack to avoid rewriting the CSV rules parser.-expandIncludes :: FilePath -> T.Text -> IO T.Text-expandIncludes basedir content = do-  let (ls,rest) = break (T.isPrefixOf "include") $ T.lines content-  case rest of-    [] -> return $ T.unlines ls-    ((T.stripPrefix "include" -> Just f):ls') -> do-      let f'       = basedir </> dropWhile isSpace (T.unpack f)-          basedir' = takeDirectory f'-      included <- readFile' f' >>= expandIncludes basedir'-      return $ T.unlines [T.unlines ls, included, T.unlines ls']-    ls' -> return $ T.unlines $ ls ++ ls'   -- should never get here+expandIncludes dir content = mapM (expandLine dir) (T.lines content) >>= return . T.unlines+  where+    expandLine dir line =+      case line of+        (T.stripPrefix "include " -> Just f) -> expandIncludes dir' =<< T.readFile f'+          where+            f' = dir </> dropWhile isSpace (T.unpack f)+            dir' = takeDirectory f'+        _ -> return line   -- | An error-throwing action that parses this text as CSV conversion rules  -- and runs some extra validation checks. The file path is for error messages.@@ -451,6 +458,7 @@   -- ,"default-currency"   -- ,"skip-lines" -- old   ,"skip"+  ,"newest-first"    -- ,"base-account"    -- ,"base-currency"   ]@@ -654,10 +662,10 @@        ++"change your amount or currency rules, "        ++"or "++maybe "add a" (const "change your") mskip++" skip rule"       ]-    -- Using costOfMixedAmount here to allow complex costs like "10 GBP @@ 15 USD".-    -- Aim is to have "10 GBP @@ 15 USD" applied to account2, but have "-15USD" applied to account1-    amount1        = costOfMixedAmount amount-    amount2        = (-amount)+    amount1        = amount+    -- convert balancing amount to cost like hledger print, so eg if +    -- amount1 is "10 GBP @@ 15 USD", amount2 will be "-15 USD".+    amount2        = costOfMixedAmount (-amount)     s `or` def  = if null s then def else s     defaccount1 = fromMaybe "unknown" $ mdirective "default-account1"     defaccount2 = case isNegativeMixedAmount amount2 of@@ -689,8 +697,8 @@       tcomment                 = T.pack comment,       tpreceding_comment_lines = T.pack precomment,       tpostings                =-        [posting {paccount=account2, pamount=amount2, ptransaction=Just t}-        ,posting {paccount=account1, pamount=amount1, ptransaction=Just t, pbalanceassertion=balance}+        [posting {paccount=account1, pamount=amount1, ptransaction=Just t, pbalanceassertion=balance}+        ,posting {paccount=account2, pamount=amount2, ptransaction=Just t}         ]       } 
Hledger/Read/JournalReader.hs view
@@ -321,7 +321,7 @@   old <- rstrip <$> (some $ noneOf ("=" :: [Char]))   char '='   many spacenonewline-  new <- rstrip <$> anyChar `manyTill` eolof  -- don't require a final newline, good for cli options+  new <- rstrip <$> anyChar `manyTill` eolof  -- eol in journal, eof in command lines, normally   return $ BasicAlias (T.pack old) (T.pack new)  regexaliasp :: TextParser m AccountAlias@@ -333,7 +333,7 @@   many spacenonewline   char '='   many spacenonewline-  repl <- rstrip <$> anyChar `manyTill` eolof+  repl <- anyChar `manyTill` eolof   return $ RegexAlias re repl  endaliasesdirectivep :: JournalParser m ()
Hledger/Read/TimedotReader.hs view
@@ -5,7 +5,9 @@  @ #DATE-#ACCT DOTS  # Each dot represents 15m, spaces are ignored+#ACCT  DOTS  # Each dot represents 15m, spaces are ignored+#ACCT  8    # numbers with or without a following h represent hours+#ACCT  5m   # numbers followed by m represent minutes  # on 2/1, 1h was spent on FOSS haskell work, 0.25h on research, etc. 2/1@@ -126,19 +128,41 @@   return t  timedotdurationp :: JournalParser m Quantity-timedotdurationp = try timedotnumberp <|> timedotdotsp+timedotdurationp = try timedotnumericp <|> timedotdotsp --- | Parse a duration written as a decimal number of hours (optionally followed by the letter h).+-- | Parse a duration of seconds, minutes, hours, days, weeks, months or years,+-- written as a decimal number followed by s, m, h, d, w, mo or y, assuming h+-- if there is no unit. Returns the duration as hours, assuming+-- 1m = 60s, 1h = 60m, 1d = 24h, 1w = 7d, 1mo = 30d, 1y=365d. -- @+-- 1.5 -- 1.5h+-- 90m -- @-timedotnumberp :: JournalParser m Quantity-timedotnumberp = do-   (q, _, _, _) <- lift numberp-   lift (many spacenonewline)-   optional $ char 'h'-   lift (many spacenonewline)-   return q+timedotnumericp :: JournalParser m Quantity+timedotnumericp = do+  (q, _, _, _) <- lift numberp+  msymbol <- optional $ choice $ map (string . fst) timeUnits+  lift (many spacenonewline)+  let q' = +        case msymbol of+          Nothing  -> q+          Just sym ->+            case lookup sym timeUnits of+              Just mult -> q * mult  +              Nothing   -> q  -- shouldn't happen.. ignore+  return q'++-- (symbol, equivalent in hours). +timeUnits =+  [("s",2.777777777777778e-4)+  ,("mo",5040) -- before "m"+  ,("m",1.6666666666666666e-2)+  ,("h",1)+  ,("d",24)+  ,("w",168)+  ,("y",61320)+  ]  -- | Parse a quantity written as a line of dots, each representing 0.25. -- @
Hledger/Reports.hs view
@@ -39,5 +39,6 @@  tests_Hledger_Reports_ReportOptions,  tests_Hledger_Reports_EntriesReport,  tests_Hledger_Reports_PostingsReport,- tests_Hledger_Reports_BalanceReport+ tests_Hledger_Reports_BalanceReport,+ tests_Hledger_Reports_MultiBalanceReport  ]
Hledger/Reports/BalanceReport.hs view
@@ -92,6 +92,7 @@                          dbg1 "accts" $                          take 1 $ clipAccountsAndAggregate (queryDepth q) $ flattenAccounts accts           | flat_ opts = dbg1 "accts" $+                         maybesortflat $                          filterzeros $                          filterempty $                          drop 1 $ clipAccountsAndAggregate (queryDepth q) $ flattenAccounts accts@@ -100,6 +101,7 @@                          drop 1 $ flattenAccounts $                          markboring $                          prunezeros $+                         maybesorttree $                          clipAccounts (queryDepth q) accts           where             balance     = if flat_ opts then aebalance else aibalance@@ -107,6 +109,12 @@             filterempty = filter (\a -> anumpostings a > 0 || not (isZeroMixedAmount (balance a)))             prunezeros  = if empty_ opts then id else fromMaybe nullacct . pruneAccounts (isZeroMixedAmount . balance)             markboring  = if no_elide_ opts then id else markBoringParentAccounts+            maybesortflat | sort_amount_ opts = sortBy (maybeflip $ comparing balance)+                          | otherwise = id+              where+                maybeflip = if normalbalance_ opts == Just NormalNegative then id else flip+            maybesorttree | sort_amount_ opts = sortAccountTreeByAmount (fromMaybe NormalPositive $ normalbalance_ opts)+                          | otherwise = id       items = dbg1 "items" $ map (balanceReportItem opts q) accts'       total | not (flat_ opts) = dbg1 "total" $ sum [amt | (_,_,indent,amt) <- items, indent == 0]             | otherwise        = dbg1 "total" $@@ -213,8 +221,10 @@   ,"balanceReport with no args on sample journal" ~: do    (defreportopts, samplejournal) `gives`     ([-      ("assets","assets",0, mamountp' "$-1.00")-     ,("assets:bank:saving","bank:saving",1, mamountp' "$1.00")+      ("assets","assets",0, mamountp' "$0.00")+     ,("assets:bank","bank",1, mamountp' "$2.00")+     ,("assets:bank:checking","checking",2, mamountp' "$1.00")+     ,("assets:bank:saving","saving",2, mamountp' "$1.00")      ,("assets:cash","cash",1, mamountp' "$-2.00")      ,("expenses","expenses",0, mamountp' "$2.00")      ,("expenses:food","food",1, mamountp' "$1.00")@@ -222,27 +232,22 @@      ,("income","income",0, mamountp' "$-2.00")      ,("income:gifts","gifts",1, mamountp' "$-1.00")      ,("income:salary","salary",1, mamountp' "$-1.00")-     ,("liabilities:debts","liabilities:debts",0, mamountp' "$1.00")      ],      Mixed [usd0])    ,"balanceReport with --depth=N" ~: do    (defreportopts{depth_=Just 1}, samplejournal) `gives`     ([-      ("assets",      "assets",      0, mamountp' "$-1.00")-     ,("expenses",    "expenses",    0, mamountp'  "$2.00")+     ("expenses",    "expenses",    0, mamountp'  "$2.00")      ,("income",      "income",      0, mamountp' "$-2.00")-     ,("liabilities", "liabilities", 0, mamountp'  "$1.00")      ],      Mixed [usd0])    ,"balanceReport with depth:N" ~: do    (defreportopts{query_="depth:1"}, samplejournal) `gives`     ([-      ("assets",      "assets",      0, mamountp' "$-1.00")-     ,("expenses",    "expenses",    0, mamountp'  "$2.00")+     ("expenses",    "expenses",    0, mamountp'  "$2.00")      ,("income",      "income",      0, mamountp' "$-2.00")-     ,("liabilities", "liabilities", 0, mamountp'  "$1.00")      ],      Mixed [usd0]) @@ -268,18 +273,29 @@   ,"balanceReport with not:desc:" ~: do    (defreportopts{query_="not:desc:income"}, samplejournal) `gives`     ([-      ("assets","assets",0, mamountp' "$-2.00")-     ,("assets:bank","bank",1, Mixed [usd0])-     ,("assets:bank:checking","checking",2,mamountp' "$-1.00")-     ,("assets:bank:saving","saving",2, mamountp' "$1.00")+      ("assets","assets",0, mamountp' "$-1.00")+     ,("assets:bank:saving","bank:saving",1, mamountp' "$1.00")      ,("assets:cash","cash",1, mamountp' "$-2.00")      ,("expenses","expenses",0, mamountp' "$2.00")      ,("expenses:food","food",1, mamountp' "$1.00")      ,("expenses:supplies","supplies",1, mamountp' "$1.00")      ,("income:gifts","income:gifts",0, mamountp' "$-1.00")-     ,("liabilities:debts","liabilities:debts",0, mamountp' "$1.00")      ],      Mixed [usd0])++  ,"balanceReport with period on a populated period" ~: do+    (defreportopts{period_= PeriodBetween (fromGregorian 2008 1 1) (fromGregorian 2008 1 2)}, samplejournal) `gives`+     (+      [+       ("assets:bank:checking","assets:bank:checking",0, mamountp' "$1.00")+      ,("income:salary","income:salary",0, mamountp' "$-1.00")+      ],+      Mixed [usd0])++   ,"balanceReport with period on an unpopulated period" ~: do+    (defreportopts{period_= PeriodBetween (fromGregorian 2008 1 2) (fromGregorian 2008 1 3)}, samplejournal) `gives`+     ([],Mixed [nullamt])+   {-
Hledger/Reports/MultiBalanceReports.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE FlexibleInstances, ScopedTypeVariables #-}+{-# LANGUAGE FlexibleInstances, ScopedTypeVariables, OverloadedStrings #-} {-|  Multi-column balance reports, used by the balance command.@@ -10,10 +10,10 @@   MultiBalanceReportRow,   multiBalanceReport,   multiBalanceReportValue,-  singleBalanceReport+  singleBalanceReport,    -- -- * Tests-  -- tests_Hledger_Reports_MultiBalanceReport+  tests_Hledger_Reports_MultiBalanceReport ) where @@ -22,11 +22,12 @@ import Data.Ord import Data.Time.Calendar import Safe--- import Test.HUnit+import Test.HUnit  import Hledger.Data import Hledger.Query import Hledger.Utils+import Hledger.Read (mamountp') import Hledger.Reports.ReportOptions import Hledger.Reports.BalanceReport @@ -35,7 +36,7 @@ -- -- 1. a list of each column's period (date span) ----- 2. a list of row items, each containing:+-- 2. a list of rows, each containing: -- --   * the full account name --@@ -43,7 +44,7 @@ -- --   * the account's depth -----   * the amounts to show in each column+--   * a list of amounts, one for each column -- --   * the total of the row's amounts --@@ -53,14 +54,14 @@ -- -- The meaning of the amounts depends on the type of multi balance -- report, of which there are three: periodic, cumulative and historical--- (see 'BalanceType' and "Hledger.Cli.Balance").+-- (see 'BalanceType' and "Hledger.Cli.Commands.Balance"). newtype MultiBalanceReport =   MultiBalanceReport ([DateSpan]                      ,[MultiBalanceReportRow]                      ,MultiBalanceReportTotals                      ) type MultiBalanceReportRow    = (AccountName, AccountName, Int, [MixedAmount], MixedAmount, MixedAmount)-type MultiBalanceReportTotals = ([MixedAmount], MixedAmount, MixedAmount)+type MultiBalanceReportTotals = ([MixedAmount], MixedAmount, MixedAmount) -- (Totals list, sum of totals, average of totals)  instance Show MultiBalanceReport where     -- use ppShow to break long lists onto multiple lines@@ -90,7 +91,7 @@ -- showing the change of balance, accumulated balance, or historical balance -- in each of the specified periods. multiBalanceReport :: ReportOpts -> Query -> Journal -> MultiBalanceReport-multiBalanceReport opts q j = MultiBalanceReport (displayspans, items, totalsrow)+multiBalanceReport opts q j = MultiBalanceReport (displayspans, sorteditems, totalsrow)     where       symq       = dbg1 "symq"   $ filterQuery queryIsSym $ dbg1 "requested q" q       depthq     = dbg1 "depthq" $ filterQuery queryIsDepth q@@ -168,7 +169,7 @@           [(a, map snd abs) | abs@((a,_):_) <- transpose acctBalChangesPerSpan] -- never null, or used when null...        items :: [MultiBalanceReportRow] =-          dbg1 "items"+          dbg1 "items" $           [(a, accountLeafName a, accountNameLevel a, displayedBals, rowtot, rowavg)            | (a,changes) <- acctBalChanges            , let displayedBals = case balancetype_ opts of@@ -180,11 +181,48 @@            , empty_ opts || depth == 0 || any (not . isZeroMixedAmount) displayedBals            ] +      sorteditems :: [MultiBalanceReportRow] =+        dbg1 "sorteditems" $+        maybesort items+        where+          maybesort+            | not $ sort_amount_ opts         = id+            | accountlistmode_ opts == ALTree = sortTreeMultiBalanceReportRowsByAmount+            | otherwise                       = sortFlatMultiBalanceReportRowsByAmount+            where+              -- Sort the report rows, representing a flat account list, by row total. +              sortFlatMultiBalanceReportRowsByAmount = sortBy (maybeflip $ comparing fifth6)+                where+                  maybeflip = if normalbalance_ opts == Just NormalNegative then id else flip++              -- Sort the report rows, representing a tree of accounts, by row total at each level.+              -- To do this we recreate an Account tree with the row totals as balances, +              -- so we can do a hierarchical sort, flatten again, and then reorder the  +              -- report rows similarly. Yes this is pretty long winded. +              sortTreeMultiBalanceReportRowsByAmount rows = sortedrows+                where+                  anamesandrows = [(first6 r, r) | r <- rows]+                  anames = map fst anamesandrows+                  atotals = [(a,tot) | (a,_,_,_,tot,_) <- rows]+                  nametree = treeFromPaths $ map expandAccountName anames+                  accounttree = nameTreeToAccount "root" nametree+                  accounttreewithbals = mapAccounts setibalance accounttree+                    where+                      -- this error should not happen, but it's ugly TODO +                      setibalance a = a{aibalance=fromMaybe (error "sortTreeMultiBalanceReportRowsByAmount 1") $ lookup (aname a) atotals}+                  sortedaccounttree = sortAccountTreeByAmount (fromMaybe NormalPositive $ normalbalance_ opts) accounttreewithbals+                  sortedaccounts = drop 1 $ flattenAccounts sortedaccounttree+                  sortedrows = [ +                    -- this error should not happen, but it's ugly TODO +                    fromMaybe (error "sortTreeMultiBalanceReportRowsByAmount 2") $ lookup (aname a) anamesandrows+                    | a <- sortedaccounts +                    ]+       totals :: [MixedAmount] =           -- dbg1 "totals" $           map sum balsbycol           where-            balsbycol = transpose [bs | (a,_,_,bs,_,_) <- items, not (tree_ opts) || a `elem` highestlevelaccts]+            balsbycol = transpose [bs | (a,_,_,bs,_,_) <- sorteditems, not (tree_ opts) || a `elem` highestlevelaccts]             highestlevelaccts     =                 dbg1 "highestlevelaccts"                 [a | a <- displayedAccts, not $ any (`elem` displayedAccts) $ init $ expandAccountName a]@@ -209,3 +247,53 @@           (map convert coltotals, convert rowtotaltotal, convert rowavgtotal))     convert = mixedAmountValue j d +tests_multiBalanceReport =+  let+    (opts,journal) `gives` r = do+      let (eitems, etotal) = r+          (MultiBalanceReport (_, aitems, atotal)) = multiBalanceReport opts (queryFromOpts nulldate opts) journal+          showw (acct,acct',indent,lAmt,amt,amt') = (acct, acct', indent, map showMixedAmountDebug lAmt, showMixedAmountDebug amt, showMixedAmountDebug amt')+      assertEqual "items" (map showw eitems) (map showw aitems)+      assertEqual "total" (showMixedAmountDebug etotal) ((\(_, b, _) -> showMixedAmountDebug b) atotal) -- we only check the sum of the totals+    usd0 = usd 0+    amount0 = Amount {acommodity="$", aquantity=0, aprice=NoPrice, astyle=AmountStyle {ascommodityside = L, ascommodityspaced = False, asprecision = 2, asdecimalpoint = Just '.', asdigitgroups = Nothing}, amultiplier=False}+  in [+   "multiBalanceReport with no args on null journal" ~: do+   (defreportopts, nulljournal) `gives` ([], Mixed [nullamt])++   ,"multiBalanceReport with -H on a populated period" ~: do+    (defreportopts{period_= PeriodBetween (fromGregorian 2008 1 1) (fromGregorian 2008 1 2), balancetype_=HistoricalBalance}, samplejournal) `gives`+     (+      [+       ("assets:bank:checking","checking",3, [mamountp' "$1.00"], mamountp' "$1.00",Mixed [amount0 {aquantity=1}])+      ,("income:salary","salary",2, [mamountp' "$-1.00"], mamountp' "$-1.00",Mixed [amount0 {aquantity=(-1)}])+      ],+      Mixed [usd0])++   ,"multiBalanceReport tests the ability to have a valid history on an empty period" ~: do+    (defreportopts{period_= PeriodBetween (fromGregorian 2008 1 2) (fromGregorian 2008 1 3), balancetype_=HistoricalBalance}, samplejournal) `gives`+     (+      [+       ("assets:bank:checking","checking",3, [mamountp' "$1.00"], mamountp' "$1.00",Mixed [amount0 {aquantity=1}])+      ,("income:salary","salary",2, [mamountp' "$-1.00"], mamountp' "$-1.00",Mixed [amount0 {aquantity=(-1)}])+      ],+      Mixed [usd0])++   ,"multiBalanceReport tests the ability to have a valid history on an empty period (More complex)" ~: do+    (defreportopts{period_= PeriodBetween (fromGregorian 2009 1 1) (fromGregorian 2009 1 2), balancetype_=HistoricalBalance}, samplejournal) `gives`+     (+      [+      ("assets:bank:checking","checking",3, [mamountp' "$1.00"], mamountp' "$1.00",Mixed [amount0 {aquantity=1}])+      ,("assets:bank:saving","saving",3, [mamountp' "$1.00"], mamountp' "$1.00",Mixed [amount0 {aquantity=1}])+      ,("assets:cash","cash",2, [mamountp' "$-2.00"], mamountp' "$-2.00",Mixed [amount0 {aquantity=(-2)}])+      ,("expenses:food","food",2, [mamountp' "$1.00"], mamountp' "$1.00",Mixed [amount0 {aquantity=(1)}])+      ,("expenses:supplies","supplies",2, [mamountp' "$1.00"], mamountp' "$1.00",Mixed [amount0 {aquantity=(1)}])+      ,("income:gifts","gifts",2, [mamountp' "$-1.00"], mamountp' "$-1.00",Mixed [amount0 {aquantity=(-1)}])+      ,("income:salary","salary",2, [mamountp' "$-1.00"], mamountp' "$-1.00",Mixed [amount0 {aquantity=(-1)}])+      ],+      Mixed [usd0])+  ]++tests_Hledger_Reports_MultiBalanceReport :: Test+tests_Hledger_Reports_MultiBalanceReport = TestList+  tests_multiBalanceReport
Hledger/Reports/PostingsReport.hs view
@@ -263,17 +263,17 @@    -- with the query specified explicitly    let (query, journal) `gives` n = (length $ snd $ postingsReport defreportopts query journal) `is` n    (Any, nulljournal) `gives` 0-   (Any, samplejournal) `gives` 11+   (Any, samplejournal) `gives` 13    -- register --depth just clips account names-   (Depth 2, samplejournal) `gives` 11+   (Depth 2, samplejournal) `gives` 13    (And [Depth 1, StatusQ Cleared, Acct "expenses"], samplejournal) `gives` 2    (And [And [Depth 1, StatusQ Cleared], Acct "expenses"], samplejournal) `gives` 2     -- with query and/or command-line options-   assertEqual "" 11 (length $ snd $ postingsReport defreportopts Any samplejournal)-   assertEqual ""  9 (length $ snd $ postingsReport defreportopts{interval_=Months 1} Any samplejournal)-   assertEqual "" 19 (length $ snd $ postingsReport defreportopts{interval_=Months 1, empty_=True} Any samplejournal)-   assertEqual ""  4 (length $ snd $ postingsReport defreportopts (Acct "assets:bank:checking") samplejournal)+   assertEqual "" 13 (length $ snd $ postingsReport defreportopts Any samplejournal)+   assertEqual "" 11 (length $ snd $ postingsReport defreportopts{interval_=Months 1} Any samplejournal)+   assertEqual "" 20 (length $ snd $ postingsReport defreportopts{interval_=Months 1, empty_=True} Any samplejournal)+   assertEqual ""  5 (length $ snd $ postingsReport defreportopts (Acct "assets:bank:checking") samplejournal)     -- (defreportopts, And [Acct "a a", Acct "'b"], samplejournal2) `gives` 0    -- [(Just (parsedate "2008-01-01","income"),assets:bank:checking             $1,$1)
Hledger/Reports/ReportOptions.hs view
@@ -70,9 +70,10 @@  instance Default AccountListMode where def = ALDefault --- | Standard options for customising report filtering and output,--- corresponding to hledger's command-line options and query language--- arguments. Used in hledger-lib and above.+-- | Standard options for customising report filtering and output.+-- Most of these correspond to standard hledger command-line options+-- or query arguments, but not all. Some are used only by certain+-- commands, as noted below.  data ReportOpts = ReportOpts {      period_         :: Period     ,interval_       :: Interval@@ -86,10 +87,10 @@     ,real_           :: Bool     ,format_         :: Maybe FormatStr     ,query_          :: String -- all arguments, as a string-    -- register only+    -- register command only     ,average_        :: Bool     ,related_        :: Bool-    -- balance only+    -- balance-type commands only     ,balancetype_    :: BalanceType     ,accountlistmode_ :: AccountListMode     ,drop_           :: Int@@ -97,11 +98,15 @@     ,no_total_       :: Bool     ,value_          :: Bool     ,pretty_tables_  :: Bool+    ,sort_amount_    :: Bool+    ,normalbalance_  :: Maybe NormalBalance+      -- ^ when running a balance report on accounts of the same normal balance type,+      -- eg in the income section of an income statement, this helps --sort-amount know+      -- how to sort negative numbers.     ,color_          :: Bool  } deriving (Show, Data, Typeable)  instance Default ReportOpts where def = defreportopts-instance Default Bool where def = False  defreportopts :: ReportOpts defreportopts = ReportOpts@@ -127,6 +132,8 @@     def     def     def+    def+    def  rawOptsToReportOpts :: RawOpts -> IO ReportOpts rawOptsToReportOpts rawopts = checkReportOpts <$> do@@ -154,6 +161,7 @@     ,row_total_   = boolopt "row-total" rawopts'     ,no_total_    = boolopt "no-total" rawopts'     ,value_       = boolopt "value" rawopts'+    ,sort_amount_ = boolopt "sort-amount" rawopts'     ,pretty_tables_ = boolopt "pretty-tables" rawopts'     ,color_       = color     }
Hledger/Utils.hs view
@@ -35,6 +35,7 @@ where import Control.Monad (liftM) -- import Data.Char+import Data.Default import Data.List -- import Data.Maybe -- import Data.PPrint@@ -116,6 +117,8 @@   return $ utcToZonedTime tz t  -- misc++instance Default Bool where def = False  isLeft :: Either a b -> Bool isLeft (Left _) = True
Hledger/Utils/Debug.hs view
@@ -10,9 +10,7 @@ module Hledger.Utils.Debug (   module Hledger.Utils.Debug   ,module Debug.Trace-#if __GLASGOW_HASKELL__ >= 704   ,ppShow-#endif ) where @@ -28,14 +26,7 @@ import           System.IO.Unsafe (unsafePerformIO) import           Text.Megaparsec import           Text.Printf--#if __GLASGOW_HASKELL__ >= 704 import           Text.Show.Pretty (ppShow)-#else--- the required pretty-show version requires GHC >= 7.4-ppShow :: Show a => a -> String-ppShow = show-#endif  pprint :: Show a => a -> IO () pprint = putStrLn . ppShow
Hledger/Utils/UTF8IOCompat.hs view
@@ -44,10 +44,6 @@ -- import qualified Data.ByteString.Lazy.UTF8 as U8 (toString, fromString) import Prelude hiding (readFile, writeFile, appendFile, getContents, putStr, putStrLn) import System.IO -- (Handle)--- #if __GLASGOW_HASKELL__ < 702--- import Codec.Binary.UTF8.String as UTF8 (decodeString, encodeString, isUTF8Encoded)--- import System.Info (os)--- #endif  -- bom :: B.ByteString -- bom = B.pack [0xEF, 0xBB, 0xBF]@@ -100,24 +96,12 @@ -- | Convert a system string to an ordinary string, decoding from UTF-8 if -- it appears to be UTF8-encoded and GHC version is less than 7.2. fromSystemString :: SystemString -> String--- #if __GLASGOW_HASKELL__ < 702--- fromSystemString s = if UTF8.isUTF8Encoded s then UTF8.decodeString s else s--- #else fromSystemString = id--- #endif  -- | Convert a unicode string to a system string, encoding with UTF-8 if -- we are on a posix platform with GHC < 7.2. toSystemString :: String -> SystemString--- #if __GLASGOW_HASKELL__ < 702--- toSystemString = case os of---                      "unix" -> UTF8.encodeString---                      "linux" -> UTF8.encodeString---                      "darwin" -> UTF8.encodeString---                      _ -> id--- #else toSystemString = id--- #endif  -- | A SystemString-aware version of error. error' :: String -> a
doc/hledger_csv.5 view
@@ -1,5 +1,5 @@ -.TH "hledger_csv" "5" "September 2017" "hledger 1.3.2" "hledger User Manuals"+.TH "hledger_csv" "5" "September 2017" "hledger 1.4" "hledger User Manuals"   @@ -23,7 +23,7 @@ To learn about \f[I]exporting\f[] CSV, see CSV output. .SH CSV RULES .PP-The following six kinds of rule can appear in the rules file, in any+The following seven kinds of rule can appear in the rules file, in any order. Blank lines and lines beginning with \f[C]#\f[] or \f[C];\f[] are ignored.@@ -195,32 +195,65 @@ include\ common.rules \f[] .fi+.SS newest\-first+.PP+\f[C]newest\-first\f[]+.PP+Consider adding this rule if all of the following are true: you might be+processing just one day of data, your CSV records are in reverse+chronological order (newest first), and you care about preserving the+order of same\-day transactions.+It usually isn\[aq]t needed, because hledger autodetects the CSV order,+but when all CSV records have the same date it will assume they are+oldest first. .SH CSV TIPS+.SS CSV ordering .PP-Each generated journal entry will have two postings, to-\f[C]account1\f[] and \f[C]account2\f[] respectively.-Currently it\[aq]s not possible to generate entries with more than two+The generated journal entries will be sorted by date.+The order of same\-day entries will be preserved (except in the special+case where you might need \f[C]newest\-first\f[], see above).+.SS CSV accounts+.PP+Each journal entry will have two postings, to \f[C]account1\f[] and+\f[C]account2\f[] respectively.+It\[aq]s not yet possible to generate entries with more than two postings.+It\[aq]s conventional and recommended to use \f[C]account1\f[] for the+account whose CSV we are reading.+.SS CSV amounts .PP+The \f[C]amount\f[] field sets the amount of the \f[C]account1\f[]+posting.+.PP If the CSV has debit/credit amounts in separate fields, assign to the-\f[C]amount\-in\f[] and \f[C]amount\-out\f[] pseudo fields instead of-\f[C]amount\f[].+\f[C]amount\-in\f[] and \f[C]amount\-out\f[] pseudo fields instead.+(Whichever one has a value will be used, with appropriate sign.+If both contain a value, it may not work so well.) .PP-If the CSV has the currency in a separate field, assign that to the-\f[C]currency\f[] pseudo field which will be automatically prepended to-the amount.-(Or you can do the same thing with a field assignment.)+If an amount value is parenthesised, it will be de\-parenthesised and+sign\-flipped. .PP-If the CSV includes a running balance, you can assign that to the-\f[C]balance\f[] pseudo field to generate a balance assertion on-\f[C]account1\f[] whenever the balance field is non\-empty.-(Eg to double\-check your bank\[aq]s balance calculation.)+If an amount value begins with a double minus sign, those will cancel+out and be removed. .PP-If an amount value is parenthesised, it will be de\-parenthesised and-sign\-flipped automatically.+If the CSV has the currency symbol in a separate field, assign that to+the \f[C]currency\f[] pseudo field to have it prepended to the amount.+Or, you can use a field assignment to \f[C]amount\f[] that interpolates+both CSV fields (giving more control, eg to put the currency symbol on+the right).+.SS CSV balance assertions .PP-The generated journal entries will be sorted by date.-The original order of same\-day entries will be preserved, usually.+If the CSV includes a running balance, you can assign that to the+\f[C]balance\f[] pseudo field; whenever the running balance value is+non\-empty, it will be asserted as the balance after the+\f[C]account1\f[] posting.+.SS Reading multiple CSV files+.PP+You can read multiple CSV files at once using multiple \f[C]\-f\f[]+arguments on the command line, and hledger will look for a+correspondingly\-named rules file for each.+Note if you use the \f[C]\-\-rules\-file\f[] option, this one rules file+will be used for all the CSV files being read.   .SH "REPORTING BUGS"
doc/hledger_csv.5.info view
@@ -3,8 +3,8 @@  File: hledger_csv.5.info,  Node: Top,  Next: CSV RULES,  Up: (dir) -hledger_csv(5) hledger 1.3.2-****************************+hledger_csv(5) hledger 1.4+**************************  hledger can read CSV files, converting each CSV record into a journal entry (transaction), if you provide some conversion hints in a "rules@@ -27,7 +27,7 @@ 1 CSV RULES *********** -The following six kinds of rule can appear in the rules file, in any+The following seven kinds of rule can appear in the rules file, in any order.  Blank lines and lines beginning with '#' or ';' are ignored. * Menu: @@ -37,6 +37,7 @@ * field assignment:: * conditional block:: * include::+* newest-first::   File: hledger_csv.5.info,  Node: skip,  Next: date-format,  Up: CSV RULES@@ -156,7 +157,7 @@  comment  XXX deductible ? check it  -File: hledger_csv.5.info,  Node: include,  Prev: conditional block,  Up: CSV RULES+File: hledger_csv.5.info,  Node: include,  Next: newest-first,  Prev: conditional block,  Up: CSV RULES  1.6 include ===========@@ -171,51 +172,131 @@ include common.rules  +File: hledger_csv.5.info,  Node: newest-first,  Prev: include,  Up: CSV RULES++1.7 newest-first+================++'newest-first'++   Consider adding this rule if all of the following are true: you might+be processing just one day of data, your CSV records are in reverse+chronological order (newest first), and you care about preserving the+order of same-day transactions.  It usually isn't needed, because+hledger autodetects the CSV order, but when all CSV records have the+same date it will assume they are oldest first.++ File: hledger_csv.5.info,  Node: CSV TIPS,  Prev: CSV RULES,  Up: Top  2 CSV TIPS ********** -Each generated journal entry will have two postings, to 'account1' and-'account2' respectively.  Currently it's not possible to generate-entries with more than two postings.+* Menu: -   If the CSV has debit/credit amounts in separate fields, assign to the-'amount-in' and 'amount-out' pseudo fields instead of 'amount'.+* CSV ordering::+* CSV accounts::+* CSV amounts::+* CSV balance assertions::+* Reading multiple CSV files:: -   If the CSV has the currency in a separate field, assign that to the-'currency' pseudo field which will be automatically prepended to the-amount.  (Or you can do the same thing with a field assignment.)++File: hledger_csv.5.info,  Node: CSV ordering,  Next: CSV accounts,  Up: CSV TIPS -   If the CSV includes a running balance, you can assign that to the-'balance' pseudo field to generate a balance assertion on 'account1'-whenever the balance field is non-empty.  (Eg to double-check your-bank's balance calculation.)+2.1 CSV ordering+================ +The generated journal entries will be sorted by date.  The order of+same-day entries will be preserved (except in the special case where you+might need 'newest-first', see above).+++File: hledger_csv.5.info,  Node: CSV accounts,  Next: CSV amounts,  Prev: CSV ordering,  Up: CSV TIPS++2.2 CSV accounts+================++Each journal entry will have two postings, to 'account1' and 'account2'+respectively.  It's not yet possible to generate entries with more than+two postings.  It's conventional and recommended to use 'account1' for+the account whose CSV we are reading.+++File: hledger_csv.5.info,  Node: CSV amounts,  Next: CSV balance assertions,  Prev: CSV accounts,  Up: CSV TIPS++2.3 CSV amounts+===============++The 'amount' field sets the amount of the 'account1' posting.++   If the CSV has debit/credit amounts in separate fields, assign to the+'amount-in' and 'amount-out' pseudo fields instead.  (Whichever one has+a value will be used, with appropriate sign.  If both contain a value,+it may not work so well.)+    If an amount value is parenthesised, it will be de-parenthesised and-sign-flipped automatically.+sign-flipped. -   The generated journal entries will be sorted by date.  The original-order of same-day entries will be preserved, usually.+   If an amount value begins with a double minus sign, those will cancel+out and be removed. +   If the CSV has the currency symbol in a separate field, assign that+to the 'currency' pseudo field to have it prepended to the amount.  Or,+you can use a field assignment to 'amount' that interpolates both CSV+fields (giving more control, eg to put the currency symbol on the+right).+ +File: hledger_csv.5.info,  Node: CSV balance assertions,  Next: Reading multiple CSV files,  Prev: CSV amounts,  Up: CSV TIPS++2.4 CSV balance assertions+==========================++If the CSV includes a running balance, you can assign that to the+'balance' pseudo field; whenever the running balance value is non-empty,+it will be asserted as the balance after the 'account1' posting.+++File: hledger_csv.5.info,  Node: Reading multiple CSV files,  Prev: CSV balance assertions,  Up: CSV TIPS++2.5 Reading multiple CSV files+==============================++You can read multiple CSV files at once using multiple '-f' arguments on+the command line, and hledger will look for a correspondingly-named+rules file for each.  Note if you use the '--rules-file' option, this+one rules file will be used for all the CSV files being read.++ Tag Table: Node: Top74-Node: CSV RULES814-Ref: #csv-rules924-Node: skip1167-Ref: #skip1263-Node: date-format1435-Ref: #date-format1564-Node: field list2070-Ref: #field-list2209-Node: field assignment2914-Ref: #field-assignment3071-Node: conditional block3575-Ref: #conditional-block3731-Node: include4627-Ref: #include4738-Node: CSV TIPS4969-Ref: #csv-tips5065+Node: CSV RULES810+Ref: #csv-rules920+Node: skip1182+Ref: #skip1278+Node: date-format1450+Ref: #date-format1579+Node: field list2085+Ref: #field-list2224+Node: field assignment2929+Ref: #field-assignment3086+Node: conditional block3590+Ref: #conditional-block3746+Node: include4642+Ref: #include4774+Node: newest-first5005+Ref: #newest-first5121+Node: CSV TIPS5532+Ref: #csv-tips5628+Node: CSV ordering5746+Ref: #csv-ordering5866+Node: CSV accounts6047+Ref: #csv-accounts6187+Node: CSV amounts6441+Ref: #csv-amounts6589+Node: CSV balance assertions7364+Ref: #csv-balance-assertions7548+Node: Reading multiple CSV files7753+Ref: #reading-multiple-csv-files7925  End Tag Table
doc/hledger_csv.5.txt view
@@ -19,7 +19,7 @@        To learn about exporting CSV, see CSV output.  CSV RULES-       The following six kinds of rule can appear in the rules  file,  in  any+       The following seven kinds of rule can appear in the rules file, in  any        order.  Blank lines and lines beginning with # or ; are ignored.     skip@@ -123,33 +123,62 @@               # rules reused with several CSV files               include common.rules +   newest-first+       newest-first++       Consider adding this rule if all of the following are true:  you  might+       be  processing  just  one  day of data, your CSV records are in reverse+       chronological order (newest first), and you care about  preserving  the+       order  of  same-day  transactions.   It  usually  isn't needed, because+       hledger autodetects the CSV order, but when all CSV  records  have  the+       same date it will assume they are oldest first.+ CSV TIPS-       Each generated journal entry will have two postings,  to  account1  and-       account2 respectively.  Currently it's not possible to generate entries-       with more than two postings.+   CSV ordering+       The  generated  journal  entries  will be sorted by date.  The order of+       same-day entries will be preserved (except in the  special  case  where+       you might need newest-first, see above). +   CSV accounts+       Each  journal  entry  will  have two postings, to account1 and account2+       respectively.  It's not yet possible to generate entries with more than+       two  postings.   It's  conventional and recommended to use account1 for+       the account whose CSV we are reading.++   CSV amounts+       The amount field sets the amount of the account1 posting.+        If the CSV has debit/credit amounts in separate fields, assign  to  the-       amount-in and amount-out pseudo fields instead of amount.+       amount-in  and  amount-out pseudo fields instead.  (Whichever one has a+       value will be used, with appropriate sign.  If both contain a value, it+       may not work so well.) -       If  the  CSV  has  the currency in a separate field, assign that to the-       currency pseudo field which will  be  automatically  prepended  to  the-       amount.  (Or you can do the same thing with a field assignment.)+       If  an  amount  value is parenthesised, it will be de-parenthesised and+       sign-flipped. -       If  the CSV includes a running balance, you can assign that to the bal--       ance pseudo field to generate a balance assertion on account1  whenever-       the  balance  field is non-empty.  (Eg to double-check your bank's bal--       ance calculation.)+       If an amount value begins with a double minus sign, those  will  cancel+       out and be removed. -       If an amount value is parenthesised, it will  be  de-parenthesised  and-       sign-flipped automatically.+       If  the CSV has the currency symbol in a separate field, assign that to+       the currency pseudo field to have it prepended to the amount.  Or,  you+       can  use a field assignment to amount that interpolates both CSV fields+       (giving more control, eg to put the currency symbol on the right). -       The  generated  journal  entries  will be sorted by date.  The original-       order of same-day entries will be preserved, usually.+   CSV balance assertions+       If the CSV includes a running balance, you can assign that to the  bal-+       ance  pseudo field; whenever the running balance value is non-empty, it+       will be asserted as the balance after the account1 posting. +   Reading multiple CSV files+       You can read multiple CSV files at once using multiple -f arguments  on+       the  command  line,  and  hledger will look for a correspondingly-named+       rules file for each.  Note if you use the --rules-file option, this one+       rules file will be used for all the CSV files being read.  + REPORTING BUGS-       Report bugs at http://bugs.hledger.org (or on the #hledger IRC  channel+       Report  bugs at http://bugs.hledger.org (or on the #hledger IRC channel        or hledger mail list)  @@ -163,7 +192,7 @@   SEE ALSO-       hledger(1),      hledger-ui(1),     hledger-web(1),     hledger-api(1),+       hledger(1),     hledger-ui(1),     hledger-web(1),      hledger-api(1),        hledger_csv(5), hledger_journal(5), hledger_timeclock(5), hledger_time-        dot(5), ledger(1) @@ -171,4 +200,4 @@   -hledger 1.3.2                   September 2017                  hledger_csv(5)+hledger 1.4                     September 2017                  hledger_csv(5)
doc/hledger_journal.5 view
@@ -1,6 +1,6 @@ .\"t -.TH "hledger_journal" "5" "September 2017" "hledger 1.3.2" "hledger User Manuals"+.TH "hledger_journal" "5" "September 2017" "hledger 1.4" "hledger User Manuals"   @@ -53,6 +53,10 @@ \ \ \ \ expenses:supplies\ \ \ \ \ $1\ \ \ \ ;\ <\-\ this\ transaction\ debits\ two\ expense\ accounts \ \ \ \ assets:cash\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ;\ <\-\ $\-2\ inferred +2008/10/01\ take\ a\ loan+\ \ \ \ assets:bank:checking\ \ $1+\ \ \ \ liabilities:debts\ \ \ \ $\-1+ 2008/12/31\ *\ pay\ off\ \ \ \ \ \ \ \ \ \ \ \ ;\ <\-\ an\ optional\ *\ or\ !\ after\ the\ date\ means\ "cleared"\ (or\ anything\ you\ want) \ \ \ \ liabilities:debts\ \ \ \ \ $1 \ \ \ \ assets:bank:checking@@ -73,7 +77,10 @@ parentheses) .IP \[bu] 2 (optional) a transaction description (any remaining text until end of-line)+line or a semicolon)+.IP \[bu] 2+(optional) a transaction comment (any remaining text following a+semicolon until end of line) .PP Then comes zero or more (but usually at least 2) indented lines representing...@@ -296,6 +303,20 @@ at your bank, \f[C]\-U\f[] to see things which will probably hit your bank soon (like uncashed checks), and no flags to see the most up\-to\-date state of your finances.+.SS Description+.PP+A transaction\[aq]s description is the rest of the line following the+date and status mark (or until a comment begins).+Sometimes called the "narration" in traditional bookkeeping, it can be+used for whatever you wish, or left blank.+Transaction descriptions can be queried, unlike comments.+.SS Payee and note+.PP+You can optionally include a \f[C]|\f[] (pipe) character in a+description to subdivide it into a payee/payer name on the left and+additional notes on the right.+This may be worthwhile if you need to do more precise querying and+pivoting by payee. .SS Account names .PP Account names typically have several parts separated by a full colon,@@ -793,24 +814,6 @@ .PP Tags are like Ledger\[aq]s metadata feature, except hledger\[aq]s tag values are simple strings.-.SS Implicit tags-.PP-Some predefined "implicit" tags are also provided:-.IP \[bu] 2-\f[C]code\f[] \- the transaction\[aq]s code field-.IP \[bu] 2-\f[C]description\f[] \- the transaction\[aq]s description-.IP \[bu] 2-\f[C]payee\f[] \- the part of description before \f[C]|\f[], or all of-it-.IP \[bu] 2-\f[C]note\f[] \- the part of description after \f[C]|\f[], or all of it-.PP-\f[C]payee\f[] and \f[C]note\f[] support descriptions written in a-special \f[C]PAYEE\ |\ NOTE\f[] format, accessing the parts before and-after the pipe character respectively.-For descriptions not containing a pipe character they are the same as-\f[C]description\f[]. .SS Directives .SS Account aliases .PP@@ -878,9 +881,7 @@ replaced by REPLACEMENT. If REGEX contains parenthesised match groups, these can be referenced by the usual numeric backreferences in REPLACEMENT.-Note, currently regular expression aliases may cause noticeable-slow\-downs.-(And if you use Ledger on your hledger file, they will be ignored.) Eg:+Eg: .IP .nf \f[C]@@ -888,6 +889,9 @@ #\ rewrites\ "assets:bank:wells\ fargo:checking"\ to\ \ "assets:wells\ fargo\ checking" \f[] .fi+.PP+Also note that REPLACEMENT continues to the end of line (or on command+line, to end of option argument), so it can contain trailing whitespace. .SS Multiple aliases .PP You can define as many aliases as you like using directives or@@ -1119,6 +1123,11 @@ Text Wrangler \  T}@T{ https://github.com/ledger/ledger/wiki/Editing\-Ledger\-files\-with\-TextWrangler+T}+T{+Visual Studio Code+T}@T{+https://marketplace.visualstudio.com/items?itemName=mark\-hansen.hledger\-vscode T} .TE 
doc/hledger_journal.5.info view
@@ -4,8 +4,8 @@  File: hledger_journal.5.info,  Node: Top,  Next: FILE FORMAT,  Up: (dir) -hledger_journal(5) hledger 1.3.2-********************************+hledger_journal(5) hledger 1.4+******************************  hledger's usual data source is a plain text file containing journal entries in hledger journal format.  This file represents a standard@@ -46,6 +46,10 @@     expenses:supplies     $1    ; <- this transaction debits two expense accounts     assets:cash                 ; <- $-2 inferred +2008/10/01 take a loan+    assets:bank:checking  $1+    liabilities:debts    $-1+ 2008/12/31 * pay off            ; <- an optional * or ! after the date means "cleared" (or anything you want)     liabilities:debts     $1     assets:bank:checking@@ -67,6 +71,7 @@ * Postings:: * Dates:: * Status::+* Description:: * Account names:: * Amounts:: * Virtual Postings::@@ -92,7 +97,9 @@    * (optional) a transaction code (any short number or text, enclosed      in parentheses)    * (optional) a transaction description (any remaining text until end-     of line)+     of line or a semicolon)+   * (optional) a transaction comment (any remaining text following a+     semicolon until end of line)     Then comes zero or more (but usually at least 2) indented lines representing...@@ -227,7 +234,7 @@ transaction and DATE2 infers its year from DATE.  -File: hledger_journal.5.info,  Node: Status,  Next: Account names,  Prev: Dates,  Up: FILE FORMAT+File: hledger_journal.5.info,  Node: Status,  Next: Description,  Prev: Dates,  Up: FILE FORMAT  1.4 Status ==========@@ -277,9 +284,35 @@ your finances.  -File: hledger_journal.5.info,  Node: Account names,  Next: Amounts,  Prev: Status,  Up: FILE FORMAT+File: hledger_journal.5.info,  Node: Description,  Next: Account names,  Prev: Status,  Up: FILE FORMAT -1.5 Account names+1.5 Description+===============++A transaction's description is the rest of the line following the date+and status mark (or until a comment begins).  Sometimes called the+"narration" in traditional bookkeeping, it can be used for whatever you+wish, or left blank.  Transaction descriptions can be queried, unlike+comments.+* Menu:++* Payee and note::+++File: hledger_journal.5.info,  Node: Payee and note,  Up: Description++1.5.1 Payee and note+--------------------++You can optionally include a '|' (pipe) character in a description to+subdivide it into a payee/payer name on the left and additional notes on+the right.  This may be worthwhile if you need to do more precise+querying and pivoting by payee.+++File: hledger_journal.5.info,  Node: Account names,  Next: Amounts,  Prev: Description,  Up: FILE FORMAT++1.6 Account names =================  Account names typically have several parts separated by a full colon,@@ -297,7 +330,7 @@  File: hledger_journal.5.info,  Node: Amounts,  Next: Virtual Postings,  Prev: Account names,  Up: FILE FORMAT -1.6 Amounts+1.7 Amounts ===========  After the account name, there is usually an amount.  Important: between@@ -352,7 +385,7 @@  File: hledger_journal.5.info,  Node: Virtual Postings,  Next: Balance Assertions,  Prev: Amounts,  Up: FILE FORMAT -1.7 Virtual Postings+1.8 Virtual Postings ====================  When you parenthesise the account name in a posting, we call that a@@ -387,7 +420,7 @@  File: hledger_journal.5.info,  Node: Balance Assertions,  Next: Balance Assignments,  Prev: Virtual Postings,  Up: FILE FORMAT -1.8 Balance Assertions+1.9 Balance Assertions ======================  hledger supports Ledger-style balance assertions in journal files.@@ -421,7 +454,7 @@  File: hledger_journal.5.info,  Node: Assertions and ordering,  Next: Assertions and included files,  Up: Balance Assertions -1.8.1 Assertions and ordering+1.9.1 Assertions and ordering -----------------------------  hledger sorts an account's postings and assertions first by date and@@ -440,7 +473,7 @@  File: hledger_journal.5.info,  Node: Assertions and included files,  Next: Assertions and multiple -f options,  Prev: Assertions and ordering,  Up: Balance Assertions -1.8.2 Assertions and included files+1.9.2 Assertions and included files -----------------------------------  With included files, things are a little more complicated.  Including@@ -452,7 +485,7 @@  File: hledger_journal.5.info,  Node: Assertions and multiple -f options,  Next: Assertions and commodities,  Prev: Assertions and included files,  Up: Balance Assertions -1.8.3 Assertions and multiple -f options+1.9.3 Assertions and multiple -f options ----------------------------------------  Balance assertions don't work well across files specified with multiple@@ -461,7 +494,7 @@  File: hledger_journal.5.info,  Node: Assertions and commodities,  Next: Assertions and subaccounts,  Prev: Assertions and multiple -f options,  Up: Balance Assertions -1.8.4 Assertions and commodities+1.9.4 Assertions and commodities --------------------------------  The asserted balance must be a simple single-commodity amount, and in@@ -480,7 +513,7 @@  File: hledger_journal.5.info,  Node: Assertions and subaccounts,  Next: Assertions and virtual postings,  Prev: Assertions and commodities,  Up: Balance Assertions -1.8.5 Assertions and subaccounts+1.9.5 Assertions and subaccounts --------------------------------  Balance assertions do not count the balance from subaccounts; they check@@ -503,7 +536,7 @@  File: hledger_journal.5.info,  Node: Assertions and virtual postings,  Prev: Assertions and subaccounts,  Up: Balance Assertions -1.8.6 Assertions and virtual postings+1.9.6 Assertions and virtual postings -------------------------------------  Balance assertions are checked against all postings, both real and@@ -513,8 +546,8 @@  File: hledger_journal.5.info,  Node: Balance Assignments,  Next: Prices,  Prev: Balance Assertions,  Up: FILE FORMAT -1.9 Balance Assignments-=======================+1.10 Balance Assignments+========================  Ledger-style balance assignments are also supported.  These are like balance assertions, but with no posting amount on the left side of the@@ -546,7 +579,7 @@  File: hledger_journal.5.info,  Node: Prices,  Next: Comments,  Prev: Balance Assignments,  Up: FILE FORMAT -1.10 Prices+1.11 Prices ===========  * Menu:@@ -557,7 +590,7 @@  File: hledger_journal.5.info,  Node: Transaction prices,  Next: Market prices,  Up: Prices -1.10.1 Transaction prices+1.11.1 Transaction prices -------------------------  Within a transaction, you can note an amount's price in another@@ -618,7 +651,7 @@  File: hledger_journal.5.info,  Node: Market prices,  Prev: Transaction prices,  Up: Prices -1.10.2 Market prices+1.11.2 Market prices --------------------  Market prices are not tied to a particular transaction; they represent@@ -647,7 +680,7 @@  File: hledger_journal.5.info,  Node: Comments,  Next: Tags,  Prev: Prices,  Up: FILE FORMAT -1.11 Comments+1.12 Comments =============  Lines in the journal beginning with a semicolon (';') or hash ('#') or@@ -687,7 +720,7 @@  File: hledger_journal.5.info,  Node: Tags,  Next: Directives,  Prev: Comments,  Up: FILE FORMAT -1.12 Tags+1.13 Tags =========  Tags are a way to add extra labels or labelled data to postings and@@ -726,32 +759,11 @@     Tags are like Ledger's metadata feature, except hledger's tag values are simple strings.-* Menu: -* Implicit tags::- -File: hledger_journal.5.info,  Node: Implicit tags,  Up: Tags--1.12.1 Implicit tags-----------------------Some predefined "implicit" tags are also provided:--   * 'code' - the transaction's code field-   * 'description' - the transaction's description-   * 'payee' - the part of description before '|', or all of it-   * 'note' - the part of description after '|', or all of it--   'payee' and 'note' support descriptions written in a special 'PAYEE |-NOTE' format, accessing the parts before and after the pipe character-respectively.  For descriptions not containing a pipe character they are-the same as 'description'.-- File: hledger_journal.5.info,  Node: Directives,  Prev: Tags,  Up: FILE FORMAT -1.13 Directives+1.14 Directives ===============  * Menu:@@ -768,7 +780,7 @@  File: hledger_journal.5.info,  Node: Account aliases,  Next: account directive,  Up: Directives -1.13.1 Account aliases+1.14.1 Account aliases ----------------------  You can define aliases which rewrite your account names (after reading@@ -793,7 +805,7 @@  File: hledger_journal.5.info,  Node: Basic aliases,  Next: Regex aliases,  Up: Account aliases -1.13.1.1 Basic aliases+1.14.1.1 Basic aliases ......................  To set an account alias, use the 'alias' directive in your journal file.@@ -816,7 +828,7 @@  File: hledger_journal.5.info,  Node: Regex aliases,  Next: Multiple aliases,  Prev: Basic aliases,  Up: Account aliases -1.13.1.2 Regex aliases+1.14.1.2 Regex aliases ......................  There is also a more powerful variant that uses a regular expression,@@ -829,17 +841,19 @@    REGEX is a case-insensitive regular expression.  Anywhere it matches inside an account name, the matched part will be replaced by REPLACEMENT. If REGEX contains parenthesised match groups, these can be-referenced by the usual numeric backreferences in REPLACEMENT. Note,-currently regular expression aliases may cause noticeable slow-downs.-(And if you use Ledger on your hledger file, they will be ignored.)  Eg:+referenced by the usual numeric backreferences in REPLACEMENT. Eg:  alias /^(.+):bank:([^:]+)(.*)/ = \1:\2 \3 # rewrites "assets:bank:wells fargo:checking" to  "assets:wells fargo checking" +   Also note that REPLACEMENT continues to the end of line (or on+command line, to end of option argument), so it can contain trailing+whitespace.+  File: hledger_journal.5.info,  Node: Multiple aliases,  Next: end aliases,  Prev: Regex aliases,  Up: Account aliases -1.13.1.3 Multiple aliases+1.14.1.3 Multiple aliases .........................  You can define as many aliases as you like using directives or@@ -855,7 +869,7 @@  File: hledger_journal.5.info,  Node: end aliases,  Prev: Multiple aliases,  Up: Account aliases -1.13.1.4 end aliases+1.14.1.4 end aliases ....................  You can clear (forget) all currently defined aliases with the 'end@@ -866,7 +880,7 @@  File: hledger_journal.5.info,  Node: account directive,  Next: apply account directive,  Prev: Account aliases,  Up: Directives -1.13.2 account directive+1.14.2 account directive ------------------------  The 'account' directive predefines account names, as in Ledger and@@ -887,7 +901,7 @@  File: hledger_journal.5.info,  Node: apply account directive,  Next: Multi-line comments,  Prev: account directive,  Up: Directives -1.13.3 apply account directive+1.14.3 apply account directive ------------------------------  You can specify a parent account which will be prepended to all accounts@@ -923,7 +937,7 @@  File: hledger_journal.5.info,  Node: Multi-line comments,  Next: commodity directive,  Prev: apply account directive,  Up: Directives -1.13.4 Multi-line comments+1.14.4 Multi-line comments --------------------------  A line containing just 'comment' starts a multi-line comment, and a line@@ -932,7 +946,7 @@  File: hledger_journal.5.info,  Node: commodity directive,  Next: Default commodity,  Prev: Multi-line comments,  Up: Directives -1.13.5 commodity directive+1.14.5 commodity directive --------------------------  The 'commodity' directive predefines commodities (currently this is just@@ -964,7 +978,7 @@  File: hledger_journal.5.info,  Node: Default commodity,  Next: Default year,  Prev: commodity directive,  Up: Directives -1.13.6 Default commodity+1.14.6 Default commodity ------------------------  The D directive sets a default commodity (and display format), to be@@ -984,7 +998,7 @@  File: hledger_journal.5.info,  Node: Default year,  Next: Including other files,  Prev: Default commodity,  Up: Directives -1.13.7 Default year+1.14.7 Default year -------------------  You can set a default year to be used for subsequent dates which don't@@ -1010,7 +1024,7 @@  File: hledger_journal.5.info,  Node: Including other files,  Prev: Default year,  Up: Directives -1.13.8 Including other files+1.14.8 Including other files ----------------------------  You can pull in the content of additional journal files by writing an@@ -1043,87 +1057,91 @@ Sublime Text      https://github.com/ledger/ledger/wiki/Using-Sublime-Text Textmate          https://github.com/ledger/ledger/wiki/Using-TextMate-2 Text Wrangler     https://github.com/ledger/ledger/wiki/Editing-Ledger-files-with-TextWrangler+Visual Studio     https://marketplace.visualstudio.com/items?itemName=mark-hansen.hledger-vscode+Code   Tag Table: Node: Top78-Node: FILE FORMAT2296-Ref: #file-format2422-Node: Transactions2629-Ref: #transactions2752-Node: Postings3317-Ref: #postings3446-Node: Dates4441-Ref: #dates4558-Node: Simple dates4623-Ref: #simple-dates4751-Node: Secondary dates5117-Ref: #secondary-dates5273-Node: Posting dates6836-Ref: #posting-dates6967-Node: Status8341-Ref: #status8465-Node: Account names10179-Ref: #account-names10319-Node: Amounts10806-Ref: #amounts10944-Node: Virtual Postings13045-Ref: #virtual-postings13206-Node: Balance Assertions14426-Ref: #balance-assertions14603-Node: Assertions and ordering15499-Ref: #assertions-and-ordering15687-Node: Assertions and included files16387-Ref: #assertions-and-included-files16630-Node: Assertions and multiple -f options16963-Ref: #assertions-and-multiple--f-options17219-Node: Assertions and commodities17351-Ref: #assertions-and-commodities17588-Node: Assertions and subaccounts18284-Ref: #assertions-and-subaccounts18518-Node: Assertions and virtual postings19039-Ref: #assertions-and-virtual-postings19248-Node: Balance Assignments19390-Ref: #balance-assignments19559-Node: Prices20678-Ref: #prices20813-Node: Transaction prices20864-Ref: #transaction-prices21011-Node: Market prices23167-Ref: #market-prices23304-Node: Comments24264-Ref: #comments24388-Node: Tags25501-Ref: #tags25621-Node: Implicit tags27050-Ref: #implicit-tags27158-Node: Directives27675-Ref: #directives27790-Node: Account aliases27983-Ref: #account-aliases28129-Node: Basic aliases28733-Ref: #basic-aliases28878-Node: Regex aliases29568-Ref: #regex-aliases29738-Node: Multiple aliases30453-Ref: #multiple-aliases30627-Node: end aliases31125-Ref: #end-aliases31267-Node: account directive31368-Ref: #account-directive31550-Node: apply account directive31846-Ref: #apply-account-directive32044-Node: Multi-line comments32703-Ref: #multi-line-comments32895-Node: commodity directive33023-Ref: #commodity-directive33209-Node: Default commodity34081-Ref: #default-commodity34256-Node: Default year34793-Ref: #default-year34960-Node: Including other files35383-Ref: #including-other-files35542-Node: EDITOR SUPPORT35939-Ref: #editor-support36059+Node: FILE FORMAT2374+Ref: #file-format2500+Node: Transactions2723+Ref: #transactions2846+Node: Postings3530+Ref: #postings3659+Node: Dates4654+Ref: #dates4771+Node: Simple dates4836+Ref: #simple-dates4964+Node: Secondary dates5330+Ref: #secondary-dates5486+Node: Posting dates7049+Ref: #posting-dates7180+Node: Status8554+Ref: #status8676+Node: Description10390+Ref: #description10530+Node: Payee and note10849+Ref: #payee-and-note10965+Node: Account names11207+Ref: #account-names11352+Node: Amounts11839+Ref: #amounts11977+Node: Virtual Postings14078+Ref: #virtual-postings14239+Node: Balance Assertions15459+Ref: #balance-assertions15636+Node: Assertions and ordering16532+Ref: #assertions-and-ordering16720+Node: Assertions and included files17420+Ref: #assertions-and-included-files17663+Node: Assertions and multiple -f options17996+Ref: #assertions-and-multiple--f-options18252+Node: Assertions and commodities18384+Ref: #assertions-and-commodities18621+Node: Assertions and subaccounts19317+Ref: #assertions-and-subaccounts19551+Node: Assertions and virtual postings20072+Ref: #assertions-and-virtual-postings20281+Node: Balance Assignments20423+Ref: #balance-assignments20594+Node: Prices21713+Ref: #prices21848+Node: Transaction prices21899+Ref: #transaction-prices22046+Node: Market prices24202+Ref: #market-prices24339+Node: Comments25299+Ref: #comments25423+Node: Tags26536+Ref: #tags26656+Node: Directives28058+Ref: #directives28173+Node: Account aliases28366+Ref: #account-aliases28512+Node: Basic aliases29116+Ref: #basic-aliases29261+Node: Regex aliases29951+Ref: #regex-aliases30121+Node: Multiple aliases30839+Ref: #multiple-aliases31013+Node: end aliases31511+Ref: #end-aliases31653+Node: account directive31754+Ref: #account-directive31936+Node: apply account directive32232+Ref: #apply-account-directive32430+Node: Multi-line comments33089+Ref: #multi-line-comments33281+Node: commodity directive33409+Ref: #commodity-directive33595+Node: Default commodity34467+Ref: #default-commodity34642+Node: Default year35179+Ref: #default-year35346+Node: Including other files35769+Ref: #including-other-files35928+Node: EDITOR SUPPORT36325+Ref: #editor-support36445  End Tag Table
doc/hledger_journal.5.txt view
@@ -47,6 +47,10 @@                   expenses:supplies     $1    ; <- this transaction debits two expense accounts                   assets:cash                 ; <- $-2 inferred +              2008/10/01 take a loan+                  assets:bank:checking  $1+                  liabilities:debts    $-1+               2008/12/31 * pay off            ; <- an optional * or ! after the date means "cleared" (or anything you want)                   liabilities:debts     $1                   assets:bank:checking@@ -64,59 +68,62 @@          parentheses)         o (optional) a transaction description (any remaining text until end of-         line)+         line or a semicolon) -       Then comes zero or more (but usually at least 2) indented lines  repre-+       o (optional) a transaction comment  (any  remaining  text  following  a+         semicolon until end of line)++       Then  comes zero or more (but usually at least 2) indented lines repre-        senting...     Postings-       A  posting  is an addition of some amount to, or removal of some amount-       from, an account.  Each posting line begins with at least one space  or+       A posting is an addition of some amount to, or removal of  some  amount+       from,  an account.  Each posting line begins with at least one space or        tab (2 or 4 spaces is common), followed by:         o (optional) a status character (empty, !, or *), followed by a space -       o (required)  an  account  name (any text, optionally containing single+       o (required) an account name (any text,  optionally  containing  single          spaces, until end of line or a double space)         o (optional) two or more spaces or tabs followed by an amount. -       Positive amounts are being added to the account, negative  amounts  are+       Positive  amounts  are being added to the account, negative amounts are        being removed.         The amounts within a transaction must always sum up to zero.  As a con--       venience, one amount may be left blank; it will be inferred  so  as  to+       venience,  one  amount  may be left blank; it will be inferred so as to        balance the transaction. -       Be  sure  to  note the unusual two-space delimiter between account name-       and amount.  This makes it easy to write account names containing  spa--       ces.   But if you accidentally leave only one space (or tab) before the+       Be sure to note the unusual two-space delimiter  between  account  name+       and  amount.  This makes it easy to write account names containing spa-+       ces.  But if you accidentally leave only one space (or tab) before  the        amount, the amount will be considered part of the account name.     Dates    Simple dates-       Within a journal file, transaction dates use Y/M/D (or Y-M-D or  Y.M.D)-       Leading  zeros are optional.  The year may be omitted, in which case it-       will be inferred from  the  context  -  the  current  transaction,  the-       default  year  set  with  a default year directive, or the current date-       when the command is run.  Some examples: 2010/01/31, 1/31,  2010-01-31,+       Within  a journal file, transaction dates use Y/M/D (or Y-M-D or Y.M.D)+       Leading zeros are optional.  The year may be omitted, in which case  it+       will  be  inferred  from  the  context  -  the current transaction, the+       default year set with a default year directive,  or  the  current  date+       when  the command is run.  Some examples: 2010/01/31, 1/31, 2010-01-31,        2010.1.31.     Secondary dates-       Real-life  transactions  sometimes  involve more than one date - eg the+       Real-life transactions sometimes involve more than one date  -  eg  the        date you write a cheque, and the date it clears in your bank.  When you-       want  to  model  this,  eg  for more accurate balances, you can specify-       individual posting dates, which I recommend.  Or, you can use the  sec--       ondary  dates  (aka  auxiliary/effective  dates) feature, supported for+       want to model this, eg for more  accurate  balances,  you  can  specify+       individual  posting dates, which I recommend.  Or, you can use the sec-+       ondary dates (aka auxiliary/effective  dates)  feature,  supported  for        compatibility with Ledger.         A secondary date can be written after the primary date, separated by an-       equals  sign.   The  primary date, on the left, is used by default; the-       secondary date, on the right, is used when the --date2 flag  is  speci-+       equals sign.  The primary date, on the left, is used  by  default;  the+       secondary  date,  on the right, is used when the --date2 flag is speci-        fied (--aux-date or --effective also work). -       The  meaning of secondary dates is up to you, but it's best to follow a-       consistent rule.  Eg write the bank's clearing  date  as  primary,  and+       The meaning of secondary dates is up to you, but it's best to follow  a+       consistent  rule.   Eg  write  the bank's clearing date as primary, and        when needed, the date the transaction was initiated as secondary.         Here's an example.  Note that a secondary date will use the year of the@@ -132,18 +139,18 @@               $ hledger register checking --date2               2010/02/19 movie ticket         assets:checking                $-10         $-10 -       Secondary dates require some effort; you must use them consistently  in+       Secondary  dates require some effort; you must use them consistently in        your journal entries and remember whether to use or not use the --date2        flag for your reports.  They are included in hledger for Ledger compat--       ibility,  but  posting  dates  are  a  more powerful and less confusing+       ibility, but posting dates are  a  more  powerful  and  less  confusing        alternative.     Posting dates-       You can give individual postings a different  date  from  their  parent-       transaction,  by  adding a posting comment containing a tag (see below)+       You  can  give  individual  postings a different date from their parent+       transaction, by adding a posting comment containing a tag  (see  below)        like date:DATE.  This is probably the best way to control posting dates-       precisely.   Eg  in  this  example  the  expense  should  appear in May-       reports, and the deduction from checking should be reported on 6/1  for+       precisely.  Eg in  this  example  the  expense  should  appear  in  May+       reports,  and the deduction from checking should be reported on 6/1 for        easy bank reconciliation:                2015/5/30@@ -156,22 +163,22 @@               $ hledger -f t.j register checking               2015/06/01                      assets:checking               $-10          $-10 -       DATE  should be a simple date; if the year is not specified it will use-       the year of the transaction's date.  You can  set  the  secondary  date-       similarly,  with  date2:DATE2.   The  date:  or date2: tags must have a-       valid simple date value if they are present, eg a  date:  tag  with  no+       DATE should be a simple date; if the year is not specified it will  use+       the  year  of  the  transaction's date.  You can set the secondary date+       similarly, with date2:DATE2.  The date: or  date2:  tags  must  have  a+       valid  simple  date  value  if they are present, eg a date: tag with no        value is not allowed.         Ledger's earlier, more compact bracketed date syntax is also supported:-       [DATE], [DATE=DATE2] or [=DATE2].  hledger will attempt  to  parse  any+       [DATE],  [DATE=DATE2]  or  [=DATE2].  hledger will attempt to parse any        square-bracketed sequence of the 0123456789/-.= characters in this way.-       With this syntax, DATE infers its year from the transaction  and  DATE2+       With  this  syntax, DATE infers its year from the transaction and DATE2        infers its year from DATE.     Status-       Transactions,  or  individual postings within a transaction, can have a-       status mark,  which  is  a  single  character  before  the  transaction-       description  or  posting  account  name,  separated from it by a space,+       Transactions, or individual postings within a transaction, can  have  a+       status  mark,  which  is  a  single  character  before  the transaction+       description or posting account name, separated  from  it  by  a  space,        indicating one of three statuses:  @@ -181,39 +188,52 @@        !        pending        *        cleared -       When reporting, you  can  filter  by  status  with  the  -U/--unmarked,-       -P/--pending,  and  -C/--cleared  flags;  or the status:, status:!, and+       When  reporting,  you  can  filter  by  status  with the -U/--unmarked,+       -P/--pending, and -C/--cleared flags; or  the  status:,  status:!,  and        status:* queries; or the U, P, C keys in hledger-ui. -       Note, in Ledger and in older versions of hledger, the "unmarked"  state-       is  called  "uncleared".   As  of  hledger  1.3  we  have renamed it to+       Note,  in Ledger and in older versions of hledger, the "unmarked" state+       is called "uncleared".  As  of  hledger  1.3  we  have  renamed  it  to        unmarked for clarity. -       To replicate Ledger and old hledger's behaviour of also matching  pend-+       To  replicate Ledger and old hledger's behaviour of also matching pend-        ing, combine -U and -P. -       Status  marks  are optional, but can be helpful eg for reconciling with+       Status marks are optional, but can be helpful eg for  reconciling  with        real-world accounts.  Some editor modes provide highlighting and short--       cuts  for working with status.  Eg in Emacs ledger-mode, you can toggle+       cuts for working with status.  Eg in Emacs ledger-mode, you can  toggle        transaction status with C-c C-e, or posting status with C-c C-c. -       What "uncleared", "pending", and "cleared" actually mean is up to  you.+       What  "uncleared", "pending", and "cleared" actually mean is up to you.        Here's one suggestion:          status       meaning        --------------------------------------------------------------------------        uncleared    recorded but not yet reconciled; needs review-       pending      tentatively  reconciled  (if needed, eg during a big recon-+       pending      tentatively reconciled (if needed, eg during a  big  recon-                     ciliation)-       cleared      complete, reconciled as far  as  possible,  and  considered+       cleared      complete,  reconciled  as  far  as possible, and considered                     correct -       With  this scheme, you would use -PC to see the current balance at your-       bank, -U to see things which will probably hit  your  bank  soon  (like+       With this scheme, you would use -PC to see the current balance at  your+       bank,  -U  to  see  things which will probably hit your bank soon (like        uncashed checks), and no flags to see the most up-to-date state of your        finances. +   Description+       A  transaction's description is the rest of the line following the date+       and status mark (or until a  comment  begins).   Sometimes  called  the+       "narration" in traditional bookkeeping, it can be used for whatever you+       wish, or left blank.  Transaction descriptions can be  queried,  unlike+       comments.++   Payee and note+       You  can  optionally  include  a | (pipe) character in a description to+       subdivide it into a payee/payer name on the left and  additional  notes+       on  the  right.   This may be worthwhile if you need to do more precise+       querying and pivoting by payee.+    Account names        Account names typically have several parts separated by a  full  colon,        from  which hledger derives a hierarchical chart of accounts.  They can@@ -577,25 +597,9 @@        Tags are like Ledger's metadata feature, except  hledger's  tag  values        are simple strings. -   Implicit tags-       Some predefined "implicit" tags are also provided:--       o code - the transaction's code field--       o description - the transaction's description--       o payee - the part of description before |, or all of it--       o note - the part of description after |, or all of it--       payee  and  note support descriptions written in a special PAYEE | NOTE-       format, accessing the parts before and after the pipe character respec--       tively.   For descriptions not containing a pipe character they are the-       same as description.-    Directives    Account aliases-       You can define aliases which rewrite your account names (after  reading+       You  can define aliases which rewrite your account names (after reading        the journal, before generating reports).  hledger's account aliases can        be useful for: @@ -612,8 +616,8 @@        See also Cookbook: rewrite account names.     Basic aliases-       To set an account alias, use the alias directive in your journal  file.-       This  affects all subsequent journal entries in the current file or its+       To  set an account alias, use the alias directive in your journal file.+       This affects all subsequent journal entries in the current file or  its        included files.  The spaces around the = are optional:                alias OLD = NEW@@ -621,31 +625,33 @@        Or, you can use the --alias 'OLD=NEW' option on the command line.  This        affects all entries.  It's useful for trying out aliases interactively. -       OLD and NEW are full account names.  hledger will  replace  any  occur--       rence  of  the old account name with the new one.  Subaccounts are also+       OLD  and  NEW  are full account names.  hledger will replace any occur-+       rence of the old account name with the new one.  Subaccounts  are  also        affected.  Eg:                alias checking = assets:bank:wells fargo:checking               # rewrites "checking" to "assets:bank:wells fargo:checking", or "checking:a" to "assets:bank:wells fargo:checking:a"     Regex aliases-       There is also a more powerful variant that uses a  regular  expression,+       There  is  also a more powerful variant that uses a regular expression,        indicated by the forward slashes:                alias /REGEX/ = REPLACEMENT         or --alias '/REGEX/=REPLACEMENT'. -       REGEX  is  a  case-insensitive regular expression.  Anywhere it matches-       inside an account name, the matched part will be replaced  by  REPLACE--       MENT.   If REGEX contains parenthesised match groups, these can be ref--       erenced by the usual numeric backreferences in REPLACEMENT.  Note, cur--       rently  regular  expression  aliases  may  cause noticeable slow-downs.-       (And if you use Ledger on your hledger file, they will be ignored.) Eg:+       REGEX is a case-insensitive regular expression.   Anywhere  it  matches+       inside  an  account name, the matched part will be replaced by REPLACE-+       MENT.  If REGEX contains parenthesised match groups, these can be  ref-+       erenced by the usual numeric backreferences in REPLACEMENT.  Eg:                alias /^(.+):bank:([^:]+)(.*)/ = \1:\2 \3               # rewrites "assets:bank:wells fargo:checking" to  "assets:wells fargo checking" +       Also  note that REPLACEMENT continues to the end of line (or on command+       line, to end of option argument), so it  can  contain  trailing  white-+       space.+    Multiple aliases        You  can  define  as  many aliases as you like using directives or com-        mand-line options.  Aliases are recursive - each alias sees the  result@@ -811,7 +817,11 @@                           ing-Ledger-files-with-TextWrangler  +       Visual    Studio   https://marketplace.visualstudio.com/items?item-+       Code               Name=mark-hansen.hledger-vscode ++ REPORTING BUGS        Report  bugs at http://bugs.hledger.org (or on the #hledger IRC channel        or hledger mail list)@@ -835,4 +845,4 @@   -hledger 1.3.2                   September 2017              hledger_journal(5)+hledger 1.4                     September 2017              hledger_journal(5)
doc/hledger_timeclock.5 view
@@ -1,5 +1,5 @@ -.TH "hledger_timeclock" "5" "September 2017" "hledger 1.3.2" "hledger User Manuals"+.TH "hledger_timeclock" "5" "September 2017" "hledger 1.4" "hledger User Manuals"   
doc/hledger_timeclock.5.info view
@@ -4,8 +4,8 @@  File: hledger_timeclock.5.info,  Node: Top,  Up: (dir) -hledger_timeclock(5) hledger 1.3.2-**********************************+hledger_timeclock(5) hledger 1.4+********************************  hledger can read timeclock files.  As with Ledger, these are (a subset of) timeclock.el's format, containing clock-in and clock-out entries as
doc/hledger_timeclock.5.txt view
@@ -79,4 +79,4 @@   -hledger 1.3.2                   September 2017            hledger_timeclock(5)+hledger 1.4                     September 2017            hledger_timeclock(5)
doc/hledger_timedot.5 view
@@ -1,5 +1,5 @@ -.TH "hledger_timedot" "5" "September 2017" "hledger 1.3.2" "hledger User Manuals"+.TH "hledger_timedot" "5" "September 2017" "hledger 1.4" "hledger User Manuals"   @@ -9,17 +9,17 @@ .SH DESCRIPTION .PP Timedot is a plain text format for logging dated, categorised quantities-(eg time), supported by hledger.+(of time, usually), supported by hledger. It is convenient for approximate and retroactive time logging, eg when the real\-time clock\-in/out required with a timeclock file is too precise or too interruptive. It can be formatted like a bar chart, making clear at a glance where time was spent. .PP-Though called "timedot", the format does not specify the commodity being-logged, so could represent other dated, quantifiable things.-Eg you could record a single\-entry journal of financial transactions,-perhaps slightly more conveniently than with hledger_journal(5) format.+Though called "timedot", this format is read by hledger as commodityless+quantities, so it could be used to represent dated quantities other than+time.+In the docs below we\[aq]ll assume it\[aq]s time. .SH FILE FORMAT .PP A timedot file contains a series of day entries.@@ -27,16 +27,26 @@ pairs, one per line. Dates are hledger\-style simple dates (see hledger_journal(5)). Categories are hledger\-style account names, optionally indented.-There must be at least two spaces between the category and the quantity.-Quantities can be written in two ways:-.IP "1." 3-a series of dots (period characters).-Each dot represents "a quarter" \- eg, a quarter hour.-Spaces can be used to group dots into hours, for easier counting.-.IP "2." 3-a number (integer or decimal), representing "units" \- eg, hours.-A good alternative when dots are cumbersome.-(A number also can record negative quantities.)+As in a hledger journal, there must be at least two spaces between the+category (account name) and the quantity.+.PP+Quantities can be written as:+.IP \[bu] 2+a sequence of dots (.) representing quarter hours.+Spaces may optionally be used for grouping and readability.+Eg: ....+\&..+.IP \[bu] 2+an integral or decimal number, representing hours.+Eg: 1.5+.IP \[bu] 2+an integral or decimal number immediately followed by a unit symbol+\f[C]s\f[], \f[C]m\f[], \f[C]h\f[], \f[C]d\f[], \f[C]w\f[], \f[C]mo\f[],+or \f[C]y\f[], representing seconds, minutes, hours, days weeks, months+or years respectively.+Eg: 90m.+The following equivalencies are assumed, currently: 1m = 60s, 1h = 60m,+1d = 24h, 1w = 7d, 1mo = 30d, 1y=365d. .PP Blank lines and lines beginning with #, ; or * are ignored. An example:
doc/hledger_timedot.5.info view
@@ -4,20 +4,19 @@  File: hledger_timedot.5.info,  Node: Top,  Next: FILE FORMAT,  Up: (dir) -hledger_timedot(5) hledger 1.3.2-********************************+hledger_timedot(5) hledger 1.4+******************************  Timedot is a plain text format for logging dated, categorised quantities-(eg time), supported by hledger.  It is convenient for approximate and-retroactive time logging, eg when the real-time clock-in/out required-with a timeclock file is too precise or too interruptive.  It can be-formatted like a bar chart, making clear at a glance where time was-spent.+(of time, usually), supported by hledger.  It is convenient for+approximate and retroactive time logging, eg when the real-time+clock-in/out required with a timeclock file is too precise or too+interruptive.  It can be formatted like a bar chart, making clear at a+glance where time was spent. -   Though called "timedot", the format does not specify the commodity-being logged, so could represent other dated, quantifiable things.  Eg-you could record a single-entry journal of financial transactions,-perhaps slightly more conveniently than with hledger_journal(5) format.+   Though called "timedot", this format is read by hledger as+commodityless quantities, so it could be used to represent dated+quantities other than time.  In the docs below we'll assume it's time. * Menu:  * FILE FORMAT::@@ -31,18 +30,23 @@ A timedot file contains a series of day entries.  A day entry begins with a date, and is followed by category/quantity pairs, one per line. Dates are hledger-style simple dates (see hledger_journal(5)).-Categories are hledger-style account names, optionally indented.  There-must be at least two spaces between the category and the quantity.-Quantities can be written in two ways:+Categories are hledger-style account names, optionally indented.  As in+a hledger journal, there must be at least two spaces between the+category (account name) and the quantity. -  1. a series of dots (period characters).  Each dot represents "a-     quarter" - eg, a quarter hour.  Spaces can be used to group dots-     into hours, for easier counting.+   Quantities can be written as: -  2. a number (integer or decimal), representing "units" - eg, hours.  A-     good alternative when dots are cumbersome.  (A number also can-     record negative quantities.)+   * a sequence of dots (.)  representing quarter hours.  Spaces may+     optionally be used for grouping and readability.  Eg: ....  .. +   * an integral or decimal number, representing hours.  Eg: 1.5++   * an integral or decimal number immediately followed by a unit symbol+     's', 'm', 'h', 'd', 'w', 'mo', or 'y', representing seconds,+     minutes, hours, days weeks, months or years respectively.  Eg: 90m.+     The following equivalencies are assumed, currently: 1m = 60s, 1h =+     60m, 1d = 24h, 1w = 7d, 1mo = 30d, 1y=365d.+    Blank lines and lines beginning with #, ; or * are ignored.  An example: @@ -106,7 +110,7 @@  Tag Table: Node: Top78-Node: FILE FORMAT886-Ref: #file-format989+Node: FILE FORMAT809+Ref: #file-format912  End Tag Table
doc/hledger_timedot.5.txt view
@@ -8,33 +8,37 @@  DESCRIPTION        Timedot  is  a plain text format for logging dated, categorised quanti--       ties (eg time), supported by hledger.  It is convenient for approximate-       and  retroactive  time  logging,  eg  when  the  real-time clock-in/out-       required with a timeclock file is too precise or too interruptive.   It-       can  be formatted like a bar chart, making clear at a glance where time-       was spent.+       ties (of time, usually), supported by hledger.  It  is  convenient  for+       approximate  and  retroactive  time  logging,  eg  when  the  real-time+       clock-in/out required with a timeclock  file  is  too  precise  or  too+       interruptive.   It can be formatted like a bar chart, making clear at a+       glance where time was spent. -       Though called "timedot", the format  does  not  specify  the  commodity-       being  logged, so could represent other dated, quantifiable things.  Eg-       you could record a single-entry journal of financial transactions, per--       haps slightly more conveniently than with hledger_journal(5) format.+       Though called "timedot", this format is read by hledger  as  commodity-+       less  quantities,  so  it  could  be used to represent dated quantities+       other than time.  In the docs below we'll assume it's time.  FILE FORMAT-       A  timedot  file  contains a series of day entries.  A day entry begins-       with a date, and is followed by category/quantity pairs, one per  line.-       Dates  are  hledger-style simple dates (see hledger_journal(5)).  Cate--       gories are hledger-style account  names,  optionally  indented.   There-       must  be  at  least  two  spaces between the category and the quantity.-       Quantities can be written in two ways:+       A timedot file contains a series of day entries.  A  day  entry  begins+       with  a date, and is followed by category/quantity pairs, one per line.+       Dates are hledger-style simple dates (see  hledger_journal(5)).   Cate-+       gories  are  hledger-style account names, optionally indented.  As in a+       hledger journal, there must be at least two spaces between the category+       (account name) and the quantity. -       1. a series of dots (period characters).  Each dot represents "a  quar--          ter"  -  eg,  a quarter hour.  Spaces can be used to group dots into-          hours, for easier counting.+       Quantities can be written as: -       2. a number (integer or decimal), representing "units" - eg, hours.   A-          good  alternative  when  dots  are  cumbersome.   (A number also can-          record negative quantities.)+       o a  sequence  of  dots  (.)  representing  quarter  hours.  Spaces may+         optionally be used for grouping and readability.  Eg: ....  .. +       o an integral or decimal number, representing hours.  Eg: 1.5++       o an integral or decimal number immediately followed by a  unit  symbol+         s,  m,  h, d, w, mo, or y, representing seconds, minutes, hours, days+         weeks, months or years respectively.  Eg: 90m.  The following equiva-+         lencies  are  assumed,  currently: 1m = 60s, 1h = 60m, 1d = 24h, 1w =+         7d, 1mo = 30d, 1y=365d.+        Blank lines and lines beginning with #, ; or * are ignored.   An  exam-        ple: @@ -120,4 +124,4 @@   -hledger 1.3.2                   September 2017              hledger_timedot(5)+hledger 1.4                     September 2017              hledger_timedot(5)
hledger-lib.cabal view
@@ -3,7 +3,7 @@ -- see: https://github.com/sol/hpack  name:           hledger-lib-version:        1.3.2+version:        1.4 synopsis:       Core data types, parsers and functionality for the hledger accounting tools description:    This is a reusable library containing hledger's core functionality.                 .@@ -21,7 +21,7 @@ maintainer:     Simon Michael <simon@joyful.com> license:        GPL-3 license-file:   LICENSE-tested-with:    GHC==7.10.3, GHC==8.0+tested-with:    GHC==7.10.3, GHC==8.0.2, GHC==8.2.1 build-type:     Simple cabal-version:  >= 1.10 @@ -47,11 +47,6 @@   type: git   location: https://github.com/simonmichael/hledger -flag oldtime-  description: If building with time < 1.5, also depend on old-locale. Set automatically by cabal.-  manual: False-  default: False- library   hs-source-dirs:       ./.@@ -59,7 +54,7 @@   build-depends:       base >=4.8 && <5     , base-compat >=0.8.1-    , ansi-terminal >= 0.6.2.3 && < 0.7+    , ansi-terminal >= 0.6.2.3 && < 0.8     , array     , blaze-markup >=0.5.1     , bytestring@@ -76,27 +71,18 @@     , mtl     , mtl-compat     , old-time-    , parsec+    , parsec >= 3     , pretty-show >=1.6.4     , regex-tdfa     , safe >=0.2     , semigroups     , split >=0.1 && <0.3     , text >=1.2 && <1.3+    , time >=1.5     , transformers >=0.2 && <0.6     , uglymemo     , utf8-string >=0.3.5 && <1.1     , HUnit-  if impl(ghc <7.6)-    build-depends:-        ghc-prim-  if flag(oldtime)-    build-depends:-        time <1.5-      , old-locale-  else-    build-depends:-        time >=1.5   exposed-modules:       Hledger       Hledger.Data@@ -156,7 +142,7 @@   build-depends:       base >=4.8 && <5     , base-compat >=0.8.1-    , ansi-terminal >= 0.6.2.3 && < 0.7+    , ansi-terminal >= 0.6.2.3 && < 0.8     , array     , blaze-markup >=0.5.1     , bytestring@@ -173,29 +159,20 @@     , mtl     , mtl-compat     , old-time-    , parsec+    , parsec >= 3     , pretty-show >=1.6.4     , regex-tdfa     , safe >=0.2     , semigroups     , split >=0.1 && <0.3     , text >=1.2 && <1.3+    , time >=1.5     , transformers >=0.2 && <0.6     , uglymemo     , utf8-string >=0.3.5 && <1.1     , HUnit     , doctest >=0.8     , Glob >=0.7-  if impl(ghc <7.6)-    build-depends:-        ghc-prim-  if flag(oldtime)-    build-depends:-        time <1.5-      , old-locale-  else-    build-depends:-        time >=1.5   other-modules:       Hledger       Hledger.Data@@ -253,7 +230,7 @@   build-depends:       base >=4.8 && <5     , base-compat >=0.8.1-    , ansi-terminal >= 0.6.2.3 && < 0.7+    , ansi-terminal >= 0.6.2.3 && < 0.8     , array     , blaze-markup >=0.5.1     , bytestring@@ -270,13 +247,14 @@     , mtl     , mtl-compat     , old-time-    , parsec+    , parsec >= 3     , pretty-show >=1.6.4     , regex-tdfa     , safe >=0.2     , semigroups     , split >=0.1 && <0.3     , text >=1.2 && <1.3+    , time >=1.5     , transformers >=0.2 && <0.6     , uglymemo     , utf8-string >=0.3.5 && <1.1@@ -284,16 +262,6 @@     , hledger-lib     , test-framework     , test-framework-hunit-  if impl(ghc <7.6)-    build-depends:-        ghc-prim-  if flag(oldtime)-    build-depends:-        time <1.5-      , old-locale-  else-    build-depends:-        time >=1.5   other-modules:       Hledger       Hledger.Data
tests/doctests.hs view
@@ -5,6 +5,7 @@ import Test.DocTest  main = do-  fs1 <- filter (not . isInfixOf "/.") <$> glob "Hledger/**/*.hs"-  -- fs2 <- filter (not . isInfixOf "/.") <$> glob "other/ledger-parse/**/*.hs"-  doctest $ ["Hledger.hs"] ++ fs1 -- ++ fs2+  fs1 <- glob "Hledger/**/*.hs"+  fs2 <- glob "Text/**/*.hs"+  --fs3 <- glob "other/ledger-parse/**/*.hs"+  doctest $ filter (not . isInfixOf "/.") $ ["Hledger.hs"] ++ fs1 ++ fs2