Skip to content

Build D2 text from typed syntax nodes and parse values up front - #14

Merged
h0rv merged 1 commit into
mainfrom
typed-syntax
Oct 23, 2026
Merged

h0rv merged 1 commit into
mainfrom
typed-syntax

Conversation

@h0rv

@h0rv h0rv commented Oct 23, 2026

Copy link
Copy Markdown
Owner

All D2 text now comes from one internal module, d2/_syntax.py. The classes build typed statements instead of strings. Every quoting, escaping and indentation rule is in that one module. Values are parsed into real types when an object is built.

Syntax

  • Statements are frozen dataclasses that use only the standard library: keys, labels, URLs, colors, arrays, code blocks, fields, connections and comments. One emit function writes them out.
  • Keys are quoted whenever D2 needs it. The rules were checked by sending about 300 strings full of special characters through d2.
  • Code blocks pick a delimiter longer than any | run in their text, so code that contains | no longer breaks the output.

Typed values

  • d2.Color accepts a CSS color name, a 3 or 6 digit hex code with or without #, a gradient, or a ${variable}. It always writes the D2 form, e.g. "#dbeafe". A bad color raises ValueError when the Style is built.
  • These ranges are checked at construction, with the limits taken from d2:
    • opacity 0 to 1
    • stroke width 0 to 15
    • stroke dash 0 to 10
    • font size 8 to 100
    • grid sizes and gaps
    • positions and theme ids
  • Options d2 never accepted are corrected:
    • text_transform="title" is now "capitalize".
    • The nine inside-* label positions are replaced by the nine plain positions that d2 0.9.0 accepts.
  • An empty string means unset, and that's decided once in the constructor.

Compatibility: every public name, constructor parameter and test expectation is unchanged.

Output differences

  • Indentation: a style block inside a layer or scenario was indented wrongly before. It's correct now.
  • Pre-quoted names and labels: a name or label you quoted yourself, like '"a.b"', is now treated as literal text. Style values you quoted yourself still work.
  • Empty SQL column type: an empty column type writes id: "". The old id: was invalid D2.

Checked locally:

  • Tests: all 241 pass on 3.11, 3.13, 3.15 and 3.15t.
  • Lint: ruff, ty and uv lock --check are clean.
  • Examples: all 20 run against d2 0.9.0.
  • Output: the D2 text for about 250 diagrams matches main byte for byte, except the indentation fix.
  • doit's exact calls produce valid D2.

🤖 Generated with Claude Code

All D2 text is now produced in one module, d2._syntax, from frozen dataclasses
(Key, Label, Url, Quoted, Keyword, Color, Array, Text, Field, BlockField, Edge,
Comment) and a single emit function. Quoting and escaping rules live there only.
helpers.py is removed.

Style colors are parsed into d2.Color (CSS names, 3 or 6 digit hex, gradients,
${variables}); numeric ranges D2 enforces raise ValueError at construction; the
fill-pattern, text-transform and label position sets match D2 0.9.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@h0rv
h0rv merged commit 8b04610 into main Oct 23, 2026
9 checks passed
@h0rv
h0rv deleted the typed-syntax branch October 23, 2026 15:15
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