Skip to content

Table format - #554

Draft
teunbrand wants to merge 7 commits into
table_spansfrom
table_format
Draft

teunbrand wants to merge 7 commits into
table_spansfrom
table_format

Conversation

@teunbrand

Copy link
Copy Markdown
Collaborator

This PR stacks on top of #553.

The user-facing change It implements is the FORMAT clause for plain columns in tabulate.
The important internal change is that table cells now have a properties parameter list that the writer can read.
Currently, is only threads horizontal justification through properties, but eventually this will also be used for other style attributes and classes.

It also adds TableColumn and TableRow arguments to the writer. The TableColumn is the only place where column-wide properties can be set that can't be handed off to cell levels, such as the width attribute.
The TableRow is a stub to be expanded upon in the future, once we have to deal with group labels/summaries and such.

teunbrand and others added 7 commits September 17, 2026 15:02
…rule

SPAN's columns, PARTITION BY's columns, and FACET's row/column vars were
four identical `identifier, (',' identifier)*` grammar rules with matching
duplicate extraction logic in builder.rs. Collapse them into one
column_list rule and one parse_column_list() function. project_aesthetics
stays separate since it names aesthetics, not columns, despite the
identical shape.

Also correct two CLAUDE.md files that claimed the generated tree-sitter
parser files are committed to git — they're gitignored and regenerated by
the Rust build script instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
FORMAT configures cell formatting for a group of columns: FORMAT col, ...
[SETTING ...] [RENAMING ...], repeatable like SPAN. SETTING's semantics
are deferred — parsed and stored but not yet validated or applied.
RENAMING reuses SCALE's exact clause (scale_renaming_clause renamed to
the generic renaming_clause) so FORMAT gets identical explicit-value and
wildcard-template renaming behavior for free.

Table gains a `formats: Vec<Format>` field. Format stores value_mapping/
value_template rather than reusing Scale's label_mapping/label_template
names, since Table::labels already claims "label" for column headers.
Both fields share one default_template() helper in format.rs instead of
each struct defining its own serde default function.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Wires Format.value_mapping/value_template into actual cell display: a new
table_format module replaces each FORMAT-covered column in the resolved
DataFrame with its resolved display text before column/cell layout runs,
so create_body's ordinary value_to_string rendering already reflects any
RENAMING and never needs to know FORMAT exists. A later FORMAT clause wins
over an earlier one naming the same column; an unknown column errors.

format.rs gains resolve_column_values, which works directly off a column's
typed Arrow values rather than boxing each row into an ArrayElement first
(the path apply_label_template uses for Scale's much smaller break lists) —
avoiding that allocation for what can be a much larger number of table
rows. compute_numeric_precision now takes plain f64s so both call sites
can share it without going through ArrayElement, and the string/numeric
column conversion in format_dataframe_column is factored out into
column_to_strings for both to reuse.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Validates FORMAT's SETTING against a real FORMAT_PARAMS list (previously
accepted-and-ignored): hjust takes "left"/"right"/"center"/"centre" or a
0-1 number, via a new ParamConstraint::string_option_or_number shared with
any future param needing the same keyword-or-continuous shape.

TableColumn and TableCell gain a `properties: Parameters` field.
resolve_column_properties resolves each column's hjust exactly once,
standardised to a number (left/center/right = 0.0/0.5/1.0; an explicit
number passes through; an absent setting defaults from the column's Arrow
dtype, numeric right, else left) so every writer buckets one representation
instead of re-implementing keyword handling. Column-label cells inherit
their column's properties, matching gt's own convention.

HtmlWriter reads a cell's resolved hjust and renders it as an inline
`style` attribute (text-align, plus font-variant-numeric: tabular-nums
when right-aligned) through one translation function, so a future
property is one more mapper rather than a change to render_cell itself.
The numeric bucketing (0.25/0.75 thresholds) matches VegaLiteWriter's own
convert_hjust, so hjust means the same alignment in every writer.

Since dtype-derived defaults apply to every TABULATE output regardless of
FORMAT, several pre-existing HtmlWriter/CLI/Jupyter tests asserting bare
`<th>`/`<td>` strings needed updating; loosened them (and the new cell
tests) to check tag content and alignment keywords rather than exact
attribute strings, so future properties don't compound into unreadable
literals.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a FORMAT section to doc/syntax/clause/tabulate.qmd (clause syntax,
SETTING's hjust, RENAMING linked to SCALE's own RENAMING/break-formatting
docs rather than duplicated) plus a runnable example combining hjust
alignment with null/template RENAMING against ggsql:penguins.

doc/ggsql.xml gains a FORMAT keyword and FormatClause highlighting
context, mirroring SpanClause, so the doc site's ```ggsql fences
highlight it correctly; verified with a full quarto render of the page
(after rebuilding and reinstalling the ggsql-jupyter kernel, since the
previously-installed one predated the FORMAT grammar).

CHANGELOG.md's existing TABULATE bullet list gains a FORMAT entry at the
same one-line-per-clause detail level as LABEL/SPAN.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Table writers now receive resolved TableColumn/TableRow slices alongside
cells, so a future whole-column setting like FORMAT's width can be read
once per column instead of duplicated across every cell. Also drops
ResolvedTable's vestigial table: Table field, made dead once cells/columns
absorbed everything it used to carry.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a numeric-with-unit ParamConstraint (px/%) for FORMAT's width
SETTING, and has HtmlWriter emit a <colgroup> with a per-column
width style, mirroring gt's own colgroup output (checked directly
against gt's source: width is the only thing it ever puts there).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@teunbrand
teunbrand added this pull request to stack #552 September 21, 2026 11:37

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.

1 participant