ast-merge 3.0.0 → 4.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.
data/README.md CHANGED
@@ -1,16 +1,16 @@
1
- | 📍 NOTE |
2
- | --- |
3
- | RubyGems (the [GitHub org](https://github.com/rubygems/), not the website) [suffered](https://joel.drapper.me/p/ruby-central-security-measures/) a [hostile takeover](https://pup-e.com/blog/goodbye-rubygems/) in September 2025. |
4
- | Ultimately [4 maintainers](https://www.reddit.com/r/ruby/s/gOk42POCaV) were [hard removed](https://bsky.app/profile/martinemde.com/post/3m3occezxxs2q) and a reason has been given for only 1 of those, while 2 others resigned in protest. |
5
- | It is a [complicated story](https://joel.drapper.me/p/ruby-central-takeover/) which is difficult to [parse quickly](https://joel.drapper.me/p/ruby-central-fact-check/). |
6
- | Simply put - there was active policy for adding or removing maintainers/owners of [rubygems](https://github.com/ruby/rubygems/blob/b1ab33a3d52310a84d16b193991af07f5a6a07c0/doc/rubygems/POLICIES.md?plain=1#L187-L196) and [bundler](https://github.com/ruby/rubygems/blob/b1ab33a3d52310a84d16b193991af07f5a6a07c0/doc/bundler/playbooks/TEAM_CHANGES.md), and those [policies were not followed](https://www.reddit.com/r/ruby/comments/1ove9vp/rubycentral_hates_this_one_fact/). |
7
- | I'm adding notes like this to gems because I [don't condone theft](https://joel.drapper.me/p/ruby-central/) of repositories or gems from their rightful owners. |
8
- | If a similar theft happened with my repos/gems, I'd hope some would stand up for me. |
9
- | Disenfranchised former-maintainers have started [gem.coop](https://gem.coop). |
10
- | Once available I will publish there exclusively; unless RubyCentral makes amends with the community. |
11
- | The ["Technology for Humans: Joel Draper"](https://youtu.be/_H4qbtC5qzU?si=BvuBU90R2wAqD2E6) podcast episode by [reinteractive](https://reinteractive.com/ruby-on-rails) is the most cogent summary I'm aware of. |
12
- | See [here](https://github.com/gem-coop/gem.coop/issues/12), [here](https://gem.coop) and [here](https://martinemde.com/2025/10/05/announcing-gem-coop.html) for more info on what comes next. |
13
- | What I'm doing: A (WIP) proposal for [bundler/gem scopes](https://github.com/galtzo-floss/bundle-namespace), and a (WIP) proposal for a federated [gem server](https://github.com/galtzo-floss/gem-server). |
1
+ | 📍 NOTE |
2
+ |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
3
+ | RubyGems (the [GitHub org][rubygems-org], not the website) [suffered][draper-security] a [hostile takeover][ellen-takeover] in September 2025. |
4
+ | Ultimately [4 maintainers][simi-removed] were [hard removed][martin-removed] and a reason has been given for only 1 of those, while 2 others resigned in protest. |
5
+ | It is a [complicated story][draper-takeover] which is difficult to [parse quickly][draper-lies]. |
6
+ | Simply put - there was active policy for adding or removing maintainers/owners of [rubygems][rubygems-maint-policy] and [bundler][bundler-maint-policy], and those [policies were not followed][policy-fail]. |
7
+ | I'm adding notes like this to gems because I [don't condone theft][draper-theft] of repositories or gems from their rightful owners. |
8
+ | If a similar theft happened with my repos/gems, I'd hope some would stand up for me. |
9
+ | Disenfranchised former-maintainers have started [gem.coop][gem-coop]. |
10
+ | Once available I will publish there exclusively; unless RubyCentral makes amends with the community. |
11
+ | The ["Technology for Humans: Joel Draper"][reinteractive-podcast] podcast episode by [reinteractive][reinteractive] is the most cogent summary I'm aware of. |
12
+ | See [here][gem-naming], [here][gem-coop] and [here][martin-ann] for more info on what comes next. |
13
+ | What I'm doing: A (WIP) proposal for [bundler/gem scopes][gem-scopes], and a (WIP) proposal for a federated [gem server][gem-server]. |
14
14
 
15
15
  [rubygems-org]: https://github.com/rubygems/
16
16
  [draper-security]: https://joel.drapper.me/p/ruby-central-security-measures/
@@ -31,7 +31,7 @@
31
31
  [rubygems-maint-policy]: https://github.com/ruby/rubygems/blob/b1ab33a3d52310a84d16b193991af07f5a6a07c0/doc/rubygems/POLICIES.md?plain=1#L187-L196
32
32
  [policy-fail]: https://www.reddit.com/r/ruby/comments/1ove9vp/rubycentral_hates_this_one_fact/
33
33
 
34
- [![Galtzo FLOSS Logo by Aboling0, CC BY-SA 4.0](https://logos.galtzo.com/assets/images/galtzo-floss/avatar-192px.svg)](https://discord.gg/3qme4XHNKN) [![ruby-lang Logo, Yukihiro Matsumoto, Ruby Visual Identity Team, CC BY-SA 2.5](https://logos.galtzo.com/assets/images/ruby-lang/avatar-192px.svg)](https://www.ruby-lang.org/) [![kettle-rb Logo by Aboling0, CC BY-SA 4.0](https://logos.galtzo.com/assets/images/kettle-rb/avatar-192px.svg)](https://github.com/kettle-rb)
34
+ [![Galtzo FLOSS Logo by Aboling0, CC BY-SA 4.0][🖼️galtzo-i]][🖼️galtzo-discord] [![ruby-lang Logo, Yukihiro Matsumoto, Ruby Visual Identity Team, CC BY-SA 2.5][🖼️ruby-lang-i]][🖼️ruby-lang] [![kettle-rb Logo by Aboling0, CC BY-SA 4.0][🖼️kettle-rb-i]][🖼️kettle-rb]
35
35
 
36
36
  [🖼️galtzo-i]: https://logos.galtzo.com/assets/images/galtzo-floss/avatar-192px.svg
37
37
  [🖼️galtzo-discord]: https://discord.gg/3qme4XHNKN
@@ -42,14 +42,15 @@
42
42
 
43
43
  # ☯️ Ast::Merge
44
44
 
45
- [![Version](https://img.shields.io/gem/v/ast-merge.svg)](https://bestgems.org/gems/ast-merge) [![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/kettle-rb/ast-merge.svg)](http://github.com/kettle-rb/ast-merge/releases) [![License: MIT](https://img.shields.io/badge/License-MIT-259D6C.svg)](https://opensource.org/licenses/MIT) [![Downloads Rank](https://img.shields.io/gem/rd/ast-merge.svg)](https://bestgems.org/gems/ast-merge) [![Open Source Helpers](https://www.codetriage.com/kettle-rb/ast-merge/badges/users.svg)](https://www.codetriage.com/kettle-rb/ast-merge) [![CodeCov Test Coverage](https://codecov.io/gh/kettle-rb/ast-merge/graph/badge.svg)](https://codecov.io/gh/kettle-rb/ast-merge) [![Coveralls Test Coverage](https://coveralls.io/repos/github/kettle-rb/ast-merge/badge.svg?branch=main)](https://coveralls.io/github/kettle-rb/ast-merge?branch=main) [![QLTY Test Coverage](https://qlty.sh/gh/kettle-rb/projects/ast-merge/coverage.svg)](https://qlty.sh/gh/kettle-rb/projects/ast-merge/metrics/code?sort=coverageRating) [![QLTY Maintainability](https://qlty.sh/gh/kettle-rb/projects/ast-merge/maintainability.svg)](https://qlty.sh/gh/kettle-rb/projects/ast-merge) [![CI Heads](https://github.com/kettle-rb/ast-merge/actions/workflows/heads.yml/badge.svg)](https://github.com/kettle-rb/ast-merge/actions/workflows/heads.yml) [![CI Runtime Dependencies @ HEAD](https://github.com/kettle-rb/ast-merge/actions/workflows/dep-heads.yml/badge.svg)](https://github.com/kettle-rb/ast-merge/actions/workflows/dep-heads.yml) [![CI Current](https://github.com/kettle-rb/ast-merge/actions/workflows/current.yml/badge.svg)](https://github.com/kettle-rb/ast-merge/actions/workflows/current.yml) [![CI Truffle Ruby](https://github.com/kettle-rb/ast-merge/actions/workflows/truffle.yml/badge.svg)](https://github.com/kettle-rb/ast-merge/actions/workflows/truffle.yml) [![Deps Locked](https://github.com/kettle-rb/ast-merge/actions/workflows/locked_deps.yml/badge.svg)](https://github.com/kettle-rb/ast-merge/actions/workflows/locked_deps.yml) [![Deps Unlocked](https://github.com/kettle-rb/ast-merge/actions/workflows/unlocked_deps.yml/badge.svg)](https://github.com/kettle-rb/ast-merge/actions/workflows/unlocked_deps.yml) [![CI Supported](https://github.com/kettle-rb/ast-merge/actions/workflows/supported.yml/badge.svg)](https://github.com/kettle-rb/ast-merge/actions/workflows/supported.yml) [![CI Test Coverage](https://github.com/kettle-rb/ast-merge/actions/workflows/coverage.yml/badge.svg)](https://github.com/kettle-rb/ast-merge/actions/workflows/coverage.yml) [![CI Style](https://github.com/kettle-rb/ast-merge/actions/workflows/style.yml/badge.svg)](https://github.com/kettle-rb/ast-merge/actions/workflows/style.yml) [![CodeQL](https://github.com/kettle-rb/ast-merge/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/kettle-rb/ast-merge/security/code-scanning) [![Apache SkyWalking Eyes License Compatibility Check](https://github.com/kettle-rb/prism-merge/actions/workflows/license-eye.yml/badge.svg)](https://github.com/kettle-rb/ast-merge/actions/workflows/license-eye.yml)
45
+ [![Version][👽versioni]][👽dl-rank] [![GitHub tag (latest SemVer)][⛳️tag-img]][⛳️tag] [![License: MIT][📄license-img]][📄license-ref] [![Downloads Rank][👽dl-ranki]][👽dl-rank] [![Open Source Helpers][👽oss-helpi]][👽oss-help] [![CodeCov Test Coverage][🏀codecovi]][🏀codecov] [![Coveralls Test Coverage][🏀coveralls-img]][🏀coveralls] [![QLTY Test Coverage][🏀qlty-covi]][🏀qlty-cov] [![QLTY Maintainability][🏀qlty-mnti]][🏀qlty-mnt] [![CI Heads][🚎3-hd-wfi]][🚎3-hd-wf] [![CI Runtime Dependencies @ HEAD][🚎12-crh-wfi]][🚎12-crh-wf] [![CI Current][🚎11-c-wfi]][🚎11-c-wf] [![CI Truffle Ruby][🚎9-t-wfi]][🚎9-t-wf] [![Deps Locked][🚎13-🔒️-wfi]][🚎13-🔒️-wf] [![Deps Unlocked][🚎14-🔓️-wfi]][🚎14-🔓️-wf] [![CI Supported][🚎6-s-wfi]][🚎6-s-wf] [![CI Test Coverage][🚎2-cov-wfi]][🚎2-cov-wf] [![CI Style][🚎5-st-wfi]][🚎5-st-wf] [![CodeQL][🖐codeQL-img]][🖐codeQL] [![Apache SkyWalking Eyes License Compatibility Check][🚎15-🪪-wfi]][🚎15-🪪-wf]
46
46
 
47
- `if ci_badges.map(&:color).detect { it != "green"}` ☝️ [let me know](https://discord.gg/3qme4XHNKN), as I may have missed the [discord notification](https://discord.gg/3qme4XHNKN).
47
+ `if ci_badges.map(&:color).detect { it != "green"}` ☝️ [let me know][🖼️galtzo-discord], as I may have missed the [discord notification][🖼️galtzo-discord].
48
48
 
49
49
  -----
50
+
50
51
  `if ci_badges.map(&:color).all? { it == "green"}` 👇️ send money so I can do more of this. FLOSS maintenance is now my full-time job.
51
52
 
52
- [![OpenCollective Backers](https://opencollective.com/kettle-rb/backers/badge.svg?style=flat)](https://opencollective.com/kettle-rb#backer) [![OpenCollective Sponsors](https://opencollective.com/kettle-rb/sponsors/badge.svg?style=flat)](https://opencollective.com/kettle-rb#sponsor) [![Sponsor Me on Github](https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github)](https://github.com/sponsors/pboling) [![Liberapay Goal Progress](https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat)](https://liberapay.com/pboling/donate) [![Donate on PayPal](https://img.shields.io/badge/donate-paypal-a51611.svg?style=flat&logo=paypal)](https://www.paypal.com/paypalme/peterboling) [![Buy me a coffee](https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat)](https://www.buymeacoffee.com/pboling) [![Donate on Polar](https://img.shields.io/badge/polar-donate-a51611.svg?style=flat)](https://polar.sh/pboling) [![Donate at ko-fi.com](https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat)](https://ko-fi.com/O5O86SNP4)
53
+ [![OpenCollective Backers][🖇osc-backers-i]][🖇osc-backers] [![OpenCollective Sponsors][🖇osc-sponsors-i]][🖇osc-sponsors] [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Liberapay Goal Progress][⛳liberapay-img]][⛳liberapay] [![Donate on PayPal][🖇paypal-img]][🖇paypal] [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate at ko-fi.com][🖇kofi-img]][🖇kofi]
53
54
 
54
55
  ## 🌻 Synopsis
55
56
 
@@ -59,28 +60,53 @@ Ast::Merge is **not typically used directly** - instead, use one of the format-s
59
60
 
60
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.
61
62
 
62
- | Gem | Language<br>/ Format | Parser Backend(s) | Description |
63
- |------------------------------------------|----------------------|-----------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------|
64
- | [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) |
65
- | [ast-merge][ast-merge] | Text | internal | **Infrastructure**: Shared base classes and merge logic for all `*-merge` gems |
66
- | [bash-merge][bash-merge] | Bash | [tree-sitter-bash][ts-bash] (via tree_haver) | Smart merge for Bash scripts |
67
- | [commonmarker-merge][commonmarker-merge] | Markdown | [Commonmarker][commonmarker] (via tree_haver) | Smart merge for Markdown (CommonMark via comrak Rust) |
68
- | [dotenv-merge][dotenv-merge] | Dotenv | internal | Smart merge for `.env` 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
- | [markdown-merge][markdown-merge] | Markdown | [Commonmarker][commonmarker] / [Markly][markly] (via tree_haver) | **Foundation**: Shared base for Markdown mergers with inner code block merging |
72
- | [markly-merge][markly-merge] | Markdown | [Markly][markly] (via tree_haver) | Smart merge for Markdown (CommonMark via cmark-gfm C) |
73
- | [prism-merge][prism-merge] | Ruby | [Prism][prism] (`prism` std lib gem) | Smart merge for Ruby source files |
74
- | [psych-merge][psych-merge] | YAML | [Psych][psych] (`psych` std lib gem) | Smart merge for YAML files |
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
- | [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 |
63
+ | Gem | Version | CI | | Language<br>/ Format | Parser Backend(s) | Description |
64
+ |------------------------------------------|----------------------------------------------------------------|--------------------------------------------------------------|----------|-------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------|-------------|
65
+ | [tree_haver][tree_haver] | [![Version][tree_haver-gem-i]][tree_haver-gem] | [![Version][tree_haver-ci-i]][tree_haver-ci] | Multi | MRI C, Rust, FFI, Java, Prism, Psych, Commonmarker, Markly, Citrus, Parslet | **Foundation**: Cross-Ruby adapter for parsing libraries (like Faraday for HTTP) |
66
+ | [ast-merge][ast-merge] | [![Version][ast-merge-gem-i]][ast-merge-gem] | [![Version][ast-merge-ci-i]][ast-merge-ci] | Text | internal | **Infrastructure**: Shared base classes and merge logic for all `*-merge` gems |
67
+ | [bash-merge][bash-merge] | [![Version][bash-merge-gem-i]][bash-merge-gem] | [![Version][bash-merge-ci-i]][bash-merge-ci] | Bash | [tree-sitter-bash][ts-bash] (via tree_haver) | Smart merge for Bash scripts |
68
+ | [commonmarker-merge][commonmarker-merge] | [![Version][commonmarker-merge-gem-i]][commonmarker-merge-gem] | [![Version][commonmarker-merge-ci-i]][commonmarker-merge-ci] | Markdown | [Commonmarker][commonmarker] (via tree_haver) | Smart merge for Markdown (CommonMark via comrak Rust) |
69
+ | [dotenv-merge][dotenv-merge] | [![Version][dotenv-merge-gem-i]][dotenv-merge-gem] | [![Version][dotenv-merge-ci-i]][dotenv-merge-ci] | Dotenv | internal | Smart merge for `.env` files |
70
+ | [json-merge][json-merge] | [![Version][json-merge-gem-i]][json-merge-gem] | [![Version][json-merge-ci-i]][json-merge-ci] | JSON | [tree-sitter-json][ts-json] (via tree_haver) | Smart merge for JSON files |
71
+ | [jsonc-merge][jsonc-merge] | [![Version][jsonc-merge-gem-i]][jsonc-merge-gem] | [![Version][jsonc-merge-ci-i]][jsonc-merge-ci] | JSONC | [tree-sitter-jsonc][ts-jsonc] (via tree_haver) | ⚠️ Proof of concept; Smart merge for JSON with Comments |
72
+ | [markdown-merge][markdown-merge] | [![Version][markdown-merge-gem-i]][markdown-merge-gem] | [![Version][markdown-merge-ci-i]][markdown-merge-ci] | Markdown | [Commonmarker][commonmarker] / [Markly][markly] (via tree_haver) | **Foundation**: Shared base for Markdown mergers with inner code block merging |
73
+ | [markly-merge][markly-merge] | [![Version][markly-merge-gem-i]][markly-merge-gem] | [![Version][markly-merge-ci-i]][markly-merge-ci] | Markdown | [Markly][markly] (via tree_haver) | Smart merge for Markdown (CommonMark via cmark-gfm C) |
74
+ | [prism-merge][prism-merge] | [![Version][prism-merge-gem-i]][prism-merge-gem] | [![Version][prism-merge-ci-i]][prism-merge-ci] | Ruby | [Prism][prism] (`prism` std lib gem) | Smart merge for Ruby source files |
75
+ | [psych-merge][psych-merge] | [![Version][psych-merge-gem-i]][psych-merge-gem] | [![Version][psych-merge-ci-i]][psych-merge-ci] | YAML | [Psych][psych] (`psych` std lib gem) | Smart merge for YAML files |
76
+ | [rbs-merge][rbs-merge] | [![Version][rbs-merge-gem-i]][rbs-merge-gem] | [![Version][rbs-merge-ci-i]][rbs-merge-ci] | RBS | [tree-sitter-bash][ts-rbs] (via tree_haver), [RBS][rbs] (`rbs` std lib gem) | Smart merge for Ruby type signatures |
77
+ | [toml-merge][toml-merge] | [![Version][toml-merge-gem-i]][toml-merge-gem] | [![Version][toml-merge-ci-i]][toml-merge-ci] | TOML | [Parslet + toml][toml], [Citrus + toml-rb][toml-rb], [tree-sitter-toml][ts-toml] (all via tree_haver) | Smart merge for TOML files |
78
+
79
+ #### Backend Platform Compatibility
80
+
81
+ tree_haver supports multiple parsing backends, but not all backends work on all Ruby platforms:
82
+
83
+ | Platform 👉️<br> TreeHaver Backend 👇️ | MRI | JRuby | TruffleRuby | Notes |
84
+ |------------------------------------------------|:---:|:-----:|:-----------:|-----------------------------------------------------|
85
+ | **MRI** ([ruby_tree_sitter][ruby_tree_sitter]) | ✅ | ❌ | ❌ | C extension, MRI only |
86
+ | **Rust** ([tree_stump][tree_stump]) | ✅ | ❌ | ❌ | Rust extension via magnus/rb-sys, MRI only |
87
+ | **FFI** | ✅ | ✅ | ❌ | TruffleRuby's FFI doesn't support `STRUCT_BY_VALUE` |
88
+ | **Java** ([jtreesitter][jtreesitter]) | ❌ | ✅ | ❌ | JRuby only, requires grammar JARs |
89
+ | **Prism** | ✅ | ✅ | ✅ | Ruby parsing, stdlib in Ruby 3.4+ |
90
+ | **Psych** | ✅ | ✅ | ✅ | YAML parsing, stdlib |
91
+ | **Citrus** | ✅ | ✅ | ✅ | Pure Ruby PEG parser, no native dependencies |
92
+ | **Parslet** | ✅ | ✅ | ✅ | Pure Ruby PEG parser, no native dependencies |
93
+ | **Commonmarker** | ✅ | ❌ | ❓ | Rust extension for Markdown |
94
+ | **Markly** | ✅ | ❌ | ❓ | C extension for Markdown |
95
+
96
+ **Legend**: ✅ = Works, ❌ = Does not work, ❓ = Untested
97
+
98
+ **Why some backends don't work on certain platforms**:
99
+
100
+ - **JRuby**: Runs on the JVM; cannot load native C/Rust extensions (`.so` files)
101
+ - **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`)
102
+ - **FFI on TruffleRuby**: TruffleRuby's FFI implementation doesn't support returning structs by value, which tree-sitter's C API requires
77
103
 
78
104
  **Example implementations** for the gem templating use case:
79
105
 
80
- | Gem | Purpose | Description |
81
- | --- | --- | --- |
82
- | [kettle-dev](https://github.com/kettle-rb/kettle-dev) | Gem Development | Gem templating tool using `*-merge` gems |
83
- | [kettle-jem](https://github.com/kettle-rb/kettle-jem) | Gem Templating | Gem template library with smart merge support |
106
+ | Gem | Purpose | Description |
107
+ |--------------------------|-----------------|-----------------------------------------------|
108
+ | [kettle-dev][kettle-dev] | Gem Development | Gem templating tool using `*-merge` gems |
109
+ | [kettle-jem][kettle-jem] | Gem Templating | Gem template library with smart merge support |
84
110
 
85
111
  [tree_haver]: https://github.com/kettle-rb/tree_haver
86
112
  [ast-merge]: https://github.com/kettle-rb/ast-merge
@@ -97,59 +123,97 @@ The `*-merge` gem family provides intelligent, AST-based merging for various fil
97
123
  [commonmarker-merge]: https://github.com/kettle-rb/commonmarker-merge
98
124
  [kettle-dev]: https://github.com/kettle-rb/kettle-dev
99
125
  [kettle-jem]: https://github.com/kettle-rb/kettle-jem
126
+ [tree_haver-gem]: https://bestgems.org/gems/tree_haver
127
+ [ast-merge-gem]: https://bestgems.org/gems/ast-merge
128
+ [prism-merge-gem]: https://bestgems.org/gems/prism-merge
129
+ [psych-merge-gem]: https://bestgems.org/gems/psych-merge
130
+ [json-merge-gem]: https://bestgems.org/gems/json-merge
131
+ [jsonc-merge-gem]: https://bestgems.org/gems/jsonc-merge
132
+ [bash-merge-gem]: https://bestgems.org/gems/bash-merge
133
+ [rbs-merge-gem]: https://bestgems.org/gems/rbs-merge
134
+ [dotenv-merge-gem]: https://bestgems.org/gems/dotenv-merge
135
+ [toml-merge-gem]: https://bestgems.org/gems/toml-merge
136
+ [markdown-merge-gem]: https://bestgems.org/gems/markdown-merge
137
+ [markly-merge-gem]: https://bestgems.org/gems/markly-merge
138
+ [commonmarker-merge-gem]: https://bestgems.org/gems/commonmarker-merge
139
+ [kettle-dev-gem]: https://bestgems.org/gems/kettle-dev
140
+ [kettle-jem-gem]: https://bestgems.org/gems/kettle-jem
141
+ [tree_haver-gem-i]: https://img.shields.io/gem/v/tree_haver.svg
142
+ [ast-merge-gem-i]: https://img.shields.io/gem/v/ast-merge.svg
143
+ [prism-merge-gem-i]: https://img.shields.io/gem/v/prism-merge.svg
144
+ [psych-merge-gem-i]: https://img.shields.io/gem/v/psych-merge.svg
145
+ [json-merge-gem-i]: https://img.shields.io/gem/v/json-merge.svg
146
+ [jsonc-merge-gem-i]: https://img.shields.io/gem/v/jsonc-merge.svg
147
+ [bash-merge-gem-i]: https://img.shields.io/gem/v/bash-merge.svg
148
+ [rbs-merge-gem-i]: https://img.shields.io/gem/v/rbs-merge.svg
149
+ [dotenv-merge-gem-i]: https://img.shields.io/gem/v/dotenv-merge.svg
150
+ [toml-merge-gem-i]: https://img.shields.io/gem/v/toml-merge.svg
151
+ [markdown-merge-gem-i]: https://img.shields.io/gem/v/markdown-merge.svg
152
+ [markly-merge-gem-i]: https://img.shields.io/gem/v/markly-merge.svg
153
+ [commonmarker-merge-gem-i]: https://img.shields.io/gem/v/commonmarker-merge.svg
154
+ [kettle-dev-gem-i]: https://img.shields.io/gem/v/kettle-dev.svg
155
+ [kettle-jem-gem-i]: https://img.shields.io/gem/v/kettle-jem.svg
156
+ [tree_haver-ci-i]: https://github.com/kettle-rb/tree_haver/actions/workflows/current.yml/badge.svg
157
+ [ast-merge-ci-i]: https://github.com/kettle-rb/ast-merge/actions/workflows/current.yml/badge.svg
158
+ [prism-merge-ci-i]: https://github.com/kettle-rb/prism-merge/actions/workflows/current.yml/badge.svg
159
+ [psych-merge-ci-i]: https://github.com/kettle-rb/psych-merge/actions/workflows/current.yml/badge.svg
160
+ [json-merge-ci-i]: https://github.com/kettle-rb/json-merge/actions/workflows/current.yml/badge.svg
161
+ [jsonc-merge-ci-i]: https://github.com/kettle-rb/jsonc-merge/actions/workflows/current.yml/badge.svg
162
+ [bash-merge-ci-i]: https://github.com/kettle-rb/bash-merge/actions/workflows/current.yml/badge.svg
163
+ [rbs-merge-ci-i]: https://github.com/kettle-rb/rbs-merge/actions/workflows/current.yml/badge.svg
164
+ [dotenv-merge-ci-i]: https://github.com/kettle-rb/dotenv-merge/actions/workflows/current.yml/badge.svg
165
+ [toml-merge-ci-i]: https://github.com/kettle-rb/toml-merge/actions/workflows/current.yml/badge.svg
166
+ [markdown-merge-ci-i]: https://github.com/kettle-rb/markdown-merge/actions/workflows/current.yml/badge.svg
167
+ [markly-merge-ci-i]: https://github.com/kettle-rb/markly-merge/actions/workflows/current.yml/badge.svg
168
+ [commonmarker-merge-ci-i]: https://github.com/kettle-rb/commonmarker-merge/actions/workflows/current.yml/badge.svg
169
+ [kettle-dev-ci-i]: https://github.com/kettle-rb/kettle-dev/actions/workflows/current.yml/badge.svg
170
+ [kettle-jem-ci-i]: https://github.com/kettle-rb/kettle-jem/actions/workflows/current.yml/badge.svg
171
+ [tree_haver-ci]: https://github.com/kettle-rb/tree_haver/actions/workflows/current.yml
172
+ [ast-merge-ci]: https://github.com/kettle-rb/ast-merge/actions/workflows/current.yml
173
+ [prism-merge-ci]: https://github.com/kettle-rb/prism-merge/actions/workflows/current.yml
174
+ [psych-merge-ci]: https://github.com/kettle-rb/psych-merge/actions/workflows/current.yml
175
+ [json-merge-ci]: https://github.com/kettle-rb/json-merge/actions/workflows/current.yml
176
+ [jsonc-merge-ci]: https://github.com/kettle-rb/jsonc-merge/actions/workflows/current.yml
177
+ [bash-merge-ci]: https://github.com/kettle-rb/bash-merge/actions/workflows/current.yml
178
+ [rbs-merge-ci]: https://github.com/kettle-rb/rbs-merge/actions/workflows/current.yml
179
+ [dotenv-merge-ci]: https://github.com/kettle-rb/dotenv-merge/actions/workflows/current.yml
180
+ [toml-merge-ci]: https://github.com/kettle-rb/toml-merge/actions/workflows/current.yml
181
+ [markdown-merge-ci]: https://github.com/kettle-rb/markdown-merge/actions/workflows/current.yml
182
+ [markly-merge-ci]: https://github.com/kettle-rb/markly-merge/actions/workflows/current.yml
183
+ [commonmarker-merge-ci]: https://github.com/kettle-rb/commonmarker-merge/actions/workflows/current.yml
184
+ [kettle-dev-ci]: https://github.com/kettle-rb/kettle-dev/actions/workflows/current.yml
185
+ [kettle-jem-ci]: https://github.com/kettle-rb/kettle-jem/actions/workflows/current.yml
100
186
  [prism]: https://github.com/ruby/prism
101
187
  [psych]: https://github.com/ruby/psych
102
188
  [ts-json]: https://github.com/tree-sitter/tree-sitter-json
189
+ [ts-jsonc]: https://gitlab.com/WhyNotHugo/tree-sitter-jsonc
103
190
  [ts-bash]: https://github.com/tree-sitter/tree-sitter-bash
191
+ [ts-rbs]: https://github.com/joker1007/tree-sitter-rbs
104
192
  [ts-toml]: https://github.com/tree-sitter-grammars/tree-sitter-toml
193
+ [dotenv]: https://github.com/bkeepers/dotenv
105
194
  [rbs]: https://github.com/ruby/rbs
106
195
  [toml-rb]: https://github.com/emancu/toml-rb
196
+ [toml]: https://github.com/jm/toml
107
197
  [markly]: https://github.com/ioquatix/markly
108
198
  [commonmarker]: https://github.com/gjtorikian/commonmarker
109
199
  [ruby_tree_sitter]: https://github.com/Faveod/ruby-tree-sitter
110
200
  [tree_stump]: https://github.com/joker1007/tree_stump
111
201
  [jtreesitter]: https://central.sonatype.com/artifact/io.github.tree-sitter/jtreesitter
112
202
 
113
-
114
- [ts-jsonc]: https://gitlab.com/WhyNotHugo/tree-sitter-jsonc
115
- [dotenv]: https://github.com/bkeepers/dotenv
116
-
117
- #### Backend Platform Compatibility
118
-
119
- tree\_haver supports multiple parsing backends, but not all backends work on all Ruby platforms:
120
-
121
- | Backend | MRI | JRuby | TruffleRuby | Notes |
122
- | --- | :-: | :-: | :-: | --- |
123
- | **MRI** ([ruby\_tree\_sitter](https://github.com/Faveod/ruby-tree-sitter)) | ✅ | ❌ | ❌ | C extension, MRI only |
124
- | **Rust** ([tree\_stump](https://github.com/joker1007/tree_stump)) | ✅ | ❌ | ❌ | Rust extension via magnus/rb-sys, MRI only |
125
- | **FFI** | ✅ | ✅ | ❌ | TruffleRuby's FFI doesn't support `STRUCT_BY_VALUE` |
126
- | **Java** ([jtreesitter](https://central.sonatype.com/artifact/io.github.tree-sitter/jtreesitter)) | ❌ | ✅ | ❌ | JRuby only, requires grammar JARs |
127
- | **Prism** | ✅ | ✅ | ✅ | Ruby parsing, stdlib in Ruby 3.4+ |
128
- | **Psych** | ✅ | ✅ | ✅ | YAML parsing, stdlib |
129
- | **Citrus** | ✅ | ✅ | ✅ | Pure Ruby, no native dependencies |
130
- | **Commonmarker** | ✅ | ❌ | ❓ | Rust extension for Markdown |
131
- | **Markly** | ✅ | ❌ | ❓ | C extension for Markdown |
132
-
133
- **Legend**: ✅ = Works, ❌ = Does not work, ❓ = Untested
134
-
135
- **Why some backends don't work on certain platforms**:
136
-
137
- - **JRuby**: Runs on the JVM; cannot load native C/Rust extensions (`.so` files)
138
- - **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`)
139
- - **FFI on TruffleRuby**: TruffleRuby's FFI implementation doesn't support returning structs by value, which tree-sitter's C API requires
140
203
  ### Architecture: tree\_haver + ast-merge
141
204
 
142
205
  The `*-merge` gem family is built on a two-layer architecture:
143
206
 
144
207
  #### Layer 1: tree\_haver (Parsing Foundation)
145
208
 
146
- [tree\_haver](https://github.com/kettle-rb/tree_haver) provides cross-Ruby parsing capabilities:
209
+ [tree\_haver][tree_haver] provides cross-Ruby parsing capabilities:
147
210
 
148
211
  - **Universal Backend Support**: Automatically selects the best parsing backend for your Ruby implementation (MRI, JRuby, TruffleRuby)
149
212
  - **10 Backend Options**: MRI C extensions, Rust bindings, FFI, Java (JRuby), language-specific parsers (Prism, Psych, Commonmarker, Markly), and pure Ruby fallback (Citrus)
150
213
  - **Unified API**: Write parsing code once, run on any Ruby implementation
151
214
  - **Grammar Discovery**: Built-in `GrammarFinder` for platform-aware grammar library discovery
152
215
  - **Thread-Safe**: Language registry with thread-safe caching
216
+
153
217
  #### Layer 2: ast-merge (Merge Infrastructure)
154
218
 
155
219
  Ast::Merge builds on tree\_haver to provide:
@@ -162,9 +226,10 @@ Ast::Merge builds on tree\_haver to provide:
162
226
  - **Error Classes**: `ParseError`, `TemplateParseError`, `DestinationParseError`
163
227
  - **Region Detection**: `RegionDetectorBase`, `FencedCodeBlockDetector` for text-based analysis
164
228
  - **RSpec Shared Examples**: Test helpers for implementing new merge gems
229
+
165
230
  ### Creating a New Merge Gem
166
231
 
167
- ``` ruby
232
+ ```ruby
168
233
  require "ast/merge"
169
234
 
170
235
  module MyFormat
@@ -212,7 +277,7 @@ module MyFormat
212
277
 
213
278
  class ConflictResolver < Ast::Merge::ConflictResolverBase
214
279
  def initialize(template_analysis, dest_analysis, preference: :destination,
215
- add_template_only_nodes: false, match_refiner: nil, **options)
280
+ add_template_only_nodes: false, match_refiner: nil, **options)
216
281
  super(
217
282
  strategy: :batch, # or :node, :boundary
218
283
  preference: preference,
@@ -234,7 +299,7 @@ module MyFormat
234
299
  class MergeResult < Ast::Merge::MergeResultBase
235
300
  def initialize(**options)
236
301
  super(**options)
237
- @statistics = { merged_count: 0 }
302
+ @statistics = {merged_count: 0}
238
303
  end
239
304
 
240
305
  def to_my_format
@@ -257,27 +322,27 @@ end
257
322
 
258
323
  ### Base Classes Reference
259
324
 
260
- | Base Class | Purpose | Key Methods to Implement |
261
- | --- | --- | --- |
262
- | `SmartMergerBase` | Main merge orchestration | `analysis_class`, `perform_merge` |
263
- | `ConflictResolverBase` | Resolve node conflicts | `resolve_batch` or `resolve_node_pair` |
264
- | `MergeResultBase` | Track merge results | `to_s`, format-specific output |
265
- | `MatchRefinerBase` | Fuzzy node matching | `similarity` |
266
- | `ContentMatchRefiner` | Text content fuzzy matching | Ready to use |
267
- | `FileAnalyzable` | File parsing/analysis | `compute_node_signature` |
325
+ | Base Class | Purpose | Key Methods to Implement |
326
+ |------------------------|-----------------------------|----------------------------------------|
327
+ | `SmartMergerBase` | Main merge orchestration | `analysis_class`, `perform_merge` |
328
+ | `ConflictResolverBase` | Resolve node conflicts | `resolve_batch` or `resolve_node_pair` |
329
+ | `MergeResultBase` | Track merge results | `to_s`, format-specific output |
330
+ | `MatchRefinerBase` | Fuzzy node matching | `similarity` |
331
+ | `ContentMatchRefiner` | Text content fuzzy matching | Ready to use |
332
+ | `FileAnalyzable` | File parsing/analysis | `compute_node_signature` |
268
333
 
269
334
  ### ContentMatchRefiner
270
335
 
271
336
  `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.
272
337
 
273
- ``` ruby
338
+ ```ruby
274
339
  # Basic usage - match nodes with 70% similarity
275
340
  refiner = Ast::Merge::ContentMatchRefiner.new(threshold: 0.7)
276
341
 
277
342
  # Only match specific node types
278
343
  refiner = Ast::Merge::ContentMatchRefiner.new(
279
344
  threshold: 0.6,
280
- node_types: [:paragraph, :heading]
345
+ node_types: [:paragraph, :heading],
281
346
  )
282
347
 
283
348
  # Custom weights for scoring
@@ -286,14 +351,14 @@ refiner = Ast::Merge::ContentMatchRefiner.new(
286
351
  weights: {
287
352
  content: 0.8, # Levenshtein similarity (default: 0.7)
288
353
  length: 0.1, # Length similarity (default: 0.15)
289
- position: 0.1 # Position in document (default: 0.15)
290
- }
354
+ position: 0.1, # Position in document (default: 0.15)
355
+ },
291
356
  )
292
357
 
293
358
  # Custom content extraction
294
359
  refiner = Ast::Merge::ContentMatchRefiner.new(
295
360
  threshold: 0.7,
296
- content_extractor: ->(node) { node.text_content.downcase.strip }
361
+ content_extractor: ->(node) { node.text_content.downcase.strip },
297
362
  )
298
363
 
299
364
  # Use with a merger
@@ -301,26 +366,28 @@ merger = MyFormat::SmartMerger.new(
301
366
  template,
302
367
  destination,
303
368
  preference: :template,
304
- match_refiner: refiner
369
+ match_refiner: refiner,
305
370
  )
306
371
  ```
307
372
 
308
373
  This is particularly useful for:
374
+
309
375
  - Paragraphs with minor edits (typos, rewording)
310
376
  - Headings with slight changes
311
377
  - Comments with updated text
312
378
  - Any text-based node that may have been slightly modified
379
+
313
380
  ### Namespace Reference
314
381
 
315
382
  The `Ast::Merge` module is organized into several namespaces, each with detailed documentation:
316
383
 
317
- | Namespace | Purpose | Documentation |
318
- | --- | --- | --- |
319
- | `Ast::Merge::Detector` | Region detection and merging | [lib/ast/merge/detector/README.md](lib/ast/merge/detector/README.md) |
320
- | `Ast::Merge::Recipe` | YAML-based merge recipes | [lib/ast/merge/recipe/README.md](lib/ast/merge/recipe/README.md) |
321
- | `Ast::Merge::Comment` | Comment parsing and representation | [lib/ast/merge/comment/README.md](lib/ast/merge/comment/README.md) |
322
- | `Ast::Merge::Text` | Plain text AST parsing | [lib/ast/merge/text/README.md](lib/ast/merge/text/README.md) |
323
- | `Ast::Merge::RSpec` | Shared RSpec examples | [lib/ast/merge/rspec/README.md](lib/ast/merge/rspec/README.md) |
384
+ | Namespace | Purpose | Documentation |
385
+ |------------------------|------------------------------------|----------------------------------------------------------------------|
386
+ | `Ast::Merge::Detector` | Region detection and merging | [lib/ast/merge/detector/README.md](lib/ast/merge/detector/README.md) |
387
+ | `Ast::Merge::Recipe` | YAML-based merge recipes | [lib/ast/merge/recipe/README.md](lib/ast/merge/recipe/README.md) |
388
+ | `Ast::Merge::Comment` | Comment parsing and representation | [lib/ast/merge/comment/README.md](lib/ast/merge/comment/README.md) |
389
+ | `Ast::Merge::Text` | Plain text AST parsing | [lib/ast/merge/text/README.md](lib/ast/merge/text/README.md) |
390
+ | `Ast::Merge::RSpec` | Shared RSpec examples | [lib/ast/merge/rspec/README.md](lib/ast/merge/rspec/README.md) |
324
391
 
325
392
  **Key Classes by Namespace:**
326
393
 
@@ -329,46 +396,47 @@ The `Ast::Merge` module is organized into several namespaces, each with detailed
329
396
  - **Comment**: `Line`, `Block`, `Empty`, `Parser`, `Style`
330
397
  - **Text**: `SmartMerger`, `FileAnalysis`, `LineNode`, `WordNode`, `Section`
331
398
  - **RSpec**: Shared examples and dependency tags for testing `*-merge` implementations
399
+
332
400
  ## 💡 Info you can shake a stick at
333
401
 
334
- | Tokens to Remember | [![Gem name](https://img.shields.io/badge/name-ast--merge-3C2D2D.svg?style=square&logo=rubygems&logoColor=red)](https://bestgems.org/gems/ast-merge) [![Gem namespace](https://img.shields.io/badge/namespace-Ast::Merge-3C2D2D.svg?style=square&logo=ruby&logoColor=white)](https://github.com/kettle-rb/ast-merge) |
335
- | --- | --- |
336
- | Works with JRuby | [![JRuby 10.0 Compat](https://img.shields.io/badge/JRuby-current-FBE742?style=for-the-badge&logo=ruby&logoColor=green)](https://github.com/kettle-rb/ast-merge/actions/workflows/current.yml) [![JRuby HEAD Compat](https://img.shields.io/badge/JRuby-HEAD-FBE742?style=for-the-badge&logo=ruby&logoColor=blue)](https://github.com/kettle-rb/ast-merge/actions/workflows/heads.yml) |
337
- | Works with Truffle Ruby | [![Truffle Ruby 23.1 Compat](https://img.shields.io/badge/Truffle_Ruby-23.1-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink)](https://github.com/kettle-rb/ast-merge/actions/workflows/truffle.yml) [![Truffle Ruby 24.1 Compat](https://img.shields.io/badge/Truffle_Ruby-current-34BCB1?style=for-the-badge&logo=ruby&logoColor=green)](https://github.com/kettle-rb/ast-merge/actions/workflows/current.yml) |
338
- | Works with MRI Ruby 3 | [![Ruby 3.2 Compat](https://img.shields.io/badge/Ruby-3.2-CC342D?style=for-the-badge&logo=ruby&logoColor=white)](https://github.com/kettle-rb/ast-merge/actions/workflows/supported.yml) [![Ruby 3.3 Compat](https://img.shields.io/badge/Ruby-3.3-CC342D?style=for-the-badge&logo=ruby&logoColor=white)](https://github.com/kettle-rb/ast-merge/actions/workflows/supported.yml) [![Ruby 3.4 Compat](https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green)](https://github.com/kettle-rb/ast-merge/actions/workflows/current.yml) [![Ruby HEAD Compat](https://img.shields.io/badge/Ruby-HEAD-CC342D?style=for-the-badge&logo=ruby&logoColor=blue)](https://github.com/kettle-rb/ast-merge/actions/workflows/heads.yml) |
339
- | Support & Community | [![Join Me on Daily.dev's RubyFriends](https://img.shields.io/badge/daily.dev-%F0%9F%92%8E_Ruby_Friends-0A0A0A?style=for-the-badge&logo=dailydotdev&logoColor=white)](https://app.daily.dev/squads/rubyfriends) [![Live Chat on Discord](https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord)](https://discord.gg/3qme4XHNKN) [![Get help from me on Upwork](https://img.shields.io/badge/UpWork-13544E?style=for-the-badge&logo=Upwork&logoColor=white)](https://www.upwork.com/freelancers/~014942e9b056abdf86?mp_source=share) [![Get help from me on Codementor](https://img.shields.io/badge/CodeMentor-Get_Help-1abc9c?style=for-the-badge&logo=CodeMentor&logoColor=white)](https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github) |
340
- | Source | [![Source on GitLab.com](https://img.shields.io/badge/GitLab-FBA326?style=for-the-badge&logo=Gitlab&logoColor=orange)](https://gitlab.com/kettle-rb/ast-merge/) [![Source on CodeBerg.org](https://img.shields.io/badge/CodeBerg-4893CC?style=for-the-badge&logo=CodeBerg&logoColor=blue)](https://codeberg.org/kettle-rb/ast-merge) [![Source on Github.com](https://img.shields.io/badge/GitHub-238636?style=for-the-badge&logo=Github&logoColor=green)](https://github.com/kettle-rb/ast-merge) [![The best SHA: dQw4w9WgXcQ\!](https://img.shields.io/badge/KLOC-3.271-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue)](https://www.youtube.com/watch?v=dQw4w9WgXcQ) |
341
- | Documentation | [![Current release on RubyDoc.info](https://img.shields.io/badge/RubyDoc-Current_Release-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white)](http://rubydoc.info/gems/ast-merge) [![YARD on Galtzo.com](https://img.shields.io/badge/YARD_on_Galtzo.com-HEAD-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white)](https://ast-merge.galtzo.com) [![Maintainer Blog](https://img.shields.io/badge/blog-railsbling-0093D0.svg?style=for-the-badge&logo=rubyonrails&logoColor=orange)](http://www.railsbling.com/tags/ast-merge) [![GitLab Wiki](https://img.shields.io/badge/wiki-examples-943CD2.svg?style=for-the-badge&logo=gitlab&logoColor=white)](https://gitlab.com/kettle-rb/ast-merge/-/wikis/home) [![GitHub Wiki](https://img.shields.io/badge/wiki-examples-943CD2.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/kettle-rb/ast-merge/wiki) |
342
- | Compliance | [![License: MIT](https://img.shields.io/badge/License-MIT-259D6C.svg)](https://opensource.org/licenses/MIT) [![Compatible with Apache Software Projects: Verified by SkyWalking Eyes](https://img.shields.io/badge/Apache_Compatible:_Category_A-%E2%9C%93-259D6C.svg?style=flat&logo=Apache)](https://dev.to/galtzo/how-to-check-license-compatibility-41h0) [![📄ilo-declaration-img](https://img.shields.io/badge/ILO_Fundamental_Principles-✓-259D6C.svg?style=flat)](https://www.ilo.org/declaration/lang--en/index.htm) [![Security Policy](https://img.shields.io/badge/security-policy-259D6C.svg?style=flat)](SECURITY.md) [![Contributor Covenant 2.1](https://img.shields.io/badge/Contributor_Covenant-2.1-259D6C.svg)](CODE_OF_CONDUCT.md) [![SemVer 2.0.0](https://img.shields.io/badge/semver-2.0.0-259D6C.svg?style=flat)](https://semver.org/spec/v2.0.0.html) |
343
- | Style | [![Enforced Code Style Linter](https://img.shields.io/badge/code_style_&_linting-rubocop--lts-34495e.svg?plastic&logo=ruby&logoColor=white)](https://github.com/rubocop-lts/rubocop-lts) [![Keep-A-Changelog 1.0.0](https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat)](https://keepachangelog.com/en/1.0.0/) [![Gitmoji Commits](https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square)](https://gitmoji.dev) [![Compatibility appraised by: appraisal2](https://img.shields.io/badge/appraised_by-appraisal2-34495e.svg?plastic&logo=ruby&logoColor=white)](https://github.com/appraisal-rb/appraisal2) |
344
- | Maintainer 🎖️ | [![Follow Me on LinkedIn](https://img.shields.io/badge/PeterBoling-LinkedIn-0B66C2?style=flat&logo=newjapanprowrestling)](http://www.linkedin.com/in/peterboling) [![Follow Me on Ruby.Social](https://img.shields.io/mastodon/follow/109447111526622197?domain=https://ruby.social&style=flat&logo=mastodon&label=Ruby%20@galtzo)](https://ruby.social/@galtzo) [![Follow Me on Bluesky](https://img.shields.io/badge/@galtzo.com-0285FF?style=flat&logo=bluesky&logoColor=white)](https://bsky.app/profile/galtzo.com) [![Contact Maintainer](https://img.shields.io/badge/Contact-Maintainer-0093D0.svg?style=flat&logo=rubyonrails&logoColor=red)](http://www.railsbling.com/contact) [![My technical writing](https://img.shields.io/badge/dev.to-0A0A0A?style=flat&logo=devdotto&logoColor=white)](https://dev.to/galtzo) |
345
- | `...` 💖 | [![Find Me on WellFound:](https://img.shields.io/badge/peter--boling-orange?style=flat&logo=wellfound)](https://wellfound.com/u/peter-boling) [![Find Me on CrunchBase](https://img.shields.io/badge/peter--boling-purple?style=flat&logo=crunchbase)](https://www.crunchbase.com/person/peter-boling) [![My LinkTree](https://img.shields.io/badge/galtzo-purple?style=flat&logo=linktree)](https://linktr.ee/galtzo) [![More About Me](https://img.shields.io/badge/about.me-0A0A0A?style=flat&logo=aboutme&logoColor=white)](https://about.me/peter.boling) [🧊](https://codeberg.org/pboling) [🐙](https://github.org/pboling) [🛖](https://sr.ht/~galtzo/) [🧪](https://gitlab.com/pboling) |
402
+ | Tokens to Remember | [![Gem name][⛳️name-img]][👽dl-rank] [![Gem namespace][⛳️namespace-img]][📜src-gh] |
403
+ |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
404
+ | Works with JRuby | [![JRuby 10.0 Compat][💎jruby-c-i]][🚎11-c-wf] [![JRuby HEAD Compat][💎jruby-headi]][🚎3-hd-wf] |
405
+ | Works with Truffle Ruby | [![Truffle Ruby 23.1 Compat][💎truby-23.1i]][🚎9-t-wf] [![Truffle Ruby 24.1 Compat][💎truby-c-i]][🚎11-c-wf] |
406
+ | Works with MRI Ruby 3 | [![Ruby 3.2 Compat][💎ruby-3.2i]][🚎6-s-wf] [![Ruby 3.3 Compat][💎ruby-3.3i]][🚎6-s-wf] [![Ruby 3.4 Compat][💎ruby-c-i]][🚎11-c-wf] [![Ruby HEAD Compat][💎ruby-headi]][🚎3-hd-wf] |
407
+ | Support & Community | [![Join Me on Daily.dev's RubyFriends][✉️ruby-friends-img]][✉️ruby-friends] [![Live Chat on Discord][✉️discord-invite-img-ftb]][🖼️galtzo-discord] [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork] [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor] |
408
+ | Source | [![Source on GitLab.com][📜src-gl-img]][📜src-gl] [![Source on CodeBerg.org][📜src-cb-img]][📜src-cb] [![Source on Github.com][📜src-gh-img]][📜src-gh] [![The best SHA: dQw4w9WgXcQ\!](https://img.shields.io/badge/KLOC-3.271-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue)][🧮kloc] |
409
+ | Documentation | [![Current release on RubyDoc.info][📜docs-cr-rd-img]][🚎yard-current] [![YARD on Galtzo.com][📜docs-head-rd-img]][🚎yard-head] [![Maintainer Blog][🚂maint-blog-img]][🚂maint-blog] [![GitLab Wiki][📜gl-wiki-img]][📜gl-wiki] [![GitHub Wiki][📜gh-wiki-img]][📜gh-wiki] |
410
+ | Compliance | [![License: MIT][📄license-img]][📄license-ref] [![Compatible with Apache Software Projects: Verified by SkyWalking Eyes][📄license-compat-img]][📄license-compat] [![📄ilo-declaration-img][📄ilo-declaration-img]][📄ilo-declaration] [![Security Policy][🔐security-img]][🔐security] [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct] [![SemVer 2.0.0][📌semver-img]][📌semver] |
411
+ | Style | [![Enforced Code Style Linter][💎rlts-img]][💎rlts] [![Keep-A-Changelog 1.0.0][📗keep-changelog-img]][📗keep-changelog] [![Gitmoji Commits][📌gitmoji-img]][📌gitmoji] [![Compatibility appraised by: appraisal2][💎appraisal2-img]][💎appraisal2] |
412
+ | Maintainer 🎖️ | [![Follow Me on LinkedIn][💖🖇linkedin-img]][💖🖇linkedin] [![Follow Me on Ruby.Social][💖🐘ruby-mast-img]][💖🐘ruby-mast] [![Follow Me on Bluesky][💖🦋bluesky-img]][💖🦋bluesky] [![Contact Maintainer][🚂maint-contact-img]][🚂maint-contact] [![My technical writing][💖💁🏼‍♂️devto-img]][💖💁🏼‍♂️devto] |
413
+ | `...` 💖 | [![Find Me on WellFound:][💖✌️wellfound-img]][💖✌️wellfound] [![Find Me on CrunchBase][💖💲crunchbase-img]][💖💲crunchbase] [![My LinkTree][💖🌳linktree-img]][💖🌳linktree] [![More About Me][💖💁🏼‍♂️aboutme-img]][💖💁🏼‍♂️aboutme] [🧊][💖🧊berg] [🐙][💖🐙hub] [🛖][💖🛖hut] [🧪][💖🧪lab] |
346
414
 
347
415
  ### Compatibility
348
416
 
349
417
  Compatible with MRI Ruby 3.2.0+, and concordant releases of JRuby, and TruffleRuby.
350
418
 
351
- | 🚚 *Amazing* test matrix was brought to you by | 🔎 appraisal2 🔎 and the color 💚 green 💚 |
352
- | --- | --- |
353
- | 👟 Check it out\! | ✨ [github.com/appraisal-rb/appraisal2](https://github.com/appraisal-rb/appraisal2) ✨ |
419
+ | 🚚 *Amazing* test matrix was brought to you by | 🔎 appraisal2 🔎 and the color 💚 green 💚 |
420
+ |------------------------------------------------|--------------------------------------------------------------------------------------|
421
+ | 👟 Check it out\! | ✨ [github.com/appraisal-rb/appraisal2][💎appraisal2] ✨ |
354
422
 
355
423
  ### Federated DVCS
356
424
 
357
425
  <details markdown="1">
358
426
  <summary>Find this repo on federated forges (Coming soon!)</summary>
359
427
 
360
- | Federated [DVCS](https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/) Repository | Status | Issues | PRs | Wiki | CI | Discussions |
361
- | --- | --- | --- | --- | --- | --- | --- |
362
- | 🧪 [kettle-rb/ast-merge on GitLab](https://gitlab.com/kettle-rb/ast-merge/) | The Truth | [💚](https://gitlab.com/kettle-rb/ast-merge/-/issues) | [💚](https://gitlab.com/kettle-rb/ast-merge/-/merge_requests) | [💚](https://gitlab.com/kettle-rb/ast-merge/-/wikis/home) | 🐭 Tiny Matrix | ➖ |
363
- | 🧊 [kettle-rb/ast-merge on CodeBerg](https://codeberg.org/kettle-rb/ast-merge) | An Ethical Mirror ([Donate](https://donate.codeberg.org/)) | [💚](https://codeberg.org/kettle-rb/ast-merge/issues) | [💚](https://codeberg.org/kettle-rb/ast-merge/pulls) | ➖ | ⭕️ No Matrix | ➖ |
364
- | 🐙 [kettle-rb/ast-merge on GitHub](https://github.com/kettle-rb/ast-merge) | Another Mirror | [💚](https://github.com/kettle-rb/ast-merge/issues) | [💚](https://github.com/kettle-rb/ast-merge/pulls) | [💚](https://github.com/kettle-rb/ast-merge/wiki) | 💯 Full Matrix | [💚](https://github.com/kettle-rb/ast-merge/discussions) |
365
- | 🎮️ [Discord Server](https://discord.gg/3qme4XHNKN) | [![Live Chat on Discord](https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord)](https://discord.gg/3qme4XHNKN) | [Let's](https://discord.gg/3qme4XHNKN) | [talk](https://discord.gg/3qme4XHNKN) | [about](https://discord.gg/3qme4XHNKN) | [this](https://discord.gg/3qme4XHNKN) | [library\!](https://discord.gg/3qme4XHNKN) |
428
+ | Federated [DVCS][💎d-in-dvcs] Repository | Status | Issues | PRs | Wiki | CI | Discussions |
429
+ |-----------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------|---------------------------------------------------------------|-----------------------------------------------------------|---------------------------------------|----------------------------------------------------------|
430
+ | 🧪 [kettle-rb/ast-merge on GitLab][📜src-gl] | The Truth | [💚][🤝gl-issues] | [💚][🤝gl-pulls] | [💚][📜gl-wiki] | 🐭 Tiny Matrix | ➖ |
431
+ | 🧊 [kettle-rb/ast-merge on CodeBerg][📜src-cb] | An Ethical Mirror ([Donate][🤝cb-donate]) | [💚][🤝cb-issues] | [💚][🤝cb-pulls] | ➖ | ⭕️ No Matrix | ➖ |
432
+ | 🐙 [kettle-rb/ast-merge on GitHub][📜src-gh] | Another Mirror | [💚][🤝gh-issues] | [💚][🤝gh-pulls] | [💚][📜gh-wiki] | 💯 Full Matrix | [💚][gh-discussions] |
433
+ | 🎮️ [Discord Server][🖼️galtzo-discord] | [![Live Chat on Discord][✉️discord-invite-img-ftb]][🖼️galtzo-discord] | [Let's][🖼️galtzo-discord] | [talk][🖼️galtzo-discord] | [about][🖼️galtzo-discord] | [this][🖼️galtzo-discord] | [library\!][🖼️galtzo-discord] |
366
434
 
367
435
  </details>
368
436
 
369
437
  [gh-discussions]: https://github.com/kettle-rb/ast-merge/discussions
370
438
 
371
- ### Enterprise Support [![Tidelift](https://tidelift.com/badges/package/rubygems/ast-merge)](https://tidelift.com/subscription/pkg/rubygems-ast-merge?utm_source=rubygems-ast-merge&utm_medium=referral&utm_campaign=readme)
439
+ ### Enterprise Support [![Tidelift](https://tidelift.com/badges/package/rubygems/ast-merge)][🏙️entsup-tidelift]
372
440
 
373
441
  Available as part of the Tidelift Subscription.
374
442
 
@@ -377,33 +445,34 @@ Available as part of the Tidelift Subscription.
377
445
 
378
446
  The maintainers of this and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use.
379
447
 
380
- [![Get help from me on Tidelift](https://img.shields.io/badge/Tidelift_and_Sonar-Enterprise_Support-FD3456?style=for-the-badge&logo=sonar&logoColor=white)](https://tidelift.com/subscription/pkg/rubygems-ast-merge?utm_source=rubygems-ast-merge&utm_medium=referral&utm_campaign=readme)
448
+ [![Get help from me on Tidelift][🏙️entsup-tidelift-img]][🏙️entsup-tidelift]
381
449
 
382
450
  - 💡Subscribe for support guarantees covering *all* your FLOSS dependencies
383
451
 
384
- - 💡Tidelift is part of [Sonar](https://blog.tidelift.com/tidelift-joins-sonar)
452
+ - 💡Tidelift is part of [Sonar][🏙️entsup-tidelift-sonar]
385
453
 
386
- - 💡Tidelift pays maintainers to maintain the software you depend on\!<br/>📊`@`Pointy Haired Boss: An [enterprise support](https://tidelift.com/subscription/pkg/rubygems-ast-merge?utm_source=rubygems-ast-merge&utm_medium=referral&utm_campaign=readme) subscription is "[never gonna let you down](https://www.youtube.com/watch?v=dQw4w9WgXcQ)", and *supports* open source maintainers
454
+ - 💡Tidelift pays maintainers to maintain the software you depend on\!<br/>📊`@`Pointy Haired Boss: An [enterprise support][🏙️entsup-tidelift] subscription is "[never gonna let you down][🧮kloc]", and *supports* open source maintainers
387
455
  Alternatively:
388
456
 
389
- - [![Live Chat on Discord](https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord)](https://discord.gg/3qme4XHNKN)
457
+ - [![Live Chat on Discord][✉️discord-invite-img-ftb]][🖼️galtzo-discord]
458
+
459
+ - [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork]
390
460
 
391
- - [![Get help from me on Upwork](https://img.shields.io/badge/UpWork-13544E?style=for-the-badge&logo=Upwork&logoColor=white)](https://www.upwork.com/freelancers/~014942e9b056abdf86?mp_source=share)
461
+ - [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor]
392
462
 
393
- - [![Get help from me on Codementor](https://img.shields.io/badge/CodeMentor-Get_Help-1abc9c?style=for-the-badge&logo=CodeMentor&logoColor=white)](https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github)
394
463
  </details>
395
464
 
396
465
  ## ✨ Installation
397
466
 
398
467
  Install the gem and add to the application's Gemfile by executing:
399
468
 
400
- ``` console
469
+ ```console
401
470
  bundle add ast-merge
402
471
  ```
403
472
 
404
473
  If bundler is not being used to manage dependencies, install the gem by executing:
405
474
 
406
- ``` console
475
+ ```console
407
476
  gem install ast-merge
408
477
  ```
409
478
 
@@ -412,19 +481,19 @@ gem install ast-merge
412
481
  <details markdown="1">
413
482
  <summary>For Medium or High Security Installations</summary>
414
483
 
415
- This gem is cryptographically signed, and has verifiable [SHA-256 and SHA-512](https://gitlab.com/kettle-rb/ast-merge/-/tree/main/checksums) checksums by
416
- [stone\_checksums](https://github.com/galtzo-floss/stone_checksums). Be sure the gem you install hasn’t been tampered with
484
+ This gem is cryptographically signed, and has verifiable [SHA-256 and SHA-512][💎SHA_checksums] checksums by
485
+ [stone\_checksums][💎stone_checksums]. Be sure the gem you install hasn’t been tampered with
417
486
  by following the instructions below.
418
487
 
419
488
  Add my public key (if you haven’t already, expires 2045-04-29) as a trusted certificate:
420
489
 
421
- ``` console
490
+ ```console
422
491
  gem cert --add <(curl -Ls https://raw.github.com/galtzo-floss/certs/main/pboling.pem)
423
492
  ```
424
493
 
425
494
  You only need to do that once. Then proceed to install with:
426
495
 
427
- ``` console
496
+ ```console
428
497
  gem install ast-merge -P HighSecurity
429
498
  ```
430
499
 
@@ -432,7 +501,7 @@ The `HighSecurity` trust profile will verify signed gems, and not allow the inst
432
501
 
433
502
  If you want to up your security game full-time:
434
503
 
435
- ``` console
504
+ ```console
436
505
  bundle config set --global trust-policy MediumSecurity
437
506
  ```
438
507
 
@@ -451,7 +520,7 @@ Each implementation (like `prism-merge`, `psych-merge`, etc.) has its own SmartM
451
520
 
452
521
  All SmartMerger implementations share these configuration options:
453
522
 
454
- ``` ruby
523
+ ```ruby
455
524
  merger = SomeFormat::Merge::SmartMerger.new(
456
525
  template,
457
526
  destination,
@@ -471,6 +540,7 @@ Control which source wins when both files have the same structural element:
471
540
  - **`:template`** - Template values replace destination values
472
541
  - **`:destination`** (default) - Destination values are preserved
473
542
  - **Hash** - Per-node-type preference (see Advanced Configuration)
543
+
474
544
  ### Template-Only Nodes
475
545
 
476
546
  Control whether to add nodes that only exist in the template:
@@ -478,11 +548,12 @@ Control whether to add nodes that only exist in the template:
478
548
  - **`true`** - Add all template-only nodes
479
549
  - **`false`** (default) - Skip template-only nodes
480
550
  - **Callable** - Filter which template-only nodes to add
551
+
481
552
  #### Callable Filter
482
553
 
483
554
  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:
484
555
 
485
- ``` ruby
556
+ ```ruby
486
557
  # Only add nodes with gem_family signatures
487
558
  merger = SomeFormat::Merge::SmartMerger.new(
488
559
  template,
@@ -490,7 +561,7 @@ merger = SomeFormat::Merge::SmartMerger.new(
490
561
  add_template_only_nodes: ->(node, entry) {
491
562
  sig = entry[:signature]
492
563
  sig.is_a?(Array) && sig.first == :gem_family
493
- }
564
+ },
494
565
  )
495
566
 
496
567
  # Only add link definitions that match a pattern
@@ -500,20 +571,22 @@ merger = Markly::Merge::SmartMerger.new(
500
571
  add_template_only_nodes: ->(node, entry) {
501
572
  entry[:template_node].type == :link_definition &&
502
573
  entry[:signature]&.last&.include?("gem")
503
- }
574
+ },
504
575
  )
505
576
  ```
506
577
 
507
578
  The `entry` hash contains:
579
+
508
580
  - `:template_node` - The node being considered for addition
509
581
  - `:signature` - The node's signature (Array or other value)
510
582
  - `:template_index` - Index in the template statements
511
583
  - `:dest_index` - Always `nil` for template-only nodes
584
+
512
585
  ## 🔧 Basic Usage
513
586
 
514
587
  ### Using Shared Examples in Tests
515
588
 
516
- ``` ruby
589
+ ```ruby
517
590
  # spec/spec_helper.rb
518
591
  require "ast/merge/rspec/shared_examples"
519
592
 
@@ -538,6 +611,7 @@ end
538
611
  - `"Ast::Merge::DebugLogger"` - Tests for DebugLogger implementations
539
612
  - `"Ast::Merge::FileAnalysisBase"` - Tests for FileAnalysis implementations
540
613
  - `"Ast::Merge::MergerConfig"` - Tests for SmartMerger implementations
614
+
541
615
  ## 🎛️ Advanced Configuration
542
616
 
543
617
  ### Freeze Blocks
@@ -547,11 +621,12 @@ to preserve content exactly as-is, preventing any changes from the template.
547
621
  This is useful for hand-edited customizations you never want overwritten.
548
622
 
549
623
  A freeze block consists of:
624
+
550
625
  - A **start marker** comment (e.g., `# mytoken:freeze`)
551
626
  - The protected content
552
627
  - An **end marker** comment (e.g., `# mytoken:unfreeze`)
553
- <!-- end list -->
554
- ``` ruby
628
+
629
+ ```ruby
555
630
  # In a Ruby file with prism-merge:
556
631
  class MyApp
557
632
  # prism-merge:freeze
@@ -572,16 +647,16 @@ freeze token (the `token` in `token:freeze`), which defaults to the gem name (e.
572
647
  Different file formats use different comment syntaxes. The merge tools detect freeze markers
573
648
  using the appropriate pattern for each format:
574
649
 
575
- | Pattern Type | Start Marker | End Marker | Languages |
576
- | --- | --- | --- | --- |
577
- | `:hash_comment` | `# token:freeze` | `# token:unfreeze` | Ruby, Python, YAML, Bash, Shell |
578
- | `:html_comment` | `<!-- token:freeze -->` | `<!-- token:unfreeze -->` | HTML, XML, Markdown |
579
- | `:c_style_line` | `// token:freeze` | `// token:unfreeze` | C (C99+), C++, JavaScript, TypeScript, Java, C\#, Go, Rust, Swift, Kotlin, PHP, JSONC |
580
- | `:c_style_block` | `/* token:freeze */` | `/* token:unfreeze */` | C, C++, JavaScript, TypeScript, Java, C\#, Go, Rust, Swift, Kotlin, PHP, CSS |
650
+ | Pattern Type | Start Marker | End Marker | Languages |
651
+ |------------------|-------------------------|---------------------------|---------------------------------------------------------------------------------------|
652
+ | `:hash_comment` | `# token:freeze` | `# token:unfreeze` | Ruby, Python, YAML, Bash, Shell |
653
+ | `:html_comment` | `<!-- token:freeze -->` | `<!-- token:unfreeze -->` | HTML, XML, Markdown |
654
+ | `:c_style_line` | `// token:freeze` | `// token:unfreeze` | C (C99+), C++, JavaScript, TypeScript, Java, C\#, Go, Rust, Swift, Kotlin, PHP, JSONC |
655
+ | `:c_style_block` | `/* token:freeze */` | `/* token:unfreeze */` | C, C++, JavaScript, TypeScript, Java, C\#, Go, Rust, Swift, Kotlin, PHP, CSS |
581
656
 
582
- | 📍 NOTE |
583
- | --- |
584
- | CSS only supports block comments (`/* */`), not line comments. |
657
+ | 📍 NOTE |
658
+ |-------------------------------------------------------------------|
659
+ | CSS only supports block comments (`/* */`), not line comments. |
585
660
  | JSON does not support comments; use JSONC for JSON with comments. |
586
661
 
587
662
  ### Per-Node-Type Preference with `node_typing`
@@ -601,8 +676,8 @@ preferences for different types of nodes (e.g., prefer template for linter confi
601
676
 
602
677
  - `:default` key for the fallback preference
603
678
  - Custom keys matching the `merge_type` values from your `node_typing`
604
- <!-- end list -->
605
- ``` ruby
679
+
680
+ ```ruby
606
681
  # Example: Prefer template for lint gem configs, destination for everything else
607
682
  node_typing = {
608
683
  call_node: ->(node) {
@@ -630,7 +705,7 @@ merger = Prism::Merge::SmartMerger.new(
630
705
  The `Ast::Merge::NodeTyping::Wrapper` class wraps an AST node and adds a `merge_type` attribute.
631
706
  It delegates all method calls to the wrapped node, so it can be used transparently in place of the original node.
632
707
 
633
- ``` ruby
708
+ ```ruby
634
709
  # Wrap a node with a custom merge_type
635
710
  wrapped = Ast::Merge::NodeTyping::Wrapper.new(original_node, :special_config)
636
711
  wrapped.merge_type # => :special_config
@@ -640,7 +715,7 @@ wrapped.location # => delegates to original_node.location
640
715
 
641
716
  #### NodeTyping Utility Methods
642
717
 
643
- ``` ruby
718
+ ```ruby
644
719
  # Process a node through the node_typing configuration
645
720
  processed = Ast::Merge::NodeTyping.process(node, node_typing_config)
646
721
 
@@ -659,7 +734,7 @@ Ast::Merge::NodeTyping.unwrap(wrapped_node) # => original_node
659
734
  Even without `node_typing`, you can use a Hash-based preference to set a default
660
735
  and document your intention for future per-type customization:
661
736
 
662
- ``` ruby
737
+ ```ruby
663
738
  # Simple Hash preference (functionally equivalent to preference: :destination)
664
739
  merger = MyMerger.new(
665
740
  template_content,
@@ -672,11 +747,11 @@ merger = MyMerger.new(
672
747
 
673
748
  The `MergerConfig` class provides factory methods that support all options:
674
749
 
675
- ``` ruby
750
+ ```ruby
676
751
  # Create config preferring destination
677
752
  config = Ast::Merge::MergerConfig.destination_wins(
678
753
  freeze_token: "my-freeze",
679
- : my_generator,
754
+ signature_generator: my_generator,
680
755
  node_typing: my_typing,
681
756
  )
682
757
 
@@ -695,17 +770,17 @@ Raising a monthly budget of... "dollars" would make the project more sustainable
695
770
 
696
771
  We welcome both individual and corporate sponsors\! We also offer a
697
772
  wide array of funding channels to account for your preferences
698
- (although currently [Open Collective](https://opencollective.com/kettle-rb) is our preferred funding platform).
773
+ (although currently [Open Collective][🖇osc] is our preferred funding platform).
699
774
 
700
775
  **If you're working in a company that's making significant use of kettle-rb tools we'd
701
776
  appreciate it if you suggest to your company to become a kettle-rb sponsor.**
702
777
 
703
778
  You can support the development of kettle-rb tools via
704
- [GitHub Sponsors](https://github.com/sponsors/pboling),
705
- [Liberapay](https://liberapay.com/pboling/donate),
706
- [PayPal](https://www.paypal.com/paypalme/peterboling),
707
- [Open Collective](https://opencollective.com/kettle-rb)
708
- and [Tidelift](https://tidelift.com/subscription/pkg/rubygems-ast-merge?utm_source=rubygems-ast-merge&utm_medium=referral&utm_campaign=readme).
779
+ [GitHub Sponsors][🖇sponsor],
780
+ [Liberapay][⛳liberapay],
781
+ [PayPal][🖇paypal],
782
+ [Open Collective][🖇osc]
783
+ and [Tidelift][🏙️entsup-tidelift].
709
784
 
710
785
  | 📍 NOTE |
711
786
  | --- |
@@ -713,9 +788,9 @@ and [Tidelift](https://tidelift.com/subscription/pkg/rubygems-ast-merge?utm_sour
713
788
 
714
789
  ### Open Collective for Individuals
715
790
 
716
- Support us with a monthly donation and help us continue our activities. \[[Become a backer](https://opencollective.com/kettle-rb#backer)\]
791
+ Support us with a monthly donation and help us continue our activities. \[[Become a backer][🖇osc-backers]\]
717
792
 
718
- NOTE: [kettle-readme-backers](https://github.com/kettle-rb/ast-merge/blob/main/exe/kettle-readme-backers) updates this list every day, automatically.
793
+ NOTE: [kettle-readme-backers][kettle-readme-backers] updates this list every day, automatically.
719
794
 
720
795
  <!-- OPENCOLLECTIVE-INDIVIDUALS:START -->
721
796
  No backers yet. Be the first\!
@@ -723,9 +798,9 @@ No backers yet. Be the first\!
723
798
 
724
799
  ### Open Collective for Organizations
725
800
 
726
- Become a sponsor and get your logo on our README on GitHub with a link to your site. \[[Become a sponsor](https://opencollective.com/kettle-rb#sponsor)\]
801
+ Become a sponsor and get your logo on our README on GitHub with a link to your site. \[[Become a sponsor][🖇osc-sponsors]\]
727
802
 
728
- NOTE: [kettle-readme-backers](https://github.com/kettle-rb/ast-merge/blob/main/exe/kettle-readme-backers) updates this list every day, automatically.
803
+ NOTE: [kettle-readme-backers][kettle-readme-backers] updates this list every day, automatically.
729
804
 
730
805
  <!-- OPENCOLLECTIVE-ORGANIZATIONS:START -->
731
806
  No sponsors yet. Be the first\!
@@ -739,48 +814,48 @@ I’m driven by a passion to foster a thriving open-source community – a space
739
814
 
740
815
  If you work at a company that uses my work, please encourage them to support me as a corporate sponsor. My work on gems you use might show up in `bundle fund`.
741
816
 
742
- I’m developing a new library, [floss\_funding](https://github.com/galtzo-floss/floss_funding), designed to empower open-source developers like myself to get paid for the work we do, in a sustainable way. Please give it a look.
817
+ I’m developing a new library, [floss\_funding][🖇floss-funding-gem], designed to empower open-source developers like myself to get paid for the work we do, in a sustainable way. Please give it a look.
743
818
 
744
- **[Floss-Funding.dev](https://floss-funding.dev): 👉️ No network calls. 👉️ No tracking. 👉️ No oversight. 👉️ Minimal crypto hashing. 💡 Easily disabled nags**
819
+ **[Floss-Funding.dev][🖇floss-funding.dev]: 👉️ No network calls. 👉️ No tracking. 👉️ No oversight. 👉️ Minimal crypto hashing. 💡 Easily disabled nags**
745
820
 
746
- [![OpenCollective Backers](https://opencollective.com/kettle-rb/backers/badge.svg?style=flat)](https://opencollective.com/kettle-rb#backer) [![OpenCollective Sponsors](https://opencollective.com/kettle-rb/sponsors/badge.svg?style=flat)](https://opencollective.com/kettle-rb#sponsor) [![Sponsor Me on Github](https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github)](https://github.com/sponsors/pboling) [![Liberapay Goal Progress](https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat)](https://liberapay.com/pboling/donate) [![Donate on PayPal](https://img.shields.io/badge/donate-paypal-a51611.svg?style=flat&logo=paypal)](https://www.paypal.com/paypalme/peterboling) [![Buy me a coffee](https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat)](https://www.buymeacoffee.com/pboling) [![Donate on Polar](https://img.shields.io/badge/polar-donate-a51611.svg?style=flat)](https://polar.sh/pboling) [![Donate to my FLOSS efforts at ko-fi.com](https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat)](https://ko-fi.com/O5O86SNP4) [![Donate to my FLOSS efforts using Patreon](https://img.shields.io/badge/patreon-donate-a51611.svg?style=flat)](https://patreon.com/galtzo)
821
+ [![OpenCollective Backers][🖇osc-backers-i]][🖇osc-backers] [![OpenCollective Sponsors][🖇osc-sponsors-i]][🖇osc-sponsors] [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Liberapay Goal Progress][⛳liberapay-img]][⛳liberapay] [![Donate on PayPal][🖇paypal-img]][🖇paypal] [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate to my FLOSS efforts at ko-fi.com][🖇kofi-img]][🖇kofi] [![Donate to my FLOSS efforts using Patreon][🖇patreon-img]][🖇patreon]
747
822
 
748
823
  ## 🔐 Security
749
824
 
750
- See [SECURITY.md](SECURITY.md).
825
+ See [SECURITY.md][🔐security].
751
826
 
752
827
  ## 🤝 Contributing
753
828
 
754
829
  If you need some ideas of where to help, you could work on adding more code coverage,
755
- or if it is already 💯 (see [below](#code-coverage)) check [reek](REEK), [issues](https://github.com/kettle-rb/ast-merge/issues), or [PRs](https://github.com/kettle-rb/ast-merge/pulls),
830
+ or if it is already 💯 (see [below](#code-coverage)) check [reek](REEK), [issues][🤝gh-issues], or [PRs][🤝gh-pulls],
756
831
  or use the gem and think about how it could be better.
757
832
 
758
- We [![Keep A Changelog](https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat)](https://keepachangelog.com/en/1.0.0/) so if you make changes, remember to update it.
833
+ We [![Keep A Changelog][📗keep-changelog-img]][📗keep-changelog] so if you make changes, remember to update it.
759
834
 
760
- See [CONTRIBUTING.md](CONTRIBUTING.md) for more detailed instructions.
835
+ See [CONTRIBUTING.md][🤝contributing] for more detailed instructions.
761
836
 
762
837
  ### 🚀 Release Instructions
763
838
 
764
- See [CONTRIBUTING.md](CONTRIBUTING.md).
839
+ See [CONTRIBUTING.md][🤝contributing].
765
840
 
766
841
  ### Code Coverage
767
842
 
768
- [![Coverage Graph](https://codecov.io/gh/kettle-rb/ast-merge/graphs/tree.svg)](https://codecov.io/gh/kettle-rb/ast-merge)
843
+ [![Coverage Graph][🏀codecov-g]][🏀codecov]
769
844
 
770
- [![Coveralls Test Coverage](https://coveralls.io/repos/github/kettle-rb/ast-merge/badge.svg?branch=main)](https://coveralls.io/github/kettle-rb/ast-merge?branch=main)
845
+ [![Coveralls Test Coverage][🏀coveralls-img]][🏀coveralls]
771
846
 
772
- [![QLTY Test Coverage](https://qlty.sh/gh/kettle-rb/projects/ast-merge/coverage.svg)](https://qlty.sh/gh/kettle-rb/projects/ast-merge/metrics/code?sort=coverageRating)
847
+ [![QLTY Test Coverage][🏀qlty-covi]][🏀qlty-cov]
773
848
 
774
849
  ### 🪇 Code of Conduct
775
850
 
776
851
  Everyone interacting with this project's codebases, issue trackers,
777
- chat rooms and mailing lists agrees to follow the [![Contributor Covenant 2.1](https://img.shields.io/badge/Contributor_Covenant-2.1-259D6C.svg)](CODE_OF_CONDUCT.md).
852
+ chat rooms and mailing lists agrees to follow the [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct].
778
853
 
779
854
  ## 🌈 Contributors
780
855
 
781
- [![Contributors](https://contrib.rocks/image?repo=kettle-rb/ast-merge)](https://github.com/kettle-rb/ast-merge/graphs/contributors)
856
+ [![Contributors][🖐contributors-img]][🖐contributors]
782
857
 
783
- Made with [contributors-img](https://contrib.rocks).
858
+ Made with [contributors-img][🖐contrib-rocks].
784
859
 
785
860
  Also see GitLab Contributors: <https://gitlab.com/kettle-rb/ast-merge/-/graphs/main>
786
861
 
@@ -799,24 +874,24 @@ Also see GitLab Contributors: <https://gitlab.com/kettle-rb/ast-merge/-/graphs/m
799
874
 
800
875
  ## 📌 Versioning
801
876
 
802
- This Library adheres to [![Semantic Versioning 2.0.0](https://img.shields.io/badge/semver-2.0.0-259D6C.svg?style=flat)](https://semver.org/spec/v2.0.0.html).
877
+ This Library adheres to [![Semantic Versioning 2.0.0][📌semver-img]][📌semver].
803
878
  Violations of this scheme should be reported as bugs.
804
879
  Specifically, if a minor or patch version is released that breaks backward compatibility,
805
880
  a new version should be immediately released that restores compatibility.
806
881
  Breaking changes to the public API will only be introduced with new major versions.
807
882
 
808
883
  > dropping support for a platform is both obviously and objectively a breaking change <br/>
809
- > —Jordan Harband ([@ljharb](https://github.com/ljharb), maintainer of SemVer) [in SemVer issue 716](https://github.com/semver/semver/issues/716#issuecomment-869336139)
884
+ > —Jordan Harband ([@ljharb](https://github.com/ljharb), maintainer of SemVer) [in SemVer issue 716][📌semver-breaking]
810
885
 
811
886
  I understand that policy doesn't work universally ("exceptions to every rule\!"),
812
887
  but it is the policy here.
813
888
  As such, in many cases it is good to specify a dependency on this library using
814
- the [Pessimistic Version Constraint](http://guides.rubygems.org/patterns/#pessimistic-version-constraint) with two digits of precision.
889
+ the [Pessimistic Version Constraint][📌pvc] with two digits of precision.
815
890
 
816
891
  For example:
817
892
 
818
- ``` ruby
819
- spec.add_dependency("ast-merge", "~> 2.0", ">= 2.0.1") # ruby >= 3.2.0
893
+ ```ruby
894
+ spec.add_dependency("ast-merge", "~> 4.0", ">= 4.0.0") # ruby >= 3.2.0
820
895
  ```
821
896
 
822
897
  <details markdown="1">
@@ -828,16 +903,17 @@ is a *breaking change* to an API, and for that reason the bike shedding is endle
828
903
  To get a better understanding of how SemVer is intended to work over a project's lifetime,
829
904
  read this article from the creator of SemVer:
830
905
 
831
- - ["Major Version Numbers are Not Sacred"](https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html)
906
+ - ["Major Version Numbers are Not Sacred"][📌major-versions-not-sacred]
907
+
832
908
  </details>
833
909
 
834
- See [CHANGELOG.md](CHANGELOG.md) for a list of releases.
910
+ See [CHANGELOG.md][📌changelog] for a list of releases.
835
911
 
836
912
  ## 📄 License
837
913
 
838
914
  The gem is available as open source under the terms of
839
- the [MIT License](LICENSE.txt) [![License: MIT](https://img.shields.io/badge/License-MIT-259D6C.svg)](https://opensource.org/licenses/MIT).
840
- See [LICENSE.txt](LICENSE.txt) for the official [Copyright Notice](https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year).
915
+ the [MIT License][📄license] [![License: MIT][📄license-img]][📄license-ref].
916
+ See [LICENSE.txt][📄license] for the official [Copyright Notice][📄copyright-notice-explainer].
841
917
 
842
918
  ### © Copyright
843
919
 
@@ -864,11 +940,11 @@ Please consider sponsoring me or the project.
864
940
 
865
941
  To join the community or get help 👇️ Join the Discord.
866
942
 
867
- [![Live Chat on Discord](https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord)](https://discord.gg/3qme4XHNKN)
943
+ [![Live Chat on Discord][✉️discord-invite-img-ftb]][🖼️galtzo-discord]
868
944
 
869
945
  To say "thanks\!" ☝️ Join the Discord or 👇️ send money.
870
946
 
871
- [![Sponsor kettle-rb/ast-merge on Open Source Collective](https://img.shields.io/opencollective/all/kettle-rb?style=for-the-badge)](https://opencollective.com/kettle-rb) 💌 [![Sponsor me on GitHub Sponsors](https://img.shields.io/badge/Sponsor_Me!-pboling-blue?style=for-the-badge&logo=github)](https://github.com/sponsors/pboling) 💌 [![Sponsor me on Liberapay](https://img.shields.io/liberapay/goal/pboling.svg?style=for-the-badge&logo=liberapay&color=a51611)](https://liberapay.com/pboling/donate) 💌 [![Donate on PayPal](https://img.shields.io/badge/donate-paypal-a51611.svg?style=for-the-badge&logo=paypal&color=0A0A0A)](https://www.paypal.com/paypalme/peterboling)
947
+ [![Sponsor kettle-rb/ast-merge on Open Source Collective][🖇osc-all-bottom-img]][🖇osc] 💌 [![Sponsor me on GitHub Sponsors][🖇sponsor-bottom-img]][🖇sponsor] 💌 [![Sponsor me on Liberapay][⛳liberapay-bottom-img]][⛳liberapay] 💌 [![Donate on PayPal][🖇paypal-bottom-img]][🖇paypal]
872
948
 
873
949
  ### Please give the project a star ⭐ ♥.
874
950
 
@@ -909,7 +985,6 @@ Thanks for RTFM. ☺️
909
985
  [✉️discord-invite-img-ftb]: https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord
910
986
  [✉️ruby-friends-img]: https://img.shields.io/badge/daily.dev-%F0%9F%92%8E_Ruby_Friends-0A0A0A?style=for-the-badge&logo=dailydotdev&logoColor=white
911
987
  [✉️ruby-friends]: https://app.daily.dev/squads/rubyfriends
912
-
913
988
  [✇bundle-group-pattern]: https://gist.github.com/pboling/4564780
914
989
  [⛳️gem-namespace]: https://github.com/kettle-rb/ast-merge
915
990
  [⛳️namespace-img]: https://img.shields.io/badge/namespace-Ast::Merge-3C2D2D.svg?style=square&logo=ruby&logoColor=white
@@ -1033,7 +1108,7 @@ Thanks for RTFM. ☺️
1033
1108
  [📌gitmoji]: https://gitmoji.dev
1034
1109
  [📌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
1110
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
1036
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-2.540-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1111
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-2.647-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1037
1112
  [🔐security]: SECURITY.md
1038
1113
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
1039
1114
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -1053,6 +1128,3 @@ Thanks for RTFM. ☺️
1053
1128
  [💎appraisal2]: https://github.com/appraisal-rb/appraisal2
1054
1129
  [💎appraisal2-img]: https://img.shields.io/badge/appraised_by-appraisal2-34495e.svg?plastic&logo=ruby&logoColor=white
1055
1130
  [💎d-in-dvcs]: https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/
1056
-
1057
- [ts-jsonc]: https://gitlab.com/WhyNotHugo/tree-sitter-jsonc
1058
- [dotenv]: https://github.com/bkeepers/dotenv