summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.md11
-rw-r--r--Hledger/Data/Dates.hs7
-rw-r--r--Hledger/Data/Journal.hs148
-rw-r--r--Hledger/Read.hs27
-rw-r--r--Hledger/Read/Common.hs10
-rw-r--r--Hledger/Read/CsvReader.hs329
-rw-r--r--Hledger/Read/JournalReader.hs19
-rw-r--r--Hledger/Utils.hs11
-rw-r--r--Hledger/Utils/Debug.hs19
-rw-r--r--Hledger/Utils/Test.hs35
-rw-r--r--hledger-lib.cabal15
-rw-r--r--hledger_csv.556
-rw-r--r--hledger_csv.info218
-rw-r--r--hledger_csv.txt63
-rw-r--r--hledger_journal.5119
-rw-r--r--hledger_journal.info410
-rw-r--r--hledger_journal.txt264
-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.info4
-rw-r--r--hledger_timedot.txt2
-rw-r--r--test/doctests.hs1
-rw-r--r--test/unittest.hs8
25 files changed, 1005 insertions, 781 deletions
diff --git a/CHANGES.md b/CHANGES.md
index f90dcb7..650270d 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,6 +1,17 @@
Internal/api/developer-ish changes in the hledger-lib (and hledger) packages.
For user-visible changes, see the hledger package changelog.
+# 1.17.1 2020-03-19
+
+- require newer Decimal, math-functions libs to ensure consistent
+ rounding behaviour, even when built with old GHCs/snapshots.
+ hledger uses banker's rounding (rounds to nearest even number, eg
+ 0.5 with with zero decimal places is "0").
+
+- added: debug helpers traceAt, traceAtWith
+
+- Journal is now a Semigroup, not a Monoid (since <> is right-biased). (Stephen Morgan)
+
# 1.17.0.1 2020-03-01
- fix org heading comments and doctest setup comment that were
diff --git a/Hledger/Data/Dates.hs b/Hledger/Data/Dates.hs
index c734b70..9b44f52 100644
--- a/Hledger/Data/Dates.hs
+++ b/Hledger/Data/Dates.hs
@@ -84,6 +84,7 @@ import Control.Applicative.Permutations
import Control.Monad (guard, unless)
import "base-compat-batteries" Data.List.Compat
import Data.Default
+import Data.Foldable (asum)
import Data.Maybe
import qualified Data.Set as Set
import Data.Text (Text)
@@ -654,7 +655,7 @@ advancetonthweekday n wd s =
-- -- | Parse a couple of date-time string formats to a time type.
-- parsedatetimeM :: String -> Maybe LocalTime
--- parsedatetimeM s = firstJust [
+-- parsedatetimeM s = asum [
-- parseTime defaultTimeLocale "%Y/%m/%d %H:%M:%S" s,
-- parseTime defaultTimeLocale "%Y-%m-%d %H:%M:%S" s
-- ]
@@ -672,9 +673,9 @@ parsetime =
-- `YYYY-MM-DD`, `YYYY/MM/DD` or `YYYY.MM.DD`, with leading zeros required.
-- For internal use, not quite the same as the journal's "simple dates".
parsedateM :: String -> Maybe Day
-parsedateM s = firstJust [
- parsetime defaultTimeLocale "%Y/%m/%d" s,
+parsedateM s = asum [
parsetime defaultTimeLocale "%Y-%m-%d" s,
+ parsetime defaultTimeLocale "%Y/%m/%d" s,
parsetime defaultTimeLocale "%Y.%m.%d" s
]
diff --git a/Hledger/Data/Journal.hs b/Hledger/Data/Journal.hs
index 377baeb..b010753 100644
--- a/Hledger/Data/Journal.hs
+++ b/Hledger/Data/Journal.hs
@@ -90,16 +90,17 @@ import Control.Monad.Extra
import Control.Monad.Reader as R
import Control.Monad.ST
import Data.Array.ST
+import Data.Default (Default(..))
import Data.Function ((&))
+import qualified Data.HashTable.Class as H (toList)
import qualified Data.HashTable.ST.Cuckoo as H
import Data.List
import Data.List.Extra (groupSort, nubSort)
import qualified Data.Map as M
import Data.Maybe
#if !(MIN_VERSION_base(4,11,0))
-import Data.Monoid
+import Data.Semigroup (Semigroup(..))
#endif
-import qualified Data.Semigroup as Sem
import qualified Data.Set as S
import Data.Text (Text)
import qualified Data.Text as T
@@ -157,7 +158,7 @@ instance Show Journal where
-- ,show $ map fst $ jfiles j
-- ]
--- The monoid instance for Journal is useful for two situations.
+-- The semigroup instance for Journal is useful for two situations.
--
-- 1. concatenating finalised journals, eg with multiple -f options:
-- FIRST <> SECOND. The second's list fields are appended to the
@@ -168,7 +169,9 @@ instance Show Journal where
-- CHILD <> PARENT. A parsed journal's data is in reverse order, so
-- this gives what we want.
--
-instance Sem.Semigroup Journal where
+-- Note that (<>) is right-biased, so nulljournal is only a left identity.
+-- In particular, this prevents Journal from being a monoid.
+instance Semigroup Journal where
j1 <> j2 = Journal {
jparsedefaultyear = jparsedefaultyear j2
,jparsedefaultcommodity = jparsedefaultcommodity j2
@@ -190,12 +193,8 @@ instance Sem.Semigroup Journal where
,jlastreadtime = max (jlastreadtime j1) (jlastreadtime j2)
}
-instance Monoid Journal where
- mempty = nulljournal
-#if !(MIN_VERSION_base(4,11,0))
- -- This is redundant starting with base-4.11 / GHC 8.4.
- mappend = (Sem.<>)
-#endif
+instance Default Journal where
+ def = nulljournal
nulljournal :: Journal
nulljournal = Journal {
@@ -587,7 +586,29 @@ journalModifyTransactions j = j{ jtxns = modifyTransactions (jtxnmodifiers j) (j
journalCheckBalanceAssertions :: Journal -> Maybe String
journalCheckBalanceAssertions = either Just (const Nothing) . journalBalanceTransactions True
--- "Transaction balancing" - inferring missing amounts and checking transaction balancedness and balance assertions
+-- "Transaction balancing", including: inferring missing amounts,
+-- applying balance assignments, checking transaction balancedness,
+-- checking balance assertions, respecting posting dates. These things
+-- are all interdependent.
+-- WARN tricky algorithm and code ahead.
+--
+-- Code overview as of 20190219, this could/should be simplified/documented more:
+-- parseAndFinaliseJournal['] (Cli/Utils.hs), journalAddForecast (Common.hs), budgetJournal (BudgetReport.hs), tests (BalanceReport.hs)
+-- journalBalanceTransactions
+-- runST
+-- runExceptT
+-- balanceTransaction (Transaction.hs)
+-- balanceTransactionHelper
+-- runReaderT
+-- balanceTransactionAndCheckAssertionsB
+-- addAmountAndCheckAssertionB
+-- addOrAssignAmountAndCheckAssertionB
+-- balanceTransactionHelper (Transaction.hs)
+-- uiCheckBalanceAssertions d ui@UIState{aopts=UIOpts{cliopts_=copts}, ajournal=j} (ErrorScreen.hs)
+-- journalCheckBalanceAssertions
+-- journalBalanceTransactions
+-- transactionWizard, postingsBalanced (Add.hs), tests (Transaction.hs)
+-- balanceTransaction (Transaction.hs) XXX hledger add won't allow balance assignments + missing amount ?
-- | Monad used for statefully balancing/amount-inferring/assertion-checking
-- a sequence of transactions.
@@ -603,37 +624,54 @@ data BalancingState s = BalancingState {
,bsAssrt :: Bool -- ^ whether to check balance assertions
-- mutable
,bsBalances :: H.HashTable s AccountName MixedAmount -- ^ running account balances, initially empty
- ,bsTransactions :: STArray s Integer Transaction -- ^ the transactions being balanced
+ ,bsTransactions :: STArray s Integer Transaction -- ^ a mutable array of the transactions being balanced
+ -- (for efficiency ? journalBalanceTransactions says: not strictly necessary but avoids a sort at the end I think)
}
-- | Access the current balancing state, and possibly modify the mutable bits,
-- lifting through the Except and Reader layers into the Balancing monad.
-withB :: (BalancingState s -> ST s a) -> Balancing s a
-withB f = ask >>= lift . lift . f
+withRunningBalance :: (BalancingState s -> ST s a) -> Balancing s a
+withRunningBalance f = ask >>= lift . lift . f
--- | Get an account's running balance so far.
-getAmountB :: AccountName -> Balancing s MixedAmount
-getAmountB acc = withB $ \BalancingState{bsBalances} -> do
+-- | Get this account's current exclusive running balance.
+getRunningBalanceB :: AccountName -> Balancing s MixedAmount
+getRunningBalanceB acc = withRunningBalance $ \BalancingState{bsBalances} -> do
fromMaybe 0 <$> H.lookup bsBalances acc
--- | Add an amount to an account's running balance, and return the new running balance.
-addAmountB :: AccountName -> MixedAmount -> Balancing s MixedAmount
-addAmountB acc amt = withB $ \BalancingState{bsBalances} -> do
+-- | Add this amount to this account's exclusive running balance.
+-- Returns the new running balance.
+addToRunningBalanceB :: AccountName -> MixedAmount -> Balancing s MixedAmount
+addToRunningBalanceB acc amt = withRunningBalance $ \BalancingState{bsBalances} -> do
old <- fromMaybe 0 <$> H.lookup bsBalances acc
let new = old + amt
H.insert bsBalances acc new
return new
--- | Set an account's running balance to this amount, and return the difference from the old.
-setAmountB :: AccountName -> MixedAmount -> Balancing s MixedAmount
-setAmountB acc amt = withB $ \BalancingState{bsBalances} -> do
+-- | Set this account's exclusive running balance to this amount.
+-- Returns the change in exclusive running balance.
+setRunningBalanceB :: AccountName -> MixedAmount -> Balancing s MixedAmount
+setRunningBalanceB acc amt = withRunningBalance $ \BalancingState{bsBalances} -> do
old <- fromMaybe 0 <$> H.lookup bsBalances acc
H.insert bsBalances acc amt
return $ amt - old
--- | Update (overwrite) this transaction with a new one.
-storeTransactionB :: Transaction -> Balancing s ()
-storeTransactionB t = withB $ \BalancingState{bsTransactions} ->
+-- | Set this account's exclusive running balance to whatever amount
+-- makes its *inclusive* running balance (the sum of exclusive running
+-- balances of this account and any subaccounts) be the given amount.
+-- Returns the change in exclusive running balance.
+setInclusiveRunningBalanceB :: AccountName -> MixedAmount -> Balancing s MixedAmount
+setInclusiveRunningBalanceB acc newibal = withRunningBalance $ \BalancingState{bsBalances} -> do
+ oldebal <- fromMaybe 0 <$> H.lookup bsBalances acc
+ allebals <- H.toList bsBalances
+ let subsibal = -- sum of any subaccounts' running balances
+ sum $ map snd $ filter ((acc `isAccountNamePrefixOf`).fst) allebals
+ let newebal = newibal - subsibal
+ H.insert bsBalances acc newebal
+ return $ newebal - oldebal
+
+-- | Update (overwrite) this transaction in the balancing state.
+updateTransactionB :: Transaction -> Balancing s ()
+updateTransactionB t = withRunningBalance $ \BalancingState{bsTransactions} ->
void $ writeArray bsTransactions (tindex t) t
-- | Infer any missing amounts (to satisfy balance assignments and
@@ -641,30 +679,11 @@ storeTransactionB t = withB $ \BalancingState{bsTransactions} ->
-- and (optional) all balance assertions pass. Or return an error message
-- (just the first error encountered).
--
--- Assumes journalInferCommodityStyles has been called, since those affect transaction balancing.
---
--- This does multiple things because amount inferring, balance assignments,
--- balance assertions and posting dates are interdependent.
+-- Assumes journalInferCommodityStyles has been called, since those
+-- affect transaction balancing.
--
--- This can be simplified further. Overview as of 20190219:
--- @
--- ****** parseAndFinaliseJournal['] (Cli/Utils.hs), journalAddForecast (Common.hs), budgetJournal (BudgetReport.hs), tests (BalanceReport.hs)
--- ******* journalBalanceTransactions
--- ******** runST
--- ********* runExceptT
--- ********** balanceTransaction (Transaction.hs)
--- *********** balanceTransactionHelper
--- ********** runReaderT
--- *********** balanceTransactionAndCheckAssertionsB
--- ************ addAmountAndCheckAssertionB
--- ************ addOrAssignAmountAndCheckAssertionB
--- ************ balanceTransactionHelper (Transaction.hs)
--- ****** uiCheckBalanceAssertions d ui@UIState{aopts=UIOpts{cliopts_=copts}, ajournal=j} (ErrorScreen.hs)
--- ******* journalCheckBalanceAssertions
--- ******** journalBalanceTransactions
--- ****** transactionWizard, postingsBalanced (Add.hs), tests (Transaction.hs)
--- ******* balanceTransaction (Transaction.hs) XXX hledger add won't allow balance assignments + missing amount ?
--- @
+-- This does multiple things at once because amount inferring, balance
+-- assignments, balance assertions and posting dates are interdependent.
journalBalanceTransactions :: Bool -> Journal -> Either String Journal
journalBalanceTransactions assrt j' =
let
@@ -716,11 +735,9 @@ journalBalanceTransactions assrt j' =
-- Transaction prices are removed, which helps eg balance-assertions.test: 15. Mix different commodities and assignments.
-- This stores the balanced transactions in case 2 but not in case 1.
balanceTransactionAndCheckAssertionsB :: Either Posting Transaction -> Balancing s ()
-
balanceTransactionAndCheckAssertionsB (Left p@Posting{}) =
-- update the account's running balance and check the balance assertion if any
void $ addAmountAndCheckAssertionB $ removePrices p
-
balanceTransactionAndCheckAssertionsB (Right t@Transaction{tpostings=ps}) = do
-- make sure we can handle the balance assignments
mapM_ checkIllegalBalanceAssignmentB ps
@@ -733,9 +750,9 @@ balanceTransactionAndCheckAssertionsB (Right t@Transaction{tpostings=ps}) = do
Left err -> throwError err
Right (t', inferredacctsandamts) -> do
-- for each amount just inferred, update the running balance
- mapM_ (uncurry addAmountB) inferredacctsandamts
+ mapM_ (uncurry addToRunningBalanceB) inferredacctsandamts
-- and save the balanced transaction.
- storeTransactionB t'
+ updateTransactionB t'
-- | If this posting has an explicit amount, add it to the account's running balance.
-- If it has a missing amount and a balance assignment, infer the amount from, and
@@ -744,28 +761,33 @@ balanceTransactionAndCheckAssertionsB (Right t@Transaction{tpostings=ps}) = do
-- Then test the balance assertion if any.
addOrAssignAmountAndCheckAssertionB :: Posting -> Balancing s Posting
addOrAssignAmountAndCheckAssertionB p@Posting{paccount=acc, pamount=amt, pbalanceassertion=mba}
+ -- an explicit posting amount
| hasAmount p = do
- newbal <- addAmountB acc amt
+ newbal <- addToRunningBalanceB acc amt
whenM (R.reader bsAssrt) $ checkBalanceAssertionB p newbal
return p
- | Just BalanceAssertion{baamount,batotal} <- mba = do
+
+ -- no explicit posting amount, but there is a balance assignment
+ -- TODO this doesn't yet handle inclusive assignments right, #1207
+ | Just BalanceAssertion{baamount,batotal,bainclusive} <- mba = do
(diff,newbal) <- case batotal of
+ -- a total balance assignment (==, all commodities)
True -> do
- -- a total balance assignment
let newbal = Mixed [baamount]
- diff <- setAmountB acc newbal
+ diff <- (if bainclusive then setInclusiveRunningBalanceB else setRunningBalanceB) acc newbal
return (diff,newbal)
+ -- a partial balance assignment (=, one commodity)
False -> do
- -- a partial balance assignment
- oldbalothercommodities <- filterMixedAmount ((acommodity baamount /=) . acommodity) <$> getAmountB acc
+ oldbalothercommodities <- filterMixedAmount ((acommodity baamount /=) . acommodity) <$> getRunningBalanceB acc
let assignedbalthiscommodity = Mixed [baamount]
newbal = oldbalothercommodities + assignedbalthiscommodity
- diff <- setAmountB acc newbal
+ diff <- (if bainclusive then setInclusiveRunningBalanceB else setRunningBalanceB) acc newbal
return (diff,newbal)
let p' = p{pamount=diff, poriginal=Just $ originalPosting p}
whenM (R.reader bsAssrt) $ checkBalanceAssertionB p' newbal
return p'
- -- no amount, no balance assertion (GHC 7 doesn't like Nothing <- mba here)
+
+ -- no explicit posting amount, no balance assignment
| otherwise = return p
-- | Add the posting's amount to its account's running balance, and
@@ -775,7 +797,7 @@ addOrAssignAmountAndCheckAssertionB p@Posting{paccount=acc, pamount=amt, pbalanc
-- need to see the balance as it stands after each individual posting.
addAmountAndCheckAssertionB :: Posting -> Balancing s Posting
addAmountAndCheckAssertionB p | hasAmount p = do
- newbal <- addAmountB (paccount p) (pamount p)
+ newbal <- addToRunningBalanceB (paccount p) (pamount p)
whenM (R.reader bsAssrt) $ checkBalanceAssertionB p newbal
return p
addAmountAndCheckAssertionB p = return p
@@ -808,7 +830,7 @@ checkBalanceAssertionOneCommodityB p@Posting{paccount=assertedacct} assertedamt
if isinclusive
then
-- sum the running balances of this account and any of its subaccounts seen so far
- withB $ \BalancingState{bsBalances} ->
+ withRunningBalance $ \BalancingState{bsBalances} ->
H.foldM
(\ibal (acc, amt) -> return $ ibal +
if assertedacct==acc || assertedacct `isAccountNamePrefixOf` acc then amt else 0)
diff --git a/Hledger/Read.hs b/Hledger/Read.hs
index 01528ec..0fe07f0 100644
--- a/Hledger/Read.hs
+++ b/Hledger/Read.hs
@@ -48,21 +48,24 @@ import Control.Arrow (right)
import qualified Control.Exception as C
import Control.Monad (when)
import "mtl" Control.Monad.Except (runExceptT)
-import Data.Default
-import Data.List
-import Data.Maybe
-import Data.Ord
+import Data.Default (def)
+import Data.Foldable (asum)
+import Data.List (group, sort, sortBy)
+import Data.List.NonEmpty (nonEmpty)
+import Data.Maybe (fromMaybe)
+import Data.Ord (comparing)
+import Data.Semigroup (sconcat)
import Data.Text (Text)
import qualified Data.Text as T
import Data.Time (Day)
-import Safe
+import Safe (headDef)
import System.Directory (doesFileExist, getHomeDirectory)
import System.Environment (getEnv)
import System.Exit (exitFailure)
-import System.FilePath
+import System.FilePath ((<.>), (</>), splitDirectories, splitFileName)
import System.Info (os)
-import System.IO
-import Text.Printf
+import System.IO (stderr, writeFile)
+import Text.Printf (hPrintf, printf)
import Hledger.Data.Dates (getCurrentDay, parsedate, showDate)
import Hledger.Data.Types
@@ -149,11 +152,7 @@ type PrefixedFilePath = FilePath
-- Also the final parse state saved in the Journal does span all files.
readJournalFiles :: InputOpts -> [PrefixedFilePath] -> 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
+ fmap (right (maybe def sconcat . nonEmpty) . sequence) . mapM (readJournalFile iopts)
-- | 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.
@@ -170,7 +169,7 @@ readJournalFile :: InputOpts -> PrefixedFilePath -> IO (Either String Journal)
readJournalFile iopts prefixedfile = do
let
(mfmt, f) = splitReaderPrefix prefixedfile
- iopts' = iopts{mformat_=firstJust [mfmt, mformat_ iopts]}
+ iopts' = iopts{mformat_=asum [mfmt, mformat_ iopts]}
requireJournalFileExists f
t <- readFileOrStdinPortably f
-- <- T.readFile f -- or without line ending translation, for testing
diff --git a/Hledger/Read/Common.hs b/Hledger/Read/Common.hs
index ca27d88..3a07813 100644
--- a/Hledger/Read/Common.hs
+++ b/Hledger/Read/Common.hs
@@ -222,7 +222,7 @@ rtp = runTextParser
runJournalParser, rjp
:: Monad m
=> JournalParser m a -> Text -> m (Either (ParseErrorBundle Text CustomErr) a)
-runJournalParser p t = runParserT (evalStateT p mempty) "" t
+runJournalParser p t = runParserT (evalStateT p nulljournal) "" t
rjp = runJournalParser
-- | Run an erroring journal parser in some monad. See also: parseWithState.
@@ -232,7 +232,7 @@ runErroringJournalParser, rejp
-> Text
-> m (Either FinalParseError (Either (ParseErrorBundle Text CustomErr) a))
runErroringJournalParser p t =
- runExceptT $ runParserT (evalStateT p mempty) "" t
+ runExceptT $ runParserT (evalStateT p nulljournal) "" t
rejp = runErroringJournalParser
genericSourcePos :: SourcePos -> GenericSourcePos
@@ -680,7 +680,7 @@ amountwithoutpricep = do
-- | Parse an amount from a string, or get an error.
amountp' :: String -> Amount
amountp' s =
- case runParser (evalStateT (amountp <* eof) mempty) "" (T.pack s) of
+ case runParser (evalStateT (amountp <* eof) nulljournal) "" (T.pack s) of
Right amt -> amt
Left err -> error' $ show err -- XXX should throwError
@@ -766,12 +766,12 @@ fixedlotpricep = optional $ do
-- attributes.
--
-- Some international number formats are accepted, eg either period or comma
--- may be used for the decimal point, and the other of these may be used for
+-- may be used for the decimal mark, and the other of these may be used for
-- separating digit groups in the integer part. See
-- http://en.wikipedia.org/wiki/Decimal_separator for more examples.
--
-- This returns: the parsed numeric value, the precision (number of digits
--- seen following the decimal point), the decimal point character used if any,
+-- seen following the decimal mark), the decimal mark character used if any,
-- and the digit group style if any.
--
numberp :: Maybe AmountStyle -> TextParser m (Quantity, Int, Maybe Char, Maybe DigitGroupStyle)
diff --git a/Hledger/Read/CsvReader.hs b/Hledger/Read/CsvReader.hs
index 8d62fcd..1291f44 100644
--- a/Hledger/Read/CsvReader.hs
+++ b/Hledger/Read/CsvReader.hs
@@ -457,6 +457,8 @@ journalfieldnamep = do
lift (dbgparse 2 "trying journalfieldnamep")
T.unpack <$> choiceInState (map (lift . string . T.pack) journalfieldnames)
+maxpostings = 9
+
-- Transaction fields and pseudo fields for CSV conversion.
-- Names must precede any other name they contain, for the parser
-- (amount-in before amount; date2 before date). TODO: fix
@@ -468,7 +470,7 @@ journalfieldnames =
,"balance" ++ i
,"comment" ++ i
,"currency" ++ i
- ] | x <- [1..9], let i = show x]
+ ] | x <- [1..maxpostings], let i = show x]
++
["amount-in"
,"amount-out"
@@ -757,13 +759,6 @@ showRules rules record =
csvRule :: CsvRules -> DirectiveName -> Maybe FieldTemplate
csvRule rules = (`getDirective` rules)
--- | Look up the final value assigned to a csv rule by rule keyword, taking
--- into account the current record and conditional rules.
--- Generally rules with keywords ("directives") don't have interpolated
--- values, but for now it's possible.
-csvRuleValue :: CsvRules -> CsvRecord -> DirectiveName -> Maybe String
-csvRuleValue rules record = fmap (renderTemplate rules record) . csvRule rules
-
-- | Look up the value template assigned to a hledger field by field
-- list/field assignment rules, taking into account the current record and
-- conditional rules.
@@ -775,8 +770,6 @@ hledgerField = getEffectiveAssignment
hledgerFieldValue :: CsvRules -> CsvRecord -> HledgerFieldName -> Maybe String
hledgerFieldValue rules record = fmap (renderTemplate rules record) . hledgerField rules record
-s `withDefault` def = if null s then def else s
-
transactionFromCsvRecord :: SourcePos -> CsvRules -> CsvRecord -> Transaction
transactionFromCsvRecord sourcepos rules record = t
where
@@ -828,64 +821,27 @@ transactionFromCsvRecord sourcepos rules record = t
precomment = maybe "" singleline $ fieldval "precomment"
----------------------------------------------------------------------
- -- 3. Generate the postings
-
- -- Make posting 1 if possible, with special support for old syntax to
- -- support pre-1.16 rules.
- posting1 = mkPosting rules record "1"
- ("account1" `withAlias` "account")
- ("amount1" `withAlias` "amount")
- ("amount1-in" `withAlias` "amount-in")
- ("amount1-out" `withAlias` "amount-out")
- ("balance1" `withAlias` "balance")
- "comment1" -- comment1 does not have legacy alias
- t
- where
- withAlias fld alias =
- case (field fld, field alias) of
- (Just fld, Just alias) -> error' $ unlines
- [ "error: both \"" ++ fld ++ "\" and \"" ++ alias ++ "\" have values."
- , showRecord record
- , showRules rules record
- ]
- (Nothing, Just _) -> alias
- (_, Nothing) -> fld
-
- -- Make other postings where possible, and gather all that were generated.
- postings = catMaybes $ posting1 : otherpostings
- where
- otherpostings = [mkPostingN i | x<-[2..9], let i = show x]
- where
- mkPostingN n = mkPosting rules record n
- ("account"++n) ("amount"++n) ("amount"++n++"-in")
- ("amount"++n++"-out") ("balance"++n) ("comment"++n) t
-
- -- Auto-generate a second posting or second posting amount,
- -- for compatibility with pre-1.16 rules.
- postings' =
- case postings of
- -- when rules generate just one posting, of a kind that needs to be
- -- balanced, generate the second posting to balance it.
- [p1@(p1',_)] ->
- if ptype p1' == VirtualPosting then [p1] else [p1, p2]
- where
- p2 = (nullposting{paccount=unknownExpenseAccount
- ,pamount=costOfMixedAmount (-pamount p1')
- ,ptransaction=Just t}, False)
- -- when rules generate exactly two postings, and only the second has
- -- no amount, give it the balancing amount.
- [p1@(p1',_), p2@(p2',final2)] ->
- if hasAmount p1' && not (hasAmount p2')
- then [p1, (p2'{pamount=costOfMixedAmount(-(pamount p1'))}, final2)]
- else [p1, p2]
- --
- ps -> ps
-
- -- Finally, wherever default "unknown" accounts were used, refine them
- -- based on the sign of the posting amount if it's now known.
- postings'' = map maybeImprove postings'
- where
- maybeImprove (p,final) = if final then p else improveUnknownAccountName p
+ -- 3. Generate the postings for which an account has been assigned
+ -- (possibly indirectly due to an amount or balance assignment)
+
+ p1IsVirtual = (accountNamePostingType . T.pack <$> fieldval "account1") == Just VirtualPosting
+ ps = [p | n <- [1..maxpostings]
+ ,let comment = T.pack $ fromMaybe "" $ fieldval ("comment"++show n)
+ ,let currency = fromMaybe "" (fieldval ("currency"++show n) <|> fieldval "currency")
+ ,let mamount = getAmount rules record currency p1IsVirtual n
+ ,let mbalance = getBalance rules record currency n
+ ,Just (acct,isfinal) <- [getAccount rules record mamount mbalance n] -- skips Nothings
+ ,let acct' | not isfinal && acct==unknownExpenseAccount &&
+ fromMaybe False (mamount >>= isNegativeMixedAmount) = unknownIncomeAccount
+ | otherwise = acct
+ ,let p = nullposting{paccount = accountNameWithoutPostingType acct'
+ ,pamount = fromMaybe missingmixedamt mamount
+ ,ptransaction = Just t
+ ,pbalanceassertion = mkBalanceAssertion rules record <$> mbalance
+ ,pcomment = comment
+ ,ptype = accountNamePostingType acct
+ }
+ ]
----------------------------------------------------------------------
-- 4. Build the transaction (and name it, so the postings can reference it).
@@ -899,98 +855,90 @@ transactionFromCsvRecord sourcepos rules record = t
,tdescription = T.pack description
,tcomment = T.pack comment
,tprecedingcomment = T.pack precomment
- ,tpostings = postings''
+ ,tpostings = ps
}
--- | Given CSV rules and a CSV record, generate the corresponding transaction's
--- Nth posting, if sufficient fields have been assigned for it.
--- N is provided as a string.
--- The names of the required fields are provided, allowing more flexibility.
--- The transaction which will contain this posting is also provided,
--- so we can build the usual transaction<->posting cyclic reference.
-mkPosting ::
- CsvRules -> CsvRecord -> String ->
- HledgerFieldName -> HledgerFieldName -> HledgerFieldName ->
- HledgerFieldName -> HledgerFieldName -> HledgerFieldName ->
- Transaction ->
- Maybe (Posting, Bool)
-mkPosting rules record number accountFld amountFld amountInFld amountOutFld balanceFld commentFld t =
- -- if we have figured out an account N, make a posting N
- case maccountAndIsFinal of
- Nothing -> Nothing
- Just (acct, final) ->
- Just (posting{paccount = accountNameWithoutPostingType acct
- ,pamount = fromMaybe missingmixedamt mamount
- ,ptransaction = Just t
- ,pbalanceassertion = mkBalanceAssertion rules record <$> mbalance
- ,pcomment = comment
- ,ptype = accountNamePostingType acct}
- ,final)
+-- | Figure out the amount specified for posting N, if any.
+-- Looks for a non-zero amount assigned to one of "amountN", "amountN-in", "amountN-out".
+-- Postings 1 or 2 also look at "amount", "amount-in", "amount-out".
+-- Throws an error if more than one of these has a non-zero amount assigned.
+-- A currency symbol to prepend to the amount, if any, is provided,
+-- and whether posting 1 requires balancing or not.
+getAmount :: CsvRules -> CsvRecord -> String -> Bool -> Int -> Maybe MixedAmount
+getAmount rules record currency p1IsVirtual n =
+ let
+ unnumberedfieldnames = ["amount","amount-in","amount-out"]
+ fieldnames = map (("amount"++show n)++) ["","-in","-out"]
+ -- For posting 1, also recognise the old amount/amount-in/amount-out names.
+ -- For posting 2, the same but only if posting 1 needs balancing.
+ ++ if n==1 || n==2 && not p1IsVirtual then unnumberedfieldnames else []
+ nonzeroamounts = [(f,a') | f <- fieldnames
+ , Just v@(_:_) <- [strip . renderTemplate rules record <$> hledgerField rules record f]
+ , let a = parseAmount rules record currency v
+ , not $ isZeroMixedAmount a
+ -- With amount/amount-in/amount-out, in posting 2,
+ -- flip the sign and convert to cost, as they did before 1.17
+ , let a' = if f `elem` unnumberedfieldnames && n==2 then costOfMixedAmount (-a) else a
+ ]
+ in case nonzeroamounts of
+ [] -> Nothing
+ [(f,a)] | "-out" `isSuffixOf` f -> Just (-a) -- for -out fields, flip the sign
+ [(_,a)] -> Just a
+ fs -> error' $
+ "more than one non-zero amount for this record, please ensure just one\n"
+ ++ unlines [" " ++ padright 11 f ++ ": " ++ showMixedAmount a
+ ++ " from rule: " ++ fromMaybe "" (hledgerField rules record f)
+ | (f,a) <- fs]
+ ++ " " ++ showRecord record ++ "\n"
where
- -- the account name to use for this posting, if any, and whether it is the
- -- default unknown account, which may be improved later, or an explicitly
- -- set account, which may not.
- maccountAndIsFinal :: Maybe (AccountName, Bool) =
- case maccount of
- -- accountN is set to the empty string - no posting will be generated
- Just "" -> Nothing
- -- accountN is set (possibly to "expenses:unknown"! #1192) - mark it final
- Just a -> Just (a, True)
- -- accountN is unset
- Nothing ->
- case (mamount, mbalance) of
- -- amountN is set, or implied by balanceN - set accountN to
- -- the default unknown account ("expenses:unknown") and
- -- allow it to be improved later
- (Just _, _) -> Just (unknownExpenseAccount, False)
- (_, Just _) -> Just (unknownExpenseAccount, False)
- -- amountN is also unset - no posting will be generated
- (Nothing, Nothing) -> Nothing
+ -- | Given a non-empty amount string to parse, along with a possibly
+ -- non-empty currency symbol to prepend, parse as a hledger amount (as
+ -- in journal format), or raise an error.
+ -- The CSV rules and record are provided for the error message.
+ parseAmount :: CsvRules -> CsvRecord -> String -> String -> MixedAmount
+ parseAmount rules record currency amountstr =
+ either mkerror (Mixed . (:[])) $
+ runParser (evalStateT (amountp <* eof) nulljournal) "" $
+ T.pack $ (currency++) $ simplifySign amountstr
where
- maccount = T.pack <$> (fieldval accountFld
- -- XXX what's this needed for ? Test & document, or drop.
- -- Also, this the only place we interpolate in a keyword rule, I think.
- `withDefault` ruleval ("default-account" ++ number))
- -- XXX what's this needed for ? Test & document, or drop.
- mdefaultcurrency = rule "default-currency"
- currency = fromMaybe (fromMaybe "" mdefaultcurrency) $
- fieldval ("currency"++number) `withDefault` fieldval "currency"
- mamount = chooseAmount rules record currency amountFld amountInFld amountOutFld
- mbalance :: Maybe (Amount, GenericSourcePos) =
- fieldval balanceFld >>= parsebalance currency number
+ mkerror e = error' $ unlines
+ ["error: could not parse \""++amountstr++"\" as an amount"
+ ,showRecord record
+ ,showRules rules record
+ -- ,"the default-currency is: "++fromMaybe "unspecified" (getDirective "default-currency" rules)
+ ,"the parse error is: "++customErrorBundlePretty e
+ ,"you may need to "
+ ++"change your amount*, balance*, or currency* rules, "
+ ++"or add or change your skip rule"
+ ]
+
+-- | Figure out the expected balance (assertion or assignment) specified for posting N,
+-- if any (and its parse position).
+getBalance :: CsvRules -> CsvRecord -> String -> Int -> Maybe (Amount, GenericSourcePos)
+getBalance rules record currency n =
+ (fieldval ("balance"++show n)
+ -- for posting 1, also recognise the old field name
+ <|> if n==1 then fieldval "balance" else Nothing)
+ >>= parsebalance currency n . strip
+ where
+ parsebalance currency n s
+ | null s = Nothing
+ | otherwise = Just
+ (either (mkerror n s) id $
+ runParser (evalStateT (amountp <* eof) nulljournal) "" $
+ T.pack $ (currency++) $ simplifySign s
+ ,nullsourcepos) -- XXX parse position to show when assertion fails,
+ -- the csv record's line number would be good
where
- parsebalance currency n str
- | all isSpace str = Nothing
- | otherwise = Just
- (either (balanceerror n str) id $
- runParser (evalStateT (amountp <* eof) mempty) "" $
- T.pack $ (currency++) $ simplifySign str
- ,nullsourcepos) -- XXX parse position to show when assertion fails,
- -- the csv record's line number would be good
- where
- balanceerror n str err = error' $ unlines
- ["error: could not parse \""++str++"\" as balance"++n++" amount"
- ,showRecord record
- ,showRules rules record
- ,"the default-currency is: "++fromMaybe "unspecified" mdefaultcurrency
- ,"the parse error is: "++customErrorBundlePretty err
- ]
- comment = T.pack $ fromMaybe "" $ fieldval commentFld
- rule = csvRule rules :: DirectiveName -> Maybe FieldTemplate
- ruleval = csvRuleValue rules record :: DirectiveName -> Maybe String
- -- field = hledgerField rules record :: HledgerFieldName -> Maybe FieldTemplate
+ mkerror n s e = error' $ unlines
+ ["error: could not parse \""++s++"\" as balance"++show n++" amount"
+ ,showRecord record
+ ,showRules rules record
+ -- ,"the default-currency is: "++fromMaybe "unspecified" mdefaultcurrency
+ ,"the parse error is: "++customErrorBundlePretty e
+ ]
+ -- mdefaultcurrency = rule "default-currency"
fieldval = hledgerFieldValue rules record :: HledgerFieldName -> Maybe String
--- | Default account names to use when needed.
-unknownExpenseAccount = "expenses:unknown"
-unknownIncomeAccount = "income:unknown"
-
--- | If this posting has the "expenses:unknown" account name,
--- replace that with "income:unknown" if the amount is negative.
--- The posting's amount should be explicit.
-improveUnknownAccountName p@Posting{..}
- | paccount == unknownExpenseAccount
- && fromMaybe False (isNegativeMixedAmount pamount) = p{paccount=unknownIncomeAccount}
- | otherwise = p
-- | Make a balance assertion for the given amount, with the given parse
-- position (to be shown in assertion failures), with the assertion type
@@ -1013,48 +961,33 @@ mkBalanceAssertion rules record (amt, pos) = assrt{baamount=amt, baposition=pos}
, showRules rules record
]
-chooseAmount :: CsvRules -> CsvRecord -> String -> String -> String -> String -> Maybe MixedAmount
-chooseAmount rules record currency amountFld amountInFld amountOutFld =
- let
- mamount = getEffectiveAssignment rules record amountFld
- mamountin = getEffectiveAssignment rules record amountInFld
- mamountout = getEffectiveAssignment rules record amountOutFld
- parse amt = notZero =<< (parseAmount currency <$> notEmpty =<< (strip . renderTemplate rules record) <$> amt)
- in
- case (parse mamount, parse mamountin, parse mamountout) of
- (Nothing, Nothing, Nothing) -> Nothing
- (Just a, Nothing, Nothing) -> Just a
- (Nothing, Just i, Nothing) -> Just i
- (Nothing, Nothing, Just o) -> Just $ negate o
- (Nothing, Just i, Just o) -> error' $ "both "++amountInFld++" and "++amountOutFld++" have a value\n"
- ++ " "++amountInFld++": " ++ show i ++ "\n"
- ++ " "++amountOutFld++": " ++ show o ++ "\n"
- ++ " record: " ++ showRecord record
- _ -> error' $ "found values for "++amountFld++" and for "++amountInFld++"/"++amountOutFld++"\n"
- ++ "please use either "++amountFld++" or "++amountInFld++"/"++amountOutFld++"\n"
- ++ " record: " ++ showRecord record
- where
- notZero amt = if isZeroMixedAmount amt then Nothing else Just amt
- notEmpty str = if str=="" then Nothing else Just str
-
- parseAmount currency amountstr =
- either (amounterror amountstr) (Mixed . (:[]))
- <$> runParser (evalStateT (amountp <* eof) mempty) ""
- <$> T.pack
- <$> (currency++)
- <$> simplifySign
- <$> amountstr
-
- amounterror amountstr err = error' $ unlines
- ["error: could not parse \""++fromJust amountstr++"\" as an amount"
- ,showRecord record
- ,showRules rules record
- ,"the default-currency is: "++fromMaybe "unspecified" (getDirective "default-currency" rules)
- ,"the parse error is: "++customErrorBundlePretty err
- ,"you may need to "
- ++"change your amount or currency rules, "
- ++"or add or change your skip rule"
- ]
+-- | Figure out the account name specified for posting N, if any.
+-- And whether it is the default unknown account (which may be
+-- improved later) or an explicitly set account (which may not).
+getAccount :: CsvRules -> CsvRecord -> Maybe MixedAmount -> Maybe (Amount, GenericSourcePos) -> Int -> Maybe (AccountName, Bool)
+getAccount rules record mamount mbalance n =
+ let
+ fieldval = hledgerFieldValue rules record :: HledgerFieldName -> Maybe String
+ maccount = T.pack <$> fieldval ("account"++show n)
+ in case maccount of
+ -- accountN is set to the empty string - no posting will be generated
+ Just "" -> Nothing
+ -- accountN is set (possibly to "expenses:unknown"! #1192) - mark it final
+ Just a -> Just (a, True)
+ -- accountN is unset
+ Nothing ->
+ case (mamount, mbalance) of
+ -- amountN is set, or implied by balanceN - set accountN to
+ -- the default unknown account ("expenses:unknown") and
+ -- allow it to be improved later
+ (Just _, _) -> Just (unknownExpenseAccount, False)
+ (_, Just _) -> Just (unknownExpenseAccount, False)
+ -- amountN is also unset - no posting will be generated
+ (Nothing, Nothing) -> Nothing
+
+-- | Default account names to use when needed.
+unknownExpenseAccount = "expenses:unknown"
+unknownIncomeAccount = "income:unknown"
type CsvAmountString = String
@@ -1088,7 +1021,7 @@ negateStr s = '-':s
-- | Show a (approximate) recreation of the original CSV record.
showRecord :: CsvRecord -> String
-showRecord r = "the CSV record is: "++intercalate "," (map show r)
+showRecord r = "record values: "++intercalate "," (map show r)
-- | Given the conversion rules, a CSV record and a hledger field name, find
-- the value template ultimately assigned to this field, if any, by a field
@@ -1152,7 +1085,7 @@ csvFieldValue rules record fieldname = do
-- the "simple date" formats (YYYY/MM/DD, YYYY-MM-DD, YYYY.MM.DD, leading
-- zeroes optional).
parseDateWithCustomOrDefaultFormats :: Maybe DateFormat -> String -> Maybe Day
-parseDateWithCustomOrDefaultFormats mformat s = firstJust $ map parsewith formats
+parseDateWithCustomOrDefaultFormats mformat s = asum $ map parsewith formats
where
parsetime =
#if MIN_VERSION_time(1,5,0)
diff --git a/Hledger/Read/JournalReader.hs b/Hledger/Read/JournalReader.hs
index 8ba5ae2..8574eff 100644
--- a/Hledger/Read/JournalReader.hs
+++ b/Hledger/Read/JournalReader.hs
@@ -82,9 +82,10 @@ import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Except (ExceptT(..), runExceptT)
import Control.Monad.State.Strict (get,modify',put)
import Control.Monad.Trans.Class (lift)
+import Data.Either (isRight)
import qualified Data.Map.Strict as M
#if !(MIN_VERSION_base(4,11,0))
-import Data.Monoid ((<>))
+import Data.Semigroup ((<>))
#endif
import Data.Text (Text)
import Data.String
@@ -297,7 +298,7 @@ includedirectivep = do
put $ updatedChildj <> parentj
newJournalWithParseStateFrom :: FilePath -> Journal -> Journal
- newJournalWithParseStateFrom filepath j = mempty{
+ newJournalWithParseStateFrom filepath j = nulljournal{
jparsedefaultyear = jparsedefaultyear j
,jparsedefaultcommodity = jparsedefaultcommodity j
,jparseparentaccounts = jparseparentaccounts j
@@ -424,7 +425,15 @@ commoditydirectiveonelinep = do
else modify' (\j -> j{jcommodities=M.insert acommodity comm $ jcommodities j})
pleaseincludedecimalpoint :: String
-pleaseincludedecimalpoint = "to avoid ambiguity, please include a decimal separator in commodity directives"
+pleaseincludedecimalpoint = chomp $ unlines [
+ "Please include a decimal point or decimal comma in commodity directives,"
+ ,"to help us parse correctly. It may be followed by zero or more decimal digits."
+ ,"Examples:"
+ ,"commodity $1000. ; no thousands mark, decimal period, no decimals"
+ ,"commodity 1.234,00 ARS ; period at thousands, decimal comma, 2 decimals"
+ ,"commodity EUR 1 000,000 ; space at thousands, decimal comma, 3 decimals"
+ ,"commodity INR1,23,45,678.0 ; comma at thousands/lakhs/crores, decimal period, 1 decimal"
+ ]
-- | Parse a multi-line commodity directive, containing 0 or more format subdirectives.
--
@@ -746,7 +755,7 @@ tests_JournalReader = tests "JournalReader" [
,test "yearless date with no default year" $ assertParseError datep "1/1" "current year is unknown"
,test "yearless date with default year" $ do
let s = "1/1"
- ep <- parseWithState mempty{jparsedefaultyear=Just 2018} datep s
+ ep <- parseWithState nulljournal{jparsedefaultyear=Just 2018} datep s
either (assertFailure . ("parse error at "++) . customErrorBundlePretty) (const $ return ()) ep
,test "no leading zero" $ assertParse datep "2018/1/1"
]
@@ -968,7 +977,7 @@ tests_JournalReader = tests "JournalReader" [
,test "defaultcommoditydirectivep" $ do
assertParse defaultcommoditydirectivep "D $1,000.0\n"
- assertParseError defaultcommoditydirectivep "D $1000\n" "please include a decimal separator"
+ assertParseError defaultcommoditydirectivep "D $1000\n" "Please include a decimal point or decimal comma"
,tests "defaultyeardirectivep" [
test "1000" $ assertParse defaultyeardirectivep "Y 1000" -- XXX no \n like the others
diff --git a/Hledger/Utils.hs b/Hledger/Utils.hs
index 0c172d6..fb0c172 100644
--- a/Hledger/Utils.hs
+++ b/Hledger/Utils.hs
@@ -146,13 +146,6 @@ getCurrentZonedTime = do
instance Default Bool where def = False
-isLeft :: Either a b -> Bool
-isLeft (Left _) = True
-isLeft _ = False
-
-isRight :: Either a b -> Bool
-isRight = not . isLeft
-
-- | Apply a function the specified number of times,
-- which should be > 0 (otherwise does nothing).
-- Possibly uses O(n) stack ?
@@ -178,10 +171,6 @@ expandHomePath = \case
('~':_) -> ioError $ userError "~USERNAME in paths is not supported"
p -> return p
-firstJust ms = case dropWhile (==Nothing) ms of
- [] -> Nothing
- (md:_) -> md
-
-- | Read text from a file,
-- converting any \r\n line endings to \n,,
-- using the system locale's text encoding,
diff --git a/Hledger/Utils/Debug.hs b/Hledger/Utils/Debug.hs
index e2938ce..fad152f 100644
--- a/Hledger/Utils/Debug.hs
+++ b/Hledger/Utils/Debug.hs
@@ -12,6 +12,8 @@ module Hledger.Utils.Debug (
,pshow
,ptrace
,traceWith
+ ,traceAt
+ ,traceAtWith
,debugLevel
,ptraceAt
,ptraceAtWith
@@ -81,8 +83,9 @@ pshow = ppShow
ptrace :: Show a => a -> a
ptrace = traceWith pshow
--- | Trace (print to stderr) a showable value using a custom show function.
-traceWith :: (a -> String) -> a -> a
+-- | Like traceShowId, but uses a custom show function to render the value.
+-- traceShowIdWith was too much of a mouthful.
+traceWith :: Show a => (a -> String) -> a -> a
traceWith f a = trace (f a) a
-- | Global debug level, which controls the verbosity of debug output
@@ -108,6 +111,18 @@ debugLevel = case snd $ break (=="--debug") args of
where
args = unsafePerformIO getArgs
+-- | Trace (print to stderr) a string if the global debug level is at
+-- or above the specified level. At level 0, always prints. Otherwise,
+-- uses unsafePerformIO.
+traceAt :: Int -> String -> a -> a
+traceAt level
+ | level > 0 && debugLevel < level = flip const
+ | otherwise = trace
+
+-- | Trace (print to stderr) a showable value using a custom show function.
+traceAtWith :: (a -> String) -> a -> a
+traceAtWith f a = trace (f a) a
+
-- | Pretty-print a label and a showable value to the console
-- if the global debug level is at or above the specified level.
-- At level 0, always prints. Otherwise, uses unsafePerformIO.
diff --git a/Hledger/Utils/Test.hs b/Hledger/Utils/Test.hs
index b1de72b..6460092 100644
--- a/Hledger/Utils/Test.hs
+++ b/Hledger/Utils/Test.hs
@@ -25,8 +25,9 @@ where
import Control.Monad.Except (ExceptT, runExceptT)
import Control.Monad.State.Strict (StateT, evalStateT, execStateT)
+import Data.Default (Default(..))
#if !(MIN_VERSION_base(4,11,0))
-import Data.Monoid ((<>))
+import Data.Semigroup ((<>))
#endif
-- import Data.CallStack
import Data.List (isInfixOf)
@@ -73,35 +74,35 @@ assertRight (Left a) = assertFailure $ "expected Right, got (Left " ++ show a +
-- | Assert that this stateful parser runnable in IO successfully parses
-- all of the given input text, showing the parse error if it fails.
-- Suitable for hledger's JournalParser parsers.
-assertParse :: (HasCallStack, Eq a, Show a, Monoid st) =>
+assertParse :: (HasCallStack, Eq a, Show a, Default st) =>
StateT st (ParsecT CustomErr T.Text IO) a -> T.Text -> Assertion
assertParse parser input = do
- ep <- runParserT (evalStateT (parser <* eof) mempty) "" input
+ ep <- runParserT (evalStateT (parser <* eof) def) "" input
either (assertFailure.(++"\n").("\nparse error at "++).customErrorBundlePretty)
(const $ return ())
ep
-- | Assert a parser produces an expected value.
-assertParseEq :: (HasCallStack, Eq a, Show a, Monoid st) =>
+assertParseEq :: (HasCallStack, Eq a, Show a, Default st) =>
StateT st (ParsecT CustomErr T.Text IO) a -> T.Text -> a -> Assertion
assertParseEq parser input expected = assertParseEqOn parser input id expected
-- | Like assertParseEq, but transform the parse result with the given function
-- before comparing it.
-assertParseEqOn :: (HasCallStack, Eq b, Show b, Monoid st) =>
+assertParseEqOn :: (HasCallStack, Eq b, Show b, Default st) =>
StateT st (ParsecT CustomErr T.Text IO) a -> T.Text -> (a -> b) -> b -> Assertion
assertParseEqOn parser input f expected = do
- ep <- runParserT (evalStateT (parser <* eof) mempty) "" input
+ ep <- runParserT (evalStateT (parser <* eof) def) "" input
either (assertFailure . (++"\n") . ("\nparse error at "++) . customErrorBundlePretty)
(assertEqual "" expected . f)
ep
-- | Assert that this stateful parser runnable in IO fails to parse
-- the given input text, with a parse error containing the given string.
-assertParseError :: (HasCallStack, Eq a, Show a, Monoid st) =>
+assertParseError :: (HasCallStack, Eq a, Show a, Default st) =>
StateT st (ParsecT CustomErr T.Text IO) a -> String -> String -> Assertion
assertParseError parser input errstr = do
- ep <- runParserT (evalStateT parser mempty) "" (T.pack input)
+ ep <- runParserT (evalStateT parser def) "" (T.pack input)
case ep of
Right v -> assertFailure $ "\nparse succeeded unexpectedly, producing:\n" ++ pshow v ++ "\n"
Left e -> do
@@ -113,28 +114,28 @@ assertParseError parser input errstr = do
-- | Run a stateful parser in IO like assertParse, then assert that the
-- final state (the wrapped state, not megaparsec's internal state),
-- transformed by the given function, matches the given expected value.
-assertParseStateOn :: (HasCallStack, Eq b, Show b, Monoid st) =>
+assertParseStateOn :: (HasCallStack, Eq b, Show b, Default st) =>
StateT st (ParsecT CustomErr T.Text IO) a
-> T.Text
-> (st -> b)
-> b
-> Assertion
assertParseStateOn parser input f expected = do
- es <- runParserT (execStateT (parser <* eof) mempty) "" input
+ es <- runParserT (execStateT (parser <* eof) def) "" input
case es of
Left err -> assertFailure $ (++"\n") $ ("\nparse error at "++) $ customErrorBundlePretty err
Right s -> assertEqual "" expected $ f s
-- | These "E" variants of the above are suitable for hledger's ErroringJournalParser parsers.
assertParseE
- :: (HasCallStack, Eq a, Show a, Monoid st)
+ :: (HasCallStack, Eq a, Show a, Default st)
=> StateT st (ParsecT CustomErr T.Text (ExceptT FinalParseError IO)) a
-> T.Text
-> Assertion
assertParseE parser input = do
let filepath = ""
eep <- runExceptT $
- runParserT (evalStateT (parser <* eof) mempty) filepath input
+ runParserT (evalStateT (parser <* eof) def) filepath input
case eep of
Left finalErr ->
let prettyErr = finalErrorBundlePretty $ attachSource filepath input finalErr
@@ -145,7 +146,7 @@ assertParseE parser input = do
ep
assertParseEqE
- :: (Monoid st, Eq a, Show a, HasCallStack)
+ :: (Default st, Eq a, Show a, HasCallStack)
=> StateT st (ParsecT CustomErr T.Text (ExceptT FinalParseError IO)) a
-> T.Text
-> a
@@ -153,7 +154,7 @@ assertParseEqE
assertParseEqE parser input expected = assertParseEqOnE parser input id expected
assertParseEqOnE
- :: (HasCallStack, Eq b, Show b, Monoid st)
+ :: (HasCallStack, Eq b, Show b, Default st)
=> StateT st (ParsecT CustomErr T.Text (ExceptT FinalParseError IO)) a
-> T.Text
-> (a -> b)
@@ -161,7 +162,7 @@ assertParseEqOnE
-> Assertion
assertParseEqOnE parser input f expected = do
let filepath = ""
- eep <- runExceptT $ runParserT (evalStateT (parser <* eof) mempty) filepath input
+ eep <- runExceptT $ runParserT (evalStateT (parser <* eof) def) filepath input
case eep of
Left finalErr ->
let prettyErr = finalErrorBundlePretty $ attachSource filepath input finalErr
@@ -172,14 +173,14 @@ assertParseEqOnE parser input f expected = do
ep
assertParseErrorE
- :: (Monoid st, Eq a, Show a, HasCallStack)
+ :: (Default st, Eq a, Show a, HasCallStack)
=> StateT st (ParsecT CustomErr T.Text (ExceptT FinalParseError IO)) a
-> T.Text
-> String
-> Assertion
assertParseErrorE parser input errstr = do
let filepath = ""
- eep <- runExceptT $ runParserT (evalStateT parser mempty) filepath input
+ eep <- runExceptT $ runParserT (evalStateT parser def) filepath input
case eep of
Left finalErr -> do
let prettyErr = finalErrorBundlePretty $ attachSource filepath input finalErr
diff --git a/hledger-lib.cabal b/hledger-lib.cabal
index 1e17a84..7935ced 100644
--- a/hledger-lib.cabal
+++ b/hledger-lib.cabal
@@ -4,10 +4,10 @@ cabal-version: 1.12
--
-- see: https://github.com/sol/hpack
--
--- hash: 4f9a789b279703a75cecaf4e5cca835f20a5ddb9cfeb9e98e62db665a573d016
+-- hash: 2280d91d2a756a6b17f33e15453b491d8945496a3876f4df74683110a0cc5d08
name: hledger-lib
-version: 1.17.0.1
+version: 1.17.1
synopsis: Core data types, parsers and functionality for the hledger accounting tools
description: This is a reusable library containing hledger's core functionality.
.
@@ -106,7 +106,7 @@ library
./.
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
+ Decimal >=0.5.1
, Glob >=0.9
, aeson
, ansi-terminal >=0.6.2.3
@@ -157,7 +157,7 @@ test-suite doctest
test
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
+ Decimal >=0.5.1
, Glob >=0.7
, aeson
, ansi-terminal >=0.6.2.3
@@ -199,8 +199,8 @@ test-suite doctest
, transformers >=0.2
, uglymemo
, utf8-string >=0.3.5
- buildable: False
- if (impl(ghc < 8.2))
+ buildable: True
+ if (impl(ghc < 8.2) || impl(ghc >= 8.10))
buildable: False
default-language: Haskell2010
@@ -212,7 +212,7 @@ test-suite unittest
test
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
+ Decimal >=0.5.1
, Glob >=0.9
, aeson
, ansi-terminal >=0.6.2.3
@@ -254,5 +254,4 @@ test-suite unittest
, transformers >=0.2
, uglymemo
, utf8-string >=0.3.5
- buildable: True
default-language: Haskell2010
diff --git a/hledger_csv.5 b/hledger_csv.5
index 55f5149..4832874 100644
--- a/hledger_csv.5
+++ b/hledger_csv.5
@@ -1,6 +1,6 @@
.\"t
-.TH "hledger_csv" "5" "March 2020" "hledger 1.17" "hledger User Manuals"
+.TH "hledger_csv" "5" "March 2020" "hledger 1.17.1" "hledger User Manuals"
@@ -487,34 +487,48 @@ hledger\[aq]s journal format.
\f[C]description\f[R], \f[C]comment\f[R] can be used to form the
transaction\[aq]s first line.
.SS Posting field names
+.SS account
+.PP
+\f[C]accountN\f[R], where N is 1 to 9, causes a posting to be generated,
+with that account name.
.PP
-\f[C]accountN\f[R], where N is 1 to 9, generates a posting, with that
-account name.
Most often there are two postings, so you\[aq]ll want to set
\f[C]account1\f[R] and \f[C]account2\f[R].
-If a posting\[aq]s account name is left unset but its amount is set, a
-default account name will be chosen (like expenses:unknown or
-income:unknown).
+Typically \f[C]account1\f[R] is associated with the CSV file, and is set
+once with a top-level assignment, while \f[C]account2\f[R] is set based
+on each transaction\[aq]s description, and in conditional blocks.
+.PP
+If a posting\[aq]s account name is left unset but its amount is set (see
+below), a default account name will be chosen (like
+\[dq]expenses:unknown\[dq] or \[dq]income:unknown\[dq]).
+.SS amount
.PP
\f[C]amountN\f[R] sets posting N\[aq]s amount.
-Or, \f[C]amount\f[R] with no N sets posting 1\[aq]s.
-If the CSV has debits and credits in separate fields, use
-\f[C]amountN-in\f[R] and \f[C]amountN-out\f[R] instead.
-Or \f[C]amount-in\f[R] and \f[C]amount-out\f[R] with no N for posting 1.
+If the CSV uses separate fields for debit and credit amounts, you can
+use \f[C]amountN-in\f[R] and \f[C]amountN-out\f[R] instead.
+.PP
+Also, for compatibility with hledger <1.17: \f[C]amount\f[R] or
+\f[C]amount-in\f[R]/\f[C]amount-out\f[R] with no number sets the amount
+for postings 1 and 2.
+For posting 2 the amount is negated, and converted to cost if
+there\[aq]s a transaction price.
+.SS currency
+.PP
+If the CSV has the currency symbol in a separate field (ie, not part of
+the amount field), you can use \f[C]currencyN\f[R] to prepend it to
+posting N\[aq]s amount.
+Or, \f[C]currency\f[R] with no number affects all postings.
+.SS balance
.PP
-For convenience and backwards compatibility, if you set the amount of
-posting 1 only, a second posting with the negative amount will be
-generated automatically.
-(Unless the account name is parenthesised indicating an unbalanced
-posting.)
+\f[C]balanceN\f[R] sets a balance assertion amount (or if the posting
+amount is left empty, a balance assignment) on posting N.
.PP
-If the CSV has the currency symbol in a separate field, you can use
-\f[C]currencyN\f[R] to prepend it to posting N\[aq]s amount.
-\f[C]currency\f[R] with no N affects ALL postings.
+Also, for compatibility with hledger <1.17: \f[C]balance\f[R] with no
+number is equivalent to \f[C]balance1\f[R].
.PP
-\f[C]balanceN\f[R] sets a balance assertion amount (or if the posting
-amount is left empty, a balance assignment).
-You may need to adjust this with the \f[C]balance-type\f[R] rule.
+You can adjust the type of assertion/assignment with the
+\f[C]balance-type\f[R] rule (see below).
+.SS comment
.PP
Finally, \f[C]commentN\f[R] sets a comment on the Nth posting.
Comments can also contain tags, as usual.
diff --git a/hledger_csv.info b/hledger_csv.info
index e31358f..605e4b4 100644
--- a/hledger_csv.info
+++ b/hledger_csv.info
@@ -3,8 +3,8 @@ This is hledger_csv.info, produced by makeinfo version 6.7 from stdin.

File: hledger_csv.info, Node: Top, Next: EXAMPLES, Up: (dir)
-hledger_csv(5) hledger 1.17
-***************************
+hledger_csv(5) hledger 1.17.1
+*****************************
CSV - how hledger reads CSV data, and the CSV rules file format
@@ -451,31 +451,79 @@ File: hledger_csv.info, Node: Posting field names, Prev: Transaction field nam
2.2.2 Posting field names
-------------------------
-'accountN', where N is 1 to 9, generates a posting, with that account
-name. Most often there are two postings, so you'll want to set
-'account1' and 'account2'. If a posting's account name is left unset
-but its amount is set, a default account name will be chosen (like
-expenses:unknown or income:unknown).
+* Menu:
+
+* account::
+* amount::
+* currency::
+* balance::
+* comment::
+
+
+File: hledger_csv.info, Node: account, Next: amount, Up: Posting field names
+
+2.2.2.1 account
+...............
+
+'accountN', where N is 1 to 9, causes a posting to be generated, with
+that account name.
+
+ Most often there are two postings, so you'll want to set 'account1'
+and 'account2'. Typically 'account1' is associated with the CSV file,
+and is set once with a top-level assignment, while 'account2' is set
+based on each transaction's description, and in conditional blocks.
+
+ If a posting's account name is left unset but its amount is set (see
+below), a default account name will be chosen (like "expenses:unknown"
+or "income:unknown").
+
+
+File: hledger_csv.info, Node: amount, Next: currency, Prev: account, Up: Posting field names
+
+2.2.2.2 amount
+..............
- 'amountN' sets posting N's amount. Or, 'amount' with no N sets
-posting 1's. If the CSV has debits and credits in separate fields, use
-'amountN-in' and 'amountN-out' instead. Or 'amount-in' and 'amount-out'
-with no N for posting 1.
+'amountN' sets posting N's amount. If the CSV uses separate fields for
+debit and credit amounts, you can use 'amountN-in' and 'amountN-out'
+instead.
- For convenience and backwards compatibility, if you set the amount of
-posting 1 only, a second posting with the negative amount will be
-generated automatically. (Unless the account name is parenthesised
-indicating an unbalanced posting.)
+ Also, for compatibility with hledger <1.17: 'amount' or
+'amount-in'/'amount-out' with no number sets the amount for postings 1
+and 2. For posting 2 the amount is negated, and converted to cost if
+there's a transaction price.
- If the CSV has the currency symbol in a separate field, you can use
-'currencyN' to prepend it to posting N's amount. 'currency' with no N
-affects ALL postings.
+
+File: hledger_csv.info, Node: currency, Next: balance, Prev: amount, Up: Posting field names
+
+2.2.2.3 currency
+................
+
+If the CSV has the currency symbol in a separate field (ie, not part of
+the amount field), you can use 'currencyN' to prepend it to posting N's
+amount. Or, 'currency' with no number affects all postings.
+
+
+File: hledger_csv.info, Node: balance, Next: comment, Prev: currency, Up: Posting field names
+
+2.2.2.4 balance
+...............
+
+'balanceN' sets a balance assertion amount (or if the posting amount is
+left empty, a balance assignment) on posting N.
+
+ Also, for compatibility with hledger <1.17: 'balance' with no number
+is equivalent to 'balance1'.
+
+ You can adjust the type of assertion/assignment with the
+'balance-type' rule (see below).
+
+
+File: hledger_csv.info, Node: comment, Prev: balance, Up: Posting field names
- 'balanceN' sets a balance assertion amount (or if the posting amount
-is left empty, a balance assignment). You may need to adjust this with
-the 'balance-type' rule.
+2.2.2.5 comment
+...............
- Finally, 'commentN' sets a comment on the Nth posting. Comments can
+Finally, 'commentN' sets a comment on the Nth posting. Comments can
also contain tags, as usual.
See TIPS below for more about setting amounts and currency.
@@ -971,64 +1019,74 @@ command the user specified.

Tag Table:
Node: Top72
-Node: EXAMPLES2093
-Ref: #examples2199
-Node: Basic2407
-Ref: #basic2507
-Node: Bank of Ireland3049
-Ref: #bank-of-ireland3184
-Node: Amazon4646
-Ref: #amazon4764
-Node: Paypal6483
-Ref: #paypal6577
-Node: CSV RULES14221
-Ref: #csv-rules14330
-Node: skip14606
-Ref: #skip14699
-Node: fields15074
-Ref: #fields15196
-Node: Transaction field names16361
-Ref: #transaction-field-names16521
-Node: Posting field names16632
-Ref: #posting-field-names16784
-Node: field assignment18075
-Ref: #field-assignment18218
-Node: separator19036
-Ref: #separator19165
-Node: if19576
-Ref: #if19678
-Node: end21597
-Ref: #end21703
-Node: date-format21927
-Ref: #date-format22059
-Node: newest-first22808
-Ref: #newest-first22946
-Node: include23629
-Ref: #include23758
-Node: balance-type24202
-Ref: #balance-type24322
-Node: TIPS25022
-Ref: #tips25104
-Node: Rapid feedback25360
-Ref: #rapid-feedback25477
-Node: Valid CSV25937
-Ref: #valid-csv26067
-Node: File Extension26259
-Ref: #file-extension26411
-Node: Reading multiple CSV files26821
-Ref: #reading-multiple-csv-files27006
-Node: Valid transactions27247
-Ref: #valid-transactions27425
-Node: Deduplicating importing28053
-Ref: #deduplicating-importing28232
-Node: Setting amounts29265
-Ref: #setting-amounts29434
-Node: Setting currency/commodity30420
-Ref: #setting-currencycommodity30612
-Node: Referencing other fields31415
-Ref: #referencing-other-fields31615
-Node: How CSV rules are evaluated32512
-Ref: #how-csv-rules-are-evaluated32685
+Node: EXAMPLES2097
+Ref: #examples2203
+Node: Basic2411
+Ref: #basic2511
+Node: Bank of Ireland3053
+Ref: #bank-of-ireland3188
+Node: Amazon4650
+Ref: #amazon4768
+Node: Paypal6487
+Ref: #paypal6581
+Node: CSV RULES14225
+Ref: #csv-rules14334
+Node: skip14610
+Ref: #skip14703
+Node: fields15078
+Ref: #fields15200
+Node: Transaction field names16365
+Ref: #transaction-field-names16525
+Node: Posting field names16636
+Ref: #posting-field-names16788
+Node: account16858
+Ref: #account16974
+Node: amount17510
+Ref: #amount17641
+Node: currency18022
+Ref: #currency18157
+Node: balance18363
+Ref: #balance18497
+Node: comment18814
+Ref: #comment18931
+Node: field assignment19094
+Ref: #field-assignment19237
+Node: separator20055
+Ref: #separator20184
+Node: if20595
+Ref: #if20697
+Node: end22616
+Ref: #end22722
+Node: date-format22946
+Ref: #date-format23078
+Node: newest-first23827
+Ref: #newest-first23965
+Node: include24648
+Ref: #include24777
+Node: balance-type25221
+Ref: #balance-type25341
+Node: TIPS26041
+Ref: #tips26123
+Node: Rapid feedback26379
+Ref: #rapid-feedback26496
+Node: Valid CSV26956
+Ref: #valid-csv27086
+Node: File Extension27278
+Ref: #file-extension27430
+Node: Reading multiple CSV files27840
+Ref: #reading-multiple-csv-files28025
+Node: Valid transactions28266
+Ref: #valid-transactions28444
+Node: Deduplicating importing29072
+Ref: #deduplicating-importing29251
+Node: Setting amounts30284
+Ref: #setting-amounts30453
+Node: Setting currency/commodity31439
+Ref: #setting-currencycommodity31631
+Node: Referencing other fields32434
+Ref: #referencing-other-fields32634
+Node: How CSV rules are evaluated33531
+Ref: #how-csv-rules-are-evaluated33704

End Tag Table
diff --git a/hledger_csv.txt b/hledger_csv.txt
index e6d79e6..6a5959b 100644
--- a/hledger_csv.txt
+++ b/hledger_csv.txt
@@ -376,30 +376,45 @@ CSV RULES
transaction's first line.
Posting field names
- accountN, where N is 1 to 9, generates a posting, with that account
- name. Most often there are two postings, so you'll want to set ac-
- count1 and account2. If a posting's account name is left unset but its
- amount is set, a default account name will be chosen (like expenses:un-
- known or income:unknown).
-
- amountN sets posting N's amount. Or, amount with no N sets posting
- 1's. If the CSV has debits and credits in separate fields, use
- amountN-in and amountN-out instead. Or amount-in and amount-out with
- no N for posting 1.
-
- For convenience and backwards compatibility, if you set the amount of
- posting 1 only, a second posting with the negative amount will be gen-
- erated automatically. (Unless the account name is parenthesised indi-
- cating an unbalanced posting.)
-
- If the CSV has the currency symbol in a separate field, you can use
- currencyN to prepend it to posting N's amount. currency with no N af-
- fects ALL postings.
-
- balanceN sets a balance assertion amount (or if the posting amount is
- left empty, a balance assignment). You may need to adjust this with
- the balance-type rule.
+ account
+ accountN, where N is 1 to 9, causes a posting to be generated, with
+ that account name.
+ Most often there are two postings, so you'll want to set account1 and
+ account2. Typically account1 is associated with the CSV file, and is
+ set once with a top-level assignment, while account2 is set based on
+ each transaction's description, and in conditional blocks.
+
+ If a posting's account name is left unset but its amount is set (see
+ below), a default account name will be chosen (like "expenses:unknown"
+ or "income:unknown").
+
+ amount
+ amountN sets posting N's amount. If the CSV uses separate fields for
+ debit and credit amounts, you can use amountN-in and amountN-out in-
+ stead.
+
+ Also, for compatibility with hledger <1.17: amount or amount-in/amount-
+ out with no number sets the amount for postings 1 and 2. For posting 2
+ the amount is negated, and converted to cost if there's a transaction
+ price.
+
+ currency
+ If the CSV has the currency symbol in a separate field (ie, not part of
+ the amount field), you can use currencyN to prepend it to posting N's
+ amount. Or, currency with no number affects all postings.
+
+ balance
+ balanceN sets a balance assertion amount (or if the posting amount is
+ left empty, a balance assignment) on posting N.
+
+ Also, for compatibility with hledger <1.17: balance with no number is
+ equivalent to balance1.
+
+ You can adjust the type of assertion/assignment with the balance-type
+ rule (see below).
+
+ comment
Finally, commentN sets a comment on the Nth posting. Comments can also
contain tags, as usual.
@@ -819,4 +834,4 @@ SEE ALSO
-hledger 1.17 March 2020 hledger_csv(5)
+hledger 1.17.1 March 2020 hledger_csv(5)
diff --git a/hledger_journal.5 b/hledger_journal.5
index b4757fd..b37014e 100644
--- a/hledger_journal.5
+++ b/hledger_journal.5
@@ -1,6 +1,6 @@
.\"t
-.TH "hledger_journal" "5" "March 2020" "hledger 1.17" "hledger User Manuals"
+.TH "hledger_journal" "5" "March 2020" "hledger 1.17.1" "hledger User Manuals"
@@ -524,7 +524,7 @@ EUR 1E3
\f[R]
.fi
.PP
-A decimal mark (decimal point) can be written with a period or a comma:
+A decimal mark can be written as a period or a comma:
.IP
.nf
\f[C]
@@ -1027,6 +1027,16 @@ declare a year for yearless dates
T}@T{
following inline/included entries until end of current file
T}
+T{
+\f[C]=\f[R]
+T}@T{
+T}@T{
+T}@T{
+declare an auto posting rule, adding postings to other transactions
+T}@T{
+all entries in parent/current/child files (but not sibling files, see
+#1212)
+T}
.TE
.PP
And some definitions:
@@ -1066,13 +1076,21 @@ As you can see, directives vary in which journal entries and files they
affect, and whether they are focussed on input (parsing) or output
(reports).
Some directives have multiple effects.
+.SS Directives and multiple files
+.PP
+If you use multiple \f[C]-f\f[R]/\f[C]--file\f[R] options, or the
+\f[C]include\f[R] directive, hledger will process multiple input files.
+But note that directives which affect input (see above) typically last
+only until the end of the file in which they occur.
.PP
-If you have a journal made up of multiple files, or pass multiple -f
-options on the command line, note that directives which affect input
-typically last only until the end of their defining file.
-This provides more simplicity and predictability, eg reports are not
-changed by writing file options in a different order.
-It can be surprising at times though.
+This may seem inconvenient, but it\[aq]s intentional; it makes reports
+stable and deterministic, independent of the order of input.
+Otherwise you could see different numbers if you happened to write -f
+options in a different order, or if you moved includes around while
+cleaning up your files.
+.PP
+It can be surprising though; for example, it means that \f[C]alias\f[R]
+directives do not affect parent or sibling files (see below).
.SS Comment blocks
.PP
A line containing just \f[C]comment\f[R] starts a commented region of
@@ -1543,6 +1561,46 @@ independent of which files are being read and in which order.
.PP
In case of trouble, adding \f[C]--debug=6\f[R] to the command line will
show which aliases are being applied when.
+.SS Aliases and multiple files
+.PP
+As explained at Directives and multiple files, \f[C]alias\f[R]
+directives do not affect parent or sibling files.
+Eg in this command,
+.IP
+.nf
+\f[C]
+hledger -f a.aliases -f b.journal
+\f[R]
+.fi
+.PP
+account aliases defined in a.aliases will not affect b.journal.
+Including the aliases doesn\[aq]t work either:
+.IP
+.nf
+\f[C]
+include a.aliases
+
+2020-01-01 ; not affected by a.aliases
+ foo 1
+ bar
+\f[R]
+.fi
+.PP
+This means that account aliases should usually be declared at the start
+of your top-most file, like this:
+.IP
+.nf
+\f[C]
+alias foo=Foo
+alias bar=Bar
+
+2020-01-01 ; affected by aliases above
+ foo 1
+ bar
+
+include c.journal ; also affected
+\f[R]
+.fi
.SS \f[C]end aliases\f[R]
.PP
You can clear (forget) all currently defined aliases with the
@@ -1759,31 +1817,28 @@ Goals and actual performance can then be compared in budget reports.
For more details, see: balance: Budget report and Budgeting and
Forecasting.
.PP
-.SS Auto postings / transaction modifiers
+.SS Auto postings
.PP
-Transaction modifier rules, AKA auto posting rules, describe changes to
-be applied automatically to certain matched transactions.
-Currently just one kind of change is possible - adding extra postings,
-which we call \[dq]automated postings\[dq] or just \[dq]auto
-postings\[dq].
-These rules become active when you use the \f[C]--auto\f[R] flag.
+\[dq]Automated postings\[dq] or \[dq]auto postings\[dq] are extra
+postings which get added automatically to transactions which match
+certain queries, defined by \[dq]auto posting rules\[dq], when you use
+the \f[C]--auto\f[R] flag.
.PP
-A transaction modifier rule looks much like a normal transaction except
-the first line is an equals sign followed by a query that matches
-certain postings (mnemonic: \f[C]=\f[R] suggests matching).
-And each \[dq]posting\[dq] is actually a posting-generating rule:
+An auto posting rule looks a bit like a transaction:
.IP
.nf
\f[C]
= QUERY
ACCOUNT AMOUNT
- ACCOUNT [AMOUNT]
...
+ ACCOUNT [AMOUNT]
\f[R]
.fi
.PP
-These posting-generating rules look like normal postings, except the
-amount can be:
+except the first line is an equals sign (mnemonic: \f[C]=\f[R] suggests
+matching), followed by a query (which matches existing postings), and
+each \[dq]posting\[dq] line describes a posting to be generated, and the
+posting amounts can be:
.IP \[bu] 2
a normal amount with a commodity symbol, eg \f[C]$2\f[R].
This will be used as-is.
@@ -1801,7 +1856,7 @@ N, and symbol S).
The matched posting\[aq]s amount will be multiplied by N, and its
commodity symbol will be replaced with S.
.PP
-A query term containing spaces must be enclosed in single or double
+Any query term containing spaces must be enclosed in single or double
quotes, as on the command line.
Eg, note the quotes around the second query term below:
.IP
@@ -1812,10 +1867,6 @@ Eg, note the quotes around the second query term below:
\f[R]
.fi
.PP
-These rules have global effect - a rule appearing anywhere in your data
-can potentially affect any transaction, including transactions recorded
-above it or in another file.
-.PP
Some examples:
.IP
.nf
@@ -1854,6 +1905,12 @@ $ hledger print --auto
assets:checking $20
\f[R]
.fi
+.SS Auto postings and multiple files
+.PP
+An auto posting rule can affect any transaction in the current file, or
+in any parent file or child file.
+Note, currently it will not affect sibling files (when multiple
+\f[C]-f\f[R]/\f[C]--file\f[R] are used - see #1212).
.SS Auto postings and dates
.PP
A posting date (or secondary date) in the matched posting, or (taking
@@ -1861,7 +1918,7 @@ precedence) a posting date in the auto posting rule itself, will also be
used in the generated posting.
.SS Auto postings and transaction balancing / inferred amounts / balance assertions
.PP
-Currently, transaction modifiers are applied / auto postings are added:
+Currently, auto postings are added:
.IP \[bu] 2
after missing amounts are inferred, and transactions are checked for
balancedness,
@@ -1873,7 +1930,7 @@ after auto postings are added.
This changed in hledger 1.12+; see #893 for background.
.SS Auto posting tags
.PP
-Postings added by transaction modifiers will have some extra tags:
+Automated postings will have some extra tags:
.IP \[bu] 2
\f[C]generated-posting:= QUERY\f[R] - shows this was generated by an
auto posting rule, and the query
@@ -1883,8 +1940,8 @@ appear in hledger\[aq]s output.
This can be used to match postings generated \[dq]just now\[dq], rather
than generated in the past and saved to the journal.
.PP
-Also, any transaction that has been changed by transaction modifier
-rules will have these tags added:
+Also, any transaction that has been changed by auto posting rules will
+have these tags added:
.IP \[bu] 2
\f[C]modified:\f[R] - this transaction was modified
.IP \[bu] 2
diff --git a/hledger_journal.info b/hledger_journal.info
index bc45147..6114ec3 100644
--- a/hledger_journal.info
+++ b/hledger_journal.info
@@ -4,8 +4,8 @@ stdin.

File: hledger_journal.info, Node: Top, Up: (dir)
-hledger_journal(5) hledger 1.17
-*******************************
+hledger_journal(5) hledger 1.17.1
+*********************************
Journal - hledger's default file format, representing a General Journal
@@ -85,7 +85,7 @@ optional fields, separated by spaces:
* Balance Assignments::
* Directives::
* Periodic transactions::
-* Auto postings / transaction modifiers::
+* Auto postings::

File: hledger_journal.info, Node: Dates, Next: Status, Up: Transactions
@@ -470,8 +470,7 @@ $-1
1E-6
EUR 1E3
- A decimal mark (decimal point) can be written with a period or a
-comma:
+ A decimal mark can be written as a period or a comma:
1.23
1,23456780000009
@@ -912,6 +911,11 @@ account' apply account names inline/included
dates inline/included
entries until end
of current file
+'=' declare an auto posting all entries in
+ rule, adding postings to parent/current/child
+ other transactions files (but not
+ sibling files,
+ see #1212)
And some definitions:
@@ -929,15 +933,9 @@ scope are affected by a directive
they affect, and whether they are focussed on input (parsing) or output
(reports). Some directives have multiple effects.
- If you have a journal made up of multiple files, or pass multiple -f
-options on the command line, note that directives which affect input
-typically last only until the end of their defining file. This provides
-more simplicity and predictability, eg reports are not changed by
-writing file options in a different order. It can be surprising at
-times though.
-
* Menu:
+* Directives and multiple files::
* Comment blocks::
* Including other files::
* Default year::
@@ -949,9 +947,29 @@ times though.
* Default parent account::

-File: hledger_journal.info, Node: Comment blocks, Next: Including other files, Up: Directives
+File: hledger_journal.info, Node: Directives and multiple files, Next: Comment blocks, Up: Directives
+
+1.12.1 Directives and multiple files
+------------------------------------
+
+If you use multiple '-f'/'--file' options, or the 'include' directive,
+hledger will process multiple input files. But note that directives
+which affect input (see above) typically last only until the end of the
+file in which they occur.
+
+ This may seem inconvenient, but it's intentional; it makes reports
+stable and deterministic, independent of the order of input. Otherwise
+you could see different numbers if you happened to write -f options in a
+different order, or if you moved includes around while cleaning up your
+files.
+
+ It can be surprising though; for example, it means that 'alias'
+directives do not affect parent or sibling files (see below).
+
+
+File: hledger_journal.info, Node: Comment blocks, Next: Including other files, Prev: Directives and multiple files, Up: Directives
-1.12.1 Comment blocks
+1.12.2 Comment blocks
---------------------
A line containing just 'comment' starts a commented region of the file,
@@ -961,7 +979,7 @@ file) ends it. See also comments.

File: hledger_journal.info, Node: Including other files, Next: Default year, Prev: Comment blocks, Up: Directives
-1.12.2 Including other files
+1.12.3 Including other files
----------------------------
You can pull in the content of additional files by writing an include
@@ -979,7 +997,7 @@ 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.12.3 Default year
+1.12.4 Default year
-------------------
You can set a default year to be used for subsequent dates which don't
@@ -1005,7 +1023,7 @@ Y2010 ; change default year to 2010

File: hledger_journal.info, Node: Declaring commodities, Next: Default commodity, Prev: Default year, Up: Directives
-1.12.4 Declaring commodities
+1.12.5 Declaring commodities
----------------------------
The 'commodity' directive has several functions:
@@ -1055,7 +1073,7 @@ a comma, followed by 0 or more decimal digits.

File: hledger_journal.info, Node: Default commodity, Next: Market prices, Prev: Declaring commodities, Up: Directives
-1.12.5 Default commodity
+1.12.6 Default commodity
------------------------
The 'D' directive sets a default commodity, to be used for amounts
@@ -1082,7 +1100,7 @@ D $1,000.00

File: hledger_journal.info, Node: Market prices, Next: Declaring accounts, Prev: Default commodity, Up: Directives
-1.12.6 Market prices
+1.12.7 Market prices
--------------------
The 'P' directive declares a market price, which is an exchange rate
@@ -1112,7 +1130,7 @@ another commodity using these prices.

File: hledger_journal.info, Node: Declaring accounts, Next: Rewriting accounts, Prev: Market prices, Up: Directives
-1.12.7 Declaring accounts
+1.12.8 Declaring accounts
-------------------------
'account' directives can be used to pre-declare accounts. Though not
@@ -1145,7 +1163,7 @@ account assets:bank:checking

File: hledger_journal.info, Node: Account comments, Next: Account subdirectives, Up: Declaring accounts
-1.12.7.1 Account comments
+1.12.8.1 Account comments
.........................
Comments, beginning with a semicolon, can be added:
@@ -1165,7 +1183,7 @@ account assets:bank:checking ; same-line comment, note 2+ spaces before ;

File: hledger_journal.info, Node: Account subdirectives, Next: Account types, Prev: Account comments, Up: Declaring accounts
-1.12.7.2 Account subdirectives
+1.12.8.2 Account subdirectives
..............................
We also allow (and ignore) Ledger-style indented subdirectives, just for
@@ -1183,7 +1201,7 @@ account ACCTNAME [ACCTTYPE] [;COMMENT]

File: hledger_journal.info, Node: Account types, Next: Account display order, Prev: Account subdirectives, Up: Declaring accounts
-1.12.7.3 Account types
+1.12.8.3 Account types
......................
hledger recognises five types (or classes) of account: Asset, Liability,
@@ -1228,7 +1246,7 @@ account - ; type:L

File: hledger_journal.info, Node: Account display order, Prev: Account types, Up: Declaring accounts
-1.12.7.4 Account display order
+1.12.8.4 Account display order
..............................
Account directives also set the order in which accounts are displayed,
@@ -1274,7 +1292,7 @@ means:

File: hledger_journal.info, Node: Rewriting accounts, Next: Default parent account, Prev: Declaring accounts, Up: Directives
-1.12.8 Rewriting accounts
+1.12.9 Rewriting accounts
-------------------------
You can define account alias rules which rewrite your account names, or
@@ -1298,12 +1316,13 @@ hledger-web.
* Basic aliases::
* Regex aliases::
* Combining aliases::
+* Aliases and multiple files::
* end aliases::

File: hledger_journal.info, Node: Basic aliases, Next: Regex aliases, Up: Rewriting accounts
-1.12.8.1 Basic aliases
+1.12.9.1 Basic aliases
......................
To set an account alias, use the 'alias' directive in your journal file.
@@ -1326,7 +1345,7 @@ alias checking = assets:bank:wells fargo:checking

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

-File: hledger_journal.info, Node: Combining aliases, Next: end aliases, Prev: Regex aliases, Up: Rewriting accounts
+File: hledger_journal.info, Node: Combining aliases, Next: Aliases and multiple files, Prev: Regex aliases, Up: Rewriting accounts
-1.12.8.3 Combining aliases
+1.12.9.3 Combining aliases
..........................
You can define as many aliases as you like, using journal directives
@@ -1386,9 +1405,41 @@ independent of which files are being read and in which order.
which aliases are being applied when.

-File: hledger_journal.info, Node: end aliases, Prev: Combining aliases, Up: Rewriting accounts
+File: hledger_journal.info, Node: Aliases and multiple files, Next: end aliases, Prev: Combining aliases, Up: Rewriting accounts
+
+1.12.9.4 Aliases and multiple files
+...................................
+
+As explained at Directives and multiple files, 'alias' directives do not
+affect parent or sibling files. Eg in this command,
+
+hledger -f a.aliases -f b.journal
+
+ account aliases defined in a.aliases will not affect b.journal.
+Including the aliases doesn't work either:
+
+include a.aliases
+
+2020-01-01 ; not affected by a.aliases
+ foo 1
+ bar
+
+ This means that account aliases should usually be declared at the
+start of your top-most file, like this:
+
+alias foo=Foo
+alias bar=Bar
+
+2020-01-01 ; affected by aliases above
+ foo 1
+ bar
+
+include c.journal ; also affected
+
+
+File: hledger_journal.info, Node: end aliases, Prev: Aliases and multiple files, Up: Rewriting accounts
-1.12.8.4 'end aliases'
+1.12.9.5 'end aliases'
......................
You can clear (forget) all currently defined aliases with the 'end
@@ -1399,8 +1450,8 @@ end aliases

File: hledger_journal.info, Node: Default parent account, Prev: Rewriting accounts, Up: Directives
-1.12.9 Default parent account
------------------------------
+1.12.10 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
@@ -1438,7 +1489,7 @@ If account aliases are present, they are applied after the default
parent account.

-File: hledger_journal.info, Node: Periodic transactions, Next: Auto postings / transaction modifiers, Prev: Directives, Up: Transactions
+File: hledger_journal.info, Node: Periodic transactions, Next: Auto postings, Prev: Directives, Up: Transactions
1.13 Periodic transactions
==========================
@@ -1600,29 +1651,26 @@ compared in budget reports.
Forecasting.

-File: hledger_journal.info, Node: Auto postings / transaction modifiers, Prev: Periodic transactions, Up: Transactions
+File: hledger_journal.info, Node: Auto postings, Prev: Periodic transactions, Up: Transactions
-1.14 Auto postings / transaction modifiers
-==========================================
+1.14 Auto postings
+==================
-Transaction modifier rules, AKA auto posting rules, describe changes to
-be applied automatically to certain matched transactions. Currently
-just one kind of change is possible - adding extra postings, which we
-call "automated postings" or just "auto postings". These rules become
-active when you use the '--auto' flag.
+"Automated postings" or "auto postings" are extra postings which get
+added automatically to transactions which match certain queries, defined
+by "auto posting rules", when you use the '--auto' flag.
- A transaction modifier rule looks much like a normal transaction
-except the first line is an equals sign followed by a query that matches
-certain postings (mnemonic: '=' suggests matching). And each "posting"
-is actually a posting-generating rule:
+ An auto posting rule looks a bit like a transaction:
= QUERY
ACCOUNT AMOUNT
- ACCOUNT [AMOUNT]
...
+ ACCOUNT [AMOUNT]
- These posting-generating rules look like normal postings, except the
-amount can be:
+ except the first line is an equals sign (mnemonic: '=' suggests
+matching), followed by a query (which matches existing postings), and
+each "posting" line describes a posting to be generated, and the posting
+amounts can be:
* a normal amount with a commodity symbol, eg '$2'. This will be
used as-is.
@@ -1635,17 +1683,13 @@ amount can be:
and symbol S). The matched posting's amount will be multiplied by
N, and its commodity symbol will be replaced with S.
- A query term containing spaces must be enclosed in single or double
+ Any query term containing spaces must be enclosed in single or double
quotes, as on the command line. Eg, note the quotes around the second
query term below:
= expenses:groceries 'expenses:dining out'
(budget:funds:dining out) *-1
- These rules have global effect - a rule appearing anywhere in your
-data can potentially affect any transaction, including transactions
-recorded above it or in another file.
-
Some examples:
; every time I buy food, schedule a dollar donation
@@ -1679,14 +1723,25 @@ $ hledger print --auto
* Menu:
+* Auto postings and multiple files::
* Auto postings and dates::
* Auto postings and transaction balancing / inferred amounts / balance assertions::
* Auto posting tags::

-File: hledger_journal.info, Node: Auto postings and dates, Next: Auto postings and transaction balancing / inferred amounts / balance assertions, Up: Auto postings / transaction modifiers
+File: hledger_journal.info, Node: Auto postings and multiple files, Next: Auto postings and dates, Up: Auto postings
+
+1.14.1 Auto postings and multiple files
+---------------------------------------
+
+An auto posting rule can affect any transaction in the current file, or
+in any parent file or child file. Note, currently it will not affect
+sibling files (when multiple '-f'/'--file' are used - see #1212).
+
+
+File: hledger_journal.info, Node: Auto postings and dates, Next: Auto postings and transaction balancing / inferred amounts / balance assertions, Prev: Auto postings and multiple files, Up: Auto postings
-1.14.1 Auto postings and dates
+1.14.2 Auto postings and dates
------------------------------
A posting date (or secondary date) in the matched posting, or (taking
@@ -1694,13 +1749,12 @@ precedence) a posting date in the auto posting rule itself, will also be
used in the generated posting.

-File: hledger_journal.info, Node: Auto postings and transaction balancing / inferred amounts / balance assertions, Next: Auto posting tags, Prev: Auto postings and dates, Up: Auto postings / transaction modifiers
+File: hledger_journal.info, Node: Auto postings and transaction balancing / inferred amounts / balance assertions, Next: Auto posting tags, Prev: Auto postings and dates, Up: Auto postings
-1.14.2 Auto postings and transaction balancing / inferred amounts /
+1.14.3 Auto postings and transaction balancing / inferred amounts /
-------------------------------------------------------------------
-balance assertions Currently, transaction modifiers are applied / auto
-postings are added:
+balance assertions Currently, auto postings are added:
* after missing amounts are inferred, and transactions are checked
for balancedness,
@@ -1711,12 +1765,12 @@ after auto postings are added. This changed in hledger 1.12+; see #893
for background.

-File: hledger_journal.info, Node: Auto posting tags, Prev: Auto postings and transaction balancing / inferred amounts / balance assertions, Up: Auto postings / transaction modifiers
+File: hledger_journal.info, Node: Auto posting tags, Prev: Auto postings and transaction balancing / inferred amounts / balance assertions, Up: Auto postings
-1.14.3 Auto posting tags
+1.14.4 Auto posting tags
------------------------
-Postings added by transaction modifiers will have some extra tags:
+Automated postings will have some extra tags:
* 'generated-posting:= QUERY' - shows this was generated by an auto
posting rule, and the query
@@ -1725,8 +1779,8 @@ Postings added by transaction modifiers will have some extra tags:
"just now", rather than generated in the past and saved to the
journal.
- Also, any transaction that has been changed by transaction modifier
-rules will have these tags added:
+ Also, any transaction that has been changed by auto posting rules
+will have these tags added:
* 'modified:' - this transaction was modified
* '_modified:' - a hidden tag not appearing in the comment; this
@@ -1735,116 +1789,122 @@ rules will have these tags added:

Tag Table:
Node: Top76
-Node: Transactions1869
-Ref: #transactions1961
-Node: Dates3150
-Ref: #dates3249
-Node: Simple dates3314
-Ref: #simple-dates3440
-Node: Secondary dates3949
-Ref: #secondary-dates4103
-Node: Posting dates5439
-Ref: #posting-dates5568
-Node: Status6940
-Ref: #status7061
-Node: Description8769
-Ref: #description8903
-Node: Payee and note9223
-Ref: #payee-and-note9337
-Node: Comments9672
-Ref: #comments9798
-Node: Tags10992
-Ref: #tags11107
-Node: Postings12500
-Ref: #postings12628
-Node: Virtual Postings13654
-Ref: #virtual-postings13771
-Node: Account names15076
-Ref: #account-names15217
-Node: Amounts15704
-Ref: #amounts15843
-Node: Digit group marks16775
-Ref: #digit-group-marks16923
-Node: Amount display style17861
-Ref: #amount-display-style18015
-Node: Transaction prices19176
-Ref: #transaction-prices19342
-Node: Balance Assertions21608
-Ref: #balance-assertions21788
-Node: Assertions and ordering22821
-Ref: #assertions-and-ordering23009
-Node: Assertions and included files23709
-Ref: #assertions-and-included-files23952
-Node: Assertions and multiple -f options24285
-Ref: #assertions-and-multiple--f-options24541
-Node: Assertions and commodities24673
-Ref: #assertions-and-commodities24905
-Node: Assertions and prices26062
-Ref: #assertions-and-prices26276
-Node: Assertions and subaccounts26716
-Ref: #assertions-and-subaccounts26945
-Node: Assertions and virtual postings27269
-Ref: #assertions-and-virtual-postings27511
-Node: Assertions and precision27653
-Ref: #assertions-and-precision27846
-Node: Balance Assignments28113
-Ref: #balance-assignments28287
-Node: Balance assignments and prices29451
-Ref: #balance-assignments-and-prices29623
-Node: Directives29847
-Ref: #directives30006
-Node: Comment blocks35654
-Ref: #comment-blocks35799
-Node: Including other files35975
-Ref: #including-other-files36155
-Node: Default year36563
-Ref: #default-year36732
-Node: Declaring commodities37139
-Ref: #declaring-commodities37322
-Node: Default commodity38995
-Ref: #default-commodity39171
-Node: Market prices40060
-Ref: #market-prices40225
-Node: Declaring accounts41066
-Ref: #declaring-accounts41242
-Node: Account comments42167
-Ref: #account-comments42330
-Node: Account subdirectives42754
-Ref: #account-subdirectives42949
-Node: Account types43262
-Ref: #account-types43446
-Node: Account display order45085
-Ref: #account-display-order45255
-Node: Rewriting accounts46406
-Ref: #rewriting-accounts46591
-Node: Basic aliases47317
-Ref: #basic-aliases47463
-Node: Regex aliases48167
-Ref: #regex-aliases48339
-Node: Combining aliases49057
-Ref: #combining-aliases49235
-Node: end aliases50511
-Ref: #end-aliases50659
-Node: Default parent account50760
-Ref: #default-parent-account50926
-Node: Periodic transactions51810
-Ref: #periodic-transactions52009
-Node: Periodic rule syntax53881
-Ref: #periodic-rule-syntax54087
-Node: Two spaces between period expression and description!54791
-Ref: #two-spaces-between-period-expression-and-description55110
-Node: Forecasting with periodic transactions55794
-Ref: #forecasting-with-periodic-transactions56099
-Node: Budgeting with periodic transactions58125
-Ref: #budgeting-with-periodic-transactions58364
-Node: Auto postings / transaction modifiers58813
-Ref: #auto-postings-transaction-modifiers59025
-Node: Auto postings and dates61521
-Ref: #auto-postings-and-dates61778
-Node: Auto postings and transaction balancing / inferred amounts / balance assertions61953
-Ref: #auto-postings-and-transaction-balancing-inferred-amounts-balance-assertions62328
-Node: Auto posting tags62706
-Ref: #auto-posting-tags62945
+Node: Transactions1873
+Ref: #transactions1965
+Node: Dates3130
+Ref: #dates3229
+Node: Simple dates3294
+Ref: #simple-dates3420
+Node: Secondary dates3929
+Ref: #secondary-dates4083
+Node: Posting dates5419
+Ref: #posting-dates5548
+Node: Status6920
+Ref: #status7041
+Node: Description8749
+Ref: #description8883
+Node: Payee and note9203
+Ref: #payee-and-note9317
+Node: Comments9652
+Ref: #comments9778
+Node: Tags10972
+Ref: #tags11087
+Node: Postings12480
+Ref: #postings12608
+Node: Virtual Postings13634
+Ref: #virtual-postings13751
+Node: Account names15056
+Ref: #account-names15197
+Node: Amounts15684
+Ref: #amounts15823
+Node: Digit group marks16737
+Ref: #digit-group-marks16885
+Node: Amount display style17823
+Ref: #amount-display-style17977
+Node: Transaction prices19138
+Ref: #transaction-prices19304
+Node: Balance Assertions21570
+Ref: #balance-assertions21750
+Node: Assertions and ordering22783
+Ref: #assertions-and-ordering22971
+Node: Assertions and included files23671
+Ref: #assertions-and-included-files23914
+Node: Assertions and multiple -f options24247
+Ref: #assertions-and-multiple--f-options24503
+Node: Assertions and commodities24635
+Ref: #assertions-and-commodities24867
+Node: Assertions and prices26024
+Ref: #assertions-and-prices26238
+Node: Assertions and subaccounts26678
+Ref: #assertions-and-subaccounts26907
+Node: Assertions and virtual postings27231
+Ref: #assertions-and-virtual-postings27473
+Node: Assertions and precision27615
+Ref: #assertions-and-precision27808
+Node: Balance Assignments28075
+Ref: #balance-assignments28249
+Node: Balance assignments and prices29413
+Ref: #balance-assignments-and-prices29585
+Node: Directives29809
+Ref: #directives29968
+Node: Directives and multiple files35649
+Ref: #directives-and-multiple-files35832
+Node: Comment blocks36496
+Ref: #comment-blocks36679
+Node: Including other files36855
+Ref: #including-other-files37035
+Node: Default year37443
+Ref: #default-year37612
+Node: Declaring commodities38019
+Ref: #declaring-commodities38202
+Node: Default commodity39875
+Ref: #default-commodity40051
+Node: Market prices40940
+Ref: #market-prices41105
+Node: Declaring accounts41946
+Ref: #declaring-accounts42122
+Node: Account comments43047
+Ref: #account-comments43210
+Node: Account subdirectives43634
+Ref: #account-subdirectives43829
+Node: Account types44142
+Ref: #account-types44326
+Node: Account display order45965
+Ref: #account-display-order46135
+Node: Rewriting accounts47286
+Ref: #rewriting-accounts47471
+Node: Basic aliases48228
+Ref: #basic-aliases48374
+Node: Regex aliases49078
+Ref: #regex-aliases49250
+Node: Combining aliases49968
+Ref: #combining-aliases50161
+Node: Aliases and multiple files51437
+Ref: #aliases-and-multiple-files51646
+Node: end aliases52225
+Ref: #end-aliases52382
+Node: Default parent account52483
+Ref: #default-parent-account52651
+Node: Periodic transactions53535
+Ref: #periodic-transactions53710
+Node: Periodic rule syntax55582
+Ref: #periodic-rule-syntax55788
+Node: Two spaces between period expression and description!56492
+Ref: #two-spaces-between-period-expression-and-description56811
+Node: Forecasting with periodic transactions57495
+Ref: #forecasting-with-periodic-transactions57800
+Node: Budgeting with periodic transactions59826
+Ref: #budgeting-with-periodic-transactions60065
+Node: Auto postings60514
+Ref: #auto-postings60654
+Node: Auto postings and multiple files62833
+Ref: #auto-postings-and-multiple-files63037
+Node: Auto postings and dates63246
+Ref: #auto-postings-and-dates63520
+Node: Auto postings and transaction balancing / inferred amounts / balance assertions63695
+Ref: #auto-postings-and-transaction-balancing-inferred-amounts-balance-assertions64046
+Node: Auto posting tags64388
+Ref: #auto-posting-tags64603

End Tag Table
diff --git a/hledger_journal.txt b/hledger_journal.txt
index 6116f25..402d9fb 100644
--- a/hledger_journal.txt
+++ b/hledger_journal.txt
@@ -370,7 +370,7 @@ FILE FORMAT
1E-6
EUR 1E3
- A decimal mark (decimal point) can be written with a period or a comma:
+ A decimal mark can be written as a period or a comma:
1.23
1,23456780000009
@@ -709,6 +709,11 @@ FILE FORMAT
dates line/included en-
tries until end of
current file
+ = declare an auto posting all entries in par-
+ rule, adding postings to ent/current/child
+ other transactions files (but not sib-
+ ling files, see
+ #1212)
And some definitions:
@@ -718,12 +723,10 @@ FILE FORMAT
number how to interpret numbers when parsing journal entries (the iden-
nota- tity of the decimal separator character). (Currently each com-
tion modity can have its own notation, even in the same file.)
+
dis- how to display amounts of a commodity in reports (symbol side
play and spacing, digit groups, decimal separator, decimal places)
style
-
-
-
direc- which entries and (when there are multiple files) which files
tive are affected by a directive
scope
@@ -732,34 +735,42 @@ FILE FORMAT
affect, and whether they are focussed on input (parsing) or output (re-
ports). Some directives have multiple effects.
- If you have a journal made up of multiple files, or pass multiple -f
- options on the command line, note that directives which affect input
- typically last only until the end of their defining file. This pro-
- vides more simplicity and predictability, eg reports are not changed by
- writing file options in a different order. It can be surprising at
- times though.
+ Directives and multiple files
+ If you use multiple -f/--file options, or the include directive,
+ hledger will process multiple input files. But note that directives
+ which affect input (see above) typically last only until the end of the
+ file in which they occur.
+
+ This may seem inconvenient, but it's intentional; it makes reports sta-
+ ble and deterministic, independent of the order of input. Otherwise
+ you could see different numbers if you happened to write -f options in
+ a different order, or if you moved includes around while cleaning up
+ your files.
+
+ It can be surprising though; for example, it means that alias direc-
+ tives do not affect parent or sibling files (see below).
Comment blocks
- A line containing just comment starts a commented region of the file,
+ 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
+ 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. The include file path may contain common glob patterns (e.g.
+ If the path does not begin with a slash, it is relative to the current
+ file. The include file path may contain common glob patterns (e.g.
*).
- The include directive can only be used in journal files. It can in-
+ The include directive can only be used in journal files. It can in-
clude 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.
+ 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
@@ -781,19 +792,19 @@ FILE FORMAT
Declaring commodities
The commodity directive has several functions:
- 1. It declares commodities which may be used in the journal. This is
+ 1. It declares commodities which may be used in the journal. This is
currently not enforced, but can serve as documentation.
- 2. It declares what decimal mark character (period or comma) to expect
- when parsing input - useful to disambiguate international number
- formats in your data. (Without this, hledger will parse both 1,000
+ 2. It declares what decimal mark character (period or comma) to expect
+ when parsing input - useful to disambiguate international number
+ formats in your data. (Without this, hledger will parse both 1,000
and 1.000 as 1).
- 3. It declares the amount display style to use in output - decimal and
+ 3. It declares the amount display style to use in output - decimal and
digit group marks, number of decimal places, symbol placement etc.
- You are likely to run into one of the problems solved by commodity di-
- rectives, sooner or later, so it's a good idea to just always use them
+ You are likely to run into one of the problems solved by commodity di-
+ rectives, sooner or later, so it's a good idea to just always use them
to declare your commodities.
A commodity directive is just the word commodity followed by an amount.
@@ -806,8 +817,8 @@ FILE FORMAT
; 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
+ 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
@@ -820,19 +831,19 @@ FILE FORMAT
format INR 1,00,00,000.00
The quantity of the amount does not matter; only the format is signifi-
- cant. The number must include a decimal mark: either a period or a
+ cant. The number must include a decimal mark: either a period or a
comma, followed by 0 or more decimal digits.
Default commodity
- The D directive sets a default commodity, to be used for amounts with-
+ The D directive sets a default commodity, to be used for amounts with-
out a commodity symbol (ie, plain numbers). This commodity will be ap-
plied to all subsequent commodity-less amounts, or until the next D di-
rective. (Note, this is different from Ledger's D.)
- For compatibility/historical reasons, D also acts like a commodity di-
+ For compatibility/historical reasons, D also acts like a commodity di-
rective, setting the commodity's display style (for output) and decimal
mark (for parsing input). As with commodity, the amount must always be
- written with a decimal mark (period or comma). If both directives are
+ written with a decimal mark (period or comma). If both directives are
used, commodity's style takes precedence.
The syntax is D AMOUNT. Eg:
@@ -846,9 +857,9 @@ FILE FORMAT
b
Market prices
- The P directive declares a market price, which is an exchange rate be-
- tween two commodities on a certain date. (In Ledger, they are called
- "historical prices".) These are often obtained from a stock exchange,
+ The P directive declares a market price, which is an exchange rate be-
+ tween two commodities on a certain date. (In Ledger, they are called
+ "historical prices".) These are often obtained from a stock exchange,
cryptocurrency exchange, or the foreign exchange market.
Here is the format:
@@ -859,16 +870,16 @@ FILE FORMAT
o COMMODITYA is the symbol of the commodity being priced
- o COMMODITYBAMOUNT is an amount (symbol and quantity) in a second com-
+ o COMMODITYBAMOUNT is an amount (symbol and quantity) in a second com-
modity, giving the price in commodity B of one unit of commodity A.
- These two market price directives say that one euro was worth 1.35 US
+ These two market price directives say that one euro was worth 1.35 US
dollars during 2009, and $1.40 from 2010 onward:
P 2009/1/1 EUR $1.35
P 2010/1/1 EUR $1.40
- The -V/--value flag can be used to convert reported amounts to another
+ The -V/--value flag can be used to convert reported amounts to another
commodity using these prices.
Declaring accounts
@@ -878,20 +889,20 @@ FILE FORMAT
o They can document your intended chart of accounts, providing a refer-
ence.
- o They can store extra information about accounts (account numbers,
+ o They can store extra information about accounts (account numbers,
notes, etc.)
- o They can help hledger know your accounts' types (asset, liability,
- equity, revenue, expense), useful for reports like balancesheet and
+ o They can help hledger know your accounts' types (asset, liability,
+ equity, revenue, expense), useful for reports like balancesheet and
incomestatement.
- o They control account display order in reports, allowing non-alpha-
+ o They control account display order in reports, allowing non-alpha-
betic sorting (eg Revenues to appear above Expenses).
- o They help with account name completion in the add command, hledger-
+ o They help with account name completion in the add command, hledger-
iadd, hledger-web, ledger-mode etc.
- The simplest form is just the word account followed by a hledger-style
+ The simplest form is just the word account followed by a hledger-style
account name, eg:
account assets:bank:checking
@@ -899,7 +910,7 @@ FILE FORMAT
Account comments
Comments, beginning with a semicolon, can be added:
- o on the same line, after two or more spaces (because ; is allowed in
+ o on the same line, after two or more spaces (because ; is allowed in
account names)
o on the next lines, indented
@@ -913,7 +924,7 @@ FILE FORMAT
Same-line comments are not supported by Ledger, or hledger <1.13.
Account subdirectives
- We also allow (and ignore) Ledger-style indented subdirectives, just
+ We also allow (and ignore) Ledger-style indented subdirectives, just
for compatibility.:
account assets:bank:checking
@@ -926,18 +937,18 @@ FILE FORMAT
[LEDGER-STYLE SUBDIRECTIVES, IGNORED]
Account types
- hledger recognises five types (or classes) of account: Asset, Liabil-
- ity, Equity, Revenue, Expense. This is used by a few accounting-aware
+ hledger recognises five types (or classes) of account: Asset, Liabil-
+ ity, Equity, Revenue, Expense. This is used by a few accounting-aware
reports such as balancesheet, incomestatement and cashflow.
Auto-detected account types
If you name your top-level accounts with some variation of assets, lia-
- bilities/debts, equity, revenues/income, or expenses, their types are
+ bilities/debts, equity, revenues/income, or expenses, their types are
detected automatically.
Account types declared with tags
- More generally, you can declare an account's type with an account di-
- rective, by writing a type: tag in a comment, followed by one of the
+ More generally, you can declare an account's type with an account di-
+ rective, by writing a type: tag in a comment, followed by one of the
words Asset, Liability, Equity, Revenue, Expense, or one of the letters
ALERX (case insensitive):
@@ -948,8 +959,8 @@ FILE FORMAT
account expenses ; type:Expense
Account types declared with account type codes
- Or, you can write one of those letters separated from the account name
- by two or more spaces, but this should probably be considered depre-
+ Or, you can write one of those letters separated from the account name
+ by two or more spaces, but this should probably be considered depre-
cated as of hledger 1.13:
account assets A
@@ -959,7 +970,7 @@ FILE FORMAT
account expenses X
Overriding auto-detected types
- If you ever override the types of those auto-detected english account
+ If you ever override the types of those auto-detected english account
names mentioned above, you might need to help the reports a bit. Eg:
; make "liabilities" not have the liability type - who knows why
@@ -970,8 +981,8 @@ FILE FORMAT
account - ; type:L
Account display order
- Account directives also set the order in which accounts are displayed,
- eg in reports, the hledger-ui accounts screen, and the hledger-web
+ Account directives also set the order in which accounts are displayed,
+ eg in reports, the hledger-ui accounts screen, and the hledger-web
sidebar. By default accounts are listed in alphabetical order. But if
you have these account directives in the journal:
@@ -993,20 +1004,20 @@ FILE FORMAT
Undeclared accounts, if any, are displayed last, in alphabetical order.
- Note that sorting is done at each level of the account tree (within
- each group of sibling accounts under the same parent). And currently,
+ Note that sorting is done at each level of the account tree (within
+ each group of sibling accounts under the same parent). And currently,
this directive:
account other:zoo
- would influence the position of zoo among other's subaccounts, but not
+ would influence the position of zoo among other's subaccounts, but not
the position of other among the top-level accounts. This means:
- o you will sometimes declare parent accounts (eg account other above)
+ o you will sometimes declare parent accounts (eg account other above)
that you don't intend to post to, just to customize their display or-
der
- o sibling accounts stay together (you couldn't display x:y in between
+ o sibling accounts stay together (you couldn't display x:y in between
a:b and a:c).
Rewriting accounts
@@ -1024,14 +1035,14 @@ FILE FORMAT
o customising reports
Account aliases also rewrite account names in account directives. They
- do not affect account names being entered via hledger add or hledger-
+ do not affect account names being entered via hledger add or hledger-
web.
See also 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
@@ -1039,49 +1050,49 @@ 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 case sensitive full account names. hledger will re-
- place any occurrence of the old account name with the new one. Subac-
+ OLD and NEW are case sensitive full account names. hledger will re-
+ place any occurrence of the old account name with the new one. Subac-
counts 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.
Combining aliases
- You can define as many aliases as you like, using journal directives
+ You can define as many aliases as you like, using journal directives
and/or command line options.
- Recursive aliases - where an account name is rewritten by one alias,
- then by another alias, and so on - are allowed. Each alias sees the
+ Recursive aliases - where an account name is rewritten by one alias,
+ then by another alias, and so on - are allowed. Each alias sees the
effect of previously applied aliases.
- In such cases it can be important to understand which aliases will be
- applied and in which order. For (each account name in) each journal
+ In such cases it can be important to understand which aliases will be
+ applied and in which order. For (each account name in) each journal
entry, we apply:
- 1. alias directives preceding the journal entry, most recently parsed
+ 1. alias directives preceding the journal entry, most recently parsed
first (ie, reading upward from the journal entry, bottom to top)
- 2. --alias options, in the order they appeared on the command line
+ 2. --alias options, in the order they appeared on the command line
(left to right).
In other words, for (an account name in) a given journal entry:
@@ -1092,13 +1103,40 @@ FILE FORMAT
o aliases defined after/below the entry do not affect it.
- This gives nearby aliases precedence over distant ones, and helps pro-
- vide semantic stability - aliases will keep working the same way inde-
+ This gives nearby aliases precedence over distant ones, and helps pro-
+ vide semantic stability - aliases will keep working the same way inde-
pendent of which files are being read and in which order.
- In case of trouble, adding --debug=6 to the command line will show
+ In case of trouble, adding --debug=6 to the command line will show
which aliases are being applied when.
+ Aliases and multiple files
+ As explained at Directives and multiple files, alias directives do not
+ affect parent or sibling files. Eg in this command,
+
+ hledger -f a.aliases -f b.journal
+
+ account aliases defined in a.aliases will not affect b.journal. In-
+ cluding the aliases doesn't work either:
+
+ include a.aliases
+
+ 2020-01-01 ; not affected by a.aliases
+ foo 1
+ bar
+
+ This means that account aliases should usually be declared at the start
+ of your top-most file, like this:
+
+ alias foo=Foo
+ alias bar=Bar
+
+ 2020-01-01 ; affected by aliases above
+ foo 1
+ bar
+
+ include c.journal ; also affected
+
end aliases
You can clear (forget) all currently defined aliases with the end
aliases directive:
@@ -1278,51 +1316,44 @@ FILE FORMAT
For more details, see: balance: Budget report and Budgeting and Fore-
casting.
- Auto postings / transaction modifiers
- Transaction modifier rules, AKA auto posting rules, describe changes to
- be applied automatically to certain matched transactions. Currently
- just one kind of change is possible - adding extra postings, which we
- call "automated postings" or just "auto postings". These rules become
- active when you use the --auto flag.
+ Auto postings
+ "Automated postings" or "auto postings" are extra postings which get
+ added automatically to transactions which match certain queries, de-
+ fined by "auto posting rules", when you use the --auto flag.
- A transaction modifier rule looks much like a normal transaction except
- the first line is an equals sign followed by a query that matches cer-
- tain postings (mnemonic: = suggests matching). And each "posting" is
- actually a posting-generating rule:
+ An auto posting rule looks a bit like a transaction:
= QUERY
ACCOUNT AMOUNT
- ACCOUNT [AMOUNT]
...
+ ACCOUNT [AMOUNT]
- These posting-generating rules look like normal postings, except the
- amount can be:
+ except the first line is an equals sign (mnemonic: = suggests match-
+ ing), followed by a query (which matches existing postings), and each
+ "posting" line describes a posting to be generated, and the posting
+ amounts can be:
- o a normal amount with a commodity symbol, eg $2. This will be used
+ o a normal amount with a commodity symbol, eg $2. This will be used
as-is.
o a number, eg 2. The commodity symbol (if any) from the matched post-
ing will be added to this.
- o a numeric multiplier, eg *2 (a star followed by a number N). The
+ o a numeric multiplier, eg *2 (a star followed by a number N). The
matched posting's amount (and total price, if any) will be multiplied
by N.
- o a multiplier with a commodity symbol, eg *$2 (a star, number N, and
+ o a multiplier with a commodity symbol, eg *$2 (a star, number N, and
symbol S). The matched posting's amount will be multiplied by N, and
its commodity symbol will be replaced with S.
- A query term containing spaces must be enclosed in single or double
- quotes, as on the command line. Eg, note the quotes around the second
+ Any query term containing spaces must be enclosed in single or double
+ quotes, as on the command line. Eg, note the quotes around the second
query term below:
= expenses:groceries 'expenses:dining out'
(budget:funds:dining out) *-1
- These rules have global effect - a rule appearing anywhere in your data
- can potentially affect any transaction, including transactions recorded
- above it or in another file.
-
Some examples:
; every time I buy food, schedule a dollar donation
@@ -1354,36 +1385,41 @@ FILE FORMAT
assets:checking:gifts -$20
assets:checking $20
+ Auto postings and multiple files
+ An auto posting rule can affect any transaction in the current file, or
+ in any parent file or child file. Note, currently it will not affect
+ sibling files (when multiple -f/--file are used - see #1212).
+
Auto postings and dates
- A posting date (or secondary date) in the matched posting, or (taking
- precedence) a posting date in the auto posting rule itself, will also
+ A posting date (or secondary date) in the matched posting, or (taking
+ precedence) a posting date in the auto posting rule itself, will also
be used in the generated posting.
Auto postings and transaction balancing / inferred amounts / balance asser-
tions
- Currently, transaction modifiers are applied / auto postings are added:
+ Currently, auto postings are added:
- o after missing amounts are inferred, and transactions are checked for
+ o after missing amounts are inferred, and transactions are checked for
balancedness,
o but before balance assertions are checked.
- Note this means that journal entries must be balanced both before and
+ Note this means that journal entries must be balanced both before and
after auto postings are added. This changed in hledger 1.12+; see #893
for background.
Auto posting tags
- Postings added by transaction modifiers will have some extra tags:
+ Automated postings will have some extra tags:
o generated-posting:= QUERY - shows this was generated by an auto post-
ing rule, and the query
- o _generated-posting:= QUERY - a hidden tag, which does not appear in
+ o _generated-posting:= QUERY - a hidden tag, which does not appear in
hledger's output. This can be used to match postings generated "just
now", rather than generated in the past and saved to the journal.
- Also, any transaction that has been changed by transaction modifier
- rules will have these tags added:
+ Also, any transaction that has been changed by auto posting rules will
+ have these tags added:
o modified: - this transaction was modified
@@ -1393,7 +1429,7 @@ FILE FORMAT
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)
@@ -1407,7 +1443,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)
@@ -1415,4 +1451,4 @@ SEE ALSO
-hledger 1.17 March 2020 hledger_journal(5)
+hledger 1.17.1 March 2020 hledger_journal(5)
diff --git a/hledger_timeclock.5 b/hledger_timeclock.5
index 5d680bd..3e2165a 100644
--- a/hledger_timeclock.5
+++ b/hledger_timeclock.5
@@ -1,5 +1,5 @@
-.TH "hledger_timeclock" "5" "March 2020" "hledger 1.17" "hledger User Manuals"
+.TH "hledger_timeclock" "5" "March 2020" "hledger 1.17.1" "hledger User Manuals"
diff --git a/hledger_timeclock.info b/hledger_timeclock.info
index 3d3e604..1a7dcd6 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.17
-*********************************
+hledger_timeclock(5) hledger 1.17.1
+***********************************
Timeclock - the time logging format of timeclock.el, as read by hledger
diff --git a/hledger_timeclock.txt b/hledger_timeclock.txt
index 1f45ae7..7fdaa8c 100644
--- a/hledger_timeclock.txt
+++ b/hledger_timeclock.txt
@@ -78,4 +78,4 @@ SEE ALSO
-hledger 1.17 March 2020 hledger_timeclock(5)
+hledger 1.17.1 March 2020 hledger_timeclock(5)
diff --git a/hledger_timedot.5 b/hledger_timedot.5
index 2c2945a..b973927 100644
--- a/hledger_timedot.5
+++ b/hledger_timedot.5
@@ -1,5 +1,5 @@
-.TH "hledger_timedot" "5" "March 2020" "hledger 1.17" "hledger User Manuals"
+.TH "hledger_timedot" "5" "March 2020" "hledger 1.17.1" "hledger User Manuals"
diff --git a/hledger_timedot.info b/hledger_timedot.info
index f6df78a..ee8ee21 100644
--- a/hledger_timedot.info
+++ b/hledger_timedot.info
@@ -4,8 +4,8 @@ stdin.

File: hledger_timedot.info, Node: Top, Up: (dir)
-hledger_timedot(5) hledger 1.17
-*******************************
+hledger_timedot(5) hledger 1.17.1
+*********************************
Timedot - hledger's human-friendly time logging format
diff --git a/hledger_timedot.txt b/hledger_timedot.txt
index 829965e..6178ec4 100644
--- a/hledger_timedot.txt
+++ b/hledger_timedot.txt
@@ -161,4 +161,4 @@ SEE ALSO
-hledger 1.17 March 2020 hledger_timedot(5)
+hledger 1.17.1 March 2020 hledger_timedot(5)
diff --git a/test/doctests.hs b/test/doctests.hs
index da71f22..fd6381d 100644
--- a/test/doctests.hs
+++ b/test/doctests.hs
@@ -27,6 +27,7 @@ import System.Environment
import "Glob" System.FilePath.Glob
import Test.DocTest
+main :: IO ()
main = do
args <- getArgs
let
diff --git a/test/unittest.hs b/test/unittest.hs
index 34bb6cd..c47857d 100644
--- a/test/unittest.hs
+++ b/test/unittest.hs
@@ -5,7 +5,11 @@ Run the hledger-lib package's unit tests using the tasty test runner.
-- package-qualified import to avoid cabal missing-home-modules warning (and double-building ?)
{-# LANGUAGE PackageImports #-}
import "hledger-lib" Hledger (tests_Hledger)
-
+import System.Environment (setEnv)
import Test.Tasty (defaultMain)
-main = defaultMain tests_Hledger
+main :: IO ()
+main = do
+ setEnv "TASTY_HIDE_SUCCESSES" "true"
+ setEnv "TASTY_ANSI_TRICKS" "false" -- helps the above
+ defaultMain tests_Hledger