Skip to content

fix(figure): validate data_prefix instead of emitting unparseable GLE - #13

Merged
BenHuddart merged 1 commit into
mainfrom
claude/jolly-kare-6d68a6
Jul 28, 2026
Merged

fix(figure): validate data_prefix instead of emitting unparseable GLE#13
BenHuddart merged 1 commit into
mainfrom
claude/jolly-kare-6d68a6

Conversation

@BenHuddart

Copy link
Copy Markdown
Owner

What

Figure(data_prefix=...) was used verbatim to build sidecar .dat filenames with no checking, so a prefix GLE cannot parse failed only at compile time, pointing at generated GLE rather than at the bad value:

glp.figure(data_prefix="mk+white")
# >> Error: left hand side contains unquoted string
#    on `data mk+white_0.dat d1=c1,c2`

Figure.__init__ now raises ValueError, naming the offending characters and their positions:

ValueError: data_prefix 'mk+white' contains characters that cannot appear in a GLE
data filename: '+' at index 2. Forbidden: whitespace, control characters, and any of
! " + / \ (GLE cannot parse the first four in an unquoted 'data' statement; the last
two are path separators). Most other punctuation, including . - _ and #, is allowed.

Why validate rather than sanitize

The per-series data_name= path already runs through _sanitize_data_stem, but the two arguments differ in kind:

  • data_name takes a free-form label ('Observed Signal') whose mapping to a filename is an implementation detail — sanitizing is the point.
  • data_prefix exists precisely so callers can predict sidecar names in batch pipelines. Silently rewriting mk+whitemk_white would trade a GLE parse error for a missing file even further downstream, in the caller's own tooling.

_sanitize_data_stem also lowercases, so reusing it would have quietly turned the documented data_prefix='experimentA' into experimenta_0.dat — a regression in documented behaviour (docs/source/getting_started.rst).

Where the rejected set comes from

Empirical, not assumed. I compiled a data <file> statement against GLE 4.3.10 for every printable ASCII punctuation character, with a missing-file control to confirm the probe actually detected resolution failures:

  • Breaks GLE's unquoted-filename tokenizer: !, ", +, and whitespace. Nothing else.
  • Parses fine, stays allowed: . - _ # $ % & ( ) [ ] { } , ; : = @ ~ ^ * ? < > | ` ' and non-ASCII.
  • Rejected additionally: / and \, on every platform, as path separators — a prefix names a filename stem, not a path.

Empty/whitespace-only prefixes are rejected too (pass None for default data_N.dat naming); a non-str raises TypeError.

Reviewer notes

Figure.from_dict deliberately bypasses validation. That is recorded state, not user input: the recognizer derives a prefix from filenames already present in a parsed .gle, and a quoted data "my file_0.dat" legitimately yields the prefix my file. Validating on restore would make such a figure impossible to reload and would break GUI undo/redo, which routes every snapshot through from_dict. There's a test pinning this.

Two adjacent issues left out of scope, flagged rather than fixed:

  1. Assigning fig.data_prefix = "mk+white" after construction is still unvalidated. A property setter would catch it but would also fire on the recognizer and GUI-preview paths that assign directly, so widening the change didn't seem right here.
  2. A .gle whose data filename was quoted can round-trip to a prefix that gleplot's writer then re-emits unquoted — a real pre-existing bug, but a separate one.

Testing

  • New TestDataPrefixValidation in tests/unit/test_plotting.py (10 cases), placed alongside the existing data_name naming tests.
  • Full suite: 1453 passed, 2 skipped (GLE 4.3.10 present, so compile-dependent tests ran).
  • Both edited source files are black-clean; formatting was applied only to the new regions to avoid repo-wide quote churn.

Docs

  • docs/guides/CONFIGURATION.md — new "Allowed characters" subsection under the existing data_prefix section, including the contrast with data_name.
  • docs/source/getting_started.rst — short constraints note on the experimentA example.
  • README doesn't mention data_prefix at all (only a generic advanced/ directory listing) and DOCUMENTATION_INDEX.md only indexes the example filename, so neither needed a change.

🤖 Generated with Claude Code

`Figure(data_prefix=...)` was used verbatim to build sidecar filenames
without any checking, so a prefix GLE cannot parse only failed much later,
at compile time, with a message pointing at generated GLE rather than at
the bad value:

    glp.figure(data_prefix="mk+white")
    # >> Error: left hand side contains unquoted string
    #    on `data mk+white_0.dat d1=c1,c2`

`Figure.__init__` now raises ValueError (naming the offending characters
and their positions) so the failure surfaces in Python at the point the
value was supplied.

Validate rather than sanitize. The per-series `data_name=` path runs
through `_sanitize_data_stem`, but the two arguments differ in kind:
`data_name` takes a free-form *label* ("Observed Signal") whose mapping to
a filename is an implementation detail, whereas `data_prefix` exists
precisely so callers can *predict* sidecar names in batch pipelines.
Silently rewriting `mk+white` to `mk_white` would trade a GLE parse error
for a missing file even further downstream, in the caller's own tooling.
`_sanitize_data_stem` also lowercases, so reusing it would have quietly
turned the documented `data_prefix='experimentA'` into `experimenta_0.dat`
— a regression in documented behaviour.

The rejected set is drawn from GLE 4.3.10, not assumption: compiling a
`data <file>` statement for every printable ASCII punctuation character
shows only `!`, `"`, `+` and whitespace break its unquoted-filename
tokenizer. `.`, `-`, `#`, `,`, `=`, `*` and non-ASCII all parse fine and
stay allowed. `/` and `\` are additionally rejected on every platform as
path separators, since a prefix names a filename stem, not a path.

`Figure.from_dict` restores the prefix verbatim, bypassing validation:
that is recorded state, not user input. The recognizer derives a prefix
from filenames already in a parsed `.gle`, and a quoted
`data "my file_0.dat"` legitimately yields `my file`; validating there
would make such a figure impossible to reload and would break GUI
undo/redo, which restores every snapshot through `from_dict`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@BenHuddart
BenHuddart merged commit b39d8ca into main Jul 28, 2026
4 checks passed
@BenHuddart
BenHuddart deleted the claude/jolly-kare-6d68a6 branch July 28, 2026 20:52
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