ast-merge 2.0.10 → 3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9659c263184e27b07b7fa51f976e398c82f5da9610411fbe867e2a7319042ba8
4
- data.tar.gz: 5dda81d19a9deb7bc2c9cfad0a8ab28b348cfb243ced1b453495bdef232d446f
3
+ metadata.gz: 79e0b486476d9494e6e1065d71b13ef8b940bd7d5db8caffd4784231ce239ad2
4
+ data.tar.gz: 941f9604854c537b087b76a253945f35dbc81f8f5b701f60dcad0ab1b9a35026
5
5
  SHA512:
6
- metadata.gz: 129fb9114b909bf56c1adacbf0b0f3b1527a006d12ad4df61f8f209fee482f681a18f67f4cea21f994fac7ee268ac032ef567ff3fb4dec5ed178f88506c122c5
7
- data.tar.gz: 671afd9a6960261dd3918577a8111e05bd71ecd9291388938e93baeb62b29002036e3e36d23e78e8bada44f0cd47d8e385df6052d252281cd4ea1b49bb057b9c
6
+ metadata.gz: f8b51479340dd11352faac1e7ca2933481dcbf012b1fbff8176a5f425e07717848dce99f4b631cdc5562fc897e4ae2bb7af06d9380d105a6f25ab612473cd231
7
+ data.tar.gz: d45e550372da739a549ba78c27fe15836a8ca17b674c88dcee958f0f58732f54b7d52d6d9b99a22ee635f2ad1862c9a2631e8cc15fb4fa3c38aca528d58142cc
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,50 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [3.0.0] - 2026-01-05
34
+
35
+ - TAG: [v3.0.0][3.0.0t]
36
+ - COVERAGE: 96.93% -- 2462/2540 lines in 47 files
37
+ - BRANCH COVERAGE: 89.62% -- 794/886 branches in 47 files
38
+ - 98.72% documented
39
+
40
+ ### Added
41
+
42
+ - `TestableNode` spec helper class that wraps a mock in a real `TreeHaver::Node`, providing consistent API testing without relying on fragile mocks
43
+ - `Recipe::Preset#match_refiner` accessor method (was missing, causing errors in Recipe::Runner)
44
+ - Minimal reproduction specs for `to_commonmark` normalization behavior:
45
+ - `spec/integration/link_reference_preservation_spec.rb` - tests link ref preservation
46
+ - `spec/integration/table_formatting_preservation_spec.rb` - tests table padding preservation
47
+ - `Ast::Merge::PartialTemplateMergerBase` - Abstract base class for parser-agnostic partial template merging
48
+ - `#build_position_based_signature_generator` - Creates signature generators that match elements by position
49
+ - Position counters reset per document key, enabling tables at same position to match regardless of structure
50
+
51
+ ### Changed
52
+
53
+ - **BREAKING**: `NavigableStatement#text` now requires nodes to conform to TreeHaver Node API (must have `#text` method)
54
+ - Removed conditional fallbacks for `to_plaintext`, `to_commonmark`, `slice`
55
+ - Nodes must now implement `#text` directly (all TreeHaver backends already do)
56
+ - **BREAKING**: `ContentMatchRefiner#extract_content` now requires nodes to conform to TreeHaver Node API
57
+ - Removed conditional fallbacks for `text_content`, `string_content`, `content`, `to_s`
58
+ - Custom `content_extractor` proc still supported for non-standard nodes
59
+ - Signature generators and typing scripts now receive TreeHaver nodes directly (no NavigableStatement wrapping)
60
+ - Removed NavigableStatement wrapping from `FileAnalyzable#generate_signature` and `NodeTyping.process`
61
+
62
+ ### Removed
63
+
64
+ - **BREAKING**: `Ast::Merge::PartialTemplateMerger` removed. Use `Markdown::Merge::PartialTemplateMerger` directly.
65
+ - The base class `Ast::Merge::PartialTemplateMergerBase` remains for other parsers to extend
66
+ - Migration: change `Ast::Merge::PartialTemplateMerger.new(parser: :markly, ...)` to
67
+ `Markdown::Merge::PartialTemplateMerger.new(backend: :markly, ...)`
68
+
69
+ ### Fixed
70
+
71
+ - **Source-based rendering**: `Markdown::Merge::PartialTemplateMerger#node_to_text` now prefers extracting
72
+ original source text using `analysis.source_range` instead of `to_commonmark`. This preserves:
73
+ - Link reference definitions (no conversion to inline links)
74
+ - Table column padding/alignment
75
+ - Original formatting exactly as written
76
+
33
77
  ## [2.0.10] - 2026-01-04
34
78
 
35
79
  - TAG: [v2.0.10][2.0.10t]
@@ -463,7 +507,9 @@ Please file a bug if you notice a violation of semantic versioning.
463
507
 
464
508
  - Initial release
465
509
 
466
- [Unreleased]: https://github.com/kettle-rb/ast-merge/compare/v2.0.10...HEAD
510
+ [Unreleased]: https://github.com/kettle-rb/ast-merge/compare/v3.0.0...HEAD
511
+ [3.0.0]: https://github.com/kettle-rb/ast-merge/compare/v2.0.10...v3.0.0
512
+ [3.0.0t]: https://github.com/kettle-rb/ast-merge/releases/tag/v3.0.0
467
513
  [2.0.10]: https://github.com/kettle-rb/ast-merge/compare/v2.0.9...v2.0.10
468
514
  [2.0.10t]: https://github.com/kettle-rb/ast-merge/releases/tag/v2.0.10
469
515
  [2.0.9]: https://github.com/kettle-rb/ast-merge/compare/v2.0.8...v2.0.9
data/README.md CHANGED
@@ -1033,7 +1033,7 @@ Thanks for RTFM. ☺️
1033
1033
  [📌gitmoji]: https://gitmoji.dev
1034
1034
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
1035
1035
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
1036
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-2.721-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1036
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-2.540-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1037
1037
  [🔐security]: SECURITY.md
1038
1038
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
1039
1039
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -140,28 +140,17 @@ module Ast
140
140
 
141
141
  # Extract text content from a node.
142
142
  #
143
- # Uses the custom content_extractor if provided, otherwise tries
144
- # common methods for getting text content.
143
+ # Uses the custom content_extractor if provided, otherwise uses the
144
+ # standard #text method that all TreeHaver nodes provide.
145
145
  #
146
- # @param node [Object] The node
146
+ # @param node [Object] The node (must conform to TreeHaver Node API)
147
147
  # @return [String] The text content
148
148
  def extract_content(node)
149
149
  return @content_extractor.call(node) if @content_extractor
150
150
 
151
- # Try common content extraction methods
152
- if node.respond_to?(:text_content)
153
- node.text_content.to_s
154
- elsif node.respond_to?(:string_content)
155
- node.string_content.to_s
156
- elsif node.respond_to?(:content)
157
- node.content.to_s
158
- elsif node.respond_to?(:text)
159
- node.text.to_s
160
- elsif node.respond_to?(:to_s)
161
- node.to_s
162
- else
163
- ""
164
- end
151
+ # TreeHaver nodes (and any node conforming to the unified API) provide #text.
152
+ # No conditional fallbacks - nodes must conform to the API.
153
+ node.text.to_s
165
154
  end
166
155
 
167
156
  # Compute similarity score between two nodes based on content.
@@ -224,6 +224,10 @@ module Ast
224
224
  # - Type checks work (e.g., `node.is_a?(Prism::CallNode)`)
225
225
  # - The generator sees the real AST structure
226
226
  # - Frozen nodes match by their underlying identity
227
+ #
228
+ # NOTE: For TreeHaver-based backends, the node already has a unified API
229
+ # with #text, #type, #source_position methods. For other backends, they
230
+ # must conform to the same API (either via TreeHaver or equivalent adapter).
227
231
  custom_result = signature_generator.call(actual_node)
228
232
  case custom_result
229
233
  when Array, nil
@@ -270,17 +270,9 @@ module Ast
270
270
 
271
271
  # @return [String] Node text content
272
272
  def text
273
- if node.respond_to?(:to_plaintext)
274
- node.to_plaintext.to_s
275
- elsif node.respond_to?(:to_commonmark)
276
- node.to_commonmark.to_s
277
- elsif node.respond_to?(:slice)
278
- node.slice.to_s
279
- elsif node.respond_to?(:text)
280
- node.text.to_s
281
- else
282
- node.to_s
283
- end
273
+ # TreeHaver nodes (and any node conforming to the unified API) provide #text.
274
+ # No conditional fallbacks - nodes must conform to the API.
275
+ node.text.to_s
284
276
  end
285
277
 
286
278
  # @return [Hash, nil] Source position info
@@ -139,7 +139,10 @@ module Ast
139
139
  callable = find_typing_callable(typing_config, type_key, node)
140
140
  return node unless callable
141
141
 
142
- # Call the typing callable with the node
142
+ # Call the typing callable with the node.
143
+ # NOTE: For TreeHaver-based backends, the node already has a unified API
144
+ # with #text, #type, #source_position methods. For other backends, they
145
+ # must conform to the same API (either via TreeHaver or equivalent adapter).
143
146
  callable.call(node)
144
147
  end
145
148
 
@@ -2,36 +2,23 @@
2
2
 
3
3
  module Ast
4
4
  module Merge
5
- # Merges a partial template into a specific section of a destination document.
5
+ # Base class for merging a partial template into a specific section of a destination document.
6
6
  #
7
- # Unlike the full SmartMerger which merges entire documents, PartialTemplateMerger:
7
+ # Unlike the full SmartMerger which merges entire documents, PartialTemplateMergerBase:
8
8
  # 1. Finds a specific section in the destination (using InjectionPoint)
9
9
  # 2. Replaces/merges only that section with the template
10
10
  # 3. Leaves the rest of the destination unchanged
11
11
  #
12
- # This is useful for updating a specific section (like a "Gem Family" section)
13
- # across multiple files while preserving file-specific content.
12
+ # This is an abstract base class. Subclasses must implement:
13
+ # - #create_analysis(content) - Create a FileAnalysis for the given content
14
+ # - #create_smart_merger(template, section) - Create a SmartMerger for the section merge
15
+ # - #find_section_end(statements, injection_point) - Find where the section ends
16
+ # - #node_to_text(node, analysis) - Convert a node to source text
14
17
  #
15
- # @example Basic usage
16
- # merger = PartialTemplateMerger.new(
17
- # template: template_content,
18
- # destination: destination_content,
19
- # anchor: { type: :heading, text: /Gem Family/ },
20
- # parser: :markly
21
- # )
22
- # result = merger.merge
23
- # puts result.content
18
+ # @abstract Subclass and implement parser-specific methods
19
+ # @see Markdown::Merge::PartialTemplateMerger For markdown implementation
24
20
  #
25
- # @example With boundary
26
- # merger = PartialTemplateMerger.new(
27
- # template: template_content,
28
- # destination: destination_content,
29
- # anchor: { type: :heading, text: /Installation/ },
30
- # boundary: { type: :heading }, # Stop at next heading
31
- # parser: :markly
32
- # )
33
- #
34
- class PartialTemplateMerger
21
+ class PartialTemplateMergerBase
35
22
  # Result of a partial template merge
36
23
  class Result
37
24
  # @return [String] The merged content
@@ -79,9 +66,6 @@ module Ast
79
66
  # @return [Hash, nil] Boundary matcher configuration
80
67
  attr_reader :boundary
81
68
 
82
- # @return [Symbol] Parser to use (:markly, :commonmarker, etc.)
83
- attr_reader :parser
84
-
85
69
  # @return [Symbol, Hash] Merge preference (:template, :destination, or per-type hash)
86
70
  attr_reader :preference
87
71
 
@@ -97,43 +81,46 @@ module Ast
97
81
  # @return [Hash, nil] Node typing configuration for per-type preferences
98
82
  attr_reader :node_typing
99
83
 
100
- # Initialize a PartialTemplateMerger.
84
+ # @return [Object, nil] Match refiner for fuzzy matching unmatched nodes
85
+ attr_reader :match_refiner
86
+
87
+ # Initialize a PartialTemplateMergerBase.
101
88
  #
102
89
  # @param template [String] The template content (the section to merge in)
103
90
  # @param destination [String] The destination content
104
91
  # @param anchor [Hash] Anchor matcher: { type: :heading, text: /pattern/ }
105
92
  # @param boundary [Hash, nil] Boundary matcher (defaults to same type as anchor)
106
- # @param parser [Symbol] Parser to use (:markly, :commonmarker, :prism, :psych)
107
93
  # @param preference [Symbol, Hash] Which content wins (:template, :destination, or per-type hash)
108
94
  # @param add_missing [Boolean, Proc] Whether to add template nodes not in destination
109
95
  # @param when_missing [Symbol] What to do if section not found (:skip, :append, :prepend)
110
96
  # @param replace_mode [Boolean] If true, template replaces section entirely (no merge)
111
97
  # @param signature_generator [Proc, nil] Custom signature generator for SmartMerger
112
98
  # @param node_typing [Hash, nil] Node typing configuration for per-type preferences
99
+ # @param match_refiner [Object, nil] Match refiner for fuzzy matching (e.g., ContentMatchRefiner)
113
100
  def initialize(
114
101
  template:,
115
102
  destination:,
116
103
  anchor:,
117
104
  boundary: nil,
118
- parser: :markly,
119
105
  preference: :template,
120
106
  add_missing: true,
121
107
  when_missing: :skip,
122
108
  replace_mode: false,
123
109
  signature_generator: nil,
124
- node_typing: nil
110
+ node_typing: nil,
111
+ match_refiner: nil
125
112
  )
126
113
  @template = template
127
114
  @destination = destination
128
115
  @anchor = normalize_matcher(anchor)
129
116
  @boundary = boundary ? normalize_matcher(boundary) : nil
130
- @parser = parser
131
117
  @preference = preference
132
118
  @add_missing = add_missing
133
119
  @when_missing = when_missing
134
120
  @replace_mode = replace_mode
135
121
  @signature_generator = signature_generator
136
122
  @node_typing = node_typing
123
+ @match_refiner = match_refiner
137
124
  end
138
125
 
139
126
  # Perform the partial template merge.
@@ -161,6 +148,47 @@ module Ast
161
148
  perform_section_merge(d_analysis, d_statements, injection_point)
162
149
  end
163
150
 
151
+ protected
152
+
153
+ # Create a FileAnalysis for the given content.
154
+ #
155
+ # @abstract Subclasses must implement this method
156
+ # @param content [String] The content to analyze
157
+ # @return [Object] A FileAnalysis instance
158
+ def create_analysis(content)
159
+ raise NotImplementedError, "#{self.class} must implement #create_analysis"
160
+ end
161
+
162
+ # Create a SmartMerger for merging the section.
163
+ #
164
+ # @abstract Subclasses must implement this method
165
+ # @param template_content [String] The template content
166
+ # @param destination_content [String] The destination section content
167
+ # @return [Object] A SmartMerger instance
168
+ def create_smart_merger(template_content, destination_content)
169
+ raise NotImplementedError, "#{self.class} must implement #create_smart_merger"
170
+ end
171
+
172
+ # Find where the section ends.
173
+ #
174
+ # @abstract Subclasses must implement this method
175
+ # @param statements [Array<NavigableStatement>] All statements
176
+ # @param injection_point [InjectionPoint] The injection point
177
+ # @return [Integer] Index of the last statement in the section
178
+ def find_section_end(statements, injection_point)
179
+ raise NotImplementedError, "#{self.class} must implement #find_section_end"
180
+ end
181
+
182
+ # Convert a node to its source text.
183
+ #
184
+ # @abstract Subclasses must implement this method
185
+ # @param node [Object] The node to convert
186
+ # @param analysis [Object, nil] The analysis object for source lookup
187
+ # @return [String] The source text
188
+ def node_to_text(node, analysis = nil)
189
+ raise NotImplementedError, "#{self.class} must implement #node_to_text"
190
+ end
191
+
164
192
  private
165
193
 
166
194
  def normalize_matcher(matcher)
@@ -187,17 +215,10 @@ module Ast
187
215
  end
188
216
  end
189
217
 
190
- def handle_missing_section(_d_analysis)
218
+ def handle_missing_section(d_analysis)
191
219
  case when_missing
192
- when :skip
193
- Result.new(
194
- content: destination,
195
- has_section: false,
196
- changed: false,
197
- message: "Section not found, skipping",
198
- )
199
220
  when :append
200
- # Append template at end of document
221
+ # Append template to end of destination
201
222
  new_content = destination.chomp + "\n\n" + template
202
223
  Result.new(
203
224
  content: new_content,
@@ -206,7 +227,7 @@ module Ast
206
227
  message: "Section not found, appended template",
207
228
  )
208
229
  when :prepend
209
- # Prepend template at start (after any frontmatter)
230
+ # Prepend template to beginning of destination
210
231
  new_content = template + "\n\n" + destination
211
232
  Result.new(
212
233
  content: new_content,
@@ -219,12 +240,12 @@ module Ast
219
240
  content: destination,
220
241
  has_section: false,
221
242
  changed: false,
222
- message: "Section not found, no action taken",
243
+ message: "Section not found, skipping",
223
244
  )
224
245
  end
225
246
  end
226
247
 
227
- def perform_section_merge(_d_analysis, d_statements, injection_point)
248
+ def perform_section_merge(d_analysis, d_statements, injection_point)
228
249
  # Determine section boundaries in destination
229
250
  section_start_idx = injection_point.anchor.index
230
251
  section_end_idx = find_section_end(d_statements, injection_point)
@@ -235,12 +256,12 @@ module Ast
235
256
  after_statements = d_statements[(section_end_idx + 1)..]
236
257
 
237
258
  # Determine the merged section content
238
- section_content = statements_to_content(section_statements)
259
+ section_content = statements_to_content(section_statements, d_analysis)
239
260
  merged_section, stats = merge_section_content(section_content)
240
261
 
241
- # Reconstruct the document
242
- before_content = statements_to_content(before_statements)
243
- after_content = statements_to_content(after_statements)
262
+ # Reconstruct the document using source-based extraction
263
+ before_content = statements_to_content(before_statements, d_analysis)
264
+ after_content = statements_to_content(after_statements, d_analysis)
244
265
 
245
266
  new_content = build_merged_content(before_content, merged_section, after_content)
246
267
 
@@ -280,83 +301,15 @@ module Ast
280
301
  @replace_mode == true
281
302
  end
282
303
 
283
- def find_section_end(statements, injection_point)
284
- # If boundary was specified and found, use it (exclusive - section ends before boundary)
285
- if injection_point.boundary
286
- return injection_point.boundary.index - 1
287
- end
288
-
289
- # Otherwise, find the next node of same type (for headings, same or higher level)
290
- anchor = injection_point.anchor
291
- anchor_type = anchor.type
292
-
293
- # For headings, find next heading of same or higher level
294
- if heading_type?(anchor_type)
295
- anchor_level = get_heading_level(anchor)
296
-
297
- ((anchor.index + 1)...statements.length).each do |idx|
298
- stmt = statements[idx]
299
- if heading_type?(stmt.type)
300
- stmt_level = get_heading_level(stmt)
301
- if stmt_level && anchor_level && stmt_level <= anchor_level
302
- # Found next heading of same or higher level - section ends before it
303
- return idx - 1
304
- end
305
- end
306
- end
307
- else
308
- # For non-headings, find next node of same type
309
- ((anchor.index + 1)...statements.length).each do |idx|
310
- stmt = statements[idx]
311
- if stmt.type == anchor_type
312
- return idx - 1
313
- end
314
- end
315
- end
316
-
317
- # Section extends to end of document
318
- statements.length - 1
319
- end
320
-
321
- def heading_type?(type)
322
- type.to_s == "heading" || type == :heading || type == :header
323
- end
324
-
325
- def get_heading_level(stmt)
326
- inner = stmt.respond_to?(:unwrapped_node) ? stmt.unwrapped_node : stmt.node
327
-
328
- if inner.respond_to?(:header_level)
329
- inner.header_level
330
- elsif inner.respond_to?(:level)
331
- inner.level
332
- end
333
- end
334
-
335
- def statements_to_content(statements)
304
+ def statements_to_content(statements, analysis = nil)
336
305
  return "" if statements.nil? || statements.empty?
337
306
 
338
307
  statements.map do |stmt|
339
308
  node = stmt.respond_to?(:node) ? stmt.node : stmt
340
- node_to_text(node)
309
+ node_to_text(node, analysis)
341
310
  end.join
342
311
  end
343
312
 
344
- def node_to_text(node)
345
- # Unwrap if needed
346
- inner = node
347
- while inner.respond_to?(:inner_node) && inner.inner_node != inner
348
- inner = inner.inner_node
349
- end
350
-
351
- if inner.respond_to?(:to_commonmark)
352
- inner.to_commonmark.to_s
353
- elsif inner.respond_to?(:to_s)
354
- inner.to_s
355
- else
356
- ""
357
- end
358
- end
359
-
360
313
  def build_merged_content(before, section, after)
361
314
  result = +""
362
315
 
@@ -388,54 +341,6 @@ module Ast
388
341
  result << "\n" unless result.empty? || result.end_with?("\n")
389
342
  result
390
343
  end
391
-
392
- def create_analysis(content)
393
- case parser
394
- when :markly
395
- require "markly/merge" unless defined?(Markly::Merge)
396
- Markly::Merge::FileAnalysis.new(content)
397
- when :commonmarker
398
- require "commonmarker/merge" unless defined?(Commonmarker::Merge)
399
- Commonmarker::Merge::FileAnalysis.new(content)
400
- when :prism
401
- require "prism/merge" unless defined?(Prism::Merge)
402
- Prism::Merge::FileAnalysis.new(content)
403
- when :psych
404
- require "psych/merge" unless defined?(Psych::Merge)
405
- Psych::Merge::FileAnalysis.new(content)
406
- else
407
- raise ArgumentError, "Unknown parser: #{parser}"
408
- end
409
- end
410
-
411
- def create_smart_merger(template_content, destination_content)
412
- merger_class = case parser
413
- when :markly
414
- require "markly/merge" unless defined?(Markly::Merge)
415
- Markly::Merge::SmartMerger
416
- when :commonmarker
417
- require "commonmarker/merge" unless defined?(Commonmarker::Merge)
418
- Commonmarker::Merge::SmartMerger
419
- when :prism
420
- require "prism/merge" unless defined?(Prism::Merge)
421
- Prism::Merge::SmartMerger
422
- when :psych
423
- require "psych/merge" unless defined?(Psych::Merge)
424
- Psych::Merge::SmartMerger
425
- else
426
- raise ArgumentError, "Unknown parser: #{parser}"
427
- end
428
-
429
- # Build options hash, only including non-nil values
430
- options = {
431
- preference: preference,
432
- add_template_only_nodes: add_missing,
433
- }
434
- options[:signature_generator] = signature_generator if signature_generator
435
- options[:node_typing] = node_typing if node_typing
436
-
437
- merger_class.new(template_content, destination_content, **options)
438
- end
439
344
  end
440
345
  end
441
346
  end
@@ -124,6 +124,17 @@ module Ast
124
124
  script_loader.load_callable_hash(value)
125
125
  end
126
126
 
127
+ # Get the match_refiner callable, loading from script if needed.
128
+ #
129
+ # @return [Object, nil] Match refiner instance or callable
130
+ def match_refiner
131
+ value = merge_config[:match_refiner]
132
+ return if value.nil?
133
+ return value if value.respond_to?(:call) || value.is_a?(Ast::Merge::MatchRefinerBase)
134
+
135
+ script_loader.load_callable(value)
136
+ end
137
+
127
138
  # Convert preset to a hash suitable for SmartMerger options.
128
139
  #
129
140
  # @return [Hash]
@@ -133,6 +144,7 @@ module Ast
133
144
  add_template_only_nodes: add_missing,
134
145
  signature_generator: signature_generator,
135
146
  node_typing: node_typing,
147
+ match_refiner: match_refiner,
136
148
  freeze_token: freeze_token,
137
149
  }.compact
138
150
  end
@@ -155,6 +167,7 @@ module Ast
155
167
  deep: config["deep"] == true,
156
168
  signature_generator: config["signature_generator"],
157
169
  node_typing: config["node_typing"],
170
+ match_refiner: config["match_refiner"],
158
171
  }
159
172
  end
160
173
 
@@ -144,19 +144,19 @@ module Ast
144
144
  begin
145
145
  destination_content = File.read(target_path)
146
146
 
147
- # Use PartialTemplateMerger which handles finding injection point and merging
148
- merger = PartialTemplateMerger.new(
147
+ # Use the appropriate PartialTemplateMerger based on parser
148
+ merger = create_partial_template_merger(
149
149
  template: template_content,
150
150
  destination: destination_content,
151
151
  anchor: recipe.injection[:anchor] || {},
152
152
  boundary: recipe.injection[:boundary],
153
- parser: parser,
154
153
  preference: recipe.preference,
155
154
  add_missing: recipe.add_missing,
156
155
  when_missing: recipe.when_missing,
157
156
  replace_mode: recipe.replace_mode?,
158
157
  signature_generator: recipe.signature_generator,
159
158
  node_typing: recipe.node_typing,
159
+ match_refiner: recipe.match_refiner,
160
160
  )
161
161
 
162
162
  result = merger.merge
@@ -179,6 +179,26 @@ module Ast
179
179
  end
180
180
  end
181
181
 
182
+ # Create the appropriate PartialTemplateMerger based on parser type.
183
+ #
184
+ # @param options [Hash] Merger options
185
+ # @return [Object] A PartialTemplateMerger instance
186
+ def create_partial_template_merger(**options)
187
+ case parser.to_sym
188
+ when :markly, :commonmarker
189
+ require "markdown/merge" unless defined?(Markdown::Merge)
190
+ Markdown::Merge::PartialTemplateMerger.new(backend: parser, **options)
191
+ when :prism
192
+ require "prism/merge" unless defined?(Prism::Merge)
193
+ raise NotImplementedError, "Prism PartialTemplateMerger not yet implemented"
194
+ when :psych
195
+ require "psych/merge" unless defined?(Psych::Merge)
196
+ raise NotImplementedError, "Psych PartialTemplateMerger not yet implemented"
197
+ else
198
+ raise ArgumentError, "Unknown parser: #{parser}. Supported: :markly, :commonmarker"
199
+ end
200
+ end
201
+
182
202
  def create_result_from_merge(target_path, relative_path, _destination_content, merge_result)
183
203
  changed = merge_result.changed
184
204
 
@@ -5,7 +5,7 @@ module Ast
5
5
  # Version information for Ast::Merge
6
6
  module Version
7
7
  # Current version of the ast-merge gem
8
- VERSION = "2.0.10"
8
+ VERSION = "3.0.0"
9
9
  end
10
10
  VERSION = Version::VERSION # traditional location
11
11
  end
data/lib/ast/merge.rb CHANGED
@@ -154,7 +154,7 @@ module Ast
154
154
  autoload :NavigableStatement, "ast/merge/navigable_statement"
155
155
  autoload :NodeTyping, "ast/merge/node_typing"
156
156
  autoload :NodeWrapperBase, "ast/merge/node_wrapper_base"
157
- autoload :PartialTemplateMerger, "ast/merge/partial_template_merger"
157
+ autoload :PartialTemplateMergerBase, "ast/merge/partial_template_merger_base"
158
158
  autoload :SectionTyping, "ast/merge/section_typing"
159
159
  autoload :SmartMergerBase, "ast/merge/smart_merger_base"
160
160
  autoload :Text, "ast/merge/text"
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: 2.0.10
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -66,7 +66,7 @@ dependencies:
66
66
  version: '3.2'
67
67
  - - ">="
68
68
  - !ruby/object:Gem::Version
69
- version: 3.2.3
69
+ version: 3.2.5
70
70
  type: :runtime
71
71
  prerelease: false
72
72
  version_requirements: !ruby/object:Gem::Requirement
@@ -76,7 +76,7 @@ dependencies:
76
76
  version: '3.2'
77
77
  - - ">="
78
78
  - !ruby/object:Gem::Version
79
- version: 3.2.3
79
+ version: 3.2.5
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: kettle-dev
82
82
  requirement: !ruby/object:Gem::Requirement
@@ -322,7 +322,7 @@ files:
322
322
  - lib/ast/merge/node_typing/normalizer.rb
323
323
  - lib/ast/merge/node_typing/wrapper.rb
324
324
  - lib/ast/merge/node_wrapper_base.rb
325
- - lib/ast/merge/partial_template_merger.rb
325
+ - lib/ast/merge/partial_template_merger_base.rb
326
326
  - lib/ast/merge/recipe.rb
327
327
  - lib/ast/merge/recipe/config.rb
328
328
  - lib/ast/merge/recipe/preset.rb
@@ -356,10 +356,10 @@ licenses:
356
356
  - MIT
357
357
  metadata:
358
358
  homepage_uri: https://ast-merge.galtzo.com/
359
- source_code_uri: https://github.com/kettle-rb/ast-merge/tree/v2.0.10
360
- changelog_uri: https://github.com/kettle-rb/ast-merge/blob/v2.0.10/CHANGELOG.md
359
+ source_code_uri: https://github.com/kettle-rb/ast-merge/tree/v3.0.0
360
+ changelog_uri: https://github.com/kettle-rb/ast-merge/blob/v3.0.0/CHANGELOG.md
361
361
  bug_tracker_uri: https://github.com/kettle-rb/ast-merge/issues
362
- documentation_uri: https://www.rubydoc.info/gems/ast-merge/2.0.10
362
+ documentation_uri: https://www.rubydoc.info/gems/ast-merge/3.0.0
363
363
  funding_uri: https://github.com/sponsors/pboling
364
364
  wiki_uri: https://github.com/kettle-rb/ast-merge/wiki
365
365
  news_uri: https://www.railsbling.com/tags/ast-merge
metadata.gz.sig CHANGED
Binary file