ast-merge 2.0.10 → 3.1.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 +75 -1
- data/README.md +32 -9
- data/lib/ast/merge/content_match_refiner.rb +6 -17
- data/lib/ast/merge/emitter_base.rb +123 -0
- data/lib/ast/merge/file_analyzable.rb +4 -0
- data/lib/ast/merge/freeze_node_base.rb +9 -0
- data/lib/ast/merge/navigable_statement.rb +3 -11
- data/lib/ast/merge/node_typing.rb +25 -1
- data/lib/ast/merge/{partial_template_merger.rb → partial_template_merger_base.rb} +70 -165
- data/lib/ast/merge/recipe/preset.rb +13 -0
- data/lib/ast/merge/recipe/runner.rb +23 -3
- data/lib/ast/merge/version.rb +1 -1
- data/lib/ast/merge.rb +2 -1
- data.tar.gz.sig +0 -0
- metadata +10 -9
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4ce2b23f374b6f178709f84a66ccbed7b997a05d170d7b1f63ede714fc844469
|
|
4
|
+
data.tar.gz: d0ae1bc694c669108b5ddaac372424c02f8528f3c59642ced6fb7bb1af67dcf4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 41981f4b37122e224ecc1b3047156ec15871742fbca09b8203acb91c27df7acf61164871c2ee7eac07db32a12acccc7c8dda3b83b8cd7dafd8f86ade604a82a6
|
|
7
|
+
data.tar.gz: 29c07de568c15bc8a78b6363c438e6c7902af5f24d8d441aaa9f9417bceeedb62107f40e95a226864be3492b16373ae4493efa0ed956c780448bf4a3168980b9
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,76 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
30
30
|
|
|
31
31
|
### Security
|
|
32
32
|
|
|
33
|
+
## [3.1.0] - 2026-01-08
|
|
34
|
+
|
|
35
|
+
- TAG: [v3.1.0][3.1.0t]
|
|
36
|
+
- COVERAGE: 96.89% -- 2465/2544 lines in 47 files
|
|
37
|
+
- BRANCH COVERAGE: 89.62% -- 794/886 branches in 47 files
|
|
38
|
+
- 98.75% documented
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- `Ast::Merge::EmitterBase` - Abstract base class for format-specific emitters
|
|
43
|
+
- Provides common infrastructure for converting AST structures back to text
|
|
44
|
+
- Tracks indentation level with configurable `indent_size` (default: 2 spaces)
|
|
45
|
+
- Manages output lines collection with `#lines` accessor
|
|
46
|
+
- `#emit_blank_line` - Emit an empty line
|
|
47
|
+
- `#emit_leading_comments` - Emit comments from CommentTracker
|
|
48
|
+
- `#emit_raw_lines` - Emit lines without modification (preserves exact formatting)
|
|
49
|
+
- `#to_s` - Get output as a single string with trailing newline
|
|
50
|
+
- `#clear` - Reset emitter state
|
|
51
|
+
- `#indent` / `#dedent` - Increase/decrease indentation level
|
|
52
|
+
- Subclass hooks: `#initialize_subclass_state`, `#clear_subclass_state`, `#emit_tracked_comment`
|
|
53
|
+
- Used by jsonc-merge, json-merge, bash-merge, toml-merge, and psych-merge emitters
|
|
54
|
+
|
|
55
|
+
### Changed
|
|
56
|
+
|
|
57
|
+
- tree_haver v4.0.0
|
|
58
|
+
|
|
59
|
+
## [3.0.0] - 2026-01-05
|
|
60
|
+
|
|
61
|
+
- TAG: [v3.0.0][3.0.0t]
|
|
62
|
+
- COVERAGE: 96.93% -- 2462/2540 lines in 47 files
|
|
63
|
+
- BRANCH COVERAGE: 89.62% -- 794/886 branches in 47 files
|
|
64
|
+
- 98.72% documented
|
|
65
|
+
|
|
66
|
+
### Added
|
|
67
|
+
|
|
68
|
+
- `TestableNode` spec helper class that wraps a mock in a real `TreeHaver::Node`, providing consistent API testing without relying on fragile mocks
|
|
69
|
+
- `Recipe::Preset#match_refiner` accessor method (was missing, causing errors in Recipe::Runner)
|
|
70
|
+
- Minimal reproduction specs for `to_commonmark` normalization behavior:
|
|
71
|
+
- `spec/integration/link_reference_preservation_spec.rb` - tests link ref preservation
|
|
72
|
+
- `spec/integration/table_formatting_preservation_spec.rb` - tests table padding preservation
|
|
73
|
+
- `Ast::Merge::PartialTemplateMergerBase` - Abstract base class for parser-agnostic partial template merging
|
|
74
|
+
- `#build_position_based_signature_generator` - Creates signature generators that match elements by position
|
|
75
|
+
- Position counters reset per document key, enabling tables at same position to match regardless of structure
|
|
76
|
+
|
|
77
|
+
### Changed
|
|
78
|
+
|
|
79
|
+
- **BREAKING**: `NavigableStatement#text` now requires nodes to conform to TreeHaver Node API (must have `#text` method)
|
|
80
|
+
- Removed conditional fallbacks for `to_plaintext`, `to_commonmark`, `slice`
|
|
81
|
+
- Nodes must now implement `#text` directly (all TreeHaver backends already do)
|
|
82
|
+
- **BREAKING**: `ContentMatchRefiner#extract_content` now requires nodes to conform to TreeHaver Node API
|
|
83
|
+
- Removed conditional fallbacks for `text_content`, `string_content`, `content`, `to_s`
|
|
84
|
+
- Custom `content_extractor` proc still supported for non-standard nodes
|
|
85
|
+
- Signature generators and typing scripts now receive TreeHaver nodes directly (no NavigableStatement wrapping)
|
|
86
|
+
- Removed NavigableStatement wrapping from `FileAnalyzable#generate_signature` and `NodeTyping.process`
|
|
87
|
+
|
|
88
|
+
### Removed
|
|
89
|
+
|
|
90
|
+
- **BREAKING**: `Ast::Merge::PartialTemplateMerger` removed. Use `Markdown::Merge::PartialTemplateMerger` directly.
|
|
91
|
+
- The base class `Ast::Merge::PartialTemplateMergerBase` remains for other parsers to extend
|
|
92
|
+
- Migration: change `Ast::Merge::PartialTemplateMerger.new(parser: :markly, ...)` to
|
|
93
|
+
`Markdown::Merge::PartialTemplateMerger.new(backend: :markly, ...)`
|
|
94
|
+
|
|
95
|
+
### Fixed
|
|
96
|
+
|
|
97
|
+
- **Source-based rendering**: `Markdown::Merge::PartialTemplateMerger#node_to_text` now prefers extracting
|
|
98
|
+
original source text using `analysis.source_range` instead of `to_commonmark`. This preserves:
|
|
99
|
+
- Link reference definitions (no conversion to inline links)
|
|
100
|
+
- Table column padding/alignment
|
|
101
|
+
- Original formatting exactly as written
|
|
102
|
+
|
|
33
103
|
## [2.0.10] - 2026-01-04
|
|
34
104
|
|
|
35
105
|
- TAG: [v2.0.10][2.0.10t]
|
|
@@ -463,7 +533,11 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
463
533
|
|
|
464
534
|
- Initial release
|
|
465
535
|
|
|
466
|
-
[Unreleased]: https://github.com/kettle-rb/ast-merge/compare/
|
|
536
|
+
[Unreleased]: https://github.com/kettle-rb/ast-merge/compare/v3.1.0...HEAD
|
|
537
|
+
[3.1.0]: https://github.com/kettle-rb/ast-merge/compare/v3.0.0...v3.1.0
|
|
538
|
+
[3.1.0t]: https://github.com/kettle-rb/ast-merge/releases/tag/v3.1.0
|
|
539
|
+
[3.0.0]: https://github.com/kettle-rb/ast-merge/compare/v2.0.10...v3.0.0
|
|
540
|
+
[3.0.0t]: https://github.com/kettle-rb/ast-merge/releases/tag/v3.0.0
|
|
467
541
|
[2.0.10]: https://github.com/kettle-rb/ast-merge/compare/v2.0.9...v2.0.10
|
|
468
542
|
[2.0.10t]: https://github.com/kettle-rb/ast-merge/releases/tag/v2.0.10
|
|
469
543
|
[2.0.9]: https://github.com/kettle-rb/ast-merge/compare/v2.0.8...v2.0.9
|
data/README.md
CHANGED
|
@@ -75,12 +75,36 @@ The `*-merge` gem family provides intelligent, AST-based merging for various fil
|
|
|
75
75
|
| [rbs-merge][rbs-merge] | RBS | [tree-sitter-bash][ts-rbs] (via tree_haver), [RBS][rbs] (`rbs` std lib gem) | Smart merge for Ruby type signatures |
|
|
76
76
|
| [toml-merge][toml-merge] | TOML | [Citrus + toml-rb][toml-rb] (default, via tree_haver), [tree-sitter-toml][ts-toml] (via tree_haver) | Smart merge for TOML files |
|
|
77
77
|
|
|
78
|
+
#### Backend Platform Compatibility
|
|
79
|
+
|
|
80
|
+
tree_haver supports multiple parsing backends, but not all backends work on all Ruby platforms:
|
|
81
|
+
|
|
82
|
+
| Platform 👉️<br> TreeHaver Backend 👇️ | MRI | JRuby | TruffleRuby | Notes |
|
|
83
|
+
|------------------------------------------------|:---:|:-----:|:-----------:|-----------------------------------------------------|
|
|
84
|
+
| **MRI** ([ruby_tree_sitter][ruby_tree_sitter]) | ✅ | ❌ | ❌ | C extension, MRI only |
|
|
85
|
+
| **Rust** ([tree_stump][tree_stump]) | ✅ | ❌ | ❌ | Rust extension via magnus/rb-sys, MRI only |
|
|
86
|
+
| **FFI** | ✅ | ✅ | ❌ | TruffleRuby's FFI doesn't support `STRUCT_BY_VALUE` |
|
|
87
|
+
| **Java** ([jtreesitter][jtreesitter]) | ❌ | ✅ | ❌ | JRuby only, requires grammar JARs |
|
|
88
|
+
| **Prism** | ✅ | ✅ | ✅ | Ruby parsing, stdlib in Ruby 3.4+ |
|
|
89
|
+
| **Psych** | ✅ | ✅ | ✅ | YAML parsing, stdlib |
|
|
90
|
+
| **Citrus** | ✅ | ✅ | ✅ | Pure Ruby, no native dependencies |
|
|
91
|
+
| **Commonmarker** | ✅ | ❌ | ❓ | Rust extension for Markdown |
|
|
92
|
+
| **Markly** | ✅ | ❌ | ❓ | C extension for Markdown |
|
|
93
|
+
|
|
94
|
+
**Legend**: ✅ = Works, ❌ = Does not work, ❓ = Untested
|
|
95
|
+
|
|
96
|
+
**Why some backends don't work on certain platforms**:
|
|
97
|
+
|
|
98
|
+
- **JRuby**: Runs on the JVM; cannot load native C/Rust extensions (`.so` files)
|
|
99
|
+
- **TruffleRuby**: Has C API emulation via Sulong/LLVM, but it doesn't expose all MRI internals that native extensions require (e.g., `RBasic.flags`, `rb_gc_writebarrier`)
|
|
100
|
+
- **FFI on TruffleRuby**: TruffleRuby's FFI implementation doesn't support returning structs by value, which tree-sitter's C API requires
|
|
101
|
+
|
|
78
102
|
**Example implementations** for the gem templating use case:
|
|
79
103
|
|
|
80
|
-
| Gem
|
|
81
|
-
|
|
82
|
-
| [kettle-dev]
|
|
83
|
-
| [kettle-jem]
|
|
104
|
+
| Gem | Purpose | Description |
|
|
105
|
+
|--------------------------|-----------------|-----------------------------------------------|
|
|
106
|
+
| [kettle-dev][kettle-dev] | Gem Development | Gem templating tool using `*-merge` gems |
|
|
107
|
+
| [kettle-jem][kettle-jem] | Gem Templating | Gem template library with smart merge support |
|
|
84
108
|
|
|
85
109
|
[tree_haver]: https://github.com/kettle-rb/tree_haver
|
|
86
110
|
[ast-merge]: https://github.com/kettle-rb/ast-merge
|
|
@@ -100,8 +124,11 @@ The `*-merge` gem family provides intelligent, AST-based merging for various fil
|
|
|
100
124
|
[prism]: https://github.com/ruby/prism
|
|
101
125
|
[psych]: https://github.com/ruby/psych
|
|
102
126
|
[ts-json]: https://github.com/tree-sitter/tree-sitter-json
|
|
127
|
+
[ts-jsonc]: https://gitlab.com/WhyNotHugo/tree-sitter-jsonc
|
|
103
128
|
[ts-bash]: https://github.com/tree-sitter/tree-sitter-bash
|
|
129
|
+
[ts-rbs]: https://github.com/joker1007/tree-sitter-rbs
|
|
104
130
|
[ts-toml]: https://github.com/tree-sitter-grammars/tree-sitter-toml
|
|
131
|
+
[dotenv]: https://github.com/bkeepers/dotenv
|
|
105
132
|
[rbs]: https://github.com/ruby/rbs
|
|
106
133
|
[toml-rb]: https://github.com/emancu/toml-rb
|
|
107
134
|
[markly]: https://github.com/ioquatix/markly
|
|
@@ -110,10 +137,6 @@ The `*-merge` gem family provides intelligent, AST-based merging for various fil
|
|
|
110
137
|
[tree_stump]: https://github.com/joker1007/tree_stump
|
|
111
138
|
[jtreesitter]: https://central.sonatype.com/artifact/io.github.tree-sitter/jtreesitter
|
|
112
139
|
|
|
113
|
-
|
|
114
|
-
[ts-jsonc]: https://gitlab.com/WhyNotHugo/tree-sitter-jsonc
|
|
115
|
-
[dotenv]: https://github.com/bkeepers/dotenv
|
|
116
|
-
|
|
117
140
|
#### Backend Platform Compatibility
|
|
118
141
|
|
|
119
142
|
tree\_haver supports multiple parsing backends, but not all backends work on all Ruby platforms:
|
|
@@ -1033,7 +1056,7 @@ Thanks for RTFM. ☺️
|
|
|
1033
1056
|
[📌gitmoji]: https://gitmoji.dev
|
|
1034
1057
|
[📌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
1058
|
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
1036
|
-
[🧮kloc-img]: https://img.shields.io/badge/KLOC-2.
|
|
1059
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-2.544-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
|
1037
1060
|
[🔐security]: SECURITY.md
|
|
1038
1061
|
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
|
1039
1062
|
[📄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
|
|
144
|
-
#
|
|
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
|
-
#
|
|
152
|
-
|
|
153
|
-
|
|
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.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ast
|
|
4
|
+
module Merge
|
|
5
|
+
# Base class for emitters that convert AST structures back to text.
|
|
6
|
+
# Provides common functionality for tracking indentation, managing output lines,
|
|
7
|
+
# and handling comments.
|
|
8
|
+
#
|
|
9
|
+
# Subclasses implement format-specific emission methods (e.g., emit_pair for JSON,
|
|
10
|
+
# emit_variable_assignment for Bash, etc.)
|
|
11
|
+
#
|
|
12
|
+
# @example Implementing a custom emitter
|
|
13
|
+
# class MyEmitter < Ast::Merge::EmitterBase
|
|
14
|
+
# def emit_my_construct(data)
|
|
15
|
+
# add_comma_if_needed if @needs_separator
|
|
16
|
+
# @lines << "#{current_indent}my_syntax: #{data}"
|
|
17
|
+
# @needs_separator = true
|
|
18
|
+
# end
|
|
19
|
+
# end
|
|
20
|
+
class EmitterBase
|
|
21
|
+
# @return [Array<String>] Output lines
|
|
22
|
+
attr_reader :lines
|
|
23
|
+
|
|
24
|
+
# @return [Integer] Current indentation level
|
|
25
|
+
attr_reader :indent_level
|
|
26
|
+
|
|
27
|
+
# @return [Integer] Spaces per indent level
|
|
28
|
+
attr_reader :indent_size
|
|
29
|
+
|
|
30
|
+
# Initialize a new emitter
|
|
31
|
+
#
|
|
32
|
+
# @param indent_size [Integer] Number of spaces per indent level
|
|
33
|
+
# @param options [Hash] Additional options for subclasses
|
|
34
|
+
def initialize(indent_size: 2, **options)
|
|
35
|
+
@lines = []
|
|
36
|
+
@indent_level = 0
|
|
37
|
+
@indent_size = indent_size
|
|
38
|
+
initialize_subclass_state(**options)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Hook for subclasses to initialize their own state
|
|
42
|
+
# @param options [Hash] Additional options
|
|
43
|
+
def initialize_subclass_state(**options)
|
|
44
|
+
# Override in subclasses if needed
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Emit a blank line
|
|
48
|
+
def emit_blank_line
|
|
49
|
+
@lines << ""
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Emit leading comments from CommentTracker
|
|
53
|
+
#
|
|
54
|
+
# @param comments [Array<Hash>] Comment hashes with :text, :indent, etc.
|
|
55
|
+
def emit_leading_comments(comments)
|
|
56
|
+
comments.each do |comment|
|
|
57
|
+
emit_tracked_comment(comment)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Emit a comment from CommentTracker hash
|
|
62
|
+
# Subclasses should override this to handle format-specific comment syntax
|
|
63
|
+
#
|
|
64
|
+
# @param comment [Hash] Comment hash with :text, :indent, :block, etc.
|
|
65
|
+
def emit_tracked_comment(comment)
|
|
66
|
+
raise NotImplementedError, "Subclasses must implement emit_tracked_comment"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Emit raw lines as-is (for preserving exact formatting)
|
|
70
|
+
#
|
|
71
|
+
# @param raw_lines [Array<String>] Lines to emit without modification
|
|
72
|
+
def emit_raw_lines(raw_lines)
|
|
73
|
+
raw_lines.each { |line| @lines << line.chomp }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Get the output as a single string
|
|
77
|
+
# Subclasses may override to customize output format (e.g., to_json, to_yaml)
|
|
78
|
+
#
|
|
79
|
+
# @return [String]
|
|
80
|
+
def to_s
|
|
81
|
+
content = @lines.join("\n")
|
|
82
|
+
content += "\n" unless content.empty? || content.end_with?("\n")
|
|
83
|
+
content
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Clear the emitter state
|
|
87
|
+
def clear
|
|
88
|
+
@lines = []
|
|
89
|
+
@indent_level = 0
|
|
90
|
+
clear_subclass_state
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Hook for subclasses to clear their own state
|
|
94
|
+
def clear_subclass_state
|
|
95
|
+
# Override in subclasses if needed
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Increase indentation level
|
|
99
|
+
def indent
|
|
100
|
+
@indent_level += 1
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Decrease indentation level
|
|
104
|
+
def dedent
|
|
105
|
+
@indent_level -= 1 if @indent_level > 0
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
protected
|
|
109
|
+
|
|
110
|
+
# Get the current indentation string
|
|
111
|
+
# @return [String]
|
|
112
|
+
def current_indent
|
|
113
|
+
" " * (@indent_level * @indent_size)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Add a line with current indentation
|
|
117
|
+
# @param content [String] Line content
|
|
118
|
+
def add_indented_line(content)
|
|
119
|
+
@lines << "#{current_indent}#{content}"
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -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
|
|
@@ -354,6 +354,15 @@ module Ast
|
|
|
354
354
|
true
|
|
355
355
|
end
|
|
356
356
|
|
|
357
|
+
# Node type for merge classification
|
|
358
|
+
# @return [Symbol] :freeze_block
|
|
359
|
+
def merge_type
|
|
360
|
+
:freeze_block
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
# Alias for compatibility
|
|
364
|
+
alias_method :type, :merge_type
|
|
365
|
+
|
|
357
366
|
# Returns a stable signature for this freeze block.
|
|
358
367
|
# Override in subclasses for file-type-specific normalization.
|
|
359
368
|
# @return [Array] Signature array
|
|
@@ -270,17 +270,9 @@ module Ast
|
|
|
270
270
|
|
|
271
271
|
# @return [String] Node text content
|
|
272
272
|
def text
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
|
@@ -13,6 +13,27 @@ module Ast
|
|
|
13
13
|
# The `merge_type` attribute can then be used by other merge tools like
|
|
14
14
|
# `signature_generator`, `match_refiner`, and per-node-type `preference` settings.
|
|
15
15
|
#
|
|
16
|
+
# ## Important: Two Uses of merge_type
|
|
17
|
+
#
|
|
18
|
+
# The `merge_type` method serves two complementary purposes in the codebase:
|
|
19
|
+
#
|
|
20
|
+
# ### 1. NodeTyping-specific (gated by typed_node?)
|
|
21
|
+
# Wrapped nodes (Wrapper/FrozenWrapper) with custom type tagging for:
|
|
22
|
+
# - Per-node-type preferences (e.g., `:lint_gem` → `:template`)
|
|
23
|
+
# - Match refinement based on custom categories
|
|
24
|
+
# - Only applies when `typed_node?` returns true
|
|
25
|
+
# - Accessed via `NodeTyping.merge_type_for(node)`
|
|
26
|
+
#
|
|
27
|
+
# ### 2. General node classification (any node)
|
|
28
|
+
# Any node can implement `merge_type` for category identification:
|
|
29
|
+
# - FreezeNodeBase has `merge_type` → `:freeze_block`
|
|
30
|
+
# - GapLineNode has `merge_type` → `:gap_line`
|
|
31
|
+
# - Used by systems like MarkdownStructure for structural spacing rules
|
|
32
|
+
# - These nodes are NOT "typed nodes" (typed_node? returns false)
|
|
33
|
+
#
|
|
34
|
+
# The key distinction: **typed_node? is the gate** for NodeTyping wrapper
|
|
35
|
+
# semantics. A node can have `merge_type` without being a NodeTyping wrapper.
|
|
36
|
+
#
|
|
16
37
|
# @example Basic node typing for different gem types
|
|
17
38
|
# node_typing = {
|
|
18
39
|
# CallNode: ->(node) {
|
|
@@ -139,7 +160,10 @@ module Ast
|
|
|
139
160
|
callable = find_typing_callable(typing_config, type_key, node)
|
|
140
161
|
return node unless callable
|
|
141
162
|
|
|
142
|
-
# Call the typing callable with the node
|
|
163
|
+
# Call the typing callable with the node.
|
|
164
|
+
# NOTE: For TreeHaver-based backends, the node already has a unified API
|
|
165
|
+
# with #text, #type, #source_position methods. For other backends, they
|
|
166
|
+
# must conform to the same API (either via TreeHaver or equivalent adapter).
|
|
143
167
|
callable.call(node)
|
|
144
168
|
end
|
|
145
169
|
|
|
@@ -2,36 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
module Ast
|
|
4
4
|
module Merge
|
|
5
|
-
#
|
|
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,
|
|
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
|
|
13
|
-
#
|
|
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
|
-
# @
|
|
16
|
-
#
|
|
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
|
-
|
|
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
|
-
#
|
|
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(
|
|
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
|
|
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
|
|
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,
|
|
243
|
+
message: "Section not found, skipping",
|
|
223
244
|
)
|
|
224
245
|
end
|
|
225
246
|
end
|
|
226
247
|
|
|
227
|
-
def perform_section_merge(
|
|
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
|
|
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
|
|
148
|
-
merger =
|
|
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
|
|
data/lib/ast/merge/version.rb
CHANGED
data/lib/ast/merge.rb
CHANGED
|
@@ -142,6 +142,7 @@ module Ast
|
|
|
142
142
|
autoload :ConflictResolverBase, "ast/merge/conflict_resolver_base"
|
|
143
143
|
autoload :ContentMatchRefiner, "ast/merge/content_match_refiner"
|
|
144
144
|
autoload :DebugLogger, "ast/merge/debug_logger"
|
|
145
|
+
autoload :EmitterBase, "ast/merge/emitter_base"
|
|
145
146
|
autoload :FileAnalyzable, "ast/merge/file_analyzable"
|
|
146
147
|
autoload :Freezable, "ast/merge/freezable"
|
|
147
148
|
autoload :FreezeNodeBase, "ast/merge/freeze_node_base"
|
|
@@ -154,7 +155,7 @@ module Ast
|
|
|
154
155
|
autoload :NavigableStatement, "ast/merge/navigable_statement"
|
|
155
156
|
autoload :NodeTyping, "ast/merge/node_typing"
|
|
156
157
|
autoload :NodeWrapperBase, "ast/merge/node_wrapper_base"
|
|
157
|
-
autoload :
|
|
158
|
+
autoload :PartialTemplateMergerBase, "ast/merge/partial_template_merger_base"
|
|
158
159
|
autoload :SectionTyping, "ast/merge/section_typing"
|
|
159
160
|
autoload :SmartMergerBase, "ast/merge/smart_merger_base"
|
|
160
161
|
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:
|
|
4
|
+
version: 3.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
@@ -63,20 +63,20 @@ dependencies:
|
|
|
63
63
|
requirements:
|
|
64
64
|
- - "~>"
|
|
65
65
|
- !ruby/object:Gem::Version
|
|
66
|
-
version: '
|
|
66
|
+
version: '4.0'
|
|
67
67
|
- - ">="
|
|
68
68
|
- !ruby/object:Gem::Version
|
|
69
|
-
version:
|
|
69
|
+
version: 4.0.0
|
|
70
70
|
type: :runtime
|
|
71
71
|
prerelease: false
|
|
72
72
|
version_requirements: !ruby/object:Gem::Requirement
|
|
73
73
|
requirements:
|
|
74
74
|
- - "~>"
|
|
75
75
|
- !ruby/object:Gem::Version
|
|
76
|
-
version: '
|
|
76
|
+
version: '4.0'
|
|
77
77
|
- - ">="
|
|
78
78
|
- !ruby/object:Gem::Version
|
|
79
|
-
version:
|
|
79
|
+
version: 4.0.0
|
|
80
80
|
- !ruby/object:Gem::Dependency
|
|
81
81
|
name: kettle-dev
|
|
82
82
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -309,6 +309,7 @@ files:
|
|
|
309
309
|
- lib/ast/merge/detector/mergeable.rb
|
|
310
310
|
- lib/ast/merge/detector/toml_frontmatter.rb
|
|
311
311
|
- lib/ast/merge/detector/yaml_frontmatter.rb
|
|
312
|
+
- lib/ast/merge/emitter_base.rb
|
|
312
313
|
- lib/ast/merge/file_analyzable.rb
|
|
313
314
|
- lib/ast/merge/freezable.rb
|
|
314
315
|
- lib/ast/merge/freeze_node_base.rb
|
|
@@ -322,7 +323,7 @@ files:
|
|
|
322
323
|
- lib/ast/merge/node_typing/normalizer.rb
|
|
323
324
|
- lib/ast/merge/node_typing/wrapper.rb
|
|
324
325
|
- lib/ast/merge/node_wrapper_base.rb
|
|
325
|
-
- lib/ast/merge/
|
|
326
|
+
- lib/ast/merge/partial_template_merger_base.rb
|
|
326
327
|
- lib/ast/merge/recipe.rb
|
|
327
328
|
- lib/ast/merge/recipe/config.rb
|
|
328
329
|
- lib/ast/merge/recipe/preset.rb
|
|
@@ -356,10 +357,10 @@ licenses:
|
|
|
356
357
|
- MIT
|
|
357
358
|
metadata:
|
|
358
359
|
homepage_uri: https://ast-merge.galtzo.com/
|
|
359
|
-
source_code_uri: https://github.com/kettle-rb/ast-merge/tree/
|
|
360
|
-
changelog_uri: https://github.com/kettle-rb/ast-merge/blob/
|
|
360
|
+
source_code_uri: https://github.com/kettle-rb/ast-merge/tree/v3.1.0
|
|
361
|
+
changelog_uri: https://github.com/kettle-rb/ast-merge/blob/v3.1.0/CHANGELOG.md
|
|
361
362
|
bug_tracker_uri: https://github.com/kettle-rb/ast-merge/issues
|
|
362
|
-
documentation_uri: https://www.rubydoc.info/gems/ast-merge/
|
|
363
|
+
documentation_uri: https://www.rubydoc.info/gems/ast-merge/3.1.0
|
|
363
364
|
funding_uri: https://github.com/sponsors/pboling
|
|
364
365
|
wiki_uri: https://github.com/kettle-rb/ast-merge/wiki
|
|
365
366
|
news_uri: https://www.railsbling.com/tags/ast-merge
|
metadata.gz.sig
CHANGED
|
Binary file
|