journal: strict mode also requires commodity declarations

This commit is contained in:
Simon Michael 2020-11-27 17:54:55 -08:00
parent d9b4446837
commit 8c23a16bf8
4 changed files with 76 additions and 39 deletions

View File

@ -52,6 +52,7 @@ module Hledger.Data.Journal (
-- overJournalAmounts, -- overJournalAmounts,
-- traverseJournalAmounts, -- traverseJournalAmounts,
-- journalCanonicalCommodities, -- journalCanonicalCommodities,
journalCommoditiesDeclared,
journalDateSpan, journalDateSpan,
journalStartDate, journalStartDate,
journalEndDate, journalEndDate,
@ -268,6 +269,10 @@ journalDescriptions = nubSort . map tdescription . jtxns
journalPostings :: Journal -> [Posting] journalPostings :: Journal -> [Posting]
journalPostings = concatMap tpostings . jtxns journalPostings = concatMap tpostings . jtxns
-- | Sorted unique commodity symbols declared by commodity directives in this journal.
journalCommoditiesDeclared :: Journal -> [AccountName]
journalCommoditiesDeclared = nubSort . M.keys . jcommodities
-- | Sorted unique account names posted to by this journal's transactions. -- | Sorted unique account names posted to by this journal's transactions.
journalAccountNamesUsed :: Journal -> [AccountName] journalAccountNamesUsed :: Journal -> [AccountName]
journalAccountNamesUsed = accountNamesFromPostings . journalPostings journalAccountNamesUsed = accountNamesFromPostings . journalPostings

View File

@ -148,6 +148,7 @@ import Text.Megaparsec.Custom
import Hledger.Data import Hledger.Data
import Hledger.Utils import Hledger.Utils
import Safe (headMay)
--- ** doctest setup --- ** doctest setup
-- $setup -- $setup
@ -334,33 +335,38 @@ journalFinalise InputOpts{auto_,ignore_assertions_,commoditystyles_,strict_} f t
Left e -> throwError e Left e -> throwError e
Right () -> Right () ->
-- Infer and apply canonical styles for each commodity (or throw an error). -- and using declared commodities
-- This affects transaction balancing/assertions/assignments, so needs to be done early. case if strict_ then journalCheckCommoditiesDeclared pj' else Right () of
case journalApplyCommodityStyles pj' of Left e -> throwError e
Left e -> throwError e Right () ->
Right pj'' -> either throwError return $
pj'' -- Infer and apply canonical styles for each commodity (or throw an error).
& (if not auto_ || null (jtxnmodifiers pj'') -- This affects transaction balancing/assertions/assignments, so needs to be done early.
then case journalApplyCommodityStyles pj' of
-- Auto postings are not active. Left e -> throwError e
-- Balance all transactions and maybe check balance assertions. Right pj'' -> either throwError return $
journalBalanceTransactions (not ignore_assertions_) pj''
else \j -> do -- Either monad & (if not auto_ || null (jtxnmodifiers pj'')
-- Auto postings are active. then
-- Balance all transactions without checking balance assertions, -- Auto postings are not active.
j' <- journalBalanceTransactions False j -- Balance all transactions and maybe check balance assertions.
-- then add the auto postings journalBalanceTransactions (not ignore_assertions_)
-- (Note adding auto postings after balancing means #893b fails; else \j -> do -- Either monad
-- adding them before balancing probably means #893a, #928, #938 fail.) -- Auto postings are active.
case journalModifyTransactions d j' of -- Balance all transactions without checking balance assertions,
Left e -> throwError e j' <- journalBalanceTransactions False j
Right j'' -> do -- then add the auto postings
-- then apply commodity styles once more, to style the auto posting amounts. (XXX inefficient ?) -- (Note adding auto postings after balancing means #893b fails;
j''' <- journalApplyCommodityStyles j'' -- adding them before balancing probably means #893a, #928, #938 fail.)
-- then check balance assertions. case journalModifyTransactions d j' of
journalBalanceTransactions (not ignore_assertions_) j''' Left e -> throwError e
) Right j'' -> do
& fmap journalInferMarketPricesFromTransactions -- infer market prices from commodity-exchanging transactions -- 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 -- | Check that all the journal's postings are to accounts declared with
-- account directives, returning an error message otherwise. -- account directives, returning an error message otherwise.
@ -370,13 +376,34 @@ journalCheckAccountsDeclared j = sequence_ $ map checkacct $ journalPostings j
checkacct Posting{paccount,ptransaction} checkacct Posting{paccount,ptransaction}
| paccount `elem` as = Right () | paccount `elem` as = Right ()
| otherwise = | otherwise =
Left $ "\nstrict mode: undeclared account \""++T.unpack paccount++"\" is posted to" Left $ "\nstrict mode: undeclared account \""++T.unpack paccount++"\""
++ case ptransaction of ++ case ptransaction of
Just Transaction{tsourcepos} -> "\n at: "++showGenericSourcePos tsourcepos Just Transaction{tsourcepos} -> "\nin transaction at: "++showGenericSourcePos tsourcepos
Nothing -> "" Nothing -> ""
where where
as = journalAccountNamesDeclared j as = journalAccountNamesDeclared j
-- | Check that all the commodities used in this journal's postings have been declared
-- by commodity directives, returning an error message otherwise.
journalCheckCommoditiesDeclared :: Journal -> Either String ()
journalCheckCommoditiesDeclared j =
sequence_ $ map checkcommodities $ journalPostings j
where
checkcommodities Posting{..} =
case mfirstundeclaredcomm of
Nothing -> Right ()
Just c -> Left $
"\nstrict mode: undeclared commodity \""++T.unpack c++"\""
++ case ptransaction of
Just Transaction{tsourcepos} -> "\nin transaction at: "++showGenericSourcePos tsourcepos
Nothing -> ""
where
mfirstundeclaredcomm =
headMay $ filter (not . (`elem` cs)) $ catMaybes $
(acommodity . baamount <$> pbalanceassertion) :
(map (Just . acommodity) $ amounts pamount)
cs = journalCommoditiesDeclared j
setYear :: Year -> JournalParser m () setYear :: Year -> JournalParser m ()
setYear y = modify' (\j -> j{jparsedefaultyear=Just y}) setYear y = modify' (\j -> j{jparsedefaultyear=Just y})

View File

@ -992,6 +992,11 @@ Note hledger normally uses
so 0.5 displayed with zero decimal digits is "0". so 0.5 displayed with zero decimal digits is "0".
(More at [Commodity display style](#commodity-display-style).) (More at [Commodity display style](#commodity-display-style).)
#### Commodity error checking
In [strict mode], enabled with the `-s`/`--strict` flag, hledger will report an error if a
commodity symbol is used that has not been declared by a [`commodity` directive](#declaring-commodities). This works similarly to [account error checking](#account-error-checking), see the notes there for more details.
### Default commodity ### Default commodity
The `D` directive sets a default commodity, to be used for amounts without a commodity symbol (ie, plain numbers). The `D` directive sets a default commodity, to be used for amounts without a commodity symbol (ie, plain numbers).
@ -1070,16 +1075,14 @@ The simplest form is just the word `account` followed by a hledger-style
account assets:bank:checking account assets:bank:checking
``` ```
#### Account existence #### Account error checking
By default, accounts come into existence when a transaction references them. By default, accounts come into existence when a transaction references them by name.
This is convenient, but when you mis-spell an account name in a transaction, This is convenient, but it means hledger can't warn you when you mis-spell an account name in the journal.
hledger won't be able to detect it. Usually this isn't a big problem, as you'll Usually you'll find the error later, as an extra account in balance reports,
notice the error in balance reports, or when reconciling account balances. or an incorrect balance when reconciling.
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 In [strict mode], enabled with the `-s`/`--strict` flag, hledger will report an error if any transaction uses an account name that has not been declared by an [account directive](#declaring-accounts). Some notes:
an account that has not been declared by an [account directive](#declaring-accounts).
Some things to note:
- The declaration is case-sensitive; transactions must use the correct account name capitalisation. - 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. - 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.

View File

@ -773,8 +773,10 @@ easy journal files without a lot of declarations:
With the `-s`/`--strict` flag, additional checks are performed: With the `-s`/`--strict` flag, additional checks are performed:
- Are all accounts referenced by transactions declared with an account directive ? - Are all accounts posted to, declared with an `account` directive ?
([Account existence](journal.html#account-existence)) ([Account error checking](journal.html#account-error-checking))
- Are all commodities declared with a `commodity` directive ?
([Commodity error checking](journal.html#commodity-error-checking))
See also: <https://hledger.org/checking-for-errors.html> See also: <https://hledger.org/checking-for-errors.html>