From d73d473cb7076fb5f004b72335e4691c3daa9c4c Mon Sep 17 00:00:00 2001 From: Arthur Cinader <700572+acinader@users.noreply.github.com> Date: Mon, 5 Oct 2026 16:14:19 -0700 Subject: [PATCH 1/2] imp: cli: html: register links say how the report converted amounts With --base-url, a report's register links now carry a value parameter naming the report's valuation, mirroring --value's WHEN[,COMM] syntax: value=cost for -B, value=end for -V, value=end,COMM for -X COMM, and then, now, or a date for the other valuations. A register converting amounts the same way ends on the figure that was clicked. Consumers that do not know it ignore it. Cost combined with a valuation emits none, and such links open the register unconverted, as before. AI usage: Claude (Opus 5.5 and Fable 5), ~3k output tokens. --- hledger/Hledger/Cli/Anchor.hs | 35 ++++++++++++++++--- .../Hledger/Cli/Commands/Balance/Internal.hs | 9 ++--- hledger/test/balance/base-url.test | 19 ++++++++++ 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/hledger/Hledger/Cli/Anchor.hs b/hledger/Hledger/Cli/Anchor.hs index 37e72ef2277..51e12689c29 100644 --- a/hledger/Hledger/Cli/Anchor.hs +++ b/hledger/Hledger/Cli/Anchor.hs @@ -10,6 +10,7 @@ module Hledger.Cli.Anchor ( LinkOpts(..), defaultLinkOpts, linkParams, + valueParam, composeAnchor, composeAnchorWith, accountTerm, @@ -42,6 +43,7 @@ import Hledger.Utils.IO (error') import Hledger.Utils.Text (quoteIfSpaced) import Hledger.Data.Dates (showDateSpan, showDateSpanFull, showDateSpanForQuery, showDate, nulldatespan) import Hledger.Data.Types (DateSpan(..)) +import Hledger.Data.Valuation (ConversionOp(..), ValuationType(..)) import Hledger.Reports.ReportOptions (PeriodTitles(..), BalanceAccumulation(..)) @@ -57,17 +59,42 @@ data LinkOpts = LinkOpts { -- ^ Does the report use secondary dates ? Then so must the register. loIncludesSubs :: Bool, -- ^ Does the row's figure include subaccounts (inacct:) or not (inacctonly:) ? - loNegated :: Bool + loNegated :: Bool, -- ^ Are the figures shown with the opposite sign to the register's ? + loValue :: Maybe Text + -- ^ The register's value parameter, for figures converted to cost + -- or to market value (see 'valueParam'). } defaultLinkOpts :: LinkOpts -defaultLinkOpts = LinkOpts PerPeriod nulldatespan False True False +defaultLinkOpts = LinkOpts PerPeriod nulldatespan False True False Nothing -- | The query parameters a link carries besides q: the register's --- accumulation mode, when it is not the register's default. +-- accumulation mode, when it is not the register's default, and how it +-- converts amounts, when the report converts them. linkParams :: LinkOpts -> [(Text, Text)] -linkParams lo = [("accum", "historical") | loAccum lo == Historical] +linkParams lo = + [("accum", "historical") | loAccum lo == Historical] ++ + [("value", v) | Just v <- [loValue lo]] + +-- | The value parameter that asks hledger-web's register to convert +-- amounts as a report does, mirroring --value's WHEN[,COMM] syntax: +-- "cost" for -B, "end" for -V, "end,COMM" for -X COMM, "then", "now", +-- or a date for the other valuations. A register converting them the +-- same way ends on the figure a link was clicked on. Cost combined +-- with a valuation has none, and such links open the register as it is. +valueParam :: Maybe ConversionOp -> Maybe ValuationType -> Maybe Text +valueParam conv val = + case (conv == Just ToCost, val) of + (True, Nothing) -> Just "cost" + (True, Just _) -> Nothing + (False, Nothing) -> Nothing + (False, Just vt) -> Just $ case vt of + AtEnd mc -> "end" <> comm mc + AtThen mc -> "then" <> comm mc + AtNow mc -> "now" <> comm mc + AtDate d mc -> showDate d <> comm mc + where comm = maybe "" ("," <>) -- | The span a figure's register covers: the period, or, for a -- cumulative figure, the report's start to the period's end. diff --git a/hledger/Hledger/Cli/Commands/Balance/Internal.hs b/hledger/Hledger/Cli/Commands/Balance/Internal.hs index d01805770c2..1cef6515ea1 100644 --- a/hledger/Hledger/Cli/Commands/Balance/Internal.hs +++ b/hledger/Hledger/Cli/Commands/Balance/Internal.hs @@ -14,7 +14,7 @@ import Text.Tabular.AsciiWide (Header(..), Properties(..), Table(..), concatTabl import Hledger.Utils.I18n qualified as I18n import Hledger -import Hledger.Cli.Anchor (LinkOpts(..), dateTerm, removeDates, withLink, setAccountAnchorWith, +import Hledger.Cli.Anchor (LinkOpts(..), valueParam, dateTerm, removeDates, withLink, setAccountAnchorWith, dateSpanCellWith, totalDateSpanCell, headerDateSpanCell, renderPeriodHeading, amountPhrase) import Hledger.Write.Spreadsheet (rawTableContent, headerCell, addHeaderBorders, addRowSpanHeader, @@ -71,16 +71,17 @@ figuresLink :: ReportOpts -> Bool figuresLink ropts = balancecalc_ ropts == CalcChange && not (percent_ ropts) -- | The register link options of a report's rows: its accumulation --- mode, the span its columns cover, and whether it shows figures with +-- mode, the span its columns cover, whether it shows figures with -- the sign opposite to the register's (as the compound reports' negated --- sections do). +-- sections do), and how it converts amounts. reportLinkOpts :: ReportOpts -> [DateSpan] -> LinkOpts reportLinkOpts ropts colspans = LinkOpts { loAccum = balanceaccum_ ropts, loSpan = spansSpan colspans, loDate2 = date2_ ropts, loIncludesSubs = True, - loNegated = normalbalance_ ropts == Just NormallyNegative + loNegated = normalbalance_ ropts == Just NormallyNegative, + loValue = valueParam (conversionop_ ropts) (value_ ropts) } -- | The link options of one account's row: whether its figures include diff --git a/hledger/test/balance/base-url.test b/hledger/test/balance/base-url.test index db2d473b9ba..439117ae0b4 100644 --- a/hledger/test/balance/base-url.test +++ b/hledger/test/balance/base-url.test @@ -333,3 +333,22 @@ $ hledger -f- bal -M -O fods --base-url '' $ hledger -f- bal -M --date2 -O html --base-url '' > // >= 0 + +# ** 23. Converting amounts to market value asks the register to +# convert them the same way, so that it ends on the figure clicked. +< +P 2025-01-01 AAPL $100 +P 2025-02-01 AAPL $120 + +2025-01-05 buy + assets:broker 10 AAPL @ $90 + assets:bank + +$ hledger -f- bal -M -V -O html --base-url '' assets:broker +> // +>= 0 + +# ** 24. Converting them to cost. +$ hledger -f- bal -M -B -O html --base-url '' assets:broker +> // +>= 0 From 3354659de46e4f9c3f59cbaaa7333a2bde5e4aee Mon Sep 17 00:00:00 2001 From: Arthur Cinader <700572+acinader@users.noreply.github.com> Date: Mon, 5 Oct 2026 16:22:57 -0700 Subject: [PATCH 2/2] imp: cli: html: tree-mode account cells carry their depth as a class In tree mode, each html account cell's class is now "account depth-N", N being the row's depth in the displayed tree, so that a stylesheet can indent the tree and a UI can fold it. List modeis unchanged. AI usage: Claude (Opus 5.5 and Fable 5), ~2k output tokens. --- hledger/Hledger/Cli/Commands/Balance.hs | 3 ++- .../Hledger/Cli/Commands/Balance/Internal.hs | 9 +++++++++ hledger/test/balance/html.test | 18 ++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 hledger/test/balance/html.test diff --git a/hledger/Hledger/Cli/Commands/Balance.hs b/hledger/Hledger/Cli/Commands/Balance.hs index 70cda8867db..90f0af326df 100644 --- a/hledger/Hledger/Cli/Commands/Balance.hs +++ b/hledger/Hledger/Cli/Commands/Balance.hs @@ -697,7 +697,8 @@ balanceReportAsSpreadsheetParts fmt opts (items, total) = setAccountAnchorWith lo (guard (rc==Value) >> balance_base_url_ opts) linkquery name $ - (\c -> c{Ods.cellClass = accountClass}) $ + (\c -> c{Ods.cellClass = case rc of Value -> treeAccountClass opts dep + Total -> accountClass}) $ cell $ case rc of Total -> dispName -- show the total row heading as is; --drop etc. don't apply (#2688) Value -> renderBalanceAcct opts nbsp (name, dispName, dep) in diff --git a/hledger/Hledger/Cli/Commands/Balance/Internal.hs b/hledger/Hledger/Cli/Commands/Balance/Internal.hs index 1cef6515ea1..0a8a793c67c 100644 --- a/hledger/Hledger/Cli/Commands/Balance/Internal.hs +++ b/hledger/Hledger/Cli/Commands/Balance/Internal.hs @@ -28,6 +28,14 @@ import Hledger.Write.Spreadsheet qualified as Ods accountClass :: Ods.Class accountClass = Ods.Class "account" +-- | The class of an account cell in a tree-mode report: "account" and +-- its row's depth in the tree, so that a UI can fold the tree by +-- indent, as hledger-web does. In list mode, just "account". +treeAccountClass :: ReportOpts -> Int -> Ods.Class +treeAccountClass opts dep + | accountlistmode_ opts == ALTree = Ods.Class $ "account depth-" <> T.pack (show dep) + | otherwise = accountClass + data RowClass = Value | Total deriving (Eq, Ord, Enum, Bounded, Show) @@ -325,6 +333,7 @@ balanceSubReportAsSpreadsheetParts fmt opts@ReportOpts{..} lo = rowLinkOpts opts colspans shownParents acctName anchorCell = setAccountAnchorWith lo balance_base_url_ rowquery acctName $ + (\c -> c{Ods.cellClass = treeAccountClass opts (prrIndent row)}) $ accountCell $ renderPeriodicAcct opts nbsp row totalrows = if no_total_ diff --git a/hledger/test/balance/html.test b/hledger/test/balance/html.test new file mode 100644 index 00000000000..26503ecd8b1 --- /dev/null +++ b/hledger/test/balance/html.test @@ -0,0 +1,18 @@ +# * balance html output + +# ** 1. In tree mode each account cell's class carries its depth, so a +# stylesheet can indent the tree and a UI can fold it (hledger-web does). +< +2025-01-15 a + assets:bank:checking 10 + assets:cash 5 + income + +$ hledger -f- bal --tree -O html +> /