tree_haver 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md ADDED
@@ -0,0 +1,1260 @@
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
+
15
+ [rubygems-org]: https://github.com/rubygems/
16
+ [draper-security]: https://joel.drapper.me/p/ruby-central-security-measures/
17
+ [draper-takeover]: https://joel.drapper.me/p/ruby-central-takeover/
18
+ [ellen-takeover]: https://pup-e.com/blog/goodbye-rubygems/
19
+ [simi-removed]: https://www.reddit.com/r/ruby/s/gOk42POCaV
20
+ [martin-removed]: https://bsky.app/profile/martinemde.com/post/3m3occezxxs2q
21
+ [draper-lies]: https://joel.drapper.me/p/ruby-central-fact-check/
22
+ [draper-theft]: https://joel.drapper.me/p/ruby-central/
23
+ [reinteractive]: https://reinteractive.com/ruby-on-rails
24
+ [gem-coop]: https://gem.coop
25
+ [gem-naming]: https://github.com/gem-coop/gem.coop/issues/12
26
+ [martin-ann]: https://martinemde.com/2025/10/05/announcing-gem-coop.html
27
+ [gem-scopes]: https://github.com/galtzo-floss/bundle-namespace
28
+ [gem-server]: https://github.com/galtzo-floss/gem-server
29
+ [reinteractive-podcast]: https://youtu.be/_H4qbtC5qzU?si=BvuBU90R2wAqD2E6
30
+ [bundler-maint-policy]: https://github.com/ruby/rubygems/blob/b1ab33a3d52310a84d16b193991af07f5a6a07c0/doc/bundler/playbooks/TEAM_CHANGES.md
31
+ [rubygems-maint-policy]: https://github.com/ruby/rubygems/blob/b1ab33a3d52310a84d16b193991af07f5a6a07c0/doc/rubygems/POLICIES.md?plain=1#L187-L196
32
+ [policy-fail]: https://www.reddit.com/r/ruby/comments/1ove9vp/rubycentral_hates_this_one_fact/
33
+
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
+
36
+ [🖼️galtzo-i]: https://logos.galtzo.com/assets/images/galtzo-floss/avatar-192px.svg
37
+ [🖼️galtzo-discord]: https://discord.gg/3qme4XHNKN
38
+ [🖼️ruby-lang-i]: https://logos.galtzo.com/assets/images/ruby-lang/avatar-192px.svg
39
+ [🖼️ruby-lang]: https://www.ruby-lang.org/
40
+ [🖼️kettle-rb-i]: https://logos.galtzo.com/assets/images/kettle-rb/avatar-192px.svg
41
+ [🖼️kettle-rb]: https://github.com/kettle-rb
42
+
43
+ # 🌴 TreeHaver
44
+
45
+ [![Version][👽versioni]][👽version] [![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
+
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
+
49
+ ---
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.
52
+
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]
54
+
55
+ ## 🌻 Synopsis
56
+
57
+ TreeHaver is a cross-Ruby adapter for the [Tree-sitter](https://tree-sitter.github.io/tree-sitter/) parsing library that works seamlessly across MRI Ruby, JRuby, and TruffleRuby. It provides a unified API for parsing source code using Tree-sitter grammars, regardless of your Ruby implementation.
58
+
59
+ ### The Adapter Pattern: Like Faraday, but for Parsing
60
+
61
+ If you've used [Faraday](https://github.com/lostisland/faraday), [multi_json](https://github.com/intridea/multi_json), or [multi_xml](https://github.com/sferik/multi_xml), you'll feel right at home with TreeHaver. These gems share a common philosophy:
62
+
63
+ | Gem | Unified API for | Backend Examples |
64
+ |-----|-----------------|------------------|
65
+ | **Faraday** | HTTP requests | Net::HTTP, Typhoeus, Patron, Excon |
66
+ | **multi_json** | JSON parsing | Oj, Yajl, JSON gem |
67
+ | **multi_xml** | XML parsing | Nokogiri, LibXML, Ox |
68
+ | **TreeHaver** | Tree-sitter parsing | ruby_tree_sitter, tree_stump, FFI, Java |
69
+
70
+ **Write once, run anywhere.** Just as Faraday lets you swap HTTP adapters without changing your code, TreeHaver lets you swap Tree-sitter backends. Your parsing code remains the same whether you're running on MRI with native C extensions, JRuby with FFI, or TruffleRuby.
71
+
72
+ ```ruby
73
+ # Your code stays the same regardless of backend
74
+ parser = TreeHaver::Parser.new
75
+ parser.language = TreeHaver::Language.from_library("/path/to/grammar.so")
76
+ tree = parser.parse(source_code)
77
+
78
+ # TreeHaver automatically picks the best backend:
79
+ # - MRI → ruby_tree_sitter (C extension)
80
+ # - JRuby → FFI (system's libtree-sitter)
81
+ # - TruffleRuby → FFI or MRI backend
82
+ ```
83
+
84
+ ### Key Features
85
+
86
+ - **Universal Ruby Support**: Works on MRI Ruby, JRuby, and TruffleRuby
87
+ - **Multiple Backends**:
88
+ - **MRI Backend**: Leverages the excellent [`ruby_tree_sitter`](https://github.com/Faveod/ruby-tree-sitter) gem (C extension)
89
+ - **Rust Backend**: Uses [`tree_stump`](https://github.com/anthropics/tree_stump) gem (Rust extension with precompiled binaries)
90
+ - **Note**: Currently requires [pboling's fork](https://github.com/pboling/tree_stump/tree/tree_haver) until PRs [#5](https://github.com/joker1007/tree_stump/pull/5), [#7](https://github.com/joker1007/tree_stump/pull/7), [#11](https://github.com/joker1007/tree_stump/pull/11), and [#13 (inclusive of the others)](https://github.com/joker1007/tree_stump/pull/13) are merged
91
+ - **FFI Backend**: Pure Ruby FFI bindings to `libtree-sitter` (ideal for JRuby)
92
+ - **Java Backend**: Support for JRuby's native Java integration, and native java-tree-sitter grammar JARs
93
+ - **Automatic Backend Selection**: Intelligently selects the best backend for your Ruby implementation
94
+ - **Language Agnostic**: Load any Tree-sitter grammar dynamically (TOML, JSON, Ruby, JavaScript, etc.)
95
+ - **Grammar Discovery**: Built-in `GrammarFinder` utility for platform-aware grammar library discovery
96
+ - **Thread-Safe**: Built-in language registry with thread-safe caching
97
+ - **Minimal API Surface**: Simple, focused API that covers the most common Tree-sitter use cases
98
+
99
+ ### Why TreeHaver?
100
+
101
+ Tree-sitter is a powerful parser generator that creates incremental parsers for many programming languages. However, integrating it into Ruby applications can be challenging:
102
+
103
+ - MRI-based C extensions don't work on JRuby
104
+ - FFI-based solutions may not be optimal for MRI
105
+ - Managing different backends for different Ruby implementations is cumbersome
106
+
107
+ TreeHaver solves these problems by providing a unified API that automatically selects the appropriate backend for your Ruby implementation, allowing you to write code once and run it anywhere.
108
+
109
+ ### Comparison with Other Ruby Tree-sitter Bindings
110
+
111
+ | Feature | TreeHaver | [ruby_tree_sitter] | [tree_stump] |
112
+ |---------------------------|--------------------------------|--------------------|----------------|
113
+ | **MRI Ruby** | ✅ Yes | ✅ Yes | ✅ Yes |
114
+ | **JRuby** | ✅ Yes (FFI or Java\* backend) | ❌ No | ❌ No |
115
+ | **TruffleRuby** | ✅ Yes (FFI) | ❌ No | ❓ Unknown |
116
+ | **Backend** | Multi (MRI C, Rust, FFI, Java) | C extension only | Rust extension |
117
+ | **Incremental Parsing** | ✅ Via MRI C/Rust backend | ✅ Yes | ✅ Yes |
118
+ | **Query API** | ⚡ Via MRI/Rust backend | ✅ Yes | ✅ Yes |
119
+ | **Grammar Discovery** | ✅ Built-in `GrammarFinder` | ❌ Manual | ❌ Manual |
120
+ | **Security Validations** | ✅ `PathValidator` | ❌ No | ❌ No |
121
+ | **Language Registration** | ✅ Thread-safe registry | ❌ No | ❌ No |
122
+ | **Native Performance** | ⚡ Backend-dependent | ✅ Native C | ✅ Native Rust |
123
+ | **Precompiled Binaries** | ⚡ Via Rust backend | ✅ Yes | ✅ Yes |
124
+ | **Minimum Ruby** | 3.2+ | 3.0+ | 3.1+ |
125
+
126
+ [ruby_tree_sitter]: https://github.com/Faveod/ruby-tree-sitter
127
+ [tree_stump]: https://github.com/anthropics/tree_stump
128
+
129
+ **Note:** Java backend works with grammar JARs built specifically for java-tree-sitter, or grammar .so files that statically link tree-sitter. This is why FFI is recommended for JRuby & TruffleRuby.
130
+
131
+ **Note:** TreeHaver can use `ruby_tree_sitter` or `tree_stump` as backends, giving you TreeHaver's unified API, grammar discovery, and security features, plus full access to incremental parsing when using those backends.
132
+
133
+ **Note:** `tree_stump` currently requires [pboling's fork (tree_haver branch)](https://github.com/pboling/tree_stump/tree/tree_haver) until upstream PRs [#5](https://github.com/joker1007/tree_stump/pull/5), [#7](https://github.com/joker1007/tree_stump/pull/7), [#11](https://github.com/joker1007/tree_stump/pull/11), and [#13](https://github.com/joker1007/tree_stump/pull/13) are merged.
134
+
135
+ #### When to Use Each
136
+
137
+ **Choose TreeHaver when:**
138
+ - You need JRuby or TruffleRuby support
139
+ - You're building a library that should work across Ruby implementations
140
+ - You want automatic grammar discovery and security validations
141
+ - You want flexibility to switch backends without code changes
142
+ - You need incremental parsing with a unified API
143
+
144
+ **Choose ruby_tree_sitter directly when:**
145
+ - You only target MRI Ruby
146
+ - You need the full Query API without abstraction
147
+ - You want the most battle-tested C bindings
148
+ - You don't need TreeHaver's grammar discovery
149
+
150
+ **Choose tree_stump directly when:**
151
+ - You only target MRI Ruby
152
+ - You prefer Rust-based native extensions
153
+ - You want precompiled binaries without system dependencies
154
+ - You don't need TreeHaver's grammar discovery
155
+ - **Note:** Use [pboling's fork (tree_haver branch)](https://github.com/pboling/tree_stump/tree/tree_haver) until PRs [#5](https://github.com/joker1007/tree_stump/pull/5), [#7](https://github.com/joker1007/tree_stump/pull/7), [#11](https://github.com/joker1007/tree_stump/pull/11), [#13](https://github.com/joker1007/tree_stump/pull/13) are merged
156
+
157
+ ## 💡 Info you can shake a stick at
158
+
159
+ | Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] |
160
+ |-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
161
+ | Works with JRuby | [![JRuby 10.0 Compat][💎jruby-c-i]][🚎11-c-wf] [![JRuby HEAD Compat][💎jruby-headi]][🚎3-hd-wf] |
162
+ | 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] |
163
+ | 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] |
164
+ | Support & Community | [![Join Me on Daily.dev's RubyFriends][✉️ruby-friends-img]][✉️ruby-friends] [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite] [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork] [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor] |
165
+ | Source | [![Source on GitLab.com][📜src-gl-img]][📜src-gl] [![Source on CodeBerg.org][📜src-cb-img]][📜src-cb] [![Source on Github.com][📜src-gh-img]][📜src-gh] [![The best SHA: dQw4w9WgXcQ!][🧮kloc-img]][🧮kloc] |
166
+ | 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] |
167
+ | 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] [![Security Policy][🔐security-img]][🔐security] [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct] [![SemVer 2.0.0][📌semver-img]][📌semver] |
168
+ | 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] |
169
+ | 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] |
170
+ | `...` 💖 | [![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] |
171
+
172
+ ### Compatibility
173
+
174
+ Compatible with MRI Ruby 3.2.0+, and concordant releases of JRuby, and TruffleRuby.
175
+
176
+ | 🚚 _Amazing_ test matrix was brought to you by | 🔎 appraisal2 🔎 and the color 💚 green 💚 |
177
+ |------------------------------------------------|--------------------------------------------------------|
178
+ | 👟 Check it out! | ✨ [github.com/appraisal-rb/appraisal2][💎appraisal2] ✨ |
179
+
180
+ ### Federated DVCS
181
+
182
+ <details markdown="1">
183
+ <summary>Find this repo on federated forges (Coming soon!)</summary>
184
+
185
+ | Federated [DVCS][💎d-in-dvcs] Repository | Status | Issues | PRs | Wiki | CI | Discussions |
186
+ |-------------------------------------------------|-----------------------------------------------------------------------|---------------------------|--------------------------|---------------------------|--------------------------|------------------------------|
187
+ | 🧪 [kettle-rb/tree_haver on GitLab][📜src-gl] | The Truth | [💚][🤝gl-issues] | [💚][🤝gl-pulls] | [💚][📜gl-wiki] | 🐭 Tiny Matrix | ➖ |
188
+ | 🧊 [kettle-rb/tree_haver on CodeBerg][📜src-cb] | An Ethical Mirror ([Donate][🤝cb-donate]) | [💚][🤝cb-issues] | [💚][🤝cb-pulls] | ➖ | ⭕️ No Matrix | ➖ |
189
+ | 🐙 [kettle-rb/tree_haver on GitHub][📜src-gh] | Another Mirror | [💚][🤝gh-issues] | [💚][🤝gh-pulls] | [💚][📜gh-wiki] | 💯 Full Matrix | [💚][gh-discussions] |
190
+ | 🎮️ [Discord Server][✉️discord-invite] | [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite] | [Let's][✉️discord-invite] | [talk][✉️discord-invite] | [about][✉️discord-invite] | [this][✉️discord-invite] | [library!][✉️discord-invite] |
191
+
192
+ </details>
193
+
194
+ [gh-discussions]: https://github.com/kettle-rb/tree_haver/discussions
195
+
196
+ ### Enterprise Support [![Tidelift](https://tidelift.com/badges/package/rubygems/tree_haver)](https://tidelift.com/subscription/pkg/rubygems-tree_haver?utm_source=rubygems-tree_haver&utm_medium=referral&utm_campaign=readme)
197
+
198
+ Available as part of the Tidelift Subscription.
199
+
200
+ <details markdown="1">
201
+ <summary>Need enterprise-level guarantees?</summary>
202
+
203
+ 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.
204
+
205
+ [![Get help from me on Tidelift][🏙️entsup-tidelift-img]][🏙️entsup-tidelift]
206
+
207
+ - 💡Subscribe for support guarantees covering _all_ your FLOSS dependencies
208
+ - 💡Tidelift is part of [Sonar][🏙️entsup-tidelift-sonar]
209
+ - 💡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
210
+
211
+ Alternatively:
212
+
213
+ - [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite]
214
+ - [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork]
215
+ - [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor]
216
+
217
+ </details>
218
+
219
+ ## ✨ Installation
220
+
221
+ Install the gem and add to the application's Gemfile by executing:
222
+
223
+ ```console
224
+ bundle add tree_haver
225
+ ```
226
+
227
+ If bundler is not being used to manage dependencies, install the gem by executing:
228
+
229
+ ```console
230
+ gem install tree_haver
231
+ ```
232
+
233
+ ### 🔒 Secure Installation
234
+
235
+ <details markdown="1">
236
+ <summary>For Medium or High Security Installations</summary>
237
+
238
+ This gem is cryptographically signed, and has verifiable [SHA-256 and SHA-512][💎SHA_checksums] checksums by
239
+ [stone_checksums][💎stone_checksums]. Be sure the gem you install hasn’t been tampered with
240
+ by following the instructions below.
241
+
242
+ Add my public key (if you haven’t already, expires 2045-04-29) as a trusted certificate:
243
+
244
+ ```console
245
+ gem cert --add <(curl -Ls https://raw.github.com/galtzo-floss/certs/main/pboling.pem)
246
+ ```
247
+
248
+ You only need to do that once. Then proceed to install with:
249
+
250
+ ```console
251
+ gem install tree_haver -P HighSecurity
252
+ ```
253
+
254
+ The `HighSecurity` trust profile will verify signed gems, and not allow the installation of unsigned dependencies.
255
+
256
+ If you want to up your security game full-time:
257
+
258
+ ```console
259
+ bundle config set --global trust-policy MediumSecurity
260
+ ```
261
+
262
+ `MediumSecurity` instead of `HighSecurity` is necessary if not all the gems you use are signed.
263
+
264
+ NOTE: Be prepared to track down certs for signed gems and add them the same way you added mine.
265
+
266
+ </details>
267
+
268
+ ## ⚙️ Configuration
269
+
270
+ ### Security Considerations
271
+
272
+ **⚠️ Loading shared libraries (.so/.dylib/.dll) executes arbitrary native code.**
273
+
274
+ TreeHaver provides defense-in-depth validations, but you should understand the risks:
275
+
276
+ #### Attack Vectors Mitigated
277
+
278
+ TreeHaver's `PathValidator` module protects against:
279
+
280
+ - **Path traversal**: Paths containing `/../` or `/./` are rejected
281
+ - **Null byte injection**: Paths containing null bytes are rejected
282
+ - **Non-absolute paths**: Relative paths are rejected to prevent CWD-based attacks
283
+ - **Invalid extensions**: Only `.so`, `.dylib`, and `.dll` files are accepted
284
+ - **Malicious filenames**: Filenames must match a safe pattern (alphanumeric, hyphens, underscores)
285
+ - **Invalid language names**: Language names must be lowercase alphanumeric with underscores
286
+ - **Invalid symbol names**: Symbol names must be valid C identifiers
287
+
288
+ #### Secure Usage
289
+
290
+ ```ruby
291
+ # Standard usage - paths from ENV are validated
292
+ finder = TreeHaver::GrammarFinder.new(:toml)
293
+ path = finder.find_library_path # Validates ENV path before returning
294
+
295
+ # Maximum security - only trusted system directories
296
+ path = finder.find_library_path_safe # Ignores ENV, only /usr/lib etc.
297
+
298
+ # Manual validation
299
+ if TreeHaver::PathValidator.safe_library_path?(user_provided_path)
300
+ language = TreeHaver::Language.from_library(user_provided_path)
301
+ end
302
+
303
+ # Get validation errors for debugging
304
+ errors = TreeHaver::PathValidator.validation_errors(path)
305
+ # => ["Path is not absolute", "Path contains traversal sequence"]
306
+ ```
307
+
308
+ #### Trusted Directories
309
+
310
+ The `find_library_path_safe` method only returns paths in trusted directories.
311
+
312
+ **Default trusted directories:**
313
+
314
+ - `/usr/lib`, `/usr/lib64`
315
+ - `/usr/lib/x86_64-linux-gnu`, `/usr/lib/aarch64-linux-gnu`
316
+ - `/usr/local/lib`
317
+ - `/opt/homebrew/lib`, `/opt/local/lib`
318
+
319
+ **Adding custom trusted directories:**
320
+
321
+ For non-standard installations (Homebrew on Linux, luarocks, mise, asdf, etc.), register additional trusted directories:
322
+
323
+ ```ruby
324
+ # Programmatically at application startup
325
+ TreeHaver::PathValidator.add_trusted_directory("/home/linuxbrew/.linuxbrew/Cellar")
326
+ TreeHaver::PathValidator.add_trusted_directory("~/.local/share/mise/installs/lua")
327
+
328
+ # Or via environment variable (comma-separated, in your shell profile)
329
+ export TREE_HAVER_TRUSTED_DIRS = "/home/linuxbrew/.linuxbrew/Cellar,~/.local/share/mise/installs/lua"
330
+ ```
331
+
332
+ **Example: Fedora Silverblue with Homebrew and luarocks**
333
+
334
+ ```bash
335
+ # In ~/.bashrc or ~/.zshrc
336
+ export TREE_HAVER_TRUSTED_DIRS="/home/linuxbrew/.linuxbrew/Cellar,~/.local/share/mise/installs/lua"
337
+
338
+ # tree-sitter runtime library
339
+ export TREE_SITTER_RUNTIME_LIB=/home/linuxbrew/.linuxbrew/Cellar/tree-sitter/0.26.3/lib/libtree-sitter.so
340
+
341
+ # Language grammar (luarocks-installed)
342
+ export TREE_SITTER_TOML_PATH=~/.local/share/mise/installs/lua/5.4.8/luarocks/lib/luarocks/rocks-5.4/tree-sitter-toml/0.0.31-1/parser/toml.so
343
+ ```
344
+
345
+ #### Recommendations
346
+
347
+ 1. **Production**: Consider using `find_library_path_safe` to ignore ENV overrides
348
+ 2. **Development**: Standard `find_library_path` is convenient for testing
349
+ 3. **User Input**: Always validate paths before passing to `Language.from_library`
350
+ 4. **CI/CD**: Be cautious of ENV vars that could be set by untrusted sources
351
+ 5. **Custom installs**: Register trusted directories via `TREE_HAVER_TRUSTED_DIRS` or `add_trusted_directory`
352
+
353
+ ### Backend Selection
354
+
355
+ TreeHaver automatically selects the best backend for your Ruby implementation, but you can override this behavior:
356
+
357
+ ```ruby
358
+ # Automatic backend selection (default)
359
+ TreeHaver.backend = :auto
360
+
361
+ # Force a specific backend
362
+ TreeHaver.backend = :mri # Use ruby_tree_sitter (MRI only, C extension)
363
+ TreeHaver.backend = :rust # Use tree_stump (MRI, Rust extension with precompiled binaries)
364
+ # Note: Requires pboling's fork until PRs #5, #7, #11, #13 are merged
365
+ # See: https://github.com/pboling/tree_stump/tree/tree_haver
366
+ TreeHaver.backend = :ffi # Use FFI bindings (works on MRI and JRuby)
367
+ TreeHaver.backend = :java # Use Java bindings (JRuby only, coming soon)
368
+ ```
369
+
370
+ **Auto-selection priority on MRI:** MRI → Rust → FFI
371
+
372
+ You can also set the backend via environment variable:
373
+
374
+ ```bash
375
+ export TREE_HAVER_BACKEND=rust
376
+ ```
377
+
378
+ ### Environment Variables
379
+
380
+ TreeHaver recognizes several environment variables for configuration:
381
+
382
+ **Note**: All path-based environment variables are validated before use. Invalid paths are ignored.
383
+
384
+ #### Security Configuration
385
+
386
+ - **`TREE_HAVER_TRUSTED_DIRS`**: Comma-separated list of additional trusted directories for grammar libraries
387
+ ```bash
388
+ # For Homebrew on Linux and luarocks
389
+ export TREE_HAVER_TRUSTED_DIRS="/home/linuxbrew/.linuxbrew/Cellar,~/.local/share/mise/installs/lua"
390
+ ```
391
+
392
+ Tilde (`~`) is expanded to the user's home directory. Directories listed here are considered safe for `find_library_path_safe`.
393
+
394
+ #### Core Runtime Library
395
+
396
+ - **`TREE_SITTER_RUNTIME_LIB`**: Absolute path to the core `libtree-sitter` shared library
397
+ ```bash
398
+ export TREE_SITTER_RUNTIME_LIB=/usr/local/lib/libtree-sitter.so
399
+ ```
400
+
401
+ If not set, TreeHaver tries these names in order:
402
+ - `tree-sitter`
403
+ - `libtree-sitter.so.0`
404
+ - `libtree-sitter.so`
405
+ - `libtree-sitter.dylib`
406
+ - `libtree-sitter.dll`
407
+
408
+ #### Language Symbol Resolution
409
+
410
+ When loading a language grammar, if you don't specify the `symbol:` parameter, TreeHaver resolves it in this precedence:
411
+
412
+ 1. **`TREE_SITTER_LANG_SYMBOL`**: Explicit symbol override
413
+ 2. Guessed from filename (e.g., `libtree-sitter-toml.so` → `tree_sitter_toml`)
414
+ 3. Default fallback (`tree_sitter_toml`)
415
+
416
+ ```bash
417
+ export TREE_SITTER_LANG_SYMBOL=tree_sitter_toml
418
+ ```
419
+
420
+ #### Language Library Paths
421
+
422
+ For specific languages, you can set environment variables to point to grammar libraries:
423
+
424
+ ```bash
425
+ export TREE_SITTER_TOML_PATH=/usr/local/lib/libtree-sitter-toml.so
426
+ export TREE_SITTER_JSON_PATH=/usr/local/lib/libtree-sitter-json.so
427
+ ```
428
+
429
+ #### JRuby-Specific: Java Backend JARs
430
+
431
+ For the Java backend on JRuby:
432
+
433
+ ```bash
434
+ export TREE_SITTER_JAVA_JARS_DIR=/path/to/java-tree-sitter/jars
435
+ ```
436
+
437
+ ### Language Registration
438
+
439
+ Register languages once at application startup for convenient access:
440
+
441
+ ```ruby
442
+ # Register a TOML grammar
443
+ TreeHaver.register_language(
444
+ :toml,
445
+ path: "/usr/local/lib/libtree-sitter-toml.so",
446
+ symbol: "tree_sitter_toml", # optional, will be inferred if omitted
447
+ )
448
+
449
+ # Now you can use the convenient helper
450
+ language = TreeHaver::Language.toml
451
+
452
+ # Or still override path/symbol per-call
453
+ language = TreeHaver::Language.toml(
454
+ path: "/custom/path/libtree-sitter-toml.so",
455
+ )
456
+ ```
457
+
458
+ ### Grammar Discovery with GrammarFinder
459
+
460
+ For libraries that need to automatically locate tree-sitter grammars (like the `*-merge` family of gems), TreeHaver provides the `GrammarFinder` utility class. It handles platform-aware grammar discovery without requiring language-specific code in TreeHaver itself.
461
+
462
+ ```ruby
463
+ # Create a finder for any language
464
+ finder = TreeHaver::GrammarFinder.new(:toml)
465
+
466
+ # Check if the grammar is available
467
+ if finder.available?
468
+ puts "TOML grammar found at: #{finder.find_library_path}"
469
+ else
470
+ puts finder.not_found_message
471
+ # => "Tree-sitter toml grammar not found. Searched: /usr/lib/libtree-sitter-toml.so, ..."
472
+ end
473
+
474
+ # Register the language if available
475
+ finder.register! if finder.available?
476
+
477
+ # Now use the registered language
478
+ language = TreeHaver::Language.toml
479
+ ```
480
+
481
+ #### GrammarFinder Automatic Derivation
482
+
483
+ Given just the language name, `GrammarFinder` automatically derives:
484
+
485
+ | Property | Derived Value (for `:toml`) |
486
+ |----------|----------------------------|
487
+ | ENV var | `TREE_SITTER_TOML_PATH` |
488
+ | Library filename | `libtree-sitter-toml.so` (Linux) or `.dylib` (macOS) |
489
+ | Symbol name | `tree_sitter_toml` |
490
+
491
+ #### Search Order
492
+
493
+ `GrammarFinder` searches for grammars in this order:
494
+
495
+ 1. **Environment variable**: `TREE_SITTER_<LANG>_PATH` (highest priority)
496
+ 2. **Extra paths**: Custom paths provided at initialization
497
+ 3. **System paths**: Common installation directories (`/usr/lib`, `/usr/local/lib`, `/opt/homebrew/lib`, etc.)
498
+
499
+ #### Usage in *-merge Gems
500
+
501
+ The `GrammarFinder` pattern enables clean integration in language-specific merge gems:
502
+
503
+ ```ruby
504
+ # In toml-merge
505
+ finder = TreeHaver::GrammarFinder.new(:toml)
506
+ finder.register! if finder.available?
507
+
508
+ # In json-merge
509
+ finder = TreeHaver::GrammarFinder.new(:json)
510
+ finder.register! if finder.available?
511
+
512
+ # In bash-merge
513
+ finder = TreeHaver::GrammarFinder.new(:bash)
514
+ finder.register! if finder.available?
515
+ ```
516
+
517
+ Each gem uses the same API—only the language name changes.
518
+
519
+ #### Adding Custom Search Paths
520
+
521
+ For non-standard installations, provide extra search paths:
522
+
523
+ ```ruby
524
+ finder = TreeHaver::GrammarFinder.new(:toml, extra_paths: [
525
+ "/opt/custom/lib",
526
+ "/home/user/.local/lib",
527
+ ])
528
+ ```
529
+
530
+ #### Debug Information
531
+
532
+ Get detailed information about the grammar search:
533
+
534
+ ```ruby
535
+ finder = TreeHaver::GrammarFinder.new(:toml)
536
+ puts finder.search_info
537
+ # => {
538
+ # language: :toml,
539
+ # env_var: "TREE_SITTER_TOML_PATH",
540
+ # env_value: nil,
541
+ # symbol: "tree_sitter_toml",
542
+ # library_filename: "libtree-sitter-toml.so",
543
+ # search_paths: ["/usr/lib/libtree-sitter-toml.so", ...],
544
+ # found_path: "/usr/lib/libtree-sitter-toml.so",
545
+ # available: true
546
+ # }
547
+ ```
548
+
549
+ ### Checking Capabilities
550
+
551
+ Different backends may support different features:
552
+
553
+ ```ruby
554
+ TreeHaver.capabilities
555
+ # => { backend: :mri, query: true, bytes_field: true }
556
+ # or
557
+ # => { backend: :ffi, parse: true, query: false, bytes_field: true }
558
+ ```
559
+
560
+ ### Compatibility Mode
561
+
562
+ For codebases migrating from `ruby_tree_sitter`, TreeHaver provides a compatibility shim:
563
+
564
+ ```ruby
565
+ require "tree_haver/compat"
566
+
567
+ # Now TreeSitter constants map to TreeHaver
568
+ parser = TreeSitter::Parser.new # Actually creates TreeHaver::Parser
569
+ ```
570
+
571
+ This is safe and idempotent—if the real `TreeSitter` module is already loaded, the shim does nothing.
572
+
573
+ ## 🔧 Basic Usage
574
+
575
+ ### Quick Start
576
+
577
+ Here's a complete example of parsing TOML with TreeHaver:
578
+
579
+ ```ruby
580
+ require "tree_haver"
581
+
582
+ # Load a language grammar
583
+ language = TreeHaver::Language.from_library(
584
+ "/usr/local/lib/libtree-sitter-toml.so",
585
+ symbol: "tree_sitter_toml",
586
+ )
587
+
588
+ # Create a parser
589
+ parser = TreeHaver::Parser.new
590
+ parser.language = language
591
+
592
+ # Parse some source code
593
+ source = <<~TOML
594
+ [package]
595
+ name = "my-app"
596
+ version = "1.0.0"
597
+ TOML
598
+
599
+ tree = parser.parse(source)
600
+
601
+ # Access the root node
602
+ root = tree.root_node
603
+ puts "Root node type: #{root.type}" # => "document"
604
+
605
+ # Traverse the tree
606
+ root.each do |child|
607
+ puts "Child type: #{child.type}"
608
+ child.each do |grandchild|
609
+ puts " Grandchild type: #{grandchild.type}"
610
+ end
611
+ end
612
+ ```
613
+
614
+ ### Using Language Registration
615
+
616
+ For cleaner code, register languages at startup:
617
+
618
+ ```ruby
619
+ # At application initialization
620
+ TreeHaver.register_language(
621
+ :toml,
622
+ path: "/usr/local/lib/libtree-sitter-toml.so",
623
+ )
624
+
625
+ TreeHaver.register_language(
626
+ :json,
627
+ path: "/usr/local/lib/libtree-sitter-json.so",
628
+ )
629
+
630
+ # Later in your code
631
+ toml_language = TreeHaver::Language.toml
632
+ json_language = TreeHaver::Language.json
633
+
634
+ parser = TreeHaver::Parser.new
635
+ parser.language = toml_language
636
+ tree = parser.parse(toml_source)
637
+ ```
638
+
639
+ ### Parsing Different Languages
640
+
641
+ TreeHaver works with any Tree-sitter grammar:
642
+
643
+ ```ruby
644
+ # Parse Ruby code
645
+ ruby_lang = TreeHaver::Language.from_library(
646
+ "/path/to/libtree-sitter-ruby.so",
647
+ )
648
+ parser = TreeHaver::Parser.new
649
+ parser.language = ruby_lang
650
+ tree = parser.parse("class Foo; end")
651
+
652
+ # Parse JavaScript
653
+ js_lang = TreeHaver::Language.from_library(
654
+ "/path/to/libtree-sitter-javascript.so",
655
+ )
656
+ parser.language = js_lang # Reuse the same parser
657
+ tree = parser.parse("const x = 42;")
658
+ ```
659
+
660
+ ### Walking the AST
661
+
662
+ TreeHaver provides simple node traversal:
663
+
664
+ ```ruby
665
+ tree = parser.parse(source)
666
+ root = tree.root_node
667
+
668
+ # Recursive tree walk
669
+ def walk_tree(node, depth = 0)
670
+ puts "#{" " * depth}#{node.type}"
671
+ node.each { |child| walk_tree(child, depth + 1) }
672
+ end
673
+
674
+ walk_tree(root)
675
+ ```
676
+
677
+ ### Incremental Parsing
678
+
679
+ TreeHaver supports incremental parsing when using the MRI or Rust backends. This is a major performance optimization for editors and IDEs that need to re-parse on every keystroke.
680
+
681
+ ```ruby
682
+ # Check if current backend supports incremental parsing
683
+ if TreeHaver.capabilities[:incremental]
684
+ puts "Incremental parsing is available!"
685
+ end
686
+
687
+ # Initial parse
688
+ parser = TreeHaver::Parser.new
689
+ parser.language = language
690
+ tree = parser.parse_string(nil, "x = 1")
691
+
692
+ # User edits the source: "x = 1" -> "x = 42"
693
+ # Mark the tree as edited (tell tree-sitter what changed)
694
+ tree.edit(
695
+ start_byte: 4, # edit starts at byte 4
696
+ old_end_byte: 5, # old text "1" ended at byte 5
697
+ new_end_byte: 6, # new text "42" ends at byte 6
698
+ start_point: {row: 0, column: 4},
699
+ old_end_point: {row: 0, column: 5},
700
+ new_end_point: {row: 0, column: 6},
701
+ )
702
+
703
+ # Re-parse incrementally - tree-sitter reuses unchanged nodes
704
+ new_tree = parser.parse_string(tree, "x = 42")
705
+ ```
706
+
707
+ **Note:** Incremental parsing requires the MRI (`ruby_tree_sitter`), Rust (`tree_stump`), or Java (`java-tree-sitter`) backend. The FFI backend does not currently support incremental parsing. You can check support with:
708
+
709
+ **Note:** `tree_stump` requires [pboling's fork (tree_haver branch)](https://github.com/pboling/tree_stump/tree/tree_haver) until PRs [#5](https://github.com/joker1007/tree_stump/pull/5), [#7](https://github.com/joker1007/tree_stump/pull/7), [#11](https://github.com/joker1007/tree_stump/pull/11), [#13](https://github.com/joker1007/tree_stump/pull/13) are merged.
710
+
711
+ ```ruby
712
+ tree.supports_editing? # => true if edit() is available
713
+ ```
714
+
715
+ ### Error Handling
716
+
717
+ ```ruby
718
+ begin
719
+ language = TreeHaver::Language.from_library("/path/to/grammar.so")
720
+ rescue TreeHaver::NotAvailable => e
721
+ puts "Failed to load grammar: #{e.message}"
722
+ end
723
+
724
+ # Check if a backend is available
725
+ if TreeHaver.backend_module.nil?
726
+ puts "No TreeHaver backend is available!"
727
+ puts "Install ruby_tree_sitter (MRI) or ensure ffi gem and libtree-sitter are present"
728
+ end
729
+ ```
730
+
731
+ ### Platform-Specific Examples
732
+
733
+ #### MRI Ruby
734
+
735
+ On MRI, TreeHaver uses `ruby_tree_sitter` by default:
736
+
737
+ ```ruby
738
+ # Gemfile
739
+ gem "tree_haver"
740
+ gem "ruby_tree_sitter" # MRI backend
741
+
742
+ # Code - no changes needed, TreeHaver auto-selects MRI backend
743
+ parser = TreeHaver::Parser.new
744
+ ```
745
+
746
+ #### JRuby
747
+
748
+ On JRuby, TreeHaver can use either the FFI backend or the Java backend:
749
+
750
+ **Option 1: FFI Backend (simpler setup)**
751
+
752
+ ```ruby
753
+ # Gemfile
754
+ gem "tree_haver"
755
+ gem "ffi" # Required for FFI backend
756
+
757
+ # Ensure libtree-sitter is installed on your system
758
+ # On macOS with Homebrew:
759
+ # brew install tree-sitter
760
+
761
+ # On Ubuntu/Debian:
762
+ # sudo apt-get install libtree-sitter0 libtree-sitter-dev
763
+
764
+ # Code - TreeHaver auto-selects FFI backend on JRuby
765
+ parser = TreeHaver::Parser.new
766
+ ```
767
+
768
+ **Option 2: Java Backend (native JVM performance)**
769
+
770
+ ```bash
771
+ # 1. Download java-tree-sitter JAR from Maven Central
772
+ mkdir -p vendor/jars
773
+ curl -fSL -o vendor/jars/jtreesitter-0.23.2.jar \
774
+ "https://repo1.maven.org/maven2/io/github/tree-sitter/jtreesitter/0.23.2/jtreesitter-0.23.2.jar"
775
+
776
+ # 2. Set environment variables
777
+ export CLASSPATH="$(pwd)/vendor/jars:$CLASSPATH"
778
+ export LD_LIBRARY_PATH="/path/to/libtree-sitter/lib:$LD_LIBRARY_PATH"
779
+
780
+ # 3. Run with JRuby (requires Java 22+ for Foreign Function API)
781
+ JAVA_OPTS="--enable-native-access=ALL-UNNAMED" jruby your_script.rb
782
+ ```
783
+
784
+ ```ruby
785
+ # Force Java backend
786
+ TreeHaver.backend = :java
787
+
788
+ # Check if Java backend is available
789
+ if TreeHaver::Backends::Java.available?
790
+ puts "Java backend is ready!"
791
+ puts TreeHaver.capabilities
792
+ # => { backend: :java, parse: true, query: true, bytes_field: true, incremental: true }
793
+ end
794
+ ```
795
+
796
+ **⚠️ Java Backend Limitation: Symbol Resolution**
797
+
798
+ The Java backend uses Java's Foreign Function & Memory (FFM) API which loads libraries in isolation. Unlike the system's dynamic linker (`dlopen`), FFM's `SymbolLookup.or()` chains symbol lookups but doesn't resolve dynamic library dependencies.
799
+
800
+ This means grammar `.so` files with unresolved references to `libtree-sitter.so` symbols won't load correctly. Most grammars from luarocks, npm, or other sources have these dependencies.
801
+
802
+ **Recommended approach for JRuby:** Use the **FFI backend**:
803
+
804
+ ```ruby
805
+ # On JRuby, use FFI backend (recommended)
806
+ TreeHaver.backend = :ffi
807
+ ```
808
+
809
+ The FFI backend uses Ruby's FFI gem which relies on the system's dynamic linker, correctly resolving symbol dependencies between `libtree-sitter.so` and grammar libraries.
810
+
811
+ The Java backend will work with:
812
+ - Grammar JARs built specifically for java-tree-sitter (self-contained)
813
+ - Grammar `.so` files that statically link tree-sitter
814
+
815
+ #### TruffleRuby
816
+
817
+ TruffleRuby can use either the MRI or FFI backend:
818
+
819
+ ```ruby
820
+ # Use FFI backend (recommended)
821
+ TreeHaver.backend = :ffi
822
+
823
+ # Or try MRI backend if ruby_tree_sitter compiles on your TruffleRuby version
824
+ TreeHaver.backend = :mri
825
+ ```
826
+
827
+ ### Advanced: Testing with Multiple Backends
828
+
829
+ If you're developing a library that uses TreeHaver, you can test against different backends:
830
+
831
+ ```ruby
832
+ # In your test setup
833
+ RSpec.describe("MyParser") do
834
+ before do
835
+ TreeHaver.reset_backend!(to: :ffi)
836
+ end
837
+
838
+ after do
839
+ TreeHaver.reset_backend!(to: :auto)
840
+ end
841
+
842
+ it "parses correctly with FFI backend" do
843
+ # Your test code
844
+ end
845
+ end
846
+ ```
847
+
848
+ ### Complete Real-World Example
849
+
850
+ Here's a practical example that extracts package names from a TOML file:
851
+
852
+ ```ruby
853
+ require "tree_haver"
854
+
855
+ # Setup
856
+ TreeHaver.register_language(
857
+ :toml,
858
+ path: "/usr/local/lib/libtree-sitter-toml.so",
859
+ )
860
+
861
+ def extract_package_name(toml_content)
862
+ # Create parser
863
+ parser = TreeHaver::Parser.new
864
+ parser.language = TreeHaver::Language.toml
865
+
866
+ # Parse
867
+ tree = parser.parse(toml_content)
868
+ root = tree.root_node
869
+
870
+ # Find [package] table
871
+ root.each do |child|
872
+ next unless child.type == "table"
873
+
874
+ child.each do |table_elem|
875
+ if table_elem.type == "pair"
876
+ # Look for name = "..." pair
877
+ key = table_elem.each.first&.type
878
+ # In a real implementation, you'd extract the text value
879
+ # This is simplified for demonstration
880
+ end
881
+ end
882
+ end
883
+ end
884
+
885
+ # Usage
886
+ toml = <<~TOML
887
+ [package]
888
+ name = "awesome-app"
889
+ version = "2.0.0"
890
+ TOML
891
+
892
+ package_name = extract_package_name(toml)
893
+ ```
894
+
895
+ ## 🦷 FLOSS Funding
896
+
897
+ While kettle-rb tools are free software and will always be, the project would benefit immensely from some funding.
898
+ Raising a monthly budget of... "dollars" would make the project more sustainable.
899
+
900
+ We welcome both individual and corporate sponsors! We also offer a
901
+ wide array of funding channels to account for your preferences
902
+ (although currently [Open Collective][🖇osc] is our preferred funding platform).
903
+
904
+ **If you're working in a company that's making significant use of kettle-rb tools we'd
905
+ appreciate it if you suggest to your company to become a kettle-rb sponsor.**
906
+
907
+ You can support the development of kettle-rb tools via
908
+ [GitHub Sponsors][🖇sponsor],
909
+ [Liberapay][⛳liberapay],
910
+ [PayPal][🖇paypal],
911
+ [Open Collective][🖇osc]
912
+ and [Tidelift][🏙️entsup-tidelift].
913
+
914
+ | 📍 NOTE |
915
+ |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
916
+ | If doing a sponsorship in the form of donation is problematic for your company <br/> from an accounting standpoint, we'd recommend the use of Tidelift, <br/> where you can get a support-like subscription instead. |
917
+
918
+ ### Open Collective for Individuals
919
+
920
+ Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/kettle-rb#backer)]
921
+
922
+ NOTE: [kettle-readme-backers][kettle-readme-backers] updates this list every day, automatically.
923
+
924
+ <!-- OPENCOLLECTIVE-INDIVIDUALS:START -->
925
+ No backers yet. Be the first!
926
+ <!-- OPENCOLLECTIVE-INDIVIDUALS:END -->
927
+
928
+ ### Open Collective for Organizations
929
+
930
+ Become a sponsor and get your logo on our README on GitHub with a link to your site. [[Become a sponsor](https://opencollective.com/kettle-rb#sponsor)]
931
+
932
+ NOTE: [kettle-readme-backers][kettle-readme-backers] updates this list every day, automatically.
933
+
934
+ <!-- OPENCOLLECTIVE-ORGANIZATIONS:START -->
935
+ No sponsors yet. Be the first!
936
+ <!-- OPENCOLLECTIVE-ORGANIZATIONS:END -->
937
+
938
+ [kettle-readme-backers]: https://github.com/kettle-rb/tree_haver/blob/main/exe/kettle-readme-backers
939
+
940
+ ### Another way to support open-source
941
+
942
+ I’m driven by a passion to foster a thriving open-source community – a space where people can tackle complex problems, no matter how small. Revitalizing libraries that have fallen into disrepair, and building new libraries focused on solving real-world challenges, are my passions. I was recently affected by layoffs, and the tech jobs market is unwelcoming. I’m reaching out here because your support would significantly aid my efforts to provide for my family, and my farm (11 🐔 chickens, 2 🐶 dogs, 3 🐰 rabbits, 8 🐈‍ cats).
943
+
944
+ 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`.
945
+
946
+ 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.
947
+
948
+ **[Floss-Funding.dev][🖇floss-funding.dev]: 👉️ No network calls. 👉️ No tracking. 👉️ No oversight. 👉️ Minimal crypto hashing. 💡 Easily disabled nags**
949
+
950
+ [![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]
951
+
952
+ ## 🔐 Security
953
+
954
+ See [SECURITY.md][🔐security].
955
+
956
+ ## 🤝 Contributing
957
+
958
+ If you need some ideas of where to help, you could work on adding more code coverage,
959
+ or if it is already 💯 (see [below](#code-coverage)) check [reek](REEK), [issues][🤝gh-issues], or [PRs][🤝gh-pulls],
960
+ or use the gem and think about how it could be better.
961
+
962
+ We [![Keep A Changelog][📗keep-changelog-img]][📗keep-changelog] so if you make changes, remember to update it.
963
+
964
+ See [CONTRIBUTING.md][🤝contributing] for more detailed instructions.
965
+
966
+ ### 🚀 Release Instructions
967
+
968
+ See [CONTRIBUTING.md][🤝contributing].
969
+
970
+ ### Code Coverage
971
+
972
+ [![Coverage Graph][🏀codecov-g]][🏀codecov]
973
+
974
+ [![Coveralls Test Coverage][🏀coveralls-img]][🏀coveralls]
975
+
976
+ [![QLTY Test Coverage][🏀qlty-covi]][🏀qlty-cov]
977
+
978
+ ### 🪇 Code of Conduct
979
+
980
+ Everyone interacting with this project's codebases, issue trackers,
981
+ chat rooms and mailing lists agrees to follow the [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct].
982
+
983
+ ## 🌈 Contributors
984
+
985
+ [![Contributors][🖐contributors-img]][🖐contributors]
986
+
987
+ Made with [contributors-img][🖐contrib-rocks].
988
+
989
+ Also see GitLab Contributors: [https://gitlab.com/kettle-rb/tree_haver/-/graphs/main][🚎contributors-gl]
990
+
991
+ <details>
992
+ <summary>⭐️ Star History</summary>
993
+
994
+ <a href="https://star-history.com/#kettle-rb/tree_haver&Date">
995
+ <picture>
996
+ <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=kettle-rb/tree_haver&type=Date&theme=dark" />
997
+ <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=kettle-rb/tree_haver&type=Date" />
998
+ <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=kettle-rb/tree_haver&type=Date" />
999
+ </picture>
1000
+ </a>
1001
+
1002
+ </details>
1003
+
1004
+ ## 📌 Versioning
1005
+
1006
+ This Library adheres to [![Semantic Versioning 2.0.0][📌semver-img]][📌semver].
1007
+ Violations of this scheme should be reported as bugs.
1008
+ Specifically, if a minor or patch version is released that breaks backward compatibility,
1009
+ a new version should be immediately released that restores compatibility.
1010
+ Breaking changes to the public API will only be introduced with new major versions.
1011
+
1012
+ > dropping support for a platform is both obviously and objectively a breaking change <br/>
1013
+ >—Jordan Harband ([@ljharb](https://github.com/ljharb), maintainer of SemVer) [in SemVer issue 716][📌semver-breaking]
1014
+
1015
+ I understand that policy doesn't work universally ("exceptions to every rule!"),
1016
+ but it is the policy here.
1017
+ As such, in many cases it is good to specify a dependency on this library using
1018
+ the [Pessimistic Version Constraint][📌pvc] with two digits of precision.
1019
+
1020
+ For example:
1021
+
1022
+ ```ruby
1023
+ spec.add_dependency("tree_haver", "~> 1.0")
1024
+ ```
1025
+
1026
+ <details markdown="1">
1027
+ <summary>📌 Is "Platform Support" part of the public API? More details inside.</summary>
1028
+
1029
+ SemVer should, IMO, but doesn't explicitly, say that dropping support for specific Platforms
1030
+ is a *breaking change* to an API, and for that reason the bike shedding is endless.
1031
+
1032
+ To get a better understanding of how SemVer is intended to work over a project's lifetime,
1033
+ read this article from the creator of SemVer:
1034
+
1035
+ - ["Major Version Numbers are Not Sacred"][📌major-versions-not-sacred]
1036
+
1037
+ </details>
1038
+
1039
+ See [CHANGELOG.md][📌changelog] for a list of releases.
1040
+
1041
+ ## 📄 License
1042
+
1043
+ The gem is available as open source under the terms of
1044
+ the [MIT License][📄license] [![License: MIT][📄license-img]][📄license-ref].
1045
+ See [LICENSE.txt][📄license] for the official [Copyright Notice][📄copyright-notice-explainer].
1046
+
1047
+ ### © Copyright
1048
+
1049
+ <ul>
1050
+ <li>
1051
+ Copyright (c) 2025 Peter H. Boling, of
1052
+ <a href="https://discord.gg/3qme4XHNKN">
1053
+ Galtzo.com
1054
+ <picture>
1055
+ <img src="https://logos.galtzo.com/assets/images/galtzo-floss/avatar-128px-blank.svg" alt="Galtzo.com Logo (Wordless) by Aboling0, CC BY-SA 4.0" width="24">
1056
+ </picture>
1057
+ </a>, and tree_haver contributors.
1058
+ </li>
1059
+ </ul>
1060
+
1061
+ ## 🤑 A request for help
1062
+
1063
+ Maintainers have teeth and need to pay their dentists.
1064
+ After getting laid off in an RIF in March, and encountering difficulty finding a new one,
1065
+ I began spending most of my time building open source tools.
1066
+ I'm hoping to be able to pay for my kids' health insurance this month,
1067
+ so if you value the work I am doing, I need your support.
1068
+ Please consider sponsoring me or the project.
1069
+
1070
+ To join the community or get help 👇️ Join the Discord.
1071
+
1072
+ [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite]
1073
+
1074
+ To say "thanks!" ☝️ Join the Discord or 👇️ send money.
1075
+
1076
+ [![Sponsor kettle-rb/tree_haver 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]
1077
+
1078
+ ### Please give the project a star ⭐ ♥.
1079
+
1080
+ Thanks for RTFM. ☺️
1081
+
1082
+ [⛳liberapay-img]: https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat
1083
+ [⛳liberapay-bottom-img]: https://img.shields.io/liberapay/goal/pboling.svg?style=for-the-badge&logo=liberapay&color=a51611
1084
+ [⛳liberapay]: https://liberapay.com/pboling/donate
1085
+ [🖇osc-all-img]: https://img.shields.io/opencollective/all/kettle-rb
1086
+ [🖇osc-sponsors-img]: https://img.shields.io/opencollective/sponsors/kettle-rb
1087
+ [🖇osc-backers-img]: https://img.shields.io/opencollective/backers/kettle-rb
1088
+ [🖇osc-backers]: https://opencollective.com/kettle-rb#backer
1089
+ [🖇osc-backers-i]: https://opencollective.com/kettle-rb/backers/badge.svg?style=flat
1090
+ [🖇osc-sponsors]: https://opencollective.com/kettle-rb#sponsor
1091
+ [🖇osc-sponsors-i]: https://opencollective.com/kettle-rb/sponsors/badge.svg?style=flat
1092
+ [🖇osc-all-bottom-img]: https://img.shields.io/opencollective/all/kettle-rb?style=for-the-badge
1093
+ [🖇osc-sponsors-bottom-img]: https://img.shields.io/opencollective/sponsors/kettle-rb?style=for-the-badge
1094
+ [🖇osc-backers-bottom-img]: https://img.shields.io/opencollective/backers/kettle-rb?style=for-the-badge
1095
+ [🖇osc]: https://opencollective.com/kettle-rb
1096
+ [🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github
1097
+ [🖇sponsor-bottom-img]: https://img.shields.io/badge/Sponsor_Me!-pboling-blue?style=for-the-badge&logo=github
1098
+ [🖇sponsor]: https://github.com/sponsors/pboling
1099
+ [🖇polar-img]: https://img.shields.io/badge/polar-donate-a51611.svg?style=flat
1100
+ [🖇polar]: https://polar.sh/pboling
1101
+ [🖇kofi-img]: https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat
1102
+ [🖇kofi]: https://ko-fi.com/O5O86SNP4
1103
+ [🖇patreon-img]: https://img.shields.io/badge/patreon-donate-a51611.svg?style=flat
1104
+ [🖇patreon]: https://patreon.com/galtzo
1105
+ [🖇buyme-small-img]: https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat
1106
+ [🖇buyme-img]: https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20latte&emoji=&slug=pboling&button_colour=FFDD00&font_colour=000000&font_family=Cookie&outline_colour=000000&coffee_colour=ffffff
1107
+ [🖇buyme]: https://www.buymeacoffee.com/pboling
1108
+ [🖇paypal-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=flat&logo=paypal
1109
+ [🖇paypal-bottom-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=for-the-badge&logo=paypal&color=0A0A0A
1110
+ [🖇paypal]: https://www.paypal.com/paypalme/peterboling
1111
+ [🖇floss-funding.dev]: https://floss-funding.dev
1112
+ [🖇floss-funding-gem]: https://github.com/galtzo-floss/floss_funding
1113
+ [✉️discord-invite]: https://discord.gg/3qme4XHNKN
1114
+ [✉️discord-invite-img-ftb]: https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord
1115
+ [✉️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
1116
+ [✉️ruby-friends]: https://app.daily.dev/squads/rubyfriends
1117
+
1118
+ [✇bundle-group-pattern]: https://gist.github.com/pboling/4564780
1119
+ [⛳️gem-namespace]: https://github.com/kettle-rb/tree_haver
1120
+ [⛳️namespace-img]: https://img.shields.io/badge/namespace-TreeHaver-3C2D2D.svg?style=square&logo=ruby&logoColor=white
1121
+ [⛳️gem-name]: https://bestgems.org/gems/tree_haver
1122
+ [⛳️name-img]: https://img.shields.io/badge/name-tree__haver-3C2D2D.svg?style=square&logo=rubygems&logoColor=red
1123
+ [⛳️tag-img]: https://img.shields.io/github/tag/kettle-rb/tree_haver.svg
1124
+ [⛳️tag]: http://github.com/kettle-rb/tree_haver/releases
1125
+ [🚂maint-blog]: http://www.railsbling.com/tags/tree_haver
1126
+ [🚂maint-blog-img]: https://img.shields.io/badge/blog-railsbling-0093D0.svg?style=for-the-badge&logo=rubyonrails&logoColor=orange
1127
+ [🚂maint-contact]: http://www.railsbling.com/contact
1128
+ [🚂maint-contact-img]: https://img.shields.io/badge/Contact-Maintainer-0093D0.svg?style=flat&logo=rubyonrails&logoColor=red
1129
+ [💖🖇linkedin]: http://www.linkedin.com/in/peterboling
1130
+ [💖🖇linkedin-img]: https://img.shields.io/badge/PeterBoling-LinkedIn-0B66C2?style=flat&logo=newjapanprowrestling
1131
+ [💖✌️wellfound]: https://wellfound.com/u/peter-boling
1132
+ [💖✌️wellfound-img]: https://img.shields.io/badge/peter--boling-orange?style=flat&logo=wellfound
1133
+ [💖💲crunchbase]: https://www.crunchbase.com/person/peter-boling
1134
+ [💖💲crunchbase-img]: https://img.shields.io/badge/peter--boling-purple?style=flat&logo=crunchbase
1135
+ [💖🐘ruby-mast]: https://ruby.social/@galtzo
1136
+ [💖🐘ruby-mast-img]: https://img.shields.io/mastodon/follow/109447111526622197?domain=https://ruby.social&style=flat&logo=mastodon&label=Ruby%20@galtzo
1137
+ [💖🦋bluesky]: https://bsky.app/profile/galtzo.com
1138
+ [💖🦋bluesky-img]: https://img.shields.io/badge/@galtzo.com-0285FF?style=flat&logo=bluesky&logoColor=white
1139
+ [💖🌳linktree]: https://linktr.ee/galtzo
1140
+ [💖🌳linktree-img]: https://img.shields.io/badge/galtzo-purple?style=flat&logo=linktree
1141
+ [💖💁🏼‍♂️devto]: https://dev.to/galtzo
1142
+ [💖💁🏼‍♂️devto-img]: https://img.shields.io/badge/dev.to-0A0A0A?style=flat&logo=devdotto&logoColor=white
1143
+ [💖💁🏼‍♂️aboutme]: https://about.me/peter.boling
1144
+ [💖💁🏼‍♂️aboutme-img]: https://img.shields.io/badge/about.me-0A0A0A?style=flat&logo=aboutme&logoColor=white
1145
+ [💖🧊berg]: https://codeberg.org/pboling
1146
+ [💖🐙hub]: https://github.org/pboling
1147
+ [💖🛖hut]: https://sr.ht/~galtzo/
1148
+ [💖🧪lab]: https://gitlab.com/pboling
1149
+ [👨🏼‍🏫expsup-upwork]: https://www.upwork.com/freelancers/~014942e9b056abdf86?mp_source=share
1150
+ [👨🏼‍🏫expsup-upwork-img]: https://img.shields.io/badge/UpWork-13544E?style=for-the-badge&logo=Upwork&logoColor=white
1151
+ [👨🏼‍🏫expsup-codementor]: https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github
1152
+ [👨🏼‍🏫expsup-codementor-img]: https://img.shields.io/badge/CodeMentor-Get_Help-1abc9c?style=for-the-badge&logo=CodeMentor&logoColor=white
1153
+ [🏙️entsup-tidelift]: https://tidelift.com/subscription/pkg/rubygems-tree_haver?utm_source=rubygems-tree_haver&utm_medium=referral&utm_campaign=readme
1154
+ [🏙️entsup-tidelift-img]: https://img.shields.io/badge/Tidelift_and_Sonar-Enterprise_Support-FD3456?style=for-the-badge&logo=sonar&logoColor=white
1155
+ [🏙️entsup-tidelift-sonar]: https://blog.tidelift.com/tidelift-joins-sonar
1156
+ [💁🏼‍♂️peterboling]: http://www.peterboling.com
1157
+ [🚂railsbling]: http://www.railsbling.com
1158
+ [📜src-gl-img]: https://img.shields.io/badge/GitLab-FBA326?style=for-the-badge&logo=Gitlab&logoColor=orange
1159
+ [📜src-gl]: https://gitlab.com/kettle-rb/tree_haver/
1160
+ [📜src-cb-img]: https://img.shields.io/badge/CodeBerg-4893CC?style=for-the-badge&logo=CodeBerg&logoColor=blue
1161
+ [📜src-cb]: https://codeberg.org/kettle-rb/tree_haver
1162
+ [📜src-gh-img]: https://img.shields.io/badge/GitHub-238636?style=for-the-badge&logo=Github&logoColor=green
1163
+ [📜src-gh]: https://github.com/kettle-rb/tree_haver
1164
+ [📜docs-cr-rd-img]: https://img.shields.io/badge/RubyDoc-Current_Release-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
1165
+ [📜docs-head-rd-img]: https://img.shields.io/badge/YARD_on_Galtzo.com-HEAD-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
1166
+ [📜gl-wiki]: https://gitlab.com/kettle-rb/tree_haver/-/wikis/home
1167
+ [📜gh-wiki]: https://github.com/kettle-rb/tree_haver/wiki
1168
+ [📜gl-wiki-img]: https://img.shields.io/badge/wiki-examples-943CD2.svg?style=for-the-badge&logo=gitlab&logoColor=white
1169
+ [📜gh-wiki-img]: https://img.shields.io/badge/wiki-examples-943CD2.svg?style=for-the-badge&logo=github&logoColor=white
1170
+ [👽dl-rank]: https://bestgems.org/gems/tree_haver
1171
+ [👽dl-ranki]: https://img.shields.io/gem/rd/tree_haver.svg
1172
+ [👽oss-help]: https://www.codetriage.com/kettle-rb/tree_haver
1173
+ [👽oss-helpi]: https://www.codetriage.com/kettle-rb/tree_haver/badges/users.svg
1174
+ [👽version]: https://bestgems.org/gems/tree_haver
1175
+ [👽versioni]: https://img.shields.io/gem/v/tree_haver.svg
1176
+ [🏀qlty-mnt]: https://qlty.sh/gh/kettle-rb/projects/tree_haver
1177
+ [🏀qlty-mnti]: https://qlty.sh/gh/kettle-rb/projects/tree_haver/maintainability.svg
1178
+ [🏀qlty-cov]: https://qlty.sh/gh/kettle-rb/projects/tree_haver/metrics/code?sort=coverageRating
1179
+ [🏀qlty-covi]: https://qlty.sh/gh/kettle-rb/projects/tree_haver/coverage.svg
1180
+ [🏀codecov]: https://codecov.io/gh/kettle-rb/tree_haver
1181
+ [🏀codecovi]: https://codecov.io/gh/kettle-rb/tree_haver/graph/badge.svg
1182
+ [🏀coveralls]: https://coveralls.io/github/kettle-rb/tree_haver?branch=main
1183
+ [🏀coveralls-img]: https://coveralls.io/repos/github/kettle-rb/tree_haver/badge.svg?branch=main
1184
+ [🖐codeQL]: https://github.com/kettle-rb/tree_haver/security/code-scanning
1185
+ [🖐codeQL-img]: https://github.com/kettle-rb/tree_haver/actions/workflows/codeql-analysis.yml/badge.svg
1186
+ [🚎2-cov-wf]: https://github.com/kettle-rb/tree_haver/actions/workflows/coverage.yml
1187
+ [🚎2-cov-wfi]: https://github.com/kettle-rb/tree_haver/actions/workflows/coverage.yml/badge.svg
1188
+ [🚎3-hd-wf]: https://github.com/kettle-rb/tree_haver/actions/workflows/heads.yml
1189
+ [🚎3-hd-wfi]: https://github.com/kettle-rb/tree_haver/actions/workflows/heads.yml/badge.svg
1190
+ [🚎5-st-wf]: https://github.com/kettle-rb/tree_haver/actions/workflows/style.yml
1191
+ [🚎5-st-wfi]: https://github.com/kettle-rb/tree_haver/actions/workflows/style.yml/badge.svg
1192
+ [🚎6-s-wf]: https://github.com/kettle-rb/tree_haver/actions/workflows/supported.yml
1193
+ [🚎6-s-wfi]: https://github.com/kettle-rb/tree_haver/actions/workflows/supported.yml/badge.svg
1194
+ [🚎9-t-wf]: https://github.com/kettle-rb/tree_haver/actions/workflows/truffle.yml
1195
+ [🚎9-t-wfi]: https://github.com/kettle-rb/tree_haver/actions/workflows/truffle.yml/badge.svg
1196
+ [🚎11-c-wf]: https://github.com/kettle-rb/tree_haver/actions/workflows/current.yml
1197
+ [🚎11-c-wfi]: https://github.com/kettle-rb/tree_haver/actions/workflows/current.yml/badge.svg
1198
+ [🚎12-crh-wf]: https://github.com/kettle-rb/tree_haver/actions/workflows/dep-heads.yml
1199
+ [🚎12-crh-wfi]: https://github.com/kettle-rb/tree_haver/actions/workflows/dep-heads.yml/badge.svg
1200
+ [🚎13-🔒️-wf]: https://github.com/kettle-rb/tree_haver/actions/workflows/locked_deps.yml
1201
+ [🚎13-🔒️-wfi]: https://github.com/kettle-rb/tree_haver/actions/workflows/locked_deps.yml/badge.svg
1202
+ [🚎14-🔓️-wf]: https://github.com/kettle-rb/tree_haver/actions/workflows/unlocked_deps.yml
1203
+ [🚎14-🔓️-wfi]: https://github.com/kettle-rb/tree_haver/actions/workflows/unlocked_deps.yml/badge.svg
1204
+ [🚎15-🪪-wf]: https://github.com/kettle-rb/tree_haver/actions/workflows/license-eye.yml
1205
+ [🚎15-🪪-wfi]: https://github.com/kettle-rb/tree_haver/actions/workflows/license-eye.yml/badge.svg
1206
+ [💎ruby-3.2i]: https://img.shields.io/badge/Ruby-3.2-CC342D?style=for-the-badge&logo=ruby&logoColor=white
1207
+ [💎ruby-3.3i]: https://img.shields.io/badge/Ruby-3.3-CC342D?style=for-the-badge&logo=ruby&logoColor=white
1208
+ [💎ruby-c-i]: https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green
1209
+ [💎ruby-headi]: https://img.shields.io/badge/Ruby-HEAD-CC342D?style=for-the-badge&logo=ruby&logoColor=blue
1210
+ [💎truby-23.1i]: https://img.shields.io/badge/Truffle_Ruby-23.1-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
1211
+ [💎truby-c-i]: https://img.shields.io/badge/Truffle_Ruby-current-34BCB1?style=for-the-badge&logo=ruby&logoColor=green
1212
+ [💎truby-headi]: https://img.shields.io/badge/Truffle_Ruby-HEAD-34BCB1?style=for-the-badge&logo=ruby&logoColor=blue
1213
+ [💎jruby-c-i]: https://img.shields.io/badge/JRuby-current-FBE742?style=for-the-badge&logo=ruby&logoColor=green
1214
+ [💎jruby-headi]: https://img.shields.io/badge/JRuby-HEAD-FBE742?style=for-the-badge&logo=ruby&logoColor=blue
1215
+ [🤝gh-issues]: https://github.com/kettle-rb/tree_haver/issues
1216
+ [🤝gh-pulls]: https://github.com/kettle-rb/tree_haver/pulls
1217
+ [🤝gl-issues]: https://gitlab.com/kettle-rb/tree_haver/-/issues
1218
+ [🤝gl-pulls]: https://gitlab.com/kettle-rb/tree_haver/-/merge_requests
1219
+ [🤝cb-issues]: https://codeberg.org/kettle-rb/tree_haver/issues
1220
+ [🤝cb-pulls]: https://codeberg.org/kettle-rb/tree_haver/pulls
1221
+ [🤝cb-donate]: https://donate.codeberg.org/
1222
+ [🤝contributing]: CONTRIBUTING.md
1223
+ [🏀codecov-g]: https://codecov.io/gh/kettle-rb/tree_haver/graphs/tree.svg
1224
+ [🖐contrib-rocks]: https://contrib.rocks
1225
+ [🖐contributors]: https://github.com/kettle-rb/tree_haver/graphs/contributors
1226
+ [🖐contributors-img]: https://contrib.rocks/image?repo=kettle-rb/tree_haver
1227
+ [🚎contributors-gl]: https://gitlab.com/kettle-rb/tree_haver/-/graphs/main
1228
+ [🪇conduct]: CODE_OF_CONDUCT.md
1229
+ [🪇conduct-img]: https://img.shields.io/badge/Contributor_Covenant-2.1-259D6C.svg
1230
+ [📌pvc]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint
1231
+ [📌semver]: https://semver.org/spec/v2.0.0.html
1232
+ [📌semver-img]: https://img.shields.io/badge/semver-2.0.0-259D6C.svg?style=flat
1233
+ [📌semver-breaking]: https://github.com/semver/semver/issues/716#issuecomment-869336139
1234
+ [📌major-versions-not-sacred]: https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html
1235
+ [📌changelog]: CHANGELOG.md
1236
+ [📗keep-changelog]: https://keepachangelog.com/en/1.0.0/
1237
+ [📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat
1238
+ [📌gitmoji]: https://gitmoji.dev
1239
+ [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
1240
+ [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
1241
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.501-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1242
+ [🔐security]: SECURITY.md
1243
+ [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
1244
+ [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
1245
+ [📄license]: LICENSE.txt
1246
+ [📄license-ref]: https://opensource.org/licenses/MIT
1247
+ [📄license-img]: https://img.shields.io/badge/License-MIT-259D6C.svg
1248
+ [📄license-compat]: https://dev.to/galtzo/how-to-check-license-compatibility-41h0
1249
+ [📄license-compat-img]: https://img.shields.io/badge/Apache_Compatible:_Category_A-%E2%9C%93-259D6C.svg?style=flat&logo=Apache
1250
+ [📄ilo-declaration]: https://www.ilo.org/declaration/lang--en/index.htm
1251
+ [📄ilo-declaration-img]: https://img.shields.io/badge/ILO_Fundamental_Principles-✓-259D6C.svg?style=flat
1252
+ [🚎yard-current]: http://rubydoc.info/gems/tree_haver
1253
+ [🚎yard-head]: https://tree-haver.galtzo.com
1254
+ [💎stone_checksums]: https://github.com/galtzo-floss/stone_checksums
1255
+ [💎SHA_checksums]: https://gitlab.com/kettle-rb/tree_haver/-/tree/main/checksums
1256
+ [💎rlts]: https://github.com/rubocop-lts/rubocop-lts
1257
+ [💎rlts-img]: https://img.shields.io/badge/code_style_&_linting-rubocop--lts-34495e.svg?plastic&logo=ruby&logoColor=white
1258
+ [💎appraisal2]: https://github.com/appraisal-rb/appraisal2
1259
+ [💎appraisal2-img]: https://img.shields.io/badge/appraised_by-appraisal2-34495e.svg?plastic&logo=ruby&logoColor=white
1260
+ [💎d-in-dvcs]: https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/