Skip to content

Multiple FORMATs merge instead of last-one-wins - #572

Open
teunbrand wants to merge 5 commits into
posit-dev:table-featurefrom
teunbrand:accrued_format
Open

teunbrand wants to merge 5 commits into
posit-dev:table-featurefrom
teunbrand:accrued_format

Conversation

@teunbrand

Copy link
Copy Markdown
Collaborator

This PR implements suggestion here: #558 (comment)
@thomasp85 if you want this now rather than later, feel free to merge

@teunbrand teunbrand mentioned this pull request Oct 2, 2026
@thomasp85

Copy link
Copy Markdown
Collaborator

In general this looks good, but I do have some concerns over how defaults are handled during overrides. For the column type specifically we would have

FORMAT STUB price ...
FORMAT price 
  SETTING hjust = "left"

retarget price to BODY where the user expectation is probably to not change the type unless specified

Something akin to this is happening in renaming because a default value is deduced from the content ({}). That means that

FORMAT price 
  RENAMING * => "{:num %.2f}"
FORMAT price 
  RENAMING * => "{}"

will be a no-op. Probably an edge case but super surprising if encountered.

The better approach is for both structs to encode the content as an Option<> value

@teunbrand

Copy link
Copy Markdown
Collaborator Author

For this piece:

FORMAT STUB price ...
FORMAT price 
  SETTING hjust = "left"

I'd rather require FORMAT BODY every time (make target non-optional) than having a persnickety 'the default is body but we won't enforce it if a previous clause has already declared that column to be a stub'. It smells too much of magic defaults to me.

For this piece:

FORMAT price 
  RENAMING * => "{:num %.2f}"
FORMAT price 
  RENAMING * => "{}"

Yeah I agree

@thomasp85

Copy link
Copy Markdown
Collaborator

hmm... I think I disagree... The default is on the column, not on the query. So FORMAT col when lacking a type will ask the column what type it is and the default will be BODY.

However, if we have already given the column a type from a prior FORMAT clause then the column will respond with that

This is not how it is directly implemented, I know, but that is how I think about it

A FORMAT clause that never names a wildcard template now leaves the
field None instead of storing the default "{}", so merging stacked
FORMAT clauses on one column only overrides a template a clause
actually set. parse_renaming_clause returns Option<String> for the
template to keep an explicit * => '{}' distinguishable from no
wildcard, letting a later clause reset an earlier template.
A FORMAT clause without a target identifier (or with the new ANY
keyword) now makes no claim about which table section its columns
belong to: Format.target is Option<ColumnSection>, merging stacked
clauses lets concrete BODY/STUB targets win over ANY, and unclaimed
columns resolve to the body in setup_formats. Previously a bare
FORMAT counted as a BODY claim and would silently pull columns out
of the stub when combined with FORMAT STUB.
@teunbrand

Copy link
Copy Markdown
Collaborator Author

The solution I've taken is to have the format.target and format.value_template (the *-wildcard renaming entry) be Options. None will not override previous instructions and will be overridden by later instructions. For targets, the default is now FORMAT ANY, which will be parsed to format.target = None.

FORMAT * sets a wildcard selection, expanded to the data's columns in
setup_formats via the new expand_format_columns helper (which also
absorbs SPAN id expansion and unknown-column validation). Wildcard
clauses merge like any other, so an explicit clause can still refine a
single column after a catch-all FORMAT *.
@teunbrand

teunbrand commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator Author

I'm now also allowing FORMAT * to target all columns, convenient when you want to FORMAT * RENAMING null => '' or something.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants