2014-03-20 04:11:48 +04:00
{- |
2014-03-26 04:10:30 +04:00
Options common to most hledger reports .
2014-03-20 04:11:48 +04:00
- }
2019-10-20 02:01:59 +03:00
{- # LANGUAGE OverloadedStrings, RecordWildCards, LambdaCase, DeriveDataTypeable # -}
2018-09-04 21:54:40 +03:00
2014-03-20 04:11:48 +04:00
module Hledger.Reports.ReportOptions (
ReportOpts ( .. ) ,
BalanceType ( .. ) ,
2014-12-05 23:56:33 +03:00
AccountListMode ( .. ) ,
2019-05-23 10:36:16 +03:00
ValuationType ( .. ) ,
2014-03-20 04:11:48 +04:00
FormatStr ,
defreportopts ,
2014-03-26 04:10:30 +04:00
rawOptsToReportOpts ,
2015-08-28 19:57:01 +03:00
checkReportOpts ,
2014-12-05 23:56:33 +03:00
flat_ ,
tree_ ,
2017-06-16 04:15:37 +03:00
reportOptsToggleStatus ,
2017-06-19 02:11:18 +03:00
simplifyStatuses ,
2014-03-20 04:11:48 +04:00
whichDateFromOpts ,
journalSelectingAmountFromOpts ,
2018-03-29 17:50:48 +03:00
intervalFromRawOpts ,
2020-06-02 23:15:14 +03:00
forecastPeriodFromRawOpts ,
2014-03-20 04:11:48 +04:00
queryFromOpts ,
queryFromOptsOnly ,
queryOptsFromOpts ,
transactionDateFn ,
postingDateFn ,
2018-03-30 02:16:35 +03:00
reportSpan ,
2016-12-30 22:44:01 +03:00
reportStartDate ,
reportEndDate ,
2017-12-30 04:11:17 +03:00
specifiedStartEndDates ,
specifiedStartDate ,
specifiedEndDate ,
2019-05-06 03:47:38 +03:00
reportPeriodStart ,
reportPeriodOrJournalStart ,
2019-05-04 22:00:57 +03:00
reportPeriodLastDay ,
reportPeriodOrJournalLastDay ,
2019-05-23 10:36:16 +03:00
valuationTypeIsCost ,
2019-10-20 05:41:21 +03:00
valuationTypeIsDefaultValue ,
2014-03-20 04:11:48 +04:00
2018-09-06 23:08:26 +03:00
tests_ReportOptions
2014-03-20 04:11:48 +04:00
)
where
2018-03-30 02:16:35 +03:00
import Control.Applicative ( ( <|> ) )
2014-03-26 04:10:30 +04:00
import Data.Data ( Data )
2020-01-04 09:09:01 +03:00
import Data.List.Extra ( nubSort )
2016-07-30 05:19:44 +03:00
import Data.Maybe
2016-07-29 18:57:10 +03:00
import qualified Data.Text as T
2014-03-26 04:10:30 +04:00
import Data.Typeable ( Typeable )
2014-03-20 04:11:48 +04:00
import Data.Time.Calendar
2016-07-29 20:15:48 +03:00
import Data.Default
2016-07-30 05:19:44 +03:00
import Safe
2017-04-26 04:34:09 +03:00
import System.Console.ANSI ( hSupportsANSI )
import System.IO ( stdout )
2018-09-30 04:32:08 +03:00
import Text.Megaparsec.Custom
2014-03-20 04:11:48 +04:00
import Hledger.Data
import Hledger.Query
2018-09-04 23:52:36 +03:00
import Hledger.Utils
2014-03-20 04:11:48 +04:00
2014-03-26 04:10:30 +04:00
type FormatStr = String
2016-08-12 19:44:31 +03:00
-- | Which "balance" is being shown in a balance report.
data BalanceType = PeriodChange -- ^ The change of balance in each period.
| CumulativeChange -- ^ The accumulated change across multiple periods.
| HistoricalBalance -- ^ The historical ending balance, including the effect of
-- all postings before the report period. Unless altered by,
-- a query, this is what you would see on a bank statement.
2014-03-26 04:10:30 +04:00
deriving ( Eq , Show , Data , Typeable )
2016-08-12 19:44:31 +03:00
instance Default BalanceType where def = PeriodChange
2014-03-26 04:10:30 +04:00
2014-12-05 23:56:33 +03:00
-- | Should accounts be displayed: in the command's default style, hierarchically, or as a flat list ?
data AccountListMode = ALDefault | ALTree | ALFlat deriving ( Eq , Show , Data , Typeable )
instance Default AccountListMode where def = ALDefault
2017-09-26 08:06:38 +03:00
-- | Standard options for customising report filtering and output.
-- Most of these correspond to standard hledger command-line options
-- or query arguments, but not all. Some are used only by certain
2019-07-15 13:28:52 +03:00
-- commands, as noted below.
2014-03-20 04:11:48 +04:00
data ReportOpts = ReportOpts {
2019-04-24 18:25:55 +03:00
today_ :: Maybe Day -- ^ The current date. A late addition to ReportOpts.
-- Optional, but when set it may affect some reports:
-- Reports use it when picking a -V valuation date.
2019-04-26 21:46:19 +03:00
-- This is not great, adds indeterminacy.
2019-04-24 18:25:55 +03:00
, period_ :: Period
2016-07-30 05:19:44 +03:00
, interval_ :: Interval
2017-06-16 02:54:34 +03:00
, statuses_ :: [ Status ] -- ^ Zero, one, or two statuses to be matched
2019-05-23 10:36:16 +03:00
, value_ :: Maybe ValuationType -- ^ What value should amounts be converted to ?
2014-03-20 04:11:48 +04:00
, depth_ :: Maybe Int
2019-04-24 18:25:55 +03:00
, display_ :: Maybe DisplayExp -- XXX unused ?
2014-03-20 04:11:48 +04:00
, date2_ :: Bool
, empty_ :: Bool
, no_elide_ :: Bool
, real_ :: Bool
, format_ :: Maybe FormatStr
2019-10-19 19:08:22 +03:00
, query_ :: String -- ^ All query arguments space sepeareted
-- and quoted if needed (see 'quoteIfNeeded')
2019-05-03 22:24:02 +03:00
--
2014-03-26 04:10:30 +04:00
, average_ :: Bool
2019-05-03 22:24:02 +03:00
-- register command only
2014-03-26 04:10:30 +04:00
, related_ :: Bool
2017-09-26 08:06:38 +03:00
-- balance-type commands only
2014-03-26 04:10:30 +04:00
, balancetype_ :: BalanceType
2016-07-30 05:19:44 +03:00
, accountlistmode_ :: AccountListMode
2014-03-26 04:10:30 +04:00
, drop_ :: Int
2014-12-28 02:16:36 +03:00
, row_total_ :: Bool
2014-03-26 04:10:30 +04:00
, no_total_ :: Bool
2017-03-29 21:12:01 +03:00
, pretty_tables_ :: Bool
2017-09-25 19:17:46 +03:00
, sort_amount_ :: Bool
2019-11-11 23:06:58 +03:00
, percent_ :: Bool
2018-01-30 01:52:03 +03:00
, invert_ :: Bool -- ^ if true, flip all amount signs in reports
2018-01-16 00:05:20 +03:00
, normalbalance_ :: Maybe NormalSign
-- ^ This can be set when running balance reports on a set of accounts
2018-01-23 22:32:24 +03:00
-- with the same normal balance type (eg all assets, or all incomes).
2018-01-16 04:58:14 +03:00
-- - It helps --sort-amount know how to sort negative numbers
2019-07-15 13:28:52 +03:00
-- (eg in the income section of an income statement)
-- - It helps compound balance report commands (is, bs etc.) do
-- sign normalisation, converting normally negative subreports to
-- normally positive for a more conventional display.
2017-04-26 04:34:09 +03:00
, color_ :: Bool
2020-06-02 23:15:14 +03:00
, forecast_ :: Maybe DateSpan
2019-01-24 23:54:23 +03:00
, transpose_ :: Bool
2014-03-20 04:11:48 +04:00
} deriving ( Show , Data , Typeable )
2014-03-26 04:10:30 +04:00
instance Default ReportOpts where def = defreportopts
2014-03-20 04:11:48 +04:00
2014-03-26 04:10:30 +04:00
defreportopts :: ReportOpts
2014-03-20 04:11:48 +04:00
defreportopts = ReportOpts
def
def
def
def
def
def
def
def
def
def
def
def
def
def
def
def
def
def
def
def
2017-03-29 21:12:01 +03:00
def
2017-04-26 04:34:09 +03:00
def
2017-09-25 19:17:46 +03:00
def
2017-09-26 08:06:38 +03:00
def
2017-11-18 03:40:10 +03:00
def
2017-11-18 03:40:10 +03:00
def
2019-01-24 23:54:23 +03:00
def
2019-11-11 23:06:58 +03:00
def
2017-09-26 08:06:38 +03:00
2014-03-26 04:10:30 +04:00
rawOptsToReportOpts :: RawOpts -> IO ReportOpts
2015-08-28 19:57:01 +03:00
rawOptsToReportOpts rawopts = checkReportOpts <$> do
2016-08-12 19:44:31 +03:00
let rawopts' = checkRawOpts rawopts
2017-04-26 04:34:09 +03:00
d <- getCurrentDay
color <- hSupportsANSI stdout
2014-03-26 04:10:30 +04:00
return defreportopts {
2019-04-24 18:25:55 +03:00
today_ = Just d
, period_ = periodFromRawOpts d rawopts'
2016-08-12 19:44:31 +03:00
, interval_ = intervalFromRawOpts rawopts'
2017-06-16 02:54:34 +03:00
, statuses_ = statusesFromRawOpts rawopts'
2019-05-23 10:36:16 +03:00
, value_ = valuationTypeFromRawOpts rawopts'
2016-08-12 19:44:31 +03:00
, depth_ = maybeintopt " depth " rawopts'
, display_ = maybedisplayopt d rawopts'
, date2_ = boolopt " date2 " rawopts'
, empty_ = boolopt " empty " rawopts'
, no_elide_ = boolopt " no-elide " rawopts'
, real_ = boolopt " real " rawopts'
, format_ = maybestringopt " format " rawopts' -- XXX move to CliOpts or move validation from Cli.CliOptions to here
2019-10-19 19:08:22 +03:00
, query_ = unwords . map quoteIfNeeded $ listofstringopt " args " rawopts' -- doesn't handle an arg like "" right
2016-08-12 19:44:31 +03:00
, average_ = boolopt " average " rawopts'
, related_ = boolopt " related " rawopts'
, balancetype_ = balancetypeopt rawopts'
, accountlistmode_ = accountlistmodeopt rawopts'
, drop_ = intopt " drop " rawopts'
, row_total_ = boolopt " row-total " rawopts'
, no_total_ = boolopt " no-total " rawopts'
2017-09-25 19:17:46 +03:00
, sort_amount_ = boolopt " sort-amount " rawopts'
2019-11-11 23:06:58 +03:00
, percent_ = boolopt " percent " rawopts'
2018-01-30 01:52:03 +03:00
, invert_ = boolopt " invert " rawopts'
2017-03-29 21:12:01 +03:00
, pretty_tables_ = boolopt " pretty-tables " rawopts'
2017-04-26 04:34:09 +03:00
, color_ = color
2020-06-02 23:15:14 +03:00
, forecast_ = forecastPeriodFromRawOpts d rawopts'
2019-01-24 23:54:23 +03:00
, transpose_ = boolopt " transpose " rawopts'
2014-03-26 04:10:30 +04:00
}
2016-08-12 19:44:31 +03:00
-- | Do extra validation of raw option values, raising an error if there's a problem.
checkRawOpts :: RawOpts -> RawOpts
checkRawOpts rawopts
-- our standard behaviour is to accept conflicting options actually,
-- using the last one - more forgiving for overriding command-line aliases
-- | countopts ["change","cumulative","historical"] > 1
2017-03-29 18:00:30 +03:00
-- = usageError "please specify at most one of --change, --cumulative, --historical"
2016-08-12 19:44:31 +03:00
-- | countopts ["flat","tree"] > 1
2017-03-29 18:00:30 +03:00
-- = usageError "please specify at most one of --flat, --tree"
2016-08-12 19:44:31 +03:00
-- | countopts ["daily","weekly","monthly","quarterly","yearly"] > 1
2017-03-29 18:00:30 +03:00
-- = usageError "please specify at most one of --daily, "
2016-08-12 19:44:31 +03:00
| otherwise = rawopts
-- where
-- countopts = length . filter (`boolopt` rawopts)
-- | Do extra validation of report options, raising an error if there's a problem.
2015-08-28 19:57:01 +03:00
checkReportOpts :: ReportOpts -> ReportOpts
checkReportOpts ropts @ ReportOpts { .. } =
2017-03-29 18:00:30 +03:00
either usageError ( const ropts ) $ do
2015-08-28 19:57:30 +03:00
case depth_ of
Just d | d < 0 -> Left " --depth should have a positive number "
_ -> Right ()
2015-08-28 19:57:01 +03:00
2014-12-05 23:56:33 +03:00
accountlistmodeopt :: RawOpts -> AccountListMode
2019-10-20 02:01:59 +03:00
accountlistmodeopt =
fromMaybe ALDefault . choiceopt parse where
parse = \ case
" tree " -> Just ALTree
" flat " -> Just ALFlat
_ -> Nothing
2014-12-05 23:56:33 +03:00
2014-03-26 04:10:30 +04:00
balancetypeopt :: RawOpts -> BalanceType
2019-10-20 02:01:59 +03:00
balancetypeopt =
fromMaybe PeriodChange . choiceopt parse where
parse = \ case
" historical " -> Just HistoricalBalance
" cumulative " -> Just CumulativeChange
_ -> Nothing
2014-03-26 04:10:30 +04:00
2019-07-01 20:33:06 +03:00
-- Get the period specified by any -b/--begin, -e/--end and/or -p/--period
-- options appearing in the command line.
-- Its bounds are the rightmost begin date specified by a -b or -p, and
-- the rightmost end date specified by a -e or -p. Cf #1011.
-- Today's date is provided to help interpret any relative dates.
2016-07-30 05:19:44 +03:00
periodFromRawOpts :: Day -> RawOpts -> Period
periodFromRawOpts d rawopts =
2019-06-29 23:40:18 +03:00
case ( mlastb , mlaste ) of
2016-07-30 05:19:44 +03:00
( Nothing , Nothing ) -> PeriodAll
( Just b , Nothing ) -> PeriodFrom b
( Nothing , Just e ) -> PeriodTo e
( Just b , Just e ) -> simplifyPeriod $
PeriodBetween b e
where
2019-06-29 23:40:18 +03:00
mlastb = case beginDatesFromRawOpts d rawopts of
2016-07-30 05:19:44 +03:00
[] -> Nothing
2019-06-29 23:40:18 +03:00
bs -> Just $ last bs
mlaste = case endDatesFromRawOpts d rawopts of
2016-07-30 05:19:44 +03:00
[] -> Nothing
2019-06-29 23:40:18 +03:00
es -> Just $ last es
2016-07-30 05:19:44 +03:00
-- Get all begin dates specified by -b/--begin or -p/--period options, in order,
-- using the given date to interpret relative date expressions.
beginDatesFromRawOpts :: Day -> RawOpts -> [ Day ]
2019-10-20 02:01:59 +03:00
beginDatesFromRawOpts d = collectopts ( begindatefromrawopt d )
2016-07-30 05:19:44 +03:00
where
begindatefromrawopt d ( n , v )
| n == " begin " =
2018-09-30 04:32:08 +03:00
either ( \ e -> usageError $ " could not parse " ++ n ++ " date: " ++ customErrorBundlePretty e ) Just $
2016-07-30 05:19:44 +03:00
fixSmartDateStrEither' d ( T . pack v )
| n == " period " =
case
2018-09-30 04:32:08 +03:00
either ( \ e -> usageError $ " could not parse period option: " ++ customErrorBundlePretty e ) id $
2016-07-30 05:19:44 +03:00
parsePeriodExpr d ( stripquotes $ T . pack v )
of
( _ , DateSpan ( Just b ) _ ) -> Just b
_ -> Nothing
| otherwise = Nothing
-- Get all end dates specified by -e/--end or -p/--period options, in order,
-- using the given date to interpret relative date expressions.
endDatesFromRawOpts :: Day -> RawOpts -> [ Day ]
2019-10-20 02:01:59 +03:00
endDatesFromRawOpts d = collectopts ( enddatefromrawopt d )
2016-07-30 05:19:44 +03:00
where
enddatefromrawopt d ( n , v )
| n == " end " =
2018-09-30 04:32:08 +03:00
either ( \ e -> usageError $ " could not parse " ++ n ++ " date: " ++ customErrorBundlePretty e ) Just $
2016-07-30 05:19:44 +03:00
fixSmartDateStrEither' d ( T . pack v )
| n == " period " =
case
2018-09-30 04:32:08 +03:00
either ( \ e -> usageError $ " could not parse period option: " ++ customErrorBundlePretty e ) id $
2016-07-30 05:19:44 +03:00
parsePeriodExpr d ( stripquotes $ T . pack v )
of
( _ , DateSpan _ ( Just e ) ) -> Just e
_ -> Nothing
| otherwise = Nothing
-- | Get the report interval, if any, specified by the last of -p/--period,
-- -D/--daily, -W/--weekly, -M/--monthly etc. options.
2019-04-16 22:48:45 +03:00
-- An interval from --period counts only if it is explicitly defined.
2016-07-30 05:19:44 +03:00
intervalFromRawOpts :: RawOpts -> Interval
2019-10-20 02:01:59 +03:00
intervalFromRawOpts = lastDef NoInterval . collectopts intervalfromrawopt
2016-07-30 05:19:44 +03:00
where
intervalfromrawopt ( n , v )
| n == " period " =
2019-04-16 22:48:45 +03:00
either
( \ e -> usageError $ " could not parse period option: " ++ customErrorBundlePretty e )
extractIntervalOrNothing $
2019-07-25 12:46:45 +03:00
parsePeriodExpr
( error ' " i n t e r v a l F r o m R a w O p t s : d i d n o t e x p e c t t o n e e d t o d a y' s date here " ) -- should not happen; we are just getting the interval, which does not use the reference date
( stripquotes $ T . pack v )
2016-07-30 05:19:44 +03:00
| n == " daily " = Just $ Days 1
| n == " weekly " = Just $ Weeks 1
| n == " monthly " = Just $ Months 1
| n == " quarterly " = Just $ Quarters 1
| n == " yearly " = Just $ Years 1
| otherwise = Nothing
2020-06-02 23:15:14 +03:00
-- | get period expression from --forecast option
forecastPeriodFromRawOpts :: Day -> RawOpts -> Maybe DateSpan
forecastPeriodFromRawOpts d opts =
2020-06-15 03:17:09 +03:00
case maybestringopt " forecast " opts
2020-06-02 23:15:14 +03:00
of
Nothing -> Nothing
Just " " -> Just nulldatespan
Just str ->
either ( \ e -> usageError $ " could not parse forecast period : " ++ customErrorBundlePretty e ) ( Just . snd ) $
parsePeriodExpr d $ stripquotes $ T . pack str
2019-04-16 22:48:45 +03:00
-- | Extract the interval from the parsed -p/--period expression.
-- Return Nothing if an interval is not explicitly defined.
extractIntervalOrNothing :: ( Interval , DateSpan ) -> Maybe Interval
extractIntervalOrNothing ( NoInterval , _ ) = Nothing
extractIntervalOrNothing ( interval , _ ) = Just interval
2017-06-16 02:54:34 +03:00
-- | Get any statuses to be matched, as specified by -U/--unmarked,
-- -P/--pending, -C/--cleared flags. -UPC is equivalent to no flags,
-- so this returns a list of 0-2 unique statuses.
statusesFromRawOpts :: RawOpts -> [ Status ]
2019-10-20 02:01:59 +03:00
statusesFromRawOpts = simplifyStatuses . collectopts statusfromrawopt
2016-07-30 05:19:44 +03:00
where
2017-06-16 02:54:34 +03:00
statusfromrawopt ( n , _ )
2017-06-16 02:25:37 +03:00
| n == " unmarked " = Just Unmarked
2017-06-16 02:54:34 +03:00
| n == " pending " = Just Pending
| n == " cleared " = Just Cleared
2016-07-30 05:19:44 +03:00
| otherwise = Nothing
2017-06-16 02:54:34 +03:00
-- | Reduce a list of statuses to just one of each status,
-- and if all statuses are present return the empty list.
simplifyStatuses l
| length l' >= numstatuses = []
| otherwise = l'
where
2020-01-04 09:09:01 +03:00
l' = nubSort l
2017-06-16 02:54:34 +03:00
numstatuses = length [ minBound .. maxBound :: Status ]
2014-03-26 04:10:30 +04:00
2017-06-16 04:15:37 +03:00
-- | Add/remove this status from the status list. Used by hledger-ui.
reportOptsToggleStatus s ropts @ ReportOpts { statuses_ = ss }
| s ` elem ` ss = ropts { statuses_ = filter ( /= s ) ss }
| otherwise = ropts { statuses_ = simplifyStatuses ( s : ss ) }
2019-06-02 00:54:34 +03:00
-- | Parse the type of valuation to be performed, if any, specified by
2019-06-02 02:12:20 +03:00
-- -B/--cost, -V, -X/--exchange, or --value flags. If there's more
-- than one of these, the rightmost flag wins.
2019-05-23 10:36:16 +03:00
valuationTypeFromRawOpts :: RawOpts -> Maybe ValuationType
2019-10-20 02:01:59 +03:00
valuationTypeFromRawOpts = lastMay . collectopts valuationfromrawopt
2019-04-26 03:51:51 +03:00
where
2019-06-02 00:54:34 +03:00
valuationfromrawopt ( n , v ) -- option name, value
2019-05-23 10:36:16 +03:00
| n == " B " = Just $ AtCost Nothing
2019-05-24 13:49:01 +03:00
| n == " V " = Just $ AtDefault Nothing
2019-06-02 02:12:20 +03:00
| n == " X " = Just $ AtDefault ( Just $ T . pack v )
2019-05-23 10:36:16 +03:00
| n == " value " = Just $ valuation v
| otherwise = Nothing
valuation v
2020-02-10 19:09:52 +03:00
| t ` elem ` [ " cost " , " c " ] = AtCost mc
| t ` elem ` [ " then " , " t " ] = AtThen mc
| t ` elem ` [ " end " , " e " ] = AtEnd mc
| t ` elem ` [ " now " , " n " ] = AtNow mc
2019-05-23 10:36:16 +03:00
| otherwise =
2019-06-02 00:54:34 +03:00
case parsedateM t of
Just d -> AtDate d mc
2020-02-10 23:40:02 +03:00
Nothing -> usageError $ " could not parse \ " " ++ t ++ " \ " as valuation type, should be: cost|then|end|now|c|t|e|n|YYYY-MM-DD "
2019-06-02 00:54:34 +03:00
where
2019-06-02 02:12:20 +03:00
-- parse --value's value: TYPE[,COMM]
2019-06-02 00:54:34 +03:00
( t , c' ) = break ( == ',' ) v
mc = case drop 1 c' of
" " -> Nothing
c -> Just $ T . pack c
2019-04-26 03:51:51 +03:00
2019-06-02 02:12:20 +03:00
valuationTypeIsCost :: ReportOpts -> Bool
valuationTypeIsCost ropts =
case value_ ropts of
Just ( AtCost _ ) -> True
_ -> False
2019-10-20 05:41:21 +03:00
valuationTypeIsDefaultValue :: ReportOpts -> Bool
valuationTypeIsDefaultValue ropts =
case value_ ropts of
Just ( AtDefault _ ) -> True
_ -> False
2014-03-26 04:10:30 +04:00
type DisplayExp = String
maybedisplayopt :: Day -> RawOpts -> Maybe DisplayExp
maybedisplayopt d rawopts =
maybe Nothing ( Just . regexReplaceBy " \ \ [.+? \ \ ] " fixbracketeddatestr ) $ maybestringopt " display " rawopts
where
fixbracketeddatestr " " = " "
2016-07-29 18:57:10 +03:00
fixbracketeddatestr s = " [ " ++ fixSmartDateStr d ( T . pack $ init $ tail s ) ++ " ] "
2014-03-26 04:10:30 +04:00
2016-07-30 05:19:44 +03:00
-- | Select the Transaction date accessor based on --date2.
transactionDateFn :: ReportOpts -> ( Transaction -> Day )
transactionDateFn ReportOpts { .. } = if date2_ then transactionDate2 else tdate
-- | Select the Posting date accessor based on --date2.
postingDateFn :: ReportOpts -> ( Posting -> Day )
postingDateFn ReportOpts { .. } = if date2_ then postingDate2 else postingDate
-- | Report which date we will report on based on --date2.
whichDateFromOpts :: ReportOpts -> WhichDate
whichDateFromOpts ReportOpts { .. } = if date2_ then SecondaryDate else PrimaryDate
2014-03-20 04:11:48 +04:00
2014-12-05 23:56:33 +03:00
-- | Legacy-compatible convenience aliases for accountlistmode_.
tree_ :: ReportOpts -> Bool
tree_ = ( == ALTree ) . accountlistmode_
flat_ :: ReportOpts -> Bool
flat_ = ( == ALFlat ) . accountlistmode_
2014-03-20 04:11:48 +04:00
-- depthFromOpts :: ReportOpts -> Int
-- depthFromOpts opts = min (fromMaybe 99999 $ depth_ opts) (queryDepth $ queryFromOpts nulldate opts)
2019-05-23 10:36:16 +03:00
-- | Convert this journal's postings' amounts to cost using their
-- transaction prices, if specified by options (-B/--value=cost).
-- Maybe soon superseded by newer valuation code.
2014-03-20 04:11:48 +04:00
journalSelectingAmountFromOpts :: ReportOpts -> Journal -> Journal
2019-05-23 10:36:16 +03:00
journalSelectingAmountFromOpts opts =
case value_ opts of
2019-10-20 05:58:27 +03:00
Just ( AtCost _ ) -> journalToCost
2019-05-23 10:36:16 +03:00
_ -> id
2014-03-20 04:11:48 +04:00
-- | Convert report options and arguments to a query.
queryFromOpts :: Day -> ReportOpts -> Query
2016-07-30 05:19:44 +03:00
queryFromOpts d ReportOpts { .. } = simplifyQuery $ And $ [ flagsq , argsq ]
2014-03-20 04:11:48 +04:00
where
flagsq = And $
2016-07-30 05:19:44 +03:00
[ ( if date2_ then Date2 else Date ) $ periodAsDateSpan period_ ]
2014-03-20 04:11:48 +04:00
++ ( if real_ then [ Real True ] else [] )
++ ( if empty_ then [ Empty True ] else [] ) -- ?
2017-06-16 02:54:34 +03:00
++ [ Or $ map StatusQ statuses_ ]
2014-03-20 04:11:48 +04:00
++ ( maybe [] ( ( : [] ) . Depth ) depth_ )
2016-07-29 18:57:10 +03:00
argsq = fst $ parseQuery d ( T . pack query_ )
2014-03-20 04:11:48 +04:00
-- | Convert report options to a query, ignoring any non-flag command line arguments.
queryFromOptsOnly :: Day -> ReportOpts -> Query
2016-07-30 05:19:44 +03:00
queryFromOptsOnly _d ReportOpts { .. } = simplifyQuery flagsq
2014-03-20 04:11:48 +04:00
where
flagsq = And $
2016-07-30 05:19:44 +03:00
[ ( if date2_ then Date2 else Date ) $ periodAsDateSpan period_ ]
2014-03-20 04:11:48 +04:00
++ ( if real_ then [ Real True ] else [] )
++ ( if empty_ then [ Empty True ] else [] ) -- ?
2017-06-16 02:54:34 +03:00
++ [ Or $ map StatusQ statuses_ ]
2014-03-20 04:11:48 +04:00
++ ( maybe [] ( ( : [] ) . Depth ) depth_ )
-- | Convert report options and arguments to query options.
queryOptsFromOpts :: Day -> ReportOpts -> [ QueryOpt ]
queryOptsFromOpts d ReportOpts { .. } = flagsqopts ++ argsqopts
where
flagsqopts = []
2016-07-29 18:57:10 +03:00
argsqopts = snd $ parseQuery d ( T . pack query_ )
2014-03-20 04:11:48 +04:00
2019-05-06 03:47:38 +03:00
-- Report dates.
2018-03-30 02:16:35 +03:00
-- | The effective report span is the start and end dates specified by
2019-07-15 13:28:52 +03:00
-- options or queries, or otherwise the earliest and latest transaction or
2018-03-30 02:16:35 +03:00
-- posting dates in the journal. If no dates are specified by options/queries
-- and the journal is empty, returns the null date span.
2016-12-30 22:44:01 +03:00
-- Needs IO to parse smart dates in options/queries.
2018-03-30 02:16:35 +03:00
reportSpan :: Journal -> ReportOpts -> IO DateSpan
reportSpan j ropts = do
2018-03-30 04:41:03 +03:00
( mspecifiedstartdate , mspecifiedenddate ) <-
2020-06-15 03:17:09 +03:00
dbg3 " specifieddates " <$> specifiedStartEndDates ropts
2018-03-30 02:16:35 +03:00
let
2018-03-30 04:41:03 +03:00
DateSpan mjournalstartdate mjournalenddate =
2020-06-15 03:17:09 +03:00
dbg3 " journalspan " $ journalDateSpan False j -- ignore secondary dates
2018-03-30 02:16:35 +03:00
mstartdate = mspecifiedstartdate <|> mjournalstartdate
menddate = mspecifiedenddate <|> mjournalenddate
2020-06-15 03:17:09 +03:00
return $ dbg3 " reportspan " $ DateSpan mstartdate menddate
2017-12-30 04:11:17 +03:00
2016-12-30 22:44:01 +03:00
reportStartDate :: Journal -> ReportOpts -> IO ( Maybe Day )
2018-03-30 02:16:35 +03:00
reportStartDate j ropts = spanStart <$> reportSpan j ropts
2016-12-30 22:44:01 +03:00
reportEndDate :: Journal -> ReportOpts -> IO ( Maybe Day )
2018-03-30 02:16:35 +03:00
reportEndDate j ropts = spanEnd <$> reportSpan j ropts
2016-12-30 22:44:01 +03:00
2017-12-30 04:11:17 +03:00
-- | The specified report start/end dates are the dates specified by options or queries, if any.
-- Needs IO to parse smart dates in options/queries.
specifiedStartEndDates :: ReportOpts -> IO ( Maybe Day , Maybe Day )
specifiedStartEndDates ropts = do
2016-12-30 22:44:01 +03:00
today <- getCurrentDay
let
q = queryFromOpts today ropts
2017-12-30 04:11:17 +03:00
mspecifiedstartdate = queryStartDate False q
mspecifiedenddate = queryEndDate False q
return ( mspecifiedstartdate , mspecifiedenddate )
specifiedStartDate :: ReportOpts -> IO ( Maybe Day )
specifiedStartDate ropts = fst <$> specifiedStartEndDates ropts
specifiedEndDate :: ReportOpts -> IO ( Maybe Day )
specifiedEndDate ropts = snd <$> specifiedStartEndDates ropts
2016-12-30 22:44:01 +03:00
2019-05-06 03:47:38 +03:00
-- Some pure alternatives to the above. XXX review/clean up
-- Get the report's start date.
-- If no report period is specified, will be Nothing.
-- Will also be Nothing if ReportOpts does not have today_ set,
-- since we need that to get the report period robustly
-- (unlike reportStartDate, which looks up the date with IO.)
reportPeriodStart :: ReportOpts -> Maybe Day
reportPeriodStart ropts @ ReportOpts { .. } = do
t <- today_
queryStartDate False $ queryFromOpts t ropts
-- Get the report's start date, or if no report period is specified,
-- the journal's start date (the earliest posting date). If there's no
-- report period and nothing in the journal, will be Nothing.
reportPeriodOrJournalStart :: ReportOpts -> Journal -> Maybe Day
2019-12-15 02:44:59 +03:00
reportPeriodOrJournalStart ropts j =
2019-05-06 03:47:38 +03:00
reportPeriodStart ropts <|> journalStartDate False j
2019-05-04 22:00:57 +03:00
-- Get the last day of the overall report period.
2019-07-15 13:28:52 +03:00
-- This the inclusive end date (one day before the
2019-05-06 03:47:38 +03:00
-- more commonly used, exclusive, report end date).
2019-05-04 22:00:57 +03:00
-- If no report period is specified, will be Nothing.
-- Will also be Nothing if ReportOpts does not have today_ set,
2019-05-06 03:47:38 +03:00
-- since we need that to get the report period robustly
-- (unlike reportEndDate, which looks up the date with IO.)
2019-05-04 22:00:57 +03:00
reportPeriodLastDay :: ReportOpts -> Maybe Day
reportPeriodLastDay ropts @ ReportOpts { .. } = do
t <- today_
let q = queryFromOpts t ropts
qend <- queryEndDate False q
return $ addDays ( - 1 ) qend
2019-05-06 03:47:38 +03:00
-- Get the last day of the overall report period, or if no report
-- period is specified, the last day of the journal (ie the latest
-- posting date). If there's no report period and nothing in the
-- journal, will be Nothing.
2019-05-04 22:00:57 +03:00
reportPeriodOrJournalLastDay :: ReportOpts -> Journal -> Maybe Day
2019-12-15 02:44:59 +03:00
reportPeriodOrJournalLastDay ropts j =
2019-05-04 22:00:57 +03:00
reportPeriodLastDay ropts <|> journalEndDate False j
2018-09-04 21:54:40 +03:00
-- tests
2018-09-06 23:08:26 +03:00
tests_ReportOptions = tests " ReportOptions " [
2019-11-29 02:29:03 +03:00
test " queryFromOpts " $ do
2019-11-27 23:46:29 +03:00
queryFromOpts nulldate defreportopts @?= Any
queryFromOpts nulldate defreportopts { query_ = " a " } @?= Acct " a "
queryFromOpts nulldate defreportopts { query_ = " desc:'a a' " } @?= Desc " a a "
queryFromOpts nulldate defreportopts { period_ = PeriodFrom ( parsedate " 2012/01/01 " ) , query_ = " date:'to 2013' " }
@?= ( Date $ mkdatespan " 2012/01/01 " " 2013/01/01 " )
queryFromOpts nulldate defreportopts { query_ = " date2:'in 2012' " } @?= ( Date2 $ mkdatespan " 2012/01/01 " " 2013/01/01 " )
queryFromOpts nulldate defreportopts { query_ = " 'a a' 'b " } @?= Or [ Acct " a a " , Acct " 'b " ]
2019-11-29 02:29:03 +03:00
, test " queryOptsFromOpts " $ do
2019-11-27 23:46:29 +03:00
queryOptsFromOpts nulldate defreportopts @?= []
queryOptsFromOpts nulldate defreportopts { query_ = " a " } @?= []
queryOptsFromOpts nulldate defreportopts { period_ = PeriodFrom ( parsedate " 2012/01/01 " )
, query_ = " date:'to 2013' " } @?= []
2018-09-04 21:54:40 +03:00
]
2014-03-20 04:11:48 +04:00