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 +34/−3
- Hledger/Data/Account.hs +17/−1
- Hledger/Data/Journal.hs +49/−2
- Hledger/Data/Ledger.hs +2/−2
- Hledger/Data/Posting.hs +5/−17
- Hledger/Data/StringFormat.hs +2/−2
- Hledger/Data/Types.hs +9/−0
- Hledger/Query.hs +18/−7
- Hledger/Read.hs +111/−4
- Hledger/Read/Common.hs +36/−1
- Hledger/Read/CsvReader.hs +41/−33
- Hledger/Read/JournalReader.hs +2/−2
- Hledger/Read/TimedotReader.hs +34/−10
- Hledger/Reports.hs +2/−1
- Hledger/Reports/BalanceReport.hs +30/−14
- Hledger/Reports/MultiBalanceReports.hs +99/−11
- Hledger/Reports/PostingsReport.hs +6/−6
- Hledger/Reports/ReportOptions.hs +14/−6
- Hledger/Utils.hs +3/−0
- Hledger/Utils/Debug.hs +0/−9
- Hledger/Utils/UTF8IOCompat.hs +0/−16
- doc/hledger_csv.5 +52/−19
- doc/hledger_csv.5.info +116/−35
- doc/hledger_csv.5.txt +48/−19
- doc/hledger_journal.5 +32/−23
- doc/hledger_journal.5.info +155/−137
- doc/hledger_journal.5.txt +89/−79
- doc/hledger_timeclock.5 +1/−1
- doc/hledger_timeclock.5.info +2/−2
- doc/hledger_timeclock.5.txt +1/−1
- doc/hledger_timedot.5 +26/−16
- doc/hledger_timedot.5.info +26/−22
- doc/hledger_timedot.5.txt +26/−22
- hledger-lib.cabal +11/−43
- tests/doctests.hs +4/−3
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