commenter 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ac1118f53d0edcb5d61c345203bdfdc20a5711c4971e68ae5bb9225744bae8b3
4
- data.tar.gz: 309347f180dee0adbe3a51c5d75fbf5aea0f95884667048c5a12a14faae479a2
3
+ metadata.gz: 99f441f58237864e8ea0bc5c8682f04d2b15757ef1f28c45317f0181d6621fb4
4
+ data.tar.gz: 0a48406aa86377612a2722455dad5013457a10805da311c74c1fbfb82cccafd7
5
5
  SHA512:
6
- metadata.gz: 7b90c6ec2ee250680be18144d1e32a285d26994d760806a0b2d555c964163bae84330fea41d7531b6aeb0a95d790e7ad01436592fb60640af22bcf094a5f91cb
7
- data.tar.gz: 93a090640a00260ca88db27683f8e8b38f71978e8040ab10f019013cc34472316729c66e19324b5fe1ce56b4f286abd8ff019c919e854cc2fc19d6212569185f
6
+ metadata.gz: 7c26ede9e2a9e4f5f060c0b8679cc9afcb36214a4c5b1a2f02c15a44cd6671d7b104954f4cf4b6df3df9f3806ab7c4d0418982a75371805c773c90064b82c8b9
7
+ data.tar.gz: 32dd2e31a603391bef5a112d0b10da62b7c29e249bcc76c3e5456aab939015ff51d21036108c3366743db11e01411a2bd285b7992f1a534009d5d4cc184e9d31
data/.rubocop.yml CHANGED
@@ -8,3 +8,7 @@ Style/StringLiterals:
8
8
 
9
9
  Style/StringLiteralsInInterpolation:
10
10
  EnforcedStyle: double_quotes
11
+
12
+ Metrics/ModuleLength:
13
+ Exclude:
14
+ - "spec/**/*"
data/.rubocop_todo.yml CHANGED
@@ -1,80 +1,21 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2025-06-04 10:43:42 UTC using RuboCop version 1.75.8.
3
+ # on 2026-03-16 10:59:51 UTC using RuboCop version 1.85.1.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
9
  # Offense count: 1
10
- # Configuration parameters: Severity, Include.
11
- # Include: **/*.gemspec
12
10
  Gemspec/RequiredRubyVersion:
13
11
  Exclude:
14
12
  - 'commenter.gemspec'
15
13
 
16
- # Offense count: 1
17
- # This cop supports safe autocorrection (--autocorrect).
18
- # Configuration parameters: EnforcedStyleAlignWith, Severity.
19
- # SupportedStylesAlignWith: start_of_line, begin
20
- Layout/BeginEndAlignment:
21
- Exclude:
22
- - 'lib/commenter/parser.rb'
23
-
24
- # Offense count: 1
25
- # This cop supports safe autocorrection (--autocorrect).
26
- # Configuration parameters: EnforcedStyle.
27
- # SupportedStyles: empty_lines, no_empty_lines
28
- Layout/EmptyLinesAroundBlockBody:
29
- Exclude:
30
- - 'spec/commenter_spec.rb'
31
-
32
- # Offense count: 2
33
- # This cop supports safe autocorrection (--autocorrect).
34
- # Configuration parameters: Width, AllowedPatterns.
35
- Layout/IndentationWidth:
36
- Exclude:
37
- - 'lib/commenter/parser.rb'
38
-
39
- # Offense count: 1
40
- # This cop supports safe autocorrection (--autocorrect).
41
- Layout/RescueEnsureAlignment:
42
- Exclude:
43
- - 'lib/commenter/parser.rb'
44
-
45
14
  # Offense count: 1
46
15
  Lint/NonLocalExitFromIterator:
47
16
  Exclude:
48
17
  - 'lib/commenter/filler.rb'
49
18
 
50
- # Offense count: 4
51
- # This cop supports safe autocorrection (--autocorrect).
52
- Lint/SafeNavigationWithEmpty:
53
- Exclude:
54
- - 'lib/commenter/parser.rb'
55
-
56
- # Offense count: 1
57
- # This cop supports safe autocorrection (--autocorrect).
58
- # Configuration parameters: AutoCorrect, IgnoreEmptyBlocks, AllowUnusedKeywordArguments.
59
- Lint/UnusedBlockArgument:
60
- Exclude:
61
- - 'lib/commenter/filler.rb'
62
-
63
- # Offense count: 4
64
- # This cop supports safe autocorrection (--autocorrect).
65
- # Configuration parameters: AutoCorrect, AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods, NotImplementedExceptions.
66
- # NotImplementedExceptions: NotImplementedError
67
- Lint/UnusedMethodArgument:
68
- Exclude:
69
- - 'lib/commenter/github_integration.rb'
70
-
71
- # Offense count: 1
72
- # This cop supports safe autocorrection (--autocorrect).
73
- # Configuration parameters: AutoCorrect.
74
- Lint/UselessAssignment:
75
- Exclude:
76
- - 'lib/commenter/parser.rb'
77
-
78
19
  # Offense count: 15
79
20
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
80
21
  Metrics/AbcSize:
@@ -89,9 +30,9 @@ Metrics/BlockLength:
89
30
  # Offense count: 5
90
31
  # Configuration parameters: CountComments, CountAsOne.
91
32
  Metrics/ClassLength:
92
- Max: 202
33
+ Max: 231
93
34
 
94
- # Offense count: 9
35
+ # Offense count: 8
95
36
  # Configuration parameters: AllowedMethods, AllowedPatterns.
96
37
  Metrics/CyclomaticComplexity:
97
38
  Max: 22
@@ -101,7 +42,7 @@ Metrics/CyclomaticComplexity:
101
42
  Metrics/MethodLength:
102
43
  Max: 53
103
44
 
104
- # Offense count: 9
45
+ # Offense count: 8
105
46
  # Configuration parameters: AllowedMethods, AllowedPatterns.
106
47
  Metrics/PerceivedComplexity:
107
48
  Max: 22
@@ -112,7 +53,7 @@ Metrics/PerceivedComplexity:
112
53
  # ForbiddenPrefixes: is_, has_, have_, does_
113
54
  # AllowedMethods: is_a?
114
55
  # MethodDefinitionMacros: define_method, define_singleton_method
115
- Naming/PredicateName:
56
+ Naming/PredicatePrefix:
116
57
  Exclude:
117
58
  - 'spec/**/*'
118
59
  - 'lib/commenter/comment.rb'
@@ -130,45 +71,9 @@ Style/Documentation:
130
71
  - 'lib/commenter/github_integration.rb'
131
72
  - 'lib/commenter/parser.rb'
132
73
 
133
- # Offense count: 1
134
- # This cop supports safe autocorrection (--autocorrect).
135
- # Configuration parameters: AutoCorrect, EnforcedStyle, AllowComments.
136
- # SupportedStyles: empty, nil, both
137
- Style/EmptyElse:
138
- Exclude:
139
- - 'lib/commenter/filler.rb'
140
-
141
74
  # Offense count: 3
142
75
  # This cop supports safe autocorrection (--autocorrect).
143
- Style/IfUnlessModifier:
144
- Exclude:
145
- - 'lib/commenter/cli.rb'
146
- - 'lib/commenter/github_integration.rb'
147
- - 'lib/commenter/parser.rb'
148
-
149
- # Offense count: 1
150
- # This cop supports safe autocorrection (--autocorrect).
151
- Style/MultilineIfModifier:
152
- Exclude:
153
- - 'lib/commenter/parser.rb'
154
-
155
- # Offense count: 2
156
- # This cop supports safe autocorrection (--autocorrect).
157
- Style/RescueModifier:
158
- Exclude:
159
- - 'lib/commenter/parser.rb'
160
-
161
- # Offense count: 2
162
- # This cop supports safe autocorrection (--autocorrect).
163
- # Configuration parameters: EnforcedStyle.
164
- # SupportedStyles: implicit, explicit
165
- Style/RescueStandardError:
166
- Exclude:
167
- - 'lib/commenter/parser.rb'
168
-
169
- # Offense count: 2
170
- # This cop supports safe autocorrection (--autocorrect).
171
- # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings.
76
+ # Configuration parameters: AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
172
77
  # URISchemes: http, https
173
78
  Layout/LineLength:
174
- Max: 132
79
+ Max: 138
data/CLAUDE.md ADDED
@@ -0,0 +1,58 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Commands
6
+
7
+ ```sh
8
+ bin/setup # install dependencies
9
+ bundle exec rake # default task: specs + RuboCop (this is what CI runs)
10
+ bundle exec rspec # all specs
11
+ bundle exec rspec spec/commenter/comment_spec.rb # single spec file
12
+ bundle exec rspec spec/commenter/comment_spec.rb:42 # single example
13
+ bundle exec rubocop # lint (part of default rake task — must be clean)
14
+ bundle exec rubocop -a # autocorrect
15
+ bundle exec exe/commenter # run the CLI locally
16
+ bin/console # interactive prompt
17
+ ```
18
+
19
+ CI (metanorma/ci `generic-rake.yml`) runs `bundle exec rake` on Ruby 3.2/3.4/4.0 across macOS/Ubuntu/Windows. RuboCop offenses fail CI — run `bundle exec rake`, not just rspec, before pushing. `Gemfile.lock` is gitignored; dependencies resolve fresh in CI.
20
+
21
+ Specs generate XLSX fixtures at runtime via `spec/support/xlsx_builder.rb` (plain rubyzip, no spreadsheet-writing dependency) plus row data in `spec/support/osd_fixtures.rb` — there are no binary fixture files in the repo.
22
+
23
+ ## What this gem does
24
+
25
+ Converts ISO comment sheets to structured YAML and back, and syncs comments to GitHub issues:
26
+
27
+ - **Import**: DOCX (ISO 2012-03 balloting template) or XLSX (ISO Online Standards Development exports) → YAML + schema file. Format auto-detected from extension.
28
+ - **Fill**: YAML → filled DOCX comment sheet (`data/iso_comment_template_2012-03.docx`), optional status-based cell shading.
29
+ - **GitHub round-trip**: `github-create` makes issues from YAML via Liquid templates; `github-retrieve` pulls `> **OBSERVATION:**` blockquotes from closed issues back into the YAML's observations field.
30
+
31
+ Plain text only — formulas/images/complex formatting are unsupported (docx gem limitation).
32
+
33
+ ## Architecture
34
+
35
+ Core flow: `Parser` → `CommentSheet` (metadata + `Comment` objects) → YAML / DOCX / GitHub issues.
36
+
37
+ - `Commenter::Comment` / `Commenter::CommentSheet` — data model. `Comment` carries the common fields plus optional OSD-specific fields (`user_name`, `resolution_status`, `motivation`, etc.) and a `github` sub-hash tracking issue state. The sheet's `version` field (`"2012-03"` vs `"osd"`) selects the output schema.
38
+ - `Commenter::Parser` — dispatches by format: `.docx` parsed inline with the `docx` gem; `.xlsx` delegated to `Parser::OsdXlsxParser` (uses `roo`).
39
+ - `Parser::OsdXlsxParser` — auto-detects two OSD export variants by header row ("resolved" 17-col starting `Comment ID`; "unresolved" 15-col starting `User name`), extracts metadata (date/reference/stage/titles) from header rows 1–2, maps columns by header name into `Comment` attributes, and synthesizes `observations` from `resolution_status` + `motivation`.
40
+ - `Commenter::Filler` — writes comments into the DOCX template table.
41
+ - `Commenter::GitHubIssueCreator` (`lib/commenter/github_integration.rb`) — Octokit-based; duplicate detection searches for a unique ID rendered from a configurable Liquid `unique_id` template (stage-aware by default: `[DIS] GB-001`), so the same comment ID at different ballot stages creates separate issues.
42
+ - `Commenter::Cli` (`lib/commenter/cli.rb`) — Thor CLI with subcommands `import`, `fill`, `github-create`, `github-retrieve`.
43
+
44
+ ### Schemas
45
+
46
+ `schema/iso_comment_2012-03.yaml` and `schema/iso_comment_osd.yaml` are JSON-Schema-style files for IDE validation. `import` copies the matching schema (based on sheet version) into the schema dir and stamps the YAML header with a `yaml-language-server` reference. When the data model changes, update the corresponding schema file.
47
+
48
+ ### Templates
49
+
50
+ `data/github_issue_title_template.liquid` and `data/github_issue_body_template.liquid` render issue titles/bodies; both are user-overridable via config. See README.adoc for the full variable list.
51
+
52
+ ## Conventions
53
+
54
+ - Ruby style: double-quoted strings (enforced by RuboCop).
55
+ - `data/` files (DOCX template, Liquid templates, sample config) are shipped with the gem and loaded relative to `__dir__` — do not treat them as disposable.
56
+ - Testing GitHub integration: use `--dry-run` with `GITHUB_TOKEN=dummy_token` — exercises template rendering without API calls.
57
+ - `sig/commenter.rbs` is a minimal stub; RBS coverage is not currently maintained.
58
+ - Release: pushing a `v*` tag triggers `.github/workflows/release.yml`. Version lives in `lib/commenter/version.rb`.
data/README.adoc CHANGED
@@ -7,14 +7,18 @@ image:https://img.shields.io/github/commits-since/metanorma/commenter/latest.svg
7
7
 
8
8
  == Purpose
9
9
 
10
- Commenter is a Ruby gem for working with ISO comment sheets in DOCX format.
10
+ Commenter is a Ruby gem for working with ISO comment sheets in DOCX and XLSX
11
+ format.
11
12
 
12
13
  It provides utilities for parsing, manipulating, and serializing ISO comment
13
- data, converting between DOCX and structured YAML with schema validation.
14
+ data, converting between DOCX/XLSX and structured YAML with schema validation.
14
15
 
15
- The format is taken from:
16
+ The supported input formats are:
16
17
 
17
18
  * "ISO/IEC/CEN/CENELEC electronic balloting commenting template/version 2012-03"
19
+ (DOCX)
20
+ * ISO Online Standards Development (OSD) XLSX exports (resolved and unresolved
21
+ comment sheets)
18
22
 
19
23
  This gem only supports plain text comment extraction and filling. Only use this
20
24
  to handle plain text comments and resolutions.
@@ -49,19 +53,27 @@ $ gem install commenter
49
53
 
50
54
  == Usage
51
55
 
52
- === Importing comments from DOCX
56
+ === Importing comments
53
57
 
54
- Convert an ISO comment sheet DOCX file to structured YAML:
58
+ Convert an ISO comment sheet (DOCX or XLSX) to structured YAML.
59
+ The format is auto-detected from the file extension.
55
60
 
56
61
  [source,shell]
57
62
  ----
63
+ # From DOCX (ISO 2012-03 template or OSD DOCX export)
58
64
  $ commenter import "ISO 80000-2 review comments.docx" -o comments.yaml
65
+
66
+ # From XLSX (ISO OSD resolved comment export)
67
+ $ commenter import "91855-Comments-resolved.xlsx" -o comments.yaml
68
+
69
+ # From XLSX (ISO OSD comments-only export)
70
+ $ commenter import "ISO 5843-6-Comments.xlsx" -o comments.yaml
59
71
  ----
60
72
 
61
73
  This will create two files:
62
74
 
63
75
  `comments.yaml`:: The structured comment data
64
- `schema/iso_comment_2012-03.yaml`:: The YAML schema for validation
76
+ `schema/iso_comment_2012-03.yaml` or `schema/iso_comment_osd.yaml`:: The YAML schema for validation (selected automatically based on input format)
65
77
 
66
78
  ==== Import options
67
79
 
@@ -75,6 +87,10 @@ Options:
75
87
  `-o, --output FILE`:: Output YAML file (default: comments.yaml)
76
88
  `-e, --exclude-observations`:: Skip the observations column
77
89
  `--schema-dir DIR`:: Directory for schema file (default: schema)
90
+ `--format FORMAT`:: Force input format (`docx` or `xlsx`; auto-detected by default)
91
+ `--sheet NAME`:: XLSX sheet name to parse (default: first sheet)
92
+ `--resolved-only`:: XLSX: use resolved comments sheet only
93
+ `--unresolved-only`:: XLSX: use unresolved comments sheet only
78
94
 
79
95
  ==== Metadata (extraction limitation)
80
96
 
@@ -227,6 +243,7 @@ The title template (`data/github_issue_title_template.liquid`) supports these va
227
243
  * `body` - Member body abbreviation
228
244
  * `type` - Comment type code
229
245
  * `clause`, `element`, `line_number` - Location information
246
+ * `unique_id` - Rendered unique identifier (see <<_duplicate_detection>>)
230
247
 
231
248
  ===== Body template variables
232
249
 
@@ -256,6 +273,7 @@ github:
256
273
  templates:
257
274
  title: "custom_title.liquid"
258
275
  body: "custom_body.liquid"
276
+ unique_id: "[{{ stage | upcase }}] {{ comment_id }}" # Optional: customize unique ID
259
277
  ----
260
278
  ====
261
279
 
@@ -301,9 +319,75 @@ $ commenter github comments.yaml --config github_config.yaml
301
319
 
302
320
  ==== Duplicate detection
303
321
 
304
- The gem automatically checks for existing issues to avoid duplicates by
305
- searching for the comment ID in issue titles. If an issue already exists, it
306
- will be skipped.
322
+ The gem automatically checks for existing issues to avoid duplicates. The duplicate
323
+ detection uses a **configurable unique identifier** that is:
324
+
325
+ . Rendered from a Liquid template
326
+ . Embedded in the issue title for human identification
327
+ . Used as the search pattern to find existing issues
328
+
329
+ This ensures that the search pattern always matches what appears in the title,
330
+ preventing synchronization issues.
331
+
332
+ ===== Unique ID template
333
+
334
+ The unique identifier is defined by the `unique_id` template in your configuration:
335
+
336
+ [source,yaml]
337
+ ----
338
+ github:
339
+ templates:
340
+ # Default: "[STAGE] COMMENT_ID"
341
+ unique_id: "[{{ stage | upcase }}] {{ comment_id }}"
342
+ ----
343
+
344
+ The default renders to: `[DIS] GB-001`
345
+
346
+ ===== Why stage-aware unique IDs?
347
+
348
+ ISO standards go through multiple ballot stages (WD, CD, DIS, FDIS). The same
349
+ comment ID (e.g., `GB-001`) appears at each stage, but represents **different
350
+ comments**:
351
+
352
+ | Stage | Comment ID | Unique ID | Same Issue? |
353
+ |-------|------------|-----------|-------------|
354
+ | CD | GB-001 | `[CD] GB-001` | No |
355
+ | DIS | GB-001 | `[DIS] GB-001` | No |
356
+
357
+ Without stage-aware unique IDs, CD `GB-001` and DIS `GB-001` would be treated as
358
+ duplicates, causing the DIS comment to be skipped.
359
+
360
+ ===== Custom unique ID patterns
361
+
362
+ You can customize the unique ID pattern to match your title template format:
363
+
364
+ [source,yaml]
365
+ ----
366
+ github:
367
+ templates:
368
+ # Different order
369
+ unique_id: "{{ comment_id }} [{{ stage | upcase }}]"
370
+
371
+ # With document prefix
372
+ unique_id: "{{ document }} - {{ comment_id }}"
373
+
374
+ # With body prefix (for multi-body projects)
375
+ unique_id: "{{ body }}/{{ comment_id }}"
376
+
377
+ # Single stage project (no stage needed)
378
+ unique_id: "{{ comment_id }}"
379
+ ----
380
+
381
+ ===== Using unique_id in templates
382
+
383
+ The `unique_id` is available as a variable in title and body templates:
384
+
385
+ [source,liquid]
386
+ ----
387
+ {{ unique_id }}: {{ brief_summary }} {% if document %}({{ document }}){% endif %}
388
+ ----
389
+
390
+ This renders to: `[DIS] GB-001: Clause 5.1 summary (ISO/DIS 2533)`
307
391
 
308
392
  ==== Retrieving observations from GitHub issues
309
393
 
@@ -462,12 +546,17 @@ The comment types are defined as follows:
462
546
  `te`:: Technical comment
463
547
  `ed`:: Editorial comment
464
548
 
549
+ NOTE: In generated YAML, types are stored in expanded form (`general`,
550
+ `technical`, `editorial`). Short codes are accepted as input anywhere and are
551
+ expanded when the comment is loaded.
552
+
465
553
  === Workflow integration
466
554
 
467
555
  [source,mermaid]
468
556
  ----
469
557
  flowchart LR
470
- A[ISO Comment Sheet DOCX] --> B[commenter import]
558
+ A1[ISO Comment Sheet DOCX] --> B[commenter import]
559
+ A2[ISO OSD XLSX export] --> B
471
560
  B --> C[YAML + Schema]
472
561
  C --> D[commenter github-create]
473
562
  D --> E[YAML + GitHub Info]
@@ -498,6 +587,8 @@ recognized and applied to the observations column:
498
587
 
499
588
  == Data model
500
589
 
590
+ === ISO 2012-03 template (DOCX)
591
+
501
592
  The comment structure follows this schema:
502
593
 
503
594
  [source,yaml]
@@ -525,14 +616,61 @@ comments: # Array of comment objects
525
616
  updated_at: string # ISO 8601 timestamp (optional)
526
617
  ----
527
618
 
619
+ === ISO OSD format (XLSX)
620
+
621
+ The OSD format includes additional metadata and resolution fields extracted from
622
+ the ISO Online Standards Development platform:
623
+
624
+ [source,yaml]
625
+ ----
626
+ version: "osd" # OSD format identifier
627
+ date: string # Date from XLSX header (auto-extracted)
628
+ document: string # Document reference, e.g. "ISO/DIS 5843-6(en)"
629
+ project: string # Project name (auto-extracted)
630
+ stage: string # Stage: WD, CD, DIS, FDIS, PRF, PUB (auto-extracted)
631
+ title_en: string # Document title in English (auto-extracted)
632
+ title_fr: string # Document title in French (auto-extracted)
633
+ comments:
634
+ - id: string # Comment ID (numeric from OSD)
635
+ body: string # User name / member body
636
+ locality:
637
+ clause: string # Clause number
638
+ element: string # Clause title
639
+ type: "ge" | "te" | "ed"
640
+ comments: string # Comment text
641
+ proposed_change: string
642
+ observations: string # Built from resolution_status + motivation
643
+ user_name: string # OSD user name
644
+ comment_type: string # Subtype: Editorial, General, Technical
645
+ resolution_status: string # Accepted, Partially accepted, Rejected, etc.
646
+ resolution_date: string # Date resolved
647
+ feedbacks: string # Discussion replies
648
+ motivation: string # Resolution justification
649
+ created_date: string # Date comment was created
650
+ stage_code: string # ISO stage code (e.g. "40.20")
651
+ ----
652
+
653
+ The OSD XLSX format comes in two variants, both auto-detected:
654
+
655
+ *Resolved*: 17 columns starting with `Comment ID`, includes resolution data
656
+ (`Resolution status`, `Motivation`, `Resolution Date`, `Stage code`)
657
+
658
+ *Unresolved/comments-only*: 15 columns starting with `User name`, includes
659
+ `Comment type`, `Comment/Motivation`, `Replies`, `Comment number`
660
+
528
661
 
529
662
  == Schema validation
530
663
 
531
- Each exported YAML file includes a schema reference for IDE support:
664
+ Each exported YAML file includes a schema reference for IDE support.
665
+ The schema is selected automatically based on the input format:
532
666
 
533
667
  [source,yaml]
534
668
  ----
669
+ # For ISO 2012-03 DOCX imports:
535
670
  # yaml-language-server: $schema=schema/iso_comment_2012-03.yaml
671
+
672
+ # For ISO OSD XLSX imports:
673
+ # yaml-language-server: $schema=schema/iso_comment_osd.yaml
536
674
  ----
537
675
 
538
676
  This enables:
@@ -627,7 +765,8 @@ The gem is organized into several key components:
627
765
 
628
766
  `Commenter::Comment`:: Represents individual comments with locality, type, and content
629
767
  `Commenter::CommentSheet`:: Container for multiple comments with metadata
630
- `Commenter::Parser`:: Handles DOCX parsing and YAML generation
768
+ `Commenter::Parser`:: Handles DOCX and XLSX parsing with auto-detection
769
+ `Commenter::Parser::OsdXlsxParser`:: Parses ISO OSD XLSX exports (resolved and unresolved variants)
631
770
  `Commenter::Filler`:: Fills DOCX templates with comment data
632
771
  `Commenter::GitHubIssueCreator`:: Creates GitHub issues from comments
633
772
 
@@ -635,7 +774,7 @@ The gem is organized into several key components:
635
774
 
636
775
  `Commenter::Cli`:: Thor-based command-line interface with subcommands:
637
776
 
638
- ** `import` - Convert DOCX to YAML
777
+ ** `import` - Convert DOCX or XLSX to YAML
639
778
  ** `fill` - Fill DOCX template from YAML
640
779
  ** `github-create` - Create GitHub issues from comments
641
780
  ** `github-retrieve` - Retrieve observations from GitHub issues
@@ -646,7 +785,8 @@ The gem is organized into several key components:
646
785
  * `data/github_issue_title_template.liquid` - GitHub issue title template
647
786
  * `data/github_issue_body_template.liquid` - GitHub issue body template
648
787
  * `data/github_config_sample.yaml` - Sample GitHub configuration
649
- * `schema/iso_comment_2012-03.yaml` - YAML schema for validation
788
+ * `schema/iso_comment_2012-03.yaml` - YAML schema for ISO 2012-03 validation
789
+ * `schema/iso_comment_osd.yaml` - YAML schema for ISO OSD format validation
650
790
 
651
791
  === Debugging
652
792
 
data/commenter.gemspec CHANGED
@@ -12,8 +12,8 @@ Gem::Specification.new do |spec|
12
12
  spec.authors = ["Ribose"]
13
13
  spec.email = ["open.source@ribose.com"]
14
14
 
15
- spec.summary = "Library to work with ISO comment sheets in DOCX format."
16
- spec.description = "Convert between ISO comment sheet DOCX and structured YAML with schema validation."
15
+ spec.summary = "Library to work with ISO comment sheets in DOCX and XLSX formats."
16
+ spec.description = "Convert between ISO comment sheets (DOCX and XLSX) and structured YAML with schema validation."
17
17
  spec.homepage = "https://github.com/metanorma/commenter"
18
18
  spec.license = "BSD-2-Clause"
19
19
  spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0")
@@ -35,5 +35,6 @@ Gem::Specification.new do |spec|
35
35
  spec.add_dependency "dotenv", "~> 2.8"
36
36
  spec.add_dependency "liquid", "~> 5.0"
37
37
  spec.add_dependency "octokit", "~> 6.0"
38
+ spec.add_dependency "roo", ">= 2.10"
38
39
  spec.add_dependency "thor", "~> 1.0"
39
40
  end
@@ -61,9 +61,17 @@ github:
61
61
 
62
62
  # Custom template paths (optional)
63
63
  templates:
64
+ # Title template for GitHub issues
65
+ # The {{ unique_id }} variable is required for duplicate detection
64
66
  title: "custom_title_template.liquid"
65
67
  body: "custom_body_template.liquid"
66
68
 
69
+ # Unique identifier template for duplicate detection
70
+ # This is rendered and used to search for existing issues
71
+ # Default: "[{{ stage | upcase }}] {{ comment_id }}"
72
+ # The unique_id is also available as {{ unique_id }} in title/body templates
73
+ unique_id: "[{{ stage | upcase }}] {{ comment_id }}"
74
+
67
75
  # Retrieval configuration for github-retrieve command
68
76
  retrieval:
69
77
  # Magic comment markers to look for in GitHub issue comments
@@ -1,4 +1,4 @@
1
- # {% if stage %}[{{ stage | upcase }}] {% endif %}{% if document %}{{ document }} - {% endif %}Comment {{ comment_id }}
1
+ # {% if stage %}[{{ stage | upcase }}] {% endif %}{{ comment_id }} {% if document %}({{ document }}){% endif %}
2
2
 
3
3
  **Stage:** {{ stage | default: "Not specified" }}
4
4
  **Document:** {{ document | default: "Not specified" }}
@@ -1 +1 @@
1
- {% if stage %}[{{ stage | upcase }}] {% endif %}{% if document %}{{ document }} - {% endif %}{{ comment_id }}: {{ brief_summary }}
1
+ {{ unique_id }}: {{ brief_summary }} {% if document %}({{ document }}){% endif %}
data/lib/commenter/cli.rb CHANGED
@@ -9,34 +9,43 @@ require "commenter/github_integration"
9
9
 
10
10
  module Commenter
11
11
  class Cli < Thor
12
- desc "import INPUT.docx", "Convert DOCX comment sheet to YAML"
12
+ desc "import INPUT", "Convert comment sheet (DOCX or XLSX) to YAML"
13
13
  option :output, type: :string, aliases: :o, default: "comments.yaml", desc: "Output YAML file"
14
14
  option :exclude_observations, type: :boolean, aliases: :e, desc: "Exclude observations column"
15
15
  option :schema_dir, type: :string, default: "schema", desc: "Directory for schema file"
16
- def import(input_docx)
16
+ option :format, type: :string, desc: "Force input format (docx or xlsx)"
17
+ option :sheet, type: :string, desc: "XLSX sheet name to parse (default: first sheet)"
18
+ option :resolved_only, type: :boolean, desc: "XLSX: use resolved comments sheet only"
19
+ option :unresolved_only, type: :boolean, desc: "XLSX: use unresolved comments sheet only"
20
+ def import(input_file)
17
21
  output_yaml = options[:output]
18
22
  schema_dir = options[:schema_dir]
19
23
 
20
24
  # Ensure schema directory exists
21
25
  FileUtils.mkdir_p(schema_dir) unless Dir.exist?(schema_dir)
22
26
 
23
- # Parse the DOCX file
27
+ # Parse the input file
24
28
  parser = Parser.new
25
- comment_sheet = parser.parse(input_docx, options)
29
+ comment_sheet = parser.parse(input_file, options)
30
+
31
+ # Determine which schema to use based on version
32
+ schema_name = comment_sheet.version == "osd" ? "iso_comment_osd.yaml" : "iso_comment_2012-03.yaml"
26
33
 
27
34
  # Write the YAML data file with schema reference
28
- yaml_content = generate_yaml_with_header(comment_sheet.to_yaml_h, schema_dir)
35
+ yaml_content = generate_yaml_with_header(comment_sheet.to_yaml_h, schema_dir, schema_name)
29
36
  File.write(output_yaml, yaml_content)
30
37
 
31
38
  # Copy schema file to output directory
32
- schema_source = File.join(__dir__, "../../schema/iso_comment_2012-03.yaml")
33
- schema_target = File.join(schema_dir, "iso_comment_2012-03.yaml")
39
+ schema_source = File.join(__dir__, "../../schema/#{schema_name}")
40
+ schema_target = File.join(schema_dir, schema_name)
34
41
 
35
42
  # Only copy if source and target are different
36
43
  FileUtils.cp(schema_source, schema_target) unless File.expand_path(schema_source) == File.expand_path(schema_target)
37
44
 
38
- puts "Converted #{input_docx} to #{output_yaml}"
39
- puts "Schema file created at #{schema_target}"
45
+ puts "Converted #{input_file} to #{output_yaml}"
46
+ puts " Version: #{comment_sheet.version}"
47
+ puts " Comments: #{comment_sheet.comments.length}"
48
+ puts " Schema: #{schema_target}"
40
49
  end
41
50
 
42
51
  desc "fill INPUT.yaml", "Fill DOCX template from YAML comments"
@@ -206,8 +215,8 @@ module Commenter
206
215
 
207
216
  private
208
217
 
209
- def generate_yaml_with_header(data, schema_dir)
210
- schema_path = File.join(schema_dir, "iso_comment_2012-03.yaml")
218
+ def generate_yaml_with_header(data, schema_dir, schema_name = "iso_comment_2012-03.yaml")
219
+ schema_path = File.join(schema_dir, schema_name)
211
220
  header = "# yaml-language-server: $schema=#{schema_path}\n\n"
212
221
  header + data.to_yaml
213
222
  end