Skip to content

Commit

Permalink
imp:print: zero posting amounts are now shown with commodity & style
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
simonmichael committed Aug 27, 2023
1 parent 35c0fd6 commit ff730f7
Show file tree
Hide file tree
Showing 10 changed files with 95 additions and 47 deletions.
62 changes: 46 additions & 16 deletions hledger-lib/Hledger/Data/Amount.hs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ module Hledger.Data.Amount (
maAddAmounts,
amounts,
amountsRaw,
amountsPreservingZeros,
maCommodities,
filterMixedAmount,
filterMixedAmountByCommodity,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
--
Expand All @@ -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,
Expand Down Expand Up @@ -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.
Expand Down
5 changes: 4 additions & 1 deletion hledger-lib/Hledger/Data/Posting.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions hledger/test/amount-rendering.test
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions hledger/test/csv.test
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions hledger/test/journal/parse-errors.test
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion hledger/test/journal/precision.test
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions hledger/test/journal/valuation2.test
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions hledger/test/print/explicit.test
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ $ hledger -f - print
equity
$ hledger -f - print --explicit
2017-01-01
assets 0
equity 0
assets $0
equity $0

>= 0

Expand Down
40 changes: 27 additions & 13 deletions hledger/test/print/print.test
Original file line number Diff line number Diff line change
@@ -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

>=
13 changes: 7 additions & 6 deletions hledger/test/rewrite.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down

0 comments on commit ff730f7

Please sign in to comment.