commenter 0.2.1 → 0.2.2
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/.github/workflows/release.yml +0 -1
- data/.rubocop.yml +2 -0
- data/.rubocop_todo.yml +174 -0
- data/README.adoc +439 -18
- data/commenter.gemspec +4 -0
- data/data/github_config_sample.yaml +78 -0
- data/data/github_issue_body_template.liquid +35 -0
- data/data/github_issue_title_template.liquid +1 -0
- data/exe/commenter +1 -1
- data/lib/commenter/cli.rb +137 -4
- data/lib/commenter/comment.rb +62 -3
- data/lib/commenter/comment_sheet.rb +10 -8
- data/lib/commenter/filler.rb +74 -20
- data/lib/commenter/github_integration.rb +452 -0
- data/lib/commenter/parser.rb +10 -6
- data/lib/commenter/version.rb +1 -1
- data/lib/commenter.rb +1 -0
- data/schema/iso_comment_2012-03.yaml +38 -0
- data/spec/commenter/comment_sheet_spec.rb +193 -0
- data/spec/commenter/comment_spec.rb +220 -0
- data/spec/commenter/github_integration_spec.rb +183 -0
- data/spec/commenter_spec.rb +0 -1
- metadata +66 -2
@@ -0,0 +1,78 @@
|
|
1
|
+
# GitHub configuration for commenter gem
|
2
|
+
# Copy this file and customize for your project
|
3
|
+
|
4
|
+
github:
|
5
|
+
# Repository in owner/repo format
|
6
|
+
repository: "owner/repo-name"
|
7
|
+
|
8
|
+
# GitHub token (recommended to use GITHUB_TOKEN environment variable instead)
|
9
|
+
# token: "ghp_xxxxxxxxxxxx"
|
10
|
+
|
11
|
+
# Milestone configuration
|
12
|
+
milestone:
|
13
|
+
# Use existing milestone by name
|
14
|
+
name: "ISO Comment Review"
|
15
|
+
|
16
|
+
# Alternative: Use existing milestone by number
|
17
|
+
# number: 5
|
18
|
+
|
19
|
+
# Create milestone if it doesn't exist (optional)
|
20
|
+
# create_if_missing: true
|
21
|
+
# description: "Review comments for ISO standard"
|
22
|
+
# due_date: "2024-12-31"
|
23
|
+
|
24
|
+
# Stage-specific milestones (optional)
|
25
|
+
stage_milestones:
|
26
|
+
WD: "Working Draft Review"
|
27
|
+
CD: "Committee Draft Review"
|
28
|
+
DIS: "DIS National Review"
|
29
|
+
FDIS: "Final DIS Review"
|
30
|
+
PRF: "Proof Review"
|
31
|
+
PUB: "Publication Review"
|
32
|
+
|
33
|
+
# Default labels for all issues
|
34
|
+
default_labels:
|
35
|
+
- "comment-review"
|
36
|
+
- "iso-standard"
|
37
|
+
|
38
|
+
# Stage-specific labels
|
39
|
+
stage_labels:
|
40
|
+
WD:
|
41
|
+
- "working-draft"
|
42
|
+
- "early-review"
|
43
|
+
CD:
|
44
|
+
- "committee-draft"
|
45
|
+
- "committee-review"
|
46
|
+
DIS:
|
47
|
+
- "draft-international-standard"
|
48
|
+
- "national-review"
|
49
|
+
FDIS:
|
50
|
+
- "final-draft"
|
51
|
+
- "final-review"
|
52
|
+
PRF:
|
53
|
+
- "proof-stage"
|
54
|
+
- "editorial"
|
55
|
+
PUB:
|
56
|
+
- "publication"
|
57
|
+
- "published"
|
58
|
+
|
59
|
+
# Default assignee (GitHub username)
|
60
|
+
default_assignee: "reviewer-handle"
|
61
|
+
|
62
|
+
# Custom template paths (optional)
|
63
|
+
templates:
|
64
|
+
title: "custom_title_template.liquid"
|
65
|
+
body: "custom_body_template.liquid"
|
66
|
+
|
67
|
+
# Retrieval configuration for github-retrieve command
|
68
|
+
retrieval:
|
69
|
+
# Magic comment markers to look for in GitHub issue comments
|
70
|
+
observation_markers:
|
71
|
+
- "**OBSERVATION:**"
|
72
|
+
- "**COMMENTER OBSERVATION:**"
|
73
|
+
|
74
|
+
# Fallback to last comment if no magic comment found
|
75
|
+
fallback_to_last_comment: true
|
76
|
+
|
77
|
+
# Only retrieve from closed issues (recommended)
|
78
|
+
closed_issues_only: true
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# {% if stage %}[{{ stage | upcase }}] {% endif %}{% if document %}{{ document }} - {% endif %}Comment {{ comment_id }}
|
2
|
+
|
3
|
+
**Stage:** {{ stage | default: "Not specified" }}
|
4
|
+
**Document:** {{ document | default: "Not specified" }}
|
5
|
+
**Comment type:** {{ type }} ({{ type_full_name }})
|
6
|
+
**Member body:** {{ body }}
|
7
|
+
|
8
|
+
## Location
|
9
|
+
|
10
|
+
{% if clause or element or line_number %}
|
11
|
+
{% if clause %}* **Clause:** {{ clause }}{% endif %}
|
12
|
+
{% if element %}* **Element:** {{ element }}{% endif %}
|
13
|
+
{% if line_number %}* **Line number:** {{ line_number }}{% endif %}
|
14
|
+
{% else %}
|
15
|
+
* Location not specified
|
16
|
+
{% endif %}
|
17
|
+
|
18
|
+
## Comment
|
19
|
+
|
20
|
+
{{ comments | default: "No comment provided" }}
|
21
|
+
|
22
|
+
{% if has_proposed_change %}
|
23
|
+
## Proposed change
|
24
|
+
|
25
|
+
{{ proposed_change }}
|
26
|
+
{% endif %}
|
27
|
+
|
28
|
+
{% if has_observations %}
|
29
|
+
## Observations
|
30
|
+
|
31
|
+
{{ observations }}
|
32
|
+
{% endif %}
|
33
|
+
|
34
|
+
---
|
35
|
+
*Generated from ISO comment sheet via commenter gem*
|
@@ -0,0 +1 @@
|
|
1
|
+
{% if stage %}[{{ stage | upcase }}] {% endif %}{% if document %}{{ document }} - {% endif %}{{ comment_id }}: {{ brief_summary }}
|
data/exe/commenter
CHANGED
data/lib/commenter/cli.rb
CHANGED
@@ -5,9 +5,10 @@ require "yaml"
|
|
5
5
|
require "fileutils"
|
6
6
|
require "commenter/parser"
|
7
7
|
require "commenter/filler"
|
8
|
+
require "commenter/github_integration"
|
8
9
|
|
9
10
|
module Commenter
|
10
|
-
class
|
11
|
+
class Cli < Thor
|
11
12
|
desc "import INPUT.docx", "Convert DOCX comment sheet to YAML"
|
12
13
|
option :output, type: :string, aliases: :o, default: "comments.yaml", desc: "Output YAML file"
|
13
14
|
option :exclude_observations, type: :boolean, aliases: :e, desc: "Exclude observations column"
|
@@ -32,9 +33,7 @@ module Commenter
|
|
32
33
|
schema_target = File.join(schema_dir, "iso_comment_2012-03.yaml")
|
33
34
|
|
34
35
|
# Only copy if source and target are different
|
35
|
-
unless File.expand_path(schema_source) == File.expand_path(schema_target)
|
36
|
-
FileUtils.cp(schema_source, schema_target)
|
37
|
-
end
|
36
|
+
FileUtils.cp(schema_source, schema_target) unless File.expand_path(schema_source) == File.expand_path(schema_target)
|
38
37
|
|
39
38
|
puts "Converted #{input_docx} to #{output_yaml}"
|
40
39
|
puts "Schema file created at #{schema_target}"
|
@@ -67,6 +66,140 @@ module Commenter
|
|
67
66
|
puts "Filled template to #{output_docx}"
|
68
67
|
end
|
69
68
|
|
69
|
+
desc "github-create INPUT.yaml", "Create GitHub issues from comments"
|
70
|
+
option :config, type: :string, aliases: :c, required: true, desc: "GitHub configuration YAML file"
|
71
|
+
option :output, type: :string, aliases: :o, desc: "Output YAML file (default: update original)"
|
72
|
+
option :stage, type: :string, desc: "Override approval stage (WD/CD/DIS/FDIS/PRF/PUB)"
|
73
|
+
option :milestone, type: :string, desc: "Override milestone name or number"
|
74
|
+
option :assignee, type: :string, desc: "Override assignee GitHub handle"
|
75
|
+
option :title_template, type: :string, desc: "Custom title template file"
|
76
|
+
option :body_template, type: :string, desc: "Custom body template file"
|
77
|
+
option :dry_run, type: :boolean, desc: "Preview issues without creating them"
|
78
|
+
def github_create(input_yaml)
|
79
|
+
creator = GitHubIssueCreator.new(
|
80
|
+
options[:config],
|
81
|
+
options[:title_template],
|
82
|
+
options[:body_template]
|
83
|
+
)
|
84
|
+
|
85
|
+
github_options = {
|
86
|
+
stage: options[:stage],
|
87
|
+
milestone: options[:milestone],
|
88
|
+
assignee: options[:assignee],
|
89
|
+
dry_run: options[:dry_run],
|
90
|
+
output: options[:output]
|
91
|
+
}.compact
|
92
|
+
|
93
|
+
results = creator.create_issues_from_yaml(input_yaml, github_options)
|
94
|
+
|
95
|
+
if options[:dry_run]
|
96
|
+
puts "DRY RUN - Preview of issues to be created:"
|
97
|
+
puts "=" * 50
|
98
|
+
results.each do |result|
|
99
|
+
puts "\nComment ID: #{result[:comment_id]}"
|
100
|
+
puts "Title: #{result[:title]}"
|
101
|
+
puts "Labels: #{result[:labels].join(", ")}" if result[:labels]&.any?
|
102
|
+
puts "Assignees: #{result[:assignees].join(", ")}" if result[:assignees]&.any?
|
103
|
+
puts "Milestone: #{result[:milestone]}" if result[:milestone]
|
104
|
+
puts "\nBody preview (first 200 chars):"
|
105
|
+
puts result[:body][0...200] + (result[:body].length > 200 ? "..." : "")
|
106
|
+
puts "-" * 30
|
107
|
+
end
|
108
|
+
else
|
109
|
+
puts "GitHub issue creation results:"
|
110
|
+
puts "=" * 40
|
111
|
+
|
112
|
+
created_count = 0
|
113
|
+
skipped_count = 0
|
114
|
+
error_count = 0
|
115
|
+
|
116
|
+
results.each do |result|
|
117
|
+
case result[:status]
|
118
|
+
when :created
|
119
|
+
created_count += 1
|
120
|
+
puts "✓ #{result[:comment_id]}: Created issue ##{result[:issue_number]}"
|
121
|
+
puts " URL: #{result[:issue_url]}"
|
122
|
+
when :skipped
|
123
|
+
skipped_count += 1
|
124
|
+
puts "- #{result[:comment_id]}: Skipped (#{result[:message]})"
|
125
|
+
puts " URL: #{result[:issue_url]}" if result[:issue_url]
|
126
|
+
when :error
|
127
|
+
error_count += 1
|
128
|
+
puts "✗ #{result[:comment_id]}: Error - #{result[:message]}"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
puts "\nSummary:"
|
133
|
+
puts "Created: #{created_count}, Skipped: #{skipped_count}, Errors: #{error_count}"
|
134
|
+
end
|
135
|
+
rescue StandardError => e
|
136
|
+
puts "Error: #{e.message}"
|
137
|
+
exit 1
|
138
|
+
end
|
139
|
+
|
140
|
+
desc "github-retrieve INPUT.yaml", "Retrieve observations from GitHub issues"
|
141
|
+
option :config, type: :string, aliases: :c, required: true, desc: "GitHub configuration YAML file"
|
142
|
+
option :output, type: :string, aliases: :o, desc: "Output YAML file (default: update original)"
|
143
|
+
option :include_open, type: :boolean, desc: "Include observations from open issues"
|
144
|
+
option :dry_run, type: :boolean, desc: "Preview observations without updating"
|
145
|
+
def github_retrieve(input_yaml)
|
146
|
+
retriever = GitHubIssueRetriever.new(options[:config])
|
147
|
+
|
148
|
+
retrieve_options = {
|
149
|
+
output: options[:output],
|
150
|
+
include_open: options[:include_open],
|
151
|
+
dry_run: options[:dry_run]
|
152
|
+
}.compact
|
153
|
+
|
154
|
+
results = retriever.retrieve_observations_from_yaml(input_yaml, retrieve_options)
|
155
|
+
|
156
|
+
if options[:dry_run]
|
157
|
+
puts "DRY RUN - Preview of observations to be retrieved:"
|
158
|
+
puts "=" * 50
|
159
|
+
results.each do |result|
|
160
|
+
puts "\nComment ID: #{result[:comment_id]}"
|
161
|
+
puts "Issue ##{result[:issue_number]}: #{result[:status]}"
|
162
|
+
if result[:observation]
|
163
|
+
puts "Observation preview (first 200 chars):"
|
164
|
+
puts result[:observation][0...200] + (result[:observation].length > 200 ? "..." : "")
|
165
|
+
else
|
166
|
+
puts "No observation found"
|
167
|
+
end
|
168
|
+
puts "-" * 30
|
169
|
+
end
|
170
|
+
else
|
171
|
+
puts "GitHub observation retrieval results:"
|
172
|
+
puts "=" * 40
|
173
|
+
|
174
|
+
retrieved_count = 0
|
175
|
+
skipped_count = 0
|
176
|
+
error_count = 0
|
177
|
+
|
178
|
+
results.each do |result|
|
179
|
+
case result[:status]
|
180
|
+
when :retrieved
|
181
|
+
retrieved_count += 1
|
182
|
+
puts "✓ #{result[:comment_id]}: Retrieved observation from issue ##{result[:issue_number]}"
|
183
|
+
when :skipped
|
184
|
+
skipped_count += 1
|
185
|
+
puts "- #{result[:comment_id]}: Skipped (#{result[:message]})"
|
186
|
+
when :error
|
187
|
+
error_count += 1
|
188
|
+
puts "✗ #{result[:comment_id]}: Error - #{result[:message]}"
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
puts "\nSummary:"
|
193
|
+
puts "Retrieved: #{retrieved_count}, Skipped: #{skipped_count}, Errors: #{error_count}"
|
194
|
+
|
195
|
+
output_file = options[:output] || input_yaml
|
196
|
+
puts "Updated YAML file: #{output_file}"
|
197
|
+
end
|
198
|
+
rescue StandardError => e
|
199
|
+
puts "Error: #{e.message}"
|
200
|
+
exit 1
|
201
|
+
end
|
202
|
+
|
70
203
|
def self.exit_on_failure?
|
71
204
|
true
|
72
205
|
end
|
data/lib/commenter/comment.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Commenter
|
4
4
|
class Comment
|
5
|
-
attr_accessor :id, :body, :locality, :type, :comments, :proposed_change, :observations
|
5
|
+
attr_accessor :id, :body, :locality, :type, :comments, :proposed_change, :observations, :github
|
6
6
|
|
7
7
|
def initialize(attributes = {})
|
8
8
|
# Normalize input to symbols
|
@@ -15,6 +15,7 @@ module Commenter
|
|
15
15
|
@comments = attrs[:comments]
|
16
16
|
@proposed_change = attrs[:proposed_change]
|
17
17
|
@observations = attrs[:observations]
|
18
|
+
@github = symbolize_keys(attrs[:github] || {})
|
18
19
|
end
|
19
20
|
|
20
21
|
def line_number
|
@@ -41,6 +42,63 @@ module Commenter
|
|
41
42
|
@locality[:element] = value
|
42
43
|
end
|
43
44
|
|
45
|
+
def brief_summary(max_length = 80)
|
46
|
+
parts = []
|
47
|
+
|
48
|
+
# Add locality information first
|
49
|
+
parts << "Clause #{clause}" if clause && !clause.strip.empty?
|
50
|
+
parts << element if element && !element.strip.empty?
|
51
|
+
parts << "Line #{line_number}" if line_number && !line_number.strip.empty?
|
52
|
+
|
53
|
+
locality_text = parts.join(", ")
|
54
|
+
|
55
|
+
# Add description from comment text
|
56
|
+
if @comments && !@comments.strip.empty?
|
57
|
+
# Extract first sentence or truncate
|
58
|
+
clean_text = @comments.strip.gsub(/\s+/, " ")
|
59
|
+
first_sentence = clean_text.split(/[.!?]/).first&.strip
|
60
|
+
description = if first_sentence && first_sentence.length < max_length
|
61
|
+
first_sentence
|
62
|
+
else
|
63
|
+
clean_text[0...50]
|
64
|
+
end
|
65
|
+
|
66
|
+
if locality_text.empty?
|
67
|
+
description
|
68
|
+
else
|
69
|
+
# Combine locality + description, respecting max_length
|
70
|
+
combined = "#{locality_text}: #{description}"
|
71
|
+
combined.length <= max_length ? combined : "#{locality_text}: #{description[0...(max_length - locality_text.length - 2)]}"
|
72
|
+
end
|
73
|
+
else
|
74
|
+
locality_text.empty? ? "No description" : locality_text
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def github_issue_number
|
79
|
+
@github[:issue_number]
|
80
|
+
end
|
81
|
+
|
82
|
+
def github_issue_url
|
83
|
+
@github[:issue_url]
|
84
|
+
end
|
85
|
+
|
86
|
+
def github_status
|
87
|
+
@github[:status]
|
88
|
+
end
|
89
|
+
|
90
|
+
def github_created_at
|
91
|
+
@github[:created_at]
|
92
|
+
end
|
93
|
+
|
94
|
+
def github_updated_at
|
95
|
+
@github[:updated_at]
|
96
|
+
end
|
97
|
+
|
98
|
+
def has_github_issue?
|
99
|
+
!@github[:issue_number].nil?
|
100
|
+
end
|
101
|
+
|
44
102
|
def to_h
|
45
103
|
{
|
46
104
|
id: @id,
|
@@ -49,8 +107,9 @@ module Commenter
|
|
49
107
|
type: @type,
|
50
108
|
comments: @comments,
|
51
109
|
proposed_change: @proposed_change,
|
52
|
-
observations: @observations
|
53
|
-
|
110
|
+
observations: @observations,
|
111
|
+
github: @github.empty? ? nil : @github
|
112
|
+
}.compact
|
54
113
|
end
|
55
114
|
|
56
115
|
def to_yaml_h
|
@@ -4,7 +4,7 @@ require_relative "comment"
|
|
4
4
|
|
5
5
|
module Commenter
|
6
6
|
class CommentSheet
|
7
|
-
attr_accessor :version, :date, :document, :project, :comments
|
7
|
+
attr_accessor :version, :date, :document, :project, :stage, :comments
|
8
8
|
|
9
9
|
def initialize(attributes = {})
|
10
10
|
# Normalize input to symbols
|
@@ -14,6 +14,7 @@ module Commenter
|
|
14
14
|
@date = attrs[:date]
|
15
15
|
@document = attrs[:document]
|
16
16
|
@project = attrs[:project]
|
17
|
+
@stage = attrs[:stage]
|
17
18
|
@comments = (attrs[:comments] || []).map { |c| c.is_a?(Comment) ? c : Comment.from_hash(c) }
|
18
19
|
end
|
19
20
|
|
@@ -27,6 +28,7 @@ module Commenter
|
|
27
28
|
date: @date,
|
28
29
|
document: @document,
|
29
30
|
project: @project,
|
31
|
+
stage: @stage,
|
30
32
|
comments: @comments.map(&:to_h)
|
31
33
|
}
|
32
34
|
end
|
@@ -57,13 +59,13 @@ module Commenter
|
|
57
59
|
hash.each_with_object({}) do |(key, value), result|
|
58
60
|
new_key = key.to_s
|
59
61
|
new_value = case value
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
62
|
+
when Hash
|
63
|
+
stringify_keys(value)
|
64
|
+
when Array
|
65
|
+
value.map { |item| item.is_a?(Hash) ? stringify_keys(item) : item }
|
66
|
+
else
|
67
|
+
value
|
68
|
+
end
|
67
69
|
result[new_key] = new_value
|
68
70
|
end
|
69
71
|
end
|
data/lib/commenter/filler.rb
CHANGED
@@ -15,7 +15,7 @@ module Commenter
|
|
15
15
|
template_row = table.rows.first
|
16
16
|
|
17
17
|
# Add new rows for each comment by copying the template row
|
18
|
-
comments.each_with_index do |comment,
|
18
|
+
comments.each_with_index do |comment, _index|
|
19
19
|
# Convert comment to symbol keys for consistent access
|
20
20
|
comment_data = symbolize_keys(comment)
|
21
21
|
|
@@ -24,7 +24,7 @@ module Commenter
|
|
24
24
|
new_row = template_row.copy
|
25
25
|
new_row.insert_before(template_row)
|
26
26
|
row = new_row
|
27
|
-
rescue => e
|
27
|
+
rescue StandardError => e
|
28
28
|
puts "Warning: Could not add row for comment #{comment_data[:id]}: #{e.message}"
|
29
29
|
next
|
30
30
|
end
|
@@ -64,27 +64,25 @@ module Commenter
|
|
64
64
|
paragraph.each_text_run do |text_run|
|
65
65
|
# Get current text and substitute it with new text
|
66
66
|
current_text = text_run.text
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
67
|
+
next unless current_text && !current_text.empty?
|
68
|
+
|
69
|
+
text_run.substitute(current_text, text)
|
70
|
+
text_set = true
|
71
|
+
return # Only substitute in the first text run found
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
75
75
|
# If no text runs with content were found, add text to the first paragraph
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
paragraph.text = text
|
84
|
-
end
|
76
|
+
if !text_set && cell.paragraphs.any?
|
77
|
+
paragraph = cell.paragraphs.first
|
78
|
+
# Try to add a text run to the paragraph
|
79
|
+
if paragraph.respond_to?(:add_text)
|
80
|
+
paragraph.add_text(text)
|
81
|
+
elsif paragraph.respond_to?(:text=)
|
82
|
+
paragraph.text = text
|
85
83
|
end
|
86
84
|
end
|
87
|
-
rescue => e
|
85
|
+
rescue StandardError => e
|
88
86
|
puts "Warning: Could not set text '#{text}' in cell: #{e.message}"
|
89
87
|
end
|
90
88
|
|
@@ -99,9 +97,65 @@ module Commenter
|
|
99
97
|
end
|
100
98
|
|
101
99
|
def apply_shading(cell, observation)
|
102
|
-
|
103
|
-
|
104
|
-
|
100
|
+
return unless observation && !observation.empty?
|
101
|
+
|
102
|
+
# Determine shading color based on status patterns
|
103
|
+
shading_color = determine_shading_color(observation)
|
104
|
+
return unless shading_color
|
105
|
+
|
106
|
+
puts "Applying #{shading_color} cell shading for: #{observation.strip}"
|
107
|
+
|
108
|
+
# Apply shading to the table cell itself
|
109
|
+
apply_cell_shading(cell, shading_color)
|
110
|
+
rescue StandardError => e
|
111
|
+
puts "Warning: Could not apply shading to cell: #{e.message}"
|
112
|
+
end
|
113
|
+
|
114
|
+
def determine_shading_color(observation)
|
115
|
+
text = observation.downcase.strip
|
116
|
+
|
117
|
+
case text
|
118
|
+
when /awm|accept with modifications/
|
119
|
+
"C4D79B" # Olive Green
|
120
|
+
when /accept(ed)?/
|
121
|
+
"92D050" # Green
|
122
|
+
when /noted/
|
123
|
+
"8DB4E2" # Blue
|
124
|
+
when /reject(ed)?/
|
125
|
+
"FF99CC" # Pink
|
126
|
+
when /todo/
|
127
|
+
"D9D9D9" # Light Gray (for diagonal stripes, we'll use solid for now)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def apply_cell_shading(cell, color)
|
132
|
+
# Access the cell's XML node
|
133
|
+
cell_node = cell.node
|
134
|
+
|
135
|
+
# Find or create the table cell properties (tcPr) element
|
136
|
+
tcpr_node = cell_node.at_xpath(".//w:tcPr", "w" => "http://schemas.openxmlformats.org/wordprocessingml/2006/main")
|
137
|
+
|
138
|
+
unless tcpr_node
|
139
|
+
# Create table cell properties if they don't exist
|
140
|
+
tcpr_node = cell_node.document.create_element("tcPr")
|
141
|
+
cell_node.prepend_child(tcpr_node)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Remove existing shading if present
|
145
|
+
existing_shd = tcpr_node.at_xpath(".//w:shd", "w" => "http://schemas.openxmlformats.org/wordprocessingml/2006/main")
|
146
|
+
existing_shd&.remove
|
147
|
+
|
148
|
+
# Create new shading element for the cell
|
149
|
+
shd_node = cell_node.document.create_element("shd")
|
150
|
+
shd_node["w:val"] = "clear"
|
151
|
+
shd_node["w:color"] = "auto"
|
152
|
+
shd_node["w:fill"] = color
|
153
|
+
|
154
|
+
# Add namespace declaration
|
155
|
+
shd_node.namespace = cell_node.document.root.namespace_definitions.find { |ns| ns.prefix == "w" }
|
156
|
+
|
157
|
+
# Add the shading to table cell properties
|
158
|
+
tcpr_node.add_child(shd_node)
|
105
159
|
end
|
106
160
|
end
|
107
161
|
end
|