#!/usr/bin/env stack {- stack runghc --verbosity info --package hledger-lib --package hledger --package here -} {-# LANGUAGE QuasiQuotes #-} import Data.String.Here import Hledger import Hledger.Cli import Text.Printf ------------------------------------------------------------------------------ doc = [here| $ hledger-check-dates -h check-dates [OPTIONS] [ARGS] check that transactions' date are monotonically increasing Flags: --strict makes date comparing strict ...common hledger options... With --strict, dates must also be unique. With --date2, checks transactions' secondary dates. Reads the default journal file, or another specified with -f. |] ------------------------------------------------------------------------------ argsmode :: Mode RawOpts argsmode = (defAddonCommandMode "check-dates") { modeHelp = "check that transactions' date are monotonically increasing" , modeGroupFlags = Group { groupNamed = [ ("Input",inputflags) , ("Reporting",reportflags) , ("Misc",helpflags) ] ,groupUnnamed = [ flagNone ["strict"] (\opts -> setboolopt "strict" opts) "makes date comparing strict" ] , groupHidden = [] } } data FoldAcc a b = FoldAcc { fa_error :: Maybe a , fa_previous :: Maybe b } foldWhile :: (a -> FoldAcc a b -> FoldAcc a b) -> FoldAcc a b -> [a] -> FoldAcc a b foldWhile _ acc [] = acc foldWhile fold acc (a:as) = case fold a acc of acc@FoldAcc{fa_error=Just _} -> acc acc -> foldWhile fold acc as checkTransactions :: (Transaction -> Transaction -> Bool) -> [Transaction] -> FoldAcc Transaction Transaction checkTransactions compare ts = foldWhile fold FoldAcc{fa_error=Nothing, fa_previous=Nothing} ts where fold current acc@FoldAcc{fa_previous=Nothing} = acc{fa_previous=Just current} fold current acc@FoldAcc{fa_previous=Just previous} = if compare previous current then acc{fa_previous=Just current} else acc{fa_error=Just current} main :: IO () main = do opts <- getHledgerOptsOrShowHelp argsmode doc withJournalDo opts $ \CliOpts{rawopts_=opts,reportopts_=ropts} j -> do d <- getCurrentDay let ropts_ = ropts{accountlistmode_=ALFlat} let q = queryFromOpts d ropts_ let ts = filter (q `matchesTransaction`) $ jtxns $ journalSelectingAmountFromOpts ropts j let strict = boolopt "strict" opts let date = transactionDateFn ropts let compare a b = if strict then date a < date b else date a <= date b case checkTransactions compare ts of FoldAcc{fa_previous=Nothing} -> putStrLn "ok (empty journal)" FoldAcc{fa_error=Nothing} -> putStrLn "ok" FoldAcc{fa_error=Just error, fa_previous=Just previous} -> putStrLn $ printf ("ERROR: transaction out of%s date order" ++ "\nPrevious date: %s" ++ "\nDate: %s" ++ "\nLocation: %s" ++ "\nTransaction:\n\n%s") (if strict then " STRICT" else "") (show $ date previous) (show $ date error) (show $ tsourcepos error) (showTransactionUnelided error)