commenter 0.3.1 → 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.
- checksums.yaml +4 -4
- data/CONTEXT.md +27 -0
- data/README.adoc +57 -11
- data/commenter.gemspec +1 -0
- data/lib/commenter/ballot.rb +67 -0
- data/lib/commenter/ballot_report.rb +60 -0
- data/lib/commenter/cli.rb +58 -31
- data/lib/commenter/comment.rb +100 -115
- data/lib/commenter/comment_locality.rb +18 -0
- data/lib/commenter/comment_sheet.rb +28 -68
- data/lib/commenter/comment_type.rb +5 -1
- data/lib/commenter/disposition_status.rb +37 -0
- data/lib/commenter/filler.rb +91 -59
- data/lib/commenter/github_info.rb +23 -0
- data/lib/commenter/github_integration.rb +6 -11
- data/lib/commenter/parser/osd_xlsx_parser.rb +1 -3
- data/lib/commenter/parser/track_change_docx_parser.rb +3 -5
- data/lib/commenter/parser.rb +4 -5
- data/lib/commenter/version.rb +1 -1
- data/lib/commenter.rb +11 -5
- data/spec/commenter/ballot_report_spec.rb +62 -0
- data/spec/commenter/ballot_spec.rb +68 -0
- data/spec/commenter/cli_spec.rb +41 -0
- data/spec/commenter/comment_sheet_spec.rb +10 -10
- data/spec/commenter/comment_spec.rb +22 -9
- data/spec/commenter/disposition_status_spec.rb +40 -0
- data/spec/commenter/filler_spec.rb +119 -0
- data/spec/commenter/osd_xlsx_parser_spec.rb +3 -2
- data/spec/support/redline_docx_builder.rb +11 -4
- data/spec/support/xlsx_builder.rb +17 -12
- metadata +26 -2
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe Commenter::Ballot do
|
|
6
|
+
def sheet(body:, ids:, document: nil, stage: nil)
|
|
7
|
+
Commenter::CommentSheet.new(
|
|
8
|
+
version: "2012-03",
|
|
9
|
+
document: document,
|
|
10
|
+
stage: stage,
|
|
11
|
+
comments: ids.map do |id|
|
|
12
|
+
Commenter::Comment.new(id: id, body: body, comments: "Comment #{id} from #{body}")
|
|
13
|
+
end
|
|
14
|
+
)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "combines comments from all sheets in input order" do
|
|
18
|
+
merged = described_class.new([
|
|
19
|
+
sheet(body: "DE", ids: %w[DE-001 DE-002]),
|
|
20
|
+
sheet(body: "US", ids: %w[US-001])
|
|
21
|
+
]).merge
|
|
22
|
+
|
|
23
|
+
expect(merged.comments.map(&:id)).to eq(%w[DE-001 DE-002 US-001])
|
|
24
|
+
expect(merged.comments.map(&:body)).to eq(%w[DE DE US])
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "takes metadata from the first sheet that provides it" do
|
|
28
|
+
merged = described_class.new([
|
|
29
|
+
sheet(body: "DE", ids: %w[DE-001], stage: nil),
|
|
30
|
+
sheet(body: "US", ids: %w[US-001], document: "ISO 2533:2026", stage: "DIS")
|
|
31
|
+
]).merge
|
|
32
|
+
|
|
33
|
+
expect(merged.version).to eq("2012-03")
|
|
34
|
+
expect(merged.document).to eq("ISO 2533:2026")
|
|
35
|
+
expect(merged.stage).to eq("DIS")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "silently deduplicates identical comments sharing an ID" do
|
|
39
|
+
duplicate = Commenter::Comment.new(id: "DE-001", body: "DE", comments: "Same text", proposed_change: "Same change")
|
|
40
|
+
|
|
41
|
+
merged = described_class.new([
|
|
42
|
+
Commenter::CommentSheet.new(comments: [duplicate]),
|
|
43
|
+
Commenter::CommentSheet.new(comments: [
|
|
44
|
+
Commenter::Comment.new(id: "DE-001", body: "DE", comments: "Same text",
|
|
45
|
+
proposed_change: "Same change")
|
|
46
|
+
])
|
|
47
|
+
]).merge
|
|
48
|
+
|
|
49
|
+
expect(merged.comments.map(&:id)).to eq(["DE-001"])
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "raises when the same ID carries differing content" do
|
|
53
|
+
conflicting = Commenter::Comment.new(id: "DE-001", body: "DE", comments: "Different text entirely")
|
|
54
|
+
|
|
55
|
+
expect do
|
|
56
|
+
described_class.new([
|
|
57
|
+
sheet(body: "DE", ids: %w[DE-001]),
|
|
58
|
+
Commenter::CommentSheet.new(comments: [conflicting])
|
|
59
|
+
]).merge
|
|
60
|
+
end.to raise_error(Commenter::Error, /DE-001/)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "returns an empty sheet when no input sheets are given" do
|
|
64
|
+
merged = described_class.new([]).merge
|
|
65
|
+
|
|
66
|
+
expect(merged.comments).to eq([])
|
|
67
|
+
end
|
|
68
|
+
end
|
data/spec/commenter/cli_spec.rb
CHANGED
|
@@ -32,4 +32,45 @@ RSpec.describe Commenter::Cli do
|
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
|
+
|
|
36
|
+
describe "#merge" do
|
|
37
|
+
it "writes a combined sheet with schema reference" do
|
|
38
|
+
Dir.mktmpdir do |dir|
|
|
39
|
+
one = File.join(dir, "de.yaml")
|
|
40
|
+
two = File.join(dir, "us.yaml")
|
|
41
|
+
File.write(one, { "version" => "2012-03", "comments" => [
|
|
42
|
+
{ "id" => "DE-001", "body" => "DE", "comments" => "First" }
|
|
43
|
+
] }.to_yaml)
|
|
44
|
+
File.write(two, { "comments" => [
|
|
45
|
+
{ "id" => "US-001", "body" => "US", "comments" => "Second" }
|
|
46
|
+
] }.to_yaml)
|
|
47
|
+
output = File.join(dir, "out", "merged.yaml")
|
|
48
|
+
schema_dir = File.join(dir, "out", "schema")
|
|
49
|
+
|
|
50
|
+
described_class.start(["merge", one, two, "--output", output, "--schema-dir", schema_dir])
|
|
51
|
+
|
|
52
|
+
data = YAML.safe_load_file(output)
|
|
53
|
+
expect(data["comments"].map { |comment| comment["id"] }).to eq(%w[DE-001 US-001])
|
|
54
|
+
expect(File).to exist(File.join(schema_dir, "iso_comment_2012-03.yaml"))
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
describe "#stats" do
|
|
60
|
+
it "prints a markdown disposition report" do
|
|
61
|
+
Dir.mktmpdir do |dir|
|
|
62
|
+
input = File.join(dir, "comments.yaml")
|
|
63
|
+
File.write(input, { "comments" => [
|
|
64
|
+
{ "id" => "DE-001", "body" => "DE", "comments" => "A", "observations" => "Accepted." },
|
|
65
|
+
{ "id" => "US-001", "body" => "US", "comments" => "B", "observations" => "Noted" }
|
|
66
|
+
] }.to_yaml)
|
|
67
|
+
|
|
68
|
+
expect { described_class.start(["stats", input]) }
|
|
69
|
+
.to output(a_string_including(
|
|
70
|
+
"| DE | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |",
|
|
71
|
+
"| US | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |"
|
|
72
|
+
)).to_stdout
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
35
76
|
end
|
|
@@ -115,21 +115,21 @@ RSpec.describe Commenter::CommentSheet do
|
|
|
115
115
|
|
|
116
116
|
hash = sheet.to_h
|
|
117
117
|
|
|
118
|
-
expect(hash[
|
|
119
|
-
expect(hash[
|
|
120
|
-
expect(hash[
|
|
121
|
-
expect(hash[
|
|
122
|
-
expect(hash[
|
|
123
|
-
expect(hash[
|
|
124
|
-
expect(hash[
|
|
118
|
+
expect(hash["version"]).to eq("2012-03")
|
|
119
|
+
expect(hash["date"]).to eq("2024-06-04")
|
|
120
|
+
expect(hash["document"]).to eq("Test Document")
|
|
121
|
+
expect(hash["project"]).to eq("Test Project")
|
|
122
|
+
expect(hash["stage"]).to eq("DIS")
|
|
123
|
+
expect(hash["comments"]).to be_an(Array)
|
|
124
|
+
expect(hash["comments"].first).to be_a(Hash)
|
|
125
125
|
end
|
|
126
126
|
|
|
127
|
-
it "
|
|
127
|
+
it "omits unset attributes from the hash" do
|
|
128
128
|
sheet = described_class.new({})
|
|
129
129
|
hash = sheet.to_h
|
|
130
130
|
|
|
131
|
-
expect(hash).
|
|
132
|
-
expect(
|
|
131
|
+
expect(hash).not_to have_key("stage")
|
|
132
|
+
expect(sheet.version).to eq("2012-03")
|
|
133
133
|
end
|
|
134
134
|
end
|
|
135
135
|
|
|
@@ -9,7 +9,7 @@ RSpec.describe Commenter::Comment do
|
|
|
9
9
|
id: "US-001",
|
|
10
10
|
body: "US",
|
|
11
11
|
locality: { clause: "5.1", element: "Table 1", line_number: "42" },
|
|
12
|
-
type: "
|
|
12
|
+
type: "technical",
|
|
13
13
|
comments: "Test comment",
|
|
14
14
|
proposed_change: "Test change",
|
|
15
15
|
observations: "Test observations"
|
|
@@ -28,6 +28,19 @@ RSpec.describe Commenter::Comment do
|
|
|
28
28
|
expect(comment.observations).to eq("Test observations")
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
it "expands short type codes when loaded from YAML" do
|
|
32
|
+
yaml = <<~YAML
|
|
33
|
+
---
|
|
34
|
+
id: US-009
|
|
35
|
+
type: te
|
|
36
|
+
YAML
|
|
37
|
+
|
|
38
|
+
comment = described_class.from_yaml(yaml)
|
|
39
|
+
|
|
40
|
+
expect(comment.id).to eq("US-009")
|
|
41
|
+
expect(comment.type).to eq("technical")
|
|
42
|
+
end
|
|
43
|
+
|
|
31
44
|
it "handles string keys in attributes" do
|
|
32
45
|
attributes = {
|
|
33
46
|
"id" => "US-001",
|
|
@@ -162,7 +175,7 @@ RSpec.describe Commenter::Comment do
|
|
|
162
175
|
id: "US-001",
|
|
163
176
|
body: "US",
|
|
164
177
|
locality: { clause: "5.1" },
|
|
165
|
-
type: "
|
|
178
|
+
type: "technical",
|
|
166
179
|
comments: "Test",
|
|
167
180
|
proposed_change: "Change",
|
|
168
181
|
observations: "Obs"
|
|
@@ -170,13 +183,13 @@ RSpec.describe Commenter::Comment do
|
|
|
170
183
|
|
|
171
184
|
hash = comment.to_h
|
|
172
185
|
|
|
173
|
-
expect(hash[
|
|
174
|
-
expect(hash[
|
|
175
|
-
expect(hash[
|
|
176
|
-
expect(hash[
|
|
177
|
-
expect(hash[
|
|
178
|
-
expect(hash[
|
|
179
|
-
expect(hash[
|
|
186
|
+
expect(hash["id"]).to eq("US-001")
|
|
187
|
+
expect(hash["body"]).to eq("US")
|
|
188
|
+
expect(hash["locality"]["clause"]).to eq("5.1")
|
|
189
|
+
expect(hash["type"]).to eq("technical")
|
|
190
|
+
expect(hash["comments"]).to eq("Test")
|
|
191
|
+
expect(hash["proposed_change"]).to eq("Change")
|
|
192
|
+
expect(hash["observations"]).to eq("Obs")
|
|
180
193
|
end
|
|
181
194
|
end
|
|
182
195
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe Commenter::DispositionStatus do
|
|
6
|
+
describe ".match" do
|
|
7
|
+
it "classifies accepted dispositions" do
|
|
8
|
+
expect(described_class.match("Accepted")).to eq(:accepted)
|
|
9
|
+
expect(described_class.match("accepted.")).to eq(:accepted)
|
|
10
|
+
expect(described_class.match("Partially accepted")).to eq(:accepted)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "classifies accept-with-modifications before the bare accept pattern" do
|
|
14
|
+
expect(described_class.match("Accept with modifications")).to eq(:accept_with_modifications)
|
|
15
|
+
expect(described_class.match("Accepted with modifications")).to eq(:accept_with_modifications)
|
|
16
|
+
expect(described_class.match("Accepted, with changes")).to eq(:accept_with_modifications)
|
|
17
|
+
expect(described_class.match("AWM")).to eq(:accept_with_modifications)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "classifies rejection including the 'not accepted' phrasing" do
|
|
21
|
+
expect(described_class.match("Rejected")).to eq(:rejected)
|
|
22
|
+
expect(described_class.match("Not accepted")).to eq(:rejected)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "classifies noted, todo, and non-matching text" do
|
|
26
|
+
expect(described_class.match("Noted")).to eq(:noted)
|
|
27
|
+
expect(described_class.match("TODO: review")).to eq(:todo)
|
|
28
|
+
expect(described_class.match("Deferred to next revision")).to be_nil
|
|
29
|
+
expect(described_class.match("")).to be_nil
|
|
30
|
+
expect(described_class.match(nil)).to be_nil
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe ".statuses" do
|
|
35
|
+
it "lists the canonical statuses" do
|
|
36
|
+
expect(described_class.statuses)
|
|
37
|
+
.to eq(%i[accept_with_modifications rejected accepted noted todo])
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "nokogiri"
|
|
5
|
+
require "zip"
|
|
6
|
+
|
|
7
|
+
RSpec.describe Commenter::Filler do
|
|
8
|
+
let(:template_path) { File.expand_path("../../data/iso_comment_template_2012-03.docx", __dir__) }
|
|
9
|
+
|
|
10
|
+
def comment(attrs)
|
|
11
|
+
Commenter::Comment.new({ body: "US", type: "technical", comments: "Text" }.merge(attrs))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def header_xml(output_path)
|
|
15
|
+
Zip::File.open(output_path) do |zip|
|
|
16
|
+
zip.entries.find { |entry| entry.name == "word/header2.xml" }.get_input_stream(&:read)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def body_xml(output_path)
|
|
21
|
+
Zip::File.open(output_path) do |zip|
|
|
22
|
+
zip.entries.find { |entry| entry.name == "word/document.xml" }.get_input_stream(&:read)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Rows of the comment table as arrays of cell text. Read via Nokogiri over
|
|
27
|
+
# the extracted XML rather than Docx::Document, whose open zip handle keeps
|
|
28
|
+
# the file locked on Windows and breaks Dir.mktmpdir teardown.
|
|
29
|
+
def body_rows(output_path)
|
|
30
|
+
document = Nokogiri::XML(body_xml(output_path))
|
|
31
|
+
document.xpath("//w:tbl").flat_map { |table| table.xpath("./w:tr") }.map do |row|
|
|
32
|
+
row.xpath("./w:tc").map { |cell| cell.xpath(".//w:t").map(&:text).join }
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "writes comment rows into the template table" do
|
|
37
|
+
Dir.mktmpdir do |dir|
|
|
38
|
+
output = File.join(dir, "filled.docx")
|
|
39
|
+
described_class.new.fill(template_path, output, [
|
|
40
|
+
comment(id: "US-001", locality: { clause: "5.2.1", element: "Table 3", line_number: "45" },
|
|
41
|
+
proposed_change: "Fix them", observations: "Accepted")
|
|
42
|
+
])
|
|
43
|
+
|
|
44
|
+
row = body_rows(output).first
|
|
45
|
+
expect(row[0]).to include("US-001")
|
|
46
|
+
expect(row[1]).to include("45")
|
|
47
|
+
expect(row[2]).to include("5.2.1")
|
|
48
|
+
expect(row[3]).to include("Table 3")
|
|
49
|
+
expect(row[4]).to include("technical")
|
|
50
|
+
expect(row[5]).to include("Text")
|
|
51
|
+
expect(row[6]).to include("Fix them")
|
|
52
|
+
expect(row[7]).to include("Accepted")
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "keeps multiple comments in input order and accepts plain hashes" do
|
|
57
|
+
Dir.mktmpdir do |dir|
|
|
58
|
+
output = File.join(dir, "filled.docx")
|
|
59
|
+
described_class.new.fill(template_path, output, [
|
|
60
|
+
{ "id" => "DE-001", "comments" => "First" },
|
|
61
|
+
comment(id: "US-001", comments: "Second")
|
|
62
|
+
])
|
|
63
|
+
|
|
64
|
+
rows = body_rows(output)
|
|
65
|
+
expect(rows.length).to eq(2)
|
|
66
|
+
expect(rows[0][0]).to include("DE-001")
|
|
67
|
+
expect(rows[1][0]).to include("US-001")
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "fills the header metadata labels in the page header" do
|
|
72
|
+
Dir.mktmpdir do |dir|
|
|
73
|
+
output = File.join(dir, "filled.docx")
|
|
74
|
+
described_class.new.fill(template_path, output, [comment(id: "US-001")],
|
|
75
|
+
date: "2026-08-28", document: "ISO 2533:2026", project: "Ballot review")
|
|
76
|
+
|
|
77
|
+
header = header_xml(output)
|
|
78
|
+
expect(header).to include("Date: 2026-08-28")
|
|
79
|
+
expect(header).to include("Document: ISO 2533:2026")
|
|
80
|
+
expect(header).to include("Project: Ballot review")
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it "leaves header labels untouched when no metadata is given" do
|
|
85
|
+
Dir.mktmpdir do |dir|
|
|
86
|
+
output = File.join(dir, "filled.docx")
|
|
87
|
+
described_class.new.fill(template_path, output, [comment(id: "US-001")])
|
|
88
|
+
|
|
89
|
+
header = header_xml(output)
|
|
90
|
+
expect(header).to include(">Date: <")
|
|
91
|
+
expect(header).to include(">Document:<")
|
|
92
|
+
expect(header).to include(">Project:<")
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it "escapes XML special characters in metadata values" do
|
|
97
|
+
Dir.mktmpdir do |dir|
|
|
98
|
+
output = File.join(dir, "filled.docx")
|
|
99
|
+
described_class.new.fill(template_path, output, [comment(id: "US-001")],
|
|
100
|
+
document: "A&B <standard>")
|
|
101
|
+
|
|
102
|
+
expect(header_xml(output)).to include("Document: A&B <standard>")
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it "shades observation cells according to disposition status" do
|
|
107
|
+
Dir.mktmpdir do |dir|
|
|
108
|
+
output = File.join(dir, "filled.docx")
|
|
109
|
+
described_class.new.fill(template_path, output, [
|
|
110
|
+
comment(id: "US-001", observations: "Accepted"),
|
|
111
|
+
comment(id: "US-002", observations: "Not accepted")
|
|
112
|
+
], shading: true)
|
|
113
|
+
|
|
114
|
+
xml = body_xml(output)
|
|
115
|
+
expect(xml).to include('w:fill="92D050"')
|
|
116
|
+
expect(xml).to include('w:fill="FF99CC"')
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -27,7 +27,8 @@ RSpec.describe Commenter::Parser::OsdXlsxParser do
|
|
|
27
27
|
|
|
28
28
|
expect(comment.id).to eq("1")
|
|
29
29
|
expect(comment.body).to eq("John Doe")
|
|
30
|
-
expect(comment.locality).to eq(
|
|
30
|
+
expect(comment.locality.clause).to eq("5.2.1")
|
|
31
|
+
expect(comment.locality.element).to eq("Requirements")
|
|
31
32
|
expect(comment.comments).to eq("The values in Table 3 are inconsistent.")
|
|
32
33
|
expect(comment.proposed_change).to eq("Correct the values in column 2.")
|
|
33
34
|
expect(comment.comment_type).to eq("Editorial")
|
|
@@ -62,7 +63,7 @@ RSpec.describe Commenter::Parser::OsdXlsxParser do
|
|
|
62
63
|
comments = described_class.new.parse(path).comments
|
|
63
64
|
|
|
64
65
|
expect(comments.length).to eq(2)
|
|
65
|
-
expect(comments.last.locality).to
|
|
66
|
+
expect(comments.last.locality.clause).to be_nil
|
|
66
67
|
expect(comments.last.proposed_change).to be_nil
|
|
67
68
|
end
|
|
68
69
|
end
|
|
@@ -25,10 +25,17 @@ module RedlineDocxBuilder
|
|
|
25
25
|
private_constant :HEADING_LEVEL_STYLE, :CHANGE_TAG
|
|
26
26
|
|
|
27
27
|
def self.write(path, paragraphs, comments: [])
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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)
|
|
32
39
|
path
|
|
33
40
|
end
|
|
34
41
|
|
|
@@ -18,22 +18,27 @@ module XlsxBuilder
|
|
|
18
18
|
shared = []
|
|
19
19
|
worksheets = sheets.map { |name, rows| [name, worksheet_xml(rows, shared)] }
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
write_entry(
|
|
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
|
-
|
|
35
|
+
File.binwrite(path, buffer.string)
|
|
32
36
|
path
|
|
33
37
|
end
|
|
34
38
|
|
|
35
|
-
def self.write_entry(
|
|
36
|
-
|
|
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.
|
|
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-
|
|
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,11 +151,16 @@ 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
|
|
142
160
|
- lib/commenter/comment_type.rb
|
|
161
|
+
- lib/commenter/disposition_status.rb
|
|
143
162
|
- lib/commenter/filler.rb
|
|
163
|
+
- lib/commenter/github_info.rb
|
|
144
164
|
- lib/commenter/github_integration.rb
|
|
145
165
|
- lib/commenter/github_session.rb
|
|
146
166
|
- lib/commenter/parser.rb
|
|
@@ -150,10 +170,14 @@ files:
|
|
|
150
170
|
- schema/iso_comment_2012-03.yaml
|
|
151
171
|
- schema/iso_comment_osd.yaml
|
|
152
172
|
- sig/commenter.rbs
|
|
173
|
+
- spec/commenter/ballot_report_spec.rb
|
|
174
|
+
- spec/commenter/ballot_spec.rb
|
|
153
175
|
- spec/commenter/cli_spec.rb
|
|
154
176
|
- spec/commenter/comment_sheet_spec.rb
|
|
155
177
|
- spec/commenter/comment_spec.rb
|
|
156
178
|
- spec/commenter/comment_type_spec.rb
|
|
179
|
+
- spec/commenter/disposition_status_spec.rb
|
|
180
|
+
- spec/commenter/filler_spec.rb
|
|
157
181
|
- spec/commenter/github_integration_spec.rb
|
|
158
182
|
- spec/commenter/osd_xlsx_parser_spec.rb
|
|
159
183
|
- spec/commenter/track_change_docx_parser_spec.rb
|