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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +95 -1
- data/README.md +258 -186
- data/exe/ast-merge-recipe +20 -0
- data/lib/ast/merge/conflict_resolver_base.rb +47 -1
- data/lib/ast/merge/diff_mapper_base.rb +245 -0
- data/lib/ast/merge/emitter_base.rb +123 -0
- data/lib/ast/merge/freeze_node_base.rb +9 -0
- data/lib/ast/merge/navigable/injection_point.rb +132 -0
- data/lib/ast/merge/navigable/injection_point_finder.rb +98 -0
- data/lib/ast/merge/navigable/statement.rb +380 -0
- data/lib/ast/merge/navigable.rb +20 -0
- data/lib/ast/merge/node_typing.rb +21 -0
- data/lib/ast/merge/partial_template_merger_base.rb +4 -2
- data/lib/ast/merge/recipe/preset.rb +18 -0
- data/lib/ast/merge/recipe/runner.rb +8 -1
- data/lib/ast/merge/version.rb +1 -1
- data/lib/ast/merge.rb +3 -3
- data.tar.gz.sig +0 -0
- metadata +34 -9
- metadata.gz.sig +0 -0
- data/lib/ast/merge/navigable_statement.rb +0 -625
data/README.md
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
| 📍 NOTE
|
|
2
|
-
|
|
3
|
-
| RubyGems (the [GitHub org]
|
|
4
|
-
| Ultimately [4 maintainers]
|
|
5
|
-
| It is a [complicated story]
|
|
6
|
-
| Simply put - there was active policy for adding or removing maintainers/owners of [rubygems]
|
|
7
|
-
| I'm adding notes like this to gems because I [don't condone theft]
|
|
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]
|
|
10
|
-
| Once available I will publish there exclusively; unless RubyCentral makes amends with the community.
|
|
11
|
-
| The ["Technology for Humans: Joel Draper"]
|
|
12
|
-
| See [here]
|
|
13
|
-
| What I'm doing: A (WIP) proposal for [bundler/gem scopes]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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
|
|
63
|
-
|
|
64
|
-
| [tree_haver][tree_haver] | Multi
|
|
65
|
-
| [ast-merge][ast-merge] | Text
|
|
66
|
-
| [bash-merge][bash-merge] |
|
|
67
|
-
| [commonmarker-merge][commonmarker-merge] | Markdown
|
|
68
|
-
| [dotenv-merge][dotenv-merge] | Dotenv
|
|
69
|
-
| [json-merge][json-merge] |
|
|
70
|
-
| [jsonc-merge][jsonc-merge] | JSONC
|
|
71
|
-
| [markdown-merge][markdown-merge] | Markdown
|
|
72
|
-
| [markly-merge][markly-merge] |
|
|
73
|
-
| [prism-merge][prism-merge] | Ruby
|
|
74
|
-
| [psych-merge][psych-merge] | YAML
|
|
75
|
-
| [rbs-merge][rbs-merge] | RBS
|
|
76
|
-
| [toml-merge][toml-merge] |
|
|
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
|
|
81
|
-
|
|
82
|
-
| [kettle-dev]
|
|
83
|
-
| [kettle-jem]
|
|
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]
|
|
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
|
-
```
|
|
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
|
-
|
|
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 = {
|
|
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
|
|
261
|
-
|
|
262
|
-
| `SmartMergerBase`
|
|
263
|
-
| `ConflictResolverBase` | Resolve node conflicts
|
|
264
|
-
| `MergeResultBase`
|
|
265
|
-
| `MatchRefinerBase`
|
|
266
|
-
| `ContentMatchRefiner`
|
|
267
|
-
| `FileAnalyzable`
|
|
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
|
-
```
|
|
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
|
|
318
|
-
|
|
319
|
-
| `Ast::Merge::Detector` | Region detection and merging
|
|
320
|
-
| `Ast::Merge::Recipe`
|
|
321
|
-
| `Ast::Merge::Comment`
|
|
322
|
-
| `Ast::Merge::Text`
|
|
323
|
-
| `Ast::Merge::RSpec`
|
|
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
|
|
335
|
-
|
|
336
|
-
| Works with JRuby
|
|
337
|
-
| Works with Truffle Ruby | [![Truffle Ruby 23.1 Compat]
|
|
338
|
-
| Works with MRI Ruby 3
|
|
339
|
-
| Support & Community
|
|
340
|
-
| Source
|
|
341
|
-
| Documentation
|
|
342
|
-
| Compliance
|
|
343
|
-
| Style
|
|
344
|
-
| Maintainer 🎖️
|
|
345
|
-
| `...` 💖
|
|
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] [][🧮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\!
|
|
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]
|
|
361
|
-
|
|
362
|
-
| 🧪 [kettle-rb/ast-merge on GitLab]
|
|
363
|
-
| 🧊 [kettle-rb/ast-merge on CodeBerg]
|
|
364
|
-
| 🐙 [kettle-rb/ast-merge on GitHub]
|
|
365
|
-
| 🎮️ [Discord Server]
|
|
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 []
|
|
439
|
+
### Enterprise Support [][🏙️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]
|
|
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]
|
|
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]
|
|
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]
|
|
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
|
|
461
|
+
- [![Get help from me on Codementor][👨🏼🏫expsup-codementor-img]][👨🏼🏫expsup-codementor]
|
|
392
462
|
|
|
393
|
-
- [](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
|
-
```
|
|
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
|
-
```
|
|
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]
|
|
416
|
-
[stone\_checksums]
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
|
|
554
|
-
```
|
|
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
|
|
576
|
-
|
|
577
|
-
| `:hash_comment`
|
|
578
|
-
| `:html_comment`
|
|
579
|
-
| `:c_style_line`
|
|
580
|
-
| `:c_style_block` | `/* token:freeze */`
|
|
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
|
-
|
|
605
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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]
|
|
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]
|
|
705
|
-
[Liberapay]
|
|
706
|
-
[PayPal]
|
|
707
|
-
[Open Collective]
|
|
708
|
-
and [Tidelift]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
835
|
+
See [CONTRIBUTING.md][🤝contributing] for more detailed instructions.
|
|
761
836
|
|
|
762
837
|
### 🚀 Release Instructions
|
|
763
838
|
|
|
764
|
-
See [CONTRIBUTING.md]
|
|
839
|
+
See [CONTRIBUTING.md][🤝contributing].
|
|
765
840
|
|
|
766
841
|
### Code Coverage
|
|
767
842
|
|
|
768
|
-
[![Coverage Graph]
|
|
843
|
+
[![Coverage Graph][🏀codecov-g]][🏀codecov]
|
|
769
844
|
|
|
770
|
-
[![Coveralls Test Coverage]
|
|
845
|
+
[![Coveralls Test Coverage][🏀coveralls-img]][🏀coveralls]
|
|
771
846
|
|
|
772
|
-
[![QLTY Test Coverage]
|
|
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]
|
|
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]
|
|
856
|
+
[![Contributors][🖐contributors-img]][🖐contributors]
|
|
782
857
|
|
|
783
|
-
Made with [contributors-img]
|
|
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]
|
|
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]
|
|
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]
|
|
889
|
+
the [Pessimistic Version Constraint][📌pvc] with two digits of precision.
|
|
815
890
|
|
|
816
891
|
For example:
|
|
817
892
|
|
|
818
|
-
```
|
|
819
|
-
spec.add_dependency("ast-merge", "~>
|
|
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"]
|
|
906
|
+
- ["Major Version Numbers are Not Sacred"][📌major-versions-not-sacred]
|
|
907
|
+
|
|
832
908
|
</details>
|
|
833
909
|
|
|
834
|
-
See [CHANGELOG.md]
|
|
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]
|
|
840
|
-
See [LICENSE.txt]
|
|
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]
|
|
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]
|
|
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.
|
|
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
|