From 1744021986009f97e15f565955774b4c0e93c69f Mon Sep 17 00:00:00 2001 From: Simon Michael Date: Wed, 22 Nov 2023 14:22:51 -1000 Subject: [PATCH] fix: non-print-like reports no longer add trailing decimal marks (fix #2115) That 1.31 change was advertised as being for the print command only, but it affected all commands. Now it affects only print and other "print-like" commands (ie all commands that show whole journal entries that we might want to re-parse). Also three classes of hledger output, and how they modify the commodity display styles' digit group marks and decimal marks to suit different consumers, have been identified and documented (under REPORTING CONCEPTS). --- hledger-lib/Hledger/Data/Amount.hs | 40 ++++++++++++++++------------ hledger-lib/Hledger/Data/Posting.hs | 5 +++- hledger/hledger.m4.md | 26 ++++++++++++++++++ hledger/test/journal/precision.test | 41 +++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+), 18 deletions(-) diff --git a/hledger-lib/Hledger/Data/Amount.hs b/hledger-lib/Hledger/Data/Amount.hs index fc521ad21..abd545b89 100644 --- a/hledger-lib/Hledger/Data/Amount.hs +++ b/hledger-lib/Hledger/Data/Amount.hs @@ -221,7 +221,9 @@ quoteCommoditySymbolIfNeeded s data AmountDisplayOpts = AmountDisplayOpts { displayPrice :: Bool -- ^ Whether to display the Price of an Amount. , displayZeroCommodity :: Bool -- ^ If the Amount rounds to 0, whether to display its commodity string. - , displayThousandsSep :: Bool -- ^ Whether to display thousands separators. + , displayThousandsSep :: Bool -- ^ Whether to display digit group marks (eg thousands separators) + , displayAddDecimalMark :: Bool -- ^ Whether to add a trailing decimal mark when there are no decimal digits + -- and there are digit group marks, to disambiguate , displayColour :: Bool -- ^ Whether to colourise negative Amounts. , displayOneLine :: Bool -- ^ Whether to display on one line. , displayMinWidth :: Maybe Int -- ^ Minimum width to pad to @@ -240,6 +242,7 @@ noColour = AmountDisplayOpts { displayPrice = True , displayColour = False , displayZeroCommodity = False , displayThousandsSep = True + , displayAddDecimalMark = False , displayOneLine = False , displayMinWidth = Just 0 , displayMaxWidth = Nothing @@ -645,22 +648,25 @@ showAmount = wbUnpack . showAmountB noColour -- showAmountB :: AmountDisplayOpts -> Amount -> WideBuilder showAmountB _ Amount{acommodity="AUTO"} = mempty -showAmountB opts a@Amount{astyle=style} = +showAmountB + AmountDisplayOpts{displayPrice, displayColour, displayZeroCommodity, + displayThousandsSep, displayAddDecimalMark, displayOrder} + a@Amount{astyle=style} = color $ case ascommodityside style of 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}} + color = if displayColour && isNegativeAmount a then colorB Dull Red else id + quantity = showAmountQuantity displayAddDecimalMark $ + if displayThousandsSep then a else a{astyle=(astyle a){asdigitgroups=Nothing}} (quantity', comm) - | amountLooksZero a && not (displayZeroCommodity opts) = (WideBuilder (TB.singleton '0') 1, "") + | amountLooksZero a && not displayZeroCommodity = (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 - price = if displayPrice opts then showAmountPrice a else mempty + showC l r = if isJust displayOrder then mempty else l <> r + price = if displayPrice then showAmountPrice a else mempty -- | Colour version. For a negative amount, adds ANSI codes to change the colour, -- currently to hard-coded red. @@ -691,10 +697,10 @@ showAmountDebug Amount{..} = -- | Get a Text Builder for the string representation of the number part of of an amount, -- using the display settings from its commodity. Also returns the width of the number. --- A special case: if it is showing digit group separators but no decimal places, --- show a decimal mark (with nothing after it) to make it easier to parse correctly. -showamountquantity :: Amount -> WideBuilder -showamountquantity amt@Amount{astyle=AmountStyle{asdecimalmark=mdec, asdigitgroups=mgrps}} = +-- With a true first argument, if there are no decimal digits but there are digit group separators, +-- it shows the amount with a trailing decimal mark to help disambiguate it for parsing. +showAmountQuantity :: Bool -> Amount -> WideBuilder +showAmountQuantity disambiguate amt@Amount{astyle=AmountStyle{asdecimalmark=mdec, asdigitgroups=mgrps}} = signB <> intB <> fracB where Decimal decplaces mantissa = amountRoundedQuantity amt @@ -706,13 +712,13 @@ showamountquantity amt@Amount{astyle=AmountStyle{asdecimalmark=mdec, asdigitgrou (intPart, fracPart) = T.splitAt intLen numtxtwithzero intB = applyDigitGroupStyle mgrps intLen $ if decplaces == 0 then numtxt else intPart signB = if mantissa < 0 then WideBuilder (TB.singleton '-') 1 else mempty - fracB = if decplaces > 0 || isshowingdigitgroupseparator + fracB = if decplaces > 0 || (isshowingdigitgroupseparator && disambiguate) then WideBuilder (TB.singleton dec <> TB.fromText fracPart) (1 + fromIntegral decplaces) else mempty - - isshowingdigitgroupseparator = case mgrps of - Just (DigitGroups _ (rightmostgrplen:_)) -> intLen > fromIntegral rightmostgrplen - _ -> False + where + isshowingdigitgroupseparator = case mgrps of + Just (DigitGroups _ (rightmostgrplen:_)) -> intLen > fromIntegral rightmostgrplen + _ -> False -- | Given an integer as text, and its length, apply the given DigitGroupStyle, -- inserting digit group separators between digit groups where appropriate. diff --git a/hledger-lib/Hledger/Data/Posting.hs b/hledger-lib/Hledger/Data/Posting.hs index 7ee8ca0ac..6b5f0513d 100644 --- a/hledger-lib/Hledger/Data/Posting.hs +++ b/hledger-lib/Hledger/Data/Posting.hs @@ -287,7 +287,10 @@ postingAsLines elideamount onelineamounts acctwidth amtwidth p = -- amtwidth at all. shownAmounts | elideamount = [mempty] - | otherwise = showMixedAmountLinesB noColour{displayZeroCommodity=True, displayOneLine=onelineamounts} $ pamount p + | otherwise = showMixedAmountLinesB displayopts $ pamount p + where displayopts = noColour{ + displayZeroCommodity=True, displayAddDecimalMark=True, displayOneLine=onelineamounts + } thisamtwidth = maximumBound 0 $ map wbWidth shownAmounts -- when there is a balance assertion, show it only on the last posting line diff --git a/hledger/hledger.m4.md b/hledger/hledger.m4.md index f1f443568..2fd1bb96a 100644 --- a/hledger/hledger.m4.md +++ b/hledger/hledger.m4.md @@ -4340,6 +4340,32 @@ file. # PART 3: REPORTING CONCEPTS + +# Amount formatting + +When displaying amounts, digit group marks and decimal marks are +handled a little differently depending on the report and output format +and intended consumer. hledger output falls into three rough categories: + +**1. "hledger-readable output" should be readable by hledger and by humans** + - produced by reports that show full journal entries: `print`, `import`, `close`, `rewrite`.. + - shows amounts with their original journal precisions, which may not be consistent + - adds a trailing decimal mark when needed to disambiguate [ambiguous amounts](decimal-marks-digit-group-marks) + (amounts with one digit group mark and no decimal digits) + - can be parsed reliably + +**2. "human-readable output" - usually for humans** + - produced by all other reports + - shows amounts with standard display precisions, which will be consistent within each commodity + - can show ambiguous amounts + - can be parsed reliably in the context of a known report (because of consistent style) + +**3. "machine-readable output" - usually for other software** + - produced by all reports when an output format like `csv`/`tsv`/`json`/`sql` is selected + - shows no digit group marks + - shows a period decimal mark (.) when there are decimal digits + - can be parsed reliably + # Time periods diff --git a/hledger/test/journal/precision.test b/hledger/test/journal/precision.test index d30c3788f..f6bb1375a 100644 --- a/hledger/test/journal/precision.test +++ b/hledger/test/journal/precision.test @@ -147,3 +147,44 @@ $ hledger -f- print --explicit $ hledger -f- reg 2023-01-01 (a) 1.0 A 1.0 A 2023-01-02 (a) 1.2 A 2.2 A + +# ** 10. print-like reports add a trailing decimal mark, when amounts have digit group marks but no decimal digits. +< +commodity 1.000, JPY + +2023-01-01 + (a) 1 JPY + +2023-01-02 + (b) 1,2 JPY + +2023-01-03 + (c) 1.000 JPY + +$ hledger -f - print +2023-01-01 + (a) 1 JPY + +2023-01-02 + (b) 1,2 JPY + +2023-01-03 + (c) 1.000, JPY + +>= + +# ** 11. Non-print-like reports show all amounts with consistent display precision +$ hledger -f - bal + 1 JPY a + 1 JPY b + 1.000 JPY c +-------------------- + 1.002 JPY + +# ** 12. csv, json and other machine-readable formats show all amounts without digit groups and with period decimal marks. +$ hledger -f - bal -O csv +"account","balance" +"a","1 JPY" +"b","1 JPY" +"c","1000 JPY" +"total","1002 JPY"