strict mode: -s/--strict requires posted accounts to be declared

This commit is contained in:
Simon Michael 2020-11-26 08:48:16 -08:00
parent ea0d4901ab
commit ec3ad14ae5
5 changed files with 96 additions and 32 deletions

View File

@ -90,6 +90,9 @@ m4_define({{_inputoptions_}}, {{
`-I --ignore-assertions`
: disable balance assertion checks (note: does not disable balance assignments)
`-s --strict`
: do extra error checking (check that all posted accounts are declared)
}} )m4_dnl
m4_dnl
m4_define({{_reportingoptions_}}, {{

View File

@ -198,6 +198,7 @@ data InputOpts = InputOpts {
,pivot_ :: String -- ^ use the given field's value as the account name
,auto_ :: Bool -- ^ generate automatic postings when journal is parsed
,commoditystyles_ :: Maybe (M.Map CommoditySymbol AmountStyle) -- ^ optional commodity display styles affecting all files
,strict_ :: Bool -- ^ do extra error checking (eg, all posted accounts are declared)
} deriving (Show)
instance Default InputOpts where def = definputopts
@ -214,6 +215,7 @@ definputopts = InputOpts
, pivot_ = ""
, auto_ = False
, commoditystyles_ = Nothing
, strict_ = False
}
rawOptsToInputOpts :: RawOpts -> InputOpts
@ -229,6 +231,7 @@ rawOptsToInputOpts rawopts = InputOpts{
,pivot_ = stringopt "pivot" rawopts
,auto_ = boolopt "auto" rawopts
,commoditystyles_ = Nothing
,strict_ = boolopt "strict" rawopts
}
--- ** parsing utilities
@ -317,7 +320,7 @@ parseAndFinaliseJournal' parser iopts f txt = do
-- - infer transaction-implied market prices from transaction prices
--
journalFinalise :: InputOpts -> FilePath -> Text -> ParsedJournal -> ExceptT String IO Journal
journalFinalise InputOpts{auto_,ignore_assertions_,commoditystyles_} f txt pj = do
journalFinalise InputOpts{auto_,ignore_assertions_,commoditystyles_,strict_} f txt pj = do
t <- liftIO getClockTime
d <- liftIO getCurrentDay
let pj' =
@ -326,33 +329,53 @@ journalFinalise InputOpts{auto_,ignore_assertions_,commoditystyles_} f txt pj =
& journalSetLastReadTime t -- save the last read time
& journalReverse -- convert all lists to the order they were parsed
-- Infer and apply canonical styles for each commodity (or throw an error).
-- This affects transaction balancing/assertions/assignments, so needs to be done early.
case journalApplyCommodityStyles pj' of
Left e -> throwError e
Right pj'' -> either throwError return $
pj''
& (if not auto_ || null (jtxnmodifiers pj')
then
-- Auto postings are not active.
-- Balance all transactions and maybe check balance assertions.
journalBalanceTransactions (not ignore_assertions_)
else \j -> do -- Either monad
-- Auto postings are active.
-- Balance all transactions without checking balance assertions,
j' <- journalBalanceTransactions False j
-- then add the auto postings
-- (Note adding auto postings after balancing means #893b fails;
-- adding them before balancing probably means #893a, #928, #938 fail.)
case journalModifyTransactions d j' of
Left e -> throwError e
Right j'' -> do
-- then apply commodity styles once more, to style the auto posting amounts. (XXX inefficient ?)
j''' <- journalApplyCommodityStyles j''
-- then check balance assertions.
journalBalanceTransactions (not ignore_assertions_) j'''
)
& fmap journalInferMarketPricesFromTransactions -- infer market prices from commodity-exchanging transactions
-- If in strict mode, check all postings are to declared accounts
case if strict_ then journalCheckAccountsDeclared pj' else Right () of
Left e -> throwError e
Right () ->
-- Infer and apply canonical styles for each commodity (or throw an error).
-- This affects transaction balancing/assertions/assignments, so needs to be done early.
case journalApplyCommodityStyles pj' of
Left e -> throwError e
Right pj'' -> either throwError return $
pj''
& (if not auto_ || null (jtxnmodifiers pj'')
then
-- Auto postings are not active.
-- Balance all transactions and maybe check balance assertions.
journalBalanceTransactions (not ignore_assertions_)
else \j -> do -- Either monad
-- Auto postings are active.
-- Balance all transactions without checking balance assertions,
j' <- journalBalanceTransactions False j
-- then add the auto postings
-- (Note adding auto postings after balancing means #893b fails;
-- adding them before balancing probably means #893a, #928, #938 fail.)
case journalModifyTransactions d j' of
Left e -> throwError e
Right j'' -> do
-- then apply commodity styles once more, to style the auto posting amounts. (XXX inefficient ?)
j''' <- journalApplyCommodityStyles j''
-- then check balance assertions.
journalBalanceTransactions (not ignore_assertions_) j'''
)
& fmap journalInferMarketPricesFromTransactions -- infer market prices from commodity-exchanging transactions
-- | Check that all the journal's postings are to accounts declared with
-- account directives, returning an error message otherwise.
journalCheckAccountsDeclared :: Journal -> Either String ()
journalCheckAccountsDeclared j = sequence_ $ map checkacct $ journalPostings j
where
checkacct Posting{paccount,ptransaction}
| paccount `elem` as = Right ()
| otherwise =
Left $ "\nstrict mode: undeclared account \""++T.unpack paccount++"\" is posted to"
++ case ptransaction of
Just Transaction{tsourcepos} -> "\n at: "++showGenericSourcePos tsourcepos
Nothing -> ""
where
as = journalAccountNamesDeclared j
setYear :: Year -> JournalParser m ()
setYear y = modify' (\j -> j{jparsedefaultyear=Just y})

View File

@ -1046,24 +1046,45 @@ in another commodity. See [Valuation](hledger.html#valuation).
### Declaring accounts
`account` directives can be used to pre-declare accounts.
Though not required, they can provide several benefits:
`account` directives can be used to declare accounts
(ie, the places that amounts are transferred from and to).
Though not required, these declarations can provide several benefits:
- They can document your intended chart of accounts, providing a reference.
- They can store extra information about accounts (account numbers, notes, etc.)
- They can help hledger know your accounts' types (asset, liability, equity, revenue, expense),
useful for reports like balancesheet and incomestatement.
- They control account display order in reports, allowing non-alphabetic sorting
(eg Revenues to appear above Expenses).
- They can store extra information about accounts (account numbers, notes, etc.)
- They help with account name completion
in the add command, hledger-iadd, hledger-web, ledger-mode etc.
- In [strict mode], they restrict which accounts may be posted to by transactions,
which helps detect typos.
[strict mode]: hledger.html#strict-mode
The simplest form is just the word `account` followed by a hledger-style
[account name](journal.html#account-names), eg:
[account name](journal.html#account-names), eg this account directive declares the `assets:bank:checking` account:
```journal
account assets:bank:checking
```
#### Account existence
By default, accounts come into existence when a transaction references them.
This is convenient, but when you mis-spell an account name in a transaction,
hledger won't be able to detect it. Usually this isn't a big problem, as you'll
notice the error in balance reports, or when reconciling account balances.
When you want more error checking, you can enable [strict mode] with the `-s`/`--strict` flag. Then hledger will will report an error if any transaction references
an account that has not been declared by an account directive. Some things to note:
- The declaration is case-sensitive; transactions must use the correct account name capitalisation.
- The account directive's scope is "whole file and below" (see [directives](#directives)). This means it affects all of the current file, and any files it includes, but not parent or sibling files. The position of account directives within the file does not matter, though it's usual to put them at the top.
- Accounts can only be declared in `journal` files (but will affect included files in other formats).
- It's currently not possible to declare "all possible subaccounts" with a wildcard; every account posted to must be declared.
#### Account comments
[Comments](#comments), beginning with a semicolon, can be added:

View File

@ -125,6 +125,7 @@ inputflags = [
,flagNone ["anon"] (setboolopt "anon") "anonymize accounts and payees"
,flagReq ["pivot"] (\s opts -> Right $ setopt "pivot" s opts) "TAGNAME" "use some other field/tag for account names"
,flagNone ["ignore-assertions","I"] (setboolopt "ignore-assertions") "ignore any balance assertions"
,flagNone ["strict","s"] (setboolopt "strict") "do extra error checking (check that all posted accounts are declared)"
]
-- | Common report-related flags: --period, --cost, etc.

View File

@ -761,6 +761,22 @@ If you need either of those things, you can
- use a single parent file which [includes](journal.html#including-other-files) the others
- or concatenate the files into one before reading, eg: `cat a.journal b.journal | hledger -f- CMD`.
## Strict mode
hledger checks input files for valid data.
By default, the most important errors are detected, while still accepting
easy journal files without a lot of declarations:
- Are the input files parseable, with valid syntax ?
- Are all transactions balanced ?
- Do all balance assertions pass ?
With the `-s`/`--strict` flag, additional checks are performed:
- Are all accounts referenced by transactions declared with an account directive ?
*experimental.*
## Output destination
hledger commands send their output to the terminal by default.