ast-merge 1.0.0 → 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 +194 -1
- data/README.md +235 -53
- data/exe/ast-merge-recipe +366 -0
- data/lib/ast/merge/ast_node.rb +224 -24
- data/lib/ast/merge/comment/block.rb +6 -0
- data/lib/ast/merge/comment/empty.rb +6 -0
- data/lib/ast/merge/comment/line.rb +6 -0
- data/lib/ast/merge/comment/parser.rb +9 -7
- data/lib/ast/merge/conflict_resolver_base.rb +8 -1
- data/lib/ast/merge/content_match_refiner.rb +278 -0
- data/lib/ast/merge/debug_logger.rb +6 -1
- data/lib/ast/merge/detector/base.rb +193 -0
- data/lib/ast/merge/detector/fenced_code_block.rb +227 -0
- data/lib/ast/merge/detector/mergeable.rb +369 -0
- data/lib/ast/merge/detector/toml_frontmatter.rb +82 -0
- data/lib/ast/merge/detector/yaml_frontmatter.rb +82 -0
- data/lib/ast/merge/file_analyzable.rb +5 -3
- data/lib/ast/merge/freeze_node_base.rb +1 -1
- data/lib/ast/merge/match_refiner_base.rb +1 -1
- data/lib/ast/merge/match_score_base.rb +1 -1
- data/lib/ast/merge/merge_result_base.rb +4 -1
- data/lib/ast/merge/merger_config.rb +33 -31
- data/lib/ast/merge/navigable_statement.rb +630 -0
- data/lib/ast/merge/partial_template_merger.rb +432 -0
- data/lib/ast/merge/recipe/config.rb +198 -0
- data/lib/ast/merge/recipe/preset.rb +171 -0
- data/lib/ast/merge/recipe/runner.rb +254 -0
- data/lib/ast/merge/recipe/script_loader.rb +181 -0
- data/lib/ast/merge/recipe.rb +26 -0
- data/lib/ast/merge/rspec/dependency_tags.rb +252 -0
- data/lib/ast/merge/rspec/shared_examples/reproducible_merge.rb +3 -2
- data/lib/ast/merge/rspec.rb +33 -2
- data/lib/ast/merge/section_typing.rb +52 -50
- data/lib/ast/merge/smart_merger_base.rb +86 -3
- data/lib/ast/merge/text/line_node.rb +42 -9
- data/lib/ast/merge/text/section_splitter.rb +12 -10
- data/lib/ast/merge/text/word_node.rb +47 -14
- data/lib/ast/merge/version.rb +1 -1
- data/lib/ast/merge.rb +10 -6
- data/sig/ast/merge.rbs +389 -2
- data.tar.gz.sig +0 -0
- metadata +76 -12
- metadata.gz.sig +0 -0
- data/lib/ast/merge/fenced_code_block_detector.rb +0 -211
- data/lib/ast/merge/region.rb +0 -124
- data/lib/ast/merge/region_detector_base.rb +0 -114
- data/lib/ast/merge/region_mergeable.rb +0 -364
- data/lib/ast/merge/toml_frontmatter_detector.rb +0 -88
- data/lib/ast/merge/yaml_frontmatter_detector.rb +0 -108
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ast-merge
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
@@ -57,6 +57,26 @@ dependencies:
|
|
|
57
57
|
- - ">="
|
|
58
58
|
- !ruby/object:Gem::Version
|
|
59
59
|
version: 1.1.9
|
|
60
|
+
- !ruby/object:Gem::Dependency
|
|
61
|
+
name: tree_haver
|
|
62
|
+
requirement: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - "~>"
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '3.1'
|
|
67
|
+
- - ">="
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: 3.1.1
|
|
70
|
+
type: :runtime
|
|
71
|
+
prerelease: false
|
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
73
|
+
requirements:
|
|
74
|
+
- - "~>"
|
|
75
|
+
- !ruby/object:Gem::Version
|
|
76
|
+
version: '3.1'
|
|
77
|
+
- - ">="
|
|
78
|
+
- !ruby/object:Gem::Version
|
|
79
|
+
version: 3.1.1
|
|
60
80
|
- !ruby/object:Gem::Dependency
|
|
61
81
|
name: kettle-dev
|
|
62
82
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -133,6 +153,40 @@ dependencies:
|
|
|
133
153
|
- - "~>"
|
|
134
154
|
- !ruby/object:Gem::Version
|
|
135
155
|
version: '3.0'
|
|
156
|
+
- !ruby/object:Gem::Dependency
|
|
157
|
+
name: benchmark
|
|
158
|
+
requirement: !ruby/object:Gem::Requirement
|
|
159
|
+
requirements:
|
|
160
|
+
- - "~>"
|
|
161
|
+
- !ruby/object:Gem::Version
|
|
162
|
+
version: '0.5'
|
|
163
|
+
type: :development
|
|
164
|
+
prerelease: false
|
|
165
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
166
|
+
requirements:
|
|
167
|
+
- - "~>"
|
|
168
|
+
- !ruby/object:Gem::Version
|
|
169
|
+
version: '0.5'
|
|
170
|
+
- !ruby/object:Gem::Dependency
|
|
171
|
+
name: kettle-soup-cover
|
|
172
|
+
requirement: !ruby/object:Gem::Requirement
|
|
173
|
+
requirements:
|
|
174
|
+
- - "~>"
|
|
175
|
+
- !ruby/object:Gem::Version
|
|
176
|
+
version: '1.1'
|
|
177
|
+
- - ">="
|
|
178
|
+
- !ruby/object:Gem::Version
|
|
179
|
+
version: 1.1.1
|
|
180
|
+
type: :development
|
|
181
|
+
prerelease: false
|
|
182
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
183
|
+
requirements:
|
|
184
|
+
- - "~>"
|
|
185
|
+
- !ruby/object:Gem::Version
|
|
186
|
+
version: '1.1'
|
|
187
|
+
- - ">="
|
|
188
|
+
- !ruby/object:Gem::Version
|
|
189
|
+
version: 1.1.1
|
|
136
190
|
- !ruby/object:Gem::Dependency
|
|
137
191
|
name: kettle-test
|
|
138
192
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -212,7 +266,8 @@ description: "☯️ Ast::Merge provides base classes, modules, and RSpec shared
|
|
|
212
266
|
psych-merge, json-merge, and other format-specific merge gems."
|
|
213
267
|
email:
|
|
214
268
|
- floss@galtzo.com
|
|
215
|
-
executables:
|
|
269
|
+
executables:
|
|
270
|
+
- ast-merge-recipe
|
|
216
271
|
extensions: []
|
|
217
272
|
extra_rdoc_files:
|
|
218
273
|
- CHANGELOG.md
|
|
@@ -236,6 +291,7 @@ files:
|
|
|
236
291
|
- REEK
|
|
237
292
|
- RUBOCOP.md
|
|
238
293
|
- SECURITY.md
|
|
294
|
+
- exe/ast-merge-recipe
|
|
239
295
|
- lib/ast-merge.rb
|
|
240
296
|
- lib/ast/merge.rb
|
|
241
297
|
- lib/ast/merge/ast_node.rb
|
|
@@ -246,8 +302,13 @@ files:
|
|
|
246
302
|
- lib/ast/merge/comment/parser.rb
|
|
247
303
|
- lib/ast/merge/comment/style.rb
|
|
248
304
|
- lib/ast/merge/conflict_resolver_base.rb
|
|
305
|
+
- lib/ast/merge/content_match_refiner.rb
|
|
249
306
|
- lib/ast/merge/debug_logger.rb
|
|
250
|
-
- lib/ast/merge/
|
|
307
|
+
- lib/ast/merge/detector/base.rb
|
|
308
|
+
- lib/ast/merge/detector/fenced_code_block.rb
|
|
309
|
+
- lib/ast/merge/detector/mergeable.rb
|
|
310
|
+
- lib/ast/merge/detector/toml_frontmatter.rb
|
|
311
|
+
- lib/ast/merge/detector/yaml_frontmatter.rb
|
|
251
312
|
- lib/ast/merge/file_analyzable.rb
|
|
252
313
|
- lib/ast/merge/freezable.rb
|
|
253
314
|
- lib/ast/merge/freeze_node_base.rb
|
|
@@ -255,11 +316,16 @@ files:
|
|
|
255
316
|
- lib/ast/merge/match_score_base.rb
|
|
256
317
|
- lib/ast/merge/merge_result_base.rb
|
|
257
318
|
- lib/ast/merge/merger_config.rb
|
|
319
|
+
- lib/ast/merge/navigable_statement.rb
|
|
258
320
|
- lib/ast/merge/node_typing.rb
|
|
259
|
-
- lib/ast/merge/
|
|
260
|
-
- lib/ast/merge/
|
|
261
|
-
- lib/ast/merge/
|
|
321
|
+
- lib/ast/merge/partial_template_merger.rb
|
|
322
|
+
- lib/ast/merge/recipe.rb
|
|
323
|
+
- lib/ast/merge/recipe/config.rb
|
|
324
|
+
- lib/ast/merge/recipe/preset.rb
|
|
325
|
+
- lib/ast/merge/recipe/runner.rb
|
|
326
|
+
- lib/ast/merge/recipe/script_loader.rb
|
|
262
327
|
- lib/ast/merge/rspec.rb
|
|
328
|
+
- lib/ast/merge/rspec/dependency_tags.rb
|
|
263
329
|
- lib/ast/merge/rspec/shared_examples.rb
|
|
264
330
|
- lib/ast/merge/rspec/shared_examples/conflict_resolver_base.rb
|
|
265
331
|
- lib/ast/merge/rspec/shared_examples/debug_logger.rb
|
|
@@ -279,19 +345,17 @@ files:
|
|
|
279
345
|
- lib/ast/merge/text/section_splitter.rb
|
|
280
346
|
- lib/ast/merge/text/smart_merger.rb
|
|
281
347
|
- lib/ast/merge/text/word_node.rb
|
|
282
|
-
- lib/ast/merge/toml_frontmatter_detector.rb
|
|
283
348
|
- lib/ast/merge/version.rb
|
|
284
|
-
- lib/ast/merge/yaml_frontmatter_detector.rb
|
|
285
349
|
- sig/ast/merge.rbs
|
|
286
350
|
homepage: https://github.com/kettle-rb/ast-merge
|
|
287
351
|
licenses:
|
|
288
352
|
- MIT
|
|
289
353
|
metadata:
|
|
290
354
|
homepage_uri: https://ast-merge.galtzo.com/
|
|
291
|
-
source_code_uri: https://github.com/kettle-rb/ast-merge/tree/
|
|
292
|
-
changelog_uri: https://github.com/kettle-rb/ast-merge/blob/
|
|
355
|
+
source_code_uri: https://github.com/kettle-rb/ast-merge/tree/v2.0.0
|
|
356
|
+
changelog_uri: https://github.com/kettle-rb/ast-merge/blob/v2.0.0/CHANGELOG.md
|
|
293
357
|
bug_tracker_uri: https://github.com/kettle-rb/ast-merge/issues
|
|
294
|
-
documentation_uri: https://www.rubydoc.info/gems/ast-merge/
|
|
358
|
+
documentation_uri: https://www.rubydoc.info/gems/ast-merge/2.0.0
|
|
295
359
|
funding_uri: https://github.com/sponsors/pboling
|
|
296
360
|
wiki_uri: https://github.com/kettle-rb/ast-merge/wiki
|
|
297
361
|
news_uri: https://www.railsbling.com/tags/ast-merge
|
|
@@ -320,7 +384,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
320
384
|
- !ruby/object:Gem::Version
|
|
321
385
|
version: '0'
|
|
322
386
|
requirements: []
|
|
323
|
-
rubygems_version: 4.0.
|
|
387
|
+
rubygems_version: 4.0.3
|
|
324
388
|
specification_version: 4
|
|
325
389
|
summary: "☯️ Shared infrastructure for the *-merge gem family"
|
|
326
390
|
test_files: []
|
metadata.gz.sig
CHANGED
|
Binary file
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Ast
|
|
4
|
-
module Merge
|
|
5
|
-
# Detects fenced code blocks with a specific language identifier.
|
|
6
|
-
#
|
|
7
|
-
# This detector finds Markdown-style fenced code blocks (using ``` or ~~~)
|
|
8
|
-
# that have a specific language identifier. It can be configured for any
|
|
9
|
-
# language: ruby, json, yaml, mermaid, etc.
|
|
10
|
-
#
|
|
11
|
-
# @example Detecting Ruby code blocks
|
|
12
|
-
# detector = FencedCodeBlockDetector.new("ruby", aliases: ["rb"])
|
|
13
|
-
# regions = detector.detect_all(markdown_source)
|
|
14
|
-
#
|
|
15
|
-
# @example Using factory methods
|
|
16
|
-
# detector = FencedCodeBlockDetector.ruby
|
|
17
|
-
# detector = FencedCodeBlockDetector.yaml
|
|
18
|
-
# detector = FencedCodeBlockDetector.json
|
|
19
|
-
#
|
|
20
|
-
# @api public
|
|
21
|
-
class FencedCodeBlockDetector < RegionDetectorBase
|
|
22
|
-
# @return [String] The primary language identifier
|
|
23
|
-
attr_reader :language
|
|
24
|
-
|
|
25
|
-
# @return [Array<String>] Alternative language identifiers
|
|
26
|
-
attr_reader :aliases
|
|
27
|
-
|
|
28
|
-
# Creates a new detector for the specified language.
|
|
29
|
-
#
|
|
30
|
-
# @param language [String, Symbol] The language identifier (e.g., "ruby", "json")
|
|
31
|
-
# @param aliases [Array<String, Symbol>] Alternative identifiers (e.g., ["rb"] for ruby)
|
|
32
|
-
def initialize(language, aliases: [])
|
|
33
|
-
super()
|
|
34
|
-
@language = language.to_s.downcase
|
|
35
|
-
@aliases = aliases.map { |a| a.to_s.downcase }
|
|
36
|
-
@all_identifiers = [@language] + @aliases
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# @return [Symbol] The region type (e.g., :ruby_code_block)
|
|
40
|
-
def region_type
|
|
41
|
-
:"#{@language}_code_block"
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# Check if a language identifier matches this detector.
|
|
45
|
-
#
|
|
46
|
-
# @param lang [String] The language identifier to check
|
|
47
|
-
# @return [Boolean] true if the language matches
|
|
48
|
-
def matches_language?(lang)
|
|
49
|
-
@all_identifiers.include?(lang.to_s.downcase)
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# Detects all fenced code blocks with the configured language.
|
|
53
|
-
#
|
|
54
|
-
# @param source [String] The full document content
|
|
55
|
-
# @return [Array<Region>] All detected code blocks, sorted by start_line
|
|
56
|
-
def detect_all(source)
|
|
57
|
-
return [] if source.nil? || source.empty?
|
|
58
|
-
|
|
59
|
-
regions = []
|
|
60
|
-
lines = source.lines
|
|
61
|
-
in_block = false
|
|
62
|
-
start_line = nil
|
|
63
|
-
content_lines = []
|
|
64
|
-
current_language = nil
|
|
65
|
-
fence_char = nil
|
|
66
|
-
fence_length = nil
|
|
67
|
-
indent = ""
|
|
68
|
-
|
|
69
|
-
lines.each_with_index do |line, idx|
|
|
70
|
-
line_num = idx + 1
|
|
71
|
-
|
|
72
|
-
if !in_block
|
|
73
|
-
# Match opening fence: ```lang or ~~~lang (optionally indented)
|
|
74
|
-
match = line.match(/^(\s*)(`{3,}|~{3,})(\w*)\s*$/)
|
|
75
|
-
if match
|
|
76
|
-
indent = match[1] || ""
|
|
77
|
-
fence = match[2]
|
|
78
|
-
lang = match[3].downcase
|
|
79
|
-
|
|
80
|
-
if @all_identifiers.include?(lang)
|
|
81
|
-
in_block = true
|
|
82
|
-
start_line = line_num
|
|
83
|
-
content_lines = []
|
|
84
|
-
current_language = lang
|
|
85
|
-
fence_char = fence[0]
|
|
86
|
-
fence_length = fence.length
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
elsif line.match?(/^#{Regexp.escape(indent)}#{Regexp.escape(fence_char)}{#{fence_length},}\s*$/)
|
|
90
|
-
# Match closing fence (must use same char, same indent, and at least same length)
|
|
91
|
-
opening_fence = "#{fence_char * fence_length}#{current_language}"
|
|
92
|
-
closing_fence = fence_char * fence_length
|
|
93
|
-
|
|
94
|
-
regions << build_region(
|
|
95
|
-
type: region_type,
|
|
96
|
-
content: content_lines.join,
|
|
97
|
-
start_line: start_line,
|
|
98
|
-
end_line: line_num,
|
|
99
|
-
delimiters: [opening_fence, closing_fence],
|
|
100
|
-
metadata: {language: current_language, indent: indent.empty? ? nil : indent},
|
|
101
|
-
)
|
|
102
|
-
in_block = false
|
|
103
|
-
start_line = nil
|
|
104
|
-
content_lines = []
|
|
105
|
-
current_language = nil
|
|
106
|
-
fence_char = nil
|
|
107
|
-
fence_length = nil
|
|
108
|
-
indent = ""
|
|
109
|
-
else
|
|
110
|
-
# Accumulate content lines (strip the indent if present)
|
|
111
|
-
content_lines << if indent.empty?
|
|
112
|
-
line
|
|
113
|
-
else
|
|
114
|
-
# Strip the common indent from content lines
|
|
115
|
-
line.sub(/^#{Regexp.escape(indent)}/, "")
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
# Note: Unclosed blocks are ignored (no region created)
|
|
121
|
-
regions
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
# @return [String] A description of this detector
|
|
125
|
-
def inspect
|
|
126
|
-
aliases_str = @aliases.empty? ? "" : " aliases=#{@aliases.inspect}"
|
|
127
|
-
"#<#{self.class.name} language=#{@language}#{aliases_str}>"
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
class << self
|
|
131
|
-
# Creates a detector for Ruby code blocks.
|
|
132
|
-
# @return [FencedCodeBlockDetector]
|
|
133
|
-
def ruby
|
|
134
|
-
new("ruby", aliases: ["rb"])
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
# Creates a detector for JSON code blocks.
|
|
138
|
-
# @return [FencedCodeBlockDetector]
|
|
139
|
-
def json
|
|
140
|
-
new("json")
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
# Creates a detector for YAML code blocks.
|
|
144
|
-
# @return [FencedCodeBlockDetector]
|
|
145
|
-
def yaml
|
|
146
|
-
new("yaml", aliases: ["yml"])
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
# Creates a detector for TOML code blocks.
|
|
150
|
-
# @return [FencedCodeBlockDetector]
|
|
151
|
-
def toml
|
|
152
|
-
new("toml")
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
# Creates a detector for Mermaid diagram blocks.
|
|
156
|
-
# @return [FencedCodeBlockDetector]
|
|
157
|
-
def mermaid
|
|
158
|
-
new("mermaid")
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
# Creates a detector for JavaScript code blocks.
|
|
162
|
-
# @return [FencedCodeBlockDetector]
|
|
163
|
-
def javascript
|
|
164
|
-
new("javascript", aliases: ["js"])
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
# Creates a detector for TypeScript code blocks.
|
|
168
|
-
# @return [FencedCodeBlockDetector]
|
|
169
|
-
def typescript
|
|
170
|
-
new("typescript", aliases: ["ts"])
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
# Creates a detector for Python code blocks.
|
|
174
|
-
# @return [FencedCodeBlockDetector]
|
|
175
|
-
def python
|
|
176
|
-
new("python", aliases: ["py"])
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
# Creates a detector for Bash/Shell code blocks.
|
|
180
|
-
# @return [FencedCodeBlockDetector]
|
|
181
|
-
def bash
|
|
182
|
-
new("bash", aliases: ["sh", "shell", "zsh"])
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
# Creates a detector for SQL code blocks.
|
|
186
|
-
# @return [FencedCodeBlockDetector]
|
|
187
|
-
def sql
|
|
188
|
-
new("sql")
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
# Creates a detector for HTML code blocks.
|
|
192
|
-
# @return [FencedCodeBlockDetector]
|
|
193
|
-
def html
|
|
194
|
-
new("html")
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
# Creates a detector for CSS code blocks.
|
|
198
|
-
# @return [FencedCodeBlockDetector]
|
|
199
|
-
def css
|
|
200
|
-
new("css")
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
# Creates a detector for Markdown code blocks (nested markdown).
|
|
204
|
-
# @return [FencedCodeBlockDetector]
|
|
205
|
-
def markdown
|
|
206
|
-
new("markdown", aliases: ["md"])
|
|
207
|
-
end
|
|
208
|
-
end
|
|
209
|
-
end
|
|
210
|
-
end
|
|
211
|
-
end
|
data/lib/ast/merge/region.rb
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Ast
|
|
4
|
-
module Merge
|
|
5
|
-
# Represents a detected region within a document.
|
|
6
|
-
#
|
|
7
|
-
# Regions are portions of a document that can be handled by a specialized
|
|
8
|
-
# merger. For example, YAML frontmatter in a Markdown file, or a Ruby code
|
|
9
|
-
# block that should be merged using a Ruby-aware merger.
|
|
10
|
-
#
|
|
11
|
-
# @example Creating a region for YAML frontmatter
|
|
12
|
-
# Region.new(
|
|
13
|
-
# type: :yaml_frontmatter,
|
|
14
|
-
# content: "title: My Doc\nversion: 1.0\n",
|
|
15
|
-
# start_line: 1,
|
|
16
|
-
# end_line: 4,
|
|
17
|
-
# delimiters: ["---", "---"],
|
|
18
|
-
# metadata: { format: :yaml }
|
|
19
|
-
# )
|
|
20
|
-
#
|
|
21
|
-
# @example Creating a region for a Ruby code block
|
|
22
|
-
# Region.new(
|
|
23
|
-
# type: :ruby_code_block,
|
|
24
|
-
# content: "def hello\n puts 'world'\nend\n",
|
|
25
|
-
# start_line: 5,
|
|
26
|
-
# end_line: 9,
|
|
27
|
-
# delimiters: ["```ruby", "```"],
|
|
28
|
-
# metadata: { language: "ruby" }
|
|
29
|
-
# )
|
|
30
|
-
#
|
|
31
|
-
# @api public
|
|
32
|
-
Region = Struct.new(
|
|
33
|
-
# @return [Symbol] The type of region (e.g., :yaml_frontmatter, :ruby_code_block)
|
|
34
|
-
:type,
|
|
35
|
-
|
|
36
|
-
# @return [String] The raw string content of this region (inner content, without delimiters)
|
|
37
|
-
:content,
|
|
38
|
-
|
|
39
|
-
# @return [Integer] 1-indexed start line in the original document
|
|
40
|
-
:start_line,
|
|
41
|
-
|
|
42
|
-
# @return [Integer] 1-indexed end line in the original document
|
|
43
|
-
:end_line,
|
|
44
|
-
|
|
45
|
-
# @return [Array<String>, nil] Delimiter strings to reconstruct the region
|
|
46
|
-
# ["```ruby", "```"] - [opening_delimiter, closing_delimiter]
|
|
47
|
-
:delimiters,
|
|
48
|
-
|
|
49
|
-
# @return [Hash, nil] Optional metadata for detector-specific information
|
|
50
|
-
# (e.g., { language: "ruby" }, { format: :yaml })
|
|
51
|
-
:metadata,
|
|
52
|
-
keyword_init: true,
|
|
53
|
-
) do
|
|
54
|
-
# Returns the line range covered by this region.
|
|
55
|
-
#
|
|
56
|
-
# @return [Range] The range from start_line to end_line (inclusive)
|
|
57
|
-
# @example
|
|
58
|
-
# region.line_range # => 1..4
|
|
59
|
-
def line_range
|
|
60
|
-
start_line..end_line
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# Returns the number of lines this region spans.
|
|
64
|
-
#
|
|
65
|
-
# @return [Integer] The number of lines
|
|
66
|
-
# @example
|
|
67
|
-
# region.line_count # => 4
|
|
68
|
-
def line_count
|
|
69
|
-
end_line - start_line + 1
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# Reconstructs the full region text including delimiters.
|
|
73
|
-
#
|
|
74
|
-
# @return [String] The complete region with start and end delimiters
|
|
75
|
-
# @example
|
|
76
|
-
# region.full_text
|
|
77
|
-
# # => "```ruby\ndef hello\n puts 'world'\nend\n```"
|
|
78
|
-
def full_text
|
|
79
|
-
return content if delimiters.nil? || delimiters.empty?
|
|
80
|
-
|
|
81
|
-
opening = delimiters[0] || ""
|
|
82
|
-
closing = delimiters[1] || ""
|
|
83
|
-
"#{opening}\n#{content}#{closing}"
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# Checks if this region overlaps with the given line number.
|
|
87
|
-
#
|
|
88
|
-
# @param line [Integer] The line number to check (1-indexed)
|
|
89
|
-
# @return [Boolean] true if the line is within this region
|
|
90
|
-
def contains_line?(line)
|
|
91
|
-
line_range.cover?(line)
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
# Checks if this region overlaps with another region.
|
|
95
|
-
#
|
|
96
|
-
# @param other [Region] Another region to check for overlap
|
|
97
|
-
# @return [Boolean] true if the regions overlap
|
|
98
|
-
def overlaps?(other)
|
|
99
|
-
line_range.cover?(other.start_line) ||
|
|
100
|
-
line_range.cover?(other.end_line) ||
|
|
101
|
-
other.line_range.cover?(start_line)
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
# Returns a short string representation of the region.
|
|
105
|
-
#
|
|
106
|
-
# @return [String] A concise string describing the region
|
|
107
|
-
def to_s
|
|
108
|
-
"Region<#{type}:#{start_line}-#{end_line}>"
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
# Returns a detailed human-readable representation of the region.
|
|
112
|
-
#
|
|
113
|
-
# @return [String] A string describing the region with truncated content
|
|
114
|
-
def inspect
|
|
115
|
-
truncated = if content && content.length > 30
|
|
116
|
-
"#{content[0, 30]}..."
|
|
117
|
-
else
|
|
118
|
-
content.inspect
|
|
119
|
-
end
|
|
120
|
-
"#{self} #{truncated}"
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
end
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Ast
|
|
4
|
-
module Merge
|
|
5
|
-
# Base class for region detection.
|
|
6
|
-
#
|
|
7
|
-
# Region detectors identify portions of a document that should be handled
|
|
8
|
-
# by a specialized merger. For example, detecting YAML frontmatter in a
|
|
9
|
-
# Markdown file, or Ruby code blocks that should be merged with Prism.
|
|
10
|
-
#
|
|
11
|
-
# Subclasses must implement:
|
|
12
|
-
# - {#region_type} - Returns the type symbol for detected regions
|
|
13
|
-
# - {#detect_all} - Finds all regions of this type in a document
|
|
14
|
-
#
|
|
15
|
-
# @example Implementing a custom detector
|
|
16
|
-
# class MyBlockDetector < Ast::Merge::RegionDetectorBase
|
|
17
|
-
# def region_type
|
|
18
|
-
# :my_block
|
|
19
|
-
# end
|
|
20
|
-
#
|
|
21
|
-
# def detect_all(source)
|
|
22
|
-
# # Return array of Region structs
|
|
23
|
-
# []
|
|
24
|
-
# end
|
|
25
|
-
# end
|
|
26
|
-
#
|
|
27
|
-
# @abstract Subclass and implement {#region_type} and {#detect_all}
|
|
28
|
-
# @api public
|
|
29
|
-
class RegionDetectorBase
|
|
30
|
-
# Returns the type symbol for regions detected by this detector.
|
|
31
|
-
#
|
|
32
|
-
# This symbol is used to identify the region type in the Region struct
|
|
33
|
-
# and for matching regions between template and destination documents.
|
|
34
|
-
#
|
|
35
|
-
# @return [Symbol] The region type (e.g., :yaml_frontmatter, :ruby_code_block)
|
|
36
|
-
# @abstract Subclasses must implement this method
|
|
37
|
-
def region_type
|
|
38
|
-
raise NotImplementedError, "#{self.class}#region_type must be implemented"
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# Detects all regions of this type in the given source.
|
|
42
|
-
#
|
|
43
|
-
# @param source [String] The full document content to scan
|
|
44
|
-
# @return [Array<Region>] All detected regions, sorted by start_line
|
|
45
|
-
# @abstract Subclasses must implement this method
|
|
46
|
-
#
|
|
47
|
-
# @example Return value structure
|
|
48
|
-
# [
|
|
49
|
-
# Region.new(
|
|
50
|
-
# type: :yaml_frontmatter,
|
|
51
|
-
# content: "title: My Doc\n",
|
|
52
|
-
# start_line: 1,
|
|
53
|
-
# end_line: 3,
|
|
54
|
-
# delimiters: { start: "---\n", end: "---\n" },
|
|
55
|
-
# metadata: { format: :yaml }
|
|
56
|
-
# )
|
|
57
|
-
# ]
|
|
58
|
-
def detect_all(source)
|
|
59
|
-
raise NotImplementedError, "#{self.class}#detect_all must be implemented"
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# Whether to strip delimiters from content before passing to merger.
|
|
63
|
-
#
|
|
64
|
-
# When true (default), only the inner content is passed to the region's
|
|
65
|
-
# merger. The delimiters are stored separately and reattached after merging.
|
|
66
|
-
#
|
|
67
|
-
# When false, the full content including delimiters is passed to the merger,
|
|
68
|
-
# which must then handle the delimiters itself.
|
|
69
|
-
#
|
|
70
|
-
# @return [Boolean] true if delimiters should be stripped (default: true)
|
|
71
|
-
def strip_delimiters?
|
|
72
|
-
true
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# A human-readable name for this detector.
|
|
76
|
-
#
|
|
77
|
-
# Used in error messages and debugging output.
|
|
78
|
-
#
|
|
79
|
-
# @return [String] The detector name
|
|
80
|
-
def name
|
|
81
|
-
self.class.name || "AnonymousDetector"
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# Returns a string representation of this detector.
|
|
85
|
-
#
|
|
86
|
-
# @return [String] A description of the detector
|
|
87
|
-
def inspect
|
|
88
|
-
"#<#{name} region_type=#{region_type}>"
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
protected
|
|
92
|
-
|
|
93
|
-
# Helper to build a Region struct with common defaults.
|
|
94
|
-
#
|
|
95
|
-
# @param type [Symbol] The region type
|
|
96
|
-
# @param content [String] The inner content (without delimiters)
|
|
97
|
-
# @param start_line [Integer] 1-indexed start line
|
|
98
|
-
# @param end_line [Integer] 1-indexed end line
|
|
99
|
-
# @param delimiters [Hash, nil] { start: String, end: String }
|
|
100
|
-
# @param metadata [Hash, nil] Additional metadata
|
|
101
|
-
# @return [Region] A new Region struct
|
|
102
|
-
def build_region(type:, content:, start_line:, end_line:, delimiters: nil, metadata: nil)
|
|
103
|
-
Region.new(
|
|
104
|
-
type: type,
|
|
105
|
-
content: content,
|
|
106
|
-
start_line: start_line,
|
|
107
|
-
end_line: end_line,
|
|
108
|
-
delimiters: delimiters,
|
|
109
|
-
metadata: metadata || {},
|
|
110
|
-
)
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
end
|