From 97be1646f1d00a7435f8c0b8c8f3e4ecc2638169 Mon Sep 17 00:00:00 2001 From: Simon Michael Date: Wed, 30 Aug 2023 13:13:59 +0100 Subject: [PATCH] dev: AmountStyle: make setting the precision optional This simplifies the code for styling amounts with or without precision. But it complicates the semantics (Nothing is useful only when setting style). Not sure if it's the best way. --- hledger-lib/Hledger/Data/Amount.hs | 72 ++++++++++--------- hledger-lib/Hledger/Data/Balancing.hs | 16 ++--- hledger-lib/Hledger/Data/Transaction.hs | 17 +++-- hledger-lib/Hledger/Data/Types.hs | 4 +- hledger-lib/Hledger/Data/Valuation.hs | 4 +- hledger-lib/Hledger/Read/Common.hs | 18 ++--- hledger-lib/Hledger/Read/TimedotReader.hs | 4 +- .../Hledger/Reports/MultiBalanceReport.hs | 3 +- 8 files changed, 77 insertions(+), 61 deletions(-) diff --git a/hledger-lib/Hledger/Data/Amount.hs b/hledger-lib/Hledger/Data/Amount.hs index c45618651..4723e2f00 100644 --- a/hledger-lib/Hledger/Data/Amount.hs +++ b/hledger-lib/Hledger/Data/Amount.hs @@ -246,7 +246,7 @@ csvDisplay = oneLine{displayThousandsSep=False} -- Amount styles -- | Default amount style -amountstyle = AmountStyle L False Nothing (Just '.') (Precision 0) +amountstyle = AmountStyle L False Nothing (Just '.') (Just $ Precision 0) ------------------------------------------------------------------------------- -- Amount @@ -275,11 +275,11 @@ missingamt = nullamt{acommodity="AUTO"} -- usd/eur/gbp round their argument to a whole number of pennies/cents. -- XXX these are a bit clashy num n = nullamt{acommodity="", aquantity=n} -hrs n = nullamt{acommodity="h", aquantity=n, astyle=amountstyle{asprecision=Precision 2, ascommodityside=R}} -usd n = nullamt{acommodity="$", aquantity=roundTo 2 n, astyle=amountstyle{asprecision=Precision 2}} -eur n = nullamt{acommodity="€", aquantity=roundTo 2 n, astyle=amountstyle{asprecision=Precision 2}} -gbp n = nullamt{acommodity="£", aquantity=roundTo 2 n, astyle=amountstyle{asprecision=Precision 2}} -per n = nullamt{acommodity="%", aquantity=n, astyle=amountstyle{asprecision=Precision 1, ascommodityside=R, ascommodityspaced=True}} +hrs n = nullamt{acommodity="h", aquantity=n, astyle=amountstyle{asprecision=Just $ Precision 2, ascommodityside=R}} +usd n = nullamt{acommodity="$", aquantity=roundTo 2 n, astyle=amountstyle{asprecision=Just $ Precision 2}} +eur n = nullamt{acommodity="€", aquantity=roundTo 2 n, astyle=amountstyle{asprecision=Just $ Precision 2}} +gbp n = nullamt{acommodity="£", aquantity=roundTo 2 n, astyle=amountstyle{asprecision=Just $ Precision 2}} +per n = nullamt{acommodity="%", aquantity=n, astyle=amountstyle{asprecision=Just $ Precision 1, ascommodityside=R, ascommodityspaced=True}} amt `at` priceamt = amt{aprice=Just $ UnitPrice priceamt} amt @@ priceamt = amt{aprice=Just $ TotalPrice priceamt} @@ -337,12 +337,13 @@ multiplyAmount n = transformAmount (*n) isNegativeAmount :: Amount -> Bool isNegativeAmount Amount{aquantity=q} = q < 0 --- | Round an Amount's Quantity to its specified display precision. If that is --- NaturalPrecision, this does nothing. +-- | Round an Amount's Quantity (internally) to match its display precision. +-- If that is unset or NaturalPrecision, this does nothing. amountRoundedQuantity :: Amount -> Quantity -amountRoundedQuantity Amount{aquantity=q, astyle=AmountStyle{asprecision=p}} = case p of - NaturalPrecision -> q - Precision p' -> roundTo p' q +amountRoundedQuantity Amount{aquantity=q, astyle=AmountStyle{asprecision=mp}} = case mp of + Nothing -> q + Just NaturalPrecision -> q + Just (Precision p) -> roundTo p q -- | Apply a test to both an Amount and its total price, if it has one. testAmountAndTotalPrice :: (Amount -> Bool) -> Amount -> Bool @@ -350,14 +351,17 @@ testAmountAndTotalPrice f amt = case aprice amt of Just (TotalPrice price) -> f amt && f price _ -> f amt --- | Do this Amount and (and its total price, if it has one) appear to be zero when rendered with its --- display precision ? +-- | Do this Amount and (and its total price, if it has one) appear to be zero +-- when rendered with its display precision ? +-- The display precision should usually have a specific value here; +-- if unset, it will be treated like NaturalPrecision. amountLooksZero :: Amount -> Bool amountLooksZero = testAmountAndTotalPrice looksZero where looksZero Amount{aquantity=Decimal e q, astyle=AmountStyle{asprecision=p}} = case p of - Precision d -> if e > d then abs q <= 5*10^(e-d-1) else q == 0 - NaturalPrecision -> q == 0 + Just (Precision d) -> if e > d then abs q <= 5*10^(e-d-1) else q == 0 + Just NaturalPrecision -> q == 0 + Nothing -> q == 0 -- | Is this Amount (and its total price, if it has one) exactly zero, ignoring its display precision ? amountIsZero :: Amount -> Bool @@ -369,16 +373,16 @@ withPrecision = flip amountSetPrecision -- | Set an amount's display precision. amountSetPrecision :: AmountPrecision -> Amount -> Amount -amountSetPrecision p a@Amount{astyle=s} = a{astyle=s{asprecision=p}} +amountSetPrecision p a@Amount{astyle=s} = a{astyle=s{asprecision=Just p}} -- | Increase an amount's display precision, if needed, to enough decimal places --- to show it exactly (showing all significant decimal digits, excluding trailing --- zeros). +-- to show it exactly (showing all significant decimal digits, without trailing zeros). +-- If the amount's display precision is unset, it is will be treated as precision 0. amountSetFullPrecision :: Amount -> Amount amountSetFullPrecision a = amountSetPrecision p a where p = max displayprecision naturalprecision - displayprecision = asprecision $ astyle a + displayprecision = fromMaybe (Precision 0) $ asprecision $ astyle a naturalprecision = Precision . decimalPlaces . normalizeDecimal $ aquantity a -- | Set an amount's internal precision, ie rounds the Decimal representing @@ -389,7 +393,7 @@ amountSetFullPrecision a = amountSetPrecision p a -- Intended mainly for internal use, eg when comparing amounts in tests. setAmountInternalPrecision :: Word8 -> Amount -> Amount setAmountInternalPrecision p a@Amount{ aquantity=q, astyle=s } = a{ - astyle=s{asprecision=Precision p} + astyle=s{asprecision=Just $ Precision p} ,aquantity=roundTo p q } @@ -449,27 +453,31 @@ styleAmountExceptPrecision styles a@Amount{astyle=AmountStyle{asprecision=origp} Just s -> a{astyle=s{asprecision=origp}} Nothing -> a --- v2.9 +-- v3 -- | Given some commodity display styles, find and apply the appropriate -- display style to this amount, and do the same for its cost amount if any -- (and then stop; we assume costs don't have costs). --- The main amount's display precision is set according to its style; --- the cost amount's display precision is left unchanged, regardless of its style. --- If no style is found for an amount, it is left unchanged. +-- The main amount's display precision may or may not be changed, as specified by the style. +-- the cost amount's display precision is left unchanged, ignoring what the style says. + -- If no style is found for an amount, it is left unchanged. amountSetStyles :: M.Map CommoditySymbol AmountStyle -> Amount -> Amount amountSetStyles styles = amountSetMainStyle styles <&> amountSetCostStyle styles -- | Find and apply the appropriate display style, if any, to this amount. --- The display precision is also set. +-- The display precision may or may not be changed, as specified by the style. amountSetMainStyle :: M.Map CommoditySymbol AmountStyle -> Amount -> Amount -amountSetMainStyle styles a@Amount{acommodity=comm} = +amountSetMainStyle styles a@Amount{acommodity=comm, astyle=AmountStyle{asprecision=morigp}} = case M.lookup comm styles of - Nothing -> a - Just s -> a{astyle=s} + Nothing -> a + Just s@AmountStyle{asprecision=mp} -> a{astyle=s'} + where + s' = case mp of + Nothing -> s{asprecision=morigp} + _ -> s -- | Find and apply the appropriate display style, if any, to this amount's cost, if any. --- The display precision is left unchanged. +-- The display precision is left unchanged, ignoring what the style says. amountSetCostStyle :: M.Map CommoditySymbol AmountStyle -> Amount -> Amount amountSetCostStyle styles a@Amount{aprice=mcost} = case mcost of @@ -856,7 +864,7 @@ styleMixedAmount :: M.Map CommoditySymbol AmountStyle -> MixedAmount -> MixedAmo styleMixedAmount = mixedAmountSetStyles {-# DEPRECATED styleMixedAmount "please use mixedAmountSetStyles instead" #-} --- v2.9 +-- v3 mixedAmountSetStyles :: M.Map CommoditySymbol AmountStyle -> MixedAmount -> MixedAmount mixedAmountSetStyles styles = mapMixedAmountUnsafe (amountSetStyles styles) @@ -1085,8 +1093,8 @@ tests_Amount = testGroup "Amount" [ (usd (-1.23) + usd (-1.23)) @?= usd (-2.46) sum [usd 1.23,usd (-1.23),usd (-1.23),-(usd (-1.23))] @?= usd 0 -- highest precision is preserved - asprecision (astyle $ sum [usd 1 `withPrecision` Precision 1, usd 1 `withPrecision` Precision 3]) @?= Precision 3 - asprecision (astyle $ sum [usd 1 `withPrecision` Precision 3, usd 1 `withPrecision` Precision 1]) @?= Precision 3 + asprecision (astyle $ sum [usd 1 `withPrecision` Precision 1, usd 1 `withPrecision` Precision 3]) @?= Just (Precision 3) + asprecision (astyle $ sum [usd 1 `withPrecision` Precision 3, usd 1 `withPrecision` Precision 1]) @?= Just (Precision 3) -- adding different commodities assumes conversion rate 1 assertBool "" $ amountLooksZero (usd 1.23 - eur 1.23) diff --git a/hledger-lib/Hledger/Data/Balancing.hs b/hledger-lib/Hledger/Data/Balancing.hs index eebcf0709..5027795d8 100644 --- a/hledger-lib/Hledger/Data/Balancing.hs +++ b/hledger-lib/Hledger/Data/Balancing.hs @@ -337,8 +337,8 @@ costInferrerFor t pt = maybe id infercost inferFromAndTo unitprice = aquantity fromamount `divideAmount` toamount unitprecision = case (asprecision $ astyle fromamount, asprecision $ astyle toamount) of - (Precision a, Precision b) -> Precision . max 2 $ saturatedAdd a b - _ -> NaturalPrecision + (Just (Precision a), Just (Precision b)) -> Precision . max 2 $ saturatedAdd a b + _ -> NaturalPrecision saturatedAdd a b = if maxBound - a < b then maxBound else a + b @@ -1005,26 +1005,26 @@ tests_Balancing = -- testCase "1091a" $ do commodityStylesFromAmounts [ - nullamt{aquantity=1000, astyle=AmountStyle L False Nothing (Just ',') (Precision 3)} - ,nullamt{aquantity=1000, astyle=AmountStyle L False (Just (DigitGroups ',' [3])) (Just '.') (Precision 2)} + nullamt{aquantity=1000, astyle=AmountStyle L False Nothing (Just ',') (Just $ Precision 3)} + ,nullamt{aquantity=1000, astyle=AmountStyle L False (Just (DigitGroups ',' [3])) (Just '.') (Just $ Precision 2)} ] @?= -- The commodity style should have period as decimal mark -- and comma as digit group mark. Right (M.fromList [ - ("", AmountStyle L False (Just (DigitGroups ',' [3])) (Just '.') (Precision 3)) + ("", AmountStyle L False (Just (DigitGroups ',' [3])) (Just '.') (Just $ Precision 3)) ]) -- same journal, entries in reverse order ,testCase "1091b" $ do commodityStylesFromAmounts [ - nullamt{aquantity=1000, astyle=AmountStyle L False (Just (DigitGroups ',' [3])) (Just '.') (Precision 2)} - ,nullamt{aquantity=1000, astyle=AmountStyle L False Nothing (Just ',') (Precision 3)} + nullamt{aquantity=1000, astyle=AmountStyle L False (Just (DigitGroups ',' [3])) (Just '.') (Just $ Precision 2)} + ,nullamt{aquantity=1000, astyle=AmountStyle L False Nothing (Just ',') (Just $ Precision 3)} ] @?= -- The commodity style should have period as decimal mark -- and comma as digit group mark. Right (M.fromList [ - ("", AmountStyle L False (Just (DigitGroups ',' [3])) (Just '.') (Precision 3)) + ("", AmountStyle L False (Just (DigitGroups ',' [3])) (Just '.') (Just $ Precision 3)) ]) ] diff --git a/hledger-lib/Hledger/Data/Transaction.hs b/hledger-lib/Hledger/Data/Transaction.hs index cbad985d8..9c8670dfe 100644 --- a/hledger-lib/Hledger/Data/Transaction.hs +++ b/hledger-lib/Hledger/Data/Transaction.hs @@ -355,8 +355,8 @@ transactionInferCostsFromEquity dryrun acctTypes t = first (annotateErrorWithTra | dbgamtmatch 2 a2 a (amountsMatch (-a2) a) && dbgcostmatch 1 a1 a (amountsMatch a1 (amountCost a)) -> Just costfulp | otherwise -> Nothing where - dbgamtmatch n a b = dbg7 ("conversion posting " <>show n<>" "<>showAmount a<>" balances amount "<>showAmountWithoutPrice b <>" of costful posting "<>showAmount b<>" at precision "<>amountShowPrecision a<>" ?") - dbgcostmatch n a b = dbg7 ("and\nconversion posting "<>show n<>" "<>showAmount a<>" matches cost " <>showAmount (amountCost b)<>" of costful posting "<>showAmount b<>" at precision "<>amountShowPrecision a<>" ?") + dbgamtmatch n a b = dbg7 ("conversion posting " <>show n<>" "<>showAmount a<>" balances amount "<>showAmountWithoutPrice b <>" of costful posting "<>showAmount b<>" at precision "<>dbgShowAmountPrecision a<>" ?") + dbgcostmatch n a b = dbg7 ("and\nconversion posting "<>show n<>" "<>showAmount a<>" matches cost " <>showAmount (amountCost b)<>" of costful posting "<>showAmount b<>" at precision "<>dbgShowAmountPrecision a<>" ?") -- Add a cost to a posting if it matches (negative) one of the -- supplied conversion amounts, adding the other amount as the cost. @@ -376,7 +376,11 @@ transactionInferCostsFromEquity dryrun acctTypes t = first (annotateErrorWithTra Nothing -> Left $ annotateWithPostings [p] "Conversion postings must have a single-commodity amount:" -- Do these amounts look the same when compared at the first's display precision ? - amountsMatch a b = amountLooksZero $ amountSetPrecision (asprecision $ astyle a) $ a - b + -- (Or if that's unset, compare as-is) + amountsMatch a b = + case asprecision $ astyle a of + Just p -> amountLooksZero $ amountSetPrecision p $ a - b + Nothing -> amountLooksZero $ a - b -- Delete a posting from the indexed list of postings based on either its -- index or its posting amount. @@ -392,10 +396,11 @@ transactionInferCostsFromEquity dryrun acctTypes t = first (annotateErrorWithTra deleteUniqueMatch _ [] = Nothing annotateWithPostings xs str = T.unlines $ str : postingsAsLines False xs -amountShowPrecision a = +dbgShowAmountPrecision a = case asprecision $ astyle a of - Precision n -> show n - NaturalPrecision -> show $ decimalPlaces $ normalizeDecimal $ aquantity a + Just (Precision n) -> show n + Just NaturalPrecision -> show $ decimalPlaces $ normalizeDecimal $ aquantity a + Nothing -> "unset" -- Using the provided account types map, sort the given indexed postings -- into three lists of posting numbers (stored in two pairs), like so: diff --git a/hledger-lib/Hledger/Data/Types.hs b/hledger-lib/Hledger/Data/Types.hs index b0e053b21..1629a0a1c 100644 --- a/hledger-lib/Hledger/Data/Types.hs +++ b/hledger-lib/Hledger/Data/Types.hs @@ -256,7 +256,9 @@ data AmountStyle = AmountStyle { ascommodityspaced :: !Bool, -- ^ show a space between symbol and quantity ? asdigitgroups :: !(Maybe DigitGroupStyle), -- ^ show the integer part with these digit group marks, or not asdecimalmark :: !(Maybe Char), -- ^ show this character (should be . or ,) as decimal mark, or use the default (.) - asprecision :: !AmountPrecision -- ^ show this number of digits after the decimal point + asprecision :: !(Maybe AmountPrecision) -- ^ show this number of digits after the decimal point, or show as-is (leave precision unchanged) + -- XXX Making asprecision a maybe simplifies code for styling with or without precision, + -- but complicates the semantics (Nothing is useful only when setting style). } deriving (Eq,Ord,Read,Generic) instance Show AmountStyle where diff --git a/hledger-lib/Hledger/Data/Valuation.hs b/hledger-lib/Hledger/Data/Valuation.hs index 7c20110fd..868cbb754 100644 --- a/hledger-lib/Hledger/Data/Valuation.hs +++ b/hledger-lib/Hledger/Data/Valuation.hs @@ -110,8 +110,8 @@ amountPriceDirectiveFromCost d amt@Amount{acommodity=fromcomm, aquantity=fromq} where style' = (astyle a) { asprecision = precision' } precision' = case asprecision (astyle a) of - NaturalPrecision -> NaturalPrecision - Precision p -> Precision $ (numDigitsInt $ truncate n) + p + Just (Precision p) -> Just $ Precision $ (numDigitsInt $ truncate n) + p + mp -> mp ------------------------------------------------------------------------------ -- Converting things to value diff --git a/hledger-lib/Hledger/Read/Common.hs b/hledger-lib/Hledger/Read/Common.hs index cdd90a857..832f87a83 100644 --- a/hledger-lib/Hledger/Read/Common.hs +++ b/hledger-lib/Hledger/Read/Common.hs @@ -802,7 +802,7 @@ simpleamountp mult = offAfterNum <- getOffset let numRegion = (offBeforeNum, offAfterNum) (q,prec,mdec,mgrps) <- lift $ interpretNumber numRegion suggestedStyle ambiguousRawNum mExponent - let s = amountstyle{ascommodityside=L, ascommodityspaced=commodityspaced, asprecision=prec, asdecimalmark=mdec, asdigitgroups=mgrps} + let s = amountstyle{ascommodityside=L, ascommodityspaced=commodityspaced, asprecision=Just prec, asdecimalmark=mdec, asdigitgroups=mgrps} return nullamt{acommodity=c, aquantity=sign (sign2 q), astyle=s, aprice=Nothing} -- An amount with commodity symbol on the right or no commodity symbol. @@ -824,7 +824,7 @@ simpleamountp mult = -- XXX amounts of this commodity in periodic transaction rules and auto posting rules ? #1461 let msuggestedStyle = mdecmarkStyle <|> mcommodityStyle (q,prec,mdec,mgrps) <- lift $ interpretNumber numRegion msuggestedStyle ambiguousRawNum mExponent - let s = amountstyle{ascommodityside=R, ascommodityspaced=commodityspaced, asprecision=prec, asdecimalmark=mdec, asdigitgroups=mgrps} + let s = amountstyle{ascommodityside=R, ascommodityspaced=commodityspaced, asprecision=Just prec, asdecimalmark=mdec, asdigitgroups=mgrps} return nullamt{acommodity=c, aquantity=sign q, astyle=s, aprice=Nothing} -- no symbol amount Nothing -> do @@ -840,8 +840,8 @@ simpleamountp mult = -- (unless it's a multiplier in an automated posting) defcs <- getDefaultCommodityAndStyle let (c,s) = case (mult, defcs) of - (False, Just (defc,defs)) -> (defc, defs{asprecision=max (asprecision defs) prec}) - _ -> ("", amountstyle{asprecision=prec, asdecimalmark=mdec, asdigitgroups=mgrps}) + (False, Just (defc,defs)) -> (defc, defs{asprecision=max (asprecision defs) (Just prec)}) + _ -> ("", amountstyle{asprecision=Just prec, asdecimalmark=mdec, asdigitgroups=mgrps}) return nullamt{acommodity=c, aquantity=sign q, astyle=s, aprice=Nothing} -- For reducing code duplication. Doesn't parse anything. Has the type @@ -1068,7 +1068,7 @@ disambiguateNumber msuggestedStyle (AmbiguousNumber grp1 sep grp2) = isValidDecimalBy c = \case AmountStyle{asdecimalmark = Just d} -> d == c AmountStyle{asdigitgroups = Just (DigitGroups g _)} -> g /= c - AmountStyle{asprecision = Precision 0} -> False + AmountStyle{asprecision = Just (Precision 0)} -> False _ -> True -- | Parse and interpret the structure of a number without external hints. @@ -1572,24 +1572,24 @@ tests_Common = testGroup "Common" [ nullamt{ acommodity="$" ,aquantity=10 -- need to test internal precision with roundTo ? I think not - ,astyle=amountstyle{asprecision=Precision 0, asdecimalmark=Nothing} + ,astyle=amountstyle{asprecision=Just $ Precision 0, asdecimalmark=Nothing} ,aprice=Just $ UnitPrice $ nullamt{ acommodity="€" ,aquantity=0.5 - ,astyle=amountstyle{asprecision=Precision 1, asdecimalmark=Just '.'} + ,astyle=amountstyle{asprecision=Just $ Precision 1, asdecimalmark=Just '.'} } } ,testCase "total price" $ assertParseEq amountp "$10 @@ €5" nullamt{ acommodity="$" ,aquantity=10 - ,astyle=amountstyle{asprecision=Precision 0, asdecimalmark=Nothing} + ,astyle=amountstyle{asprecision=Just $ Precision 0, asdecimalmark=Nothing} ,aprice=Just $ TotalPrice $ nullamt{ acommodity="€" ,aquantity=5 - ,astyle=amountstyle{asprecision=Precision 0, asdecimalmark=Nothing} + ,astyle=amountstyle{asprecision=Just $ Precision 0, asdecimalmark=Nothing} } } ,testCase "unit price, parenthesised" $ assertParse amountp "$10 (@) €0.5" diff --git a/hledger-lib/Hledger/Read/TimedotReader.hs b/hledger-lib/Hledger/Read/TimedotReader.hs index ba78f67cb..c8b870ae4 100644 --- a/hledger-lib/Hledger/Read/TimedotReader.hs +++ b/hledger-lib/Hledger/Read/TimedotReader.hs @@ -192,8 +192,8 @@ timedotentryp = do mcs <- getDefaultCommodityAndStyle let (c,s) = case mcs of - Just (defc,defs) -> (defc, defs{asprecision=max (asprecision defs) (Precision 2)}) - _ -> ("", amountstyle{asprecision=Precision 2}) + Just (defc,defs) -> (defc, defs{asprecision=max (asprecision defs) (Just $ Precision 2)}) + _ -> ("", amountstyle{asprecision=Just $ Precision 2}) -- lift $ traceparse' "timedotentryp end" return $ nullposting{paccount=a ,pamount=mixedAmount $ nullamt{acommodity=c, aquantity=hours, astyle=s} diff --git a/hledger-lib/Hledger/Reports/MultiBalanceReport.hs b/hledger-lib/Hledger/Reports/MultiBalanceReport.hs index c0a45c605..a023c9887 100644 --- a/hledger-lib/Hledger/Reports/MultiBalanceReport.hs +++ b/hledger-lib/Hledger/Reports/MultiBalanceReport.hs @@ -617,7 +617,8 @@ balanceReportTableAsText ReportOpts{..} = tests_MultiBalanceReport = testGroup "MultiBalanceReport" [ let - amt0 = Amount {acommodity="$", aquantity=0, aprice=Nothing, astyle=AmountStyle {ascommodityside = L, ascommodityspaced = False, asdigitgroups = Nothing, asdecimalmark = Just '.', asprecision = Precision 2}} + amt0 = Amount {acommodity="$", aquantity=0, aprice=Nothing, + astyle=AmountStyle {ascommodityside = L, ascommodityspaced = False, asdigitgroups = Nothing, asdecimalmark = Just '.', asprecision = Just $ Precision 2}} (rspec,journal) `gives` r = do let rspec' = rspec{_rsQuery=And [queryFromFlags $ _rsReportOpts rspec, _rsQuery rspec]} (eitems, etotal) = r