markdown-merge 1.0.3 → 7.1.1
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/LICENSE.md +13 -0
- data/README.md +99 -482
- data/lib/markdown/merge/backend_support.rb +200 -0
- data/lib/markdown/merge/cleanse/block_spacing.rb +18 -23
- data/lib/markdown/merge/cleanse/code_fence_spacing.rb +16 -16
- data/lib/markdown/merge/cleanse/condensed_link_refs.rb +36 -30
- data/lib/markdown/merge/cleanse/list_marker_duplication.rb +66 -0
- data/lib/markdown/merge/cleanse/templating_corruption.rb +86 -0
- data/lib/markdown/merge/cleanse.rb +5 -3
- data/lib/markdown/merge/code_block_match_refiner.rb +111 -0
- data/lib/markdown/merge/code_block_merger.rb +489 -47
- data/lib/markdown/merge/comment_tracker.rb +42 -0
- data/lib/markdown/merge/conflict_resolver.rb +77 -6
- data/lib/markdown/merge/debug_logger.rb +2 -2
- data/lib/markdown/merge/document_problems.rb +3 -3
- data/lib/markdown/merge/file_aligner.rb +433 -133
- data/lib/markdown/merge/file_analysis.rb +387 -51
- data/lib/markdown/merge/file_analysis_base.rb +188 -51
- data/lib/markdown/merge/gap_line_node.rb +14 -8
- data/lib/markdown/merge/link_definition_node.rb +5 -5
- data/lib/markdown/merge/link_parser.rb +60 -60
- data/lib/markdown/merge/link_reference_rehydrator.rb +14 -14
- data/lib/markdown/merge/list_match_refiner.rb +98 -0
- data/lib/markdown/merge/list_merger.rb +322 -0
- data/lib/markdown/merge/markdown_structure.rb +3 -3
- data/lib/markdown/merge/merge_result.rb +321 -4
- data/lib/markdown/merge/node_type_normalizer.rb +6 -6
- data/lib/markdown/merge/output_builder.rb +101 -19
- data/lib/markdown/merge/partial_template_merger.rb +248 -27
- data/lib/markdown/merge/preservation_support.rb +291 -0
- data/lib/markdown/merge/smart_merger.rb +62 -14
- data/lib/markdown/merge/smart_merger_base.rb +929 -60
- data/lib/markdown/merge/table_match_algorithm.rb +22 -27
- data/lib/markdown/merge/table_match_refiner.rb +6 -10
- data/lib/markdown/merge/version.rb +5 -4
- data/lib/markdown/merge/whitespace_normalizer.rb +25 -33
- data/lib/markdown/merge/wrapper_support.rb +194 -0
- data/lib/markdown/merge.rb +669 -122
- data/lib/markdown-merge.rb +9 -4
- data/sig/markdown/merge.rbs +3 -336
- data.tar.gz.sig +0 -0
- metadata +104 -93
- metadata.gz.sig +0 -0
- data/CHANGELOG.md +0 -308
- data/CITATION.cff +0 -20
- data/CODE_OF_CONDUCT.md +0 -134
- data/CONTRIBUTING.md +0 -227
- data/FUNDING.md +0 -74
- data/LICENSE.txt +0 -21
- data/REEK +0 -0
- data/RUBOCOP.md +0 -71
- data/SECURITY.md +0 -21
|
@@ -33,41 +33,51 @@ module Markdown
|
|
|
33
33
|
# Default language-to-merger mapping
|
|
34
34
|
# Each merger is a lambda that takes (template_content, dest_content, preference)
|
|
35
35
|
# and returns { merged: true/false, content: String, stats: Hash }
|
|
36
|
-
# :
|
|
36
|
+
# simplecov:disable integration - DEFAULT_MERGERS lambdas require external gems
|
|
37
37
|
DEFAULT_MERGERS = {
|
|
38
38
|
# Ruby code blocks
|
|
39
|
-
|
|
40
|
-
require
|
|
39
|
+
'ruby' => lambda { |template, dest, preference, **opts|
|
|
40
|
+
require 'prism/merge'
|
|
41
41
|
CodeBlockMerger.merge_with_prism(template, dest, preference, **opts)
|
|
42
42
|
},
|
|
43
|
-
|
|
44
|
-
require
|
|
43
|
+
'rb' => lambda { |template, dest, preference, **opts|
|
|
44
|
+
require 'prism/merge'
|
|
45
45
|
CodeBlockMerger.merge_with_prism(template, dest, preference, **opts)
|
|
46
46
|
},
|
|
47
47
|
|
|
48
48
|
# YAML code blocks
|
|
49
|
-
|
|
50
|
-
require
|
|
49
|
+
'yaml' => lambda { |template, dest, preference, **opts|
|
|
50
|
+
require 'psych/merge'
|
|
51
51
|
CodeBlockMerger.merge_with_psych(template, dest, preference, **opts)
|
|
52
52
|
},
|
|
53
|
-
|
|
54
|
-
require
|
|
53
|
+
'yml' => lambda { |template, dest, preference, **opts|
|
|
54
|
+
require 'psych/merge'
|
|
55
55
|
CodeBlockMerger.merge_with_psych(template, dest, preference, **opts)
|
|
56
56
|
},
|
|
57
57
|
|
|
58
58
|
# JSON code blocks
|
|
59
|
-
|
|
60
|
-
require
|
|
59
|
+
'json' => lambda { |template, dest, preference, **opts|
|
|
60
|
+
require 'json/merge'
|
|
61
61
|
CodeBlockMerger.merge_with_json(template, dest, preference, **opts)
|
|
62
62
|
},
|
|
63
63
|
|
|
64
|
+
# Markdown code blocks
|
|
65
|
+
'markdown' => lambda { |template, dest, preference, **opts|
|
|
66
|
+
require 'markdown/merge'
|
|
67
|
+
CodeBlockMerger.merge_with_markdown(template, dest, preference, **opts)
|
|
68
|
+
},
|
|
69
|
+
'md' => lambda { |template, dest, preference, **opts|
|
|
70
|
+
require 'markdown/merge'
|
|
71
|
+
CodeBlockMerger.merge_with_markdown(template, dest, preference, **opts)
|
|
72
|
+
},
|
|
73
|
+
|
|
64
74
|
# TOML code blocks
|
|
65
|
-
|
|
66
|
-
require
|
|
75
|
+
'toml' => lambda { |template, dest, preference, **opts|
|
|
76
|
+
require 'toml/merge'
|
|
67
77
|
CodeBlockMerger.merge_with_toml(template, dest, preference, **opts)
|
|
68
|
-
}
|
|
78
|
+
}
|
|
69
79
|
}.freeze
|
|
70
|
-
# :
|
|
80
|
+
# simplecov:enable
|
|
71
81
|
|
|
72
82
|
# @return [Hash<String, Proc>] Language to merger mapping
|
|
73
83
|
attr_reader :mergers
|
|
@@ -75,6 +85,9 @@ module Markdown
|
|
|
75
85
|
# @return [Boolean] Whether inner-merge is enabled
|
|
76
86
|
attr_reader :enabled
|
|
77
87
|
|
|
88
|
+
# @return [Array<Ast::Merge::Runtime::Delegate>] Runtime delegates exposed by this merger
|
|
89
|
+
attr_reader :runtime_delegates
|
|
90
|
+
|
|
78
91
|
# Creates a new CodeBlockMerger.
|
|
79
92
|
#
|
|
80
93
|
# @param mergers [Hash<String, Proc>] Custom language-to-merger mapping.
|
|
@@ -83,6 +96,7 @@ module Markdown
|
|
|
83
96
|
def initialize(mergers: {}, enabled: true)
|
|
84
97
|
@mergers = DEFAULT_MERGERS.merge(mergers)
|
|
85
98
|
@enabled = enabled
|
|
99
|
+
@runtime_delegates = build_runtime_delegates.freeze
|
|
86
100
|
end
|
|
87
101
|
|
|
88
102
|
# Check if inner-merge is available for a language.
|
|
@@ -103,67 +117,410 @@ module Markdown
|
|
|
103
117
|
# @param preference [Symbol] :destination or :template
|
|
104
118
|
# @param opts [Hash] Additional options passed to the merger
|
|
105
119
|
# @return [Hash] { merged: Boolean, content: String, stats: Hash }
|
|
106
|
-
def merge_code_blocks(template_node, dest_node, preference:, **opts)
|
|
107
|
-
|
|
120
|
+
def merge_code_blocks(template_node, dest_node, preference:, runtime_session: nil, parent_operation: nil, **opts)
|
|
121
|
+
if runtime_session && parent_operation
|
|
122
|
+
return merge_code_blocks_with_runtime(
|
|
123
|
+
template_node,
|
|
124
|
+
dest_node,
|
|
125
|
+
preference: preference,
|
|
126
|
+
runtime_session: runtime_session,
|
|
127
|
+
parent_operation: parent_operation,
|
|
128
|
+
**opts
|
|
129
|
+
)
|
|
130
|
+
end
|
|
108
131
|
|
|
109
|
-
|
|
110
|
-
|
|
132
|
+
merge_code_blocks_without_runtime(template_node, dest_node, preference: preference, **opts)
|
|
133
|
+
end
|
|
111
134
|
|
|
112
|
-
|
|
113
|
-
return not_merged(
|
|
135
|
+
def merge_code_blocks_without_runtime(template_node, dest_node, preference:, **opts)
|
|
136
|
+
return not_merged('inner-merge disabled') unless @enabled
|
|
137
|
+
|
|
138
|
+
language = extract_language(template_node) || extract_language(dest_node)
|
|
139
|
+
return not_merged('no language specified') unless language
|
|
114
140
|
|
|
115
141
|
template_content = extract_content(template_node)
|
|
116
142
|
dest_content = extract_content(dest_node)
|
|
117
143
|
|
|
118
|
-
|
|
144
|
+
perform_code_block_merge(
|
|
145
|
+
language: language,
|
|
146
|
+
template_content: template_content,
|
|
147
|
+
dest_content: dest_content,
|
|
148
|
+
preference: preference,
|
|
149
|
+
reference_node: dest_node,
|
|
150
|
+
**opts
|
|
151
|
+
)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
private
|
|
155
|
+
|
|
156
|
+
def merge_code_blocks_with_runtime(template_node, dest_node, preference:, runtime_session:, parent_operation:,
|
|
157
|
+
**opts)
|
|
158
|
+
operation = build_runtime_operation(
|
|
159
|
+
template_node: template_node,
|
|
160
|
+
dest_node: dest_node,
|
|
161
|
+
preference: preference,
|
|
162
|
+
runtime_session: runtime_session,
|
|
163
|
+
parent_operation: parent_operation,
|
|
164
|
+
**opts
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
parent_operation.add_child(operation)
|
|
168
|
+
parent_frame = runtime_session.frame_for(parent_operation.operation_id)
|
|
169
|
+
delegate = runtime_session.resolve_delegate_for(operation.surface, capability: :merge)
|
|
170
|
+
runtime_session.register(
|
|
171
|
+
operation,
|
|
172
|
+
frame: Ast::Merge::Runtime::Frame.new(
|
|
173
|
+
parent_operation_id: parent_operation.operation_id,
|
|
174
|
+
operation_id: operation.operation_id,
|
|
175
|
+
depth: parent_frame ? parent_frame.depth + 1 : 1,
|
|
176
|
+
surface_path: operation.surface.address,
|
|
177
|
+
language_chain: [*(parent_frame&.language_chain || [:markdown]),
|
|
178
|
+
operation.surface.effective_language].compact
|
|
179
|
+
),
|
|
180
|
+
delegate: delegate
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
unless delegate
|
|
184
|
+
reason = unsupported_runtime_reason_for(operation.surface)
|
|
185
|
+
operation.fail!(
|
|
186
|
+
diagnostic: Ast::Merge::Runtime::Diagnostic.new(
|
|
187
|
+
severity: :warn,
|
|
188
|
+
kind: :unsupported_capability,
|
|
189
|
+
operation_id: operation.operation_id,
|
|
190
|
+
surface_path: operation.surface.address,
|
|
191
|
+
message: reason,
|
|
192
|
+
metadata: {
|
|
193
|
+
capability: :merge,
|
|
194
|
+
language: operation.surface.effective_language
|
|
195
|
+
}
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
return not_merged(reason).merge(runtime_operation_id: operation.operation_id)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
operation.running!
|
|
202
|
+
child_result = delegate.merge(operation: operation, session: runtime_session)
|
|
203
|
+
if child_result.unresolved?
|
|
204
|
+
operation.unresolved!(result: child_result)
|
|
205
|
+
else
|
|
206
|
+
operation.complete!(result: child_result)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
if child_result.metadata[:merged]
|
|
210
|
+
{
|
|
211
|
+
merged: true,
|
|
212
|
+
content: child_result.replacement_text,
|
|
213
|
+
stats: child_result.metadata[:stats] || {},
|
|
214
|
+
runtime_operation_id: operation.operation_id,
|
|
215
|
+
runtime_surface_path: operation.surface.address,
|
|
216
|
+
unresolved_cases: child_result.unresolved_cases,
|
|
217
|
+
metadata: child_result.metadata
|
|
218
|
+
}
|
|
219
|
+
else
|
|
220
|
+
not_merged(child_result.metadata[:reason] || 'merger declined').merge(
|
|
221
|
+
stats: child_result.metadata[:stats] || {},
|
|
222
|
+
runtime_operation_id: operation.operation_id
|
|
223
|
+
)
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def build_runtime_operation(template_node:, dest_node:, preference:, runtime_session:, parent_operation:, **opts)
|
|
228
|
+
language = extract_language(template_node) || extract_language(dest_node)
|
|
229
|
+
reference_node = dest_node || template_node
|
|
230
|
+
surface = Ast::Merge::Runtime::Surface.new(
|
|
231
|
+
surface_kind: :markdown_fenced_code_block,
|
|
232
|
+
declared_language: language,
|
|
233
|
+
effective_language: language,
|
|
234
|
+
address: runtime_surface_address(reference_node, runtime_session),
|
|
235
|
+
parent_address: parent_operation.surface.address,
|
|
236
|
+
span: runtime_surface_span(reference_node),
|
|
237
|
+
reconstruction_strategy: :portable_write,
|
|
238
|
+
metadata: {
|
|
239
|
+
fence_info: reference_node.respond_to?(:fence_info) ? reference_node.fence_info : nil,
|
|
240
|
+
language: language
|
|
241
|
+
}.compact
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
Ast::Merge::Runtime::Operation.new(
|
|
245
|
+
operation_id: "markdown-code-block-#{runtime_session.operations.count}",
|
|
246
|
+
surface: surface,
|
|
247
|
+
template_fragment: rebuild_code_block(language, extract_content(template_node), template_node),
|
|
248
|
+
destination_fragment: rebuild_code_block(language, extract_content(dest_node), dest_node),
|
|
249
|
+
requested_strategy: :delegate_child_surface,
|
|
250
|
+
options: {
|
|
251
|
+
preference: preference,
|
|
252
|
+
add_template_only_nodes: opts.fetch(:add_template_only_nodes, false),
|
|
253
|
+
resolution_mode: opts.fetch(:resolution_mode, :eager),
|
|
254
|
+
unresolved_policy: Ast::Merge::UnresolvedPolicy.coerce(opts[:unresolved_policy]).to_h,
|
|
255
|
+
template_content: extract_content(template_node),
|
|
256
|
+
destination_content: extract_content(dest_node),
|
|
257
|
+
reference_node: reference_node
|
|
258
|
+
}
|
|
259
|
+
)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def runtime_surface_address(reference_node, runtime_session)
|
|
263
|
+
span = runtime_surface_span(reference_node)
|
|
264
|
+
suffix =
|
|
265
|
+
if span
|
|
266
|
+
"L#{span.begin}-L#{span.end}"
|
|
267
|
+
else
|
|
268
|
+
"operation-#{runtime_session.operations.count}"
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
"document[0] > fenced_code_block[#{suffix}]"
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def runtime_surface_span(node)
|
|
275
|
+
position = node_source_position(node)
|
|
276
|
+
start_line = position&.dig(:start_line)
|
|
277
|
+
end_line = position&.dig(:end_line)
|
|
278
|
+
return unless start_line && end_line
|
|
279
|
+
|
|
280
|
+
start_line..end_line
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def node_source_position(node)
|
|
284
|
+
raw_node = unwrap_code_block_node(node)
|
|
285
|
+
raw_node&.source_position
|
|
286
|
+
rescue NoMethodError
|
|
287
|
+
nil
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def build_runtime_delegates
|
|
291
|
+
[
|
|
292
|
+
Ast::Merge::Runtime::Delegate.new(
|
|
293
|
+
name: 'markdown-code-block-inner-merge',
|
|
294
|
+
priority: 100,
|
|
295
|
+
surface_kinds: [:markdown_fenced_code_block],
|
|
296
|
+
languages: mergers.keys,
|
|
297
|
+
capabilities: { merge: [:markdown_fenced_code_block] },
|
|
298
|
+
merge: method(:merge_runtime_surface),
|
|
299
|
+
metadata: {
|
|
300
|
+
source: :markdown_merge,
|
|
301
|
+
languages: mergers.keys.sort
|
|
302
|
+
}
|
|
303
|
+
)
|
|
304
|
+
]
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def merge_runtime_surface(operation:, session:)
|
|
308
|
+
result = perform_code_block_merge(
|
|
309
|
+
language: operation.surface.effective_language,
|
|
310
|
+
template_content: operation.options[:template_content].to_s,
|
|
311
|
+
dest_content: operation.options[:destination_content].to_s,
|
|
312
|
+
preference: operation.options[:preference],
|
|
313
|
+
reference_node: operation.options[:reference_node],
|
|
314
|
+
add_template_only_nodes: operation.options.fetch(:add_template_only_nodes, false),
|
|
315
|
+
resolution_mode: operation.options[:resolution_mode],
|
|
316
|
+
unresolved_policy: operation.options[:unresolved_policy]
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
if result[:merged]
|
|
320
|
+
Ast::Merge::Runtime::ChildResult.new(
|
|
321
|
+
replacement_text: result[:content],
|
|
322
|
+
diagnostics: operation.diagnostics,
|
|
323
|
+
capabilities_used: %i[delegated_child_merge language_specific_merge],
|
|
324
|
+
capabilities_missing: [],
|
|
325
|
+
unresolved_cases: result[:unresolved_cases] || [],
|
|
326
|
+
metadata: {
|
|
327
|
+
merged: true,
|
|
328
|
+
stats: result[:stats] || {},
|
|
329
|
+
language: operation.surface.effective_language,
|
|
330
|
+
delegate_name: operation.delegate_name,
|
|
331
|
+
session_policy: session.policy_context
|
|
332
|
+
}.merge(result[:metadata] || {})
|
|
333
|
+
)
|
|
334
|
+
else
|
|
335
|
+
operation.add_diagnostic(
|
|
336
|
+
Ast::Merge::Runtime::Diagnostic.new(
|
|
337
|
+
severity: :warn,
|
|
338
|
+
kind: :delegated_merge_declined,
|
|
339
|
+
operation_id: operation.operation_id,
|
|
340
|
+
surface_path: operation.surface.address,
|
|
341
|
+
message: result[:reason].to_s,
|
|
342
|
+
metadata: {
|
|
343
|
+
language: operation.surface.effective_language
|
|
344
|
+
}
|
|
345
|
+
)
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
Ast::Merge::Runtime::ChildResult.new(
|
|
349
|
+
replacement_text: '',
|
|
350
|
+
diagnostics: operation.diagnostics,
|
|
351
|
+
capabilities_used: [:delegated_child_merge],
|
|
352
|
+
capabilities_missing: [:language_specific_merge],
|
|
353
|
+
metadata: {
|
|
354
|
+
merged: false,
|
|
355
|
+
reason: result[:reason],
|
|
356
|
+
stats: result[:stats] || {},
|
|
357
|
+
language: operation.surface.effective_language,
|
|
358
|
+
delegate_name: operation.delegate_name,
|
|
359
|
+
session_policy: session.policy_context
|
|
360
|
+
}.merge(result[:metadata] || {})
|
|
361
|
+
)
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def perform_code_block_merge(language:, template_content:, dest_content:, preference:, reference_node:, **opts)
|
|
366
|
+
return not_merged('no language specified') unless language
|
|
367
|
+
|
|
368
|
+
merger = @mergers[language.to_s.downcase]
|
|
369
|
+
return not_merged("no merger for language: #{language}") unless merger
|
|
370
|
+
|
|
119
371
|
if template_content == dest_content
|
|
120
372
|
return {
|
|
121
373
|
merged: true,
|
|
122
|
-
content: rebuild_code_block(language, dest_content,
|
|
123
|
-
stats: {decision: :identical}
|
|
374
|
+
content: rebuild_code_block(language, dest_content, reference_node),
|
|
375
|
+
stats: { decision: :identical }
|
|
124
376
|
}
|
|
125
377
|
end
|
|
126
378
|
|
|
127
379
|
begin
|
|
128
|
-
result = merger.call(template_content, dest_content, preference, **opts)
|
|
380
|
+
result = merger.call(template_content, dest_content, preference, nested_mergers: @mergers, **opts)
|
|
129
381
|
if result[:merged]
|
|
382
|
+
metadata = (result[:metadata] || {}).dup
|
|
383
|
+
metadata[:root_apply_candidates_by_case_id] = root_apply_candidates_by_case_id(
|
|
384
|
+
merger: merger,
|
|
385
|
+
unresolved_cases: result[:unresolved_cases],
|
|
386
|
+
language: language,
|
|
387
|
+
template_content: template_content,
|
|
388
|
+
dest_content: dest_content,
|
|
389
|
+
preference: preference,
|
|
390
|
+
reference_node: reference_node,
|
|
391
|
+
**opts
|
|
392
|
+
)
|
|
393
|
+
metadata[:delegated_apply_renderer] = delegated_apply_renderer(
|
|
394
|
+
merger: merger,
|
|
395
|
+
language: language,
|
|
396
|
+
template_content: template_content,
|
|
397
|
+
dest_content: dest_content,
|
|
398
|
+
preference: preference,
|
|
399
|
+
reference_node: reference_node,
|
|
400
|
+
**opts
|
|
401
|
+
)
|
|
130
402
|
{
|
|
131
403
|
merged: true,
|
|
132
|
-
content: rebuild_code_block(language, result[:content],
|
|
404
|
+
content: rebuild_code_block(language, result[:content], reference_node),
|
|
133
405
|
stats: result[:stats] || {},
|
|
406
|
+
unresolved_cases: result[:unresolved_cases] || [],
|
|
407
|
+
metadata: metadata
|
|
134
408
|
}
|
|
135
409
|
else
|
|
136
|
-
not_merged(result[:reason] ||
|
|
410
|
+
not_merged(result[:reason] || 'merger declined').merge(
|
|
411
|
+
stats: result[:stats] || {},
|
|
412
|
+
metadata: result[:metadata] || {}
|
|
413
|
+
)
|
|
137
414
|
end
|
|
138
415
|
rescue LoadError => e
|
|
139
416
|
not_merged("merger gem not available: #{e.message}")
|
|
140
417
|
rescue TreeHaver::Error => e
|
|
141
|
-
# TreeHaver::NotAvailable and TreeHaver::Error inherit from Exception (not StandardError)
|
|
142
|
-
# for safety reasons related to backend conflicts. We catch them here to handle
|
|
143
|
-
# gracefully when a backend isn't properly configured.
|
|
144
418
|
not_merged("backend not available: #{e.message}")
|
|
145
419
|
rescue StandardError => e
|
|
146
|
-
# :nocov: defensive - Prism::Merge::ParseError handling when prism/merge is loaded
|
|
147
|
-
# Check for Prism::Merge::ParseError if prism/merge is loaded
|
|
148
420
|
if defined?(::Prism::Merge::ParseError) && e.is_a?(::Prism::Merge::ParseError)
|
|
149
421
|
not_merged("Ruby parse error: #{e.message}")
|
|
150
422
|
else
|
|
151
423
|
not_merged("merge failed: #{e.class}: #{e.message}")
|
|
152
424
|
end
|
|
153
|
-
# :nocov:
|
|
154
425
|
end
|
|
155
426
|
end
|
|
156
427
|
|
|
157
|
-
|
|
428
|
+
def unsupported_runtime_reason_for(surface)
|
|
429
|
+
language = surface.effective_language || surface.declared_language
|
|
430
|
+
return 'no language specified' unless language
|
|
431
|
+
|
|
432
|
+
"no merger for language: #{language}"
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
def root_apply_candidates_by_case_id(merger:, unresolved_cases:, language:, template_content:, dest_content:,
|
|
436
|
+
preference:, reference_node:, **opts)
|
|
437
|
+
cases = Array(unresolved_cases)
|
|
438
|
+
return {} unless cases.one?
|
|
439
|
+
|
|
440
|
+
resolution_case = cases.first
|
|
441
|
+
{
|
|
442
|
+
resolution_case.case_id => {
|
|
443
|
+
template: rebuild_code_block(
|
|
444
|
+
language,
|
|
445
|
+
merged_delegate_content_for(
|
|
446
|
+
merger: merger,
|
|
447
|
+
template_content: template_content,
|
|
448
|
+
dest_content: dest_content,
|
|
449
|
+
preference: preference,
|
|
450
|
+
case_id: resolution_case.case_id,
|
|
451
|
+
selection: :template,
|
|
452
|
+
**opts
|
|
453
|
+
),
|
|
454
|
+
reference_node
|
|
455
|
+
),
|
|
456
|
+
destination: rebuild_code_block(
|
|
457
|
+
language,
|
|
458
|
+
merged_delegate_content_for(
|
|
459
|
+
merger: merger,
|
|
460
|
+
template_content: template_content,
|
|
461
|
+
dest_content: dest_content,
|
|
462
|
+
preference: preference,
|
|
463
|
+
case_id: resolution_case.case_id,
|
|
464
|
+
selection: :destination,
|
|
465
|
+
**opts
|
|
466
|
+
),
|
|
467
|
+
reference_node
|
|
468
|
+
)
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
def delegated_apply_renderer(merger:, language:, template_content:, dest_content:, preference:, reference_node:,
|
|
474
|
+
**opts)
|
|
475
|
+
lambda do |selections|
|
|
476
|
+
delegated_result = delegated_merge_result_for(
|
|
477
|
+
merger: merger,
|
|
478
|
+
template_content: template_content,
|
|
479
|
+
dest_content: dest_content,
|
|
480
|
+
preference: preference,
|
|
481
|
+
apply_unresolved_resolutions: selections,
|
|
482
|
+
**opts
|
|
483
|
+
)
|
|
484
|
+
{
|
|
485
|
+
content: rebuild_code_block(language, delegated_result[:content].to_s, reference_node),
|
|
486
|
+
unresolved_cases: delegated_result[:unresolved_cases] || [],
|
|
487
|
+
metadata: delegated_result[:metadata] || {}
|
|
488
|
+
}
|
|
489
|
+
end
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
def merged_delegate_content_for(merger:, template_content:, dest_content:, preference:, case_id: nil, selection: nil,
|
|
493
|
+
apply_unresolved_resolutions: nil, **opts)
|
|
494
|
+
delegated_merge_result_for(
|
|
495
|
+
merger: merger,
|
|
496
|
+
template_content: template_content,
|
|
497
|
+
dest_content: dest_content,
|
|
498
|
+
preference: preference,
|
|
499
|
+
case_id: case_id,
|
|
500
|
+
selection: selection,
|
|
501
|
+
apply_unresolved_resolutions: apply_unresolved_resolutions,
|
|
502
|
+
**opts
|
|
503
|
+
)[:content].to_s
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
def delegated_merge_result_for(merger:, template_content:, dest_content:, preference:, case_id: nil, selection: nil,
|
|
507
|
+
apply_unresolved_resolutions: nil, **opts)
|
|
508
|
+
merger.call(
|
|
509
|
+
template_content,
|
|
510
|
+
dest_content,
|
|
511
|
+
preference,
|
|
512
|
+
**opts,
|
|
513
|
+
apply_unresolved_resolutions: apply_unresolved_resolutions || { case_id => selection }
|
|
514
|
+
)
|
|
515
|
+
end
|
|
158
516
|
|
|
159
517
|
# Extract language from a code block node.
|
|
160
518
|
#
|
|
161
519
|
# @param node [Object] The code block node
|
|
162
520
|
# @return [String, nil] The language identifier
|
|
163
521
|
def extract_language(node)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
info = node.fence_info
|
|
522
|
+
raw_node = unwrap_code_block_node(node)
|
|
523
|
+
info = safe_code_block_value(raw_node, :fence_info)
|
|
167
524
|
return if info.nil? || info.empty?
|
|
168
525
|
|
|
169
526
|
# fence_info may contain additional info after the language (e.g., "ruby linenos")
|
|
@@ -175,7 +532,8 @@ module Markdown
|
|
|
175
532
|
# @param node [Object] The code block node
|
|
176
533
|
# @return [String] The code content
|
|
177
534
|
def extract_content(node)
|
|
178
|
-
|
|
535
|
+
raw_node = unwrap_code_block_node(node)
|
|
536
|
+
safe_code_block_value(raw_node, :string_content, :text).to_s
|
|
179
537
|
end
|
|
180
538
|
|
|
181
539
|
# Rebuild a fenced code block with merged content.
|
|
@@ -184,14 +542,14 @@ module Markdown
|
|
|
184
542
|
# @param content [String] The merged content
|
|
185
543
|
# @param reference_node [Object] Node to copy fence style from
|
|
186
544
|
# @return [String] The reconstructed code block
|
|
187
|
-
def rebuild_code_block(language, content,
|
|
545
|
+
def rebuild_code_block(language, content, _reference_node)
|
|
188
546
|
# Ensure content ends with newline for proper fence closing
|
|
189
|
-
content = content.chomp
|
|
547
|
+
content = "#{content.chomp}\n" unless content.end_with?("\n")
|
|
190
548
|
|
|
191
549
|
# Use backticks as default fence
|
|
192
|
-
fence =
|
|
550
|
+
fence = '```'
|
|
193
551
|
|
|
194
|
-
"#{fence}#{language}\n#{content}#{fence}"
|
|
552
|
+
"#{fence}#{language}\n#{content}#{fence}\n"
|
|
195
553
|
end
|
|
196
554
|
|
|
197
555
|
# Return a not-merged result.
|
|
@@ -199,7 +557,27 @@ module Markdown
|
|
|
199
557
|
# @param reason [String] Why merge was not performed
|
|
200
558
|
# @return [Hash] Not-merged result hash
|
|
201
559
|
def not_merged(reason)
|
|
202
|
-
{merged: false, reason: reason}
|
|
560
|
+
{ merged: false, reason: reason }
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
def unwrap_code_block_node(node)
|
|
564
|
+
return node unless defined?(Ast::Merge::NodeTyping::Wrapper) && node.is_a?(Ast::Merge::NodeTyping::Wrapper)
|
|
565
|
+
|
|
566
|
+
Ast::Merge::NodeTyping.unwrap(node)
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
def safe_code_block_value(node, *methods)
|
|
570
|
+
methods.each do |method_name|
|
|
571
|
+
return node.public_send(method_name) if node
|
|
572
|
+
rescue NoMethodError
|
|
573
|
+
next
|
|
574
|
+
rescue Exception => e
|
|
575
|
+
next if e.instance_of?(::RSpec::Mocks::MockExpectationError)
|
|
576
|
+
|
|
577
|
+
raise
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
nil
|
|
203
581
|
end
|
|
204
582
|
|
|
205
583
|
class << self
|
|
@@ -217,12 +595,19 @@ module Markdown
|
|
|
217
595
|
dest,
|
|
218
596
|
preference: preference,
|
|
219
597
|
add_template_only_nodes: opts.fetch(:add_template_only_nodes, false),
|
|
598
|
+
resolution_mode: opts.fetch(:resolution_mode, :eager),
|
|
599
|
+
unresolved_policy: opts[:unresolved_policy]
|
|
220
600
|
)
|
|
601
|
+
merge_result = merger.merge_result
|
|
602
|
+
if opts[:apply_unresolved_resolutions]
|
|
603
|
+
merge_result.apply_unresolved_resolutions!(opts[:apply_unresolved_resolutions])
|
|
604
|
+
end
|
|
221
605
|
|
|
222
606
|
{
|
|
223
607
|
merged: true,
|
|
224
|
-
content:
|
|
608
|
+
content: merge_result.to_s,
|
|
225
609
|
stats: merger.stats,
|
|
610
|
+
unresolved_cases: merge_result.unresolved_cases
|
|
226
611
|
}
|
|
227
612
|
end
|
|
228
613
|
|
|
@@ -240,12 +625,19 @@ module Markdown
|
|
|
240
625
|
dest,
|
|
241
626
|
preference: preference,
|
|
242
627
|
add_template_only_nodes: opts.fetch(:add_template_only_nodes, false),
|
|
628
|
+
resolution_mode: opts.fetch(:resolution_mode, :eager),
|
|
629
|
+
unresolved_policy: opts[:unresolved_policy]
|
|
243
630
|
)
|
|
631
|
+
merge_result = merger.merge_result
|
|
632
|
+
if opts[:apply_unresolved_resolutions]
|
|
633
|
+
merge_result.apply_unresolved_resolutions!(opts[:apply_unresolved_resolutions])
|
|
634
|
+
end
|
|
244
635
|
|
|
245
636
|
{
|
|
246
637
|
merged: true,
|
|
247
|
-
content:
|
|
638
|
+
content: merge_result.to_yaml,
|
|
248
639
|
stats: merger.stats,
|
|
640
|
+
unresolved_cases: merge_result.unresolved_cases
|
|
249
641
|
}
|
|
250
642
|
end
|
|
251
643
|
|
|
@@ -263,12 +655,55 @@ module Markdown
|
|
|
263
655
|
dest,
|
|
264
656
|
preference: preference,
|
|
265
657
|
add_template_only_nodes: opts.fetch(:add_template_only_nodes, false),
|
|
658
|
+
resolution_mode: opts.fetch(:resolution_mode, :eager),
|
|
659
|
+
unresolved_policy: opts[:unresolved_policy]
|
|
266
660
|
)
|
|
661
|
+
merge_result = merger.merge_result
|
|
662
|
+
if opts[:apply_unresolved_resolutions]
|
|
663
|
+
merge_result.apply_unresolved_resolutions!(opts[:apply_unresolved_resolutions])
|
|
664
|
+
end
|
|
267
665
|
|
|
268
666
|
{
|
|
269
667
|
merged: true,
|
|
270
|
-
content:
|
|
668
|
+
content: merge_result.to_json,
|
|
271
669
|
stats: merger.stats,
|
|
670
|
+
unresolved_cases: merge_result.unresolved_cases
|
|
671
|
+
}
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
# Merge Markdown code using markdown-merge.
|
|
675
|
+
#
|
|
676
|
+
# @param template [String] Template Markdown code
|
|
677
|
+
# @param dest [String] Destination Markdown code
|
|
678
|
+
# @param preference [Symbol] :destination or :template
|
|
679
|
+
# @return [Hash] Merge result
|
|
680
|
+
def merge_with_markdown(template, dest, preference, **opts)
|
|
681
|
+
nested_code_block_merger = CodeBlockMerger.new(
|
|
682
|
+
mergers: opts.fetch(:nested_mergers, {}),
|
|
683
|
+
enabled: opts.fetch(:inner_merge_code_blocks, true)
|
|
684
|
+
)
|
|
685
|
+
merger = ::Markdown::Merge::SmartMerger.new(
|
|
686
|
+
template,
|
|
687
|
+
dest,
|
|
688
|
+
preference: preference,
|
|
689
|
+
add_template_only_nodes: opts.fetch(:add_template_only_nodes, false),
|
|
690
|
+
inner_merge_code_blocks: nested_code_block_merger,
|
|
691
|
+
resolution_mode: opts.fetch(:resolution_mode, :eager),
|
|
692
|
+
unresolved_policy: opts[:unresolved_policy]
|
|
693
|
+
)
|
|
694
|
+
merge_result = merger.merge_result
|
|
695
|
+
if opts[:apply_unresolved_resolutions]
|
|
696
|
+
merge_result.apply_unresolved_resolutions!(opts[:apply_unresolved_resolutions])
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
{
|
|
700
|
+
merged: true,
|
|
701
|
+
content: merge_result.to_s,
|
|
702
|
+
stats: merger.stats,
|
|
703
|
+
unresolved_cases: merge_result.unresolved_cases,
|
|
704
|
+
metadata: {
|
|
705
|
+
nested_runtime_session: merger.runtime_session&.to_h
|
|
706
|
+
}
|
|
272
707
|
}
|
|
273
708
|
end
|
|
274
709
|
|
|
@@ -286,12 +721,19 @@ module Markdown
|
|
|
286
721
|
dest,
|
|
287
722
|
preference: preference,
|
|
288
723
|
add_template_only_nodes: opts.fetch(:add_template_only_nodes, false),
|
|
724
|
+
resolution_mode: opts.fetch(:resolution_mode, :eager),
|
|
725
|
+
unresolved_policy: opts[:unresolved_policy]
|
|
289
726
|
)
|
|
727
|
+
merge_result = merger.merge_result
|
|
728
|
+
if opts[:apply_unresolved_resolutions]
|
|
729
|
+
merge_result.apply_unresolved_resolutions!(opts[:apply_unresolved_resolutions])
|
|
730
|
+
end
|
|
290
731
|
|
|
291
732
|
{
|
|
292
733
|
merged: true,
|
|
293
|
-
content:
|
|
734
|
+
content: merge_result.to_toml,
|
|
294
735
|
stats: merger.stats,
|
|
736
|
+
unresolved_cases: merge_result.unresolved_cases
|
|
295
737
|
}
|
|
296
738
|
end
|
|
297
739
|
end
|