diff --git a/hledger-lib/Hledger/Data/JournalChecks.hs b/hledger-lib/Hledger/Data/JournalChecks.hs index 21575c112..e122ad3e8 100644 --- a/hledger-lib/Hledger/Data/JournalChecks.hs +++ b/hledger-lib/Hledger/Data/JournalChecks.hs @@ -8,6 +8,7 @@ others can be called only via the check command. {-# LANGUAGE NamedFieldPuns #-} module Hledger.Data.JournalChecks ( + journalStrictChecks, journalCheckAccounts, journalCheckBalanceAssertions, journalCheckCommodities, @@ -42,6 +43,15 @@ import Data.Ord import Hledger.Data.Dates (showDate) import Hledger.Data.Balancing (journalBalanceTransactions, defbalancingopts) +-- | Run the extra -s/--strict checks on a journal, in order of priority, +-- returning the first error message if any of them fail. +journalStrictChecks :: Journal -> Either String () +journalStrictChecks j = do + -- keep the order of checks here synced with Check.md and Hledger.Cli.Commands.Check.Check. + -- journalCheckOrdereddates j + -- journalCheckBalanceAssertions j + journalCheckCommodities j + journalCheckAccounts j -- | Check that all the journal's postings are to accounts with -- account directives, returning an error message otherwise. diff --git a/hledger-lib/Hledger/Read.hs b/hledger-lib/Hledger/Read.hs index 1c8c936bf..f68ab5225 100644 --- a/hledger-lib/Hledger/Read.hs +++ b/hledger-lib/Hledger/Read.hs @@ -107,7 +107,6 @@ module Hledger.Read ( orDieTrying, -- * Misc - journalStrictChecks, saveLatestDates, saveLatestDatesForFiles, @@ -159,7 +158,7 @@ import Hledger.Read.RulesReader (tests_RulesReader) -- import Hledger.Read.TimeclockReader (tests_TimeclockReader) import Hledger.Utils import Prelude hiding (getContents, writeFile) -import Hledger.Data.JournalChecks (journalCheckAccounts, journalCheckCommodities) +import Hledger.Data.JournalChecks (journalStrictChecks) --- ** doctest setup -- $setup @@ -307,13 +306,6 @@ readJournalFilesAndLatestDates iopts pfs = do (js, lastdates) <- unzip <$> mapM (readJournalFileAndLatestDates iopts) pfs return (maybe def sconcat $ nonEmpty js, catMaybes lastdates) --- | Run the extra -s/--strict checks on a journal, --- returning the first error message if any of them fail. -journalStrictChecks :: Journal -> Either String () -journalStrictChecks j = do - journalCheckAccounts j - journalCheckCommodities j - -- | An easy version of 'readJournal' which assumes default options, and fails -- in the IO monad. readJournal' :: Text -> IO Journal diff --git a/hledger/Hledger/Cli/Commands/Check.hs b/hledger/Hledger/Cli/Commands/Check.hs index 7e42122c6..896d4582f 100644 --- a/hledger/Hledger/Cli/Commands/Check.hs +++ b/hledger/Hledger/Cli/Commands/Check.hs @@ -11,7 +11,7 @@ module Hledger.Cli.Commands.Check ( import Data.Char (toLower) import Data.Either (partitionEithers) -import Data.List (isPrefixOf, find) +import Data.List (isPrefixOf, find, sort) import Control.Monad (forM_) import System.Console.CmdArgs.Explicit @@ -36,7 +36,7 @@ check copts@CliOpts{rawopts_} j = do case partitionEithers (map parseCheckArgument args) of (unknowns@(_:_), _) -> error' $ "These checks are unknown: "++unwords unknowns - ([], checks) -> forM_ checks $ runCheck copts' j + ([], checks) -> forM_ (sort checks) $ runCheck copts' j -- | Regenerate this CliOpts' report specification, after updating its -- underlying report options with the given update function. @@ -49,26 +49,25 @@ cliOptsUpdateReportSpecWith roptsupdate copts@CliOpts{reportspec_} = Right rs -> copts{reportspec_=rs} -- | A type of error check that we can perform on the data. --- Some of these imply other checks that are done first, --- eg currently Parseable and Autobalanced are always done, --- and Assertions are always done unless -I is in effect. +-- If performing multiple checks, they will be performed in the order defined here, generally. +-- (We report only the first failure, so the more useful checks should come first.) data Check = + -- keep the order here synced with Check.md and Hledger.Data.JournalChecks.journalStrictChecks. -- done always Parseable | Autobalanced - -- done always unless -I is used - | Assertions - -- done when -s is used, or on demand by check - | Accounts - | Commodities + | Assertions -- unless -I is used + -- done when --strict is used, or when specified with the check command | Balanced - -- done on demand by check + | Commodities + | Accounts + -- done when specified with the check command | Ordereddates | Payees - | Recentassertions | Tags + | Recentassertions | Uniqueleafnames - deriving (Read,Show,Eq,Enum,Bounded) + deriving (Read,Show,Eq,Enum,Bounded,Ord) -- | Parse the name (or a name prefix) of an error check, or return the name unparsed. -- Check names are conventionally all lower case, but this parses case insensitively. @@ -97,6 +96,12 @@ runCheck _opts j (chck,_) = do d <- getCurrentDay let results = case chck of + -- these checks are assumed to have passed earlier during journal parsing: + Parseable -> Right () + Autobalanced -> Right () + Balanced -> Right () + Assertions -> Right () + Accounts -> journalCheckAccounts j Commodities -> journalCheckCommodities j Ordereddates -> journalCheckOrdereddates j @@ -104,8 +109,6 @@ runCheck _opts j (chck,_) = do Recentassertions -> journalCheckRecentAssertions d j Tags -> journalCheckTags j Uniqueleafnames -> journalCheckUniqueleafnames j - -- the other checks have been done earlier during withJournalDo - _ -> Right () case results of Right () -> return () diff --git a/hledger/Hledger/Cli/Commands/Check.md b/hledger/Hledger/Cli/Commands/Check.md index 1cda053f6..eeb13e968 100644 --- a/hledger/Hledger/Cli/Commands/Check.md +++ b/hledger/Hledger/Cli/Commands/Check.md @@ -4,7 +4,7 @@ Check for various kinds of errors in your data. _FLAGS -hledger provides a number of built-in error checks to help +hledger provides a number of built-in correctness checks to help prevent problems in your data. Some of these are run automatically; or, you can use this `check` command to run them on demand, @@ -22,63 +22,64 @@ hledger check ordereddates payees # basic + two other checks If you are an Emacs user, you can also configure flycheck-hledger to run these checks, providing instant feedback as you edit the journal. -Here are the checks currently available: +Here are the checks currently available. +They are performed in the order they are shown here. +(Eg, an `ordereddates` failure takes precedence over an `assertions` failure). ### Default checks -These checks are run automatically by (almost) all hledger commands: +These checks are always performed, by (almost) all hledger commands: -- **parseable** - data files are in a supported [format](hledger.md#data-formats), - with no syntax errors and no invalid include directives. +- **parseable** - data files are in a supported [format](#data-formats), + with no syntax errors and no invalid include directives -- **autobalanced** - all transactions are [balanced](hledger.html#postings), after converting to cost. - Missing amounts and missing [costs] are inferred automatically where possible. +- **autobalanced** - all transactions are [balanced](#postings), + after inferring missing amounts and conversion [costs] where possible, + and then converting to cost -- **assertions** - all [balance assertions] in the journal are passing. +- **assertions** - all [balance assertions] in the journal are passing. (This check can be disabled with `-I`/`--ignore-assertions`.) ### Strict checks -These additional checks are run when the `-s`/`--strict` ([strict mode]) flag is used. -Or, they can be run by giving their names as arguments to `check`: +These additional checks are run when the `-s`/`--strict` ([strict mode]) +flag is used with any command; or, +when they are given as arguments to the `check` command: -- **balanced** - all transactions are balanced after converting to cost, - without inferring missing costs. - If conversion costs are required, they must be explicit. - -- **accounts** - all account names used by transactions - [have been declared](hledger.html#account-error-checking) +- **balanced** - like `autobalanced`, but conversion costs will not be + inferred, and must be written explicitly - **commodities** - all commodity symbols used - [have been declared](hledger.html#commodity-error-checking) + [have been declared](#commodity-error-checking) + +- **accounts** - all account names used + [have been declared](#account-error-checking) ### Other checks -These checks can be run only by giving their names as arguments to `check`. -They are more specialised and not desirable for everyone: +These checks can be run by giving their names as arguments to `check`: -- **ordereddates** - transactions are ordered by date within each file +- **ordereddates** - within each file, transactions are ordered by date - **payees** - all payees used by transactions [have been declared](#payee-directive) +- **tags** - all tags used by transactions [have been declared](#tag-directive) + - **recentassertions** - all accounts with balance assertions have a balance assertion within 7 days of their latest posting -- **tags** - all tags used by transactions [have been declared](#tag-directive) - - **uniqueleafnames** - all account leaf names are unique ### Custom checks -A few more checks are are available as separate [add-on commands], -in : +You can build your own custom checks with [add-on command scripts]. +(See also [Cookbook > Scripting](scripting.html).) +Here are some examples from [hledger/bin/](https://github.com/simonmichael/hledger/tree/master/bin): - **hledger-check-tagfiles** - all tag values containing / (a forward slash) exist as file paths - **hledger-check-fancyassertions** - more complex balance assertions are passing -You could make similar scripts to perform your own custom checks. -See: Cookbook -> [Scripting](scripting.html). ### More about specific checks @@ -92,7 +93,7 @@ It assumes that adding a balance assertion requires/reminds you to check the rea in that case, I recommend to import transactions uncleared, and when you manually review and clear them, also check the latest assertion against the real-world balance.) -[add-on commands]: #add-on-commands +[add-on command scripts]: #add-on-commands [balance assertions]: #balance-assertions [strict mode]: #strict-mode [costs]: #costs diff --git a/hledger/test/check-accounts.test b/hledger/test/check-accounts.test index bf3da6e3e..63630d75e 100644 --- a/hledger/test/check-accounts.test +++ b/hledger/test/check-accounts.test @@ -16,10 +16,12 @@ $ hledger -f- check accounts # ** 3. also fails for forecast accounts < +commodity $ account a ~ 2022-01-31 a $1 b + $ hledger -f- --today 2022-01-01 --forecast check accounts >2 /account "b" has not been declared/ >=1 @@ -31,6 +33,7 @@ $ hledger -f- --today 2022-01-01 --forecast --strict bal # ** 5. also fails for auto accounts < +commodity $ account a = a diff --git a/hledger/test/check-ordereddates.test b/hledger/test/check-ordereddates.test index 8e3737498..996387d64 100644 --- a/hledger/test/check-ordereddates.test +++ b/hledger/test/check-ordereddates.test @@ -1,11 +1,12 @@ -# check ordereddates succeeds when transaction dates are ordered +# * check ordereddates +# ** 1. check ordereddates succeeds when transaction dates are ordered < 2020-01-01 2020-01-01 2020-01-02 $ hledger -f- check ordereddates -# and otherwise fails +# ** 2. and otherwise fails < 2020-01-01 2020-01-02 @@ -15,18 +16,12 @@ $ hledger -f- check ordereddates >2 /date .*is out of order/ >=1 -# With --date2, it checks secondary dates instead +# ** 3. --date2 and secondary dates are ignored < 2020-01-02 2020-01-01=2020-01-03 $ hledger -f- check ordereddates --date2 - -# -< -2020-01-01=2020-01-03 -2020-01-02 -$ hledger -f- check ordereddates --date2 ->2 /date2 .*is out of order/ +>2 /date .*is out of order/ >=1 # XXX not supported: With a query, only matched transactions' dates are checked.