ast-merge 1.1.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +198 -7
- data/README.md +208 -39
- data/exe/ast-merge-recipe +366 -0
- 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 +2 -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/merge_result_base.rb +4 -1
- 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/smart_merger_base.rb +86 -3
- 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 +60 -16
- metadata.gz.sig +0 -0
- data/lib/ast/merge/fenced_code_block_detector.rb +0 -313
- 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 -88
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 77a90e163c91b44ac5b425a36dc12643f44dcbb67512f1687aa356a25b88aad8
|
|
4
|
+
data.tar.gz: 13791029a6b5c52e02d679720342aa291221d6b57c2b952a9affe7096630b639
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4cd7681688742d99e088033bc186b576834ea36f3b6a75184761cc0ec762dabe6fdd880829fa97df13dfea7867acd879d1cde372a3b03f7206a84d94770dec32
|
|
7
|
+
data.tar.gz: 0523adc4e7a51ff72efe423bfd01d42f9829c1371c1c92e65bc3eea492f52e1096992ed56766c461aa88b037db328758a4b09c7ee0d74c548457449b49ba2b9b
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGELOG.md
CHANGED
|
@@ -20,6 +20,197 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
20
20
|
|
|
21
21
|
### Added
|
|
22
22
|
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
### Deprecated
|
|
26
|
+
|
|
27
|
+
### Removed
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
### Security
|
|
32
|
+
|
|
33
|
+
## [2.0.1] - 2025-12-29
|
|
34
|
+
|
|
35
|
+
- TAG: [v2.0.1][2.0.1t]
|
|
36
|
+
- COVERAGE: 88.47% -- 2894/3271 lines in 53 files
|
|
37
|
+
- BRANCH COVERAGE: 67.83% -- 698/1029 branches in 53 files
|
|
38
|
+
- 98.82% documented
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- Upgraded dependencies
|
|
43
|
+
- yard-fence v0.8.1
|
|
44
|
+
- tree_haver v3.1.2
|
|
45
|
+
- kettle-test v1.0.7
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- Documentation cleanup (via yard-fence)
|
|
50
|
+
|
|
51
|
+
## [2.0.0] - 2025-12-28
|
|
52
|
+
|
|
53
|
+
- TAG: [v2.0.0][2.0.0t]
|
|
54
|
+
- COVERAGE: 88.47% -- 2894/3271 lines in 53 files
|
|
55
|
+
- BRANCH COVERAGE: 67.83% -- 698/1029 branches in 53 files
|
|
56
|
+
- 98.82% documented
|
|
57
|
+
|
|
58
|
+
### Added
|
|
59
|
+
|
|
60
|
+
- **RSpec Dependency Tags**: Conditional test execution based on available merge gems
|
|
61
|
+
- New `lib/ast/merge/rspec/dependency_tags.rb` provides automatic test filtering
|
|
62
|
+
- Tags for all merge gems: `:markly_merge`, `:prism_merge`, `:json_merge`, `:toml_merge`, etc.
|
|
63
|
+
- Composite tag `:any_markdown_merge` for tests that work with any markdown merger
|
|
64
|
+
- Negated tags (e.g., `:not_prism_merge`) for testing fallback behavior
|
|
65
|
+
- `AST_MERGE_DEBUG=1` environment variable prints dependency summary
|
|
66
|
+
- Eliminates need for `require` statements inside spec files
|
|
67
|
+
- See [lib/ast/merge/rspec/README.md](lib/ast/merge/rspec/README.md) for full documentation
|
|
68
|
+
|
|
69
|
+
- **Recipe::Preset**: Base class for merge configuration presets
|
|
70
|
+
- Provides merge configuration (signature generators, node typing, preferences) without requiring template files
|
|
71
|
+
- `Recipe::Config` now inherits from `Preset`, adding template/target handling
|
|
72
|
+
- `to_h` method converts preset to SmartMerger-compatible options hash
|
|
73
|
+
- Enables kettle-jem and other libraries to define reusable merge presets
|
|
74
|
+
- Supports script loading for signature generators and node typing via `ScriptLoader`
|
|
75
|
+
|
|
76
|
+
- **exe/ast-merge-recipe**: Shipped executable for running merge recipes
|
|
77
|
+
- Uses `bundler/inline` for dependency management
|
|
78
|
+
- Supports `--dry-run`, `--verbose`, `--parser`, `--base-dir` options
|
|
79
|
+
- Uses `optparse` for proper option parsing
|
|
80
|
+
- Loads merge gems from recipe YAML `merge_gems` section
|
|
81
|
+
- Development mode via `KETTLE_RB_DEV=true` or `--dev-root` option
|
|
82
|
+
- All gems sourced from gem.coop
|
|
83
|
+
|
|
84
|
+
- **PartialTemplateMerger**: Section-based merging for partial templates
|
|
85
|
+
- Find and merge specific sections in destination documents
|
|
86
|
+
- Support for both `replace_mode` (full replacement) and merge mode (intelligent merging)
|
|
87
|
+
- Configurable anchor and boundary matchers for section detection
|
|
88
|
+
- Custom `signature_generator` and `node_typing` support for advanced matching
|
|
89
|
+
- `when_missing` behavior: `:skip`, `:append`, `:prepend`
|
|
90
|
+
|
|
91
|
+
- **Recipe**: YAML-based recipe system for defining merge operations
|
|
92
|
+
- Load recipes from YAML files with `Recipe.load(path)`
|
|
93
|
+
- Define template, targets, injection point, merge preferences
|
|
94
|
+
- Support for `when_missing: skip|add|error` behavior
|
|
95
|
+
- Support for `replace_mode` option
|
|
96
|
+
- Automatic path resolution relative to recipe file location
|
|
97
|
+
|
|
98
|
+
- **RecipeRunner**: Execute recipes against multiple target files
|
|
99
|
+
- Uses PartialTemplateMerger for section-based merging
|
|
100
|
+
- Dry-run mode with `--dry-run` flag
|
|
101
|
+
- Results tracking with status, stats, and error reporting
|
|
102
|
+
- TableTennis integration for formatted output
|
|
103
|
+
- Support for different parsers (markly, commonmarker, prism, psych)
|
|
104
|
+
|
|
105
|
+
- **RecipeScriptLoader**: Load Ruby scripts referenced by recipes
|
|
106
|
+
- Convention: scripts in folder matching recipe basename (e.g., `my_recipe/` for `my_recipe.yml`)
|
|
107
|
+
- Support for inline lambda expressions in YAML
|
|
108
|
+
- Script caching for performance
|
|
109
|
+
- Validation that scripts return callable objects
|
|
110
|
+
|
|
111
|
+
- **bin/ast-merge-recipe**: CLI for running merge recipes
|
|
112
|
+
- `bin/ast-merge-recipe RECIPE_FILE [--dry-run] [--verbose]`
|
|
113
|
+
- Color-coded output with status symbols
|
|
114
|
+
- Summary table with counts
|
|
115
|
+
|
|
116
|
+
- **NavigableStatement**: New wrapper class for uniform node navigation (language-agnostic)
|
|
117
|
+
- Provides flat list navigation (`next`, `previous`, `index`) for all nodes
|
|
118
|
+
- Tree depth calculation with `tree_depth` method
|
|
119
|
+
- `same_or_shallower_than?` for level-based boundary detection
|
|
120
|
+
- Language-agnostic section boundaries using tree hierarchy
|
|
121
|
+
- Provides tree navigation (`tree_parent`, `tree_next`, `tree_previous`) for parser-backed nodes
|
|
122
|
+
- `synthetic?` method to detect nodes without tree navigation (GapLineNode, LinkDefinitionNode, etc.)
|
|
123
|
+
- `type?` and `text_matches?` helpers for node matching
|
|
124
|
+
- `node_attribute` for accessing parser-specific attributes with fallbacks
|
|
125
|
+
- `each_following` and `take_until` for sequential traversal
|
|
126
|
+
- `find_matching` and `find_first` class methods for querying statement lists
|
|
127
|
+
- `build_list` class method to create linked statement lists from raw statements
|
|
128
|
+
|
|
129
|
+
- **InjectionPoint**: New class for defining where content can be injected (language-agnostic)
|
|
130
|
+
- Supports positions: `:before`, `:after`, `:first_child`, `:last_child`, `:replace`
|
|
131
|
+
- Optional boundary for replacement ranges
|
|
132
|
+
- `replacement?`, `child_injection?`, `sibling_injection?` predicates
|
|
133
|
+
- `replaced_statements` to get all statements in a replacement range
|
|
134
|
+
|
|
135
|
+
- **InjectionPointFinder**: New class for finding injection points by matching criteria
|
|
136
|
+
- `find` to locate a single injection point by type/text pattern
|
|
137
|
+
- `find_all` to locate all matching injection points
|
|
138
|
+
- Works with any `*-merge` gem (prism-merge, markly-merge, psych-merge, etc.)
|
|
139
|
+
|
|
140
|
+
- **SmartMergerBase**: `add_template_only_nodes` now accepts a callable filter
|
|
141
|
+
- Boolean `true`/`false` still works as before (add all or none)
|
|
142
|
+
- Callable (Proc/Lambda) receives `(node, entry)` and returns truthy to add the node
|
|
143
|
+
- Enables selective addition of template-only nodes based on signature, type, or content
|
|
144
|
+
- Example use case: Add missing link reference definitions while skipping other template content
|
|
145
|
+
- Entry hash includes `:template_node`, `:signature`, `:template_index` for filtering decisions
|
|
146
|
+
|
|
147
|
+
- **ContentMatchRefiner**: New match refiner for fuzzy text content matching
|
|
148
|
+
- Uses Levenshtein distance to pair nodes with similar (but not identical) text
|
|
149
|
+
- Configurable scoring weights for content similarity, length, and position
|
|
150
|
+
- Custom content extractor support for parser-specific text extraction
|
|
151
|
+
- Node type filtering to limit which types are processed
|
|
152
|
+
- Can be combined with other refiners (e.g., TableMatchRefiner)
|
|
153
|
+
- Useful for matching paragraphs, headings, comments with minor edits
|
|
154
|
+
|
|
155
|
+
- **SmartMergerBase**: Added validity check after FileAnalysis creation
|
|
156
|
+
- Checks `valid?` after creating FileAnalysis and raises appropriate parse error if invalid
|
|
157
|
+
- Catches silent failures like grammar not available or parse errors
|
|
158
|
+
- Documented the FileAnalysis error handling pattern for all *-merge gems
|
|
159
|
+
|
|
160
|
+
- **SmartMergerBase**: Added explicit `node_typing` parameter
|
|
161
|
+
- All child SmartMergers were already using `node_typing` via `**format_options`
|
|
162
|
+
- Now explicitly documented and accessible via `attr_reader :node_typing`
|
|
163
|
+
- Validates node_typing configuration via `NodeTyping.validate!` if provided
|
|
164
|
+
- Enables per-node-type merge preferences across all `*-merge` gems
|
|
165
|
+
|
|
166
|
+
- **ConflictResolverBase**: Added `match_refiner` parameter and `**options` for forward compatibility
|
|
167
|
+
- All batch-strategy resolvers were storing `match_refiner` locally
|
|
168
|
+
- Now explicitly accepted in base class with `attr_reader :match_refiner`
|
|
169
|
+
- Added `**options` catch-all for future parameters without breaking child classes
|
|
170
|
+
|
|
171
|
+
- **MergeResultBase**: Added `**options` for forward compatibility
|
|
172
|
+
- Allows subclasses to accept new parameters without modification
|
|
173
|
+
- Maintains backward compatibility with existing no-arg and keyword-arg constructors
|
|
174
|
+
|
|
175
|
+
- **RBS Signatures**: Added comprehensive type signatures for base classes
|
|
176
|
+
- `SmartMergerBase` with all standard options and abstract method declarations
|
|
177
|
+
- `ConflictResolverBase` with strategy-based resolution methods
|
|
178
|
+
- `MergeResultBase` with unified constructor and decision tracking
|
|
179
|
+
- `MatchRefinerBase` with similarity computation interface
|
|
180
|
+
- `RegionMergeable` module for nested content merging
|
|
181
|
+
- `NodeTyping` module with `Wrapper` class for typed nodes
|
|
182
|
+
- Type aliases: `node_typing_callable`, `node_typing_hash`, `preference_type`
|
|
183
|
+
|
|
184
|
+
- **Documentation**: Updated README with comprehensive base class documentation
|
|
185
|
+
- Standard options table with all `SmartMergerBase` parameters
|
|
186
|
+
- Forward compatibility section explaining the `**options` pattern
|
|
187
|
+
- Complete "Creating a New Merge Gem" example with all base classes
|
|
188
|
+
- Base Classes Reference table
|
|
189
|
+
|
|
190
|
+
### Changed
|
|
191
|
+
|
|
192
|
+
- **BREAKING - Namespace Reorganization**: Major restructuring for better organization
|
|
193
|
+
- `Ast::Merge::Region` → `Ast::Merge::Detector::Region` (Struct moved into Detector namespace)
|
|
194
|
+
- `Ast::Merge::RegionDetectorBase` → `Ast::Merge::Detector::Base`
|
|
195
|
+
- `Ast::Merge::RegionMergeable` → `Ast::Merge::Detector::Mergeable`
|
|
196
|
+
- `Ast::Merge::FencedCodeBlockDetector` → `Ast::Merge::Detector::FencedCodeBlock`
|
|
197
|
+
- `Ast::Merge::YamlFrontmatterDetector` → `Ast::Merge::Detector::YamlFrontmatter`
|
|
198
|
+
- `Ast::Merge::TomlFrontmatterDetector` → `Ast::Merge::Detector::TomlFrontmatter`
|
|
199
|
+
- `Ast::Merge::Recipe` class → `Ast::Merge::Recipe::Config`
|
|
200
|
+
- `Ast::Merge::RecipeRunner` → `Ast::Merge::Recipe::Runner`
|
|
201
|
+
- `Ast::Merge::RecipeScriptLoader` → `Ast::Merge::Recipe::ScriptLoader`
|
|
202
|
+
- `Ast::Merge::RegionMergeable::RegionConfig` → `Ast::Merge::Detector::Mergeable::Config`
|
|
203
|
+
- `Ast::Merge::RegionMergeable::ExtractedRegion` → `Ast::Merge::Detector::Mergeable::ExtractedRegion`
|
|
204
|
+
|
|
205
|
+
## [1.1.0] - 2025-12-18
|
|
206
|
+
|
|
207
|
+
- TAG: [v1.1.0][1.1.0t]
|
|
208
|
+
- COVERAGE: 95.16% -- 2338/2457 lines in 44 files
|
|
209
|
+
- BRANCH COVERAGE: 82.59% -- 517/626 branches in 44 files
|
|
210
|
+
- 98.45% documented
|
|
211
|
+
|
|
212
|
+
### Added
|
|
213
|
+
|
|
23
214
|
- **tree_haver Integration**: Major architectural enhancement
|
|
24
215
|
- Added `tree_haver` (~> 3.1) as a runtime dependency
|
|
25
216
|
- `Ast::Merge::AstNode` now implements the TreeHaver::Node protocol for compatibility with tree_haver-based merge operations
|
|
@@ -52,10 +243,6 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
52
243
|
- markdown-merge description updated to "**Foundation**: Shared base for Markdown mergers with inner code block merging"
|
|
53
244
|
- **Configuration Documentation**: Enhanced backend selection documentation
|
|
54
245
|
|
|
55
|
-
### Deprecated
|
|
56
|
-
|
|
57
|
-
### Removed
|
|
58
|
-
|
|
59
246
|
### Fixed
|
|
60
247
|
|
|
61
248
|
- Fixed gemspec and Appraisals alignment with tree_haver requirements
|
|
@@ -63,8 +250,6 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
63
250
|
- Fixed badge rendering in documentation
|
|
64
251
|
- Fixed README structure issues (removed H3 duplicates, standardized gem family tables)
|
|
65
252
|
|
|
66
|
-
### Security
|
|
67
|
-
|
|
68
253
|
## [1.0.0] - 2025-12-12
|
|
69
254
|
|
|
70
255
|
- TAG: [v1.0.0][1.0.0t]
|
|
@@ -76,6 +261,12 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
76
261
|
|
|
77
262
|
- Initial release
|
|
78
263
|
|
|
79
|
-
[Unreleased]: https://github.com/kettle-rb/ast-merge/compare/
|
|
264
|
+
[Unreleased]: https://github.com/kettle-rb/ast-merge/compare/v2.0.1...HEAD
|
|
265
|
+
[2.0.1]: https://github.com/kettle-rb/ast-merge/compare/v2.0.0...v2.0.1
|
|
266
|
+
[2.0.1t]: https://github.com/kettle-rb/ast-merge/releases/tag/v2.0.1
|
|
267
|
+
[2.0.0]: https://github.com/kettle-rb/ast-merge/compare/v1.1.0...v2.0.0
|
|
268
|
+
[2.0.0t]: https://github.com/kettle-rb/ast-merge/releases/tag/v2.0.0
|
|
269
|
+
[1.1.0]: https://github.com/kettle-rb/ast-merge/compare/v1.0.0...v1.1.0
|
|
270
|
+
[1.1.0t]: https://github.com/kettle-rb/ast-merge/releases/tag/v1.1.0
|
|
80
271
|
[1.0.0]: https://github.com/kettle-rb/ast-merge/compare/a63a4858cb229530c1706925bb209546695e8b3a...v1.0.0
|
|
81
272
|
[1.0.0t]: https://github.com/kettle-rb/ast-merge/tags/v1.0.0
|
data/README.md
CHANGED
|
@@ -60,21 +60,21 @@ Ast::Merge is **not typically used directly** - instead, use one of the format-s
|
|
|
60
60
|
|
|
61
61
|
The `*-merge` gem family provides intelligent, AST-based merging for various file formats. At the foundation is [tree_haver][tree_haver], which provides a unified cross-Ruby parsing API that works seamlessly across MRI, JRuby, and TruffleRuby.
|
|
62
62
|
|
|
63
|
-
| Gem
|
|
64
|
-
|
|
65
|
-
| [tree_haver][tree_haver]
|
|
66
|
-
| [ast-merge][ast-merge]
|
|
67
|
-
| [prism-merge][prism-merge]
|
|
68
|
-
| [psych-merge][psych-merge]
|
|
69
|
-
| [json-merge][json-merge]
|
|
70
|
-
| [jsonc-merge][jsonc-merge]
|
|
71
|
-
| [bash-merge][bash-merge]
|
|
72
|
-
| [rbs-merge][rbs-merge]
|
|
73
|
-
| [dotenv-merge][dotenv-merge]
|
|
74
|
-
| [toml-merge][toml-merge]
|
|
75
|
-
| [markdown-merge][markdown-merge]
|
|
76
|
-
| [markly-merge][markly-merge]
|
|
77
|
-
| [commonmarker-merge][commonmarker-merge] | Markdown | [Commonmarker][commonmarker] (via tree_haver)
|
|
63
|
+
| Gem | Format | Parser Backend(s) | Description |
|
|
64
|
+
|------------------------------------------|----------|-----------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------|
|
|
65
|
+
| [tree_haver][tree_haver] | Multi | MRI C, Rust, FFI, Java, Prism, Psych, Commonmarker, Markly, Citrus | **Foundation**: Cross-Ruby adapter for parsing libraries (like Faraday for HTTP) |
|
|
66
|
+
| [ast-merge][ast-merge] | Text | internal | **Infrastructure**: Shared base classes and merge logic for all `*-merge` gems |
|
|
67
|
+
| [prism-merge][prism-merge] | Ruby | [Prism][prism] | Smart merge for Ruby source files |
|
|
68
|
+
| [psych-merge][psych-merge] | YAML | [Psych][psych] | Smart merge for YAML files |
|
|
69
|
+
| [json-merge][json-merge] | JSON | [tree-sitter-json][ts-json] (via tree_haver) | Smart merge for JSON files |
|
|
70
|
+
| [jsonc-merge][jsonc-merge] | JSONC | [tree-sitter-jsonc][ts-jsonc] (via tree_haver) | ⚠️ Proof of concept; Smart merge for JSON with Comments |
|
|
71
|
+
| [bash-merge][bash-merge] | Bash | [tree-sitter-bash][ts-bash] (via tree_haver) | Smart merge for Bash scripts |
|
|
72
|
+
| [rbs-merge][rbs-merge] | RBS | [RBS][rbs] | Smart merge for Ruby type signatures |
|
|
73
|
+
| [dotenv-merge][dotenv-merge] | Dotenv | internal | Smart merge for `.env` files |
|
|
74
|
+
| [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 |
|
|
75
|
+
| [markdown-merge][markdown-merge] | Markdown | [Commonmarker][commonmarker] / [Markly][markly] (via tree_haver) | **Foundation**: Shared base for Markdown mergers with inner code block merging |
|
|
76
|
+
| [markly-merge][markly-merge] | Markdown | [Markly][markly] (via tree_haver) | Smart merge for Markdown (CommonMark via cmark-gfm C) |
|
|
77
|
+
| [commonmarker-merge][commonmarker-merge] | Markdown | [Commonmarker][commonmarker] (via tree_haver) | Smart merge for Markdown (CommonMark via comrak Rust) |
|
|
78
78
|
|
|
79
79
|
**Example implementations** for the gem templating use case:
|
|
80
80
|
|
|
@@ -101,11 +101,10 @@ The `*-merge` gem family provides intelligent, AST-based merging for various fil
|
|
|
101
101
|
[prism]: https://github.com/ruby/prism
|
|
102
102
|
[psych]: https://github.com/ruby/psych
|
|
103
103
|
[ts-json]: https://github.com/tree-sitter/tree-sitter-json
|
|
104
|
-
[ts-jsonc]: https://gitlab.com/WhyNotHugo/tree-sitter-jsonc
|
|
105
104
|
[ts-bash]: https://github.com/tree-sitter/tree-sitter-bash
|
|
106
105
|
[ts-toml]: https://github.com/tree-sitter-grammars/tree-sitter-toml
|
|
107
106
|
[rbs]: https://github.com/ruby/rbs
|
|
108
|
-
[
|
|
107
|
+
[toml-rb]: https://github.com/emancu/toml-rb
|
|
109
108
|
[markly]: https://github.com/ioquatix/markly
|
|
110
109
|
[commonmarker]: https://github.com/gjtorikian/commonmarker
|
|
111
110
|
|
|
@@ -143,34 +142,168 @@ require "ast/merge"
|
|
|
143
142
|
|
|
144
143
|
module MyFormat
|
|
145
144
|
module Merge
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
# Inherit from base classes and pass **options for forward compatibility
|
|
146
|
+
|
|
147
|
+
class SmartMerger < Ast::Merge::SmartMergerBase
|
|
148
|
+
DEFAULT_FREEZE_TOKEN = "myformat-merge"
|
|
149
|
+
|
|
150
|
+
def initialize(template, dest, my_custom_option: nil, **options)
|
|
151
|
+
@my_custom_option = my_custom_option
|
|
152
|
+
super(template, dest, **options)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
protected
|
|
156
|
+
|
|
157
|
+
def analysis_class
|
|
158
|
+
FileAnalysis
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def default_freeze_token
|
|
162
|
+
DEFAULT_FREEZE_TOKEN
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def perform_merge
|
|
166
|
+
# Implement format-specific merge logic
|
|
167
|
+
# Returns a MergeResult
|
|
168
|
+
end
|
|
148
169
|
end
|
|
149
|
-
|
|
150
|
-
class
|
|
151
|
-
|
|
170
|
+
|
|
171
|
+
class FileAnalysis
|
|
172
|
+
include Ast::Merge::FileAnalyzable
|
|
173
|
+
|
|
174
|
+
def initialize(source, freeze_token: nil, signature_generator: nil, **options)
|
|
175
|
+
@source = source
|
|
176
|
+
@freeze_token = freeze_token
|
|
177
|
+
@signature_generator = signature_generator
|
|
178
|
+
# Process source...
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def compute_node_signature(node)
|
|
182
|
+
# Return signature array for node matching
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
class ConflictResolver < Ast::Merge::ConflictResolverBase
|
|
187
|
+
def initialize(template_analysis, dest_analysis, preference: :destination,
|
|
188
|
+
add_template_only_nodes: false, match_refiner: nil, **options)
|
|
189
|
+
super(
|
|
190
|
+
strategy: :batch, # or :node, :boundary
|
|
191
|
+
preference: preference,
|
|
192
|
+
template_analysis: template_analysis,
|
|
193
|
+
dest_analysis: dest_analysis,
|
|
194
|
+
add_template_only_nodes: add_template_only_nodes,
|
|
195
|
+
match_refiner: match_refiner,
|
|
196
|
+
**options
|
|
197
|
+
)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
protected
|
|
201
|
+
|
|
202
|
+
def resolve_batch(result)
|
|
203
|
+
# Implement batch resolution logic
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
class MergeResult < Ast::Merge::MergeResultBase
|
|
208
|
+
def initialize(**options)
|
|
209
|
+
super(**options)
|
|
210
|
+
@statistics = { merged_count: 0 }
|
|
211
|
+
end
|
|
212
|
+
|
|
152
213
|
def to_my_format
|
|
153
214
|
to_s
|
|
154
215
|
end
|
|
155
216
|
end
|
|
217
|
+
|
|
218
|
+
class MatchRefiner < Ast::Merge::MatchRefinerBase
|
|
219
|
+
def initialize(threshold: 0.7, node_types: nil, **options)
|
|
220
|
+
super(threshold: threshold, node_types: node_types, **options)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def similarity(template_node, dest_node)
|
|
224
|
+
# Return similarity score between 0.0 and 1.0
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
```
|
|
156
230
|
|
|
157
|
-
|
|
158
|
-
include Ast::Merge::FileAnalysisBase
|
|
231
|
+
### Base Classes Reference
|
|
159
232
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
233
|
+
| Base Class | Purpose | Key Methods to Implement |
|
|
234
|
+
|------------|---------|-------------------------|
|
|
235
|
+
| `SmartMergerBase` | Main merge orchestration | `analysis_class`, `perform_merge` |
|
|
236
|
+
| `ConflictResolverBase` | Resolve node conflicts | `resolve_batch` or `resolve_node_pair` |
|
|
237
|
+
| `MergeResultBase` | Track merge results | `to_s`, format-specific output |
|
|
238
|
+
| `MatchRefinerBase` | Fuzzy node matching | `similarity` |
|
|
239
|
+
| `ContentMatchRefiner` | Text content fuzzy matching | Ready to use |
|
|
240
|
+
| `FileAnalyzable` | File parsing/analysis | `compute_node_signature` |
|
|
164
241
|
|
|
165
|
-
|
|
166
|
-
include Ast::Merge::MergerConfig
|
|
242
|
+
### ContentMatchRefiner
|
|
167
243
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
244
|
+
`Ast::Merge::ContentMatchRefiner` is a built-in match refiner for fuzzy text content matching using Levenshtein distance. Unlike signature-based matching which requires exact content hashes, this refiner allows matching nodes with similar (but not identical) content.
|
|
245
|
+
|
|
246
|
+
```ruby
|
|
247
|
+
# Basic usage - match nodes with 70% similarity
|
|
248
|
+
refiner = Ast::Merge::ContentMatchRefiner.new(threshold: 0.7)
|
|
249
|
+
|
|
250
|
+
# Only match specific node types
|
|
251
|
+
refiner = Ast::Merge::ContentMatchRefiner.new(
|
|
252
|
+
threshold: 0.6,
|
|
253
|
+
node_types: [:paragraph, :heading]
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# Custom weights for scoring
|
|
257
|
+
refiner = Ast::Merge::ContentMatchRefiner.new(
|
|
258
|
+
threshold: 0.7,
|
|
259
|
+
weights: {
|
|
260
|
+
content: 0.8, # Levenshtein similarity (default: 0.7)
|
|
261
|
+
length: 0.1, # Length similarity (default: 0.15)
|
|
262
|
+
position: 0.1 # Position in document (default: 0.15)
|
|
263
|
+
}
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
# Custom content extraction
|
|
267
|
+
refiner = Ast::Merge::ContentMatchRefiner.new(
|
|
268
|
+
threshold: 0.7,
|
|
269
|
+
content_extractor: ->(node) { node.text_content.downcase.strip }
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
# Use with a merger
|
|
273
|
+
merger = MyFormat::SmartMerger.new(
|
|
274
|
+
template,
|
|
275
|
+
destination,
|
|
276
|
+
preference: :template,
|
|
277
|
+
match_refiner: refiner
|
|
278
|
+
)
|
|
172
279
|
```
|
|
173
280
|
|
|
281
|
+
This is particularly useful for:
|
|
282
|
+
- Paragraphs with minor edits (typos, rewording)
|
|
283
|
+
- Headings with slight changes
|
|
284
|
+
- Comments with updated text
|
|
285
|
+
- Any text-based node that may have been slightly modified
|
|
286
|
+
|
|
287
|
+
### Namespace Reference
|
|
288
|
+
|
|
289
|
+
The `Ast::Merge` module is organized into several namespaces, each with detailed documentation:
|
|
290
|
+
|
|
291
|
+
| Namespace | Purpose | Documentation |
|
|
292
|
+
|-----------|---------|---------------|
|
|
293
|
+
| `Ast::Merge::Detector` | Region detection and merging | [lib/ast/merge/detector/README.md](lib/ast/merge/detector/README.md) |
|
|
294
|
+
| `Ast::Merge::Recipe` | YAML-based merge recipes | [lib/ast/merge/recipe/README.md](lib/ast/merge/recipe/README.md) |
|
|
295
|
+
| `Ast::Merge::Comment` | Comment parsing and representation | [lib/ast/merge/comment/README.md](lib/ast/merge/comment/README.md) |
|
|
296
|
+
| `Ast::Merge::Text` | Plain text AST parsing | [lib/ast/merge/text/README.md](lib/ast/merge/text/README.md) |
|
|
297
|
+
| `Ast::Merge::RSpec` | Shared RSpec examples | [lib/ast/merge/rspec/README.md](lib/ast/merge/rspec/README.md) |
|
|
298
|
+
|
|
299
|
+
**Key Classes by Namespace:**
|
|
300
|
+
|
|
301
|
+
- **Detector**: `Region`, `Base`, `Mergeable`, `FencedCodeBlock`, `YamlFrontmatter`, `TomlFrontmatter`
|
|
302
|
+
- **Recipe**: `Config`, `Runner`, `ScriptLoader`
|
|
303
|
+
- **Comment**: `Line`, `Block`, `Empty`, `Parser`, `Style`
|
|
304
|
+
- **Text**: `SmartMerger`, `FileAnalysis`, `LineNode`, `WordNode`, `Section`
|
|
305
|
+
- **RSpec**: Shared examples and dependency tags for testing `*-merge` implementations
|
|
306
|
+
|
|
174
307
|
## 💡 Info you can shake a stick at
|
|
175
308
|
|
|
176
309
|
| Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] |
|
|
@@ -297,8 +430,8 @@ merger = SomeFormat::Merge::SmartMerger.new(
|
|
|
297
430
|
destination,
|
|
298
431
|
# When conflicts occur, prefer template or destination values
|
|
299
432
|
preference: :template, # or :destination (default), or a Hash for per-node-type
|
|
300
|
-
# Add nodes that only exist in template
|
|
301
|
-
add_template_only_nodes: true, # default: false
|
|
433
|
+
# Add nodes that only exist in template (Boolean or callable filter)
|
|
434
|
+
add_template_only_nodes: true, # default: false, or ->(node, entry) { ... }
|
|
302
435
|
# Custom node type handling
|
|
303
436
|
node_typing: {}, # optional, for per-node-type preference
|
|
304
437
|
)
|
|
@@ -316,8 +449,41 @@ Control which source wins when both files have the same structural element:
|
|
|
316
449
|
|
|
317
450
|
Control whether to add nodes that only exist in the template:
|
|
318
451
|
|
|
319
|
-
- **`true`** - Add
|
|
452
|
+
- **`true`** - Add all template-only nodes
|
|
320
453
|
- **`false`** (default) - Skip template-only nodes
|
|
454
|
+
- **Callable** - Filter which template-only nodes to add
|
|
455
|
+
|
|
456
|
+
#### Callable Filter
|
|
457
|
+
|
|
458
|
+
When you need fine-grained control over which template-only nodes are added, pass a callable (Proc/Lambda) that receives `(node, entry)` and returns truthy to add or falsey to skip:
|
|
459
|
+
|
|
460
|
+
```ruby
|
|
461
|
+
# Only add nodes with gem_family signatures
|
|
462
|
+
merger = SomeFormat::Merge::SmartMerger.new(
|
|
463
|
+
template,
|
|
464
|
+
destination,
|
|
465
|
+
add_template_only_nodes: ->(node, entry) {
|
|
466
|
+
sig = entry[:signature]
|
|
467
|
+
sig.is_a?(Array) && sig.first == :gem_family
|
|
468
|
+
}
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
# Only add link definitions that match a pattern
|
|
472
|
+
merger = Markly::Merge::SmartMerger.new(
|
|
473
|
+
template,
|
|
474
|
+
destination,
|
|
475
|
+
add_template_only_nodes: ->(node, entry) {
|
|
476
|
+
entry[:template_node].type == :link_definition &&
|
|
477
|
+
entry[:signature]&.last&.include?("gem")
|
|
478
|
+
}
|
|
479
|
+
)
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
The `entry` hash contains:
|
|
483
|
+
- `:template_node` - The node being considered for addition
|
|
484
|
+
- `:signature` - The node's signature (Array or other value)
|
|
485
|
+
- `:template_index` - Index in the template statements
|
|
486
|
+
- `:dest_index` - Always `nil` for template-only nodes
|
|
321
487
|
|
|
322
488
|
## 🔧 Basic Usage
|
|
323
489
|
|
|
@@ -485,7 +651,7 @@ The `MergerConfig` class provides factory methods that support all options:
|
|
|
485
651
|
# Create config preferring destination
|
|
486
652
|
config = Ast::Merge::MergerConfig.destination_wins(
|
|
487
653
|
freeze_token: "my-freeze",
|
|
488
|
-
|
|
654
|
+
: my_generator,
|
|
489
655
|
node_typing: my_typing,
|
|
490
656
|
)
|
|
491
657
|
|
|
@@ -843,7 +1009,7 @@ Thanks for RTFM. ☺️
|
|
|
843
1009
|
[📌gitmoji]: https://gitmoji.dev
|
|
844
1010
|
[📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
|
|
845
1011
|
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
846
|
-
[🧮kloc-img]: https://img.shields.io/badge/KLOC-
|
|
1012
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-3.271-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
|
847
1013
|
[🔐security]: SECURITY.md
|
|
848
1014
|
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
|
849
1015
|
[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
|
|
@@ -863,3 +1029,6 @@ Thanks for RTFM. ☺️
|
|
|
863
1029
|
[💎appraisal2]: https://github.com/appraisal-rb/appraisal2
|
|
864
1030
|
[💎appraisal2-img]: https://img.shields.io/badge/appraised_by-appraisal2-34495e.svg?plastic&logo=ruby&logoColor=white
|
|
865
1031
|
[💎d-in-dvcs]: https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/
|
|
1032
|
+
|
|
1033
|
+
[ts-jsonc]: https://gitlab.com/WhyNotHugo/tree-sitter-jsonc
|
|
1034
|
+
[dotenv]: https://github.com/bkeepers/dotenv
|