chord_pro 0.8.0
chord_pro: ^0.8.0 copied to clipboard
A Dart parser for the ChordPro 6 song format. Extracts chords, lyrics, metadata, comments, layout hints and chord diagrams.
0.8.0 #
Round-tripping release. Text that the parser used to drop on the floor —
the body of a selector-suppressed section, and a verbatim line that happens
to start with { — now survives into the AST, so re-emitting a song from
what was parsed no longer loses lines.
Breaking #
- A
{start_of_X-sel}whose selector is not active now produces aSectioninSong.sectionsinstead of being dropped. The section is flagged with the newSection.isSelectorSuppressedand carries its body lines, so re-emitting a song from the AST no longer deletes the text between the markers (#36). A renderer that honours selectors should iterate the newSong.activeSections(sectionsminus the suppressed ones) instead ofsections. Two consequences of the section now being real: a run of loose lines interrupted by a suppressed section is split into two loose sections rather than joined into one, and a suppressed start inside an open section auto-closes it with the samenestedSectiondiagnostic an applying start already produced. Spec: https://www.chordpro.org/chordpro/chordpro-configuration-selectors/
New #
Section.isSelectorSuppressed—truewhen the section's start directive carried a selector that was not active for this parse. Carried throughSong.transposedand part ofSectionequality.Song.activeSections— the sections a selector-honouring renderer should output, i.e.sectionswithout the suppressed ones.
Fixed #
- Inside a verbatim environment (
tab,grid,abc,ly,svg,textblock,grille) only the matching{end_of_X}is interpreted now. A body line starting with{— a LilyPond{\key c \major}, a tab annotated{riff x2}— used to be parsed as a directive, so it vanished from the section body and reappeared inSong.directiveswith no diagnostic. Such lines areLine.verbatimbody text again, and no longer land inSong.directives. The same rule applies to a selector-suppressed verbatim environment. Spec: https://www.chordpro.org/chordpro/directives-env_tab/
0.7.0 #
Correctness and API release from a full package review. Three parser bugs fixed, value equality added across the AST, diagnostics given stable codes, and the spec audit taught to check its own forward direction.
Breaking #
Diagnosticnow requires acode, aDiagnosticCodenaming the condition it reports. Switch oncodeinstead of matchingmessage, which is prose and may be reworded in any release. Code that constructs aDiagnosticmust pass one; code that only reads them is unaffected.Chord.extensions(aList<String>that never held more than one element) is nowChord.extension, aString?holding everything after the quality verbatim. Replacechord.extensions.join()withchord.extension ?? ''. The ChordPro spec defines no grammar for what follows the quality, so the remainder was never actually split. Spec: https://www.chordpro.org/chordpro/chordpro-chords/Song.transposedno longer returnsthiswhen the shift is a whole number of octaves. It normalises chord spelling to the requestedaccidentalsas well as the pitch, sotransposed(0, accidentals: AccidentalPreference.flats)now respells a song in flats instead of doing nothing. Callers that relied ontransposed(0)being an identity should skip the call instead.Chord.tryParse('b7')and its siblingsb1–b7now reportChordSystem.nashvillerather thanChordSystem.letter. See Fixed.
New #
- Every public type in the AST implements
==andhashCodestructurally:Song,Metadata,Section,Line, all fiveInlineTokensubtypes,Chord,ChordDefinition,Directive,ImageDirective,GridAttributes,TextblockAttributes,DiagramsSetting,FormattingProps,FormattingSettings,Diagnostic,ParseResult,SourceSpanandRawLine. Parsed songs can now be compared in tests, deduped, cached or used as map keys. The package still has zero runtime dependencies. DiagnosticCode— a stable identifier on every diagnostic, exported from the barrel.- Numeric metadata directives given a non-number (
{capo: high},{year: MCMXCIX},{tempo: fast},{columns: many},{transpose: up}) now produce aDiagnosticCode.invalidNumericValuewarning instead of being dropped in silence. - An unterminated selector-suppressed section is reported as
DiagnosticCode.unterminatedSuppressedSection, matching the warning an unterminated ordinary section already produced.
Fixed #
Song.transposeddroppedSection.attributes, so transposing a song lost its grid geometry and every textblock attribute —{start_of_grid: 4x4}came back with no shape at all. The attributes are now carried over. Spec: https://www.chordpro.org/chordpro/directives-env_grid/- A selector-suppressed section with no matching end directive swallowed the
rest of the document,
{new_song}included: two songs collapsed into one with no sections and both titles merged. Song boundaries are structural and are never conditional, so they now end the suppression. Nesting is tracked too, so a same-kind section inside a suppressed one no longer ends the suppression early and leaks its lines. Spec: https://www.chordpro.org/chordpro/chordpro-directives/ - Flat Nashville roots (
b1–b7) were reported asChordSystem.letter, because the notation system was re-derived from the root text, where a leadingbis ambiguous between a flat and a notes-mode lowercase root. The system now comes from the branch that matched the root. Spec: https://www.chordpro.org/chordpro/chordpro-chords/ Line.tokens,ChordDefinition.frets/fingers/keysand the chord extension list were handed out as mutable lists, so a caller could rewrite a parsed song in place. Every collection the parser produces is now unmodifiable, including the ones rebuilt bySong.transposed.- A single-hex-digit brace escape on a line of its own (
\u{7}) was left unresolved by a minimum-length guard, and the inline tokenizer then ate the backslash and read{7}as a directive. The guard is gone; lines with no backslash take a fast path instead, which is also cheaper than the three rewriting passes it replaces. Spec: https://www.chordpro.org/chordpro/chordpro-introduction/ altBracketswas a whole-document string replacement, so the configured pair was rewritten inside directive values and verbatim bodies too — a song using«»as quotation marks in its{title}came back with brackets. The rewrite now applies to lyric lines only. Spec: https://www.chordpro.org/chordpro/chordpro-configuration-generic/- A
{chorus}recall inside an open section was appended toSong.sectionsbefore the section it appeared inside, losing source order. The open section is now closed first (reported asDiagnosticCode.chorusRecallInsideSection) and its own end directive is consumed quietly rather than reported as a stray end. Spec: https://www.chordpro.org/chordpro/directives-chorus/ settings.strictscanned the raw directive stream, so{meta: key D}satisfiedMetadata.keysyet still warned that no key was declared. It now reads the reduced metadata, and the warning's span points at the song that is missing the key instead of always at line 1.{define: ""}produced a chord definition named"", and{image: ""}produced nothing at all — in both cases silently, because the "malformed" diagnostic for each was unreachable. Both are now reported.
Internal #
test/spec_coverage_test.dartgained the checklist → audit direction: every checklist subsection holding ticked spec obligations must be named by an[§…]coordinate intest/spec_audit_test.dart. Backfilled the subsections it found bare (§2.12, §6.7, §13.1–§13.4, §13.7, §13.8).- The
{start_of_grid}body-token vocabulary is unticked inchordpro-spec-checklist.mdand recorded indoc/reference/limitations.md: grid bodies are captured verbatim, so those tokens were never surfaced. - Added
test/fuzz_test.dart, a seeded property test asserting that parsing is total — random input never throws and always yields at least one song — across the plain, option-laden and transposing paths.
0.6.3 #
Maintenance release: documents the {colb} shorthand, adds a
reverse-direction spec-coverage guard to the test suite, and widens the
very_good_analysis dev-dependency constraint to <12.0.0. No parser or
public API changes.
Fixed #
{colb}— the parser's non-spec shorthand for{column_break}— is now listed in the non-spec extensions table (doc/reference/non-spec-extensions.md). The spec's own shorthand{cb}is already taken by{comment_box}, so{colb}provides an unambiguous short form. It was recognised by the assembler but undocumented; a newtest/spec_coverage_test.dartguard now fails when any name in the parser's dispatch tables is absent from both the spec checklist and that table. Spec: https://www.chordpro.org/chordpro/directives-column_break/
0.6.2 #
Breaking #
InlineTokenis asealed class. AddingChordRecallTokento its hierarchy is a breaking change for any code with an exhaustiveswitchonInlineTokensubtypes. Add aChordRecallTokencase (or a wildcard_fallback) to fix.
New #
ChordRecallToken— newInlineTokensubtype emitted when the tokenizer encounters[^], the chord-recall operator introduced in ChordPro 6.070 (experimental). Renderers should advance the active chord-change set (cc) cursor when they encounter this token. Spec: https://www.chordpro.org/chordpro/ChordChanges/notesModeparameter onChord.tryParse,tokenizeInline,assemble,ChordPro.parse, andChordPro.parseSong— whentrue, lowercasea–gare accepted as letter-system chord roots, mirroring thesettings.notesChordPro configuration option. Defaults tofalse; no behaviour change for existing call sites. Transposition of notes-mode roots is supported through the same chromatic table as uppercase roots.strictparameter onChordPro.parseandChordPro.parseSong(and the internalassemble) — whentrue, aDiagnosticSeverity.warningis emitted for each song that lacks a{key}directive, mirroring thesettings.strictChordPro configuration option. Defaults tofalse, consistent with the ChordPro 6.100 change that made forgiving the built-in default.forceCommonKeysparameter onSong.transposed,Chord.transpose, andtransposeRoot— whentrue, roots that would produce key signatures with more than 5 accidentals are substituted with their enharmonic equivalents:C#→Db,D#→Eb,G#→Ab,A#→Bb. Mirrors thekeys.force-commonChordPro 6.100 configuration option. Defaults tofalse; no behaviour change for existing call sites.Preprocessortypedef (String Function(String line)) exported frompackage:chord_pro/chord_pro.dart. Pass a list of preprocessors toChordPro.parse/ChordPro.parseSongvia thepreprocessors:named argument; each function is applied to every physical source line in list order before scanning begins. Line endings (\r\n,\r) are normalised to\nbefore preprocessing.
0.6.1 #
Maintenance release: adds the grille delegated environment and corrects
the legacy {no_grid} / {ng} directives. No breaking changes.
New #
SectionKind.grille— recognises{start_of_grille}/{end_of_grille}as the experimentalGrille.pmdelegated environment from the reference implementation. Previously these fell through toSectionKind.custom(non-verbatim); they are now captured verbatim, consistent with other delegated environments (abc,ly,svg,textblock). Spec: https://www.chordpro.org/chordpro/directives-env_grille/
Fixed #
- Legacy
{no_grid}/{ng}now disable chord diagrams (equivalent to{diagrams: off}), matching their{diagrams}/{g}counterparts. Previously they fell through to the generic directive path, leavingSong.diagramsunset. Both forms remain in the losslessSong.directivesstream.
0.6.0 #
Final ChordPro 6 spec-parity pass: fills the parser-implementable gaps left by 0.5.0's audit. Surface changes are mostly additive; a handful of multi-valued metadata fields move from scalar to list to match the spec.
Breaking #
Metadata.sortTitle/Metadata.sortArtist→Metadata.sortTitles/Metadata.sortArtists(lists). One sort entry pertitle/artistin matching source order perdirectives-sorttitle/anddirectives-sortartist/. The old scalar names remain as convenience getters returning the first entry.Metadata.key/Metadata.time/Metadata.tempo→Metadata.keys/Metadata.times/Metadata.tempos(lists). Each declaration applies from its position onward perdirectives-key/,directives-time/,directives-tempo/. The scalar names remain as convenience getters for the first (primary) declaration.
New #
ChordPro.parse/parseSongaccept analtBracketsargument that rewrites a configured two-character pair (e.g.«») to[/]before parsing. Mirrors theparser.altbracketsconfiguration option.GridAttributes.ccNameand.ccProgressiondecode the ChordPro 6.070cc="Name"/cc="Name:C1 C2 …"forms into a typed name plus chord list. The rawccstring is still surfaced verbatim.ImageDirectiveacceptstrbl=as a synonym forbordertrbl=(both names appear in the official docs).{define}/{chord}acceptbase_fret(underscore) as a synonym forbase-fretperdirectives-define/.- Falsy keyword set for
toc=,omit=, … now covers the fullkey_value_pairs/list:0,false,null,no,none,off, plus the empty string. - Reserved metadata namespace extended to cover the full
chordpro-configuration-format-strings/list:page,pageno,pages,pagerange,instrument,instrument.type,instrument.description,tuning,user,user.name,user.fullname.
0.5.0 #
ChordPro 6 spec parity pass against the upstream reference parser
(ChordPro/chordpro head, currently 6.101). Net additive surface — no
breaking changes from 0.4.0.
New typed accessors #
Metadata.arrangers(list, likecomposersandlyricists). Spec: https://www.chordpro.org/chordpro/directives-arranger/.Metadata.transposeQualifier(TransposeQualifier enum:none,sharps,flats,followKey). Captures the postfixs | # | ♯ | f | b | ♭ | kon{transpose: N…}perTranspose.pm:114. Thekqualifier was added in ChordPro 6.100.ImageDirective.label(visible caption, ChordPro 6.040),.href(6.060),.x,.y,.spread,.bordertrbl,.center,.chord,.type,.persist,.omit.ImageDirective.anchorEnum(ImageAnchorenum:paper,page,allpages,column,float,line).allpageswas added in ChordPro 6.080.ChordDefinition.display(5.989),.format,.keys(0.979),.copy,.copyall,.diagram(6.010),.isTransposable(6.100). Fingers may now be string letters (A–M,O–W,Y,Z) in addition to integers and muted markers.Section.labelis now populated from{start_of_*: label="…"}as well as the legacy bare-value form.Section.attributesexposes any extra KV pairs.Section.gridAttributes(GridAttributeswithshape,cc,label—ccdefaults to"grid"per spec).Section.textblockAttributes(TextblockAttributescovering the full ChordPro 6.050 attribute set plus image-inherited fields).Song.tocSuppressed(from{ns toc=no}, ChordPro 6.040).Song.titlesAlignment(TitlesAlignment.left|center|right;centreaccepted as alias).Song.diagrams(DiagramsSetting{enabled, position}; position enumtop|bottom|right|below). The{g}shorthand now aliases{diagrams}per spec.
Parser / scanner #
+accepted as an augmented quality marker (spec alternate foraug).C+now parses asquality: '+'rather thanextensions: ['+'].\u{X+}brace-form unicode escape (1+ hex digits, surrogates recombined) added in ChordPro 6.060, alongside the existing\uXXXX4-digit form.- Empty
[], whitespace-only[ ]+, and pipe[|]chord brackets are recognised per ChordPro 6.020/6.080 emergency rules: whitespace-only and pipe becomeAnnotationTokens; empty[]is a zero-width placeholder (no token emitted). - Auto-generated metadata names (
_key,key.print,key.sound,today,songindex,chordpro.version, …) are reserved — user{meta:}cannot collide. {define}fret values now accept-1(ChordPro 6.060) andN(in addition tox/X/-).- Bracketed
{define: [Name] …}form parses as transposable (attributes discarded per spec).
Reference #
Verified against lib/ChordPro/Song.pm and
lib/ChordPro/Chords/Transpose.pm at ChordPro/chordpro HEAD plus
the upstream Changes file through 6.101 (2026-04-30).
0.4.0 #
Spec-compliance pass against the ChordPro reference parser.
- Breaking: the canonical negation form is now postfix
!—{name-sel!}— matching the ChordPro spec and the reference Perl parser (s/\!$//).Directive.toStringemits this form. The previous prefix{name-!sel}and legacy{name+sel}forms continue to be accepted on input for backward compatibility but are now flagged as non-spec. - Conditional selectors now gate all directives, not just metadata
and formatting. Sections with an inactive selector skip their entire
body (lines, nested directives, and the section end), per spec
("selection applies to everything in the section, up to and including
the final section end directive"). Comments, images, layout breaks,
chorus recalls, and
{define}/{chord}definitions are likewise suppressed when their selector polarity does not match. - Add the spec-listed chord qualifiers
^(alternate formaj),h(half-diminished), and0(diminished, literal zero). The pre-existingø/°glyphs remain as documented non-spec extensions. - Add ChordPro 6.01 scanner features:
\-terminated line continuation (with leading whitespace stripped from the next line) and\uXXXXUnicode escapes anywhere in the source. - Document explicitly which features are non-spec extensions of this
parser (German
H, unicode accidentals,ø/°,NC/N.C./N.C, bracket-escapes, mid-lyric directives, legacy selector forms) and which spec features are not yet covered (label="…"parsing, Pango markup,{define}format strings).
0.3.0 #
- Add
selectors:argument toChordPro.parse/parseSongso callers can activate conditional directives. The same set is forwarded toreduceFormatting, which now honours selector polarity. Matching is case-insensitive. Directive.toStringemits a postfix!for negative polarity instead of the legacy+selform.- Drop the unused
collectiondependency.
0.2.0 #
- Skip
#file-comment lines per spec. - Add
sortartistandtagmetadata fields. - Accept spec-form
{name-!sel: …}selector negation alongside the legacy+selform. - Emit
{comment}/{comment_italic}(ci) /{comment_box}(cb) /{highlight}asLine.commententries. - Parse
{image: …}into a typedImageDirectiveexposed asLine.imageentries. - Add
Chord.transpose,Song.transposed, andmetadata.transpose. - Recognise
{new_page}/{new_physical_page}/{column_break}asLine.layoutBreakentries; capture{columns}/{col}asmetadata.columns. - Add
svgandtextblockverbatim section kinds. - Reduce font/size/colour directives into
Song.formatting(FormattingSettingskeyed by target). - Recognise
x_*custom-extension directives viaDirective.isCustomExtensionandSong.customExtensions. - Extend the chord parser to handle German
H, unicode♯/♭, minor variants (mi,-), half-diminishedø, diminished°, andNC/N.C.no-chord markers.
0.1.0 #
- Intial