ast-merge 3.1.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,21 +60,21 @@ 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 |
77
78
 
78
79
  #### Backend Platform Compatibility
79
80
 
@@ -87,7 +88,8 @@ tree_haver supports multiple parsing backends, but not all backends work on all
87
88
  | **Java** ([jtreesitter][jtreesitter]) | ❌ | ✅ | ❌ | JRuby only, requires grammar JARs |
88
89
  | **Prism** | ✅ | ✅ | ✅ | Ruby parsing, stdlib in Ruby 3.4+ |
89
90
  | **Psych** | ✅ | ✅ | ✅ | YAML parsing, stdlib |
90
- | **Citrus** | ✅ | ✅ | ✅ | Pure Ruby, no native dependencies |
91
+ | **Citrus** | ✅ | ✅ | ✅ | Pure Ruby PEG parser, no native dependencies |
92
+ | **Parslet** | ✅ | ✅ | ✅ | Pure Ruby PEG parser, no native dependencies |
91
93
  | **Commonmarker** | ✅ | ❌ | ❓ | Rust extension for Markdown |
92
94
  | **Markly** | ✅ | ❌ | ❓ | C extension for Markdown |
93
95
 
@@ -121,6 +123,66 @@ tree_haver supports multiple parsing backends, but not all backends work on all
121
123
  [commonmarker-merge]: https://github.com/kettle-rb/commonmarker-merge
122
124
  [kettle-dev]: https://github.com/kettle-rb/kettle-dev
123
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
124
186
  [prism]: https://github.com/ruby/prism
125
187
  [psych]: https://github.com/ruby/psych
126
188
  [ts-json]: https://github.com/tree-sitter/tree-sitter-json
@@ -131,48 +193,27 @@ tree_haver supports multiple parsing backends, but not all backends work on all
131
193
  [dotenv]: https://github.com/bkeepers/dotenv
132
194
  [rbs]: https://github.com/ruby/rbs
133
195
  [toml-rb]: https://github.com/emancu/toml-rb
196
+ [toml]: https://github.com/jm/toml
134
197
  [markly]: https://github.com/ioquatix/markly
135
198
  [commonmarker]: https://github.com/gjtorikian/commonmarker
136
199
  [ruby_tree_sitter]: https://github.com/Faveod/ruby-tree-sitter
137
200
  [tree_stump]: https://github.com/joker1007/tree_stump
138
201
  [jtreesitter]: https://central.sonatype.com/artifact/io.github.tree-sitter/jtreesitter
139
202
 
140
- #### Backend Platform Compatibility
141
-
142
- tree\_haver supports multiple parsing backends, but not all backends work on all Ruby platforms:
143
-
144
- | Backend | MRI | JRuby | TruffleRuby | Notes |
145
- | --- | :-: | :-: | :-: | --- |
146
- | **MRI** ([ruby\_tree\_sitter](https://github.com/Faveod/ruby-tree-sitter)) | ✅ | ❌ | ❌ | C extension, MRI only |
147
- | **Rust** ([tree\_stump](https://github.com/joker1007/tree_stump)) | ✅ | ❌ | ❌ | Rust extension via magnus/rb-sys, MRI only |
148
- | **FFI** | ✅ | ✅ | ❌ | TruffleRuby's FFI doesn't support `STRUCT_BY_VALUE` |
149
- | **Java** ([jtreesitter](https://central.sonatype.com/artifact/io.github.tree-sitter/jtreesitter)) | ❌ | ✅ | ❌ | JRuby only, requires grammar JARs |
150
- | **Prism** | ✅ | ✅ | ✅ | Ruby parsing, stdlib in Ruby 3.4+ |
151
- | **Psych** | ✅ | ✅ | ✅ | YAML parsing, stdlib |
152
- | **Citrus** | ✅ | ✅ | ✅ | Pure Ruby, no native dependencies |
153
- | **Commonmarker** | ✅ | ❌ | ❓ | Rust extension for Markdown |
154
- | **Markly** | ✅ | ❌ | ❓ | C extension for Markdown |
155
-
156
- **Legend**: ✅ = Works, ❌ = Does not work, ❓ = Untested
157
-
158
- **Why some backends don't work on certain platforms**:
159
-
160
- - **JRuby**: Runs on the JVM; cannot load native C/Rust extensions (`.so` files)
161
- - **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`)
162
- - **FFI on TruffleRuby**: TruffleRuby's FFI implementation doesn't support returning structs by value, which tree-sitter's C API requires
163
203
  ### Architecture: tree\_haver + ast-merge
164
204
 
165
205
  The `*-merge` gem family is built on a two-layer architecture:
166
206
 
167
207
  #### Layer 1: tree\_haver (Parsing Foundation)
168
208
 
169
- [tree\_haver](https://github.com/kettle-rb/tree_haver) provides cross-Ruby parsing capabilities:
209
+ [tree\_haver][tree_haver] provides cross-Ruby parsing capabilities:
170
210
 
171
211
  - **Universal Backend Support**: Automatically selects the best parsing backend for your Ruby implementation (MRI, JRuby, TruffleRuby)
172
212
  - **10 Backend Options**: MRI C extensions, Rust bindings, FFI, Java (JRuby), language-specific parsers (Prism, Psych, Commonmarker, Markly), and pure Ruby fallback (Citrus)
173
213
  - **Unified API**: Write parsing code once, run on any Ruby implementation
174
214
  - **Grammar Discovery**: Built-in `GrammarFinder` for platform-aware grammar library discovery
175
215
  - **Thread-Safe**: Language registry with thread-safe caching
216
+
176
217
  #### Layer 2: ast-merge (Merge Infrastructure)
177
218
 
178
219
  Ast::Merge builds on tree\_haver to provide:
@@ -185,9 +226,10 @@ Ast::Merge builds on tree\_haver to provide:
185
226
  - **Error Classes**: `ParseError`, `TemplateParseError`, `DestinationParseError`
186
227
  - **Region Detection**: `RegionDetectorBase`, `FencedCodeBlockDetector` for text-based analysis
187
228
  - **RSpec Shared Examples**: Test helpers for implementing new merge gems
229
+
188
230
  ### Creating a New Merge Gem
189
231
 
190
- ``` ruby
232
+ ```ruby
191
233
  require "ast/merge"
192
234
 
193
235
  module MyFormat
@@ -235,7 +277,7 @@ module MyFormat
235
277
 
236
278
  class ConflictResolver < Ast::Merge::ConflictResolverBase
237
279
  def initialize(template_analysis, dest_analysis, preference: :destination,
238
- add_template_only_nodes: false, match_refiner: nil, **options)
280
+ add_template_only_nodes: false, match_refiner: nil, **options)
239
281
  super(
240
282
  strategy: :batch, # or :node, :boundary
241
283
  preference: preference,
@@ -257,7 +299,7 @@ module MyFormat
257
299
  class MergeResult < Ast::Merge::MergeResultBase
258
300
  def initialize(**options)
259
301
  super(**options)
260
- @statistics = { merged_count: 0 }
302
+ @statistics = {merged_count: 0}
261
303
  end
262
304
 
263
305
  def to_my_format
@@ -280,27 +322,27 @@ end
280
322
 
281
323
  ### Base Classes Reference
282
324
 
283
- | Base Class | Purpose | Key Methods to Implement |
284
- | --- | --- | --- |
285
- | `SmartMergerBase` | Main merge orchestration | `analysis_class`, `perform_merge` |
286
- | `ConflictResolverBase` | Resolve node conflicts | `resolve_batch` or `resolve_node_pair` |
287
- | `MergeResultBase` | Track merge results | `to_s`, format-specific output |
288
- | `MatchRefinerBase` | Fuzzy node matching | `similarity` |
289
- | `ContentMatchRefiner` | Text content fuzzy matching | Ready to use |
290
- | `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` |
291
333
 
292
334
  ### ContentMatchRefiner
293
335
 
294
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.
295
337
 
296
- ``` ruby
338
+ ```ruby
297
339
  # Basic usage - match nodes with 70% similarity
298
340
  refiner = Ast::Merge::ContentMatchRefiner.new(threshold: 0.7)
299
341
 
300
342
  # Only match specific node types
301
343
  refiner = Ast::Merge::ContentMatchRefiner.new(
302
344
  threshold: 0.6,
303
- node_types: [:paragraph, :heading]
345
+ node_types: [:paragraph, :heading],
304
346
  )
305
347
 
306
348
  # Custom weights for scoring
@@ -309,14 +351,14 @@ refiner = Ast::Merge::ContentMatchRefiner.new(
309
351
  weights: {
310
352
  content: 0.8, # Levenshtein similarity (default: 0.7)
311
353
  length: 0.1, # Length similarity (default: 0.15)
312
- position: 0.1 # Position in document (default: 0.15)
313
- }
354
+ position: 0.1, # Position in document (default: 0.15)
355
+ },
314
356
  )
315
357
 
316
358
  # Custom content extraction
317
359
  refiner = Ast::Merge::ContentMatchRefiner.new(
318
360
  threshold: 0.7,
319
- content_extractor: ->(node) { node.text_content.downcase.strip }
361
+ content_extractor: ->(node) { node.text_content.downcase.strip },
320
362
  )
321
363
 
322
364
  # Use with a merger
@@ -324,26 +366,28 @@ merger = MyFormat::SmartMerger.new(
324
366
  template,
325
367
  destination,
326
368
  preference: :template,
327
- match_refiner: refiner
369
+ match_refiner: refiner,
328
370
  )
329
371
  ```
330
372
 
331
373
  This is particularly useful for:
374
+
332
375
  - Paragraphs with minor edits (typos, rewording)
333
376
  - Headings with slight changes
334
377
  - Comments with updated text
335
378
  - Any text-based node that may have been slightly modified
379
+
336
380
  ### Namespace Reference
337
381
 
338
382
  The `Ast::Merge` module is organized into several namespaces, each with detailed documentation:
339
383
 
340
- | Namespace | Purpose | Documentation |
341
- | --- | --- | --- |
342
- | `Ast::Merge::Detector` | Region detection and merging | [lib/ast/merge/detector/README.md](lib/ast/merge/detector/README.md) |
343
- | `Ast::Merge::Recipe` | YAML-based merge recipes | [lib/ast/merge/recipe/README.md](lib/ast/merge/recipe/README.md) |
344
- | `Ast::Merge::Comment` | Comment parsing and representation | [lib/ast/merge/comment/README.md](lib/ast/merge/comment/README.md) |
345
- | `Ast::Merge::Text` | Plain text AST parsing | [lib/ast/merge/text/README.md](lib/ast/merge/text/README.md) |
346
- | `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) |
347
391
 
348
392
  **Key Classes by Namespace:**
349
393
 
@@ -352,46 +396,47 @@ The `Ast::Merge` module is organized into several namespaces, each with detailed
352
396
  - **Comment**: `Line`, `Block`, `Empty`, `Parser`, `Style`
353
397
  - **Text**: `SmartMerger`, `FileAnalysis`, `LineNode`, `WordNode`, `Section`
354
398
  - **RSpec**: Shared examples and dependency tags for testing `*-merge` implementations
399
+
355
400
  ## 💡 Info you can shake a stick at
356
401
 
357
- | 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) |
358
- | --- | --- |
359
- | 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) |
360
- | 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) |
361
- | 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) |
362
- | 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) |
363
- | 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) |
364
- | 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) |
365
- | 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) |
366
- | 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) |
367
- | 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) |
368
- | `...` 💖 | [![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] |
369
414
 
370
415
  ### Compatibility
371
416
 
372
417
  Compatible with MRI Ruby 3.2.0+, and concordant releases of JRuby, and TruffleRuby.
373
418
 
374
- | 🚚 *Amazing* test matrix was brought to you by | 🔎 appraisal2 🔎 and the color 💚 green 💚 |
375
- | --- | --- |
376
- | 👟 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] ✨ |
377
422
 
378
423
  ### Federated DVCS
379
424
 
380
425
  <details markdown="1">
381
426
  <summary>Find this repo on federated forges (Coming soon!)</summary>
382
427
 
383
- | Federated [DVCS](https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/) Repository | Status | Issues | PRs | Wiki | CI | Discussions |
384
- | --- | --- | --- | --- | --- | --- | --- |
385
- | 🧪 [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 | ➖ |
386
- | 🧊 [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 | ➖ |
387
- | 🐙 [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) |
388
- | 🎮️ [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] |
389
434
 
390
435
  </details>
391
436
 
392
437
  [gh-discussions]: https://github.com/kettle-rb/ast-merge/discussions
393
438
 
394
- ### 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]
395
440
 
396
441
  Available as part of the Tidelift Subscription.
397
442
 
@@ -400,33 +445,34 @@ Available as part of the Tidelift Subscription.
400
445
 
401
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.
402
447
 
403
- [![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]
404
449
 
405
450
  - 💡Subscribe for support guarantees covering *all* your FLOSS dependencies
406
451
 
407
- - 💡Tidelift is part of [Sonar](https://blog.tidelift.com/tidelift-joins-sonar)
452
+ - 💡Tidelift is part of [Sonar][🏙️entsup-tidelift-sonar]
408
453
 
409
- - 💡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
410
455
  Alternatively:
411
456
 
412
- - [![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]
413
460
 
414
- - [![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]
415
462
 
416
- - [![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)
417
463
  </details>
418
464
 
419
465
  ## ✨ Installation
420
466
 
421
467
  Install the gem and add to the application's Gemfile by executing:
422
468
 
423
- ``` console
469
+ ```console
424
470
  bundle add ast-merge
425
471
  ```
426
472
 
427
473
  If bundler is not being used to manage dependencies, install the gem by executing:
428
474
 
429
- ``` console
475
+ ```console
430
476
  gem install ast-merge
431
477
  ```
432
478
 
@@ -435,19 +481,19 @@ gem install ast-merge
435
481
  <details markdown="1">
436
482
  <summary>For Medium or High Security Installations</summary>
437
483
 
438
- 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
439
- [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
440
486
  by following the instructions below.
441
487
 
442
488
  Add my public key (if you haven’t already, expires 2045-04-29) as a trusted certificate:
443
489
 
444
- ``` console
490
+ ```console
445
491
  gem cert --add <(curl -Ls https://raw.github.com/galtzo-floss/certs/main/pboling.pem)
446
492
  ```
447
493
 
448
494
  You only need to do that once. Then proceed to install with:
449
495
 
450
- ``` console
496
+ ```console
451
497
  gem install ast-merge -P HighSecurity
452
498
  ```
453
499
 
@@ -455,7 +501,7 @@ The `HighSecurity` trust profile will verify signed gems, and not allow the inst
455
501
 
456
502
  If you want to up your security game full-time:
457
503
 
458
- ``` console
504
+ ```console
459
505
  bundle config set --global trust-policy MediumSecurity
460
506
  ```
461
507
 
@@ -474,7 +520,7 @@ Each implementation (like `prism-merge`, `psych-merge`, etc.) has its own SmartM
474
520
 
475
521
  All SmartMerger implementations share these configuration options:
476
522
 
477
- ``` ruby
523
+ ```ruby
478
524
  merger = SomeFormat::Merge::SmartMerger.new(
479
525
  template,
480
526
  destination,
@@ -494,6 +540,7 @@ Control which source wins when both files have the same structural element:
494
540
  - **`:template`** - Template values replace destination values
495
541
  - **`:destination`** (default) - Destination values are preserved
496
542
  - **Hash** - Per-node-type preference (see Advanced Configuration)
543
+
497
544
  ### Template-Only Nodes
498
545
 
499
546
  Control whether to add nodes that only exist in the template:
@@ -501,11 +548,12 @@ Control whether to add nodes that only exist in the template:
501
548
  - **`true`** - Add all template-only nodes
502
549
  - **`false`** (default) - Skip template-only nodes
503
550
  - **Callable** - Filter which template-only nodes to add
551
+
504
552
  #### Callable Filter
505
553
 
506
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:
507
555
 
508
- ``` ruby
556
+ ```ruby
509
557
  # Only add nodes with gem_family signatures
510
558
  merger = SomeFormat::Merge::SmartMerger.new(
511
559
  template,
@@ -513,7 +561,7 @@ merger = SomeFormat::Merge::SmartMerger.new(
513
561
  add_template_only_nodes: ->(node, entry) {
514
562
  sig = entry[:signature]
515
563
  sig.is_a?(Array) && sig.first == :gem_family
516
- }
564
+ },
517
565
  )
518
566
 
519
567
  # Only add link definitions that match a pattern
@@ -523,20 +571,22 @@ merger = Markly::Merge::SmartMerger.new(
523
571
  add_template_only_nodes: ->(node, entry) {
524
572
  entry[:template_node].type == :link_definition &&
525
573
  entry[:signature]&.last&.include?("gem")
526
- }
574
+ },
527
575
  )
528
576
  ```
529
577
 
530
578
  The `entry` hash contains:
579
+
531
580
  - `:template_node` - The node being considered for addition
532
581
  - `:signature` - The node's signature (Array or other value)
533
582
  - `:template_index` - Index in the template statements
534
583
  - `:dest_index` - Always `nil` for template-only nodes
584
+
535
585
  ## 🔧 Basic Usage
536
586
 
537
587
  ### Using Shared Examples in Tests
538
588
 
539
- ``` ruby
589
+ ```ruby
540
590
  # spec/spec_helper.rb
541
591
  require "ast/merge/rspec/shared_examples"
542
592
 
@@ -561,6 +611,7 @@ end
561
611
  - `"Ast::Merge::DebugLogger"` - Tests for DebugLogger implementations
562
612
  - `"Ast::Merge::FileAnalysisBase"` - Tests for FileAnalysis implementations
563
613
  - `"Ast::Merge::MergerConfig"` - Tests for SmartMerger implementations
614
+
564
615
  ## 🎛️ Advanced Configuration
565
616
 
566
617
  ### Freeze Blocks
@@ -570,11 +621,12 @@ to preserve content exactly as-is, preventing any changes from the template.
570
621
  This is useful for hand-edited customizations you never want overwritten.
571
622
 
572
623
  A freeze block consists of:
624
+
573
625
  - A **start marker** comment (e.g., `# mytoken:freeze`)
574
626
  - The protected content
575
627
  - An **end marker** comment (e.g., `# mytoken:unfreeze`)
576
- <!-- end list -->
577
- ``` ruby
628
+
629
+ ```ruby
578
630
  # In a Ruby file with prism-merge:
579
631
  class MyApp
580
632
  # prism-merge:freeze
@@ -595,16 +647,16 @@ freeze token (the `token` in `token:freeze`), which defaults to the gem name (e.
595
647
  Different file formats use different comment syntaxes. The merge tools detect freeze markers
596
648
  using the appropriate pattern for each format:
597
649
 
598
- | Pattern Type | Start Marker | End Marker | Languages |
599
- | --- | --- | --- | --- |
600
- | `:hash_comment` | `# token:freeze` | `# token:unfreeze` | Ruby, Python, YAML, Bash, Shell |
601
- | `:html_comment` | `<!-- token:freeze -->` | `<!-- token:unfreeze -->` | HTML, XML, Markdown |
602
- | `:c_style_line` | `// token:freeze` | `// token:unfreeze` | C (C99+), C++, JavaScript, TypeScript, Java, C\#, Go, Rust, Swift, Kotlin, PHP, JSONC |
603
- | `: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 |
604
656
 
605
- | 📍 NOTE |
606
- | --- |
607
- | CSS only supports block comments (`/* */`), not line comments. |
657
+ | 📍 NOTE |
658
+ |-------------------------------------------------------------------|
659
+ | CSS only supports block comments (`/* */`), not line comments. |
608
660
  | JSON does not support comments; use JSONC for JSON with comments. |
609
661
 
610
662
  ### Per-Node-Type Preference with `node_typing`
@@ -624,8 +676,8 @@ preferences for different types of nodes (e.g., prefer template for linter confi
624
676
 
625
677
  - `:default` key for the fallback preference
626
678
  - Custom keys matching the `merge_type` values from your `node_typing`
627
- <!-- end list -->
628
- ``` ruby
679
+
680
+ ```ruby
629
681
  # Example: Prefer template for lint gem configs, destination for everything else
630
682
  node_typing = {
631
683
  call_node: ->(node) {
@@ -653,7 +705,7 @@ merger = Prism::Merge::SmartMerger.new(
653
705
  The `Ast::Merge::NodeTyping::Wrapper` class wraps an AST node and adds a `merge_type` attribute.
654
706
  It delegates all method calls to the wrapped node, so it can be used transparently in place of the original node.
655
707
 
656
- ``` ruby
708
+ ```ruby
657
709
  # Wrap a node with a custom merge_type
658
710
  wrapped = Ast::Merge::NodeTyping::Wrapper.new(original_node, :special_config)
659
711
  wrapped.merge_type # => :special_config
@@ -663,7 +715,7 @@ wrapped.location # => delegates to original_node.location
663
715
 
664
716
  #### NodeTyping Utility Methods
665
717
 
666
- ``` ruby
718
+ ```ruby
667
719
  # Process a node through the node_typing configuration
668
720
  processed = Ast::Merge::NodeTyping.process(node, node_typing_config)
669
721
 
@@ -682,7 +734,7 @@ Ast::Merge::NodeTyping.unwrap(wrapped_node) # => original_node
682
734
  Even without `node_typing`, you can use a Hash-based preference to set a default
683
735
  and document your intention for future per-type customization:
684
736
 
685
- ``` ruby
737
+ ```ruby
686
738
  # Simple Hash preference (functionally equivalent to preference: :destination)
687
739
  merger = MyMerger.new(
688
740
  template_content,
@@ -695,11 +747,11 @@ merger = MyMerger.new(
695
747
 
696
748
  The `MergerConfig` class provides factory methods that support all options:
697
749
 
698
- ``` ruby
750
+ ```ruby
699
751
  # Create config preferring destination
700
752
  config = Ast::Merge::MergerConfig.destination_wins(
701
753
  freeze_token: "my-freeze",
702
- : my_generator,
754
+ signature_generator: my_generator,
703
755
  node_typing: my_typing,
704
756
  )
705
757
 
@@ -718,17 +770,17 @@ Raising a monthly budget of... "dollars" would make the project more sustainable
718
770
 
719
771
  We welcome both individual and corporate sponsors\! We also offer a
720
772
  wide array of funding channels to account for your preferences
721
- (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).
722
774
 
723
775
  **If you're working in a company that's making significant use of kettle-rb tools we'd
724
776
  appreciate it if you suggest to your company to become a kettle-rb sponsor.**
725
777
 
726
778
  You can support the development of kettle-rb tools via
727
- [GitHub Sponsors](https://github.com/sponsors/pboling),
728
- [Liberapay](https://liberapay.com/pboling/donate),
729
- [PayPal](https://www.paypal.com/paypalme/peterboling),
730
- [Open Collective](https://opencollective.com/kettle-rb)
731
- 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].
732
784
 
733
785
  | 📍 NOTE |
734
786
  | --- |
@@ -736,9 +788,9 @@ and [Tidelift](https://tidelift.com/subscription/pkg/rubygems-ast-merge?utm_sour
736
788
 
737
789
  ### Open Collective for Individuals
738
790
 
739
- 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]\]
740
792
 
741
- 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.
742
794
 
743
795
  <!-- OPENCOLLECTIVE-INDIVIDUALS:START -->
744
796
  No backers yet. Be the first\!
@@ -746,9 +798,9 @@ No backers yet. Be the first\!
746
798
 
747
799
  ### Open Collective for Organizations
748
800
 
749
- 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]\]
750
802
 
751
- 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.
752
804
 
753
805
  <!-- OPENCOLLECTIVE-ORGANIZATIONS:START -->
754
806
  No sponsors yet. Be the first\!
@@ -762,48 +814,48 @@ I’m driven by a passion to foster a thriving open-source community – a space
762
814
 
763
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`.
764
816
 
765
- 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.
766
818
 
767
- **[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**
768
820
 
769
- [![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]
770
822
 
771
823
  ## 🔐 Security
772
824
 
773
- See [SECURITY.md](SECURITY.md).
825
+ See [SECURITY.md][🔐security].
774
826
 
775
827
  ## 🤝 Contributing
776
828
 
777
829
  If you need some ideas of where to help, you could work on adding more code coverage,
778
- 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],
779
831
  or use the gem and think about how it could be better.
780
832
 
781
- 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.
782
834
 
783
- See [CONTRIBUTING.md](CONTRIBUTING.md) for more detailed instructions.
835
+ See [CONTRIBUTING.md][🤝contributing] for more detailed instructions.
784
836
 
785
837
  ### 🚀 Release Instructions
786
838
 
787
- See [CONTRIBUTING.md](CONTRIBUTING.md).
839
+ See [CONTRIBUTING.md][🤝contributing].
788
840
 
789
841
  ### Code Coverage
790
842
 
791
- [![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]
792
844
 
793
- [![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]
794
846
 
795
- [![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]
796
848
 
797
849
  ### 🪇 Code of Conduct
798
850
 
799
851
  Everyone interacting with this project's codebases, issue trackers,
800
- 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].
801
853
 
802
854
  ## 🌈 Contributors
803
855
 
804
- [![Contributors](https://contrib.rocks/image?repo=kettle-rb/ast-merge)](https://github.com/kettle-rb/ast-merge/graphs/contributors)
856
+ [![Contributors][🖐contributors-img]][🖐contributors]
805
857
 
806
- Made with [contributors-img](https://contrib.rocks).
858
+ Made with [contributors-img][🖐contrib-rocks].
807
859
 
808
860
  Also see GitLab Contributors: <https://gitlab.com/kettle-rb/ast-merge/-/graphs/main>
809
861
 
@@ -822,24 +874,24 @@ Also see GitLab Contributors: <https://gitlab.com/kettle-rb/ast-merge/-/graphs/m
822
874
 
823
875
  ## 📌 Versioning
824
876
 
825
- 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].
826
878
  Violations of this scheme should be reported as bugs.
827
879
  Specifically, if a minor or patch version is released that breaks backward compatibility,
828
880
  a new version should be immediately released that restores compatibility.
829
881
  Breaking changes to the public API will only be introduced with new major versions.
830
882
 
831
883
  > dropping support for a platform is both obviously and objectively a breaking change <br/>
832
- > —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]
833
885
 
834
886
  I understand that policy doesn't work universally ("exceptions to every rule\!"),
835
887
  but it is the policy here.
836
888
  As such, in many cases it is good to specify a dependency on this library using
837
- 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.
838
890
 
839
891
  For example:
840
892
 
841
- ``` ruby
842
- 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
843
895
  ```
844
896
 
845
897
  <details markdown="1">
@@ -851,16 +903,17 @@ is a *breaking change* to an API, and for that reason the bike shedding is endle
851
903
  To get a better understanding of how SemVer is intended to work over a project's lifetime,
852
904
  read this article from the creator of SemVer:
853
905
 
854
- - ["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
+
855
908
  </details>
856
909
 
857
- See [CHANGELOG.md](CHANGELOG.md) for a list of releases.
910
+ See [CHANGELOG.md][📌changelog] for a list of releases.
858
911
 
859
912
  ## 📄 License
860
913
 
861
914
  The gem is available as open source under the terms of
862
- the [MIT License](LICENSE.txt) [![License: MIT](https://img.shields.io/badge/License-MIT-259D6C.svg)](https://opensource.org/licenses/MIT).
863
- 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].
864
917
 
865
918
  ### © Copyright
866
919
 
@@ -887,11 +940,11 @@ Please consider sponsoring me or the project.
887
940
 
888
941
  To join the community or get help 👇️ Join the Discord.
889
942
 
890
- [![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]
891
944
 
892
945
  To say "thanks\!" ☝️ Join the Discord or 👇️ send money.
893
946
 
894
- [![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]
895
948
 
896
949
  ### Please give the project a star ⭐ ♥.
897
950
 
@@ -932,7 +985,6 @@ Thanks for RTFM. ☺️
932
985
  [✉️discord-invite-img-ftb]: https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord
933
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
934
987
  [✉️ruby-friends]: https://app.daily.dev/squads/rubyfriends
935
-
936
988
  [✇bundle-group-pattern]: https://gist.github.com/pboling/4564780
937
989
  [⛳️gem-namespace]: https://github.com/kettle-rb/ast-merge
938
990
  [⛳️namespace-img]: https://img.shields.io/badge/namespace-Ast::Merge-3C2D2D.svg?style=square&logo=ruby&logoColor=white
@@ -1056,7 +1108,7 @@ Thanks for RTFM. ☺️
1056
1108
  [📌gitmoji]: https://gitmoji.dev
1057
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
1058
1110
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
1059
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-2.544-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
1060
1112
  [🔐security]: SECURITY.md
1061
1113
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
1062
1114
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -1076,6 +1128,3 @@ Thanks for RTFM. ☺️
1076
1128
  [💎appraisal2]: https://github.com/appraisal-rb/appraisal2
1077
1129
  [💎appraisal2-img]: https://img.shields.io/badge/appraised_by-appraisal2-34495e.svg?plastic&logo=ruby&logoColor=white
1078
1130
  [💎d-in-dvcs]: https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/
1079
-
1080
- [ts-jsonc]: https://gitlab.com/WhyNotHugo/tree-sitter-jsonc
1081
- [dotenv]: https://github.com/bkeepers/dotenv