feat:print: add --round option for more control of precisions (#2085)

This commit is contained in:
Simon Michael 2023-09-13 06:28:01 +01:00
parent c7bcdfcdcf
commit 5a72b9e9ea
7 changed files with 218 additions and 106 deletions

View File

@ -324,7 +324,7 @@ journalFinalise iopts@InputOpts{..} f txt pj = do
& journalAddFile (f, txt) -- save the main file's info & journalAddFile (f, txt) -- save the main file's info
& journalReverse -- convert all lists to the order they were parsed & journalReverse -- convert all lists to the order they were parsed
& journalAddAccountTypes -- build a map of all known account types & journalAddAccountTypes -- build a map of all known account types
& journalStyleAmounts -- Infer and apply commodity styles - should be done early & journalStyleAmounts -- Infer and apply commodity styles (but don't round) - should be done early
<&> journalAddForecast (verbose_tags_) (forecastPeriod iopts pj) -- Add forecast transactions if enabled <&> journalAddForecast (verbose_tags_) (forecastPeriod iopts pj) -- Add forecast transactions if enabled
<&> journalPostingsAddAccountTags -- Add account tags to postings, so they can be matched by auto postings. <&> journalPostingsAddAccountTags -- Add account tags to postings, so they can be matched by auto postings.
>>= (if auto_ && not (null $ jtxnmodifiers pj) >>= (if auto_ && not (null $ jtxnmodifiers pj)

View File

@ -16,7 +16,7 @@ module Hledger.Cli.Commands.Print (
where where
import Data.List (intersperse) import Data.List (intersperse, intercalate)
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Builder as TB import qualified Data.Text.Lazy.Builder as TB
@ -28,20 +28,32 @@ import Hledger.Read.CsvUtils (CSV, printCSV)
import Hledger.Cli.CliOptions import Hledger.Cli.CliOptions
import Hledger.Cli.Utils import Hledger.Cli.Utils
import System.Exit (exitFailure) import System.Exit (exitFailure)
import qualified Data.Map as M (map) import Safe (lastMay)
printmode = hledgerCommandMode printmode = hledgerCommandMode
$(embedFileRelative "Hledger/Cli/Commands/Print.txt") $(embedFileRelative "Hledger/Cli/Commands/Print.txt")
([let arg = "DESC" in ([flagNone ["explicit","x"] (setboolopt "explicit")
flagReq ["match","m"] (\s opts -> Right $ setopt "match" s opts) arg
("fuzzy search for one recent transaction with description closest to "++arg)
,flagNone ["explicit","x"] (setboolopt "explicit")
"show all amounts explicitly" "show all amounts explicitly"
,flagNone ["show-costs"] (setboolopt "show-costs") ,flagNone ["show-costs"] (setboolopt "show-costs")
"show transaction prices even with conversion postings" "show transaction prices even with conversion postings"
,flagReq ["round"] (\s opts -> Right $ setopt "round" s opts) "TYPE" $
intercalate "\n"
["how much rounding or padding should be done when displaying amounts ?"
,"none - show original decimal digits,"
," as in journal"
,"soft - just add or remove decimal zeros"
," to match precision (default)"
,"hard - round posting amounts to precision"
," (can unbalance transactions)"
,"all - also round cost amounts to precision"
," (can unbalance transactions)"
]
,flagNone ["new"] (setboolopt "new") ,flagNone ["new"] (setboolopt "new")
"show only newer-dated transactions added in each file since last run" "show only newer-dated transactions added in each file since last run"
,let arg = "DESC" in
flagReq ["match","m"] (\s opts -> Right $ setopt "match" s opts) arg
("fuzzy search for one recent transaction with description closest to "++arg)
,outputFormatFlag ["txt","csv","json","sql"] ,outputFormatFlag ["txt","csv","json","sql"]
,outputFileFlag ,outputFileFlag
]) ])
@ -49,6 +61,18 @@ printmode = hledgerCommandMode
hiddenflags hiddenflags
([], Just $ argsFlag "[QUERY]") ([], Just $ argsFlag "[QUERY]")
-- | Get the --round option's value, if any. Can fail with a parse error.
roundFromRawOpts :: RawOpts -> Maybe Rounding
roundFromRawOpts = lastMay . collectopts roundfromrawopt
where
roundfromrawopt (n,v)
| n=="round", v=="none" = Just NoRounding
| n=="round", v=="soft" = Just SoftRounding
| n=="round", v=="hard" = Just HardRounding
| n=="round", v=="all" = Just AllRounding
| n=="round" = error' $ "--round's value should be none, soft, hard or all; got: "++v
| otherwise = Nothing
-- | Print journal transactions in standard format. -- | Print journal transactions in standard format.
print' :: CliOpts -> Journal -> IO () print' :: CliOpts -> Journal -> IO ()
print' opts j = do print' opts j = do
@ -69,16 +93,22 @@ print' opts j = do
Nothing -> putStrLn "no matches found." >> exitFailure Nothing -> putStrLn "no matches found." >> exitFailure
printEntries :: CliOpts -> Journal -> IO () printEntries :: CliOpts -> Journal -> IO ()
printEntries opts@CliOpts{reportspec_=rspec} j = printEntries opts@CliOpts{rawopts_=rawopts, reportspec_=rspec} j =
writeOutputLazyText opts $ render $ entriesReport rspec j writeOutputLazyText opts $ render $ entriesReport rspec j
where where
stylesnorounding = M.map (amountStyleSetRounding NoRounding) $ journalCommodityStyles j -- print does user-specified rounding or (by default) no rounding, in all output formats
stylessoftrounding = M.map (amountStyleSetRounding SoftRounding) $ journalCommodityStyles j styles =
case roundFromRawOpts rawopts of
Nothing -> styles0
Just NoRounding -> styles0
Just r -> amountStylesSetRounding r styles0
where styles0 = journalCommodityStyles j
fmt = outputFormatFromOpts opts fmt = outputFormatFromOpts opts
render | fmt=="txt" = entriesReportAsText opts . styleAmounts stylesnorounding render | fmt=="txt" = entriesReportAsText opts . styleAmounts styles
| fmt=="csv" = printCSV . entriesReportAsCsv . styleAmounts stylessoftrounding | fmt=="csv" = printCSV . entriesReportAsCsv . styleAmounts styles
| fmt=="json" = toJsonText . styleAmounts stylessoftrounding | fmt=="json" = toJsonText . styleAmounts styles
| fmt=="sql" = entriesReportAsSql . styleAmounts stylessoftrounding | fmt=="sql" = entriesReportAsSql . styleAmounts styles
| otherwise = error' $ unsupportedOutputFormatError fmt -- PARTIAL: | otherwise = error' $ unsupportedOutputFormatError fmt -- PARTIAL:
entriesReportAsText :: CliOpts -> EntriesReport -> TL.Text entriesReportAsText :: CliOpts -> EntriesReport -> TL.Text

View File

@ -4,31 +4,19 @@ Show transaction journal entries, sorted by date.
_FLAGS _FLAGS
The print command displays full journal entries (transactions) from The print command displays full journal entries (transactions)
the journal file, sorted by date from the journal file, sorted by date
(or with `--date2`, by [secondary date](#secondary-dates)). (or with `--date2`, by [secondary date](#secondary-dates)).
Amounts are shown mostly normalised to
[commodity display style](#commodity-display-styles),
eg the placement of commodity symbols will be consistent.
All of their decimal places are shown, as in the original journal entry
(with one alteration: in some cases trailing zeroes are added.)
Amounts are shown right-aligned within each transaction (but not across all transactions).
Directives and inter-transaction comments are not shown, currently. Directives and inter-transaction comments are not shown, currently.
This means the print command is somewhat lossy, and if you are using it to This means the print command is somewhat lossy, and if you are using it to
reformat your journal you should take care to also copy over the directives reformat/regenerate your journal you should take care to also copy over
and file-level comments. the directives and inter-transaction comments.
Eg: Eg:
```shell ```shell
$ hledger print $ hledger print -f examples/sample.journal date:200806
2008/01/01 income
assets:bank:checking $1
income:salary $-1
2008/06/01 gift 2008/06/01 gift
assets:bank:checking $1 assets:bank:checking $1
income:gifts $-1 income:gifts $-1
@ -42,12 +30,55 @@ $ hledger print
expenses:supplies $1 expenses:supplies $1
assets:cash $-2 assets:cash $-2
2008/12/31 * pay off
liabilities:debts $1
assets:bank:checking $-1
``` ```
print's output is usually a valid [hledger journal](https://hledger.org/hledger.html), and you can process it again with a second hledger command. This can be useful for certain kinds of search, eg: ### print explicitness
Normally, whether posting amounts are implicit or explicit is preserved.
For example, when an amount is omitted in the journal, it will not appear in the output.
Similarly, if a conversion cost is implied but not written, it will not appear in the output.
You can use the `-x`/`--explicit` flag to force explicit display of all amounts and costs.
This can be useful for troubleshooting or for making your journal more readable and
robust against data entry errors.
`-x` is also implied by using any of `-B`,`-V`,`-X`,`--value`.
The `-x`/`--explicit` flag will cause any postings with a multi-commodity amount
(which can arise when a multi-commodity transaction has an implicit amount)
to be split into multiple single-commodity postings,
keeping the output parseable.
### print amount style
Amounts are shown right-aligned within each transaction
(but not aligned across all transactions; you can do that with ledger-mode in Emacs).
Amounts will be (mostly) normalised to their [commodity display style](#commodity-display-styles):
their symbol placement, decimal mark, and digit group marks will be made consistent.
By default, decimal digits are shown as they are written in the journal.
With the `--round` option, `print` will try increasingly hard to
display decimal digits according to the [commodity display styles](#commodity-display-style):
- `--round=none` show amounts with original precisions (default)
- `--round=soft` add/remove decimal zeros in amounts (except costs)
- `--round=hard` round amounts (except costs), possibly hiding significant digits
- `--round=all` round all amounts and costs
`soft` is good for non-lossy cleanup, formatting amounts more
consistently where it's safe to do so.
`hard` and `all` can cause `print` to show invalid unbalanced journal entries;
they may be useful eg for journal cleanup, with manual fixups where needed.
### print parseability
print's output is usually a valid [hledger journal](#journal),
and you can process it again with a second hledger command.
This can be useful for certain kinds of search
(though the same can be achieved with `expr:` queries now):
```shell ```shell
# Show running total of food expenses paid from cash. # Show running total of food expenses paid from cash.
@ -61,31 +92,24 @@ There are some situations where print's output can become unparseable:
- [Auto postings](#auto-postings) can generate postings with [too many missing amounts](https://github.com/simonmichael/hledger/issues/1276). - [Auto postings](#auto-postings) can generate postings with [too many missing amounts](https://github.com/simonmichael/hledger/issues/1276).
- [Account aliases can generate bad account names](#aliases-can-generate-bad-account-names). - [Account aliases can generate bad account names](#aliases-can-generate-bad-account-names).
Normally, the journal entry's explicit or implicit amount style is preserved.
For example, when an amount is omitted in the journal, it will not appear in the output.
Similarly, when a cost is implied but not written, it will not appear in the output.
You can use the `-x`/`--explicit` flag to make all amounts and costs explicit,
which can be useful for troubleshooting or for making your journal more readable and
robust against data entry errors.
`-x` is also implied by using any of `-B`,`-V`,`-X`,`--value`.
Note, `-x`/`--explicit` will cause postings with a multi-commodity amount ### print, other features
(these can arise when a multi-commodity transaction has an implicit amount)
to be split into multiple single-commodity postings,
keeping the output parseable.
With `-B`/`--cost`, amounts with [costs](https://hledger.org/hledger.html#costs) With `-B`/`--cost`, amounts with [costs](https://hledger.org/hledger.html#costs)
are converted to cost using that price. This can be used for troubleshooting. are shown converted to cost.
With `-m DESC`/`--match=DESC`, print does a fuzzy search for one recent transaction With `--new`, print shows only transactions it has not seen on a previous run.
This uses the same deduplication system as the [`import`](#import) command.
(See import's docs for details.)
With `-m DESC`/`--match=DESC`, print shows one recent transaction
whose description is most similar to DESC. whose description is most similar to DESC.
DESC should contain at least two characters. DESC should contain at least two characters.
If there is no similar-enough match, If there is no similar-enough match,
no transaction will be shown and the program exit code will be non-zero. no transaction will be shown and the program exit code will be non-zero.
With `--new`, hledger prints only transactions it has not seen on a previous run.
This uses the same deduplication system as the [`import`](#import) command. ### print output format
(See import's docs for details.)
This command also supports the This command also supports the
[output destination](hledger.html#output-destination) and [output destination](hledger.html#output-destination) and

View File

@ -14,7 +14,7 @@ $ mkdir -p b/c/d ; printf '2010/1/1\n (D) 1\n' >b/c/d/d.journal ; printf '2010
>= 0 >= 0
# 2. including other formats # 2. Including other formats.
< <
2016/1/1 2016/1/1
(x) 1 (x) 1

View File

@ -111,7 +111,7 @@ P 2000/1/1 $ €1.20
$ hledger -f- print -V $ hledger -f- print -V
2000-01-01 2000-01-01
(a) €120.00 = $100 (a) €120 = $100
>=0 >=0

View File

@ -25,7 +25,8 @@ $ hledger -f- reg --output-format=json
"ascommodityspaced": true, "ascommodityspaced": true,
"asdecimalmark": ".", "asdecimalmark": ".",
"asdigitgroups": null, "asdigitgroups": null,
"asprecision": 1 "asprecision": 1,
"asrounding": "HardRounding"
} }
} }
], ],
@ -53,7 +54,8 @@ $ hledger -f- reg --output-format=json
"ascommodityspaced": true, "ascommodityspaced": true,
"asdecimalmark": ".", "asdecimalmark": ".",
"asdigitgroups": null, "asdigitgroups": null,
"asprecision": 1 "asprecision": 1,
"asrounding": "HardRounding"
} }
} }
] ]
@ -82,7 +84,8 @@ $ hledger -f- bal --output-format=json
"ascommodityspaced": true, "ascommodityspaced": true,
"asdecimalmark": ".", "asdecimalmark": ".",
"asdigitgroups": null, "asdigitgroups": null,
"asprecision": 1 "asprecision": 1,
"asrounding": "HardRounding"
} }
} }
] ]
@ -102,7 +105,8 @@ $ hledger -f- bal --output-format=json
"ascommodityspaced": true, "ascommodityspaced": true,
"asdecimalmark": ".", "asdecimalmark": ".",
"asdigitgroups": null, "asdigitgroups": null,
"asprecision": 1 "asprecision": 1,
"asrounding": "HardRounding"
} }
} }
] ]

View File

@ -1,41 +1,95 @@
# print amount styling tests # print amount styling tests
# #
# The amounts are: # Here's an overview of historical behaviour.
# amt - the posting amount # See the tests below for examples.
# cost - the posting amount's cost
# bal - the balance assertion amount
# balcost - the balance assertion amount's cost
# #
# Styling includes: # print shows four kinds of amount:
# basic - everything except precision # amt - posting amount
# prec - precision (number of decimal places) # cost - posting amount's cost
# bal - balance assertion/assignment amount
# balcost - balance assertion/assignment amount's cost
# #
# Historical behaviour: # Which amounts does print do basic styling (eg symbol placement) on ?
# | hledger | amt basic | cost basic | bal basic | balcost basic | amt prec | cost prec | bal prec | balcost prec |
# | 1.14 | Y | N | N | N | Y | N | N | N |
# | 1.22 | Y | N | Y | N | N | N | N | N |
# | 1.30 | Y | Y | Y | N | N | N | N | N |
# | 1.31 | Y | Y | Y | Y | N | N | N | N |
# #
# In the following, # | hledger | amt | cost | bal | balcost |
# basic styling will move the commodity symbol to the left # |-----------|-----|------|-----|---------|
# precision styling will hide the decimal place # | 1.1-1.14 | Y | N | N | N |
# | 1.15-1.22 | Y | N | Y | N |
# | 1.23-1.30 | Y | Y | Y | N |
# | | | | | |
# | 1.31- | Y | Y | Y | Y |
#
# Which kind of rounding does print do on each amount ?
#
# | hledger | amt | cost | bal | balcost |
# |---------------------|------|------|------|---------|
# | 1.0-1.20 | hard | none | none | none |
# | 1.21-1.30 | soft | none | none | none |
# | 1.31 | none | none | none | none |
# | | | | | |
# | 1.31.1 | none | none | none | none |
# | 1.31.1 --round=soft | soft | none | soft | none |
# | 1.31.1 --round=hard | hard | none | hard | none |
# | 1.31.1 --round=all | hard | hard | hard | hard |
# 1. print styles all amounts, but leaves all precisions unchanged, even with -c.
# Four print style tests. In these, basic styling is applied
# to all amounts (the commodity symbol moves to the left),
# and precision styling is applied as described below.
< <
commodity A 1000. ; A and B styles
commodity B 1000. commodity A1000.00
commodity B1000.00
; a amounts have 1 significant digit
; b amounts have 1 significant digit and 2 zeros
; c amounts have 3 significant digits
2023-01-01 2023-01-01
(a) 1.2 A @ 3.4 B = 1.2 A @ 3.4 B (a) 0.1 A @ 0.1 B = 0.1 A @ 0.1 B
(b) 0.100 A @ 0.100 B = 0.100 A @ 0.100 B
(c) 0.123 A @ 0.123 B = 0.123 A @ 0.123 B
$ hledger -f- print -c A1000.00 -c B1000.00 # 1. By default, print shows all amounts with original precisions
# (like 1.31)
$ hledger -f- print
2023-01-01 2023-01-01
(a) A1.2 @ B3.4 = A1.2 @ B3.4 (a) A0.1 @ B0.1 = A0.1 @ B0.1
(b) A0.100 @ B0.100 = A0.100 @ B0.100
(c) A0.123 @ B0.123 = A0.123 @ B0.123
>= >=
# 2. Precisions are also preserved when there's an implicit conversion (#2079). # 2. With --round=soft, print adds/removes zeros in non-cost amounts
# (like 1.30 but more thorough, also affects balance assertion amounts,
# also does basic styling of balance assertion costs)
$ hledger -f- print --round=soft
2023-01-01
(a) A0.10 @ B0.1 = A0.10 @ B0.1
(b) A0.10 @ B0.100 = A0.10 @ B0.100
(c) A0.123 @ B0.123 = A0.123 @ B0.123
>=
# 3. With --round=hard, print rounds non-cost amounts.
$ hledger -f- print --round=hard
2023-01-01
(a) A0.10 @ B0.1 = A0.10 @ B0.1
(b) A0.10 @ B0.100 = A0.10 @ B0.100
(c) A0.12 @ B0.123 = A0.12 @ B0.123
>=
# 4. with --round=all, print rounds all amounts.
$ hledger -f- print --round=all
2023-01-01
(a) A0.10 @ B0.10 = A0.10 @ B0.10
(b) A0.10 @ B0.10 = A0.10 @ B0.10
(c) A0.12 @ B0.12 = A0.12 @ B0.12
>=
# 5. print also preserves precisions when there's an implicit conversion
# (unlike 1.30, #2079).
< <
commodity A 1000. commodity A 1000.
@ -52,29 +106,7 @@ $ hledger -f- print
>= >=
# 6. When showing digit group marks, print always shows a decimal mark as well,
# Maybe later:
# # 0. With print, -c has extra power: it can increase all amount precisions.
# $ hledger -f- print -c A1000.00 -c B1000.00
# 2023-01-01
# (a) A1.20 @ B3.40 = A1.20 @ B3.40
#
# >=
#
# # 0. And -c can decrease trailing decimal zeros. But not significant decimal digits
# (because that would change transactions).
# <
# 2023-01-01
# (a) 1.2340 A @ 3.4560 B = 1.234 A @ 3.456 B
#
# $ hledger -f- print -c A1000. -c B1000.
# 2023-01-01
# (a) A1.234 @ B3.456 = A1.234 @ B3.456
#
# >=
# 3. When showing digit group marks, print always shows a decimal mark as well,
# even when no decimal digits are shown. # even when no decimal digits are shown.
< <
decimal-mark . decimal-mark .
@ -87,3 +119,25 @@ $ hledger -f- print
>= >=
# 7. print shows zeros with a commodity symbol and decimal digits when possible.
# This also means that "multi-commodity zeros" are shown more verbosely.
<
2023-01-01
a A 0.00
b B 0.00
c
2023-01-02
d
$ hledger -f- print -x
2023-01-01
a A 0.00
b B 0.00
c A 0.00
c B 0.00
2023-01-02
d 0
>=