Move SortSpec to Hledger.Reports.ReportOptions

As part of this migration, I also switched from using Data.List.splitOn
to Hledger.Utils.splitAtElement.
This commit is contained in:
Michael Rees 2024-07-19 18:26:04 -05:00 committed by Simon Michael
parent 00eb0aa16b
commit b4a9f87fe4
5 changed files with 50 additions and 49 deletions

View File

@ -16,17 +16,15 @@ module Hledger.Reports.PostingsReport (
postingsReport, postingsReport,
mkpostingsReportItem, mkpostingsReportItem,
SortSpec, SortSpec,
getSortSpec,
-- * Tests -- * Tests
tests_PostingsReport tests_PostingsReport
) )
where where
import Data.List (nub, sortBy, sortOn, stripPrefix, isPrefixOf) import Data.List (nub, sortBy, sortOn)
import Data.List.Split (splitOn)
import Data.List.Extra (nubSort) import Data.List.Extra (nubSort)
import Data.Maybe (isJust, isNothing, fromMaybe) import Data.Maybe (isJust, isNothing)
import Data.Ord import Data.Ord
import Data.Text (Text) import Data.Text (Text)
import Data.Time.Calendar (Day) import Data.Time.Calendar (Day)
@ -65,44 +63,15 @@ instance HasAmounts PostingsReportItem where
-- with a tuple. -- with a tuple.
type SummaryPosting = (Posting, Period) type SummaryPosting = (Posting, Period)
-- Possible value expressions taken by the --sort command
-- Each of these takes a bool, which shows if it has been inverted
-- (True -> has been inverted, reverse the order)
data ValueExp
= Date' Bool
| Account' Bool
| Amount' Bool
deriving (Show)
type SortSpec = [ValueExp]
-- Load a SortSpec from the argument given to --sort
-- If there is no spec given, then sort by [Date' False] by default
getSortSpec :: RawOpts -> SortSpec
getSortSpec opts =
let opt = maybestringopt "sort" opts
optParser s =
let terms = map strip $ splitOn "," s
termParser t = case trimmed of
"date" -> Date' isNegated
"account" -> Account' isNegated
"amount" -> Amount' isNegated
_ -> error' $ "unsupported value expression '" ++ t ++ "' given to --sort"
where isNegated = isPrefixOf "-" t
trimmed = fromMaybe t (stripPrefix "-" t)
in map termParser terms
in maybe [Date' False] optParser opt
-- | Select postings from the journal and add running balance and other -- | Select postings from the journal and add running balance and other
-- information to make a postings report. Used by eg hledger's register command. -- information to make a postings report. Used by eg hledger's register command.
postingsReport :: ReportSpec -> Maybe SortSpec -> Journal -> PostingsReport postingsReport :: ReportSpec -> Journal -> PostingsReport
postingsReport rspec@ReportSpec{_rsReportOpts=ropts@ReportOpts{..}} sortspec j = items postingsReport rspec@ReportSpec{_rsReportOpts=ropts@ReportOpts{..}} j = items
where where
(reportspan, colspans) = reportSpanBothDates j rspec (reportspan, colspans) = reportSpanBothDates j rspec
whichdate = whichDate ropts whichdate = whichDate ropts
mdepth = queryDepth $ _rsQuery rspec mdepth = queryDepth $ _rsQuery rspec
multiperiod = interval_ /= NoInterval multiperiod = interval_ /= NoInterval
-- Sort by the date (or date2) field if nothing else is specified
sspec = fromMaybe [Date' False] sortspec
-- postings to be included in the report, and similarly-matched postings before the report start date -- postings to be included in the report, and similarly-matched postings before the report start date
(precedingps, reportps) = matchedPostingsBeforeAndDuring rspec j reportspan (precedingps, reportps) = matchedPostingsBeforeAndDuring rspec j reportspan
@ -115,7 +84,7 @@ postingsReport rspec@ReportSpec{_rsReportOpts=ropts@ReportOpts{..}} sortspec j =
summariseps = summarisePostingsByInterval whichdate mdepth showempty colspans summariseps = summarisePostingsByInterval whichdate mdepth showempty colspans
showempty = empty_ || average_ showempty = empty_ || average_
sortedps = sortPostings ropts sspec displayps sortedps = sortPostings ropts sortspec_ displayps
-- Posting report items ready for display. -- Posting report items ready for display.
items = items =
@ -273,11 +242,8 @@ negatePostingAmount = postingTransformAmount negate
tests_PostingsReport = testGroup "PostingsReport" [ tests_PostingsReport = testGroup "PostingsReport" [
let sspec = Just [Date' False]
in
testCase "postingsReport" $ do testCase "postingsReport" $ do
let (query, journal) `gives` n = (length $ postingsReport defreportspec{_rsQuery=query} sspec journal) @?= n let (query, journal) `gives` n = (length $ postingsReport defreportspec{_rsQuery=query} journal) @?= n
-- with the query specified explicitly -- with the query specified explicitly
(Any, nulljournal) `gives` 0 (Any, nulljournal) `gives` 0
(Any, samplejournal) `gives` 13 (Any, samplejournal) `gives` 13
@ -286,10 +252,10 @@ tests_PostingsReport = testGroup "PostingsReport" [
(And [Depth 1, StatusQ Cleared, Acct (toRegex' "expenses")], samplejournal) `gives` 2 (And [Depth 1, StatusQ Cleared, Acct (toRegex' "expenses")], samplejournal) `gives` 2
(And [And [Depth 1, StatusQ Cleared], Acct (toRegex' "expenses")], samplejournal) `gives` 2 (And [And [Depth 1, StatusQ Cleared], Acct (toRegex' "expenses")], samplejournal) `gives` 2
-- with query and/or command-line options -- with query and/or command-line options
(length $ postingsReport defreportspec sspec samplejournal) @?= 13 (length $ postingsReport defreportspec samplejournal) @?= 13
(length $ postingsReport defreportspec{_rsReportOpts=defreportopts{interval_=Months 1}} sspec samplejournal) @?= 11 (length $ postingsReport defreportspec{_rsReportOpts=defreportopts{interval_=Months 1}} samplejournal) @?= 11
(length $ postingsReport defreportspec{_rsReportOpts=defreportopts{interval_=Months 1, empty_=True}} sspec samplejournal) @?= 20 (length $ postingsReport defreportspec{_rsReportOpts=defreportopts{interval_=Months 1, empty_=True}} samplejournal) @?= 20
(length $ postingsReport defreportspec{_rsQuery=Acct $ toRegex' "assets:bank:checking"} sspec samplejournal) @?= 5 (length $ postingsReport defreportspec{_rsQuery=Acct $ toRegex' "assets:bank:checking"} samplejournal) @?= 5
-- (defreportopts, And [Acct "a a", Acct "'b"], samplejournal2) `gives` 0 -- (defreportopts, And [Acct "a a", Acct "'b"], samplejournal2) `gives` 0
-- [(Just (fromGregorian 2008 01 01,"income"),assets:bank:checking $1,$1) -- [(Just (fromGregorian 2008 01 01,"income"),assets:bank:checking $1,$1)

View File

@ -21,6 +21,8 @@ module Hledger.Reports.ReportOptions (
HasReportOpts(..), HasReportOpts(..),
ReportSpec(..), ReportSpec(..),
HasReportSpec(..), HasReportSpec(..),
SortField(..),
SortSpec,
overEither, overEither,
setEither, setEither,
BalanceCalculation(..), BalanceCalculation(..),
@ -71,7 +73,7 @@ import Data.Char (toLower)
import Data.Either (fromRight) import Data.Either (fromRight)
import Data.Either.Extra (eitherToMaybe) import Data.Either.Extra (eitherToMaybe)
import Data.Functor.Identity (Identity(..)) import Data.Functor.Identity (Identity(..))
import Data.List.Extra (find, isPrefixOf, nubSort) import Data.List.Extra (find, isPrefixOf, nubSort, stripPrefix)
import Data.Maybe (fromMaybe, isJust, isNothing) import Data.Maybe (fromMaybe, isJust, isNothing)
import qualified Data.Text as T import qualified Data.Text as T
import Data.Time.Calendar (Day, addDays) import Data.Time.Calendar (Day, addDays)
@ -142,6 +144,8 @@ data ReportOpts = ReportOpts {
,average_ :: Bool ,average_ :: Bool
-- for posting reports (register) -- for posting reports (register)
,related_ :: Bool ,related_ :: Bool
-- for sorting reports (register)
,sortspec_ :: SortSpec
-- for account transactions reports (aregister) -- for account transactions reports (aregister)
,txn_dates_ :: Bool ,txn_dates_ :: Bool
-- for balance reports (bal, bs, cf, is) -- for balance reports (bal, bs, cf, is)
@ -197,6 +201,7 @@ defreportopts = ReportOpts
, querystring_ = [] , querystring_ = []
, average_ = False , average_ = False
, related_ = False , related_ = False
, sortspec_ = [Date' False] -- by default, sort by date in ascending order
, txn_dates_ = False , txn_dates_ = False
, balancecalc_ = def , balancecalc_ = def
, balanceaccum_ = def , balanceaccum_ = def
@ -251,6 +256,7 @@ rawOptsToReportOpts d rawopts =
,querystring_ = querystring ,querystring_ = querystring
,average_ = boolopt "average" rawopts ,average_ = boolopt "average" rawopts
,related_ = boolopt "related" rawopts ,related_ = boolopt "related" rawopts
,sortspec_ = getSortSpec rawopts
,txn_dates_ = boolopt "txn-dates" rawopts ,txn_dates_ = boolopt "txn-dates" rawopts
,balancecalc_ = balancecalcopt rawopts ,balancecalc_ = balancecalcopt rawopts
,balanceaccum_ = balanceaccumopt rawopts ,balanceaccum_ = balanceaccumopt rawopts
@ -663,6 +669,36 @@ queryFromFlags ReportOpts{..} = simplifyQuery $ And flagsq
consIf f b = if b then (f True:) else id consIf f b = if b then (f True:) else id
consJust f = maybe id ((:) . f) consJust f = maybe id ((:) . f)
-- Methods/types needed for --sort argument
-- Possible arguments taken by the --sort command
-- Each of these takes a bool, which shows if it has been inverted
-- (True -> has been inverted, reverse the order)
data SortField
= Date' Bool
| Account' Bool
| Amount' Bool
deriving (Show)
type SortSpec = [SortField]
-- Load a SortSpec from the argument given to --sort
-- If there is no spec given, then sort by [Date' False] by default
getSortSpec :: RawOpts -> SortSpec
getSortSpec opts =
let opt = maybestringopt "sort" opts
optParser s =
let terms = map strip $ splitAtElement ',' s
termParser t = case trimmed of
"date" -> Date' isNegated
"account" -> Account' isNegated
"amount" -> Amount' isNegated
_ -> error' $ "unsupported field '" ++ t ++ "' given to --sort"
where isNegated = isPrefixOf "-" t
trimmed = fromMaybe t (stripPrefix "-" t)
in map termParser terms
in maybe [Date' False] optParser opt
-- Report dates. -- Report dates.
-- | The effective report span is the start and end dates specified by -- | The effective report span is the start and end dates specified by

View File

@ -147,7 +147,6 @@ library
, pretty-simple >4 && <5 , pretty-simple >4 && <5
, regex-tdfa , regex-tdfa
, safe >=0.3.20 , safe >=0.3.20
, split >=0.1
, tabular >=0.2 , tabular >=0.2
, tasty >=1.2.3 , tasty >=1.2.3
, tasty-hunit >=0.10.0.2 , tasty-hunit >=0.10.0.2

View File

@ -460,7 +460,7 @@ ensureOneNewlineTerminated = (<>"\n") . T.dropWhileEnd (=='\n')
registerFromString :: T.Text -> IO TL.Text registerFromString :: T.Text -> IO TL.Text
registerFromString s = do registerFromString s = do
j <- readJournal' s j <- readJournal' s
return . postingsReportAsText opts $ postingsReport rspec Nothing j return . postingsReportAsText opts $ postingsReport rspec j
where where
ropts = defreportopts{empty_=True} ropts = defreportopts{empty_=True}
rspec = defreportspec{_rsReportOpts=ropts} rspec = defreportspec{_rsReportOpts=ropts}

View File

@ -86,7 +86,7 @@ register opts@CliOpts{rawopts_=rawopts, reportspec_=rspec} j
| otherwise = writeOutputLazyText opts $ render $ styleAmounts styles rpt | otherwise = writeOutputLazyText opts $ render $ styleAmounts styles rpt
where where
styles = journalCommodityStylesWith HardRounding j styles = journalCommodityStylesWith HardRounding j
rpt = postingsReport rspec (Just (getSortSpec rawopts)) j rpt = postingsReport rspec j
render | fmt=="txt" = postingsReportAsText opts render | fmt=="txt" = postingsReportAsText opts
| fmt=="csv" = printCSV . postingsReportAsCsv | fmt=="csv" = printCSV . postingsReportAsCsv
| fmt=="tsv" = printTSV . postingsReportAsCsv | fmt=="tsv" = printTSV . postingsReportAsCsv
@ -261,7 +261,7 @@ tests_Register = testGroup "Register" [
testCase "unicode in register layout" $ do testCase "unicode in register layout" $ do
j <- readJournal' "2009/01/01 * медвежья шкура\n расходы:покупки 100\n актив:наличные\n" j <- readJournal' "2009/01/01 * медвежья шкура\n расходы:покупки 100\n актив:наличные\n"
let rspec = defreportspec let rspec = defreportspec
(TL.unpack . postingsReportAsText defcliopts $ postingsReport rspec Nothing j) (TL.unpack . postingsReportAsText defcliopts $ postingsReport rspec j)
@?= @?=
unlines unlines
["2009-01-01 медвежья шкура расходы:покупки 100 100" ["2009-01-01 медвежья шкура расходы:покупки 100 100"