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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +67 -1
- data/README.md +228 -179
- 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/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/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 +2 -3
- data.tar.gz.sig +0 -0
- metadata +33 -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,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
|
|
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 |
|
|
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]
|
|
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
|
-
```
|
|
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
|
-
|
|
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 = {
|
|
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
|
|
284
|
-
|
|
285
|
-
| `SmartMergerBase`
|
|
286
|
-
| `ConflictResolverBase` | Resolve node conflicts
|
|
287
|
-
| `MergeResultBase`
|
|
288
|
-
| `MatchRefinerBase`
|
|
289
|
-
| `ContentMatchRefiner`
|
|
290
|
-
| `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` |
|
|
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
|
-
```
|
|
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
|
|
341
|
-
|
|
342
|
-
| `Ast::Merge::Detector` | Region detection and merging
|
|
343
|
-
| `Ast::Merge::Recipe`
|
|
344
|
-
| `Ast::Merge::Comment`
|
|
345
|
-
| `Ast::Merge::Text`
|
|
346
|
-
| `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) |
|
|
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
|
|
358
|
-
|
|
359
|
-
| Works with JRuby
|
|
360
|
-
| Works with Truffle Ruby | [![Truffle Ruby 23.1 Compat]
|
|
361
|
-
| Works with MRI Ruby 3
|
|
362
|
-
| Support & Community
|
|
363
|
-
| Source
|
|
364
|
-
| Documentation
|
|
365
|
-
| Compliance
|
|
366
|
-
| Style
|
|
367
|
-
| Maintainer 🎖️
|
|
368
|
-
| `...` 💖
|
|
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] |
|
|
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\!
|
|
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]
|
|
384
|
-
|
|
385
|
-
| 🧪 [kettle-rb/ast-merge on GitLab]
|
|
386
|
-
| 🧊 [kettle-rb/ast-merge on CodeBerg]
|
|
387
|
-
| 🐙 [kettle-rb/ast-merge on GitHub]
|
|
388
|
-
| 🎮️ [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] |
|
|
389
434
|
|
|
390
435
|
</details>
|
|
391
436
|
|
|
392
437
|
[gh-discussions]: https://github.com/kettle-rb/ast-merge/discussions
|
|
393
438
|
|
|
394
|
-
### Enterprise Support []
|
|
439
|
+
### Enterprise Support [][🏙️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]
|
|
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]
|
|
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]
|
|
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]
|
|
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
|
|
461
|
+
- [![Get help from me on Codementor][👨🏼🏫expsup-codementor-img]][👨🏼🏫expsup-codementor]
|
|
415
462
|
|
|
416
|
-
- [](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
|
-
```
|
|
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
|
-
```
|
|
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]
|
|
439
|
-
[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
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
|
|
577
|
-
```
|
|
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
|
|
599
|
-
|
|
600
|
-
| `:hash_comment`
|
|
601
|
-
| `:html_comment`
|
|
602
|
-
| `:c_style_line`
|
|
603
|
-
| `: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 |
|
|
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
|
-
|
|
628
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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]
|
|
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]
|
|
728
|
-
[Liberapay]
|
|
729
|
-
[PayPal]
|
|
730
|
-
[Open Collective]
|
|
731
|
-
and [Tidelift]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
835
|
+
See [CONTRIBUTING.md][🤝contributing] for more detailed instructions.
|
|
784
836
|
|
|
785
837
|
### 🚀 Release Instructions
|
|
786
838
|
|
|
787
|
-
See [CONTRIBUTING.md]
|
|
839
|
+
See [CONTRIBUTING.md][🤝contributing].
|
|
788
840
|
|
|
789
841
|
### Code Coverage
|
|
790
842
|
|
|
791
|
-
[![Coverage Graph]
|
|
843
|
+
[![Coverage Graph][🏀codecov-g]][🏀codecov]
|
|
792
844
|
|
|
793
|
-
[![Coveralls Test Coverage]
|
|
845
|
+
[![Coveralls Test Coverage][🏀coveralls-img]][🏀coveralls]
|
|
794
846
|
|
|
795
|
-
[![QLTY Test Coverage]
|
|
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]
|
|
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]
|
|
856
|
+
[![Contributors][🖐contributors-img]][🖐contributors]
|
|
805
857
|
|
|
806
|
-
Made with [contributors-img]
|
|
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]
|
|
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]
|
|
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]
|
|
889
|
+
the [Pessimistic Version Constraint][📌pvc] with two digits of precision.
|
|
838
890
|
|
|
839
891
|
For example:
|
|
840
892
|
|
|
841
|
-
```
|
|
842
|
-
spec.add_dependency("ast-merge", "~>
|
|
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"]
|
|
906
|
+
- ["Major Version Numbers are Not Sacred"][📌major-versions-not-sacred]
|
|
907
|
+
|
|
855
908
|
</details>
|
|
856
909
|
|
|
857
|
-
See [CHANGELOG.md]
|
|
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]
|
|
863
|
-
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].
|
|
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]
|
|
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]
|
|
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.
|
|
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
|