Skip to content

feat: preserve PGN annotation structure#117

Draft
CorentinGS wants to merge 1 commit into
mainfrom
fix/annotation-structure
Draft

feat: preserve PGN annotation structure#117
CorentinGS wants to merge 1 commit into
mainfrom
fix/annotation-structure

Conversation

@CorentinGS
Copy link
Copy Markdown
Owner

@CorentinGS CorentinGS commented Jun 2, 2026

Summary

This PR preserves PGN comment annotation structure instead of flattening every annotation into a single text/comment map representation.

It keeps each PGN {...} comment block as an ordered list of text and command annotation items, so round-tripping a game can preserve:

  • multiple comment blocks attached to the same move
  • interleaved text and command annotations inside one block
  • command annotation order
  • duplicate command annotations such as repeated [%clk ...]
  • annotations attached inside variations

New API

New exported types in move.go:

type CommentItemKind int

const (
    CommentText CommentItemKind = iota
    CommentCommand
)

type CommentItem struct {
    Kind  CommentItemKind
    Text  string
    Key   string
    Value string
}

type CommentBlock struct {
    Items []CommentItem
}

New exported method:

func (m *Move) CommentBlocks() []CommentBlock

Move.CommentBlocks() returns a defensive copy of the structured PGN comment blocks for a move. Use it when an importer/exporter needs access to the original block boundaries or ordered comment/command items.

Example:

for _, block := range move.CommentBlocks() {
    for _, item := range block.Items {
        switch item.Kind {
        case chess.CommentText:
            fmt.Println(item.Text)
        case chess.CommentCommand:
            fmt.Printf("%s=%s\n", item.Key, item.Value)
        }
    }
}

Legacy API Compatibility

The existing annotation helpers remain available:

  • Move.Comments()
  • Move.GetCommand()
  • Move.SetCommand()
  • Move.SetComment()
  • Move.AddComment()

Compatibility behavior:

  • Move.Comments() still returns flattened text comments.
  • Move.GetCommand(key) still returns a single value for a command key.
  • For duplicate structured commands, GetCommand returns the last occurrence, matching the practical behavior of map-based overwrites.
  • SetCommand updates the last matching structured command when possible, or appends one if none exists.
  • SetComment intentionally resets structured comment blocks back to the legacy single flattened comment representation.
  • AddComment appends text while keeping structured comment state in sync.

Breaking Changes

No source-level breaking change is intended.

Important behavior changes for PGN output:

  • PGN serialization now preserves parsed annotation block structure instead of always emitting flattened legacy comments/commands.
  • Parsed command annotations can preserve duplicate keys and ordering in CommentBlocks().
  • Legacy map-based command access still exposes only one value per key.

Implementation Notes

  • PGN parsing now builds CommentBlock / CommentItem values while parsing comments and command annotations.
  • Game serialization now writes structured comment blocks directly.
  • Legacy annotations are normalized into structured blocks internally before output.
  • Unreachable legacy annotation writer fallback code was removed after normalization made it dead code.
  • README and CHANGELOG were updated with the new structured annotation API.

Tests

Added coverage for:

  • annotation round-tripping with mixed text and command annotations
  • multiple PGN comment blocks on one move
  • duplicate command annotations
  • annotations inside variations
  • CommentBlocks() defensive-copy behavior
  • legacy annotation APIs interacting with structured comments
  • parser error paths for comments, commands, and variations
  • result parsing for drawn games
  • annotation writer edge cases

Local verification:

  • go test ./... passes
  • root package coverage improved from 87.0% to 89.0% locally after the added tests and dead-code simplification

@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 2, 2026

Codecov Report

❌ Patch coverage is 98.17073% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.18%. Comparing base (dd27ede) to head (60f222b).

Files with missing lines Patch % Lines
move.go 98.18% 1 Missing and 1 partial ⚠️
pgn.go 95.83% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #117      +/-   ##
==========================================
+ Coverage   70.32%   72.18%   +1.85%     
==========================================
  Files          27       27              
  Lines        4186     4257      +71     
==========================================
+ Hits         2944     3073     +129     
+ Misses       1110     1065      -45     
+ Partials      132      119      -13     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@CorentinGS CorentinGS force-pushed the fix/annotation-structure branch 2 times, most recently from d76a3b1 to fdd76b9 Compare June 3, 2026 07:43
@CorentinGS CorentinGS force-pushed the fix/annotation-structure branch from fdd76b9 to 60f222b Compare June 3, 2026 08:03
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