kettle-dev 1.2.3 → 2.0.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
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +176 -3
- data/CITATION.cff +2 -2
- data/CONTRIBUTING.md +11 -17
- data/README.md +390 -319
- data/exe/kettle-dev-setup +12 -63
- data/exe/kettle-gh-release +82 -0
- data/lib/kettle/dev/gem_spec_reader.rb +2 -2
- data/lib/kettle/dev/open_collective_config.rb +12 -0
- data/lib/kettle/dev/rakelib/yard.rake +15 -0
- data/lib/kettle/dev/tasks/ci_task.rb +4 -4
- data/lib/kettle/dev/version.rb +1 -1
- data/lib/kettle/dev.rb +4 -12
- data/sig/kettle/dev/source_merger.rbs +40 -56
- data.tar.gz.sig +0 -0
- metadata +15 -144
- metadata.gz.sig +0 -0
- data/.aiignore.example +0 -19
- data/.devcontainer/apt-install/devcontainer-feature.json +0 -9
- data/.devcontainer/apt-install/install.sh +0 -11
- data/.devcontainer/devcontainer.json +0 -28
- data/.env.local.example +0 -31
- data/.envrc +0 -47
- data/.envrc.example +0 -51
- data/.envrc.no-osc.example +0 -51
- data/.git-hooks/commit-msg +0 -54
- data/.git-hooks/commit-subjects-goalie.txt +0 -8
- data/.git-hooks/footer-template.erb.txt +0 -16
- data/.git-hooks/prepare-commit-msg +0 -8
- data/.github/.codecov.yml.example +0 -14
- data/.github/FUNDING.yml +0 -13
- data/.github/FUNDING.yml.no-osc.example +0 -13
- data/.github/dependabot.yml +0 -13
- data/.github/workflows/ancient.yml +0 -83
- data/.github/workflows/ancient.yml.example +0 -81
- data/.github/workflows/auto-assign.yml +0 -21
- data/.github/workflows/codeql-analysis.yml +0 -70
- data/.github/workflows/coverage.yml +0 -127
- data/.github/workflows/coverage.yml.example +0 -127
- data/.github/workflows/current.yml +0 -116
- data/.github/workflows/current.yml.example +0 -115
- data/.github/workflows/dep-heads.yml +0 -117
- data/.github/workflows/dependency-review.yml +0 -20
- data/.github/workflows/discord-notifier.yml.example +0 -39
- data/.github/workflows/heads.yml +0 -117
- data/.github/workflows/heads.yml.example +0 -116
- data/.github/workflows/jruby.yml +0 -82
- data/.github/workflows/jruby.yml.example +0 -72
- data/.github/workflows/legacy.yml +0 -76
- data/.github/workflows/license-eye.yml +0 -40
- data/.github/workflows/locked_deps.yml +0 -85
- data/.github/workflows/opencollective.yml +0 -40
- data/.github/workflows/style.yml +0 -67
- data/.github/workflows/supported.yml +0 -75
- data/.github/workflows/truffle.yml +0 -99
- data/.github/workflows/unlocked_deps.yml +0 -84
- data/.github/workflows/unsupported.yml +0 -76
- data/.gitignore +0 -50
- data/.gitlab-ci.yml.example +0 -134
- data/.idea/.gitignore +0 -45
- data/.junie/guidelines-rbs.md +0 -49
- data/.junie/guidelines.md +0 -141
- data/.junie/guidelines.md.example +0 -140
- data/.licenserc.yaml +0 -7
- data/.opencollective.yml +0 -3
- data/.opencollective.yml.example +0 -3
- data/.qlty/qlty.toml +0 -79
- data/.rspec +0 -9
- data/.rubocop.yml +0 -13
- data/.rubocop_rspec.yml +0 -33
- data/.simplecov +0 -16
- data/.simplecov.example +0 -11
- data/.tool-versions +0 -1
- data/.yardignore +0 -13
- data/.yardopts +0 -14
- data/Appraisal.root.gemfile +0 -10
- data/Appraisals +0 -151
- data/Appraisals.example +0 -102
- data/CHANGELOG.md.example +0 -47
- data/CONTRIBUTING.md.example +0 -227
- data/FUNDING.md.no-osc.example +0 -63
- data/Gemfile +0 -40
- data/Gemfile.example +0 -34
- data/README.md.example +0 -570
- data/README.md.no-osc.example +0 -536
- data/Rakefile.example +0 -68
- data/gemfiles/modular/coverage.gemfile +0 -6
- data/gemfiles/modular/debug.gemfile +0 -13
- data/gemfiles/modular/documentation.gemfile +0 -14
- data/gemfiles/modular/erb/r2/v3.0.gemfile +0 -1
- data/gemfiles/modular/erb/r2.3/default.gemfile +0 -6
- data/gemfiles/modular/erb/r2.6/v2.2.gemfile +0 -3
- data/gemfiles/modular/erb/r3/v5.0.gemfile +0 -1
- data/gemfiles/modular/erb/r3.1/v4.0.gemfile +0 -2
- data/gemfiles/modular/erb/vHEAD.gemfile +0 -2
- data/gemfiles/modular/injected.gemfile +0 -60
- data/gemfiles/modular/mutex_m/r2/v0.3.gemfile +0 -2
- data/gemfiles/modular/mutex_m/r2.4/v0.1.gemfile +0 -3
- data/gemfiles/modular/mutex_m/r3/v0.3.gemfile +0 -2
- data/gemfiles/modular/mutex_m/vHEAD.gemfile +0 -2
- data/gemfiles/modular/optional.gemfile +0 -8
- data/gemfiles/modular/optional.gemfile.example +0 -5
- data/gemfiles/modular/runtime_heads.gemfile +0 -10
- data/gemfiles/modular/runtime_heads.gemfile.example +0 -8
- data/gemfiles/modular/stringio/r2/v3.0.gemfile +0 -5
- data/gemfiles/modular/stringio/r2.4/v0.0.2.gemfile +0 -4
- data/gemfiles/modular/stringio/r3/v3.0.gemfile +0 -5
- data/gemfiles/modular/stringio/vHEAD.gemfile +0 -2
- data/gemfiles/modular/style.gemfile +0 -25
- data/gemfiles/modular/style.gemfile.example +0 -25
- data/gemfiles/modular/templating.gemfile +0 -3
- data/gemfiles/modular/x_std_libs/r2/libs.gemfile +0 -3
- data/gemfiles/modular/x_std_libs/r2.3/libs.gemfile +0 -3
- data/gemfiles/modular/x_std_libs/r2.4/libs.gemfile +0 -3
- data/gemfiles/modular/x_std_libs/r2.6/libs.gemfile +0 -3
- data/gemfiles/modular/x_std_libs/r3/libs.gemfile +0 -3
- data/gemfiles/modular/x_std_libs/r3.1/libs.gemfile +0 -3
- data/gemfiles/modular/x_std_libs/vHEAD.gemfile +0 -3
- data/gemfiles/modular/x_std_libs.gemfile +0 -2
- data/kettle-dev.gemspec.example +0 -154
- data/lib/kettle/dev/modular_gemfiles.rb +0 -119
- data/lib/kettle/dev/prism_appraisals.rb +0 -351
- data/lib/kettle/dev/prism_gemfile.rb +0 -177
- data/lib/kettle/dev/prism_gemspec.rb +0 -284
- data/lib/kettle/dev/prism_utils.rb +0 -201
- data/lib/kettle/dev/rakelib/install.rake +0 -10
- data/lib/kettle/dev/rakelib/template.rake +0 -10
- data/lib/kettle/dev/setup_cli.rb +0 -403
- data/lib/kettle/dev/source_merger.rb +0 -622
- data/lib/kettle/dev/tasks/install_task.rb +0 -553
- data/lib/kettle/dev/tasks/template_task.rb +0 -975
- data/lib/kettle/dev/template_helpers.rb +0 -685
|
@@ -1,351 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "prism"
|
|
4
|
-
|
|
5
|
-
module Kettle
|
|
6
|
-
module Dev
|
|
7
|
-
# AST-driven merger for Appraisals files using Prism.
|
|
8
|
-
# Preserves all comments: preamble headers, block headers, and inline comments.
|
|
9
|
-
# Uses PrismUtils for shared Prism AST operations.
|
|
10
|
-
module PrismAppraisals
|
|
11
|
-
TRACKED_METHODS = [:gem, :eval_gemfile, :gemfile].freeze
|
|
12
|
-
|
|
13
|
-
module_function
|
|
14
|
-
|
|
15
|
-
# Merge template and destination Appraisals files preserving comments
|
|
16
|
-
def merge(template_content, dest_content)
|
|
17
|
-
template_content ||= ""
|
|
18
|
-
dest_content ||= ""
|
|
19
|
-
|
|
20
|
-
return template_content if dest_content.strip.empty?
|
|
21
|
-
return dest_content if template_content.strip.empty?
|
|
22
|
-
|
|
23
|
-
tmpl_result = PrismUtils.parse_with_comments(template_content)
|
|
24
|
-
dest_result = PrismUtils.parse_with_comments(dest_content)
|
|
25
|
-
|
|
26
|
-
tmpl_preamble, tmpl_blocks = extract_blocks(tmpl_result, template_content)
|
|
27
|
-
dest_preamble, dest_blocks = extract_blocks(dest_result, dest_content)
|
|
28
|
-
|
|
29
|
-
merged_preamble = merge_preambles(tmpl_preamble, dest_preamble)
|
|
30
|
-
merged_blocks = merge_blocks(tmpl_blocks, dest_blocks, tmpl_result, dest_result)
|
|
31
|
-
|
|
32
|
-
build_output(merged_preamble, merged_blocks)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# ...existing helper methods copied from original AppraisalsAstMerger...
|
|
36
|
-
def extract_blocks(parse_result, source_content)
|
|
37
|
-
root = parse_result.value
|
|
38
|
-
return [[], []] unless root&.statements&.body
|
|
39
|
-
|
|
40
|
-
source_lines = source_content.lines
|
|
41
|
-
blocks = []
|
|
42
|
-
first_appraise_line = nil
|
|
43
|
-
|
|
44
|
-
root.statements.body.each do |node|
|
|
45
|
-
if appraise_call?(node)
|
|
46
|
-
first_appraise_line ||= node.location.start_line
|
|
47
|
-
name = extract_appraise_name(node)
|
|
48
|
-
next unless name
|
|
49
|
-
|
|
50
|
-
block_header = extract_block_header(node, source_lines, blocks)
|
|
51
|
-
|
|
52
|
-
blocks << {
|
|
53
|
-
node: node,
|
|
54
|
-
name: name,
|
|
55
|
-
header: block_header,
|
|
56
|
-
}
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
preamble_comments = if first_appraise_line
|
|
61
|
-
parse_result.comments.select { |c| c.location.start_line < first_appraise_line }
|
|
62
|
-
else
|
|
63
|
-
parse_result.comments
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
block_header_lines = blocks.flat_map { |b| b[:header].lines.map { |l| l.strip } }.to_set
|
|
67
|
-
preamble_comments = preamble_comments.reject { |c| block_header_lines.include?(c.slice.strip) }
|
|
68
|
-
|
|
69
|
-
[preamble_comments, blocks]
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def appraise_call?(node)
|
|
73
|
-
PrismUtils.block_call_to?(node, :appraise)
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def extract_appraise_name(node)
|
|
77
|
-
return unless node.is_a?(Prism::CallNode)
|
|
78
|
-
PrismUtils.extract_literal_value(node.arguments&.arguments&.first)
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def merge_preambles(tmpl_comments, dest_comments)
|
|
82
|
-
tmpl_lines = tmpl_comments.map { |c| c.slice.strip }
|
|
83
|
-
dest_lines = dest_comments.map { |c| c.slice.strip }
|
|
84
|
-
|
|
85
|
-
magic_pattern = /^#.*frozen_string_literal/
|
|
86
|
-
if tmpl_lines.any? { |line| line.match?(magic_pattern) }
|
|
87
|
-
dest_lines.reject! { |line| line.match?(magic_pattern) }
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
merged = []
|
|
91
|
-
seen = Set.new
|
|
92
|
-
|
|
93
|
-
(tmpl_lines + dest_lines).each do |line|
|
|
94
|
-
normalized = line.downcase
|
|
95
|
-
unless seen.include?(normalized)
|
|
96
|
-
merged << line
|
|
97
|
-
seen << normalized
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
merged
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
def extract_block_header(node, source_lines, previous_blocks)
|
|
105
|
-
begin_line = node.location.start_line
|
|
106
|
-
min_line = if previous_blocks.empty?
|
|
107
|
-
1
|
|
108
|
-
else
|
|
109
|
-
previous_blocks.last[:node].location.end_line + 1
|
|
110
|
-
end
|
|
111
|
-
check_line = begin_line - 2
|
|
112
|
-
header_lines = []
|
|
113
|
-
while check_line >= 0 && (check_line + 1) >= min_line
|
|
114
|
-
line = source_lines[check_line]
|
|
115
|
-
break unless line
|
|
116
|
-
if line.strip.empty?
|
|
117
|
-
break
|
|
118
|
-
elsif line.lstrip.start_with?("#")
|
|
119
|
-
header_lines.unshift(line)
|
|
120
|
-
check_line -= 1
|
|
121
|
-
else
|
|
122
|
-
break
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
header_lines.join
|
|
126
|
-
rescue StandardError => e
|
|
127
|
-
Kettle::Dev.debug_error(e, __method__) if defined?(Kettle::Dev.debug_error)
|
|
128
|
-
""
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
def merge_blocks(template_blocks, dest_blocks, tmpl_result, dest_result)
|
|
132
|
-
merged = []
|
|
133
|
-
dest_by_name = dest_blocks.each_with_object({}) { |b, h| h[b[:name]] = b }
|
|
134
|
-
template_names = template_blocks.map { |b| b[:name] }.to_set
|
|
135
|
-
placed_dest = Set.new
|
|
136
|
-
|
|
137
|
-
template_blocks.each_with_index do |tmpl_block, idx|
|
|
138
|
-
name = tmpl_block[:name]
|
|
139
|
-
if idx == 0 || dest_by_name[name]
|
|
140
|
-
dest_blocks.each do |db|
|
|
141
|
-
next if template_names.include?(db[:name])
|
|
142
|
-
next if placed_dest.include?(db[:name])
|
|
143
|
-
dest_idx_of_shared = dest_blocks.index { |b| b[:name] == name }
|
|
144
|
-
dest_idx_of_only = dest_blocks.index { |b| b[:name] == db[:name] }
|
|
145
|
-
if dest_idx_of_only && dest_idx_of_shared && dest_idx_of_only < dest_idx_of_shared
|
|
146
|
-
merged << db
|
|
147
|
-
placed_dest << db[:name]
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
dest_block = dest_by_name[name]
|
|
153
|
-
if dest_block
|
|
154
|
-
merged_header = merge_block_headers(tmpl_block[:header], dest_block[:header])
|
|
155
|
-
merged_statements = merge_block_statements(
|
|
156
|
-
tmpl_block[:node].block.body,
|
|
157
|
-
dest_block[:node].block.body,
|
|
158
|
-
dest_result,
|
|
159
|
-
)
|
|
160
|
-
merged << {
|
|
161
|
-
name: name,
|
|
162
|
-
header: merged_header,
|
|
163
|
-
node: tmpl_block[:node],
|
|
164
|
-
statements: merged_statements,
|
|
165
|
-
}
|
|
166
|
-
placed_dest << name
|
|
167
|
-
else
|
|
168
|
-
merged << tmpl_block
|
|
169
|
-
end
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
dest_blocks.each do |dest_block|
|
|
173
|
-
next if placed_dest.include?(dest_block[:name])
|
|
174
|
-
next if template_names.include?(dest_block[:name])
|
|
175
|
-
merged << dest_block
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
merged
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
def merge_block_headers(tmpl_header, dest_header)
|
|
182
|
-
tmpl_lines = tmpl_header.to_s.lines.map(&:strip).reject(&:empty?)
|
|
183
|
-
dest_lines = dest_header.to_s.lines.map(&:strip).reject(&:empty?)
|
|
184
|
-
merged = []
|
|
185
|
-
seen = Set.new
|
|
186
|
-
(tmpl_lines + dest_lines).each do |line|
|
|
187
|
-
normalized = line.downcase
|
|
188
|
-
unless seen.include?(normalized)
|
|
189
|
-
merged << line
|
|
190
|
-
seen << normalized
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
|
-
return "" if merged.empty?
|
|
194
|
-
merged.join("\n") + "\n"
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
def merge_block_statements(tmpl_body, dest_body, dest_result)
|
|
198
|
-
tmpl_stmts = PrismUtils.extract_statements(tmpl_body)
|
|
199
|
-
dest_stmts = PrismUtils.extract_statements(dest_body)
|
|
200
|
-
|
|
201
|
-
tmpl_keys = Set.new
|
|
202
|
-
tmpl_key_to_node = {}
|
|
203
|
-
tmpl_stmts.each do |stmt|
|
|
204
|
-
key = statement_key(stmt)
|
|
205
|
-
if key
|
|
206
|
-
tmpl_keys << key
|
|
207
|
-
tmpl_key_to_node[key] = stmt
|
|
208
|
-
end
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
dest_keys = Set.new
|
|
212
|
-
dest_stmts.each do |stmt|
|
|
213
|
-
key = statement_key(stmt)
|
|
214
|
-
dest_keys << key if key
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
merged = []
|
|
218
|
-
dest_stmts.each_with_index do |dest_stmt, idx|
|
|
219
|
-
dest_key = statement_key(dest_stmt)
|
|
220
|
-
|
|
221
|
-
if dest_key && tmpl_keys.include?(dest_key)
|
|
222
|
-
merged << {node: tmpl_key_to_node[dest_key], inline_comments: [], leading_comments: [], shared: true, key: dest_key}
|
|
223
|
-
else
|
|
224
|
-
inline_comments = PrismUtils.inline_comments_for_node(dest_result, dest_stmt)
|
|
225
|
-
prev_stmt = (idx > 0) ? dest_stmts[idx - 1] : nil
|
|
226
|
-
leading_comments = PrismUtils.find_leading_comments(dest_result, dest_stmt, prev_stmt, dest_body)
|
|
227
|
-
merged << {node: dest_stmt, inline_comments: inline_comments, leading_comments: leading_comments, shared: false}
|
|
228
|
-
end
|
|
229
|
-
end
|
|
230
|
-
|
|
231
|
-
tmpl_stmts.each do |tmpl_stmt|
|
|
232
|
-
tmpl_key = statement_key(tmpl_stmt)
|
|
233
|
-
unless tmpl_key && dest_keys.include?(tmpl_key)
|
|
234
|
-
merged << {node: tmpl_stmt, inline_comments: [], leading_comments: [], shared: false}
|
|
235
|
-
end
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
merged.each do |item|
|
|
239
|
-
item.delete(:shared)
|
|
240
|
-
item.delete(:key)
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
merged
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
def statement_key(node)
|
|
247
|
-
PrismUtils.statement_key(node, tracked_methods: TRACKED_METHODS)
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
def build_output(preamble_lines, blocks)
|
|
251
|
-
output = []
|
|
252
|
-
preamble_lines.each { |line| output << line }
|
|
253
|
-
output << "" unless preamble_lines.empty?
|
|
254
|
-
|
|
255
|
-
blocks.each do |block|
|
|
256
|
-
header = block[:header]
|
|
257
|
-
if header && !header.strip.empty?
|
|
258
|
-
output << header.rstrip
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
name = block[:name]
|
|
262
|
-
output << "appraise(\"#{name}\") {"
|
|
263
|
-
|
|
264
|
-
statements = block[:statements] || extract_original_statements(block[:node])
|
|
265
|
-
statements.each do |stmt_info|
|
|
266
|
-
leading = stmt_info[:leading_comments] || []
|
|
267
|
-
leading.each do |comment|
|
|
268
|
-
output << " #{comment.slice.strip}"
|
|
269
|
-
end
|
|
270
|
-
|
|
271
|
-
node = stmt_info[:node]
|
|
272
|
-
line = normalize_statement(node)
|
|
273
|
-
# Remove any leading whitespace/newlines from the normalized line
|
|
274
|
-
line = line.to_s.sub(/\A\s+/, "")
|
|
275
|
-
|
|
276
|
-
inline = stmt_info[:inline_comments] || []
|
|
277
|
-
inline_str = inline.map { |c| c.slice.strip }.join(" ")
|
|
278
|
-
output << " #{line}#{" " + inline_str unless inline_str.empty?}"
|
|
279
|
-
end
|
|
280
|
-
|
|
281
|
-
output << "}"
|
|
282
|
-
output << ""
|
|
283
|
-
end
|
|
284
|
-
|
|
285
|
-
build = output.join("\n").strip + "\n"
|
|
286
|
-
build
|
|
287
|
-
end
|
|
288
|
-
|
|
289
|
-
def normalize_statement(node)
|
|
290
|
-
return PrismUtils.node_to_source(node) unless node.is_a?(Prism::CallNode)
|
|
291
|
-
PrismUtils.normalize_call_node(node)
|
|
292
|
-
end
|
|
293
|
-
|
|
294
|
-
def normalize_argument(arg)
|
|
295
|
-
PrismUtils.normalize_argument(arg)
|
|
296
|
-
end
|
|
297
|
-
|
|
298
|
-
def extract_original_statements(node)
|
|
299
|
-
body = node.block&.body
|
|
300
|
-
return [] unless body
|
|
301
|
-
statements = body.is_a?(Prism::StatementsNode) ? body.body : [body]
|
|
302
|
-
statements.compact.map { |stmt| {node: stmt, inline_comments: [], leading_comments: []} }
|
|
303
|
-
end
|
|
304
|
-
|
|
305
|
-
# Remove gem calls that reference the given gem name (to prevent self-dependency).
|
|
306
|
-
# Works by locating gem() call nodes within appraise blocks where the first argument matches gem_name.
|
|
307
|
-
# @param content [String] Appraisals content
|
|
308
|
-
# @param gem_name [String] the gem name to remove
|
|
309
|
-
# @return [String] modified content with self-referential gem calls removed
|
|
310
|
-
def remove_gem_dependency(content, gem_name)
|
|
311
|
-
return content if gem_name.to_s.strip.empty?
|
|
312
|
-
|
|
313
|
-
result = PrismUtils.parse_with_comments(content)
|
|
314
|
-
root = result.value
|
|
315
|
-
return content unless root&.statements&.body
|
|
316
|
-
|
|
317
|
-
out = content.dup
|
|
318
|
-
|
|
319
|
-
# Iterate through all appraise blocks
|
|
320
|
-
root.statements.body.each do |node|
|
|
321
|
-
next unless appraise_call?(node)
|
|
322
|
-
next unless node.block&.body
|
|
323
|
-
|
|
324
|
-
body_stmts = PrismUtils.extract_statements(node.block.body)
|
|
325
|
-
|
|
326
|
-
# Find gem call nodes within this appraise block where first argument matches gem_name
|
|
327
|
-
body_stmts.each do |stmt|
|
|
328
|
-
next unless stmt.is_a?(Prism::CallNode) && stmt.name == :gem
|
|
329
|
-
|
|
330
|
-
first_arg = stmt.arguments&.arguments&.first
|
|
331
|
-
arg_val = begin
|
|
332
|
-
PrismUtils.extract_literal_value(first_arg)
|
|
333
|
-
rescue StandardError
|
|
334
|
-
nil
|
|
335
|
-
end
|
|
336
|
-
|
|
337
|
-
if arg_val && arg_val.to_s == gem_name.to_s
|
|
338
|
-
# Remove this gem call from content
|
|
339
|
-
out = out.sub(stmt.slice, "")
|
|
340
|
-
end
|
|
341
|
-
end
|
|
342
|
-
end
|
|
343
|
-
|
|
344
|
-
out
|
|
345
|
-
rescue StandardError => e
|
|
346
|
-
Kettle::Dev.debug_error(e, __method__) if defined?(Kettle::Dev.debug_error)
|
|
347
|
-
content
|
|
348
|
-
end
|
|
349
|
-
end
|
|
350
|
-
end
|
|
351
|
-
end
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Kettle
|
|
4
|
-
module Dev
|
|
5
|
-
# Prism helpers for Gemfile-like merging.
|
|
6
|
-
module PrismGemfile
|
|
7
|
-
module_function
|
|
8
|
-
|
|
9
|
-
# Merge gem calls from src_content into dest_content.
|
|
10
|
-
# - Replaces dest `source` call with src's if present.
|
|
11
|
-
# - Replaces or inserts non-comment `git_source` definitions.
|
|
12
|
-
# - Appends missing `gem` calls (by name) from src to dest preserving dest content and newlines.
|
|
13
|
-
# This is a conservative, comment-preserving approach using Prism to detect call nodes.
|
|
14
|
-
def merge_gem_calls(src_content, dest_content)
|
|
15
|
-
src_res = PrismUtils.parse_with_comments(src_content)
|
|
16
|
-
dest_res = PrismUtils.parse_with_comments(dest_content)
|
|
17
|
-
|
|
18
|
-
src_stmts = PrismUtils.extract_statements(src_res.value.statements)
|
|
19
|
-
dest_stmts = PrismUtils.extract_statements(dest_res.value.statements)
|
|
20
|
-
|
|
21
|
-
# Find source nodes
|
|
22
|
-
src_source_node = src_stmts.find { |n| PrismUtils.call_to?(n, :source) }
|
|
23
|
-
dest_source_node = dest_stmts.find { |n| PrismUtils.call_to?(n, :source) }
|
|
24
|
-
|
|
25
|
-
out = dest_content.dup
|
|
26
|
-
dest_lines = out.lines
|
|
27
|
-
|
|
28
|
-
# Replace or insert source line
|
|
29
|
-
if src_source_node
|
|
30
|
-
src_src = src_source_node.slice
|
|
31
|
-
if dest_source_node
|
|
32
|
-
out = out.sub(dest_source_node.slice, src_src)
|
|
33
|
-
dest_lines = out.lines
|
|
34
|
-
else
|
|
35
|
-
# insert after any leading comment/blank block
|
|
36
|
-
insert_idx = 0
|
|
37
|
-
while insert_idx < dest_lines.length && (dest_lines[insert_idx].strip.empty? || dest_lines[insert_idx].lstrip.start_with?("#"))
|
|
38
|
-
insert_idx += 1
|
|
39
|
-
end
|
|
40
|
-
dest_lines.insert(insert_idx, src_src.rstrip + "\n")
|
|
41
|
-
out = dest_lines.join
|
|
42
|
-
dest_lines = out.lines
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# --- Handle git_source replacement/insertion ---
|
|
47
|
-
src_git_nodes = src_stmts.select { |n| PrismUtils.call_to?(n, :git_source) }
|
|
48
|
-
if src_git_nodes.any?
|
|
49
|
-
# We'll operate on dest_lines for insertion; recompute dest_stmts if we changed out
|
|
50
|
-
dest_res = PrismUtils.parse_with_comments(out)
|
|
51
|
-
dest_stmts = PrismUtils.extract_statements(dest_res.value.statements)
|
|
52
|
-
|
|
53
|
-
# Iterate in reverse when inserting so that inserting at the same index
|
|
54
|
-
# preserves the original order from the source (we insert at a fixed index).
|
|
55
|
-
src_git_nodes.reverse_each do |gnode|
|
|
56
|
-
key = PrismUtils.statement_key(gnode) # => [:git_source, name]
|
|
57
|
-
name = key && key[1]
|
|
58
|
-
replaced = false
|
|
59
|
-
|
|
60
|
-
if name
|
|
61
|
-
dest_same_idx = dest_stmts.index { |d| PrismUtils.statement_key(d) && PrismUtils.statement_key(d)[0] == :git_source && PrismUtils.statement_key(d)[1] == name }
|
|
62
|
-
if dest_same_idx
|
|
63
|
-
# Replace the matching dest node slice
|
|
64
|
-
out = out.sub(dest_stmts[dest_same_idx].slice, gnode.slice)
|
|
65
|
-
replaced = true
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# If not replaced, prefer to replace an existing github entry in destination
|
|
70
|
-
# (this mirrors previous behavior in template_helpers which favored replacing
|
|
71
|
-
# a github git_source when inserting others).
|
|
72
|
-
unless replaced
|
|
73
|
-
dest_github_idx = dest_stmts.index { |d| PrismUtils.statement_key(d) && PrismUtils.statement_key(d)[0] == :git_source && PrismUtils.statement_key(d)[1] == "github" }
|
|
74
|
-
if dest_github_idx
|
|
75
|
-
out = out.sub(dest_stmts[dest_github_idx].slice, gnode.slice)
|
|
76
|
-
replaced = true
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
unless replaced
|
|
81
|
-
# Insert below source line if present, else at top after comments
|
|
82
|
-
dest_lines = out.lines
|
|
83
|
-
insert_idx = dest_lines.index { |ln| !ln.strip.start_with?("#") && ln =~ /^\s*source\s+/ } || 0
|
|
84
|
-
insert_idx += 1 if insert_idx
|
|
85
|
-
dest_lines.insert(insert_idx, gnode.slice.rstrip + "\n")
|
|
86
|
-
out = dest_lines.join
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# Recompute dest_stmts for subsequent iterations
|
|
90
|
-
dest_res = PrismUtils.parse_with_comments(out)
|
|
91
|
-
dest_stmts = PrismUtils.extract_statements(dest_res.value.statements)
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
# Collect gem names present in dest (top-level only)
|
|
96
|
-
dest_res = PrismUtils.parse_with_comments(out)
|
|
97
|
-
dest_stmts = PrismUtils.extract_statements(dest_res.value.statements)
|
|
98
|
-
dest_gem_names = dest_stmts.map { |n| PrismUtils.statement_key(n) }.compact.select { |k| k[0] == :gem }.map { |k| k[1] }.to_set
|
|
99
|
-
|
|
100
|
-
# Find gem call nodes in src and append missing ones (top-level only)
|
|
101
|
-
missing_nodes = src_stmts.select do |n|
|
|
102
|
-
k = PrismUtils.statement_key(n)
|
|
103
|
-
k && k.first == :gem && !dest_gem_names.include?(k[1])
|
|
104
|
-
end
|
|
105
|
-
if missing_nodes.any?
|
|
106
|
-
out << "\n" unless out.end_with?("\n") || out.empty?
|
|
107
|
-
missing_nodes.each do |n|
|
|
108
|
-
# Preserve inline comments for the source node when appending
|
|
109
|
-
inline = begin
|
|
110
|
-
PrismUtils.inline_comments_for_node(src_res, n)
|
|
111
|
-
rescue
|
|
112
|
-
[]
|
|
113
|
-
end
|
|
114
|
-
line = n.slice.rstrip
|
|
115
|
-
if inline && inline.any?
|
|
116
|
-
inline_text = inline.map { |c| c.slice.strip }.join(" ")
|
|
117
|
-
# Only append the inline text if it's not already part of the slice
|
|
118
|
-
line = line + " " + inline_text unless line.include?(inline_text)
|
|
119
|
-
end
|
|
120
|
-
out << line + "\n"
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
out
|
|
125
|
-
rescue StandardError => e
|
|
126
|
-
# Use debug_log if available, otherwise Kettle::Dev.debug_error
|
|
127
|
-
if defined?(Kettle::Dev) && Kettle::Dev.respond_to?(:debug_error)
|
|
128
|
-
Kettle::Dev.debug_error(e, __method__)
|
|
129
|
-
else
|
|
130
|
-
Kernel.warn("[#{__method__}] #{e.class}: #{e.message}")
|
|
131
|
-
end
|
|
132
|
-
dest_content
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
# Remove gem calls that reference the given gem name (to prevent self-dependency).
|
|
136
|
-
# Works by locating gem() call nodes where the first argument matches gem_name.
|
|
137
|
-
# @param content [String] Gemfile-like content
|
|
138
|
-
# @param gem_name [String] the gem name to remove
|
|
139
|
-
# @return [String] modified content with self-referential gem calls removed
|
|
140
|
-
def remove_gem_dependency(content, gem_name)
|
|
141
|
-
return content if gem_name.to_s.strip.empty?
|
|
142
|
-
|
|
143
|
-
result = PrismUtils.parse_with_comments(content)
|
|
144
|
-
stmts = PrismUtils.extract_statements(result.value.statements)
|
|
145
|
-
|
|
146
|
-
# Find gem call nodes where first argument matches gem_name
|
|
147
|
-
gem_nodes = stmts.select do |n|
|
|
148
|
-
next false unless n.is_a?(Prism::CallNode) && n.name == :gem
|
|
149
|
-
|
|
150
|
-
first_arg = n.arguments&.arguments&.first
|
|
151
|
-
arg_val = begin
|
|
152
|
-
PrismUtils.extract_literal_value(first_arg)
|
|
153
|
-
rescue StandardError
|
|
154
|
-
nil
|
|
155
|
-
end
|
|
156
|
-
arg_val && arg_val.to_s == gem_name.to_s
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
# Remove each matching gem call from content
|
|
160
|
-
out = content.dup
|
|
161
|
-
gem_nodes.each do |gn|
|
|
162
|
-
# Remove the entire line(s) containing this node
|
|
163
|
-
out = out.sub(gn.slice, "")
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
out
|
|
167
|
-
rescue StandardError => e
|
|
168
|
-
if defined?(Kettle::Dev) && Kettle::Dev.respond_to?(:debug_error)
|
|
169
|
-
Kettle::Dev.debug_error(e, __method__)
|
|
170
|
-
else
|
|
171
|
-
Kernel.warn("[#{__method__}] #{e.class}: #{e.message}")
|
|
172
|
-
end
|
|
173
|
-
content
|
|
174
|
-
end
|
|
175
|
-
end
|
|
176
|
-
end
|
|
177
|
-
end
|