pkg!: cli: Add --today option to explicitly set the current date. (#1674)

rawOptsTo* in hledger-lib now takes a day as an argument, and does not
live in the IO monad, since it's now pure.

This is so that we can run tests containing future transactions that
won't fail as soon as ‘the future’ actually arrives.
This commit is contained in:
Stephen Morgan 2021-08-30 00:25:11 +10:00 committed by Simon Michael
parent 765466c392
commit 6905e40c4d
9 changed files with 32 additions and 31 deletions

View File

@ -228,21 +228,20 @@ parseCommodity optStr =
Left _ -> Left optStr Left _ -> Left optStr
Right (Amount acommodity _ astyle _) -> Right (acommodity, astyle) Right (Amount acommodity _ astyle _) -> Right (acommodity, astyle)
-- | Parse an InputOpts from a RawOpts and the current date. -- | Parse an InputOpts from a RawOpts and a provided date.
-- This will fail with a usage error if the forecast period expression cannot be parsed. -- This will fail with a usage error if the forecast period expression cannot be parsed.
rawOptsToInputOpts :: RawOpts -> IO InputOpts rawOptsToInputOpts :: Day -> RawOpts -> InputOpts
rawOptsToInputOpts rawopts = do rawOptsToInputOpts day rawopts =
d <- getCurrentDay
let noinferprice = boolopt "strict" rawopts || stringopt "args" rawopts == "balancednoautoconversion" let noinferprice = boolopt "strict" rawopts || stringopt "args" rawopts == "balancednoautoconversion"
-- Do we really need to do all this work just to get the requested end date? This is duplicating -- Do we really need to do all this work just to get the requested end date? This is duplicating
-- much of reportOptsToSpec. -- much of reportOptsToSpec.
ropts = rawOptsToReportOpts d rawopts ropts = rawOptsToReportOpts day rawopts
argsquery = lefts . rights . map (parseQueryTerm d) $ querystring_ ropts argsquery = lefts . rights . map (parseQueryTerm day) $ querystring_ ropts
datequery = simplifyQuery . filterQuery queryIsDate . And $ queryFromFlags ropts : argsquery datequery = simplifyQuery . filterQuery queryIsDate . And $ queryFromFlags ropts : argsquery
return InputOpts{ in InputOpts{
-- files_ = listofstringopt "file" rawopts -- files_ = listofstringopt "file" rawopts
mformat_ = Nothing mformat_ = Nothing
,mrules_file_ = maybestringopt "rules-file" rawopts ,mrules_file_ = maybestringopt "rules-file" rawopts
@ -251,7 +250,7 @@ rawOptsToInputOpts rawopts = do
,new_ = boolopt "new" rawopts ,new_ = boolopt "new" rawopts
,new_save_ = True ,new_save_ = True
,pivot_ = stringopt "pivot" rawopts ,pivot_ = stringopt "pivot" rawopts
,forecast_ = forecastPeriodFromRawOpts d rawopts ,forecast_ = forecastPeriodFromRawOpts day rawopts
,reportspan_ = DateSpan (queryStartDate False datequery) (queryEndDate False datequery) ,reportspan_ = DateSpan (queryStartDate False datequery) (queryEndDate False datequery)
,auto_ = boolopt "auto" rawopts ,auto_ = boolopt "auto" rawopts
,balancingopts_ = defbalancingopts{ ,balancingopts_ = defbalancingopts{

View File

@ -777,9 +777,7 @@ updateReportSpec = setEither reportOpts
updateReportSpecWith :: (ReportOpts -> ReportOpts) -> ReportSpec -> Either String ReportSpec updateReportSpecWith :: (ReportOpts -> ReportOpts) -> ReportSpec -> Either String ReportSpec
updateReportSpecWith = overEither reportOpts updateReportSpecWith = overEither reportOpts
-- | Generate a ReportSpec from RawOpts and the current date. -- | Generate a ReportSpec from RawOpts and a provided day, or return an error
rawOptsToReportSpec :: RawOpts -> IO ReportSpec -- string if there are regular expression errors.
rawOptsToReportSpec rawopts = do rawOptsToReportSpec :: Day -> RawOpts -> Either String ReportSpec
d <- getCurrentDay rawOptsToReportSpec day = reportOptsToSpec day . rawOptsToReportOpts day
let ropts = rawOptsToReportOpts d rawopts
either fail return $ reportOptsToSpec d ropts

View File

@ -118,7 +118,8 @@ helpflags = [
,flagNone ["man"] (setboolopt "man") "Show user manual with man" ,flagNone ["man"] (setboolopt "man") "Show user manual with man"
,flagNone ["info"] (setboolopt "info") "Show info manual with info" ,flagNone ["info"] (setboolopt "info") "Show info manual with info"
-- ,flagNone ["browse-args"] (setboolopt "browse-args") "use a web UI to select options and build up a command line" -- ,flagNone ["browse-args"] (setboolopt "browse-args") "use a web UI to select options and build up a command line"
,flagReq ["debug"] (\s opts -> Right $ setopt "debug" s opts) "[N]" "show debug output (levels 1-9, default: 1)" ,flagReq ["debug"] (\s opts -> Right $ setopt "debug" s opts) "[N]" "show debug output (levels 1-9, default: 1)"
,flagReq ["today"] (\s opts -> Right $ setopt "today" s opts) "DATE" "generate reports treating DATE as the current day (for testing purposes)"
,flagNone ["version"] (setboolopt "version") "show version information" ,flagNone ["version"] (setboolopt "version") "show version information"
] ]
@ -466,8 +467,11 @@ replaceNumericFlags = map replace
-- Also records the terminal width, if supported. -- Also records the terminal width, if supported.
rawOptsToCliOpts :: RawOpts -> IO CliOpts rawOptsToCliOpts :: RawOpts -> IO CliOpts
rawOptsToCliOpts rawopts = do rawOptsToCliOpts rawopts = do
iopts <- rawOptsToInputOpts rawopts day <- case maybestringopt "today" rawopts of
rspec <- rawOptsToReportSpec rawopts Nothing -> getCurrentDay
Just d -> maybe (fail $ "Unable to parse date \"" ++ d ++ "\"") pure $ parsedateM d -- PARTIAL:
let iopts = rawOptsToInputOpts day rawopts
rspec <- either fail pure $ rawOptsToReportSpec day rawopts -- PARTIAL:
mcolumns <- readMay <$> getEnvSafe "COLUMNS" mcolumns <- readMay <$> getEnvSafe "COLUMNS"
mtermwidth <- mtermwidth <-
#ifdef mingw32_HOST_OS #ifdef mingw32_HOST_OS

View File

@ -33,6 +33,7 @@ import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.IO as TL import qualified Data.Text.Lazy.IO as TL
import Data.Time.Calendar (Day) import Data.Time.Calendar (Day)
import Data.Time.Format (formatTime, defaultTimeLocale, iso8601DateFormat) import Data.Time.Format (formatTime, defaultTimeLocale, iso8601DateFormat)
import Lens.Micro ((^.))
import Safe (headDef, headMay, atMay) import Safe (headDef, headMay, atMay)
import System.Console.CmdArgs.Explicit (flagNone) import System.Console.CmdArgs.Explicit (flagNone)
import System.Console.Haskeline (runInputT, defaultSettings, setComplete) import System.Console.Haskeline (runInputT, defaultSettings, setComplete)
@ -93,8 +94,8 @@ add opts j
| otherwise = do | otherwise = do
hPutStrLn stderr $ "Adding transactions to journal file " <> journalFilePath j hPutStrLn stderr $ "Adding transactions to journal file " <> journalFilePath j
showHelp showHelp
today <- getCurrentDay let today = opts^.rsDay
let es = defEntryState{esOpts=opts es = defEntryState{esOpts=opts
,esArgs=listofstringopt "args" $ rawopts_ opts ,esArgs=listofstringopt "args" $ rawopts_ opts
,esToday=today ,esToday=today
,esDefDate=today ,esDefDate=today

View File

@ -49,8 +49,8 @@ closemode = hledgerCommandMode
-- debugger, beware: close is incredibly devious. simple rules combine to make a horrid maze. -- debugger, beware: close is incredibly devious. simple rules combine to make a horrid maze.
-- tests are in hledger/test/close.test. -- tests are in hledger/test/close.test.
close CliOpts{rawopts_=rawopts, reportspec_=rspec} j = do close CliOpts{rawopts_=rawopts, reportspec_=rspec} j = do
today <- getCurrentDay
let let
today = _rsDay rspec
-- show opening entry, closing entry, or (default) both ? -- show opening entry, closing entry, or (default) both ?
(opening, closing) = (opening, closing) =
case (boolopt "open" rawopts, boolopt "close" rawopts) of case (boolopt "open" rawopts, boolopt "close" rawopts) of

View File

@ -39,9 +39,9 @@ rewritemode = hledgerCommandMode
rewrite opts@CliOpts{rawopts_=rawopts,reportspec_=rspec} j@Journal{jtxns=ts} = do rewrite opts@CliOpts{rawopts_=rawopts,reportspec_=rspec} j@Journal{jtxns=ts} = do
-- rewrite matched transactions -- rewrite matched transactions
d <- getCurrentDay let today = _rsDay rspec
let modifiers = transactionModifierFromOpts opts : jtxnmodifiers j let modifiers = transactionModifierFromOpts opts : jtxnmodifiers j
let j' = j{jtxns=either error' id $ modifyTransactions mempty d modifiers ts} -- PARTIAL: let j' = j{jtxns=either error' id $ modifyTransactions mempty today modifiers ts} -- PARTIAL:
-- run the print command, showing all transactions, or show diffs -- run the print command, showing all transactions, or show diffs
printOrDiff rawopts opts{reportspec_=rspec{_rsQuery=Any}} j j' printOrDiff rawopts opts{reportspec_=rspec{_rsQuery=Any}} j j'

View File

@ -59,12 +59,11 @@ data OneSpan = OneSpan
roi :: CliOpts -> Journal -> IO () roi :: CliOpts -> Journal -> IO ()
roi CliOpts{rawopts_=rawopts, reportspec_=rspec@ReportSpec{_rsReportOpts=ReportOpts{..}}} j = do roi CliOpts{rawopts_=rawopts, reportspec_=rspec@ReportSpec{_rsReportOpts=ReportOpts{..}}} j = do
d <- getCurrentDay
-- We may be converting posting amounts to value, per hledger_options.m4.md "Effect of --value on reports". -- We may be converting posting amounts to value, per hledger_options.m4.md "Effect of --value on reports".
let let
today = _rsDay rspec
priceOracle = journalPriceOracle infer_value_ j priceOracle = journalPriceOracle infer_value_ j
styles = journalCommodityStyles j styles = journalCommodityStyles j
today = _rsDay rspec
mixedAmountValue periodlast date = mixedAmountValue periodlast date =
maybe id (mixedAmountApplyValuation priceOracle styles periodlast today date) value_ maybe id (mixedAmountApplyValuation priceOracle styles periodlast today date) value_
. mixedAmountToCost cost_ styles . mixedAmountToCost cost_ styles
@ -74,7 +73,7 @@ roi CliOpts{rawopts_=rawopts, reportspec_=rspec@ReportSpec{_rsReportOpts=ReportO
showCashFlow = boolopt "cashflow" rawopts showCashFlow = boolopt "cashflow" rawopts
prettyTables = pretty_tables_ prettyTables = pretty_tables_
makeQuery flag = do makeQuery flag = do
q <- either usageError (return . fst) . parseQuery d . T.pack $ stringopt flag rawopts q <- either usageError (return . fst) . parseQuery today . T.pack $ stringopt flag rawopts
return . simplifyQuery $ And [queryFromFlags ropts{period_=PeriodAll}, q] return . simplifyQuery $ And [queryFromFlags ropts{period_=PeriodAll}, q]
investmentsQuery <- makeQuery "investment" investmentsQuery <- makeQuery "investment"

View File

@ -43,12 +43,12 @@ statsmode = hledgerCommandMode
-- | Print various statistics for the journal. -- | Print various statistics for the journal.
stats :: CliOpts -> Journal -> IO () stats :: CliOpts -> Journal -> IO ()
stats opts@CliOpts{reportspec_=rspec} j = do stats opts@CliOpts{reportspec_=rspec} j = do
d <- getCurrentDay let today = _rsDay rspec
let q = _rsQuery rspec q = _rsQuery rspec
l = ledgerFromJournal q j l = ledgerFromJournal q j
reportspan = ledgerDateSpan l `spanDefaultsFrom` queryDateSpan False q reportspan = ledgerDateSpan l `spanDefaultsFrom` queryDateSpan False q
intervalspans = splitSpan (interval_ $ _rsReportOpts rspec) reportspan intervalspans = splitSpan (interval_ $ _rsReportOpts rspec) reportspan
showstats = showLedgerStats l d showstats = showLedgerStats l today
s = unlinesB $ map showstats intervalspans s = unlinesB $ map showstats intervalspans
writeOutputLazyText opts $ TB.toLazyText s writeOutputLazyText opts $ TB.toLazyText s

View File

@ -27,8 +27,8 @@ tagsmode = hledgerCommandMode
tags :: CliOpts -> Journal -> IO () tags :: CliOpts -> Journal -> IO ()
tags CliOpts{rawopts_=rawopts,reportspec_=rspec} j = do tags CliOpts{rawopts_=rawopts,reportspec_=rspec} j = do
d <- getCurrentDay let today = _rsDay rspec
let args = listofstringopt "args" rawopts args = listofstringopt "args" rawopts
mtagpat <- mapM (either Fail.fail pure . toRegexCI . T.pack) $ headMay args mtagpat <- mapM (either Fail.fail pure . toRegexCI . T.pack) $ headMay args
let let
querystring = map T.pack $ drop 1 args querystring = map T.pack $ drop 1 args
@ -36,7 +36,7 @@ tags CliOpts{rawopts_=rawopts,reportspec_=rspec} j = do
parsed = boolopt "parsed" rawopts parsed = boolopt "parsed" rawopts
empty = empty_ $ _rsReportOpts rspec empty = empty_ $ _rsReportOpts rspec
argsquery <- either usageError (return . fst) $ parseQueryList d querystring argsquery <- either usageError (return . fst) $ parseQueryList today querystring
let let
q = simplifyQuery $ And [queryFromFlags $ _rsReportOpts rspec, argsquery] q = simplifyQuery $ And [queryFromFlags $ _rsReportOpts rspec, argsquery]
txns = filter (q `matchesTransaction`) $ jtxns $ journalApplyValuationFromOpts rspec j txns = filter (q `matchesTransaction`) $ jtxns $ journalApplyValuationFromOpts rspec j