hledger/hledger-lib/Hledger/Reports/ReportOptions.hs

338 lines
12 KiB
Haskell
Raw Normal View History

2015-08-28 21:17:49 +03:00
{-# LANGUAGE CPP, RecordWildCards, DeriveDataTypeable #-}
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
-}
module Hledger.Reports.ReportOptions (
ReportOpts(..),
BalanceType(..),
AccountListMode(..),
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,
flat_,
tree_,
2014-03-20 04:11:48 +04:00
dateSpanFromOpts,
intervalFromOpts,
clearedValueFromOpts,
whichDateFromOpts,
journalSelectingAmountFromOpts,
queryFromOpts,
queryFromOptsOnly,
queryOptsFromOpts,
transactionDateFn,
postingDateFn,
tests_Hledger_Reports_ReportOptions
)
where
2014-03-26 04:10:30 +04:00
import Data.Data (Data)
2015-08-28 21:17:49 +03:00
#if !MIN_VERSION_base(4,8,0)
import Data.Functor.Compat ((<$>))
#endif
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
2014-03-26 04:10:30 +04:00
import System.Console.CmdArgs.Default -- some additional default stuff
2014-03-20 04:11:48 +04:00
import Test.HUnit
import Hledger.Data
import Hledger.Query
import Hledger.Utils
2014-03-26 04:10:30 +04:00
type FormatStr = String
-- | Which balance is being shown in a multi-column balance report.
data BalanceType = PeriodBalance -- ^ The change of balance in each period.
| CumulativeBalance -- ^ The accumulated balance at each period's end, starting from zero at the report start date.
| HistoricalBalance -- ^ The historical balance at each period's end, starting from the account balances at the report start date.
deriving (Eq,Show,Data,Typeable)
instance Default BalanceType where def = PeriodBalance
-- | 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
2014-03-20 04:11:48 +04:00
-- | Standard options for customising report filtering and output,
-- corresponding to hledger's command-line options and query language
-- arguments. Used in hledger-lib and above.
data ReportOpts = ReportOpts {
begin_ :: Maybe Day
,end_ :: Maybe Day
,period_ :: Maybe (Interval,DateSpan)
,cleared_ :: Bool
,pending_ :: Bool
2014-03-20 04:11:48 +04:00
,uncleared_ :: Bool
,cost_ :: Bool
,depth_ :: Maybe Int
,display_ :: Maybe DisplayExp
,date2_ :: Bool
,empty_ :: Bool
,no_elide_ :: Bool
,real_ :: Bool
,daily_ :: Bool
,weekly_ :: Bool
,monthly_ :: Bool
,quarterly_ :: Bool
,yearly_ :: Bool
,format_ :: Maybe FormatStr
,query_ :: String -- all arguments, as a string
2014-03-26 04:10:30 +04:00
-- register
,average_ :: Bool
,related_ :: Bool
-- balance
,balancetype_ :: BalanceType
,accountlistmode_ :: AccountListMode
2014-03-26 04:10:30 +04:00
,drop_ :: Int
,row_total_ :: Bool
2014-03-26 04:10:30 +04:00
,no_total_ :: Bool
,value_ :: 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
def
def
def
def
def
def
def
def
2014-03-20 04:11:48 +04: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
2014-03-26 04:10:30 +04:00
d <- getCurrentDay
return defreportopts{
begin_ = maybesmartdateopt d "begin" rawopts
,end_ = maybesmartdateopt d "end" rawopts
,period_ = maybeperiodopt d rawopts
,cleared_ = boolopt "cleared" rawopts
,pending_ = boolopt "pending" rawopts
2014-03-26 04:10:30 +04:00
,uncleared_ = boolopt "uncleared" rawopts
,cost_ = boolopt "cost" rawopts
,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
,daily_ = boolopt "daily" rawopts
,weekly_ = boolopt "weekly" rawopts
,monthly_ = boolopt "monthly" rawopts
,quarterly_ = boolopt "quarterly" rawopts
,yearly_ = boolopt "yearly" rawopts
,format_ = maybestringopt "format" rawopts -- XXX move to CliOpts or move validation from Cli.CliOptions to here
2014-03-26 04:10:30 +04:00
,query_ = unwords $ listofstringopt "args" rawopts -- doesn't handle an arg like "" right
,average_ = boolopt "average" rawopts
,related_ = boolopt "related" rawopts
,balancetype_ = balancetypeopt rawopts
,accountlistmode_ = accountlistmodeopt rawopts
2014-03-26 04:10:30 +04:00
,drop_ = intopt "drop" rawopts
,row_total_ = boolopt "row-total" rawopts
2014-03-26 04:10:30 +04:00
,no_total_ = boolopt "no-total" rawopts
,value_ = boolopt "value" rawopts
2014-03-26 04:10:30 +04:00
}
2015-08-28 19:57:01 +03:00
-- | Do extra validation of opts, raising an error if there is trouble.
checkReportOpts :: ReportOpts -> ReportOpts
checkReportOpts ropts@ReportOpts{..} =
either optserror (const ropts) $ do
case depth_ of
Just d | d < 0 -> Left "--depth should have a positive number"
_ -> Right ()
2015-08-28 19:57:01 +03:00
accountlistmodeopt :: RawOpts -> AccountListMode
accountlistmodeopt rawopts =
case reverse $ filter (`elem` ["tree","flat"]) $ map fst rawopts of
("tree":_) -> ALTree
("flat":_) -> ALFlat
_ -> ALDefault
2014-03-26 04:10:30 +04:00
balancetypeopt :: RawOpts -> BalanceType
balancetypeopt rawopts
| length [o | o <- ["cumulative","historical"], isset o] > 1
= optserror "please specify at most one of --cumulative and --historical"
| isset "cumulative" = CumulativeBalance
| isset "historical" = HistoricalBalance
| otherwise = PeriodBalance
where
isset = flip boolopt rawopts
maybesmartdateopt :: Day -> String -> RawOpts -> Maybe Day
maybesmartdateopt d name rawopts =
case maybestringopt name rawopts of
Nothing -> Nothing
Just s -> either
(\e -> optserror $ "could not parse "++name++" date: "++show e)
Just
$ fixSmartDateStrEither' d (T.pack s)
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 "" = ""
fixbracketeddatestr s = "[" ++ fixSmartDateStr d (T.pack $ init $ tail s) ++ "]"
2014-03-26 04:10:30 +04:00
maybeperiodopt :: Day -> RawOpts -> Maybe (Interval,DateSpan)
maybeperiodopt d rawopts =
case maybestringopt "period" rawopts of
Nothing -> Nothing
Just s -> either
(\e -> optserror $ "could not parse period option: "++show e)
Just
$ parsePeriodExpr d (T.pack s)
2014-03-20 04:11:48 +04: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
-- | Figure out the date span we should report on, based on any
-- begin/end/period options provided. A period option will cause begin and
-- end options to be ignored.
dateSpanFromOpts :: Day -> ReportOpts -> DateSpan
dateSpanFromOpts _ ReportOpts{..} =
case period_ of Just (_,span) -> span
Nothing -> DateSpan begin_ end_
-- | Figure out the reporting interval, if any, specified by the options.
-- --period overrides --daily overrides --weekly overrides --monthly etc.
intervalFromOpts :: ReportOpts -> Interval
intervalFromOpts ReportOpts{..} =
case period_ of
Just (interval,_) -> interval
Nothing -> i
where i | daily_ = Days 1
| weekly_ = Weeks 1
| monthly_ = Months 1
| quarterly_ = Quarters 1
| yearly_ = Years 1
| otherwise = NoInterval
-- | Get a maybe boolean representing the last cleared/uncleared option if any.
clearedValueFromOpts :: ReportOpts -> Maybe ClearedStatus
clearedValueFromOpts ReportOpts{..} | cleared_ = Just Cleared
| pending_ = Just Pending
| uncleared_ = Just Uncleared
2014-03-20 04:11:48 +04:00
| otherwise = Nothing
-- depthFromOpts :: ReportOpts -> Int
-- depthFromOpts opts = min (fromMaybe 99999 $ depth_ opts) (queryDepth $ queryFromOpts nulldate opts)
-- | Report which date we will report on based on --date2.
whichDateFromOpts :: ReportOpts -> WhichDate
whichDateFromOpts ReportOpts{..} = if date2_ then SecondaryDate else PrimaryDate
-- | 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
-- | Convert this journal's postings' amounts to the cost basis amounts if
-- specified by options.
journalSelectingAmountFromOpts :: ReportOpts -> Journal -> Journal
journalSelectingAmountFromOpts opts
| cost_ opts = journalConvertAmountsToCost
| otherwise = id
-- | Convert report options and arguments to a query.
queryFromOpts :: Day -> ReportOpts -> Query
queryFromOpts d opts@ReportOpts{..} = simplifyQuery $ And $ [flagsq, argsq]
where
flagsq = And $
[(if date2_ then Date2 else Date) $ dateSpanFromOpts d opts]
++ (if real_ then [Real True] else [])
++ (if empty_ then [Empty True] else []) -- ?
++ (maybe [] ((:[]) . Status) (clearedValueFromOpts opts))
++ (maybe [] ((:[]) . Depth) depth_)
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
queryFromOptsOnly d opts@ReportOpts{..} = simplifyQuery flagsq
where
flagsq = And $
[(if date2_ then Date2 else Date) $ dateSpanFromOpts d opts]
++ (if real_ then [Real True] else [])
++ (if empty_ then [Empty True] else []) -- ?
++ (maybe [] ((:[]) . Status) (clearedValueFromOpts opts))
++ (maybe [] ((:[]) . Depth) depth_)
2014-03-26 04:10:30 +04:00
tests_queryFromOpts :: [Test]
2014-03-20 04:11:48 +04:00
tests_queryFromOpts = [
"queryFromOpts" ~: do
assertEqual "" Any (queryFromOpts nulldate defreportopts)
assertEqual "" (Acct "a") (queryFromOpts nulldate defreportopts{query_="a"})
assertEqual "" (Desc "a a") (queryFromOpts nulldate defreportopts{query_="desc:'a a'"})
assertEqual "" (Date $ mkdatespan "2012/01/01" "2013/01/01")
(queryFromOpts nulldate defreportopts{begin_=Just (parsedate "2012/01/01")
,query_="date:'to 2013'"
})
assertEqual "" (Date2 $ mkdatespan "2012/01/01" "2013/01/01")
2014-12-16 22:06:21 +03:00
(queryFromOpts nulldate defreportopts{query_="date2:'in 2012'"})
2014-03-20 04:11:48 +04:00
assertEqual "" (Or [Acct "a a", Acct "'b"])
(queryFromOpts nulldate defreportopts{query_="'a a' 'b"})
]
-- | Convert report options and arguments to query options.
queryOptsFromOpts :: Day -> ReportOpts -> [QueryOpt]
queryOptsFromOpts d ReportOpts{..} = flagsqopts ++ argsqopts
where
flagsqopts = []
argsqopts = snd $ parseQuery d (T.pack query_)
2014-03-20 04:11:48 +04:00
2014-03-26 04:10:30 +04:00
tests_queryOptsFromOpts :: [Test]
2014-03-20 04:11:48 +04:00
tests_queryOptsFromOpts = [
"queryOptsFromOpts" ~: do
assertEqual "" [] (queryOptsFromOpts nulldate defreportopts)
assertEqual "" [] (queryOptsFromOpts nulldate defreportopts{query_="a"})
assertEqual "" [] (queryOptsFromOpts nulldate defreportopts{begin_=Just (parsedate "2012/01/01")
,query_="date:'to 2013'"
})
]
tests_Hledger_Reports_ReportOptions :: Test
tests_Hledger_Reports_ReportOptions = TestList $
tests_queryFromOpts
++ tests_queryOptsFromOpts