summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES12
-rw-r--r--Hledger/Data/AccountName.hs18
-rw-r--r--Hledger/Data/Amount.hs21
-rw-r--r--Hledger/Data/AutoTransaction.hs5
-rw-r--r--Hledger/Data/Journal.hs65
-rw-r--r--Hledger/Data/Transaction.hs28
-rw-r--r--Hledger/Data/Types.hs25
-rw-r--r--Hledger/Read.hs159
-rw-r--r--Hledger/Read/Common.hs55
-rw-r--r--Hledger/Read/CsvReader.hs27
-rw-r--r--Hledger/Read/JournalReader.hs17
-rw-r--r--Hledger/Read/TimeclockReader.hs4
-rw-r--r--Hledger/Read/TimedotReader.hs4
-rw-r--r--Hledger/Reports.hs7
-rw-r--r--Hledger/Reports/BalanceReport.hs2
-rw-r--r--Hledger/Reports/BudgetReport.hs361
-rw-r--r--Hledger/Reports/MultiBalanceReports.hs25
-rw-r--r--Hledger/Reports/ReportOptions.hs3
-rw-r--r--Hledger/Reports/ReportTypes.hs40
-rw-r--r--Text/Tabular/AsciiWide.hs111
-rw-r--r--hledger-lib.cabal147
-rw-r--r--hledger_csv.52
-rw-r--r--hledger_csv.info60
-rw-r--r--hledger_csv.txt2
-rw-r--r--hledger_journal.5390
-rw-r--r--hledger_journal.info602
-rw-r--r--hledger_journal.txt386
-rw-r--r--hledger_timeclock.52
-rw-r--r--hledger_timeclock.info4
-rw-r--r--hledger_timeclock.txt2
-rw-r--r--hledger_timedot.52
-rw-r--r--hledger_timedot.info8
-rw-r--r--hledger_timedot.txt2
33 files changed, 1617 insertions, 981 deletions
diff --git a/CHANGES b/CHANGES
index e405da7..c1bc449 100644
--- a/CHANGES
+++ b/CHANGES
@@ -2,6 +2,18 @@ API-ish changes in the hledger-lib package.
Most user-visible changes are noted in the hledger changelog, instead.
+# 1.9.1 (2018/4/30)
+
+* new generic PeriodicReport, and some report-related type aliases
+
+* new BudgetReport
+
+* make (readJournal|tryReader)s?WithOpts the default api, dropping "WithOpts"
+
+* automated postings and command line account aliases happen earlier
+ in journal processing (see hledger changelog)
+
+
# 1.9 (2018/3/31)
* support ghc 8.4, latest deps
diff --git a/Hledger/Data/AccountName.hs b/Hledger/Data/AccountName.hs
index aeb39d2..54c9759 100644
--- a/Hledger/Data/AccountName.hs
+++ b/Hledger/Data/AccountName.hs
@@ -57,8 +57,24 @@ accountNameLevel :: AccountName -> Int
accountNameLevel "" = 0
accountNameLevel a = T.length (T.filter (==acctsepchar) a) + 1
+-- | A top-level account prefixed to some accounts in budget reports.
+-- Defined here so it can be ignored by accountNameDrop.
+unbudgetedAccountName :: T.Text
+unbudgetedAccountName = "<unbudgeted>"
+
+-- | Remove some number of account name components from the front of the account name.
+-- If the special "<unbudgeted>" top-level account is present, it is preserved and
+-- dropping affects the rest of the account name.
accountNameDrop :: Int -> AccountName -> AccountName
-accountNameDrop n = accountNameFromComponents . drop n . accountNameComponents
+accountNameDrop n a
+ | a == unbudgetedAccountName = a
+ | unbudgetedAccountAndSep `T.isPrefixOf` a =
+ case accountNameDrop n $ T.drop (T.length unbudgetedAccountAndSep) a of
+ "" -> unbudgetedAccountName
+ a' -> unbudgetedAccountAndSep <> a'
+ | otherwise = accountNameFromComponents $ drop n $ accountNameComponents a
+ where
+ unbudgetedAccountAndSep = unbudgetedAccountName <> acctsep
-- | Sorted unique account names implied by these account names,
-- ie these plus all their parent accounts up to the root.
diff --git a/Hledger/Data/Amount.hs b/Hledger/Data/Amount.hs
index e3b3d18..a1fe168 100644
--- a/Hledger/Data/Amount.hs
+++ b/Hledger/Data/Amount.hs
@@ -61,6 +61,7 @@ module Hledger.Data.Amount (
amountValue,
-- ** rendering
amountstyle,
+ styleAmount,
showAmount,
cshowAmount,
showAmountWithZeroCommodity,
@@ -93,6 +94,7 @@ module Hledger.Data.Amount (
isReallyZeroMixedAmountCost,
mixedAmountValue,
-- ** rendering
+ styleMixedAmount,
showMixedAmount,
showMixedAmountOneLine,
showMixedAmountDebug,
@@ -131,8 +133,14 @@ import Hledger.Utils
deriving instance Show MarketPrice
+
+-------------------------------------------------------------------------------
+-- Amount styles
+
+-- | Default amount style
amountstyle = AmountStyle L False 0 (Just '.') Nothing
+
-------------------------------------------------------------------------------
-- Amount
@@ -265,6 +273,14 @@ showPriceDebug NoPrice = ""
showPriceDebug (UnitPrice pa) = " @ " ++ showAmountDebug pa
showPriceDebug (TotalPrice pa) = " @@ " ++ showAmountDebug pa
+-- | Given a map of standard amount display styles, apply the appropriate one to this amount.
+-- If there's no standard style for this amount's commodity, return the amount unchanged.
+styleAmount :: M.Map CommoditySymbol AmountStyle -> Amount -> Amount
+styleAmount styles a =
+ case M.lookup (acommodity a) styles of
+ Just s -> a{astyle=s}
+ Nothing -> a
+
-- | Get the string representation of an amount, based on its
-- commodity's display settings. String representations equivalent to
-- zero are converted to just \"0\". The special "missing" amount is
@@ -555,6 +571,10 @@ isReallyZeroMixedAmountCost = isReallyZeroMixedAmount . costOfMixedAmount
-- where a' = normaliseMixedAmountSquashPricesForDisplay a
-- b' = normaliseMixedAmountSquashPricesForDisplay b
+-- | Given a map of standard amount display styles, apply the appropriate ones to each individual amount.
+styleMixedAmount :: M.Map CommoditySymbol AmountStyle -> MixedAmount -> MixedAmount
+styleMixedAmount styles (Mixed as) = Mixed $ map (styleAmount styles) as
+
-- | Get the string representation of a mixed amount, after
-- normalising it to one amount per commodity. Assumes amounts have
-- no or similar prices, otherwise this can show misleading prices.
@@ -648,6 +668,7 @@ canonicaliseMixedAmount styles (Mixed as) = Mixed $ map (canonicaliseAmount styl
mixedAmountValue :: Journal -> Day -> MixedAmount -> MixedAmount
mixedAmountValue j d (Mixed as) = Mixed $ map (amountValue j d) as
+
-------------------------------------------------------------------------------
-- misc
diff --git a/Hledger/Data/AutoTransaction.hs b/Hledger/Data/AutoTransaction.hs
index 9481305..8a0f369 100644
--- a/Hledger/Data/AutoTransaction.hs
+++ b/Hledger/Data/AutoTransaction.hs
@@ -71,8 +71,9 @@ runModifierTransaction :: Query -> ModifierTransaction -> (Transaction -> Transa
runModifierTransaction q mt = modifier where
q' = simplifyQuery $ And [q, mtvaluequery mt (error "query cannot depend on current time")]
mods = map runModifierPosting $ mtpostings mt
- generatePostings ps = [m p | p <- ps, q' `matchesPosting` p, m <- mods]
- modifier t@(tpostings -> ps) = t { tpostings = ps ++ generatePostings ps }
+ generatePostings ps = [p' | p <- ps
+ , p' <- if q' `matchesPosting` p then p:[ m p | m <- mods] else [p]]
+ modifier t@(tpostings -> ps) = t { tpostings = generatePostings ps }
-- | Extract 'Query' equivalent of 'mtvalueexpr' from 'ModifierTransaction'
--
diff --git a/Hledger/Data/Journal.hs b/Hledger/Data/Journal.hs
index 8e62a0a..abf0c71 100644
--- a/Hledger/Data/Journal.hs
+++ b/Hledger/Data/Journal.hs
@@ -16,10 +16,10 @@ module Hledger.Data.Journal (
addModifierTransaction,
addPeriodicTransaction,
addTransaction,
- journalApplyAliases,
journalBalanceTransactions,
journalApplyCommodityStyles,
commodityStylesFromAmounts,
+ journalCommodityStyles,
journalConvertAmountsToCost,
journalFinalise,
journalPivot,
@@ -485,19 +485,6 @@ filterJournalTransactionsByAccount apats j@Journal{jtxns=ts} = j{jtxns=filter tm
-}
--- | Apply additional account aliases (eg from the command-line) to all postings in a journal.
-journalApplyAliases :: [AccountAlias] -> Journal -> Journal
-journalApplyAliases aliases j@Journal{jtxns=ts} =
- -- (if null aliases
- -- then id
- -- else (dbgtrace $
- -- "applying additional command-line aliases:\n"
- -- ++ chomp (unlines $ map (" "++) $ lines $ ppShow aliases))) $
- j{jtxns=map dotransaction ts}
- where
- dotransaction t@Transaction{tpostings=ps} = t{tpostings=map doposting ps}
- doposting p@Posting{paccount=a} = p{paccount= accountNameApplyAliases aliases a}
-
-- | Do post-parse processing on a parsed journal to make it ready for
-- use. Reverse parsed data to normal order, canonicalise amount
-- formats, check/ensure that transactions are balanced, and maybe
@@ -617,8 +604,11 @@ journalBalanceTransactionsST assrt j createStore storeIn extract =
runExceptT $ do
bals <- lift $ HT.newSized size
txStore <- lift $ createStore
- flip R.runReaderT (Env bals (storeIn txStore) assrt $
- Just $ jinferredcommodities j) $ do
+ let env = Env bals
+ (storeIn txStore)
+ assrt
+ (Just $ journalCommodityStyles j)
+ flip R.runReaderT env $ do
dated <- fmap snd . sortBy (comparing fst) . concat
<$> mapM' discriminateByDate (jtxns j)
mapM' checkInferAndRegisterAmounts dated
@@ -738,7 +728,6 @@ storeTransaction tx = liftModifier $ ($tx) . eStoreTx
liftModifier :: (Env s -> ST s a) -> CurrentBalancesModifier s a
liftModifier f = R.ask >>= lift . lift . f
-
-- | Choose and apply a consistent display format to the posting
-- amounts in each commodity. Each commodity's format is specified by
-- a commodity format directive, or otherwise inferred from posting
@@ -747,28 +736,20 @@ journalApplyCommodityStyles :: Journal -> Journal
journalApplyCommodityStyles j@Journal{jtxns=ts, jmarketprices=mps} = j''
where
j' = journalInferCommodityStyles j
+ styles = journalCommodityStyles j'
j'' = j'{jtxns=map fixtransaction ts, jmarketprices=map fixmarketprice mps}
fixtransaction t@Transaction{tpostings=ps} = t{tpostings=map fixposting ps}
- fixposting p@Posting{pamount=a} = p{pamount=fixmixedamount a}
- fixmarketprice mp@MarketPrice{mpamount=a} = mp{mpamount=fixamount a}
- fixmixedamount (Mixed as) = Mixed $ map fixamount as
- fixamount a@Amount{acommodity=c} = a{astyle=journalCommodityStyle j' c}
-
--- | Get this journal's standard display style for the given
--- commodity. That is the style defined by the last corresponding
--- commodity format directive if any, otherwise the style inferred
--- from the posting amounts (or in some cases, price amounts) in this
--- commodity if any, otherwise the default style.
-journalCommodityStyle :: Journal -> CommoditySymbol -> AmountStyle
-journalCommodityStyle j = fromMaybe amountstyle{asprecision=2} . journalCommodityStyleLookup j
-
-journalCommodityStyleLookup :: Journal -> CommoditySymbol -> Maybe AmountStyle
-journalCommodityStyleLookup j c =
- listToMaybe $
- catMaybes [
- M.lookup c (jcommodities j) >>= cformat
- ,M.lookup c $ jinferredcommodities j
- ]
+ fixposting p@Posting{pamount=a} = p{pamount=styleMixedAmount styles a}
+ fixmarketprice mp@MarketPrice{mpamount=a} = mp{mpamount=styleAmount styles a}
+
+-- | Get all the amount styles defined in this journal, either
+-- declared by a commodity directive (preferred) or inferred from amounts,
+-- as a map from symbol to style.
+journalCommodityStyles :: Journal -> M.Map CommoditySymbol AmountStyle
+journalCommodityStyles j = declaredstyles <> inferredstyles
+ where
+ declaredstyles = M.mapMaybe cformat $ jcommodities j
+ inferredstyles = jinferredcommodities j
-- | Infer a display format for each commodity based on the amounts parsed.
-- "hledger... will use the format of the first posting amount in the
@@ -776,8 +757,8 @@ journalCommodityStyleLookup j c =
journalInferCommodityStyles :: Journal -> Journal
journalInferCommodityStyles j =
j{jinferredcommodities =
- commodityStylesFromAmounts $
- dbg8 "journalChooseCommmodityStyles using amounts" $ journalAmounts j}
+ commodityStylesFromAmounts $
+ dbg8 "journalInferCommmodityStyles using amounts" $ journalAmounts j}
-- | Given a list of amounts in parse order, build a map from their commodity names
-- to standard commodity display formats.
@@ -833,10 +814,8 @@ journalConvertAmountsToCost j@Journal{jtxns=ts} = j{jtxns=map fixtransaction ts}
fixtransaction t@Transaction{tpostings=ps} = t{tpostings=map fixposting ps}
fixposting p@Posting{pamount=a} = p{pamount=fixmixedamount a}
fixmixedamount (Mixed as) = Mixed $ map fixamount as
- fixamount = applyJournalStyle . costOfAmount
- applyJournalStyle a
- | Just s <- journalCommodityStyleLookup j (acommodity a) = a{astyle=s}
- | otherwise = a
+ fixamount = styleAmount styles . costOfAmount
+ styles = journalCommodityStyles j
-- -- | Get this journal's unique, display-preference-canonicalised commodities, by symbol.
-- journalCanonicalCommodities :: Journal -> M.Map String CommoditySymbol
diff --git a/Hledger/Data/Transaction.hs b/Hledger/Data/Transaction.hs
index ba74ae1..2f22446 100644
--- a/Hledger/Data/Transaction.hs
+++ b/Hledger/Data/Transaction.hs
@@ -278,7 +278,7 @@ tests_inference = [
"inferBalancingAmount" ~: do
let p `gives` p' = assertEqual (show p) (Right p') $ inferTransaction p
inferTransaction :: Transaction -> Either String Transaction
- inferTransaction = runIdentity . runExceptT . inferBalancingAmount (\_ _ -> return ())
+ inferTransaction = runIdentity . runExceptT . inferBalancingAmount (\_ _ -> return ()) Map.empty
nulltransaction `gives` nulltransaction
nulltransaction{
tpostings=[
@@ -382,11 +382,11 @@ balanceTransactionUpdate :: MonadError String m
-- ^ update function
-> Maybe (Map.Map CommoditySymbol AmountStyle)
-> Transaction -> m Transaction
-balanceTransactionUpdate update styles t =
- finalize =<< inferBalancingAmount update t
+balanceTransactionUpdate update mstyles t =
+ finalize =<< inferBalancingAmount update (fromMaybe Map.empty mstyles) t
where
finalize t' = let t'' = inferBalancingPrices t'
- in if isTransactionBalanced styles t''
+ in if isTransactionBalanced mstyles t''
then return $ txnTieKnot t''
else throwError $ printerr $ nonzerobalanceerror t''
printerr s = intercalate "\n" [s, showTransactionUnelided t]
@@ -409,11 +409,12 @@ balanceTransactionUpdate update styles t =
-- We can infer a missing amount when there are multiple postings and exactly
-- one of them is amountless. If the amounts had price(s) the inferred amount
-- have the same price(s), and will be converted to the price commodity.
-inferBalancingAmount :: MonadError String m
- => (AccountName -> MixedAmount -> m ())
- -- ^ update function
- -> Transaction -> m Transaction
-inferBalancingAmount update t@Transaction{tpostings=ps}
+inferBalancingAmount :: MonadError String m =>
+ (AccountName -> MixedAmount -> m ()) -- ^ update function
+ -> Map.Map CommoditySymbol AmountStyle -- ^ standard amount styles
+ -> Transaction
+ -> m Transaction
+inferBalancingAmount update styles t@Transaction{tpostings=ps}
| length amountlessrealps > 1
= throwError $ printerr "could not balance this transaction - can't have more than one real posting with no amount (remember to put 2 or more spaces before amounts)"
| length amountlessbvps > 1
@@ -432,8 +433,13 @@ inferBalancingAmount update t@Transaction{tpostings=ps}
inferamount p@Posting{ptype=BalancedVirtualPosting}
| not (hasAmount p) = updateAmount p bvsum
inferamount p = return p
- updateAmount p amt = update (paccount p) amt' >> return p { pamount=amt', porigin=Just $ originalPosting p }
- where amt' = normaliseMixedAmount $ costOfMixedAmount (-amt)
+ updateAmount p amt =
+ update (paccount p) amt' >> return p { pamount=amt', porigin=Just $ originalPosting p }
+ where
+ -- Inferred amounts are converted to cost.
+ -- Also, ensure the new amount has the standard style for its commodity
+ -- (the main amount styling pass happened before this balancing pass).
+ amt' = styleMixedAmount styles $ normaliseMixedAmount $ costOfMixedAmount (-amt)
-- | Infer prices for this transaction's posting amounts, if needed to make
-- the postings balance, and if possible. This is done once for the real
diff --git a/Hledger/Data/Types.hs b/Hledger/Data/Types.hs
index 33acb3a..b2a05e0 100644
--- a/Hledger/Data/Types.hs
+++ b/Hledger/Data/Types.hs
@@ -22,7 +22,6 @@ where
import GHC.Generics (Generic)
import Control.DeepSeq (NFData)
-import Control.Monad.Except (ExceptT)
import Data.Data
import Data.Decimal
import Data.Default
@@ -303,7 +302,7 @@ data Journal = Journal {
-- principal data
,jaccounts :: [(AccountName, Maybe AccountCode)] -- ^ accounts that have been declared by account directives
,jcommodities :: M.Map CommoditySymbol Commodity -- ^ commodities and formats declared by commodity directives
- ,jinferredcommodities :: M.Map CommoditySymbol AmountStyle -- ^ commodities and formats inferred from journal amounts
+ ,jinferredcommodities :: M.Map CommoditySymbol AmountStyle -- ^ commodities and formats inferred from journal amounts XXX misnamed
,jmarketprices :: [MarketPrice]
,jmodifiertxns :: [ModifierTransaction]
,jperiodictxns :: [PeriodicTransaction]
@@ -329,28 +328,6 @@ type ParsedJournal = Journal
-- The --output-format option selects one of these for output.
type StorageFormat = String
--- | A hledger journal reader is a triple of storage format name, a
--- detector of that format, and a parser from that format to Journal.
-data Reader = Reader {
-
- -- The canonical name of the format handled by this reader
- rFormat :: StorageFormat
-
- -- The file extensions recognised as containing this format
- ,rExtensions :: [String]
-
- -- A text parser for this format, accepting an optional rules file,
- -- assertion-checking flag, and file path for error messages,
- -- producing an exception-raising IO action that returns a journal
- -- or error message.
- ,rParser :: Maybe FilePath -> Bool -> FilePath -> Text -> ExceptT String IO Journal
-
- -- Experimental readers are never tried automatically.
- ,rExperimental :: Bool
- }
-
-instance Show Reader where show r = rFormat r ++ " reader"
-
-- | An account, with name, balances and links to parent/subaccounts
-- which let you walk up or down the account tree.
data Account = Account {
diff --git a/Hledger/Read.hs b/Hledger/Read.hs
index 75f2156..462f8ea 100644
--- a/Hledger/Read.hs
+++ b/Hledger/Read.hs
@@ -14,7 +14,6 @@ module Hledger.Read (
PrefixedFilePath,
defaultJournal,
defaultJournalPath,
- readJournalFilesWithOpts,
readJournalFiles,
readJournalFile,
requireJournalFileExists,
@@ -26,7 +25,6 @@ module Hledger.Read (
readJournal',
-- * Re-exported
- JournalReader.accountaliasp,
JournalReader.postingp,
module Hledger.Read.Common,
@@ -36,10 +34,10 @@ module Hledger.Read (
) where
-import Control.Applicative ((<|>))
import Control.Arrow (right)
import qualified Control.Exception as C
import Control.Monad.Except
+import Data.Default
import Data.List
import Data.Maybe
import Data.Ord
@@ -90,7 +88,7 @@ type PrefixedFilePath = FilePath
-- | Read the default journal file specified by the environment, or raise an error.
defaultJournal :: IO Journal
-defaultJournal = defaultJournalPath >>= readJournalFile Nothing Nothing True >>= either error' return
+defaultJournal = defaultJournalPath >>= readJournalFile def >>= either error' return
-- | Get the default journal file path specified by the environment.
-- Like ledger, we look first for the LEDGER_FILE environment
@@ -112,52 +110,6 @@ defaultJournalPath = do
home <- getHomeDirectory `C.catch` (\(_::C.IOException) -> return "")
return $ home </> journalDefaultFilename
--- | @readJournalFiles mformat mrulesfile assrt prefixedfiles@
---
--- Read a Journal from each specified file path and combine them into one.
--- Or, return the first error message.
---
--- Combining Journals means concatenating them, basically.
--- The parse state resets at the start of each file, which means that
--- directives & aliases do not cross file boundaries.
--- (The final parse state saved in the Journal does span all files, however.)
---
--- As with readJournalFile,
--- file paths can optionally have a READER: prefix,
--- and the @mformat@, @mrulesfile, and @assrt@ arguments are supported
--- (and these are applied to all files).
---
-readJournalFiles :: Maybe StorageFormat -> Maybe FilePath -> Bool -> [PrefixedFilePath] -> IO (Either String Journal)
-readJournalFiles mformat mrulesfile assrt prefixedfiles = do
- (right mconcat1 . sequence)
- <$> mapM (readJournalFile mformat mrulesfile assrt) prefixedfiles
- where mconcat1 :: Monoid t => [t] -> t
- mconcat1 [] = mempty
- mconcat1 x = foldr1 mappend x
-
--- | @readJournalFile mformat mrulesfile assrt prefixedfile@
---
--- Read a Journal from this file, or from stdin if the file path is -,
--- or return an error message. The file path can have a READER: prefix.
---
--- The reader (data format) is chosen based on (in priority order):
--- the @mformat@ argument;
--- the file path's READER: prefix, if any;
--- a recognised file name extension (in readJournal);
--- if none of these identify a known reader, all built-in readers are tried in turn.
---
--- A CSV conversion rules file (@mrulesfiles@) can be specified to help convert CSV data.
---
--- Optionally, any balance assertions in the journal can be checked (@assrt@).
---
-readJournalFile :: Maybe StorageFormat -> Maybe FilePath -> Bool -> PrefixedFilePath -> IO (Either String Journal)
-readJournalFile mformat mrulesfile assrt prefixedfile = do
- let
- (mprefixformat, f) = splitReaderPrefix prefixedfile
- mfmt = mformat <|> mprefixformat
- requireJournalFileExists f
- readFileOrStdinPortably f >>= readJournal mfmt mrulesfile assrt (Just f)
-
-- | If a filepath is prefixed by one of the reader names and a colon,
-- split that off. Eg "csv:-" -> (Just "csv", "-").
splitReaderPrefix :: PrefixedFilePath -> (Maybe String, FilePath)
@@ -195,7 +147,7 @@ newJournalContent = do
-- | Read a Journal from the given text trying all readers in turn, or throw an error.
readJournal' :: Text -> IO Journal
-readJournal' t = readJournal Nothing Nothing True Nothing t >>= either error' return
+readJournal' t = readJournal def Nothing t >>= either error' return
tests_readJournal' = [
"readJournal' parses sample journal" ~: do
@@ -203,28 +155,6 @@ tests_readJournal' = [
assertBool "" True
]
--- | @readJournal mformat mrulesfile assrt mfile txt@
---
--- Read a Journal from some text, or return an error message.
---
--- The reader (data format) is chosen based on (in priority order):
--- the @mformat@ argument;
--- a recognised file name extension in @mfile@ (if provided).
--- If none of these identify a known reader, all built-in readers are tried in turn
--- (returning the first one's error message if none of them succeed).
---
--- A CSV conversion rules file (@mrulesfiles@) can be specified to help convert CSV data.
---
--- Optionally, any balance assertions in the journal can be checked (@assrt@).
---
-readJournal :: Maybe StorageFormat -> Maybe FilePath -> Bool -> Maybe FilePath -> Text -> IO (Either String Journal)
-readJournal mformat mrulesfile assrt mfile txt =
- let
- stablereaders = filter (not.rExperimental) readers
- rs = maybe stablereaders (:[]) $ findReader mformat mfile
- in
- tryReaders rs mrulesfile assrt mfile txt
-
-- | @findReader mformat mpath@
--
-- Find the reader named by @mformat@, if provided.
@@ -241,43 +171,41 @@ findReader Nothing (Just path) =
(prefix,path') = splitReaderPrefix path
ext = drop 1 $ takeExtension path'
--- | @tryReaders readers mrulesfile assrt path t@
+-- | Read a Journal from each specified file path and combine them into one.
+-- Or, return the first error message.
--
--- Try to parse the given text to a Journal using each reader in turn,
--- returning the first success, or if all of them fail, the first error message.
-tryReaders :: [Reader] -> Maybe FilePath -> Bool -> Maybe FilePath -> Text -> IO (Either String Journal)
-tryReaders readers mrulesfile assrt path t = 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) mrulesfile assrt path') t
- 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)" 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)
+-- Combining Journals means concatenating them, basically.
+-- The parse state resets at the start of each file, which means that
+-- directives & aliases do not affect subsequent sibling or parent files.
+-- They do affect included child files though.
+-- Also the final parse state saved in the Journal does span all files.
+readJournalFiles :: InputOpts -> [FilePath] -> IO (Either String Journal)
+readJournalFiles iopts =
+ (right mconcat1 . sequence <$>) . mapM (readJournalFile 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
+-- | Read a Journal from this file, or from stdin if the file path is -,
+-- or return an error message. The file path can have a READER: prefix.
+--
+-- The reader (data format) to use is determined from (in priority order):
+-- the @mformat_@ specified in the input options, if any;
+-- the file path's READER: prefix, if any;
+-- a recognised file name extension.
+-- if none of these identify a known reader, all built-in readers are tried in turn.
+--
+-- The input options can also configure balance assertion checking, automated posting
+-- generation, a rules file for converting CSV data, etc.
+readJournalFile :: InputOpts -> PrefixedFilePath -> IO (Either String Journal)
+readJournalFile iopts prefixedfile = do
let
(mfmt, f) = splitReaderPrefix prefixedfile
iopts' = iopts{mformat_=firstJust [mfmt, mformat_ iopts]}
requireJournalFileExists f
t <- readFileOrStdinPortably f
- ej <- readJournalWithOpts iopts' (Just f) t
+ ej <- readJournal iopts' (Just f) t
case ej of
Left e -> return $ Left e
Right j | new_ iopts -> do
@@ -342,21 +270,40 @@ journalFilterSinceLatestDates ds@(d:_) j = (j', ds')
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
+-- | @readJournal iopts mfile txt@
+--
+-- Read a Journal from some text, or return an error message.
+--
+-- The reader (data format) is chosen based on a recognised file name extension in @mfile@ (if provided).
+-- If it does not identify a known reader, all built-in readers are tried in turn
+-- (returning the first one's error message if none of them succeed).
+--
+-- Input ioptions (@iopts@) specify CSV conversion rules file to help convert CSV data,
+-- enable or disable balance assertion checking and automated posting generation.
+--
+readJournal :: InputOpts -> Maybe FilePath -> Text -> IO (Either String Journal)
+readJournal iopts mfile txt =
+ tryReaders 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
+-- | @tryReaders iopts readers path t@
+--
+-- Try to parse the given text to a Journal using each reader in turn,
+-- returning the first success, or if all of them fail, the first error message.
+--
+-- Input ioptions (@iopts@) specify CSV conversion rules file to help convert CSV data,
+-- enable or disable balance assertion checking and automated posting generation.
+--
+tryReaders :: InputOpts -> Maybe FilePath -> [Reader] -> Text -> IO (Either String Journal)
+tryReaders 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
+ result <- (runExceptT . (rParser r) 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
@@ -408,7 +355,7 @@ tests_Hledger_Read = TestList $
"journal" ~: do
r <- runExceptT $ parseWithState mempty JournalReader.journalp ""
assertBool "journalp should parse an empty file" (isRight $ r)
- jE <- readJournal Nothing Nothing True Nothing "" -- don't know how to get it from journal
+ jE <- readJournal def Nothing "" -- don't know how to get it from journal
either error' (assertBool "journalp parsing an empty file should give an empty journal" . null . jtxns) jE
]
diff --git a/Hledger/Read/Common.hs b/Hledger/Read/Common.hs
index caf5fd0..1663602 100644
--- a/Hledger/Read/Common.hs
+++ b/Hledger/Read/Common.hs
@@ -47,6 +47,28 @@ import Text.Megaparsec.Compat
import Hledger.Data
import Hledger.Utils
+import qualified Hledger.Query as Q (Query(Any))
+
+-- | A hledger journal reader is a triple of storage format name, a
+-- detector of that format, and a parser from that format to Journal.
+data Reader = Reader {
+
+ -- The canonical name of the format handled by this reader
+ rFormat :: StorageFormat
+
+ -- The file extensions recognised as containing this format
+ ,rExtensions :: [String]
+
+ -- A text parser for this format, accepting input options, file
+ -- path for error messages and file contents, producing an exception-raising IO
+ -- action that returns a journal or error message.
+ ,rParser :: InputOpts -> FilePath -> Text -> ExceptT String IO Journal
+
+ -- Experimental readers are never tried automatically.
+ ,rExperimental :: Bool
+ }
+
+instance Show Reader where show r = rFormat r ++ " reader"
-- $setup
@@ -63,12 +85,13 @@ data InputOpts = InputOpts {
,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
+ ,auto_ :: Bool -- ^ generate automatic postings when journal is parsed
} deriving (Show, Data) --, Typeable)
instance Default InputOpts where def = definputopts
definputopts :: InputOpts
-definputopts = InputOpts def def def def def def True def
+definputopts = InputOpts def def def def def def True def def
rawOptsToInputOpts :: RawOpts -> InputOpts
rawOptsToInputOpts rawopts = InputOpts{
@@ -81,6 +104,7 @@ rawOptsToInputOpts rawopts = InputOpts{
,new_ = boolopt "new" rawopts
,new_save_ = True
,pivot_ = stringopt "pivot" rawopts
+ ,auto_ = boolopt "auto" rawopts
}
--- * parsing utils
@@ -115,27 +139,40 @@ journalSourcePos p p' = JournalSourcePos (sourceName p) (fromIntegral . unPos $
| otherwise = unPos $ sourceLine p' -- might be at end of file withat last new-line
--- | Given a megaparsec ParsedJournal parser, balance assertion flag, file
+-- | Generate Automatic postings and add them to the current journal.
+generateAutomaticPostings :: Journal -> Journal
+generateAutomaticPostings j = j { jtxns = map modifier $ jtxns j }
+ where
+ modifier = foldr (flip (.) . runModifierTransaction') id mtxns
+ runModifierTransaction' = fmap txnTieKnot . runModifierTransaction Q.Any
+ mtxns = jmodifiertxns j
+
+-- | Given a megaparsec ParsedJournal parser, input options, file
-- path and file content: parse and post-process a Journal, or give an error.
-parseAndFinaliseJournal :: ErroringJournalParser IO ParsedJournal -> Bool
- -> FilePath -> Text -> ExceptT String IO Journal
-parseAndFinaliseJournal parser assrt f txt = do
+parseAndFinaliseJournal :: ErroringJournalParser IO ParsedJournal -> InputOpts
+ -> FilePath -> Text -> ExceptT String IO Journal
+parseAndFinaliseJournal parser iopts f txt = do
t <- liftIO getClockTime
y <- liftIO getCurrentYear
ep <- runParserT (evalStateT parser nulljournal {jparsedefaultyear=Just y}) f txt
case ep of
- Right pj -> case journalFinalise t f txt assrt pj of
+ Right pj ->
+ let pj' = if auto_ iopts then generateAutomaticPostings pj else pj in
+ case journalFinalise t f txt (not $ ignore_assertions_ iopts) pj' of
Right j -> return j
Left e -> throwError e
Left e -> throwError $ parseErrorPretty e
-parseAndFinaliseJournal' :: JournalParser Identity ParsedJournal -> Bool -> FilePath -> Text -> ExceptT String IO Journal
-parseAndFinaliseJournal' parser assrt f txt = do
+parseAndFinaliseJournal' :: JournalParser Identity ParsedJournal -> InputOpts
+ -> FilePath -> Text -> ExceptT String IO Journal
+parseAndFinaliseJournal' parser iopts f txt = do
t <- liftIO getClockTime
y <- liftIO getCurrentYear
let ep = runParser (evalStateT parser nulljournal {jparsedefaultyear=Just y}) f txt
case ep of
- Right pj -> case journalFinalise t f txt assrt pj of
+ Right pj ->
+ let pj' = if auto_ iopts then generateAutomaticPostings pj else pj in
+ case journalFinalise t f txt (not $ ignore_assertions_ iopts) pj' of
Right j -> return j
Left e -> throwError e
Left e -> throwError $ parseErrorPretty e
diff --git a/Hledger/Read/CsvReader.hs b/Hledger/Read/CsvReader.hs
index d35bc95..a4363da 100644
--- a/Hledger/Read/CsvReader.hs
+++ b/Hledger/Read/CsvReader.hs
@@ -60,7 +60,7 @@ import Text.Printf (printf)
import Hledger.Data
import Hledger.Utils.UTF8IOCompat (getContents)
import Hledger.Utils
-import Hledger.Read.Common (amountp, statusp, genericSourcePos)
+import Hledger.Read.Common (Reader(..),InputOpts(..),amountp, statusp, genericSourcePos)
reader :: Reader
@@ -73,8 +73,9 @@ reader = Reader
-- | Parse and post-process a "Journal" from CSV data, or give an error.
-- XXX currently ignores the string and reads from the file path
-parse :: Maybe FilePath -> Bool -> FilePath -> Text -> ExceptT String IO Journal
-parse rulesfile _ f t = do
+parse :: InputOpts -> FilePath -> Text -> ExceptT String IO Journal
+parse iopts f t = do
+ let rulesfile = mrules_file_ iopts
r <- liftIO $ readJournalFromCsv rulesfile f t
case r of Left e -> throwError e
Right j -> return $ journalNumberAndTieTransactions j
@@ -725,10 +726,26 @@ getAmountStr rules record =
type CsvAmountString = String
-- | Canonicalise the sign in a CSV amount string.
--- Such strings can be parenthesized, which is equivalent to having a minus sign.
--- Also they can end up with a double minus sign, which cancels out.
+-- Such strings can have a minus sign, negating parentheses,
+-- or any two of these (which cancels out).
+--
+-- >>> simplifySign "1"
+-- "1"
+-- >>> simplifySign "-1"
+-- "-1"
+-- >>> simplifySign "(1)"
+-- "-1"
+-- >>> simplifySign "--1"
+-- "1"
+-- >>> simplifySign "-(1)"
+-- "1"
+-- >>> simplifySign "(-1)"
+-- "1"
+-- >>> simplifySign "((1))"
+-- "1"
simplifySign :: CsvAmountString -> CsvAmountString
simplifySign ('(':s) | lastMay s == Just ')' = simplifySign $ negateStr $ init s
+simplifySign ('-':'(':s) | lastMay s == Just ')' = simplifySign $ init s
simplifySign ('-':'-':s) = s
simplifySign s = s
diff --git a/Hledger/Read/JournalReader.hs b/Hledger/Read/JournalReader.hs
index 5effefd..58641a8 100644
--- a/Hledger/Read/JournalReader.hs
+++ b/Hledger/Read/JournalReader.hs
@@ -63,8 +63,7 @@ module Hledger.Read.JournalReader (
-- numberp,
statusp,
emptyorcommentlinep,
- followingcommentp,
- accountaliasp
+ followingcommentp
-- * Tests
,tests_Hledger_Read_JournalReader
@@ -119,8 +118,18 @@ reader = Reader
-- | Parse and post-process a "Journal" from hledger's journal file
-- format, or give an error.
-parse :: Maybe FilePath -> Bool -> FilePath -> Text -> ExceptT String IO Journal
-parse _ = parseAndFinaliseJournal journalp
+parse :: InputOpts -> FilePath -> Text -> ExceptT String IO Journal
+parse iopts = parseAndFinaliseJournal journalp' iopts
+ where
+ journalp' = do
+ -- reverse parsed aliases to ensure that they are applied in order given on commandline
+ mapM_ addAccountAlias (reverse $ aliasesFromOpts iopts)
+ journalp
+
+-- | Get the account name aliases from options, if any.
+aliasesFromOpts :: InputOpts -> [AccountAlias]
+aliasesFromOpts = map (\a -> fromparse $ runParser accountaliasp ("--alias "++quoteIfNeeded a) $ T.pack a)
+ . aliases_
--- * parsers
--- ** journal
diff --git a/Hledger/Read/TimeclockReader.hs b/Hledger/Read/TimeclockReader.hs
index 6dc993c..07a6169 100644
--- a/Hledger/Read/TimeclockReader.hs
+++ b/Hledger/Read/TimeclockReader.hs
@@ -79,8 +79,8 @@ reader = Reader
-- | Parse and post-process a "Journal" from timeclock.el's timeclock
-- format, saving the provided file path and the current time, or give an
-- error.
-parse :: Maybe FilePath -> Bool -> FilePath -> Text -> ExceptT String IO Journal
-parse _ = parseAndFinaliseJournal timeclockfilep
+parse :: InputOpts -> FilePath -> Text -> ExceptT String IO Journal
+parse = parseAndFinaliseJournal timeclockfilep
timeclockfilep :: ErroringJournalParser IO ParsedJournal
timeclockfilep = do many timeclockitemp
diff --git a/Hledger/Read/TimedotReader.hs b/Hledger/Read/TimedotReader.hs
index 1ad99db..375fe94 100644
--- a/Hledger/Read/TimedotReader.hs
+++ b/Hledger/Read/TimedotReader.hs
@@ -65,8 +65,8 @@ reader = Reader
}
-- | Parse and post-process a "Journal" from the timedot format, or give an error.
-parse :: Maybe FilePath -> Bool -> FilePath -> Text -> ExceptT String IO Journal
-parse _ = parseAndFinaliseJournal timedotfilep
+parse :: InputOpts -> FilePath -> Text -> ExceptT String IO Journal
+parse = parseAndFinaliseJournal timedotfilep
timedotfilep :: JournalParser m ParsedJournal
timedotfilep = do many timedotfileitemp
diff --git a/Hledger/Reports.hs b/Hledger/Reports.hs
index 4a91314..a7b33ed 100644
--- a/Hledger/Reports.hs
+++ b/Hledger/Reports.hs
@@ -10,11 +10,13 @@ javascript, or whatever.
module Hledger.Reports (
module Hledger.Reports.ReportOptions,
+ module Hledger.Reports.ReportTypes,
module Hledger.Reports.EntriesReport,
module Hledger.Reports.PostingsReport,
module Hledger.Reports.TransactionsReports,
module Hledger.Reports.BalanceReport,
module Hledger.Reports.MultiBalanceReports,
+ module Hledger.Reports.BudgetReport,
-- module Hledger.Reports.BalanceHistoryReport,
-- * Tests
@@ -25,11 +27,13 @@ where
import Test.HUnit
import Hledger.Reports.ReportOptions
+import Hledger.Reports.ReportTypes
import Hledger.Reports.EntriesReport
import Hledger.Reports.PostingsReport
import Hledger.Reports.TransactionsReports
import Hledger.Reports.BalanceReport
import Hledger.Reports.MultiBalanceReports
+import Hledger.Reports.BudgetReport
-- import Hledger.Reports.BalanceHistoryReport
tests_Hledger_Reports :: Test
@@ -40,5 +44,6 @@ tests_Hledger_Reports = TestList $
tests_Hledger_Reports_EntriesReport,
tests_Hledger_Reports_PostingsReport,
tests_Hledger_Reports_BalanceReport,
- tests_Hledger_Reports_MultiBalanceReport
+ tests_Hledger_Reports_MultiBalanceReport,
+ tests_Hledger_Reports_BudgetReport
]
diff --git a/Hledger/Reports/BalanceReport.hs b/Hledger/Reports/BalanceReport.hs
index 269aeb6..9e6a732 100644
--- a/Hledger/Reports/BalanceReport.hs
+++ b/Hledger/Reports/BalanceReport.hs
@@ -350,7 +350,7 @@ tests_balanceReport =
]
,"accounts report with cost basis" ~: do
- j <- (readJournal Nothing Nothing Nothing $ unlines
+ j <- (readJournal def Nothing $ unlines
[""
,"2008/1/1 test "
," a:b 10h @ $50"
diff --git a/Hledger/Reports/BudgetReport.hs b/Hledger/Reports/BudgetReport.hs
new file mode 100644
index 0000000..83eb0bb
--- /dev/null
+++ b/Hledger/Reports/BudgetReport.hs
@@ -0,0 +1,361 @@
+{- |
+-}
+
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Hledger.Reports.BudgetReport
+where
+
+import Data.Decimal
+import Data.List
+import Data.Maybe
+#if !(MIN_VERSION_base(4,11,0))
+import Data.Monoid ((<>))
+#endif
+import Data.Ord
+import Data.Time.Calendar
+import Safe
+import Test.HUnit
+--import Data.List
+--import Data.Maybe
+import qualified Data.Map as Map
+import Data.Map (Map)
+import qualified Data.Text as T
+--import qualified Data.Text.Lazy as TL
+--import System.Console.CmdArgs.Explicit as C
+--import Lucid as L
+--import Text.CSV
+--import Test.HUnit
+import Text.Printf (printf)
+import Text.Tabular as T
+--import Text.Tabular.AsciiWide
+
+import Hledger.Data
+--import Hledger.Query
+import Hledger.Utils
+--import Hledger.Read (mamountp')
+import Hledger.Reports.ReportOptions
+import Hledger.Reports.ReportTypes
+import Hledger.Reports.MultiBalanceReports
+
+
+--type MultiBalanceReportRow = (AccountName, AccountName, Int, [MixedAmount], MixedAmount, MixedAmount)
+--type MultiBalanceReportTotals = ([MixedAmount], MixedAmount, MixedAmount) -- (Totals list, sum of totals, average of totals)
+
+--type PeriodicReportRow a =
+-- ( AccountName -- ^ A full account name.
+-- , [a] -- ^ The data value for each subperiod.
+-- , a -- ^ The total of this row's values.
+-- , a -- ^ The average of this row's values.
+-- )
+
+type BudgetGoal = Change
+type BudgetTotal = Total
+type BudgetAverage = Average
+
+-- | A budget report tracks expected and actual changes per account and subperiod.
+type BudgetReport = PeriodicReport (Maybe Change, Maybe BudgetGoal)
+
+-- | Calculate budget goals from all periodic transactions,
+-- actual balance changes from the regular transactions,
+-- and compare these to get a 'BudgetReport'.
+-- Unbudgeted accounts may be hidden or renamed (see budgetRollup).
+budgetReport :: ReportOpts -> Bool -> Bool -> DateSpan -> Day -> Journal -> BudgetReport
+budgetReport ropts assrt showunbudgeted reportspan d j =
+ let
+ q = queryFromOpts d ropts
+ budgetedaccts =
+ dbg2 "budgetedacctsinperiod" $
+ accountNamesFromPostings $
+ concatMap tpostings $
+ concatMap (flip runPeriodicTransaction reportspan) $
+ jperiodictxns j
+ actualj = dbg1 "actualj" $ budgetRollUp budgetedaccts showunbudgeted j
+ budgetj = dbg1 "budgetj" $ budgetJournal assrt ropts reportspan j
+ actualreport@(MultiBalanceReport (actualspans, _, _)) = dbg1 "actualreport" $ multiBalanceReport ropts q actualj
+ budgetgoalreport@(MultiBalanceReport (_, budgetgoalitems, budgetgoaltotals)) = dbg1 "budgetgoalreport" $ multiBalanceReport ropts q budgetj
+ budgetgoalreport'
+ -- If no interval is specified:
+ -- budgetgoalreport's span might be shorter actualreport's due to periodic txns;
+ -- it should be safe to replace it with the latter, so they combine well.
+ | interval_ ropts == NoInterval = MultiBalanceReport (actualspans, budgetgoalitems, budgetgoaltotals)
+ | otherwise = budgetgoalreport
+ in
+ dbg1 "budgetreport" $ combineBudgetAndActual budgetgoalreport' actualreport
+
+-- | Use all periodic transactions in the journal to generate
+-- budget transactions in the specified report period.
+-- Budget transactions are similar to forecast transactions except
+-- their purpose is to set goal amounts (of change) per account and period.
+budgetJournal :: Bool -> ReportOpts -> DateSpan -> Journal -> Journal
+budgetJournal assrt _ropts reportspan j =
+ either error' id $ journalBalanceTransactions assrt j{ jtxns = budgetts }
+ where
+ budgetspan = dbg2 "budgetspan" $ reportspan
+ budgetts =
+ dbg1 "budgetts" $
+ [makeBudgetTxn t
+ | pt <- jperiodictxns j
+ , t <- runPeriodicTransaction pt budgetspan
+ ]
+ makeBudgetTxn t = txnTieKnot $ t { tdescription = T.pack "Budget transaction" }
+
+-- | Adjust a journal's account names for budget reporting, in two ways:
+--
+-- 1. accounts with no budget goal anywhere in their ancestry are moved
+-- under the "unbudgeted" top level account.
+--
+-- 2. subaccounts with no budget goal are merged with their closest parent account
+-- with a budget goal, so that only budgeted accounts are shown.
+-- This can be disabled by --show-unbudgeted.
+--
+budgetRollUp :: [AccountName] -> Bool -> Journal -> Journal
+budgetRollUp budgetedaccts showunbudgeted j = j { jtxns = remapTxn <$> jtxns j }
+ where
+ remapTxn = mapPostings (map remapPosting)
+ where
+ mapPostings f t = txnTieKnot $ t { tpostings = f $ tpostings t }
+ remapPosting p = p { paccount = remapAccount $ paccount p, porigin = Just . fromMaybe p $ porigin p }
+ where
+ remapAccount a
+ | hasbudget = a
+ | hasbudgetedparent = if showunbudgeted then a else budgetedparent
+ | otherwise = if showunbudgeted then u <> acctsep <> a else u
+ where
+ hasbudget = a `elem` budgetedaccts
+ hasbudgetedparent = not $ T.null budgetedparent
+ budgetedparent = headDef "" $ filter (`elem` budgetedaccts) $ parentAccountNames a
+ u = unbudgetedAccountName
+
+-- | Combine a per-account-and-subperiod report of budget goals, and one
+-- of actual change amounts, into a budget performance report.
+-- The two reports should have the same report interval, but need not
+-- have exactly the same account rows or date columns.
+-- (Cells in the combined budget report can be missing a budget goal,
+-- an actual amount, or both.) The combined report will include:
+--
+-- - consecutive subperiods at the same interval as the two reports,
+-- spanning the period of both reports
+--
+-- - all accounts mentioned in either report, sorted by account code or
+-- account name or amount as appropriate.
+--
+combineBudgetAndActual :: MultiBalanceReport -> MultiBalanceReport -> BudgetReport
+combineBudgetAndActual
+ (MultiBalanceReport (budgetperiods, budgetrows, (budgettots, budgetgrandtot, budgetgrandavg)))
+ (MultiBalanceReport (actualperiods, actualrows, (actualtots, actualgrandtot, actualgrandavg))) =
+ let
+ periods = nub $ sort $ filter (/= nulldatespan) $ budgetperiods ++ actualperiods
+
+ -- first, combine any corresponding budget goals with actual changes
+ rows1 =
+ [ (acct, treeacct, treeindent, amtandgoals, totamtandgoal, avgamtandgoal)
+ | (acct, treeacct, treeindent, actualamts, actualtot, actualavg) <- actualrows
+ , let mbudgetgoals = Map.lookup acct budgetGoalsByAcct :: Maybe ([BudgetGoal], BudgetTotal, BudgetAverage)
+ , let budgetmamts = maybe (replicate (length periods) Nothing) (map Just . first3) mbudgetgoals :: [Maybe BudgetGoal]
+ , let mbudgettot = maybe Nothing (Just . second3) mbudgetgoals :: Maybe BudgetTotal
+ , let mbudgetavg = maybe Nothing (Just . third3) mbudgetgoals :: Maybe BudgetAverage
+ , let acctBudgetByPeriod = Map.fromList [ (p,budgetamt) | (p, Just budgetamt) <- zip budgetperiods budgetmamts ] :: Map DateSpan BudgetGoal
+ , let acctActualByPeriod = Map.fromList [ (p,actualamt) | (p, Just actualamt) <- zip actualperiods (map Just actualamts) ] :: Map DateSpan Change
+ , let amtandgoals = [ (Map.lookup p acctActualByPeriod, Map.lookup p acctBudgetByPeriod) | p <- periods ] :: [(Maybe Change, Maybe BudgetGoal)]
+ , let totamtandgoal = (Just actualtot, mbudgettot)
+ , let avgamtandgoal = (Just actualavg, mbudgetavg)
+ ]
+ where
+ budgetGoalsByAcct :: Map AccountName ([BudgetGoal], BudgetTotal, BudgetAverage) =
+ Map.fromList [ (acct, (amts, tot, avg)) | (acct, _, _, amts, tot, avg) <- budgetrows ]
+
+ -- next, make rows for budget goals with no actual changes
+ rows2 =
+ [ (acct, treeacct, treeindent, amtandgoals, totamtandgoal, avgamtandgoal)
+ | (acct, treeacct, treeindent, budgetgoals, budgettot, budgetavg) <- budgetrows
+ , not $ acct `elem` acctsdone
+ , let acctBudgetByPeriod = Map.fromList $ zip budgetperiods budgetgoals :: Map DateSpan BudgetGoal
+ , let amtandgoals = [ (Nothing, Map.lookup p acctBudgetByPeriod) | p <- periods ] :: [(Maybe Change, Maybe BudgetGoal)]
+ , let totamtandgoal = (Nothing, Just budgettot)
+ , let avgamtandgoal = (Nothing, Just budgetavg)
+ ]
+ where
+ acctsdone = map first6 rows1
+
+ -- combine and re-sort rows
+ -- TODO: respect hierarchy in tree mode
+ -- TODO: respect --sort-amount
+ -- TODO: add --sort-budget to sort by budget goal amount
+ rows :: [PeriodicReportRow (Maybe Change, Maybe BudgetGoal)] =
+ sortBy (comparing first6) $ rows1 ++ rows2
+-- massive duplication from multiBalanceReport to handle tree mode sorting ?
+-- dbg1 "sorteditems" $
+-- sortitems items
+-- where
+-- sortitems
+-- | sort_amount_ opts && accountlistmode_ opts == ALTree = sortTreeMultiBalanceReportRowsByAmount
+-- | sort_amount_ opts = sortFlatMultiBalanceReportRowsByAmount
+-- | not (sort_amount_ opts) && accountlistmode_ opts == ALTree = sortTreeMultiBalanceReportRowsByAccountCodeAndName
+-- | otherwise = sortFlatMultiBalanceReportRowsByAccountCodeAndName
+-- where
+-- -- Sort the report rows, representing a flat account list, by row total.
+-- sortFlatMultiBalanceReportRowsByAmount = sortBy (maybeflip $ comparing fifth6)
+-- where
+-- maybeflip = if normalbalance_ opts == Just NormallyNegative 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 NormallyPositive $ normalbalance_ opts) accounttreewithbals
+-- sortedaccounts = drop 1 $ flattenAccounts sortedaccounttree
+-- -- dropped the root account, also ignore any parent accounts not in rows
+-- sortedrows = concatMap (\a -> maybe [] (:[]) $ lookup (aname a) anamesandrows) sortedaccounts
+--
+-- -- Sort the report rows by account code if any, with the empty account code coming last, then account name.
+-- sortFlatMultiBalanceReportRowsByAccountCodeAndName = sortBy (comparing acodeandname)
+-- where
+-- acodeandname r = (acode', aname)
+-- where
+-- aname = first6 r
+-- macode = fromMaybe Nothing $ lookup aname $ jaccounts j
+-- acode' = fromMaybe maxBound macode
+--
+-- -- Sort the report rows, representing a tree of accounts, by account code and then account name at each level.
+-- -- Convert a tree of account names, look up the account codes, sort and flatten the tree, reorder the rows.
+-- sortTreeMultiBalanceReportRowsByAccountCodeAndName rows = sortedrows
+-- where
+-- anamesandrows = [(first6 r, r) | r <- rows]
+-- anames = map fst anamesandrows
+-- nametree = treeFromPaths $ map expandAccountName anames
+-- accounttree = nameTreeToAccount "root" nametree
+-- accounttreewithcodes = mapAccounts (accountSetCodeFrom j) accounttree
+-- sortedaccounttree = sortAccountTreeByAccountCodeAndName accounttreewithcodes
+-- sortedaccounts = drop 1 $ flattenAccounts sortedaccounttree
+-- -- dropped the root account, also ignore any parent accounts not in rows
+-- sortedrows = concatMap (\a -> maybe [] (:[]) $ lookup (aname a) anamesandrows) sortedaccounts
+--
+
+ -- TODO: grand total & average shows 0% when there are no actual amounts, inconsistent with other cells
+ totalrow =
+ ( ""
+ , ""
+ , 0
+ , [ (Map.lookup p totActualByPeriod, Map.lookup p totBudgetByPeriod) | p <- periods ] :: [(Maybe Total, Maybe BudgetTotal)]
+ , ( Just actualgrandtot, Just budgetgrandtot ) :: (Maybe Total, Maybe BudgetTotal)
+ , ( Just actualgrandavg, Just budgetgrandavg ) :: (Maybe Total, Maybe BudgetTotal)
+ )
+ where
+ totBudgetByPeriod = Map.fromList $ zip budgetperiods budgettots :: Map DateSpan BudgetTotal
+ totActualByPeriod = Map.fromList $ zip actualperiods actualtots :: Map DateSpan Change
+
+ in
+ PeriodicReport
+ ( periods
+ , rows
+ , totalrow
+ )
+
+-- | Figure out the overall period of a BudgetReport.
+budgetReportSpan :: BudgetReport -> DateSpan
+budgetReportSpan (PeriodicReport ([], _, _)) = DateSpan Nothing Nothing
+budgetReportSpan (PeriodicReport (spans, _, _)) = DateSpan (spanStart $ head spans) (spanEnd $ last spans)
+
+-- | Render a budget report as plain text suitable for console output.
+budgetReportAsText :: ReportOpts -> BudgetReport -> String
+budgetReportAsText ropts budgetr =
+ printf "Budget performance in %s:\n\n" (showDateSpan $ budgetReportSpan budgetr)
+ ++
+ tableAsText ropts showcell (budgetReportAsTable ropts budgetr)
+ where
+ -- XXX lay out actual, percentage and/or goal in the single table cell for now, should probably use separate cells
+ showcell :: (Maybe Change, Maybe BudgetGoal) -> String
+ showcell (mactual, mbudget) = actualstr ++ " " ++ budgetstr
+ where
+ actualwidth = 7
+ percentwidth = 4
+ budgetwidth = 5
+ actual = fromMaybe 0 mactual
+ actualstr = printf ("%"++show actualwidth++"s") (showamt actual)
+ budgetstr = case mbudget of
+ Nothing -> replicate (percentwidth + 7 + budgetwidth) ' '
+ Just budget ->
+ case percentage actual budget of
+ Just pct ->
+ printf ("[%"++show percentwidth++"s%% of %"++show budgetwidth++"s]")
+ (show $ roundTo 0 pct) (showbudgetamt budget)
+ Nothing ->
+ printf ("["++replicate (percentwidth+5) ' '++"%"++show budgetwidth++"s]")
+ (showbudgetamt budget)
+
+ -- | Calculate the percentage of actual change to budget goal to show, if any.
+ -- Both amounts are converted to cost, if possible, before comparing.
+ -- A percentage will not be shown if:
+ -- - actual or goal are not the same, single, commodity
+ -- - the goal is zero
+ percentage :: Change -> BudgetGoal -> Maybe Percentage
+ percentage actual budget =
+ case (toCost actual, toCost budget) of
+ (Mixed [a], Mixed [b]) | (acommodity a == acommodity b || isZeroAmount a) && not (isZeroAmount b)
+ -> Just $ 100 * aquantity a / aquantity b
+ _ -> Nothing
+ where
+ toCost = normaliseMixedAmount . costOfMixedAmount
+
+ showamt :: MixedAmount -> String
+ showamt | color_ ropts = cshowMixedAmountOneLineWithoutPrice
+ | otherwise = showMixedAmountOneLineWithoutPrice
+
+ -- don't show the budget amount in color, it messes up alignment
+ showbudgetamt = showMixedAmountOneLineWithoutPrice
+
+-- | Build a 'Table' from a multi-column balance report.
+budgetReportAsTable :: ReportOpts -> BudgetReport -> Table String String (Maybe MixedAmount, Maybe MixedAmount)
+budgetReportAsTable
+ ropts
+ (PeriodicReport
+ ( periods
+ , rows
+ , (_, _, _, coltots, grandtot, grandavg)
+ )) =
+ addtotalrow $
+ Table
+ (T.Group NoLine $ map Header accts)
+ (T.Group NoLine $ map Header colheadings)
+ (map rowvals rows)
+ where
+ colheadings = map showDateSpanMonthAbbrev periods
+ ++ (if row_total_ ropts then [" Total"] else [])
+ ++ (if average_ ropts then ["Average"] else [])
+ accts = map renderacct rows
+ renderacct (a,a',i,_,_,_)
+ | tree_ ropts = replicate ((i-1)*2) ' ' ++ T.unpack a'
+ | otherwise = T.unpack $ maybeAccountNameDrop ropts a
+ rowvals (_,_,_,as,rowtot,rowavg) = as
+ ++ (if row_total_ ropts then [rowtot] else [])
+ ++ (if average_ ropts then [rowavg] else [])
+ addtotalrow | no_total_ ropts = id
+ | otherwise = (+----+ (row "" $
+ coltots
+ ++ (if row_total_ ropts && not (null coltots) then [grandtot] else [])
+ ++ (if average_ ropts && not (null coltots) then [grandavg] else [])
+ ))
+
+-- XXX here for now
+-- | Drop leading components of accounts names as specified by --drop, but only in --flat mode.
+maybeAccountNameDrop :: ReportOpts -> AccountName -> AccountName
+maybeAccountNameDrop opts a | tree_ opts = a
+ | otherwise = accountNameDrop (drop_ opts) a
+
+tests_Hledger_Reports_BudgetReport :: Test
+tests_Hledger_Reports_BudgetReport = TestList [
+ ]
diff --git a/Hledger/Reports/MultiBalanceReports.hs b/Hledger/Reports/MultiBalanceReports.hs
index caabe89..721a8d7 100644
--- a/Hledger/Reports/MultiBalanceReports.hs
+++ b/Hledger/Reports/MultiBalanceReports.hs
@@ -12,6 +12,8 @@ module Hledger.Reports.MultiBalanceReports (
balanceReportFromMultiBalanceReport,
mbrNegate,
mbrNormaliseSign,
+ multiBalanceReportSpan,
+ tableAsText,
-- -- * Tests
tests_Hledger_Reports_MultiBalanceReport
@@ -24,6 +26,8 @@ import Data.Ord
import Data.Time.Calendar
import Safe
import Test.HUnit
+import Text.Tabular as T
+import Text.Tabular.AsciiWide
import Hledger.Data
import Hledger.Query
@@ -259,6 +263,11 @@ mbrNegate (MultiBalanceReport (colspans, rows, totalsrow)) =
mbrRowNegate (acct,shortacct,indent,amts,tot,avg) = (acct,shortacct,indent,map negate amts,-tot,-avg)
mbrTotalsRowNegate (amts,tot,avg) = (map negate amts,-tot,-avg)
+-- | Figure out the overall date span of a multicolumn balance report.
+multiBalanceReportSpan :: MultiBalanceReport -> DateSpan
+multiBalanceReportSpan (MultiBalanceReport ([], _, _)) = DateSpan Nothing Nothing
+multiBalanceReportSpan (MultiBalanceReport (colspans, _, _)) = DateSpan (spanStart $ head colspans) (spanEnd $ last colspans)
+
-- | Generates a simple non-columnar BalanceReport, but using multiBalanceReport,
-- in order to support --historical. Does not support tree-mode boring parent eliding.
-- If the normalbalance_ option is set, it adjusts the sorting and sign of amounts
@@ -322,6 +331,22 @@ tests_multiBalanceReport =
Mixed [usd0])
]
+-- common rendering helper, XXX here for now
+
+tableAsText :: ReportOpts -> (a -> String) -> Table String String a -> String
+tableAsText (ReportOpts{pretty_tables_ = pretty}) showcell =
+ unlines
+ . trimborder
+ . lines
+ . render pretty id id showcell
+ . align
+ where
+ trimborder = drop 1 . init . map (drop 1 . init)
+ align (Table l t d) = Table l' t d
+ where
+ acctswidth = maximum' $ map strWidth (headerContents l)
+ l' = padRightWide acctswidth <$> l
+
tests_Hledger_Reports_MultiBalanceReport :: Test
tests_Hledger_Reports_MultiBalanceReport = TestList
tests_multiBalanceReport
diff --git a/Hledger/Reports/ReportOptions.hs b/Hledger/Reports/ReportOptions.hs
index 440390e..39c05d2 100644
--- a/Hledger/Reports/ReportOptions.hs
+++ b/Hledger/Reports/ReportOptions.hs
@@ -115,7 +115,6 @@ data ReportOpts = ReportOpts {
-- normally positive for a more conventional display.
,color_ :: Bool
,forecast_ :: Bool
- ,auto_ :: Bool
} deriving (Show, Data, Typeable)
instance Default ReportOpts where def = defreportopts
@@ -148,7 +147,6 @@ defreportopts = ReportOpts
def
def
def
- def
rawOptsToReportOpts :: RawOpts -> IO ReportOpts
rawOptsToReportOpts rawopts = checkReportOpts <$> do
@@ -181,7 +179,6 @@ rawOptsToReportOpts rawopts = checkReportOpts <$> do
,pretty_tables_ = boolopt "pretty-tables" rawopts'
,color_ = color
,forecast_ = boolopt "forecast" rawopts'
- ,auto_ = boolopt "auto" rawopts'
}
-- | Do extra validation of raw option values, raising an error if there's a problem.
diff --git a/Hledger/Reports/ReportTypes.hs b/Hledger/Reports/ReportTypes.hs
new file mode 100644
index 0000000..625cccd
--- /dev/null
+++ b/Hledger/Reports/ReportTypes.hs
@@ -0,0 +1,40 @@
+{- |
+New common report types, used by the BudgetReport for now, perhaps all reports later.
+-}
+
+module Hledger.Reports.ReportTypes
+where
+
+import Data.Decimal
+import Hledger.Data
+
+type Percentage = Decimal
+
+type Change = MixedAmount -- ^ A change in balance during a certain period.
+type Balance = MixedAmount -- ^ An ending balance as of some date.
+type Total = MixedAmount -- ^ The sum of 'Change's in a report or a report row. Does not make sense for 'Balance's.
+type Average = MixedAmount -- ^ The average of 'Change's or 'Balance's in a report or report row.
+
+-- | A generic tabular report of some value, where each row corresponds to an account
+-- and each column is a date period. The column periods are usually consecutive subperiods
+-- formed by splitting the overall report period by some report interval (daily, weekly, etc.)
+-- Depending on the value type, this can be a report of balance changes, ending balances,
+-- budget performance, etc. Successor to MultiBalanceReport.
+data PeriodicReport a =
+ PeriodicReport
+ ( [DateSpan] -- The subperiods formed by splitting the overall report period by the report interval.
+ -- For ending-balance reports, only the end date is significant.
+ -- Usually displayed as report columns.
+ , [PeriodicReportRow a] -- One row per account in the report.
+ , PeriodicReportRow a -- The grand totals row. The account name in this row is always empty.
+ )
+ deriving (Show)
+
+type PeriodicReportRow a =
+ ( AccountName -- A full account name.
+ , AccountName -- Shortened form of the account name to display in tree mode. Usually the leaf name, possibly with parent accounts prefixed.
+ , Int -- Indent level for displaying this account name in tree mode. 0, 1, 2...
+ , [a] -- The data value for each subperiod.
+ , a -- The total of this row's values.
+ , a -- The average of this row's values.
+ )
diff --git a/Text/Tabular/AsciiWide.hs b/Text/Tabular/AsciiWide.hs
new file mode 100644
index 0000000..60733cf
--- /dev/null
+++ b/Text/Tabular/AsciiWide.hs
@@ -0,0 +1,111 @@
+-- | Text.Tabular.AsciiArt from tabular-0.2.2.7, modified to treat
+-- wide characters as double width.
+
+module Text.Tabular.AsciiWide where
+
+import Data.List (intersperse, transpose)
+import Text.Tabular
+import Hledger.Utils.String
+
+-- | for simplicity, we assume that each cell is rendered
+-- on a single line
+render :: Bool -- ^ pretty tables
+ -> (rh -> String)
+ -> (ch -> String)
+ -> (a -> String)
+ -> Table rh ch a
+ -> String
+render pretty fr fc f (Table rh ch cells) =
+ unlines $ [ bar SingleLine -- +--------------------------------------+
+ , renderColumns pretty sizes ch2
+ , bar DoubleLine -- +======================================+
+ ] ++
+ (renderRs $ fmap renderR $ zipHeader [] cells $ fmap fr rh) ++
+ [ bar SingleLine ] -- +--------------------------------------+
+ where
+ bar = concat . renderHLine pretty sizes ch2
+ -- ch2 and cell2 include the row and column labels
+ ch2 = Group DoubleLine [Header "", fmap fc ch]
+ cells2 = headerContents ch2
+ : zipWith (\h cs -> h : map f cs) rhStrings cells
+ --
+ renderR (cs,h) = renderColumns pretty sizes $ Group DoubleLine
+ [ Header h
+ , fmap fst $ zipHeader "" (map f cs) ch]
+ rhStrings = map fr $ headerContents rh
+ -- maximum width for each column
+ sizes = map (maximum . map strWidth) . transpose $ cells2
+ renderRs (Header s) = [s]
+ renderRs (Group p hs) = concat . intersperse sep . map renderRs $ hs
+ where sep = renderHLine pretty sizes ch2 p
+
+verticalBar :: Bool -> Char
+verticalBar pretty = if pretty then '│' else '|'
+
+leftBar :: Bool -> String
+leftBar pretty = verticalBar pretty : " "
+
+rightBar :: Bool -> String
+rightBar pretty = " " ++ [verticalBar pretty]
+
+midBar :: Bool -> String
+midBar pretty = " " ++ verticalBar pretty : " "
+
+doubleMidBar :: Bool -> String
+doubleMidBar pretty = if pretty then " ║ " else " || "
+
+horizontalBar :: Bool -> Char
+horizontalBar pretty = if pretty then '─' else '-'
+
+doubleHorizontalBar :: Bool -> Char
+doubleHorizontalBar pretty = if pretty then '═' else '='
+
+-- | We stop rendering on the shortest list!
+renderColumns :: Bool -- ^ pretty
+ -> [Int] -- ^ max width for each column
+ -> Header String
+ -> String
+renderColumns pretty is h = leftBar pretty ++ coreLine ++ rightBar pretty
+ where
+ coreLine = concatMap helper $ flattenHeader $ zipHeader 0 is h
+ helper = either hsep (uncurry padLeftWide)
+ hsep :: Properties -> String
+ hsep NoLine = " "
+ hsep SingleLine = midBar pretty
+ hsep DoubleLine = doubleMidBar pretty
+
+renderHLine :: Bool -- ^ pretty
+ -> [Int] -- ^ width specifications
+ -> Header String
+ -> Properties
+ -> [String]
+renderHLine _ _ _ NoLine = []
+renderHLine pretty w h SingleLine = [renderHLine' pretty SingleLine w (horizontalBar pretty) h]
+renderHLine pretty w h DoubleLine = [renderHLine' pretty DoubleLine w (doubleHorizontalBar pretty) h]
+
+doubleCross :: Bool -> String
+doubleCross pretty = if pretty then "╬" else "++"
+
+doubleVerticalCross :: Bool -> String
+doubleVerticalCross pretty = if pretty then "╫" else "++"
+
+cross :: Bool -> Char
+cross pretty = if pretty then '┼' else '+'
+
+renderHLine' :: Bool -> Properties -> [Int] -> Char -> Header String -> String
+renderHLine' pretty prop is sep h = [ cross pretty, sep ] ++ coreLine ++ [sep, cross pretty]
+ where
+ coreLine = concatMap helper $ flattenHeader $ zipHeader 0 is h
+ helper = either vsep dashes
+ dashes (i,_) = replicate i sep
+ vsep NoLine = replicate 2 sep -- match the double space sep in renderColumns
+ vsep SingleLine = sep : cross pretty : [sep]
+ vsep DoubleLine = sep : cross' ++ [sep]
+ cross' = case prop of
+ DoubleLine -> doubleCross pretty
+ _ -> doubleVerticalCross pretty
+
+-- padLeft :: Int -> String -> String
+-- padLeft l s = padding ++ s
+-- where padding = replicate (l - length s) ' '
+
diff --git a/hledger-lib.cabal b/hledger-lib.cabal
index 8cd8443..4f6789e 100644
--- a/hledger-lib.cabal
+++ b/hledger-lib.cabal
@@ -1,11 +1,11 @@
--- This file has been generated from package.yaml by hpack version 0.20.0.
+-- This file has been generated from package.yaml by hpack version 0.28.2.
--
-- see: https://github.com/sol/hpack
--
--- hash: 151fb38a89818af88d66f068ef87e0af6f8497b1ef11ab825558b8147952c88d
+-- hash: 1905b347a666e216347e595c5fcb0b340e5618a383a5493438200c6bcd5e6f98
name: hledger-lib
-version: 1.9
+version: 1.9.1
synopsis: Core data types, parsers and functionality for the hledger accounting tools
description: This is a reusable library containing hledger's core functionality.
.
@@ -26,7 +26,6 @@ license-file: LICENSE
tested-with: GHC==7.10.3, GHC==8.0.2, GHC==8.2.1
build-type: Simple
cabal-version: >= 1.10
-
extra-source-files:
CHANGES
hledger_csv.5
@@ -48,44 +47,6 @@ source-repository head
location: https://github.com/simonmichael/hledger
library
- hs-source-dirs:
- ./.
- ghc-options: -Wall -fno-warn-unused-do-bind -fno-warn-name-shadowing -fno-warn-missing-signatures -fno-warn-type-defaults -fno-warn-orphans
- build-depends:
- Decimal
- , HUnit
- , ansi-terminal >=0.6.2.3
- , array
- , base >=4.8 && <4.12
- , base-compat >=0.8.1
- , blaze-markup >=0.5.1
- , bytestring
- , cmdargs >=0.10
- , containers
- , csv
- , data-default >=0.5
- , deepseq
- , directory
- , extra
- , filepath
- , hashtables >=1.2
- , megaparsec >=5.0
- , mtl
- , mtl-compat
- , old-time
- , parsec >=3
- , pretty-show >=1.6.4
- , regex-tdfa
- , safe >=0.2
- , split >=0.1
- , text >=1.2
- , time >=1.5
- , transformers >=0.2
- , uglymemo
- , utf8-string >=0.3.5
- if (!impl(ghc >= 8.0))
- build-depends:
- semigroups ==0.18.*
exposed-modules:
Hledger
Hledger.Data
@@ -114,8 +75,10 @@ library
Hledger.Read.TimeclockReader
Hledger.Reports
Hledger.Reports.ReportOptions
+ Hledger.Reports.ReportTypes
Hledger.Reports.BalanceHistoryReport
Hledger.Reports.BalanceReport
+ Hledger.Reports.BudgetReport
Hledger.Reports.EntriesReport
Hledger.Reports.MultiBalanceReports
Hledger.Reports.PostingsReport
@@ -131,20 +94,14 @@ library
Hledger.Utils.Tree
Hledger.Utils.UTF8IOCompat
Text.Megaparsec.Compat
+ Text.Tabular.AsciiWide
other-modules:
Paths_hledger_lib
- default-language: Haskell2010
-
-test-suite doctests
- type: exitcode-stdio-1.0
- main-is: doctests.hs
hs-source-dirs:
./.
- tests
ghc-options: -Wall -fno-warn-unused-do-bind -fno-warn-name-shadowing -fno-warn-missing-signatures -fno-warn-type-defaults -fno-warn-orphans
build-depends:
Decimal
- , Glob >=0.7
, HUnit
, ansi-terminal >=0.6.2.3
, array
@@ -158,7 +115,6 @@ test-suite doctests
, data-default >=0.5
, deepseq
, directory
- , doctest >=0.8
, extra
, filepath
, hashtables >=1.2
@@ -171,6 +127,7 @@ test-suite doctests
, regex-tdfa
, safe >=0.2
, split >=0.1
+ , tabular >=0.2
, text >=1.2
, time >=1.5
, transformers >=0.2
@@ -179,8 +136,11 @@ test-suite doctests
if (!impl(ghc >= 8.0))
build-depends:
semigroups ==0.18.*
- if impl(ghc >= 8.4) && os(darwin)
- buildable: False
+ default-language: Haskell2010
+
+test-suite doctests
+ type: exitcode-stdio-1.0
+ main-is: doctests.hs
other-modules:
Hledger
Hledger.Data
@@ -210,10 +170,12 @@ test-suite doctests
Hledger.Reports
Hledger.Reports.BalanceHistoryReport
Hledger.Reports.BalanceReport
+ Hledger.Reports.BudgetReport
Hledger.Reports.EntriesReport
Hledger.Reports.MultiBalanceReports
Hledger.Reports.PostingsReport
Hledger.Reports.ReportOptions
+ Hledger.Reports.ReportTypes
Hledger.Reports.TransactionsReports
Hledger.Utils
Hledger.Utils.Color
@@ -226,18 +188,15 @@ test-suite doctests
Hledger.Utils.Tree
Hledger.Utils.UTF8IOCompat
Text.Megaparsec.Compat
+ Text.Tabular.AsciiWide
Paths_hledger_lib
- default-language: Haskell2010
-
-test-suite easytests
- type: exitcode-stdio-1.0
- main-is: easytests.hs
hs-source-dirs:
./.
tests
ghc-options: -Wall -fno-warn-unused-do-bind -fno-warn-name-shadowing -fno-warn-missing-signatures -fno-warn-type-defaults -fno-warn-orphans
build-depends:
Decimal
+ , Glob >=0.7
, HUnit
, ansi-terminal >=0.6.2.3
, array
@@ -251,11 +210,10 @@ test-suite easytests
, data-default >=0.5
, deepseq
, directory
- , easytest
+ , doctest >=0.8
, extra
, filepath
, hashtables >=1.2
- , hledger-lib
, megaparsec >=5.0
, mtl
, mtl-compat
@@ -265,6 +223,7 @@ test-suite easytests
, regex-tdfa
, safe >=0.2
, split >=0.1
+ , tabular >=0.2
, text >=1.2
, time >=1.5
, transformers >=0.2
@@ -273,6 +232,13 @@ test-suite easytests
if (!impl(ghc >= 8.0))
build-depends:
semigroups ==0.18.*
+ if impl(ghc >= 8.4) && os(darwin)
+ buildable: False
+ default-language: Haskell2010
+
+test-suite easytests
+ type: exitcode-stdio-1.0
+ main-is: easytests.hs
other-modules:
Hledger
Hledger.Data
@@ -302,10 +268,12 @@ test-suite easytests
Hledger.Reports
Hledger.Reports.BalanceHistoryReport
Hledger.Reports.BalanceReport
+ Hledger.Reports.BudgetReport
Hledger.Reports.EntriesReport
Hledger.Reports.MultiBalanceReports
Hledger.Reports.PostingsReport
Hledger.Reports.ReportOptions
+ Hledger.Reports.ReportTypes
Hledger.Reports.TransactionsReports
Hledger.Utils
Hledger.Utils.Color
@@ -318,12 +286,8 @@ test-suite easytests
Hledger.Utils.Tree
Hledger.Utils.UTF8IOCompat
Text.Megaparsec.Compat
+ Text.Tabular.AsciiWide
Paths_hledger_lib
- default-language: Haskell2010
-
-test-suite hunittests
- type: exitcode-stdio-1.0
- main-is: hunittests.hs
hs-source-dirs:
./.
tests
@@ -343,6 +307,7 @@ test-suite hunittests
, data-default >=0.5
, deepseq
, directory
+ , easytest
, extra
, filepath
, hashtables >=1.2
@@ -356,8 +321,7 @@ test-suite hunittests
, regex-tdfa
, safe >=0.2
, split >=0.1
- , test-framework
- , test-framework-hunit
+ , tabular >=0.2
, text >=1.2
, time >=1.5
, transformers >=0.2
@@ -366,6 +330,11 @@ test-suite hunittests
if (!impl(ghc >= 8.0))
build-depends:
semigroups ==0.18.*
+ default-language: Haskell2010
+
+test-suite hunittests
+ type: exitcode-stdio-1.0
+ main-is: hunittests.hs
other-modules:
Hledger
Hledger.Data
@@ -395,10 +364,12 @@ test-suite hunittests
Hledger.Reports
Hledger.Reports.BalanceHistoryReport
Hledger.Reports.BalanceReport
+ Hledger.Reports.BudgetReport
Hledger.Reports.EntriesReport
Hledger.Reports.MultiBalanceReports
Hledger.Reports.PostingsReport
Hledger.Reports.ReportOptions
+ Hledger.Reports.ReportTypes
Hledger.Reports.TransactionsReports
Hledger.Utils
Hledger.Utils.Color
@@ -411,5 +382,49 @@ test-suite hunittests
Hledger.Utils.Tree
Hledger.Utils.UTF8IOCompat
Text.Megaparsec.Compat
+ Text.Tabular.AsciiWide
Paths_hledger_lib
+ hs-source-dirs:
+ ./.
+ tests
+ ghc-options: -Wall -fno-warn-unused-do-bind -fno-warn-name-shadowing -fno-warn-missing-signatures -fno-warn-type-defaults -fno-warn-orphans
+ build-depends:
+ Decimal
+ , HUnit
+ , ansi-terminal >=0.6.2.3
+ , array
+ , base >=4.8 && <4.12
+ , base-compat >=0.8.1
+ , blaze-markup >=0.5.1
+ , bytestring
+ , cmdargs >=0.10
+ , containers
+ , csv
+ , data-default >=0.5
+ , deepseq
+ , directory
+ , extra
+ , filepath
+ , hashtables >=1.2
+ , hledger-lib
+ , megaparsec >=5.0
+ , mtl
+ , mtl-compat
+ , old-time
+ , parsec >=3
+ , pretty-show >=1.6.4
+ , regex-tdfa
+ , safe >=0.2
+ , split >=0.1
+ , tabular >=0.2
+ , test-framework
+ , test-framework-hunit
+ , text >=1.2
+ , time >=1.5
+ , transformers >=0.2
+ , uglymemo
+ , utf8-string >=0.3.5
+ if (!impl(ghc >= 8.0))
+ build-depends:
+ semigroups ==0.18.*
default-language: Haskell2010
diff --git a/hledger_csv.5 b/hledger_csv.5
index 0ca4805..1043528 100644
--- a/hledger_csv.5
+++ b/hledger_csv.5
@@ -1,5 +1,5 @@
-.TH "hledger_csv" "5" "March 2018" "hledger 1.9" "hledger User Manuals"
+.TH "hledger_csv" "5" "April 2018" "hledger 1.9.1" "hledger User Manuals"
diff --git a/hledger_csv.info b/hledger_csv.info
index 5babf2e..0cf93c9 100644
--- a/hledger_csv.info
+++ b/hledger_csv.info
@@ -3,8 +3,8 @@ This is hledger_csv.info, produced by makeinfo version 6.5 from stdin.

File: hledger_csv.info, Node: Top, Next: CSV RULES, Up: (dir)
-hledger_csv(5) hledger 1.9
-**************************
+hledger_csv(5) hledger 1.9.1
+****************************
hledger can read CSV (comma-separated value) files as if they were
journal files, automatically converting each CSV record into a
@@ -317,33 +317,33 @@ one rules file will be used for all the CSV files being read.

Tag Table:
Node: Top72
-Node: CSV RULES2161
-Ref: #csv-rules2269
-Node: skip2531
-Ref: #skip2625
-Node: date-format2797
-Ref: #date-format2924
-Node: field list3430
-Ref: #field-list3567
-Node: field assignment4272
-Ref: #field-assignment4427
-Node: conditional block4931
-Ref: #conditional-block5085
-Node: include5981
-Ref: #include6111
-Node: newest-first6342
-Ref: #newest-first6456
-Node: CSV TIPS6867
-Ref: #csv-tips6961
-Node: CSV ordering7079
-Ref: #csv-ordering7197
-Node: CSV accounts7378
-Ref: #csv-accounts7516
-Node: CSV amounts7770
-Ref: #csv-amounts7916
-Node: CSV balance assertions8691
-Ref: #csv-balance-assertions8873
-Node: Reading multiple CSV files9078
-Ref: #reading-multiple-csv-files9248
+Node: CSV RULES2165
+Ref: #csv-rules2273
+Node: skip2535
+Ref: #skip2629
+Node: date-format2801
+Ref: #date-format2928
+Node: field list3434
+Ref: #field-list3571
+Node: field assignment4276
+Ref: #field-assignment4431
+Node: conditional block4935
+Ref: #conditional-block5089
+Node: include5985
+Ref: #include6115
+Node: newest-first6346
+Ref: #newest-first6460
+Node: CSV TIPS6871
+Ref: #csv-tips6965
+Node: CSV ordering7083
+Ref: #csv-ordering7201
+Node: CSV accounts7382
+Ref: #csv-accounts7520
+Node: CSV amounts7774
+Ref: #csv-amounts7920
+Node: CSV balance assertions8695
+Ref: #csv-balance-assertions8877
+Node: Reading multiple CSV files9082
+Ref: #reading-multiple-csv-files9252

End Tag Table
diff --git a/hledger_csv.txt b/hledger_csv.txt
index bd95bdd..b629029 100644
--- a/hledger_csv.txt
+++ b/hledger_csv.txt
@@ -249,4 +249,4 @@ SEE ALSO
-hledger 1.9 March 2018 hledger_csv(5)
+hledger 1.9.1 April 2018 hledger_csv(5)
diff --git a/hledger_journal.5 b/hledger_journal.5
index 0c3f102..2afe30b 100644
--- a/hledger_journal.5
+++ b/hledger_journal.5
@@ -1,6 +1,6 @@
.\"t
-.TH "hledger_journal" "5" "March 2018" "hledger 1.9" "hledger User Manuals"
+.TH "hledger_journal" "5" "April 2018" "hledger 1.9.1" "hledger User Manuals"
@@ -758,11 +758,6 @@ Lines in the journal beginning with a semicolon (\f[C];\f[]) or hash
(Star comments cause org\-mode nodes to be ignored, allowing emacs users
to fold and navigate their journals with org\-mode or orgstruct\-mode.)
.PP
-Also, anything between \f[C]comment\f[] and \f[C]end\ comment\f[]
-directives is a (multi\-line) comment.
-If there is no \f[C]end\ comment\f[], the comment extends to the end of
-the file.
-.PP
You can attach comments to a transaction by writing them after the
description and/or indented on the following lines (before the
postings).
@@ -795,6 +790,9 @@ end\ comment
;\ a\ file\ comment\ (because\ not\ indented)
\f[]
.fi
+.PP
+You can also comment larger regions of a file using \f[C]comment\f[] and
+\f[C]end\ comment\f[] directives.
.SS Tags
.PP
Tags are a way to add extra labels or labelled data to postings and
@@ -855,7 +853,173 @@ For example, the following transaction has three tags (\f[C]A\f[],
Tags are like Ledger's metadata feature, except hledger's tag values are
simple strings.
.SS Directives
-.SS Account aliases
+.SS Comment blocks
+.PP
+A line containing just \f[C]comment\f[] starts a commented region of the
+file, and a line containing just \f[C]end\ comment\f[] (or the end of
+the current file) ends it.
+See also comments.
+.SS Including other files
+.PP
+You can pull in the content of additional files by writing an include
+directive, like this:
+.IP
+.nf
+\f[C]
+include\ path/to/file.journal
+\f[]
+.fi
+.PP
+If the path does not begin with a slash, it is relative to the current
+file.
+Glob patterns (\f[C]*\f[]) are not currently supported.
+.PP
+The \f[C]include\f[] directive can only be used in journal files.
+It can include journal, timeclock or timedot files, but not CSV files.
+.SS Default year
+.PP
+You can set a default year to be used for subsequent dates which don't
+specify a year.
+This is a line beginning with \f[C]Y\f[] followed by the year.
+Eg:
+.IP
+.nf
+\f[C]
+Y2009\ \ \ \ \ \ ;\ set\ default\ year\ to\ 2009
+
+12/15\ \ \ \ \ \ ;\ equivalent\ to\ 2009/12/15
+\ \ expenses\ \ 1
+\ \ assets
+
+Y2010\ \ \ \ \ \ ;\ change\ default\ year\ to\ 2010
+
+2009/1/30\ \ ;\ specifies\ the\ year,\ not\ affected
+\ \ expenses\ \ 1
+\ \ assets
+
+1/31\ \ \ \ \ \ \ ;\ equivalent\ to\ 2010/1/31
+\ \ expenses\ \ 1
+\ \ assets
+\f[]
+.fi
+.SS Declaring commodities
+.PP
+The \f[C]commodity\f[] directive declares commodities which may be used
+in the journal (though currently we do not enforce this).
+It may be written on a single line, like this:
+.IP
+.nf
+\f[C]
+;\ commodity\ EXAMPLEAMOUNT
+
+;\ display\ AAAA\ amounts\ with\ the\ symbol\ on\ the\ right,\ space\-separated,
+;\ using\ period\ as\ decimal\ point,\ with\ four\ decimal\ places,\ and
+;\ separating\ thousands\ with\ comma.
+commodity\ 1,000.0000\ AAAA
+\f[]
+.fi
+.PP
+or on multiple lines, using the \[lq]format\[rq] subdirective.
+In this case the commodity symbol appears twice and should be the same
+in both places:
+.IP
+.nf
+\f[C]
+;\ commodity\ SYMBOL
+;\ \ \ format\ EXAMPLEAMOUNT
+
+;\ display\ indian\ rupees\ with\ currency\ name\ on\ the\ left,
+;\ thousands,\ lakhs\ and\ crores\ comma\-separated,
+;\ period\ as\ decimal\ point,\ and\ two\ decimal\ places.
+commodity\ INR
+\ \ format\ INR\ 9,99,99,999.00
+\f[]
+.fi
+.PP
+Commodity directives have a second purpose: they define the standard
+display format for amounts in the commodity.
+Normally the display format is inferred from journal entries, but this
+can be unpredictable; declaring it with a commodity directive overrides
+this and removes ambiguity.
+Towards this end, amounts in commodity directives must always be written
+with a decimal point (a period or comma, followed by 0 or more decimal
+digits).
+.SS Default commodity
+.PP
+The D directive sets a default commodity (and display format), to be
+used for amounts without a commodity symbol (ie, plain numbers).
+(Note this differs from Ledger's default commodity directive.) The
+commodity and display format will be applied to all subsequent
+commodity\-less amounts, or until the next D directive.
+.IP
+.nf
+\f[C]
+#\ commodity\-less\ amounts\ should\ be\ treated\ as\ dollars
+#\ (and\ displayed\ with\ symbol\ on\ the\ left,\ thousands\ separators\ and\ two\ decimal\ places)
+D\ $1,000.00
+
+1/1
+\ \ a\ \ \ \ \ 5\ \ \ \ ;\ <\-\ commodity\-less\ amount,\ becomes\ $1
+\ \ b
+\f[]
+.fi
+.PP
+As with the \f[C]commodity\f[] directive, the amount must always be
+written with a decimal point.
+.SS Declaring accounts
+.PP
+The \f[C]account\f[] directive predeclares account names.
+The simplest form is \f[C]account\ ACCTNAME\f[], eg:
+.IP
+.nf
+\f[C]
+account\ assets:bank:checking
+\f[]
+.fi
+.PP
+Currently this mainly helps with account name autocompletion in eg
+hledger add, hledger\-iadd, hledger\-web, and ledger\-mode.
+.PD 0
+.P
+.PD
+In future it will also help detect misspelled accounts.
+.PP
+Account names can be followed by a numeric account code:
+.IP
+.nf
+\f[C]
+account\ assets\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ 1000
+account\ assets:bank:checking\ \ \ \ 1110
+account\ liabilities\ \ \ \ \ \ \ \ \ \ \ \ \ 2000
+account\ revenues\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ 4000
+account\ expenses\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ 6000
+\f[]
+.fi
+.PP
+This affects account display order in reports: accounts with codes are
+listed before accounts without codes, in increasing code order.
+(Otherwise, accounts are listed alphabetically.) Account codes should be
+all numeric digits, unique, and separated from the account name by at
+least two spaces (since account names may contain single spaces).
+By convention, often the first digit indicates the type of account, as
+in this numbering scheme and the example above.
+In future, we might use this to recognize account types.
+.PP
+An account directive can also have indented subdirectives following it,
+which are currently ignored.
+Here is the full syntax:
+.IP
+.nf
+\f[C]
+;\ account\ ACCTNAME\ \ [OPTIONALCODE]
+;\ \ \ [OPTIONALSUBDIRECTIVES]
+
+account\ assets:bank:checking\ \ \ 1110
+\ \ a\ comment
+\ \ some\-tag:12345
+\f[]
+.fi
+.SS Rewriting accounts
.PP
You can define aliases which rewrite your account names (after reading
the journal, before generating reports).
@@ -871,7 +1035,7 @@ combining two accounts into one
.IP \[bu] 2
customising reports
.PP
-See also Cookbook: rewrite account names.
+See also Cookbook: Rewrite account names.
.SS Basic aliases
.PP
To set an account alias, use the \f[C]alias\f[] directive in your
@@ -946,7 +1110,7 @@ alias directives, most recently seen first (recent directives take
precedence over earlier ones; directives not yet seen are ignored)
.IP "2." 3
alias options, in the order they appear on the command line
-.SS end aliases
+.SS \f[C]end\ aliases\f[]
.PP
You can clear (forget) all currently defined aliases with the
\f[C]end\ aliases\f[] directive:
@@ -956,60 +1120,7 @@ You can clear (forget) all currently defined aliases with the
end\ aliases
\f[]
.fi
-.SS account directive
-.PP
-The \f[C]account\f[] directive predeclares account names.
-The simplest form is \f[C]account\ ACCTNAME\f[], eg:
-.IP
-.nf
-\f[C]
-account\ assets:bank:checking
-\f[]
-.fi
-.PP
-Currently this mainly helps with account name autocompletion in eg
-hledger add, hledger\-iadd, hledger\-web, and ledger\-mode.
-.PD 0
-.P
-.PD
-In future it will also help detect misspelled accounts.
-.PP
-Account names can be followed by a numeric account code:
-.IP
-.nf
-\f[C]
-account\ assets\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ 1000
-account\ assets:bank:checking\ \ \ \ 1110
-account\ liabilities\ \ \ \ \ \ \ \ \ \ \ \ \ 2000
-account\ revenues\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ 4000
-account\ expenses\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ 6000
-\f[]
-.fi
-.PP
-This affects account display order in reports: accounts with codes are
-listed before accounts without codes, in increasing code order.
-(Otherwise, accounts are listed alphabetically.) Account codes should be
-all numeric digits, unique, and separated from the account name by at
-least two spaces (since account names may contain single spaces).
-By convention, often the first digit indicates the type of account, as
-in this numbering scheme and the example above.
-In future, we might use this to recognize account types.
-.PP
-An account directive can also have indented subdirectives following it,
-which are currently ignored.
-Here is the full syntax:
-.IP
-.nf
-\f[C]
-;\ account\ ACCTNAME\ \ [OPTIONALCODE]
-;\ \ \ [OPTIONALSUBDIRECTIVES]
-
-account\ assets:bank:checking\ \ \ 1110
-\ \ a\ comment
-\ \ some\-tag:12345
-\f[]
-.fi
-.SS apply account directive
+.SS Default parent account
.PP
You can specify a parent account which will be prepended to all accounts
within a section of the journal.
@@ -1054,116 +1165,13 @@ include\ personal.journal
.PP
Prior to hledger 1.0, legacy \f[C]account\f[] and \f[C]end\f[] spellings
were also supported.
-.SS Multi\-line comments
-.PP
-A line containing just \f[C]comment\f[] starts a multi\-line comment,
-and a line containing just \f[C]end\ comment\f[] ends it.
-See comments.
-.SS commodity directive
+.SS Periodic transactions
.PP
-The \f[C]commodity\f[] directive predefines commodities (currently this
-is just informational), and also it may define the display format for
-amounts in this commodity (overriding the automatically inferred
-format).
-.PP
-It may be written on a single line, like this:
-.IP
-.nf
-\f[C]
-;\ commodity\ EXAMPLEAMOUNT
-
-;\ display\ AAAA\ amounts\ with\ the\ symbol\ on\ the\ right,\ space\-separated,
-;\ using\ period\ as\ decimal\ point,\ with\ four\ decimal\ places,\ and
-;\ separating\ thousands\ with\ comma.
-commodity\ 1,000.0000\ AAAA
-\f[]
-.fi
-.PP
-or on multiple lines, using the \[lq]format\[rq] subdirective.
-In this case the commodity symbol appears twice and should be the same
-in both places:
-.IP
-.nf
-\f[C]
-;\ commodity\ SYMBOL
-;\ \ \ format\ EXAMPLEAMOUNT
-
-;\ display\ indian\ rupees\ with\ currency\ name\ on\ the\ left,
-;\ thousands,\ lakhs\ and\ crores\ comma\-separated,
-;\ period\ as\ decimal\ point,\ and\ two\ decimal\ places.
-commodity\ INR
-\ \ format\ INR\ 9,99,99,999.00
-\f[]
-.fi
-.SS Default commodity
-.PP
-The D directive sets a default commodity (and display format), to be
-used for amounts without a commodity symbol (ie, plain numbers).
-(Note this differs from Ledger's default commodity directive.) The
-commodity and display format will be applied to all subsequent
-commodity\-less amounts, or until the next D directive.
-.IP
-.nf
-\f[C]
-#\ commodity\-less\ amounts\ should\ be\ treated\ as\ dollars
-#\ (and\ displayed\ with\ symbol\ on\ the\ left,\ thousands\ separators\ and\ two\ decimal\ places)
-D\ $1,000.00
-
-1/1
-\ \ a\ \ \ \ \ 5\ \ \ \ ;\ <\-\ commodity\-less\ amount,\ becomes\ $1
-\ \ b
-\f[]
-.fi
-.SS Default year
-.PP
-You can set a default year to be used for subsequent dates which don't
-specify a year.
-This is a line beginning with \f[C]Y\f[] followed by the year.
-Eg:
-.IP
-.nf
-\f[C]
-Y2009\ \ \ \ \ \ ;\ set\ default\ year\ to\ 2009
-
-12/15\ \ \ \ \ \ ;\ equivalent\ to\ 2009/12/15
-\ \ expenses\ \ 1
-\ \ assets
-
-Y2010\ \ \ \ \ \ ;\ change\ default\ year\ to\ 2010
-
-2009/1/30\ \ ;\ specifies\ the\ year,\ not\ affected
-\ \ expenses\ \ 1
-\ \ assets
-
-1/31\ \ \ \ \ \ \ ;\ equivalent\ to\ 2010/1/31
-\ \ expenses\ \ 1
-\ \ assets
-\f[]
-.fi
-.SS Including other files
-.PP
-You can pull in the content of additional journal files by writing an
-include directive, like this:
-.IP
-.nf
-\f[C]
-include\ path/to/file.journal
-\f[]
-.fi
-.PP
-If the path does not begin with a slash, it is relative to the current
-file.
-Glob patterns (\f[C]*\f[]) are not currently supported.
-.PP
-The \f[C]include\f[] directive can only be used in journal files.
-It can include journal, timeclock or timedot files, but not CSV files.
-.SH Periodic transactions
-.PP
-Periodic transactions are a kind of rule with a dual purpose: they can
-specify recurring future transactions (with \f[C]\-\-forecast\f[]), or
-budget goals (with \f[C]\-\-budget\f[]).
-They look a bit like a transaction, except the first line is a tilde
-(\f[C]~\f[]) followed by a period expression:
+Periodic transaction rules (enabled by \f[C]\-\-forecast\f[] or
+\f[C]\-\-budget\f[]) describe recurring transactions.
+They look like a transaction where the first line is a tilde
+(\f[C]~\f[]) followed by a period expression (mnemonic: \f[C]~\f[] is
+like a recurring sine wave):
.IP
.nf
\f[C]
@@ -1173,25 +1181,34 @@ They look a bit like a transaction, except the first line is a tilde
\f[]
.fi
.PP
+Periodic transactions have a dual purpose:
+.IP \[bu] 2
With \f[C]\-\-forecast\f[], each periodic transaction rule generates
-recurring \[lq]forecast\[rq] transactions at the specified interval,
-beginning the day after the latest recorded journal transaction (or
-today, if there are no transactions) and ending 6 months from today (or
-at the report end date, if specified).
-.PP
-With \f[C]balance\ \-\-budget\f[], each periodic transaction declares
-recurring budget goals for the specified accounts.
+future transactions, recurring at the specified interval, which can be
+seen in reports.
+Forecast transactions begin the day after the latest recorded journal
+transaction (or today, if there are no transactions) and end 6 months
+from today (or at the report end date, if specified).
+.IP \[bu] 2
+With \f[C]\-\-budget\f[] (supported by the balance command), each
+periodic transaction rule declares recurring budget goals for the
+specified accounts, which can be seen in budget reports.
Eg the example above declares the goal of receiving $400 from
\f[C]income:acme\ inc\f[] (and also, depositing $400 into
\f[C]assets:bank:checking\f[]) every week.
.PP
-For more details, see: balance: Budgeting and Budgeting and Forecasting.
-.SH Automated postings
+(Actually, you can generate one\-off transactions too, by writing a
+period expression with no report interval.)
.PP
-Automated postings are postings added automatically by rule to certain
-transactions (with \f[C]\-\-auto\f[]).
+For more details, see: balance: Budget report and Cookbook: Budgeting
+and Forecasting.
+.SS Automated postings
+.PP
+Automated postings (enabled by \f[C]\-\-auto\f[]) are postings added
+automatically by rule to certain transactions.
An automated posting rule looks like a transaction where the first line
-is an equal sign (\f[C]=\f[]) followed by a query:
+is an equal sign (\f[C]=\f[]) followed by a query (mnemonic: \f[C]=\f[]
+tests for matching transactions, and also looks like posting lines):
.IP
.nf
\f[C]
@@ -1226,10 +1243,13 @@ transaction involving the \f[C]expenses:gifts\f[] account:
$\ hledger\ print\ \-\-auto
2017/12/14
\ \ \ \ expenses:gifts\ \ \ \ \ \ \ \ \ \ \ \ \ $20
-\ \ \ \ assets
\ \ \ \ (budget:gifts)\ \ \ \ \ \ \ \ \ \ \ \ $\-20
+\ \ \ \ assets
\f[]
.fi
+.PP
+Like postings recorded by hand, automated postings participate in
+transaction balancing, missing amount inference and balance assertions.
.SH EDITOR SUPPORT
.PP
Add\-on modes exist for various text editors, to make working with
diff --git a/hledger_journal.info b/hledger_journal.info
index b504424..51f0518 100644
--- a/hledger_journal.info
+++ b/hledger_journal.info
@@ -4,8 +4,8 @@ stdin.

File: hledger_journal.info, Node: Top, Next: FILE FORMAT, Up: (dir)
-hledger_journal(5) hledger 1.9
-******************************
+hledger_journal(5) hledger 1.9.1
+********************************
hledger's usual data source is a plain text file containing journal
entries in hledger journal format. This file represents a standard
@@ -57,12 +57,10 @@ assisted by the helper modes for emacs or vim.
* Menu:
* FILE FORMAT::
-* Periodic transactions::
-* Automated postings::
* EDITOR SUPPORT::

-File: hledger_journal.info, Node: FILE FORMAT, Next: Periodic transactions, Prev: Top, Up: Top
+File: hledger_journal.info, Node: FILE FORMAT, Next: EDITOR SUPPORT, Prev: Top, Up: Top
1 FILE FORMAT
*************
@@ -83,6 +81,8 @@ File: hledger_journal.info, Node: FILE FORMAT, Next: Periodic transactions, P
* Comments::
* Tags::
* Directives::
+* Periodic transactions::
+* Automated postings::

File: hledger_journal.info, Node: Transactions, Next: Postings, Up: FILE FORMAT
@@ -713,10 +713,6 @@ star ('*') are comments, and will be ignored. (Star comments cause
org-mode nodes to be ignored, allowing emacs users to fold and navigate
their journals with org-mode or orgstruct-mode.)
- Also, anything between 'comment' and 'end comment' directives is a
-(multi-line) comment. If there is no 'end comment', the comment extends
-to the end of the file.
-
You can attach comments to a transaction by writing them after the
description and/or indented on the following lines (before the
postings). Similarly, you can attach comments to an individual posting
@@ -744,6 +740,9 @@ end comment
; another comment line for posting 2
; a file comment (because not indented)
+ You can also comment larger regions of a file using 'comment' and
+'end comment' directives.
+

File: hledger_journal.info, Node: Tags, Next: Directives, Prev: Comments, Up: FILE FORMAT
@@ -788,27 +787,183 @@ example, the following transaction has three tags ('A', 'TAG2',
are simple strings.

-File: hledger_journal.info, Node: Directives, Prev: Tags, Up: FILE FORMAT
+File: hledger_journal.info, Node: Directives, Next: Periodic transactions, Prev: Tags, Up: FILE FORMAT
1.14 Directives
===============
* Menu:
-* Account aliases::
-* account directive::
-* apply account directive::
-* Multi-line comments::
-* commodity directive::
-* Default commodity::
-* Default year::
+* Comment blocks::
* Including other files::
+* Default year::
+* Declaring commodities::
+* Default commodity::
+* Declaring accounts::
+* Rewriting accounts::
+* Default parent account::
+
+
+File: hledger_journal.info, Node: Comment blocks, Next: Including other files, Up: Directives
+
+1.14.1 Comment blocks
+---------------------
+
+A line containing just 'comment' starts a commented region of the file,
+and a line containing just 'end comment' (or the end of the current
+file) ends it. See also comments.
+
+
+File: hledger_journal.info, Node: Including other files, Next: Default year, Prev: Comment blocks, Up: Directives
+
+1.14.2 Including other files
+----------------------------
+
+You can pull in the content of additional files by writing an include
+directive, like this:
+
+include path/to/file.journal
+
+ If the path does not begin with a slash, it is relative to the
+current file. Glob patterns ('*') are not currently supported.
+
+ The 'include' directive can only be used in journal files. It can
+include journal, timeclock or timedot files, but not CSV files.
+
+
+File: hledger_journal.info, Node: Default year, Next: Declaring commodities, Prev: Including other files, Up: Directives
+
+1.14.3 Default year
+-------------------
+
+You can set a default year to be used for subsequent dates which don't
+specify a year. This is a line beginning with 'Y' followed by the year.
+Eg:
+
+Y2009 ; set default year to 2009
+
+12/15 ; equivalent to 2009/12/15
+ expenses 1
+ assets
+
+Y2010 ; change default year to 2010
+
+2009/1/30 ; specifies the year, not affected
+ expenses 1
+ assets
+
+1/31 ; equivalent to 2010/1/31
+ expenses 1
+ assets

-File: hledger_journal.info, Node: Account aliases, Next: account directive, Up: Directives
+File: hledger_journal.info, Node: Declaring commodities, Next: Default commodity, Prev: Default year, Up: Directives
+
+1.14.4 Declaring commodities
+----------------------------
-1.14.1 Account aliases
-----------------------
+The 'commodity' directive declares commodities which may be used in the
+journal (though currently we do not enforce this). It may be written on
+a single line, like this:
+
+; commodity EXAMPLEAMOUNT
+
+; display AAAA amounts with the symbol on the right, space-separated,
+; using period as decimal point, with four decimal places, and
+; separating thousands with comma.
+commodity 1,000.0000 AAAA
+
+ or on multiple lines, using the "format" subdirective. In this case
+the commodity symbol appears twice and should be the same in both
+places:
+
+; commodity SYMBOL
+; format EXAMPLEAMOUNT
+
+; display indian rupees with currency name on the left,
+; thousands, lakhs and crores comma-separated,
+; period as decimal point, and two decimal places.
+commodity INR
+ format INR 9,99,99,999.00
+
+ Commodity directives have a second purpose: they define the standard
+display format for amounts in the commodity. Normally the display
+format is inferred from journal entries, but this can be unpredictable;
+declaring it with a commodity directive overrides this and removes
+ambiguity. Towards this end, amounts in commodity directives must
+always be written with a decimal point (a period or comma, followed by 0
+or more decimal digits).
+
+
+File: hledger_journal.info, Node: Default commodity, Next: Declaring accounts, Prev: Declaring commodities, Up: Directives
+
+1.14.5 Default commodity
+------------------------
+
+The D directive sets a default commodity (and display format), to be
+used for amounts without a commodity symbol (ie, plain numbers). (Note
+this differs from Ledger's default commodity directive.) The commodity
+and display format will be applied to all subsequent commodity-less
+amounts, or until the next D directive.
+
+# commodity-less amounts should be treated as dollars
+# (and displayed with symbol on the left, thousands separators and two decimal places)
+D $1,000.00
+
+1/1
+ a 5 ; <- commodity-less amount, becomes $1
+ b
+
+ As with the 'commodity' directive, the amount must always be written
+with a decimal point.
+
+
+File: hledger_journal.info, Node: Declaring accounts, Next: Rewriting accounts, Prev: Default commodity, Up: Directives
+
+1.14.6 Declaring accounts
+-------------------------
+
+The 'account' directive predeclares account names. The simplest form is
+'account ACCTNAME', eg:
+
+account assets:bank:checking
+
+ Currently this mainly helps with account name autocompletion in eg
+hledger add, hledger-iadd, hledger-web, and ledger-mode.
+In future it will also help detect misspelled accounts.
+
+ Account names can be followed by a numeric account code:
+
+account assets 1000
+account assets:bank:checking 1110
+account liabilities 2000
+account revenues 4000
+account expenses 6000
+
+ This affects account display order in reports: accounts with codes
+are listed before accounts without codes, in increasing code order.
+(Otherwise, accounts are listed alphabetically.) Account codes should
+be all numeric digits, unique, and separated from the account name by at
+least two spaces (since account names may contain single spaces). By
+convention, often the first digit indicates the type of account, as in
+this numbering scheme and the example above. In future, we might use
+this to recognize account types.
+
+ An account directive can also have indented subdirectives following
+it, which are currently ignored. Here is the full syntax:
+
+; account ACCTNAME [OPTIONALCODE]
+; [OPTIONALSUBDIRECTIVES]
+
+account assets:bank:checking 1110
+ a comment
+ some-tag:12345
+
+
+File: hledger_journal.info, Node: Rewriting accounts, Next: Default parent account, Prev: Declaring accounts, Up: Directives
+
+1.14.7 Rewriting accounts
+-------------------------
You can define aliases which rewrite your account names (after reading
the journal, before generating reports). hledger's account aliases can
@@ -821,7 +976,7 @@ be useful for:
or combining two accounts into one
* customising reports
- See also Cookbook: rewrite account names.
+ See also Cookbook: Rewrite account names.
* Menu:
* Basic aliases::
@@ -830,9 +985,9 @@ be useful for:
* end aliases::

-File: hledger_journal.info, Node: Basic aliases, Next: Regex aliases, Up: Account aliases
+File: hledger_journal.info, Node: Basic aliases, Next: Regex aliases, Up: Rewriting accounts
-1.14.1.1 Basic aliases
+1.14.7.1 Basic aliases
......................
To set an account alias, use the 'alias' directive in your journal file.
@@ -853,9 +1008,9 @@ 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"

-File: hledger_journal.info, Node: Regex aliases, Next: Multiple aliases, Prev: Basic aliases, Up: Account aliases
+File: hledger_journal.info, Node: Regex aliases, Next: Multiple aliases, Prev: Basic aliases, Up: Rewriting accounts
-1.14.1.2 Regex aliases
+1.14.7.2 Regex aliases
......................
There is also a more powerful variant that uses a regular expression,
@@ -878,9 +1033,9 @@ command line, to end of option argument), so it can contain trailing
whitespace.

-File: hledger_journal.info, Node: Multiple aliases, Next: end aliases, Prev: Regex aliases, Up: Account aliases
+File: hledger_journal.info, Node: Multiple aliases, Next: end aliases, Prev: Regex aliases, Up: Rewriting accounts
-1.14.1.3 Multiple aliases
+1.14.7.3 Multiple aliases
.........................
You can define as many aliases as you like using directives or
@@ -894,10 +1049,10 @@ following order:
2. alias options, in the order they appear on the command line

-File: hledger_journal.info, Node: end aliases, Prev: Multiple aliases, Up: Account aliases
+File: hledger_journal.info, Node: end aliases, Prev: Multiple aliases, Up: Rewriting accounts
-1.14.1.4 end aliases
-....................
+1.14.7.4 'end aliases'
+......................
You can clear (forget) all currently defined aliases with the 'end
aliases' directive:
@@ -905,52 +1060,10 @@ aliases' directive:
end aliases

-File: hledger_journal.info, Node: account directive, Next: apply account directive, Prev: Account aliases, Up: Directives
-
-1.14.2 account directive
-------------------------
-
-The 'account' directive predeclares account names. The simplest form is
-'account ACCTNAME', eg:
-
-account assets:bank:checking
-
- Currently this mainly helps with account name autocompletion in eg
-hledger add, hledger-iadd, hledger-web, and ledger-mode.
-In future it will also help detect misspelled accounts.
-
- Account names can be followed by a numeric account code:
+File: hledger_journal.info, Node: Default parent account, Prev: Rewriting accounts, Up: Directives
-account assets 1000
-account assets:bank:checking 1110
-account liabilities 2000
-account revenues 4000
-account expenses 6000
-
- This affects account display order in reports: accounts with codes
-are listed before accounts without codes, in increasing code order.
-(Otherwise, accounts are listed alphabetically.) Account codes should
-be all numeric digits, unique, and separated from the account name by at
-least two spaces (since account names may contain single spaces). By
-convention, often the first digit indicates the type of account, as in
-this numbering scheme and the example above. In future, we might use
-this to recognize account types.
-
- An account directive can also have indented subdirectives following
-it, which are currently ignored. Here is the full syntax:
-
-; account ACCTNAME [OPTIONALCODE]
-; [OPTIONALSUBDIRECTIVES]
-
-account assets:bank:checking 1110
- a comment
- some-tag:12345
-
-
-File: hledger_journal.info, Node: apply account directive, Next: Multi-line comments, Prev: account directive, Up: Directives
-
-1.14.3 apply account directive
-------------------------------
+1.14.8 Default parent account
+-----------------------------
You can specify a parent account which will be prepended to all accounts
within a section of the journal. Use the 'apply account' and 'end apply
@@ -983,148 +1096,52 @@ include personal.journal
supported.

-File: hledger_journal.info, Node: Multi-line comments, Next: commodity directive, Prev: apply account directive, Up: Directives
-
-1.14.4 Multi-line comments
---------------------------
-
-A line containing just 'comment' starts a multi-line comment, and a line
-containing just 'end comment' ends it. See comments.
-
-
-File: hledger_journal.info, Node: commodity directive, Next: Default commodity, Prev: Multi-line comments, Up: Directives
-
-1.14.5 commodity directive
---------------------------
-
-The 'commodity' directive predefines commodities (currently this is just
-informational), and also it may define the display format for amounts in
-this commodity (overriding the automatically inferred format).
-
- It may be written on a single line, like this:
-
-; commodity EXAMPLEAMOUNT
-
-; display AAAA amounts with the symbol on the right, space-separated,
-; using period as decimal point, with four decimal places, and
-; separating thousands with comma.
-commodity 1,000.0000 AAAA
-
- or on multiple lines, using the "format" subdirective. In this case
-the commodity symbol appears twice and should be the same in both
-places:
-
-; commodity SYMBOL
-; format EXAMPLEAMOUNT
-
-; display indian rupees with currency name on the left,
-; thousands, lakhs and crores comma-separated,
-; period as decimal point, and two decimal places.
-commodity INR
- format INR 9,99,99,999.00
-
-
-File: hledger_journal.info, Node: Default commodity, Next: Default year, Prev: commodity directive, Up: Directives
-
-1.14.6 Default commodity
-------------------------
-
-The D directive sets a default commodity (and display format), to be
-used for amounts without a commodity symbol (ie, plain numbers). (Note
-this differs from Ledger's default commodity directive.) The commodity
-and display format will be applied to all subsequent commodity-less
-amounts, or until the next D directive.
-
-# commodity-less amounts should be treated as dollars
-# (and displayed with symbol on the left, thousands separators and two decimal places)
-D $1,000.00
-
-1/1
- a 5 ; <- commodity-less amount, becomes $1
- b
-
-
-File: hledger_journal.info, Node: Default year, Next: Including other files, Prev: Default commodity, Up: Directives
+File: hledger_journal.info, Node: Periodic transactions, Next: Automated postings, Prev: Directives, Up: FILE FORMAT
-1.14.7 Default year
--------------------
+1.15 Periodic transactions
+==========================
-You can set a default year to be used for subsequent dates which don't
-specify a year. This is a line beginning with 'Y' followed by the year.
-Eg:
-
-Y2009 ; set default year to 2009
-
-12/15 ; equivalent to 2009/12/15
- expenses 1
- assets
-
-Y2010 ; change default year to 2010
-
-2009/1/30 ; specifies the year, not affected
- expenses 1
- assets
-
-1/31 ; equivalent to 2010/1/31
- expenses 1
- assets
-
-
-File: hledger_journal.info, Node: Including other files, Prev: Default year, Up: Directives
-
-1.14.8 Including other files
-----------------------------
-
-You can pull in the content of additional journal files by writing an
-include directive, like this:
-
-include path/to/file.journal
-
- If the path does not begin with a slash, it is relative to the
-current file. Glob patterns ('*') are not currently supported.
-
- The 'include' directive can only be used in journal files. It can
-include journal, timeclock or timedot files, but not CSV files.
-
-
-File: hledger_journal.info, Node: Periodic transactions, Next: Automated postings, Prev: FILE FORMAT, Up: Top
-
-2 Periodic transactions
-***********************
-
-Periodic transactions are a kind of rule with a dual purpose: they can
-specify recurring future transactions (with '--forecast'), or budget
-goals (with '--budget'). They look a bit like a transaction, except the
-first line is a tilde ('~') followed by a period expression:
+Periodic transaction rules (enabled by '--forecast' or '--budget')
+describe recurring transactions. They look like a transaction where the
+first line is a tilde ('~') followed by a period expression (mnemonic:
+'~' is like a recurring sine wave):
~ weekly
assets:bank:checking $400 ; paycheck
income:acme inc
- With '--forecast', each periodic transaction rule generates recurring
-"forecast" transactions at the specified interval, beginning the day
-after the latest recorded journal transaction (or today, if there are no
-transactions) and ending 6 months from today (or at the report end date,
-if specified).
+ Periodic transactions have a dual purpose:
+
+ * With '--forecast', each periodic transaction rule generates future
+ transactions, recurring at the specified interval, which can be
+ seen in reports. Forecast transactions begin the day after the
+ latest recorded journal transaction (or today, if there are no
+ transactions) and end 6 months from today (or at the report end
+ date, if specified).
- With 'balance --budget', each periodic transaction declares recurring
-budget goals for the specified accounts. Eg the example above declares
-the goal of receiving $400 from 'income:acme inc' (and also, depositing
-$400 into 'assets:bank:checking') every week.
+ * With '--budget' (supported by the balance command), each periodic
+ transaction rule declares recurring budget goals for the specified
+ accounts, which can be seen in budget reports. Eg the example
+ above declares the goal of receiving $400 from 'income:acme inc'
+ (and also, depositing $400 into 'assets:bank:checking') every week.
- For more details, see: balance: Budgeting and Budgeting and
-Forecasting.
+ (Actually, you can generate one-off transactions too, by writing a
+period expression with no report interval.)
+
+ For more details, see: balance: Budget report and Cookbook: Budgeting
+and Forecasting.

-File: hledger_journal.info, Node: Automated postings, Next: EDITOR SUPPORT, Prev: Periodic transactions, Up: Top
+File: hledger_journal.info, Node: Automated postings, Prev: Periodic transactions, Up: FILE FORMAT
-3 Automated postings
-********************
+1.16 Automated postings
+=======================
-Automated postings are postings added automatically by rule to certain
-transactions (with '--auto'). An automated posting rule looks like a
-transaction where the first line is an equal sign ('=') followed by a
-query:
+Automated postings (enabled by '--auto') are postings added
+automatically by rule to certain transactions. An automated posting
+rule looks like a transaction where the first line is an equal sign
+('=') followed by a query (mnemonic: '=' tests for matching
+transactions, and also looks like posting lines):
= expenses:gifts
budget:gifts *-1
@@ -1149,13 +1166,16 @@ every transaction involving the 'expenses:gifts' account:
$ hledger print --auto
2017/12/14
expenses:gifts $20
- assets
(budget:gifts) $-20
+ assets
+
+ Like postings recorded by hand, automated postings participate in
+transaction balancing, missing amount inference and balance assertions.

-File: hledger_journal.info, Node: EDITOR SUPPORT, Prev: Automated postings, Up: Top
+File: hledger_journal.info, Node: EDITOR SUPPORT, Prev: FILE FORMAT, Up: Top
-4 EDITOR SUPPORT
+2 EDITOR SUPPORT
****************
Add-on modes exist for various text editors, to make working with
@@ -1182,89 +1202,89 @@ Code

Tag Table:
Node: Top76
-Node: FILE FORMAT2419
-Ref: #file-format2550
-Node: Transactions2773
-Ref: #transactions2894
-Node: Postings3578
-Ref: #postings3705
-Node: Dates4700
-Ref: #dates4815
-Node: Simple dates4880
-Ref: #simple-dates5006
-Node: Secondary dates5372
-Ref: #secondary-dates5526
-Node: Posting dates7089
-Ref: #posting-dates7218
-Node: Status8592
-Ref: #status8712
-Node: Description10420
-Ref: #description10558
-Node: Payee and note10877
-Ref: #payee-and-note10991
-Node: Account names11233
-Ref: #account-names11376
-Node: Amounts11863
-Ref: #amounts11999
-Node: Virtual Postings15014
-Ref: #virtual-postings15173
-Node: Balance Assertions16393
-Ref: #balance-assertions16568
-Node: Assertions and ordering17464
-Ref: #assertions-and-ordering17650
-Node: Assertions and included files18350
-Ref: #assertions-and-included-files18591
-Node: Assertions and multiple -f options18924
-Ref: #assertions-and-multiple--f-options19178
-Node: Assertions and commodities19310
-Ref: #assertions-and-commodities19545
-Node: Assertions and subaccounts20241
-Ref: #assertions-and-subaccounts20473
-Node: Assertions and virtual postings20994
-Ref: #assertions-and-virtual-postings21201
-Node: Balance Assignments21343
-Ref: #balance-assignments21512
-Node: Prices22632
-Ref: #prices22765
-Node: Transaction prices22816
-Ref: #transaction-prices22961
-Node: Market prices25117
-Ref: #market-prices25252
-Node: Comments26212
-Ref: #comments26334
-Node: Tags27576
-Ref: #tags27694
-Node: Directives29096
-Ref: #directives29209
-Node: Account aliases29402
-Ref: #account-aliases29546
-Node: Basic aliases30150
-Ref: #basic-aliases30293
-Node: Regex aliases30983
-Ref: #regex-aliases31151
-Node: Multiple aliases31869
-Ref: #multiple-aliases32041
-Node: end aliases32539
-Ref: #end-aliases32679
-Node: account directive32780
-Ref: #account-directive32960
-Node: apply account directive34307
-Ref: #apply-account-directive34503
-Node: Multi-line comments35162
-Ref: #multi-line-comments35352
-Node: commodity directive35480
-Ref: #commodity-directive35664
-Node: Default commodity36536
-Ref: #default-commodity36709
-Node: Default year37246
-Ref: #default-year37411
-Node: Including other files37834
-Ref: #including-other-files37991
-Node: Periodic transactions38388
-Ref: #periodic-transactions38554
-Node: Automated postings39543
-Ref: #automated-postings39706
-Node: EDITOR SUPPORT40608
-Ref: #editor-support40733
+Node: FILE FORMAT2374
+Ref: #file-format2498
+Node: Transactions2770
+Ref: #transactions2891
+Node: Postings3575
+Ref: #postings3702
+Node: Dates4697
+Ref: #dates4812
+Node: Simple dates4877
+Ref: #simple-dates5003
+Node: Secondary dates5369
+Ref: #secondary-dates5523
+Node: Posting dates7086
+Ref: #posting-dates7215
+Node: Status8589
+Ref: #status8709
+Node: Description10417
+Ref: #description10555
+Node: Payee and note10874
+Ref: #payee-and-note10988
+Node: Account names11230
+Ref: #account-names11373
+Node: Amounts11860
+Ref: #amounts11996
+Node: Virtual Postings15011
+Ref: #virtual-postings15170
+Node: Balance Assertions16390
+Ref: #balance-assertions16565
+Node: Assertions and ordering17461
+Ref: #assertions-and-ordering17647
+Node: Assertions and included files18347
+Ref: #assertions-and-included-files18588
+Node: Assertions and multiple -f options18921
+Ref: #assertions-and-multiple--f-options19175
+Node: Assertions and commodities19307
+Ref: #assertions-and-commodities19542
+Node: Assertions and subaccounts20238
+Ref: #assertions-and-subaccounts20470
+Node: Assertions and virtual postings20991
+Ref: #assertions-and-virtual-postings21198
+Node: Balance Assignments21340
+Ref: #balance-assignments21509
+Node: Prices22629
+Ref: #prices22762
+Node: Transaction prices22813
+Ref: #transaction-prices22958
+Node: Market prices25114
+Ref: #market-prices25249
+Node: Comments26209
+Ref: #comments26331
+Node: Tags27501
+Ref: #tags27619
+Node: Directives29021
+Ref: #directives29164
+Node: Comment blocks29357
+Ref: #comment-blocks29502
+Node: Including other files29678
+Ref: #including-other-files29858
+Node: Default year30247
+Ref: #default-year30416
+Node: Declaring commodities30839
+Ref: #declaring-commodities31022
+Node: Default commodity32249
+Ref: #default-commodity32430
+Node: Declaring accounts33062
+Ref: #declaring-accounts33242
+Node: Rewriting accounts34589
+Ref: #rewriting-accounts34774
+Node: Basic aliases35378
+Ref: #basic-aliases35524
+Node: Regex aliases36214
+Ref: #regex-aliases36385
+Node: Multiple aliases37103
+Ref: #multiple-aliases37278
+Node: end aliases37776
+Ref: #end-aliases37923
+Node: Default parent account38024
+Ref: #default-parent-account38190
+Node: Periodic transactions38849
+Ref: #periodic-transactions39028
+Node: Automated postings40327
+Ref: #automated-postings40481
+Node: EDITOR SUPPORT41614
+Ref: #editor-support41732

End Tag Table
diff --git a/hledger_journal.txt b/hledger_journal.txt
index 485f4d2..dc6e261 100644
--- a/hledger_journal.txt
+++ b/hledger_journal.txt
@@ -553,10 +553,6 @@ FILE FORMAT
nodes to be ignored, allowing emacs users to fold and navigate their
journals with org-mode or orgstruct-mode.)
- Also, anything between comment and end comment directives is a
- (multi-line) comment. If there is no end comment, the comment extends
- to the end of the file.
-
You can attach comments to a transaction by writing them after the
description and/or indented on the following lines (before the post-
ings). Similarly, you can attach comments to an individual posting by
@@ -584,21 +580,24 @@ FILE FORMAT
; another comment line for posting 2
; a file comment (because not indented)
+ You can also comment larger regions of a file using comment and
+ end comment directives.
+
Tags
- Tags are a way to add extra labels or labelled data to postings and
+ Tags are a way to add extra labels or labelled data to postings and
transactions, which you can then search or pivot on.
- A simple tag is a word (which may contain hyphens) followed by a full
+ A simple tag is a word (which may contain hyphens) followed by a full
colon, written inside a transaction or posting comment line:
2017/1/16 bought groceries ; sometag:
- Tags can have a value, which is the text after the colon, up to the
+ Tags can have a value, which is the text after the colon, up to the
next comma or end of line, with leading/trailing whitespace removed:
expenses:food $10 ; a-posting-tag: the tag value
- Note this means hledger's tag values can not contain commas or new-
+ Note this means hledger's tag values can not contain commas or new-
lines. Ending at commas means you can write multiple short tags on one
line, comma separated:
@@ -612,21 +611,147 @@ FILE FORMAT
o "tag2" is another tag, whose value is "some value ..."
- Tags in a transaction comment affect the transaction and all of its
- postings, while tags in a posting comment affect only that posting.
- For example, the following transaction has three tags (A, TAG2,
+ Tags in a transaction comment affect the transaction and all of its
+ postings, while tags in a posting comment affect only that posting.
+ For example, the following transaction has three tags (A, TAG2,
third-tag) and the posting has four (those plus posting-tag):
1/1 a transaction ; A:, TAG2:
; third-tag: a third transaction tag, <- with a value
(a) $1 ; posting-tag:
- Tags are like Ledger's metadata feature, except hledger's tag values
+ Tags are like Ledger's metadata feature, except hledger's tag values
are simple strings.
Directives
- Account aliases
- You can define aliases which rewrite your account names (after reading
+ Comment blocks
+ A line containing just comment starts a commented region of the file,
+ and a line containing just end comment (or the end of the current file)
+ ends it. See also comments.
+
+ Including other files
+ You can pull in the content of additional files by writing an include
+ directive, like this:
+
+ include path/to/file.journal
+
+ If the path does not begin with a slash, it is relative to the current
+ file. Glob patterns (*) are not currently supported.
+
+ The include directive can only be used in journal files. It can
+ include journal, timeclock or timedot files, but not CSV files.
+
+ Default year
+ You can set a default year to be used for subsequent dates which don't
+ specify a year. This is a line beginning with Y followed by the year.
+ Eg:
+
+ Y2009 ; set default year to 2009
+
+ 12/15 ; equivalent to 2009/12/15
+ expenses 1
+ assets
+
+ Y2010 ; change default year to 2010
+
+ 2009/1/30 ; specifies the year, not affected
+ expenses 1
+ assets
+
+ 1/31 ; equivalent to 2010/1/31
+ expenses 1
+ assets
+
+ Declaring commodities
+ The commodity directive declares commodities which may be used in the
+ journal (though currently we do not enforce this). It may be written
+ on a single line, like this:
+
+ ; commodity EXAMPLEAMOUNT
+
+ ; display AAAA amounts with the symbol on the right, space-separated,
+ ; using period as decimal point, with four decimal places, and
+ ; separating thousands with comma.
+ commodity 1,000.0000 AAAA
+
+ or on multiple lines, using the "format" subdirective. In this case
+ the commodity symbol appears twice and should be the same in both
+ places:
+
+ ; commodity SYMBOL
+ ; format EXAMPLEAMOUNT
+
+ ; display indian rupees with currency name on the left,
+ ; thousands, lakhs and crores comma-separated,
+ ; period as decimal point, and two decimal places.
+ commodity INR
+ format INR 9,99,99,999.00
+
+ Commodity directives have a second purpose: they define the standard
+ display format for amounts in the commodity. Normally the display for-
+ mat is inferred from journal entries, but this can be unpredictable;
+ declaring it with a commodity directive overrides this and removes
+ ambiguity. Towards this end, amounts in commodity directives must
+ always be written with a decimal point (a period or comma, followed by
+ 0 or more decimal digits).
+
+ Default commodity
+ The D directive sets a default commodity (and display format), to be
+ used for amounts without a commodity symbol (ie, plain numbers). (Note
+ this differs from Ledger's default commodity directive.) The commodity
+ and display format will be applied to all subsequent commodity-less
+ amounts, or until the next D directive.
+
+ # commodity-less amounts should be treated as dollars
+ # (and displayed with symbol on the left, thousands separators and two decimal places)
+ D $1,000.00
+
+ 1/1
+ a 5 ; <- commodity-less amount, becomes $1
+ b
+
+ As with the commodity directive, the amount must always be written with
+ a decimal point.
+
+ Declaring accounts
+ The account directive predeclares account names. The simplest form is
+ account ACCTNAME, eg:
+
+ account assets:bank:checking
+
+ Currently this mainly helps with account name autocompletion in eg
+ hledger add, hledger-iadd, hledger-web, and ledger-mode.
+ In future it will also help detect misspelled accounts.
+
+ Account names can be followed by a numeric account code:
+
+ account assets 1000
+ account assets:bank:checking 1110
+ account liabilities 2000
+ account revenues 4000
+ account expenses 6000
+
+ This affects account display order in reports: accounts with codes are
+ listed before accounts without codes, in increasing code order. (Oth-
+ erwise, accounts are listed alphabetically.) Account codes should be
+ all numeric digits, unique, and separated from the account name by at
+ least two spaces (since account names may contain single spaces). By
+ convention, often the first digit indicates the type of account, as in
+ this numbering scheme and the example above. In future, we might use
+ this to recognize account types.
+
+ An account directive can also have indented subdirectives following it,
+ which are currently ignored. Here is the full syntax:
+
+ ; account ACCTNAME [OPTIONALCODE]
+ ; [OPTIONALSUBDIRECTIVES]
+
+ account assets:bank:checking 1110
+ a comment
+ some-tag:12345
+
+ Rewriting accounts
+ You can define aliases which rewrite your account names (after reading
the journal, before generating reports). hledger's account aliases can
be useful for:
@@ -640,11 +765,11 @@ FILE FORMAT
o customising reports
- See also Cookbook: rewrite account names.
+ 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
@@ -652,91 +777,54 @@ FILE FORMAT
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-
+ 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-
+ 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
- of applying previous ones. (This is different from Ledger, where
+ You can define as many aliases as you like using directives or com-
+ mand-line options. Aliases are recursive - each alias sees the result
+ of applying previous ones. (This is different from Ledger, where
aliases are non-recursive by default). Aliases are applied in the fol-
lowing order:
- 1. alias directives, most recently seen first (recent directives take
+ 1. alias directives, most recently seen first (recent directives take
precedence over earlier ones; directives not yet seen are ignored)
2. alias options, in the order they appear on the command line
end aliases
- You can clear (forget) all currently defined aliases with the
+ You can clear (forget) all currently defined aliases with the
end aliases directive:
end aliases
- account directive
- The account directive predeclares account names. The simplest form is
- account ACCTNAME, eg:
-
- account assets:bank:checking
-
- Currently this mainly helps with account name autocompletion in eg
- hledger add, hledger-iadd, hledger-web, and ledger-mode.
- In future it will also help detect misspelled accounts.
-
- Account names can be followed by a numeric account code:
-
- account assets 1000
- account assets:bank:checking 1110
- account liabilities 2000
- account revenues 4000
- account expenses 6000
-
- This affects account display order in reports: accounts with codes are
- listed before accounts without codes, in increasing code order. (Oth-
- erwise, accounts are listed alphabetically.) Account codes should be
- all numeric digits, unique, and separated from the account name by at
- least two spaces (since account names may contain single spaces). By
- convention, often the first digit indicates the type of account, as in
- this numbering scheme and the example above. In future, we might use
- this to recognize account types.
-
- An account directive can also have indented subdirectives following it,
- which are currently ignored. Here is the full syntax:
-
- ; account ACCTNAME [OPTIONALCODE]
- ; [OPTIONALSUBDIRECTIVES]
-
- account assets:bank:checking 1110
- a comment
- some-tag:12345
-
- apply account directive
- You can specify a parent account which will be prepended to all
- accounts within a section of the journal. Use the apply account and
+ Default parent account
+ You can specify a parent account which will be prepended to all
+ accounts within a section of the journal. Use the apply account and
end apply account directives like so:
apply account home
@@ -753,7 +841,7 @@ FILE FORMAT
home:food $10
home:cash $-10
- If end apply account is omitted, the effect lasts to the end of the
+ If end apply account is omitted, the effect lasts to the end of the
file. Included files are also affected, eg:
apply account business
@@ -762,129 +850,58 @@ FILE FORMAT
apply account personal
include personal.journal
- Prior to hledger 1.0, legacy account and end spellings were also sup-
+ Prior to hledger 1.0, legacy account and end spellings were also sup-
ported.
- Multi-line comments
- A line containing just comment starts a multi-line comment, and a line
- containing just end comment ends it. See comments.
-
- commodity directive
- The commodity directive predefines commodities (currently this is just
- informational), and also it may define the display format for amounts
- in this commodity (overriding the automatically inferred format).
-
- It may be written on a single line, like this:
-
- ; commodity EXAMPLEAMOUNT
-
- ; display AAAA amounts with the symbol on the right, space-separated,
- ; using period as decimal point, with four decimal places, and
- ; separating thousands with comma.
- commodity 1,000.0000 AAAA
-
- or on multiple lines, using the "format" subdirective. In this case
- the commodity symbol appears twice and should be the same in both
- places:
-
- ; commodity SYMBOL
- ; format EXAMPLEAMOUNT
-
- ; display indian rupees with currency name on the left,
- ; thousands, lakhs and crores comma-separated,
- ; period as decimal point, and two decimal places.
- commodity INR
- format INR 9,99,99,999.00
-
- Default commodity
- The D directive sets a default commodity (and display format), to be
- used for amounts without a commodity symbol (ie, plain numbers). (Note
- this differs from Ledger's default commodity directive.) The commodity
- and display format will be applied to all subsequent commodity-less
- amounts, or until the next D directive.
-
- # commodity-less amounts should be treated as dollars
- # (and displayed with symbol on the left, thousands separators and two decimal places)
- D $1,000.00
-
- 1/1
- a 5 ; <- commodity-less amount, becomes $1
- b
-
- Default year
- You can set a default year to be used for subsequent dates which don't
- specify a year. This is a line beginning with Y followed by the year.
- Eg:
-
- Y2009 ; set default year to 2009
-
- 12/15 ; equivalent to 2009/12/15
- expenses 1
- assets
-
- Y2010 ; change default year to 2010
-
- 2009/1/30 ; specifies the year, not affected
- expenses 1
- assets
-
- 1/31 ; equivalent to 2010/1/31
- expenses 1
- assets
-
- Including other files
- You can pull in the content of additional journal files by writing an
- include directive, like this:
-
- include path/to/file.journal
-
- If the path does not begin with a slash, it is relative to the current
- file. Glob patterns (*) are not currently supported.
-
- The include directive can only be used in journal files. It can
- include journal, timeclock or timedot files, but not CSV files.
-
-Periodic transactions
- Periodic transactions are a kind of rule with a dual purpose: they can
- specify recurring future transactions (with --forecast), or budget
- goals (with --budget). They look a bit like a transaction, except the
- first line is a tilde (~) followed by a period expression:
+ Periodic transactions
+ Periodic transaction rules (enabled by --forecast or --budget) describe
+ recurring transactions. They look like a transaction where the first
+ line is a tilde (~) followed by a period expression (mnemonic: ~ is
+ like a recurring sine wave):
~ weekly
assets:bank:checking $400 ; paycheck
income:acme inc
- With --forecast, each periodic transaction rule generates recurring
- "forecast" transactions at the specified interval, beginning the day
- after the latest recorded journal transaction (or today, if there are
- no transactions) and ending 6 months from today (or at the report end
- date, if specified).
+ Periodic transactions have a dual purpose:
- With balance --budget, each periodic transaction declares recurring
- budget goals for the specified accounts. Eg the example above declares
- the goal of receiving $400 from income:acme inc (and also, depositing
- $400 into assets:bank:checking) every week.
+ o With --forecast, each periodic transaction rule generates future
+ transactions, recurring at the specified interval, which can be seen
+ in reports. Forecast transactions begin the day after the latest
+ recorded journal transaction (or today, if there are no transactions)
+ and end 6 months from today (or at the report end date, if speci-
+ fied).
- For more details, see: balance: Budgeting and Budgeting and Forecast-
- ing.
+ o With --budget (supported by the balance command), each periodic
+ transaction rule declares recurring budget goals for the specified
+ accounts, which can be seen in budget reports. Eg the example above
+ declares the goal of receiving $400 from income:acme inc (and also,
+ depositing $400 into assets:bank:checking) every week.
-Automated postings
- Automated postings are postings added automatically by rule to certain
- transactions (with --auto). An automated posting rule looks like a
- transaction where the first line is an equal sign (=) followed by a
- query:
+ (Actually, you can generate one-off transactions too, by writing a
+ period expression with no report interval.)
+
+ For more details, see: balance: Budget report and Cookbook: Budgeting
+ and Forecasting.
+
+ Automated postings
+ Automated postings (enabled by --auto) are postings added automatically
+ by rule to certain transactions. An automated posting rule looks like
+ a transaction where the first line is an equal sign (=) followed by a
+ query (mnemonic: = tests for matching transactions, and also looks like
+ posting lines):
= expenses:gifts
budget:gifts *-1
assets:budget *1
- The posting amounts can be of the form *N, which means "the amount of
- the matched transaction's first posting, multiplied by N". They can
+ The posting amounts can be of the form *N, which means "the amount of
+ the matched transaction's first posting, multiplied by N". They can
also be ordinary fixed amounts. Fixed amounts with no commodity symbol
- will be given the same commodity as the matched transaction's first
+ will be given the same commodity as the matched transaction's first
posting.
- This example adds a corresponding (unbalanced) budget posting to every
+ This example adds a corresponding (unbalanced) budget posting to every
transaction involving the expenses:gifts account:
= expenses:gifts
@@ -897,16 +914,19 @@ Automated postings
$ hledger print --auto
2017/12/14
expenses:gifts $20
- assets
(budget:gifts) $-20
+ assets
+
+ Like postings recorded by hand, automated postings participate in
+ transaction balancing, missing amount inference and balance assertions.
EDITOR SUPPORT
Add-on modes exist for various text editors, to make working with jour-
- nal files easier. They add colour, navigation aids and helpful com-
- mands. For hledger users who edit the journal file directly (the
+ nal files easier. They add colour, navigation aids and helpful com-
+ mands. For hledger users who edit the journal file directly (the
majority), using one of these modes is quite recommended.
- These were written with Ledger in mind, but also work with hledger
+ These were written with Ledger in mind, but also work with hledger
files:
@@ -925,7 +945,7 @@ EDITOR SUPPORT
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)
@@ -939,7 +959,7 @@ COPYRIGHT
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)
@@ -947,4 +967,4 @@ SEE ALSO
-hledger 1.9 March 2018 hledger_journal(5)
+hledger 1.9.1 April 2018 hledger_journal(5)
diff --git a/hledger_timeclock.5 b/hledger_timeclock.5
index feb2a16..98f36e3 100644
--- a/hledger_timeclock.5
+++ b/hledger_timeclock.5
@@ -1,5 +1,5 @@
-.TH "hledger_timeclock" "5" "March 2018" "hledger 1.9" "hledger User Manuals"
+.TH "hledger_timeclock" "5" "April 2018" "hledger 1.9.1" "hledger User Manuals"
diff --git a/hledger_timeclock.info b/hledger_timeclock.info
index b521d40..780b0aa 100644
--- a/hledger_timeclock.info
+++ b/hledger_timeclock.info
@@ -4,8 +4,8 @@ stdin.

File: hledger_timeclock.info, Node: Top, Up: (dir)
-hledger_timeclock(5) hledger 1.9
-********************************
+hledger_timeclock(5) hledger 1.9.1
+**********************************
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
diff --git a/hledger_timeclock.txt b/hledger_timeclock.txt
index 0add1b2..d57f4f6 100644
--- a/hledger_timeclock.txt
+++ b/hledger_timeclock.txt
@@ -77,4 +77,4 @@ SEE ALSO
-hledger 1.9 March 2018 hledger_timeclock(5)
+hledger 1.9.1 April 2018 hledger_timeclock(5)
diff --git a/hledger_timedot.5 b/hledger_timedot.5
index 45f5e24..ea203c7 100644
--- a/hledger_timedot.5
+++ b/hledger_timedot.5
@@ -1,5 +1,5 @@
-.TH "hledger_timedot" "5" "March 2018" "hledger 1.9" "hledger User Manuals"
+.TH "hledger_timedot" "5" "April 2018" "hledger 1.9.1" "hledger User Manuals"
diff --git a/hledger_timedot.info b/hledger_timedot.info
index 0b77c91..96268f9 100644
--- a/hledger_timedot.info
+++ b/hledger_timedot.info
@@ -4,8 +4,8 @@ stdin.

File: hledger_timedot.info, Node: Top, Next: FILE FORMAT, Up: (dir)
-hledger_timedot(5) hledger 1.9
-******************************
+hledger_timedot(5) hledger 1.9.1
+********************************
Timedot is a plain text format for logging dated, categorised quantities
(of time, usually), supported by hledger. It is convenient for
@@ -110,7 +110,7 @@ $ hledger -f t.timedot --alias /\\./=: bal date:2016/2/4

Tag Table:
Node: Top76
-Node: FILE FORMAT805
-Ref: #file-format906
+Node: FILE FORMAT809
+Ref: #file-format910

End Tag Table
diff --git a/hledger_timedot.txt b/hledger_timedot.txt
index 64eb4c7..6ba0028 100644
--- a/hledger_timedot.txt
+++ b/hledger_timedot.txt
@@ -124,4 +124,4 @@ SEE ALSO
-hledger 1.9 March 2018 hledger_timedot(5)
+hledger 1.9.1 April 2018 hledger_timedot(5)