Skip to content

Fix optional token elision in naming patterns - #850

Open
krejko wants to merge 1 commit into
Listenarrs:canaryfrom
nexalapp:fix/optional-token-elision
Open

Fix optional token elision in naming patterns#850
krejko wants to merge 1 commit into
Listenarrs:canaryfrom
nexalapp:fix/optional-token-elision

Conversation

@krejko

@krejko krejko commented Aug 20, 2026

Copy link
Copy Markdown

Summary

Naming patterns support optional tokens by emitting a sentinel for empty values and stripping the surrounding punctuation afterwards. That works for a bracket group holding a single token, but not for one holding several — so a pattern like [{Series} {SeriesNumber}] leaks a literal sentinel into folder and file names for every book without a series.

With {Author}/[{Series} {SeriesNumber}] {Title} {{Narrator}} ({Year}):

Case Before After
Standalone book J.K. Rowling/[__EMPTY_VAR_] The Ickabog {Stephen Fry} (2020) J.K. Rowling/The Ickabog {Stephen Fry} (2020)
Series without a number Cory Doctorow/[Radicalized /] Radicalized (2019) Cory Doctorow/[Radicalized] Radicalized (2019)
Series with everything unchanged unchanged

Two root causes:

  1. The sentinel was __EMPTY_VAR__, and _ is part of the separator character class used by the cleanup regexes — so cleanup partially consumed the sentinel itself, leaving __EMPTY_VAR_ behind.
  2. The bracket rule only matched a group containing exactly one sentinel. With two, the group survived long enough for the slash rule to rewrite the sentinel as / inside the brackets.

Changes

Fixed

  • Replace the __EMPTY_VAR__ sentinel with U+E000, which shares no characters with the separator class.
  • Resolve bracket groups before separator and slash handling, so a surviving sentinel can no longer inject a stray / into a name.
  • Collapse bracket groups containing any number of empty tokens: drop the group when nothing meaningful survives, otherwise keep it with the empty tokens removed.

Added

  • FileNamingService_OptionalTokenElisionTests covering all-tokens-present, empty series, empty series + narrator, series without a number, trailing optional tokens, the filename variant, and a guard that the sentinel never reaches output.

Testing

dotnet test filtered to the naming suites: 28 passed, 1 failed.

The single failure is DownloadNaming_AudiobookMetadataTests.ProcessCompletedDownload_UsesAudiobookMetadata_ForNaming, which fails identically on unmodified canary — verified by stashing this change and re-running that test alone. It is pre-existing and unrelated.

Existing DownloadNaming_PatternCollapseTests and FileNamingService_PatternSelectionTests still pass, so single-token groups and duplicate-component collapsing are unaffected.

Notes

Delimiters are escaped explicitly rather than through Regex.Escape, which does not escape ] — building the negated class with it produces [^\[]], closing the class early. That bug was caught by the new tests.

Behaviour only changes where the old output was already broken; patterns whose groups hold a single token render exactly as before.

🤖 Generated with Claude Code

A bracket group holding more than one optional token (for example
"[{Series} {SeriesNumber}]") was never collapsed when every token inside it was
empty, so books without a series rendered a literal sentinel into their folder
and file names.

- Replace the "__EMPTY_VAR__" sentinel with U+E000. The old value contains
  underscores, and "_" is part of the separator character class used during
  cleanup, so the cleanup regexes partially consumed the sentinel itself.
- Resolve bracket groups before separator and slash handling so a surviving
  sentinel can no longer leak a stray "/" into a rendered name.
- Handle groups containing any number of empty tokens: drop the whole group
  when nothing meaningful survives, otherwise keep the group with the empty
  tokens removed. This lets "[{Series} {SeriesNumber}]" disappear for a
  standalone book and render as "[Radicalized]" for a series with no number.

Escape the group delimiters explicitly rather than via Regex.Escape, which
leaves "]" untouched and would close the negated character class early.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@krejko
krejko requested a review from a team August 20, 2026 18:40
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