commenter 0.3.0 → 0.4.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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -0
  3. data/CLAUDE.md +17 -7
  4. data/CONTEXT.md +27 -0
  5. data/README.adoc +92 -17
  6. data/commenter.gemspec +1 -0
  7. data/lib/commenter/ballot.rb +67 -0
  8. data/lib/commenter/ballot_report.rb +60 -0
  9. data/lib/commenter/cli.rb +62 -43
  10. data/lib/commenter/comment.rb +103 -126
  11. data/lib/commenter/comment_locality.rb +18 -0
  12. data/lib/commenter/comment_sheet.rb +31 -63
  13. data/lib/commenter/comment_type.rb +39 -0
  14. data/lib/commenter/disposition_status.rb +37 -0
  15. data/lib/commenter/filler.rb +91 -59
  16. data/lib/commenter/github_info.rb +23 -0
  17. data/lib/commenter/github_integration.rb +17 -71
  18. data/lib/commenter/github_session.rb +35 -0
  19. data/lib/commenter/parser/osd_xlsx_parser.rb +1 -12
  20. data/lib/commenter/parser/track_change_docx_parser.rb +330 -0
  21. data/lib/commenter/parser.rb +10 -13
  22. data/lib/commenter/version.rb +1 -1
  23. data/lib/commenter.rb +16 -5
  24. data/spec/commenter/ballot_report_spec.rb +62 -0
  25. data/spec/commenter/ballot_spec.rb +68 -0
  26. data/spec/commenter/cli_spec.rb +41 -0
  27. data/spec/commenter/comment_sheet_spec.rb +35 -10
  28. data/spec/commenter/comment_spec.rb +22 -9
  29. data/spec/commenter/comment_type_spec.rb +45 -0
  30. data/spec/commenter/disposition_status_spec.rb +40 -0
  31. data/spec/commenter/filler_spec.rb +119 -0
  32. data/spec/commenter/github_integration_spec.rb +65 -32
  33. data/spec/commenter/osd_xlsx_parser_spec.rb +3 -2
  34. data/spec/commenter/track_change_docx_parser_spec.rb +277 -0
  35. data/spec/support/redline_docx_builder.rb +95 -0
  36. data/spec/support/xlsx_builder.rb +17 -12
  37. metadata +32 -2
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zip"
4
+
5
+ # Builds minimal redline DOCX files for specs, without binary fixtures. The
6
+ # document body is assembled from a paragraph DSL:
7
+ #
8
+ # { heading: "1", level: 1, text: "Scope" }
9
+ # { text: "Body text " }
10
+ # { change: :ins, id: "1", author: "CHEN Yvonne", date: "2025-12-05T20:10:00Z",
11
+ # text: "inserted" }
12
+ # { marker: true } # self-closing paragraph-mark insertion inside rPr
13
+ # { anchor: "74", text: "Anchored text" } # w:commentRangeStart/End around text
14
+ #
15
+ # Multiple entries in one paragraph can be given via :parts.
16
+ #
17
+ # comments: [{ id: "74", author: "CHEN Yvonne", date: "...", text: "remark" }]
18
+ # are written to word/comments.xml.
19
+ module RedlineDocxBuilder
20
+ W_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
21
+ private_constant :W_NS
22
+
23
+ HEADING_LEVEL_STYLE = { 1 => "Heading1", 2 => "Heading2", 3 => "Heading3" }.freeze
24
+ CHANGE_TAG = { ins: "w:ins", del: "w:del", moveFrom: "w:moveFrom", moveTo: "w:moveTo" }.freeze
25
+ private_constant :HEADING_LEVEL_STYLE, :CHANGE_TAG
26
+
27
+ def self.write(path, paragraphs, comments: [])
28
+ # Written via an in-memory buffer and File.binwrite rather than
29
+ # Zip::File#create + close: rubyzip commits the latter with File.rename,
30
+ # which fails with EACCES on Windows when the caller still holds an open
31
+ # handle on the destination (e.g. an unclosed Tempfile).
32
+ buffer = Zip::OutputStream.write_buffer do |out|
33
+ out.put_next_entry("word/document.xml")
34
+ out.write(document_xml(paragraphs))
35
+ out.put_next_entry("word/comments.xml")
36
+ out.write(comments_xml(comments))
37
+ end
38
+ File.binwrite(path, buffer.string)
39
+ path
40
+ end
41
+
42
+ def self.document_xml(paragraphs)
43
+ <<~XML
44
+ <?xml version="1.0" encoding="UTF-8"?>
45
+ <w:document xmlns:w="#{W_NS}"><w:body>#{paragraphs.map { |p| paragraph_xml(p) }.join}</w:body></w:document>
46
+ XML
47
+ end
48
+
49
+ def self.comments_xml(comments)
50
+ entries = comments.map do |c|
51
+ attrs = %(w:id="#{c[:id]}" w:author="#{c[:author]}" w:date="#{c[:date]}" w:initials="X")
52
+ %(<w:comment #{attrs}><w:p><w:r><w:t>#{c[:text]}</w:t></w:r></w:p></w:comment>)
53
+ end
54
+
55
+ <<~XML
56
+ <?xml version="1.0" encoding="UTF-8"?>
57
+ <w:comments xmlns:w="#{W_NS}">#{entries.join}</w:comments>
58
+ XML
59
+ end
60
+
61
+ def self.paragraph_xml(paragraph)
62
+ if paragraph[:marker]
63
+ return %(<w:p><w:pPr><w:rPr><w:ins w:id="#{paragraph[:id]}" w:author="#{paragraph[:author]}"/></w:rPr></w:pPr></w:p>)
64
+ end
65
+
66
+ inner = if paragraph[:parts]
67
+ paragraph[:parts].map { |part| part_xml(part) }.join
68
+ elsif paragraph[:heading]
69
+ %(<w:r><w:t>#{paragraph[:text]}</w:t></w:r>)
70
+ elsif paragraph[:change]
71
+ change_xml(paragraph)
72
+ else
73
+ %(<w:r><w:t>#{paragraph[:text]}</w:t></w:r>)
74
+ end
75
+
76
+ ppr = paragraph[:heading] ? %(<w:pPr><w:pStyle w:val="#{HEADING_LEVEL_STYLE.fetch(paragraph[:level], 1)}"/></w:pPr>) : ""
77
+ anchor = paragraph[:anchor] ? %(<w:commentRangeStart w:id="#{paragraph[:anchor]}"/>) : ""
78
+ anchor_end = paragraph[:anchor] ? %(<w:commentRangeEnd w:id="#{paragraph[:anchor]}"/>) : ""
79
+ "<w:p>#{ppr}#{anchor}#{inner}#{anchor_end}</w:p>"
80
+ end
81
+
82
+ def self.part_xml(part)
83
+ part[:change] ? change_xml(part) : %(<w:r><w:t>#{part[:text]}</w:t></w:r>)
84
+ end
85
+
86
+ def self.change_xml(change)
87
+ tag = CHANGE_TAG.fetch(change[:change])
88
+ attrs = %(w:id="#{change[:id]}" w:author="#{change[:author]}" w:date="#{change[:date]}")
89
+
90
+ return %(<#{tag} #{attrs}>#{change[:nested].map { |child| change_xml(child) }.join}</#{tag}>) if change[:nested]
91
+
92
+ text_tag = change[:change] == :del ? "w:delText" : "w:t"
93
+ %(<#{tag} #{attrs}><w:r><#{text_tag}>#{change[:text]}</#{text_tag}></w:r></#{tag}>)
94
+ end
95
+ end
@@ -18,22 +18,27 @@ module XlsxBuilder
18
18
  shared = []
19
19
  worksheets = sheets.map { |name, rows| [name, worksheet_xml(rows, shared)] }
20
20
 
21
- zip = Zip::File.new(path, create: true)
22
- write_entry(zip, "[Content_Types].xml", content_types_xml(worksheets.size))
23
- write_entry(zip, "_rels/.rels", root_rels_xml)
24
- write_entry(zip, "xl/workbook.xml", workbook_xml(worksheets.map(&:first)))
25
- write_entry(zip, "xl/_rels/workbook.xml.rels", workbook_rels_xml(worksheets.size))
26
- write_entry(zip, "xl/sharedStrings.xml", shared_strings_xml(shared))
27
- write_entry(zip, "xl/styles.xml", styles_xml)
28
- worksheets.each_with_index do |(_name, xml), index|
29
- write_entry(zip, "xl/worksheets/sheet#{index + 1}.xml", xml)
21
+ # In-memory buffer + File.binwrite rather than Zip::File#create + close:
22
+ # rubyzip commits the latter with File.rename, which fails with EACCES on
23
+ # Windows when the caller still holds an open handle on the destination.
24
+ buffer = Zip::OutputStream.write_buffer do |out|
25
+ write_entry(out, "[Content_Types].xml", content_types_xml(worksheets.size))
26
+ write_entry(out, "_rels/.rels", root_rels_xml)
27
+ write_entry(out, "xl/workbook.xml", workbook_xml(worksheets.map(&:first)))
28
+ write_entry(out, "xl/_rels/workbook.xml.rels", workbook_rels_xml(worksheets.size))
29
+ write_entry(out, "xl/sharedStrings.xml", shared_strings_xml(shared))
30
+ write_entry(out, "xl/styles.xml", styles_xml)
31
+ worksheets.each_with_index do |(_name, xml), index|
32
+ write_entry(out, "xl/worksheets/sheet#{index + 1}.xml", xml)
33
+ end
30
34
  end
31
- zip.close
35
+ File.binwrite(path, buffer.string)
32
36
  path
33
37
  end
34
38
 
35
- def self.write_entry(zip, name, content)
36
- zip.get_output_stream(name) { |stream| stream.write(content) }
39
+ def self.write_entry(out, name, content)
40
+ out.put_next_entry(name)
41
+ out.write(content)
37
42
  end
38
43
 
39
44
  def self.worksheet_xml(rows, shared)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: commenter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-08-26 00:00:00.000000000 Z
11
+ date: 2026-08-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '5.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: lutaml-model
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.8'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.8'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: octokit
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -125,6 +139,7 @@ files:
125
139
  - ".rubocop_todo.yml"
126
140
  - CLAUDE.md
127
141
  - CODE_OF_CONDUCT.md
142
+ - CONTEXT.md
128
143
  - Gemfile
129
144
  - README.adoc
130
145
  - Rakefile
@@ -136,25 +151,40 @@ files:
136
151
  - data/iso_comment_template_2012-03.docx
137
152
  - exe/commenter
138
153
  - lib/commenter.rb
154
+ - lib/commenter/ballot.rb
155
+ - lib/commenter/ballot_report.rb
139
156
  - lib/commenter/cli.rb
140
157
  - lib/commenter/comment.rb
158
+ - lib/commenter/comment_locality.rb
141
159
  - lib/commenter/comment_sheet.rb
160
+ - lib/commenter/comment_type.rb
161
+ - lib/commenter/disposition_status.rb
142
162
  - lib/commenter/filler.rb
163
+ - lib/commenter/github_info.rb
143
164
  - lib/commenter/github_integration.rb
165
+ - lib/commenter/github_session.rb
144
166
  - lib/commenter/parser.rb
145
167
  - lib/commenter/parser/osd_xlsx_parser.rb
168
+ - lib/commenter/parser/track_change_docx_parser.rb
146
169
  - lib/commenter/version.rb
147
170
  - schema/iso_comment_2012-03.yaml
148
171
  - schema/iso_comment_osd.yaml
149
172
  - sig/commenter.rbs
173
+ - spec/commenter/ballot_report_spec.rb
174
+ - spec/commenter/ballot_spec.rb
150
175
  - spec/commenter/cli_spec.rb
151
176
  - spec/commenter/comment_sheet_spec.rb
152
177
  - spec/commenter/comment_spec.rb
178
+ - spec/commenter/comment_type_spec.rb
179
+ - spec/commenter/disposition_status_spec.rb
180
+ - spec/commenter/filler_spec.rb
153
181
  - spec/commenter/github_integration_spec.rb
154
182
  - spec/commenter/osd_xlsx_parser_spec.rb
183
+ - spec/commenter/track_change_docx_parser_spec.rb
155
184
  - spec/commenter_spec.rb
156
185
  - spec/spec_helper.rb
157
186
  - spec/support/osd_fixtures.rb
187
+ - spec/support/redline_docx_builder.rb
158
188
  - spec/support/xlsx_builder.rb
159
189
  homepage: https://github.com/metanorma/commenter
160
190
  licenses: