From ff730f775ba0cc6fc855a9767268c5094ddca1fd Mon Sep 17 00:00:00 2001 From: Simon Michael Date: Thu, 24 Aug 2023 14:57:19 +0100 Subject: [PATCH] imp:print: zero posting amounts are now shown with commodity & style print now shows zero posting amounts with their original commodity symbol and the corresponding style (instead of stripping the symbol). If an inferred amount has multiple zeroes in different commodities, a posting is displayed for each of these. Possible breaking changes: showMixedAmountLinesB, showAmountB, showAmountPrice now preserve commodityful zeroes when rendering. This is intended to improve print output, but it seems possible it might also affect balance and register reports, though our tests show no change in those. --- hledger-lib/Hledger/Data/Amount.hs | 62 +++++++++++++++++++------- hledger-lib/Hledger/Data/Posting.hs | 5 ++- hledger/test/amount-rendering.test | 4 +- hledger/test/csv.test | 4 +- hledger/test/journal/parse-errors.test | 4 +- hledger/test/journal/precision.test | 2 +- hledger/test/journal/valuation2.test | 4 +- hledger/test/print/explicit.test | 4 +- hledger/test/print/print.test | 40 +++++++++++------ hledger/test/rewrite.test | 13 +++--- 10 files changed, 95 insertions(+), 47 deletions(-) diff --git a/hledger-lib/Hledger/Data/Amount.hs b/hledger-lib/Hledger/Data/Amount.hs index b9b44059f24..775c06bca62 100644 --- a/hledger-lib/Hledger/Data/Amount.hs +++ b/hledger-lib/Hledger/Data/Amount.hs @@ -102,6 +102,7 @@ module Hledger.Data.Amount ( maAddAmounts, amounts, amountsRaw, + amountsPreservingZeros, maCommodities, filterMixedAmount, filterMixedAmountByCommodity, @@ -170,6 +171,7 @@ import Hledger.Data.Types import Hledger.Utils (colorB, numDigitsInt) import Hledger.Utils.Text (textQuoteIfNeeded) import Text.WideString (WideBuilder(..), wbFromText, wbToText, wbUnpack) +import Data.Functor ((<&>)) -- A 'Commodity' is a symbol representing a currency or some other kind of @@ -208,10 +210,10 @@ data AmountDisplayOpts = AmountDisplayOpts , displayOrder :: Maybe [CommoditySymbol] } deriving (Show) --- | Display Amount and MixedAmount with no colour. +-- | By default, display Amount and MixedAmount using @noColour@ amount display options. instance Default AmountDisplayOpts where def = noColour --- | Display Amount and MixedAmount with no colour. +-- | Display amounts without colour, and with various other defaults. noColour :: AmountDisplayOpts noColour = AmountDisplayOpts { displayPrice = True , displayColour = False @@ -406,8 +408,8 @@ amountStripPrices a = a{aprice=Nothing} showAmountPrice :: Amount -> WideBuilder showAmountPrice amt = case aprice amt of Nothing -> mempty - Just (UnitPrice pa) -> WideBuilder (TB.fromString " @ ") 3 <> showAmountB noColour pa - Just (TotalPrice pa) -> WideBuilder (TB.fromString " @@ ") 4 <> showAmountB noColour (sign pa) + Just (UnitPrice pa) -> WideBuilder (TB.fromString " @ ") 3 <> showAmountB noColour{displayZeroCommodity=True} pa + Just (TotalPrice pa) -> WideBuilder (TB.fromString " @@ ") 4 <> showAmountB noColour{displayZeroCommodity=True} (sign pa) where sign = if aquantity amt < 0 then negate else id showAmountPriceDebug :: Maybe AmountPrice -> String @@ -460,14 +462,16 @@ showAmountB :: AmountDisplayOpts -> Amount -> WideBuilder showAmountB _ Amount{acommodity="AUTO"} = mempty showAmountB opts a@Amount{astyle=style} = color $ case ascommodityside style of - L -> showC (wbFromText c) space <> quantity' <> price - R -> quantity' <> showC space (wbFromText c) <> price + L -> showC (wbFromText comm) space <> quantity' <> price + R -> quantity' <> showC space (wbFromText comm) <> price where color = if displayColour opts && isNegativeAmount a then colorB Dull Red else id - quantity = showamountquantity $ if displayThousandsSep opts then a else a{astyle=(astyle a){asdigitgroups=Nothing}} - (quantity',c) | amountLooksZero a && not (displayZeroCommodity opts) = (WideBuilder (TB.singleton '0') 1,"") - | otherwise = (quantity, quoteCommoditySymbolIfNeeded $ acommodity a) - space = if not (T.null c) && ascommodityspaced style then WideBuilder (TB.singleton ' ') 1 else mempty + quantity = showamountquantity $ + if displayThousandsSep opts then a else a{astyle=(astyle a){asdigitgroups=Nothing}} + (quantity', comm) + | amountLooksZero a && not (displayZeroCommodity opts) = (WideBuilder (TB.singleton '0') 1, "") + | otherwise = (quantity, quoteCommoditySymbolIfNeeded $ acommodity a) + space = if not (T.null comm) && ascommodityspaced style then WideBuilder (TB.singleton ' ') 1 else mempty -- concatenate these texts, -- or return the empty text if there's a commodity display order. XXX why ? showC l r = if isJust (displayOrder opts) then mempty else l <> r @@ -672,7 +676,8 @@ maIsZero = mixedAmountIsZero maIsNonZero :: MixedAmount -> Bool maIsNonZero = not . mixedAmountIsZero --- | Get a mixed amount's component amounts. +-- | Get a mixed amount's component amounts, with some cleanups. +-- The following descriptions are old and possibly wrong: -- -- * amounts in the same commodity are combined unless they have different prices or total prices -- @@ -686,13 +691,37 @@ maIsNonZero = not . mixedAmountIsZero -- amounts :: MixedAmount -> [Amount] amounts (Mixed ma) - | isMissingMixedAmount (Mixed ma) = [missingamt] -- missingamt should always be alone, but detect it even if not + | isMissingMixedAmount (Mixed ma) = [missingamt] | M.null nonzeros = [newzero] | otherwise = toList nonzeros where newzero = fromMaybe nullamt $ find (not . T.null . acommodity) zeros (zeros, nonzeros) = M.partition amountIsZero ma +-- | Get a mixed amount's component amounts, with some cleanups. +-- This is a new version of @amounts@, with updated descriptions +-- and optimised for @print@ to show commodityful zeros. +-- +-- * If it contains the "missing amount" marker, only that is returned +-- (discarding any additional amounts). +-- +-- * Or if it contains any non-zero amounts, only those are returned +-- (discarding any zeroes). +-- +-- * Or if it contains any zero amounts (possibly more than one, +-- possibly in different commodities), all of those are returned. +-- +-- * Otherwise the null amount is returned. +-- +amountsPreservingZeros :: MixedAmount -> [Amount] +amountsPreservingZeros (Mixed ma) + | isMissingMixedAmount (Mixed ma) = [missingamt] + | not $ M.null nonzeros = toList nonzeros + | not $ M.null zeros = toList zeros + | otherwise = [nullamt] + where + (zeros, nonzeros) = M.partition amountIsZero ma + -- | Get a mixed amount's component amounts without normalising zero and missing -- amounts. This is used for JSON serialisation, so the order is important. In -- particular, we want the Amounts given in the order of the MixedAmountKeys, @@ -913,11 +942,12 @@ showMixedAmountOneLineB opts@AmountDisplayOpts{displayMaxWidth=mmax,displayMinWi -- Add the elision strings (if any) to each amount withElided = zipWith (\n2 amt -> (amt, elisionDisplay Nothing (wbWidth sep) n2 amt)) [n-1,n-2..0] --- Get a mixed amount's component amounts with a bit of cleanup (like @amounts@), --- and if a commodity display order is provided, sort them according to that. +-- Get a mixed amount's component amounts with a bit of cleanup, +-- optionally preserving multiple zeros in different commodities, +-- optionally sorting them according to a commodity display order. orderedAmounts :: AmountDisplayOpts -> MixedAmount -> [Amount] -orderedAmounts AmountDisplayOpts{displayOrder=mcommodityorder} = - amounts +orderedAmounts AmountDisplayOpts{displayZeroCommodity=preservezeros, displayOrder=mcommodityorder} = + if preservezeros then amountsPreservingZeros else amounts <&> maybe id (mapM findfirst) mcommodityorder -- maybe sort them (somehow..) where -- Find the first amount with the given commodity, otherwise a null amount in that commodity. diff --git a/hledger-lib/Hledger/Data/Posting.hs b/hledger-lib/Hledger/Data/Posting.hs index 352f5656cbb..5f148ec2587 100644 --- a/hledger-lib/Hledger/Data/Posting.hs +++ b/hledger-lib/Hledger/Data/Posting.hs @@ -220,6 +220,9 @@ postingsAsLines onelineamounts ps = concatMap first3 linesWithWidths -- Or if onelineamounts is true, such amounts are shown on one line, comma-separated -- (and the output will not be valid journal syntax). -- +-- If an amount is zero, any commodity symbol attached to it is shown +-- (and the corresponding commodity display style is used). +-- -- By default, 4 spaces (2 if there's a status flag) are shown between -- account name and start of amount area, which is typically 12 chars wide -- and contains a right-aligned amount (so 10-12 visible spaces between @@ -262,7 +265,7 @@ postingAsLines elideamount onelineamounts acctwidth amtwidth p = -- amtwidth at all. shownAmounts | elideamount = [mempty] - | otherwise = showMixedAmountLinesB noColour{displayOneLine=onelineamounts} $ pamount p + | otherwise = showMixedAmountLinesB noColour{displayZeroCommodity=True, displayOneLine=onelineamounts} $ pamount p thisamtwidth = maximumBound 0 $ map wbWidth shownAmounts -- when there is a balance assertion, show it only on the last posting line diff --git a/hledger/test/amount-rendering.test b/hledger/test/amount-rendering.test index deaf3393c26..2142f2c3edb 100644 --- a/hledger/test/amount-rendering.test +++ b/hledger/test/amount-rendering.test @@ -46,8 +46,8 @@ $ hledger -f - balance -N b $ hledger -f- print --explicit --empty 2010-03-01 x - a 0 @ 3EUR - b 0 + a $0.00 @ 3EUR + b 0EUR >= 0 diff --git a/hledger/test/csv.test b/hledger/test/csv.test index feb9fcf7877..58948dea474 100644 --- a/hledger/test/csv.test +++ b/hledger/test/csv.test @@ -1006,8 +1006,8 @@ account1 assets:bank:checking $ ./csvtest.sh 2020-01-21 Client card point of sale fee - assets:bank:checking 0 = $1068.94 - expenses:unknown 0 + assets:bank:checking $0 = $1068.94 + expenses:unknown $0 >=0 diff --git a/hledger/test/journal/parse-errors.test b/hledger/test/journal/parse-errors.test index c0e4aa1ab17..d75c44f70d5 100644 --- a/hledger/test/journal/parse-errors.test +++ b/hledger/test/journal/parse-errors.test @@ -96,8 +96,8 @@ $ hledger -f- print b B 0 $ hledger -f- print 2020-01-01 - a 0 - b 0 + a A 0 + b B 0 >=0 diff --git a/hledger/test/journal/precision.test b/hledger/test/journal/precision.test index c04df95b7bd..54b45f777ce 100644 --- a/hledger/test/journal/precision.test +++ b/hledger/test/journal/precision.test @@ -23,7 +23,7 @@ $ hledger -f - print a $ hledger -f - print --explicit 2010-01-01 - a 0 + a $0.00 a 1C @ $1.0049 a $-1.0049 diff --git a/hledger/test/journal/valuation2.test b/hledger/test/journal/valuation2.test index 82661e16f14..526723543b3 100644 --- a/hledger/test/journal/valuation2.test +++ b/hledger/test/journal/valuation2.test @@ -62,8 +62,8 @@ $ hledger -f- print -x --value=now,Z # and sign are not shown either. $ hledger -f- print -x --value=now,C 2019-06-01 - a 0 - b 0 + a C0 + b C0 >= # # There's nothing setting C display style, so the default style is used, diff --git a/hledger/test/print/explicit.test b/hledger/test/print/explicit.test index 59f7023410f..123953e5d51 100644 --- a/hledger/test/print/explicit.test +++ b/hledger/test/print/explicit.test @@ -92,8 +92,8 @@ $ hledger -f - print equity $ hledger -f - print --explicit 2017-01-01 - assets 0 - equity 0 + assets $0 + equity $0 >= 0 diff --git a/hledger/test/print/print.test b/hledger/test/print/print.test index 796cf8e6de2..c5cd8f60d39 100644 --- a/hledger/test/print/print.test +++ b/hledger/test/print/print.test @@ -1,13 +1,27 @@ -# test -B and #551 -#hledger -f- print -B -#<<< -#2009/1/1 -# assets:foreign currency €100 -# assets:cash $-135 -#>>> -#2009/01/01 -# assets:foreign currency $135 -# assets:cash $-135 -# -#>>>2 -#>>>= 0 +# 1. print preserves the commodity symbol of zero amounts. +< +2023-01-01 + (a) 0 A @ 0 B == 0 A @ 0 B + +$ hledger -f- print +2023-01-01 + (a) 0 A @ 0 B == 0 A @ 0 B + +>= + +# 2. The inferred balancing amount for zeros in multiple commodities +# is preserved and shown accurately, with a posting for each commodity. +< +2023-01-01 + a 0 A + b 0 B + z + +$ hledger -f- print -x +2023-01-01 + a 0 A + b 0 B + z 0 A + z 0 B + +>= diff --git a/hledger/test/rewrite.test b/hledger/test/rewrite.test index a8c7832b7b1..35ebff65632 100644 --- a/hledger/test/rewrite.test +++ b/hledger/test/rewrite.test @@ -178,6 +178,7 @@ $ hledger rewrite -f- assets:bank and 'amt:<0' --add-posting 'expenses:fee $5' ; but relative order matters to refer-rewritten transactions = ^expenses not:housing not:grocery not:food (budget:misc) *-1 + $ hledger rewrite -f- date:2017/1 --add-posting 'Here comes Santa $0' --verbose-tags 2016-12-31 ; modified: expenses:housing $600.00 @@ -187,24 +188,24 @@ $ hledger rewrite -f- date:2017/1 --add-posting 'Here comes Santa $0' --verbos 2017-01-01 ; modified: expenses:food $20.00 (budget:food) $-20.00 ; generated-posting: = ^expenses:grocery ^expenses:food - Here comes Santa 0 ; generated-posting: = date:2017/1 + Here comes Santa $0 ; generated-posting: = date:2017/1 expenses:leisure $15.00 (budget:misc) $-15.00 ; generated-posting: = ^expenses not:housing not:grocery not:food - Here comes Santa 0 ; generated-posting: = date:2017/1 + Here comes Santa $0 ; generated-posting: = date:2017/1 expenses:grocery $30.00 (budget:food) $-30.00 ; generated-posting: = ^expenses:grocery ^expenses:food - Here comes Santa 0 ; generated-posting: = date:2017/1 + Here comes Santa $0 ; generated-posting: = date:2017/1 assets:cash - Here comes Santa 0 ; generated-posting: = date:2017/1 + Here comes Santa $0 ; generated-posting: = date:2017/1 2017-01-02 ; modified: assets:cash $200.00 - Here comes Santa 0 ; generated-posting: = date:2017/1 + Here comes Santa $0 ; generated-posting: = date:2017/1 assets:bank assets:bank $-1.60 ; generated-posting: = ^assets:bank$ date:2017/1 amt:<0 expenses:fee $1.60 ; cash withdraw fee, generated-posting: = ^assets:bank$ date:2017/1 amt:<0 (budget:misc) $-1.60 ; generated-posting: = ^expenses not:housing not:grocery not:food - Here comes Santa 0 ; generated-posting: = date:2017/1 + Here comes Santa $0 ; generated-posting: = date:2017/1 2017-02-01 assets:cash $100.00