markdown-merge 7.0.0 → 7.1.3
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 +673 -0
- data/lib/markdown/merge/backend_support.rb +200 -0
- data/lib/markdown/merge/cleanse/block_spacing.rb +248 -0
- data/lib/markdown/merge/cleanse/code_fence_spacing.rb +294 -0
- data/lib/markdown/merge/cleanse/condensed_link_refs.rb +411 -0
- 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 +44 -0
- data/lib/markdown/merge/code_block_match_refiner.rb +111 -0
- data/lib/markdown/merge/code_block_merger.rb +742 -0
- data/lib/markdown/merge/comment_tracker.rb +42 -0
- data/lib/markdown/merge/conflict_resolver.rb +199 -0
- data/lib/markdown/merge/debug_logger.rb +26 -0
- data/lib/markdown/merge/document_problems.rb +190 -0
- data/lib/markdown/merge/file_aligner.rb +496 -0
- data/lib/markdown/merge/file_analysis.rb +689 -0
- data/lib/markdown/merge/file_analysis_base.rb +766 -0
- data/lib/markdown/merge/freeze_node.rb +93 -0
- data/lib/markdown/merge/gap_line_node.rb +142 -0
- data/lib/markdown/merge/link_definition_formatter.rb +49 -0
- data/lib/markdown/merge/link_definition_node.rb +157 -0
- data/lib/markdown/merge/link_parser.rb +421 -0
- data/lib/markdown/merge/link_reference_rehydrator.rb +320 -0
- 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 +123 -0
- data/lib/markdown/merge/merge_result.rb +483 -0
- data/lib/markdown/merge/node_type_normalizer.rb +126 -0
- data/lib/markdown/merge/output_builder.rb +248 -0
- data/lib/markdown/merge/partial_template_merger.rb +555 -0
- data/lib/markdown/merge/preservation_support.rb +291 -0
- data/lib/markdown/merge/rspec/shared_examples/source_preserving_provider.rb +338 -0
- data/lib/markdown/merge/smart_merger.rb +269 -0
- data/lib/markdown/merge/smart_merger_base.rb +1490 -0
- data/lib/markdown/merge/source_preserving_provider.rb +814 -0
- data/lib/markdown/merge/table_match_algorithm.rb +499 -0
- data/lib/markdown/merge/table_match_refiner.rb +132 -0
- data/lib/markdown/merge/version.rb +5 -3
- data/lib/markdown/merge/whitespace_normalizer.rb +243 -0
- data/lib/markdown/merge/wrapper_support.rb +194 -0
- data/lib/markdown/merge.rb +271 -87
- data/lib/markdown-merge.rb +7 -1
- data/sig/markdown/merge.rbs +62 -0
- data.tar.gz.sig +0 -0
- metadata +289 -15
- metadata.gz.sig +0 -0
data/lib/markdown/merge.rb
CHANGED
|
@@ -1,29 +1,134 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require 'ast/merge'
|
|
4
|
+
require 'tree_haver'
|
|
5
|
+
require_relative 'merge/version'
|
|
4
6
|
|
|
5
7
|
module Markdown
|
|
6
8
|
module Merge
|
|
7
|
-
PACKAGE_NAME =
|
|
9
|
+
PACKAGE_NAME = 'markdown-merge'
|
|
8
10
|
BACKEND_REFERENCES = {
|
|
9
|
-
|
|
11
|
+
'kreuzberg-language-pack' => TreeHaver::KREUZBERG_LANGUAGE_PACK_BACKEND,
|
|
12
|
+
'commonmarker' => TreeHaver::BackendReference.new(id: 'commonmarker', family: 'native').freeze,
|
|
13
|
+
'markly' => TreeHaver::BackendReference.new(id: 'markly', family: 'native').freeze,
|
|
14
|
+
'kramdown' => TreeHaver::BackendReference.new(id: 'kramdown', family: 'native').freeze
|
|
10
15
|
}.freeze
|
|
16
|
+
BACKEND_REGISTRY = Struct.new(:registered, :mutex).new(false, Mutex.new)
|
|
17
|
+
|
|
18
|
+
class Error < Ast::Merge::Error; end
|
|
19
|
+
|
|
20
|
+
class ParseError < Ast::Merge::ParseError
|
|
21
|
+
def initialize(message = nil, content: nil, errors: [])
|
|
22
|
+
super(message, errors: errors, content: content)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class TemplateParseError < ParseError; end
|
|
27
|
+
|
|
28
|
+
class DestinationParseError < ParseError; end
|
|
29
|
+
|
|
30
|
+
class CorruptionDetectedError < Error; end
|
|
31
|
+
|
|
32
|
+
autoload :BackendSupport, 'markdown/merge/backend_support'
|
|
33
|
+
autoload :Cleanse, 'markdown/merge/cleanse'
|
|
34
|
+
autoload :CodeBlockMatchRefiner, 'markdown/merge/code_block_match_refiner'
|
|
35
|
+
autoload :CodeBlockMerger, 'markdown/merge/code_block_merger'
|
|
36
|
+
autoload :CommentTracker, 'markdown/merge/comment_tracker'
|
|
37
|
+
autoload :ConflictResolver, 'markdown/merge/conflict_resolver'
|
|
38
|
+
autoload :DebugLogger, 'markdown/merge/debug_logger'
|
|
39
|
+
autoload :DocumentProblems, 'markdown/merge/document_problems'
|
|
40
|
+
autoload :FileAligner, 'markdown/merge/file_aligner'
|
|
41
|
+
autoload :FileAnalysis, 'markdown/merge/file_analysis'
|
|
42
|
+
autoload :FileAnalysisBase, 'markdown/merge/file_analysis_base'
|
|
43
|
+
autoload :FreezeNode, 'markdown/merge/freeze_node'
|
|
44
|
+
autoload :GapLineNode, 'markdown/merge/gap_line_node'
|
|
45
|
+
autoload :LinkDefinitionFormatter, 'markdown/merge/link_definition_formatter'
|
|
46
|
+
autoload :LinkDefinitionNode, 'markdown/merge/link_definition_node'
|
|
47
|
+
autoload :LinkParser, 'markdown/merge/link_parser'
|
|
48
|
+
autoload :LinkReferenceRehydrator, 'markdown/merge/link_reference_rehydrator'
|
|
49
|
+
autoload :ListMatchRefiner, 'markdown/merge/list_match_refiner'
|
|
50
|
+
autoload :ListMerger, 'markdown/merge/list_merger'
|
|
51
|
+
autoload :MarkdownStructure, 'markdown/merge/markdown_structure'
|
|
52
|
+
autoload :MergeResult, 'markdown/merge/merge_result'
|
|
53
|
+
autoload :NodeTypeNormalizer, 'markdown/merge/node_type_normalizer'
|
|
54
|
+
autoload :OutputBuilder, 'markdown/merge/output_builder'
|
|
55
|
+
autoload :PartialTemplateMerger, 'markdown/merge/partial_template_merger'
|
|
56
|
+
autoload :PreservationSupport, 'markdown/merge/preservation_support'
|
|
57
|
+
autoload :ProviderBackend, 'markdown/merge/source_preserving_provider'
|
|
58
|
+
autoload :SourcePreservingProvider, 'markdown/merge/source_preserving_provider'
|
|
59
|
+
autoload :SmartMerger, 'markdown/merge/smart_merger'
|
|
60
|
+
autoload :SmartMergerBase, 'markdown/merge/smart_merger_base'
|
|
61
|
+
autoload :TableMatchAlgorithm, 'markdown/merge/table_match_algorithm'
|
|
62
|
+
autoload :TableMatchRefiner, 'markdown/merge/table_match_refiner'
|
|
63
|
+
autoload :WhitespaceNormalizer, 'markdown/merge/whitespace_normalizer'
|
|
64
|
+
autoload :WrapperSupport, 'markdown/merge/wrapper_support'
|
|
65
|
+
|
|
66
|
+
# Default acyclic Markdown workflow provider backed by the language pack.
|
|
67
|
+
class Provider < SourcePreservingProvider
|
|
68
|
+
# rubocop:disable Metrics/MethodLength -- constructor declares the complete provider boundary
|
|
69
|
+
def initialize
|
|
70
|
+
super(
|
|
71
|
+
provider_id: 'ruby.markdown',
|
|
72
|
+
role: :workflow,
|
|
73
|
+
backend: ProviderBackend.new(
|
|
74
|
+
id: :'kreuzberg-language-pack',
|
|
75
|
+
package: PACKAGE_NAME,
|
|
76
|
+
dialects: %i[markdown],
|
|
77
|
+
parser: lambda { |source|
|
|
78
|
+
Markdown::Merge.register_backend!
|
|
79
|
+
TreeHaver.with_backend('kreuzberg-language-pack') { TreeHaver.parser_for(:markdown).parse(source) }
|
|
80
|
+
},
|
|
81
|
+
headings: ProviderBackend.method(:tree_sitter_headings)
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
end
|
|
85
|
+
# rubocop:enable Metrics/MethodLength
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
class << self
|
|
89
|
+
def merge_provider
|
|
90
|
+
@merge_provider ||= Provider.new
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def register_provider!(replace: false)
|
|
94
|
+
return unless Ast::Merge.respond_to?(:register_provider)
|
|
95
|
+
|
|
96
|
+
Ast::Merge.register_provider(merge_provider, replace: replace)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def register_backend!
|
|
101
|
+
BACKEND_REGISTRY.mutex.synchronize do
|
|
102
|
+
return if BACKEND_REGISTRY.registered
|
|
103
|
+
|
|
104
|
+
TreeHaver::BackendRegistry.register(BACKEND_REFERENCES.fetch('kreuzberg-language-pack'))
|
|
105
|
+
|
|
106
|
+
grammar_finder = TreeHaver::GrammarFinder.new(:markdown)
|
|
107
|
+
grammar_finder.register! if grammar_finder.available?
|
|
108
|
+
|
|
109
|
+
BACKEND_REGISTRY.registered = true
|
|
110
|
+
end
|
|
111
|
+
end
|
|
11
112
|
|
|
12
113
|
def markdown_feature_profile
|
|
13
114
|
{
|
|
14
|
-
family:
|
|
15
|
-
supported_dialects: [
|
|
115
|
+
family: 'markdown',
|
|
116
|
+
supported_dialects: ['markdown'],
|
|
16
117
|
supported_policies: []
|
|
17
118
|
}
|
|
18
119
|
end
|
|
19
120
|
|
|
20
121
|
def available_markdown_backends
|
|
21
|
-
BACKEND_REFERENCES.
|
|
122
|
+
BACKEND_REFERENCES.filter_map do |backend_id, reference|
|
|
123
|
+
reference if markdown_backend_available_for_analysis?(backend_id)
|
|
124
|
+
end
|
|
22
125
|
end
|
|
23
126
|
|
|
24
127
|
def markdown_backend_feature_profile(backend: nil)
|
|
25
128
|
resolved_backend = resolve_backend(backend)
|
|
26
|
-
|
|
129
|
+
unless BACKEND_REFERENCES.key?(resolved_backend)
|
|
130
|
+
return unsupported_feature_result("Unsupported Markdown backend #{resolved_backend}.")
|
|
131
|
+
end
|
|
27
132
|
|
|
28
133
|
markdown_feature_profile.merge(
|
|
29
134
|
backend: resolved_backend,
|
|
@@ -46,37 +151,33 @@ module Markdown
|
|
|
46
151
|
end
|
|
47
152
|
|
|
48
153
|
def parse_markdown(source, dialect, backend: nil)
|
|
49
|
-
return unsupported_feature_result("Unsupported Markdown dialect #{dialect}.") unless dialect ==
|
|
154
|
+
return unsupported_feature_result("Unsupported Markdown dialect #{dialect}.") unless dialect == 'markdown'
|
|
50
155
|
|
|
51
156
|
resolved_backend = resolve_backend(backend)
|
|
52
|
-
unless resolved_backend
|
|
157
|
+
unless BACKEND_REFERENCES.key?(resolved_backend)
|
|
53
158
|
return unsupported_feature_result("Unsupported Markdown backend #{resolved_backend}.")
|
|
54
159
|
end
|
|
55
160
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
)
|
|
59
|
-
|
|
161
|
+
register_backend!
|
|
162
|
+
parser = TreeHaver.with_backend(resolved_backend) { TreeHaver.parser_for(:markdown) }
|
|
163
|
+
tree = parser.parse(source)
|
|
164
|
+
collect_parse_errors(tree.root_node)
|
|
60
165
|
|
|
61
166
|
normalized_source = normalize_source(source)
|
|
62
167
|
{
|
|
63
168
|
ok: true,
|
|
64
169
|
diagnostics: [],
|
|
65
170
|
analysis: {
|
|
66
|
-
kind:
|
|
171
|
+
kind: 'markdown',
|
|
67
172
|
dialect: dialect,
|
|
68
173
|
normalized_source: normalized_source,
|
|
69
|
-
root_kind:
|
|
174
|
+
root_kind: 'document',
|
|
70
175
|
owners: collect_markdown_owners(normalized_source)
|
|
71
176
|
},
|
|
72
177
|
policies: []
|
|
73
178
|
}
|
|
74
|
-
rescue StandardError => e
|
|
75
|
-
|
|
76
|
-
ok: false,
|
|
77
|
-
diagnostics: [{ severity: "error", category: "parse_error", message: e.message }],
|
|
78
|
-
policies: []
|
|
79
|
-
}
|
|
179
|
+
rescue TreeHaver::Error, StandardError => e
|
|
180
|
+
parse_failure_result(e)
|
|
80
181
|
end
|
|
81
182
|
|
|
82
183
|
def match_markdown_owners(template, destination)
|
|
@@ -85,8 +186,8 @@ module Markdown
|
|
|
85
186
|
|
|
86
187
|
{
|
|
87
188
|
matched: template[:owners]
|
|
88
|
-
|
|
89
|
-
|
|
189
|
+
.filter { |owner| destination_paths[owner[:path]] }
|
|
190
|
+
.map { |owner| { template_path: owner[:path], destination_path: owner[:path] } },
|
|
90
191
|
unmatched_template: template[:owners].map { |owner| owner[:path] }.reject { |path| destination_paths[path] },
|
|
91
192
|
unmatched_destination: destination[:owners].map { |owner| owner[:path] }.reject { |path| template_paths[path] }
|
|
92
193
|
}
|
|
@@ -109,9 +210,9 @@ module Markdown
|
|
|
109
210
|
)
|
|
110
211
|
destination_paths = destination_sections.to_h { |section| [section[:path], true] }
|
|
111
212
|
merged_sections = destination_sections.map { |section| section[:text] }.reject(&:empty?) +
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
213
|
+
template_sections
|
|
214
|
+
.reject { |section| destination_paths[section[:path]] || section[:text].empty? }
|
|
215
|
+
.map { |section| section[:text] }
|
|
115
216
|
|
|
116
217
|
{
|
|
117
218
|
ok: true,
|
|
@@ -123,7 +224,7 @@ module Markdown
|
|
|
123
224
|
|
|
124
225
|
def markdown_embedded_families(analysis)
|
|
125
226
|
analysis[:owners].filter_map do |owner|
|
|
126
|
-
next unless owner[:owner_kind] ==
|
|
227
|
+
next unless owner[:owner_kind] == 'code_fence'
|
|
127
228
|
next if owner[:info_string].to_s.empty?
|
|
128
229
|
|
|
129
230
|
family = code_fence_family(owner[:info_string])
|
|
@@ -142,13 +243,13 @@ module Markdown
|
|
|
142
243
|
def markdown_discovered_surfaces(analysis)
|
|
143
244
|
markdown_embedded_families(analysis).map do |candidate|
|
|
144
245
|
Ast::Merge.discovered_surface(
|
|
145
|
-
surface_kind:
|
|
246
|
+
surface_kind: 'markdown_fenced_code_block',
|
|
146
247
|
declared_language: candidate[:language],
|
|
147
248
|
effective_language: candidate[:dialect],
|
|
148
249
|
address: "document[0] > fenced_code_block[#{candidate[:path]}]",
|
|
149
|
-
parent_address:
|
|
150
|
-
owner: Ast::Merge.surface_owner_ref(kind:
|
|
151
|
-
reconstruction_strategy:
|
|
250
|
+
parent_address: 'document[0]',
|
|
251
|
+
owner: Ast::Merge.surface_owner_ref(kind: 'structural_owner', address: candidate[:path]),
|
|
252
|
+
reconstruction_strategy: 'portable_write',
|
|
152
253
|
metadata: {
|
|
153
254
|
family: candidate[:family],
|
|
154
255
|
dialect: candidate[:dialect],
|
|
@@ -158,13 +259,13 @@ module Markdown
|
|
|
158
259
|
end
|
|
159
260
|
end
|
|
160
261
|
|
|
161
|
-
def markdown_delegated_child_operations(analysis, parent_operation_id:
|
|
262
|
+
def markdown_delegated_child_operations(analysis, parent_operation_id: 'markdown-document-0')
|
|
162
263
|
markdown_discovered_surfaces(analysis).each_with_index.map do |surface, index|
|
|
163
264
|
Ast::Merge.delegated_child_operation(
|
|
164
265
|
operation_id: "markdown-fence-#{index}",
|
|
165
266
|
parent_operation_id: parent_operation_id,
|
|
166
|
-
requested_strategy:
|
|
167
|
-
language_chain: [
|
|
267
|
+
requested_strategy: 'delegate_child_surface',
|
|
268
|
+
language_chain: ['markdown', surface[:effective_language]],
|
|
168
269
|
surface: surface
|
|
169
270
|
)
|
|
170
271
|
end
|
|
@@ -183,24 +284,27 @@ module Markdown
|
|
|
183
284
|
|
|
184
285
|
owner_path = operation.dig(:surface, :owner, :address)
|
|
185
286
|
range = ranges[owner_path]
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
287
|
+
if range.nil?
|
|
288
|
+
return {
|
|
289
|
+
ok: false,
|
|
290
|
+
diagnostics: [{ severity: 'error', category: 'configuration_error',
|
|
291
|
+
message: "missing fenced-code range for #{owner_path}" }],
|
|
292
|
+
policies: []
|
|
293
|
+
}
|
|
294
|
+
end
|
|
191
295
|
|
|
192
296
|
{ range: range, output: output }
|
|
193
297
|
end
|
|
194
298
|
|
|
195
299
|
replacements.sort_by { |entry| -entry[:range][:start] }.each do |entry|
|
|
196
|
-
body_lines = entry[:output].empty? ? [] : entry[:output].sub(/\n\z/,
|
|
300
|
+
body_lines = entry[:output].empty? ? [] : entry[:output].sub(/\n\z/, '').split("\n")
|
|
197
301
|
lines[entry[:range][:start] + 1...entry[:range][:end]] = body_lines
|
|
198
302
|
end
|
|
199
303
|
|
|
200
304
|
{
|
|
201
305
|
ok: true,
|
|
202
306
|
diagnostics: [],
|
|
203
|
-
output: "#{lines.join("\n").sub(/\n+\z/,
|
|
307
|
+
output: "#{lines.join("\n").sub(/\n+\z/, '')}\n",
|
|
204
308
|
policies: []
|
|
205
309
|
}
|
|
206
310
|
end
|
|
@@ -208,8 +312,8 @@ module Markdown
|
|
|
208
312
|
def merge_markdown_with_nested_outputs(template_source, destination_source, dialect, nested_outputs, backend: nil)
|
|
209
313
|
Ast::Merge.execute_nested_merge(
|
|
210
314
|
nested_outputs,
|
|
211
|
-
default_family:
|
|
212
|
-
request_id_prefix:
|
|
315
|
+
default_family: 'markdown',
|
|
316
|
+
request_id_prefix: 'nested_markdown_child',
|
|
213
317
|
merge_parent: -> { merge_markdown(template_source, destination_source, dialect, backend: backend) },
|
|
214
318
|
discover_operations: lambda { |merged_output|
|
|
215
319
|
analysis = parse_markdown(merged_output, dialect, backend: backend)
|
|
@@ -232,10 +336,11 @@ module Markdown
|
|
|
232
336
|
)
|
|
233
337
|
end
|
|
234
338
|
|
|
235
|
-
def merge_markdown_with_reviewed_nested_outputs(template_source, destination_source, dialect, review_state,
|
|
339
|
+
def merge_markdown_with_reviewed_nested_outputs(template_source, destination_source, dialect, review_state,
|
|
340
|
+
applied_children, backend: nil)
|
|
236
341
|
Ast::Merge.execute_reviewed_nested_merge(
|
|
237
342
|
review_state,
|
|
238
|
-
|
|
343
|
+
'markdown',
|
|
239
344
|
applied_children,
|
|
240
345
|
merge_parent: -> { merge_markdown(template_source, destination_source, dialect, backend: backend) },
|
|
241
346
|
discover_operations: lambda { |merged_output|
|
|
@@ -259,9 +364,13 @@ module Markdown
|
|
|
259
364
|
)
|
|
260
365
|
end
|
|
261
366
|
|
|
262
|
-
def merge_markdown_with_reviewed_nested_outputs_from_replay_bundle(template_source, destination_source, dialect,
|
|
263
|
-
|
|
264
|
-
|
|
367
|
+
def merge_markdown_with_reviewed_nested_outputs_from_replay_bundle(template_source, destination_source, dialect,
|
|
368
|
+
replay_bundle, backend: nil)
|
|
369
|
+
execution = Array(replay_bundle[:reviewed_nested_executions]).find { |entry| entry[:family] == 'markdown' }
|
|
370
|
+
unless execution
|
|
371
|
+
return { ok: false,
|
|
372
|
+
diagnostics: [{ severity: 'error', category: 'configuration_error', message: 'review replay bundle does not include a reviewed nested execution for markdown.' }], policies: [] }
|
|
373
|
+
end
|
|
265
374
|
|
|
266
375
|
merge_markdown_with_reviewed_nested_outputs(
|
|
267
376
|
template_source,
|
|
@@ -273,9 +382,13 @@ module Markdown
|
|
|
273
382
|
)
|
|
274
383
|
end
|
|
275
384
|
|
|
276
|
-
def merge_markdown_with_reviewed_nested_outputs_from_review_state(template_source, destination_source, dialect,
|
|
277
|
-
|
|
278
|
-
|
|
385
|
+
def merge_markdown_with_reviewed_nested_outputs_from_review_state(template_source, destination_source, dialect,
|
|
386
|
+
review_state, backend: nil)
|
|
387
|
+
execution = Array(review_state[:reviewed_nested_executions]).find { |entry| entry[:family] == 'markdown' }
|
|
388
|
+
unless execution
|
|
389
|
+
return { ok: false,
|
|
390
|
+
diagnostics: [{ severity: 'error', category: 'configuration_error', message: 'review state does not include a reviewed nested execution for markdown.' }], policies: [] }
|
|
391
|
+
end
|
|
279
392
|
|
|
280
393
|
merge_markdown_with_reviewed_nested_outputs(
|
|
281
394
|
template_source,
|
|
@@ -287,9 +400,13 @@ module Markdown
|
|
|
287
400
|
)
|
|
288
401
|
end
|
|
289
402
|
|
|
290
|
-
def merge_markdown_with_reviewed_nested_outputs_from_replay_bundle_envelope(template_source, destination_source,
|
|
403
|
+
def merge_markdown_with_reviewed_nested_outputs_from_replay_bundle_envelope(template_source, destination_source,
|
|
404
|
+
dialect, envelope, backend: nil)
|
|
291
405
|
replay_bundle, import_error = Ast::Merge.import_review_replay_bundle_envelope(envelope)
|
|
292
|
-
|
|
406
|
+
if import_error
|
|
407
|
+
return { ok: false,
|
|
408
|
+
diagnostics: [{ severity: 'error', category: import_error[:category], message: import_error[:message] }], policies: [] }
|
|
409
|
+
end
|
|
293
410
|
|
|
294
411
|
merge_markdown_with_reviewed_nested_outputs_from_replay_bundle(
|
|
295
412
|
template_source,
|
|
@@ -300,9 +417,13 @@ module Markdown
|
|
|
300
417
|
)
|
|
301
418
|
end
|
|
302
419
|
|
|
303
|
-
def merge_markdown_with_reviewed_nested_outputs_from_review_state_envelope(template_source, destination_source,
|
|
420
|
+
def merge_markdown_with_reviewed_nested_outputs_from_review_state_envelope(template_source, destination_source,
|
|
421
|
+
dialect, envelope, backend: nil)
|
|
304
422
|
review_state, import_error = Ast::Merge.import_conformance_manifest_review_state_envelope(envelope)
|
|
305
|
-
|
|
423
|
+
if import_error
|
|
424
|
+
return { ok: false,
|
|
425
|
+
diagnostics: [{ severity: 'error', category: import_error[:category], message: import_error[:message] }], policies: [] }
|
|
426
|
+
end
|
|
306
427
|
|
|
307
428
|
merge_markdown_with_reviewed_nested_outputs_from_review_state(
|
|
308
429
|
template_source,
|
|
@@ -319,12 +440,12 @@ module Markdown
|
|
|
319
440
|
|
|
320
441
|
def slugify(value)
|
|
321
442
|
slug = value
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
slug.empty? ?
|
|
443
|
+
.strip
|
|
444
|
+
.downcase
|
|
445
|
+
.gsub(/[`*_~\[\]()<>]/, '')
|
|
446
|
+
.gsub(/[^a-z0-9]+/, '-')
|
|
447
|
+
.gsub(/\A-+|-+\z/, '')
|
|
448
|
+
slug.empty? ? 'section' : slug
|
|
328
449
|
end
|
|
329
450
|
|
|
330
451
|
def collect_markdown_owners(source)
|
|
@@ -340,7 +461,7 @@ module Markdown
|
|
|
340
461
|
level = heading[1].length
|
|
341
462
|
owners << {
|
|
342
463
|
path: "/heading/#{heading_index}",
|
|
343
|
-
owner_kind:
|
|
464
|
+
owner_kind: 'heading',
|
|
344
465
|
match_key: "h#{level}:#{slugify(heading[2])}",
|
|
345
466
|
level: level
|
|
346
467
|
}
|
|
@@ -356,8 +477,8 @@ module Markdown
|
|
|
356
477
|
info_string = fence[2].strip.split(/\s+/).first.to_s
|
|
357
478
|
owners << {
|
|
358
479
|
path: "/code_fence/#{code_fence_index}",
|
|
359
|
-
owner_kind:
|
|
360
|
-
match_key: "fence:#{info_string.empty? ?
|
|
480
|
+
owner_kind: 'code_fence',
|
|
481
|
+
match_key: "fence:#{info_string.empty? ? 'plain' : info_string}",
|
|
361
482
|
**(info_string.empty? ? {} : { info_string: info_string })
|
|
362
483
|
}
|
|
363
484
|
code_fence_index += 1
|
|
@@ -366,8 +487,8 @@ module Markdown
|
|
|
366
487
|
while index < lines.length
|
|
367
488
|
trimmed = lines[index].strip
|
|
368
489
|
break if trimmed.length >= marker_length &&
|
|
369
|
-
|
|
370
|
-
|
|
490
|
+
trimmed.start_with?(marker_char * marker_length) &&
|
|
491
|
+
trimmed.delete(marker_char).empty?
|
|
371
492
|
|
|
372
493
|
index += 1
|
|
373
494
|
end
|
|
@@ -407,8 +528,8 @@ module Markdown
|
|
|
407
528
|
while index < lines.length
|
|
408
529
|
trimmed = lines[index].strip
|
|
409
530
|
break if trimmed.length >= marker_length &&
|
|
410
|
-
|
|
411
|
-
|
|
531
|
+
trimmed.start_with?(marker_char * marker_length) &&
|
|
532
|
+
trimmed.delete(marker_char).empty?
|
|
412
533
|
|
|
413
534
|
index += 1
|
|
414
535
|
end
|
|
@@ -458,8 +579,8 @@ module Markdown
|
|
|
458
579
|
while cursor < lines.length
|
|
459
580
|
trimmed = lines[cursor].strip
|
|
460
581
|
if trimmed.length >= marker_length &&
|
|
461
|
-
|
|
462
|
-
|
|
582
|
+
trimmed.start_with?(marker_char * marker_length) &&
|
|
583
|
+
trimmed.delete(marker_char).empty?
|
|
463
584
|
closing_index = cursor
|
|
464
585
|
break
|
|
465
586
|
end
|
|
@@ -481,43 +602,81 @@ module Markdown
|
|
|
481
602
|
|
|
482
603
|
def code_fence_family(info_string)
|
|
483
604
|
case info_string.to_s.downcase
|
|
484
|
-
when
|
|
485
|
-
|
|
486
|
-
when
|
|
487
|
-
|
|
488
|
-
when
|
|
489
|
-
|
|
490
|
-
when
|
|
491
|
-
|
|
492
|
-
when
|
|
493
|
-
|
|
494
|
-
when
|
|
495
|
-
|
|
605
|
+
when 'ts', 'typescript'
|
|
606
|
+
'typescript'
|
|
607
|
+
when 'rust', 'rs'
|
|
608
|
+
'rust'
|
|
609
|
+
when 'go'
|
|
610
|
+
'go'
|
|
611
|
+
when 'json', 'jsonc', 'json5'
|
|
612
|
+
'json'
|
|
613
|
+
when 'yaml', 'yml'
|
|
614
|
+
'yaml'
|
|
615
|
+
when 'toml'
|
|
616
|
+
'toml'
|
|
496
617
|
end
|
|
497
618
|
end
|
|
498
619
|
|
|
499
620
|
def code_fence_dialect(info_string, family)
|
|
500
621
|
case family
|
|
501
|
-
when
|
|
622
|
+
when 'typescript', 'rust', 'go', 'yaml', 'toml'
|
|
502
623
|
family
|
|
503
|
-
when
|
|
504
|
-
info_string.to_s.downcase
|
|
624
|
+
when 'json'
|
|
625
|
+
%w[json jsonc json5].include?(info_string.to_s.downcase) ? info_string.to_s.downcase : 'json'
|
|
505
626
|
end
|
|
506
627
|
end
|
|
507
628
|
|
|
508
629
|
def resolve_backend(backend)
|
|
509
|
-
backend.to_s
|
|
630
|
+
return backend.to_s unless backend.to_s.empty?
|
|
631
|
+
|
|
632
|
+
current = TreeHaver.current_backend_id
|
|
633
|
+
return current if BACKEND_REFERENCES.key?(current.to_s) && markdown_backend_available_for_analysis?(current)
|
|
634
|
+
|
|
635
|
+
BACKEND_REFERENCES.keys.find { |backend_id| markdown_backend_available_for_analysis?(backend_id) } ||
|
|
636
|
+
'kreuzberg-language-pack'
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
def markdown_backend_available_for_analysis?(backend_id)
|
|
640
|
+
register_backend!
|
|
641
|
+
registrations = TreeHaver.registered_languages(:markdown)
|
|
642
|
+
case backend_id.to_s
|
|
643
|
+
when 'commonmarker', 'markly', 'kramdown'
|
|
644
|
+
registrations.dig(backend_id.to_sym, :backend_module)&.then do |backend_module|
|
|
645
|
+
!backend_module.respond_to?(:available?) || backend_module.available?
|
|
646
|
+
end
|
|
647
|
+
when 'kreuzberg-language-pack'
|
|
648
|
+
registrations.key?(:tree_sitter) || registrations.key?(:tslp)
|
|
649
|
+
else
|
|
650
|
+
false
|
|
651
|
+
end
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
def collect_parse_errors(node)
|
|
655
|
+
raise TreeHaver::NotAvailable, 'Markdown parse returned no root node' unless node
|
|
656
|
+
return unless node.respond_to?(:has_error?) && node.has_error?
|
|
657
|
+
|
|
658
|
+
raise TreeHaver::NotAvailable,
|
|
659
|
+
'Markdown parse contains syntax errors'
|
|
660
|
+
end
|
|
661
|
+
|
|
662
|
+
def parse_failure_result(error)
|
|
663
|
+
{
|
|
664
|
+
ok: false,
|
|
665
|
+
diagnostics: [{ severity: 'error', category: 'parse_error', message: error.message }],
|
|
666
|
+
policies: []
|
|
667
|
+
}
|
|
510
668
|
end
|
|
511
669
|
|
|
512
670
|
def unsupported_feature_result(message)
|
|
513
671
|
{
|
|
514
672
|
ok: false,
|
|
515
|
-
diagnostics: [{ severity:
|
|
673
|
+
diagnostics: [{ severity: 'error', category: 'unsupported_feature', message: message }],
|
|
516
674
|
policies: []
|
|
517
675
|
}
|
|
518
676
|
end
|
|
519
677
|
|
|
520
678
|
module_function(
|
|
679
|
+
:register_backend!,
|
|
521
680
|
:markdown_feature_profile,
|
|
522
681
|
:available_markdown_backends,
|
|
523
682
|
:markdown_backend_feature_profile,
|
|
@@ -544,7 +703,32 @@ module Markdown
|
|
|
544
703
|
:code_fence_family,
|
|
545
704
|
:code_fence_dialect,
|
|
546
705
|
:resolve_backend,
|
|
706
|
+
:markdown_backend_available_for_analysis?,
|
|
707
|
+
:collect_parse_errors,
|
|
708
|
+
:parse_failure_result,
|
|
547
709
|
:unsupported_feature_result
|
|
548
710
|
)
|
|
549
711
|
end
|
|
550
712
|
end
|
|
713
|
+
|
|
714
|
+
Markdown::Merge.register_provider!
|
|
715
|
+
|
|
716
|
+
%w[
|
|
717
|
+
commonmarker/merge/backend
|
|
718
|
+
markly/merge/backend
|
|
719
|
+
].each do |feature|
|
|
720
|
+
require feature
|
|
721
|
+
rescue LoadError
|
|
722
|
+
nil
|
|
723
|
+
end
|
|
724
|
+
|
|
725
|
+
if defined?(Ast::Merge::RSpec::MergeGemRegistry)
|
|
726
|
+
Ast::Merge::RSpec::MergeGemRegistry.register(
|
|
727
|
+
:markdown_merge,
|
|
728
|
+
require_path: 'markdown/merge',
|
|
729
|
+
merger_class: 'Markdown::Merge::SmartMerger',
|
|
730
|
+
test_source: "# Test\n\nParagraph",
|
|
731
|
+
category: :markdown,
|
|
732
|
+
skip_instantiation: true
|
|
733
|
+
)
|
|
734
|
+
end
|
data/lib/markdown-merge.rb
CHANGED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module Markdown
|
|
2
|
+
module Merge
|
|
3
|
+
module Version
|
|
4
|
+
VERSION: String
|
|
5
|
+
end
|
|
6
|
+
VERSION: String
|
|
7
|
+
|
|
8
|
+
class ProviderBackend
|
|
9
|
+
attr_reader id: Symbol
|
|
10
|
+
attr_reader package: String
|
|
11
|
+
attr_reader dialects: Array[Symbol]
|
|
12
|
+
|
|
13
|
+
def initialize: (
|
|
14
|
+
id: Symbol | String,
|
|
15
|
+
package: String,
|
|
16
|
+
dialects: Array[Symbol | String],
|
|
17
|
+
parser: Proc,
|
|
18
|
+
headings: Proc
|
|
19
|
+
) -> void
|
|
20
|
+
def parse: (String source) -> [untyped, Array[ProviderBackend::Heading]]
|
|
21
|
+
|
|
22
|
+
def self.native_headings: (untyped root, String source, ?kramdown: bool) -> Array[ProviderBackend::Heading]
|
|
23
|
+
def self.tree_sitter_headings: (untyped root, String source) -> Array[ProviderBackend::Heading]
|
|
24
|
+
|
|
25
|
+
class Heading
|
|
26
|
+
attr_reader level: Integer
|
|
27
|
+
attr_reader text: String
|
|
28
|
+
attr_reader start_line: Integer
|
|
29
|
+
attr_reader end_line: Integer
|
|
30
|
+
attr_reader style: Symbol
|
|
31
|
+
|
|
32
|
+
def initialize: (
|
|
33
|
+
level: Integer,
|
|
34
|
+
text: String,
|
|
35
|
+
start_line: Integer,
|
|
36
|
+
end_line: Integer,
|
|
37
|
+
style: Symbol
|
|
38
|
+
) -> void
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class SourcePreservingProvider
|
|
43
|
+
attr_reader backend: ProviderBackend
|
|
44
|
+
|
|
45
|
+
def initialize: (provider_id: String, role: Symbol, backend: ProviderBackend) -> void
|
|
46
|
+
def provider_id: () -> String
|
|
47
|
+
def family: () -> String
|
|
48
|
+
def capabilities: () -> Hash[Symbol, untyped]
|
|
49
|
+
def analyze: (Hash[Symbol, untyped] request) -> Hash[Symbol, untyped]
|
|
50
|
+
def diff2: (Hash[Symbol, untyped] request) -> Hash[Symbol, untyped]
|
|
51
|
+
def merge2: (Hash[Symbol, untyped] request) -> Hash[Symbol, untyped]
|
|
52
|
+
def merge3: (Hash[Symbol, untyped] request) -> Hash[Symbol, untyped]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class Provider < SourcePreservingProvider
|
|
56
|
+
def initialize: () -> void
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.merge_provider: () -> Provider
|
|
60
|
+
def self.register_provider!: (?replace: bool) -> untyped
|
|
61
|
+
end
|
|
62
|
+
end
|
data.tar.gz.sig
CHANGED
|
Binary file
|