tree_haver 3.1.2 → 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -1,16 +1,16 @@
1
- | 📍 NOTE |
2
- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
3
- | RubyGems (the [GitHub org][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]. |
1
+ | 📍 NOTE |
2
+ | --- |
3
+ | RubyGems (the [GitHub org](https://github.com/rubygems/), not the website) [suffered](https://joel.drapper.me/p/ruby-central-security-measures/) a [hostile takeover](https://pup-e.com/blog/goodbye-rubygems/) in September 2025. |
4
+ | Ultimately [4 maintainers](https://www.reddit.com/r/ruby/s/gOk42POCaV) were [hard removed](https://bsky.app/profile/martinemde.com/post/3m3occezxxs2q) and a reason has been given for only 1 of those, while 2 others resigned in protest. |
5
+ | It is a [complicated story](https://joel.drapper.me/p/ruby-central-takeover/) which is difficult to [parse quickly](https://joel.drapper.me/p/ruby-central-fact-check/). |
6
+ | Simply put - there was active policy for adding or removing maintainers/owners of [rubygems](https://github.com/ruby/rubygems/blob/b1ab33a3d52310a84d16b193991af07f5a6a07c0/doc/rubygems/POLICIES.md?plain=1#L187-L196) and [bundler](https://github.com/ruby/rubygems/blob/b1ab33a3d52310a84d16b193991af07f5a6a07c0/doc/bundler/playbooks/TEAM_CHANGES.md), and those [policies were not followed](https://www.reddit.com/r/ruby/comments/1ove9vp/rubycentral_hates_this_one_fact/). |
7
+ | I'm adding notes like this to gems because I [don't condone theft](https://joel.drapper.me/p/ruby-central/) of repositories or gems from their rightful owners. |
8
+ | If a similar theft happened with my repos/gems, I'd hope some would stand up for me. |
9
+ | Disenfranchised former-maintainers have started [gem.coop](https://gem.coop). |
10
+ | Once available I will publish there exclusively; unless RubyCentral makes amends with the community. |
11
+ | The ["Technology for Humans: Joel Draper"](https://youtu.be/_H4qbtC5qzU?si=BvuBU90R2wAqD2E6) podcast episode by [reinteractive](https://reinteractive.com/ruby-on-rails) is the most cogent summary I'm aware of. |
12
+ | See [here](https://github.com/gem-coop/gem.coop/issues/12), [here](https://gem.coop) and [here](https://martinemde.com/2025/10/05/announcing-gem-coop.html) for more info on what comes next. |
13
+ | What I'm doing: A (WIP) proposal for [bundler/gem scopes](https://github.com/galtzo-floss/bundle-namespace), and a (WIP) proposal for a federated [gem server](https://github.com/galtzo-floss/gem-server). |
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][🖼️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]
34
+ [![Galtzo FLOSS Logo by Aboling0, CC BY-SA 4.0](https://logos.galtzo.com/assets/images/galtzo-floss/avatar-192px.svg)](https://discord.gg/3qme4XHNKN) [![ruby-lang Logo, Yukihiro Matsumoto, Ruby Visual Identity Team, CC BY-SA 2.5](https://logos.galtzo.com/assets/images/ruby-lang/avatar-192px.svg)](https://www.ruby-lang.org/) [![kettle-rb Logo by Aboling0, CC BY-SA 4.0](https://logos.galtzo.com/assets/images/kettle-rb/avatar-192px.svg)](https://github.com/kettle-rb)
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,15 +42,14 @@
42
42
 
43
43
  # 🌴 TreeHaver
44
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]
45
+ [![Version](https://img.shields.io/gem/v/tree_haver.svg)](https://bestgems.org/gems/tree_haver) [![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/kettle-rb/tree_haver.svg)](http://github.com/kettle-rb/tree_haver/releases) [![License: MIT](https://img.shields.io/badge/License-MIT-259D6C.svg)](https://opensource.org/licenses/MIT) [![Downloads Rank](https://img.shields.io/gem/rd/tree_haver.svg)](https://bestgems.org/gems/tree_haver) [![Open Source Helpers](https://www.codetriage.com/kettle-rb/tree_haver/badges/users.svg)](https://www.codetriage.com/kettle-rb/tree_haver) [![CodeCov Test Coverage](https://codecov.io/gh/kettle-rb/tree_haver/graph/badge.svg)](https://codecov.io/gh/kettle-rb/tree_haver) [![Coveralls Test Coverage](https://coveralls.io/repos/github/kettle-rb/tree_haver/badge.svg?branch=main)](https://coveralls.io/github/kettle-rb/tree_haver?branch=main) [![QLTY Test Coverage](https://qlty.sh/gh/kettle-rb/projects/tree_haver/coverage.svg)](https://qlty.sh/gh/kettle-rb/projects/tree_haver/metrics/code?sort=coverageRating) [![QLTY Maintainability](https://qlty.sh/gh/kettle-rb/projects/tree_haver/maintainability.svg)](https://qlty.sh/gh/kettle-rb/projects/tree_haver) [![CI Heads](https://github.com/kettle-rb/tree_haver/actions/workflows/heads.yml/badge.svg)](https://github.com/kettle-rb/tree_haver/actions/workflows/heads.yml) [![CI Runtime Dependencies @ HEAD](https://github.com/kettle-rb/tree_haver/actions/workflows/dep-heads.yml/badge.svg)](https://github.com/kettle-rb/tree_haver/actions/workflows/dep-heads.yml) [![CI Current](https://github.com/kettle-rb/tree_haver/actions/workflows/current.yml/badge.svg)](https://github.com/kettle-rb/tree_haver/actions/workflows/current.yml) [![CI Truffle Ruby](https://github.com/kettle-rb/tree_haver/actions/workflows/truffle.yml/badge.svg)](https://github.com/kettle-rb/tree_haver/actions/workflows/truffle.yml) [![Deps Locked](https://github.com/kettle-rb/tree_haver/actions/workflows/locked_deps.yml/badge.svg)](https://github.com/kettle-rb/tree_haver/actions/workflows/locked_deps.yml) [![Deps Unlocked](https://github.com/kettle-rb/tree_haver/actions/workflows/unlocked_deps.yml/badge.svg)](https://github.com/kettle-rb/tree_haver/actions/workflows/unlocked_deps.yml) [![CI Supported](https://github.com/kettle-rb/tree_haver/actions/workflows/supported.yml/badge.svg)](https://github.com/kettle-rb/tree_haver/actions/workflows/supported.yml) [![CI Test Coverage](https://github.com/kettle-rb/tree_haver/actions/workflows/coverage.yml/badge.svg)](https://github.com/kettle-rb/tree_haver/actions/workflows/coverage.yml) [![CI Style](https://github.com/kettle-rb/tree_haver/actions/workflows/style.yml/badge.svg)](https://github.com/kettle-rb/tree_haver/actions/workflows/style.yml) [![CodeQL](https://github.com/kettle-rb/tree_haver/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/kettle-rb/tree_haver/security/code-scanning) [![Apache SkyWalking Eyes License Compatibility Check](https://github.com/kettle-rb/tree_haver/actions/workflows/license-eye.yml/badge.svg)](https://github.com/kettle-rb/tree_haver/actions/workflows/license-eye.yml)
46
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
- ---
47
+ `if ci_badges.map(&:color).detect { it != "green"}` ☝️ [let me know](https://discord.gg/3qme4XHNKN), as I may have missed the [discord notification](https://discord.gg/3qme4XHNKN).
50
48
 
49
+ -----
51
50
  `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
51
 
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]
52
+ [![OpenCollective Backers](https://opencollective.com/kettle-rb/backers/badge.svg?style=flat)](https://opencollective.com/kettle-rb#backer) [![OpenCollective Sponsors](https://opencollective.com/kettle-rb/sponsors/badge.svg?style=flat)](https://opencollective.com/kettle-rb#sponsor) [![Sponsor Me on Github](https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github)](https://github.com/sponsors/pboling) [![Liberapay Goal Progress](https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat)](https://liberapay.com/pboling/donate) [![Donate on PayPal](https://img.shields.io/badge/donate-paypal-a51611.svg?style=flat&logo=paypal)](https://www.paypal.com/paypalme/peterboling) [![Buy me a coffee](https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat)](https://www.buymeacoffee.com/pboling) [![Donate on Polar](https://img.shields.io/badge/polar-donate-a51611.svg?style=flat)](https://polar.sh/pboling) [![Donate at ko-fi.com](https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat)](https://ko-fi.com/O5O86SNP4)
54
53
 
55
54
  ## 🌻 Synopsis
56
55
 
@@ -58,14 +57,14 @@ TreeHaver is a cross-Ruby adapter for the [tree-sitter](https://tree-sitter.gith
58
57
 
59
58
  ### The Adapter Pattern: Like Faraday, but for Parsing
60
59
 
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:
60
+ 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
61
 
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** | Code parsing | MRI, Rust, FFI, Java, Prism, Psych, Commonmarker, Markly, Citrus (& Co.) |
62
+ | Gem | Unified API for | Backend Examples |
63
+ | --- | --- | --- |
64
+ | **Faraday** | HTTP requests | Net::HTTP, Typhoeus, Patron, Excon |
65
+ | **multi\_json** | JSON parsing | Oj, Yajl, JSON gem |
66
+ | **multi\_xml** | XML parsing | Nokogiri, LibXML, Ox |
67
+ | **TreeHaver** | Code parsing | MRI, Rust, FFI, Java, Prism, Psych, Commonmarker, Markly, Citrus (& Co.) |
69
68
 
70
69
  **Write once, run anywhere.**
71
70
 
@@ -73,86 +72,97 @@ If you've used [Faraday](https://github.com/lostisland/faraday), [multi_json](ht
73
72
 
74
73
  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.
75
74
 
76
- ```ruby
75
+ ``` ruby
77
76
  # Your code stays the same regardless of backend
78
77
  parser = TreeHaver::Parser.new
79
78
  parser.language = TreeHaver::Language.from_library("/path/to/grammar.so")
80
79
  tree = parser.parse(source_code)
81
80
 
82
- # TreeHaver automatically picks the best backend:
83
- # - MRI ruby_tree_sitter (C extensions)
84
- # - JRuby FFI (system's libtree-sitter)
85
- # - TruffleRuby FFI or MRI backend
81
+ # TreeHaver automatically picks the best available backend:
82
+ # - MRI: ruby_tree_sitter, tree_stump, ffi, prism, psych, commonmarker, markly, citrus
83
+ # - JRuby: ffi, java-tree-sitter (not a gem, but the jtreesitter maven package), prism, psych, commonmarker, markly, citrus
84
+ # - TruffleRuby: prism, psych, commonmarker, markly, citrus
85
+ # (tree-sitter backends don't work on Truffleruby with ffi gem due to FFI STRUCT_BY_VALUE limitation)
86
86
  ```
87
87
 
88
88
  ### Key Features
89
89
 
90
- - **Universal Ruby Support**: Works on MRI Ruby, JRuby, and TruffleRuby
91
- - **10 Parsing Backends** - Choose the right backend for your needs:
92
- - **Tree-sitter Backends** (high-performance, incremental parsing):
93
- - **MRI Backend**: Leverages [`ruby_tree_sitter`](https://github.com/Faveod/ruby-tree-sitter) gem (C extension, fastest on MRI)
94
- - **Rust Backend**: Uses [`tree_stump`][tree_stump] gem (Rust with precompiled binaries)
95
- - **Note**: `tree_stump` currently requires unreleased fixes in the `main` branch.
96
- - **FFI Backend**: Pure Ruby FFI bindings to `libtree-sitter` (ideal for JRuby, TruffleRuby)
97
- - **Java Backend**: Native Java integration for JRuby with [`java-tree-sitter`](https://github.com/tree-sitter/java-tree-sitter) / [`jtreesitter`](https://central.sonatype.com/artifact/io.github.tree-sitter/jtreesitter) grammar JARs
98
- - **Language-Specific Backends** (native parser integration):
99
- - **Prism Backend**: Ruby's official parser ([Prism][prism], stdlib in Ruby 3.4+)
100
- - **Psych Backend**: Ruby's YAML parser ([Psych][psych], stdlib)
101
- - **Commonmarker Backend**: Fast Markdown parser ([Commonmarker][commonmarker], comrak Rust)
102
- - **Markly Backend**: GitHub Flavored Markdown ([Markly][markly], cmark-gfm C)
103
- - **Pure Ruby Fallback**:
104
- - **Citrus Backend**: Pure Ruby parsing via [`citrus`][citrus] (no native dependencies)
105
- - **Automatic Backend Selection**: Intelligently selects the best backend for your Ruby implementation
106
- - **Language Agnostic**: Parse any language - Ruby, Markdown, YAML, JSON, Bash, TOML, JavaScript, etc.
107
- - **Grammar Discovery**: Built-in `GrammarFinder` utility for platform-aware grammar library discovery
108
- - **Unified Position API**: Consistent `start_line`, `end_line`, `source_position` across all backends
109
- - **Thread-Safe**: Built-in language registry with thread-safe caching
110
- - **Minimal API Surface**: Simple, focused API that covers the most common use cases
111
-
90
+ - **Universal Ruby Support**: Works on MRI Ruby, JRuby, and TruffleRuby
91
+ - **10 Parsing Backends** - Choose the right backend for your needs:
92
+ - **Tree-sitter Backends** (high-performance, incremental parsing):
93
+ - **MRI Backend**: Leverages [`ruby_tree_sitter`](https://github.com/Faveod/ruby-tree-sitter) gem (C extension, fastest on MRI)
94
+ - **Rust Backend**: Uses [`tree_stump`](https://github.com/joker1007/tree_stump) gem (Rust with precompiled binaries)
95
+ - **Note**: `tree_stump` currently requires unreleased fixes in the `main` branch.
96
+ - **FFI Backend**: Pure Ruby FFI bindings to `libtree-sitter` (JRuby only; TruffleRuby's FFI doesn't support tree-sitter's struct-by-value returns)
97
+ - **Java Backend**: Native Java integration for JRuby with [`java-tree-sitter`](https://github.com/tree-sitter/java-tree-sitter) / [`jtreesitter`](https://central.sonatype.com/artifact/io.github.tree-sitter/jtreesitter) grammar JARs
98
+ - **Language-Specific Backends** (native parser integration):
99
+ - **Prism Backend**: Ruby's official parser ([Prism](https://github.com/ruby/prism), stdlib in Ruby 3.4+)
100
+ - **Psych Backend**: Ruby's YAML parser ([Psych](https://github.com/ruby/psych), stdlib)
101
+ - **Commonmarker Backend**: Fast Markdown parser ([Commonmarker](https://github.com/gjtorikian/commonmarker), comrak Rust)
102
+ - **Markly Backend**: GitHub Flavored Markdown ([Markly](https://github.com/ioquatix/markly), cmark-gfm C)
103
+ - **Pure Ruby Fallback**:
104
+ - **Citrus Backend**: Pure Ruby parsing via [`citrus`](https://github.com/mjackson/citrus) (no native dependencies)
105
+ - **Automatic Backend Selection**: Intelligently selects the best backend for your Ruby implementation
106
+ - **Language Agnostic**: Parse any language - Ruby, Markdown, YAML, JSON, Bash, TOML, JavaScript, etc.
107
+ - **Grammar Discovery**: Built-in `GrammarFinder` utility for platform-aware grammar library discovery
108
+ - **Unified Position API**: Consistent `start_line`, `end_line`, `source_position` across all backends
109
+ - **Thread-Safe**: Built-in language registry with thread-safe caching
110
+ - **Minimal API Surface**: Simple, focused API that covers the most common use cases
112
111
  ### Backend Requirements
113
112
 
114
113
  TreeHaver has minimal dependencies and automatically selects the best backend for your Ruby implementation. Each backend has specific version requirements:
115
114
 
116
- #### MRI Backend (ruby_tree_sitter, C extensions)
115
+ #### MRI Backend (ruby\_tree\_sitter, C extensions)
117
116
 
118
117
  **Requires `ruby_tree_sitter` v2.0+**
119
118
 
120
- In ruby_tree_sitter v2.0, all TreeSitter exceptions were changed to inherit from `Exception` (not `StandardError`). This was an intentional breaking change made for thread-safety and signal handling reasons.
119
+ In ruby\_tree\_sitter v2.0, all TreeSitter exceptions were changed to inherit from `Exception` (not `StandardError`). This was an intentional breaking change made for thread-safety and signal handling reasons.
121
120
 
122
121
  **Exception Mapping**: TreeHaver catches `TreeSitter::TreeSitterError` and its subclasses, converting them to `TreeHaver::NotAvailable` while preserving the original error message. This provides a consistent exception API across all backends:
123
122
 
124
- | ruby_tree_sitter Exception | TreeHaver Exception | When It Occurs |
125
- |-------------------------------------|----------------------------|------------------------------------------------|
126
- | `TreeSitter::ParserNotFoundError` | `TreeHaver::NotAvailable` | Parser library file cannot be loaded |
127
- | `TreeSitter::LanguageLoadError` | `TreeHaver::NotAvailable` | Language symbol loads but returns nothing |
128
- | `TreeSitter::SymbolNotFoundError` | `TreeHaver::NotAvailable` | Symbol not found in library |
129
- | `TreeSitter::ParserVersionError` | `TreeHaver::NotAvailable` | Parser version incompatible with tree-sitter |
130
- | `TreeSitter::QueryCreationError` | `TreeHaver::NotAvailable` | Query creation fails |
123
+ | ruby\_tree\_sitter Exception | TreeHaver Exception | When It Occurs |
124
+ | --- | --- | --- |
125
+ | `TreeSitter::ParserNotFoundError` | `TreeHaver::NotAvailable` | Parser library file cannot be loaded |
126
+ | `TreeSitter::LanguageLoadError` | `TreeHaver::NotAvailable` | Language symbol loads but returns nothing |
127
+ | `TreeSitter::SymbolNotFoundError` | `TreeHaver::NotAvailable` | Symbol not found in library |
128
+ | `TreeSitter::ParserVersionError` | `TreeHaver::NotAvailable` | Parser version incompatible with tree-sitter |
129
+ | `TreeSitter::QueryCreationError` | `TreeHaver::NotAvailable` | Query creation fails |
131
130
 
132
- ```ruby
131
+ ``` ruby
133
132
  # Add to your Gemfile for MRI backend
134
133
  gem "ruby_tree_sitter", "~> 2.0"
135
134
  ```
136
135
 
137
- #### Rust Backend (tree_stump)
136
+ #### Rust Backend (tree\_stump)
137
+
138
+ **MRI Ruby only** - Does not work on JRuby or TruffleRuby.
139
+
140
+ The Rust backend uses [tree\_stump](https://github.com/joker1007/tree_stump), which is a Rust native extension built with [magnus](https://github.com/matsadler/magnus) and [rb-sys](https://github.com/oxidize-rb/rb-sys). These libraries are only compatible with MRI Ruby's C API.
138
141
 
142
+ - **JRuby**: Cannot load native `.so` extensions (runs on JVM)
143
+ - **TruffleRuby**: magnus/rb-sys are incompatible with TruffleRuby's C API emulation
139
144
  NOTE: `tree_stump` currently requires unreleased fixes in the `main` branch.
140
145
 
141
- ```ruby
142
- # Add to your Gemfile for Rust backend
146
+ ``` ruby
147
+ # Add to your Gemfile for Rust backend (MRI only)
143
148
  gem "tree_stump", github: "joker1007/tree_stump", branch: "main"
144
149
  ```
145
150
 
146
151
  #### FFI Backend
147
152
 
148
- Requires the `ffi` gem and a system installation of `libtree-sitter`:
153
+ **MRI and JRuby only** - Does not work on TruffleRuby.
149
154
 
150
- ```ruby
151
- # Add to your Gemfile for FFI backend
155
+ Requires the `ffi` gem and a system installation of `libtree-sitter`.
156
+
157
+ - **TruffleRuby**: TruffleRuby's FFI implementation doesn't support `STRUCT_BY_VALUE` return types, which tree-sitter's C API uses for functions like `ts_tree_root_node` and `ts_node_child`.
158
+
159
+ <!-- end list -->
160
+ ``` ruby
161
+ # Add to your Gemfile for FFI backend (MRI and JRuby)
152
162
  gem "ffi", ">= 1.15", "< 2.0"
153
163
  ```
154
164
 
155
- ```bash
165
+ ``` bash
156
166
  # Install libtree-sitter on your system:
157
167
  # macOS
158
168
  brew install tree-sitter
@@ -168,7 +178,7 @@ dnf install tree-sitter tree-sitter-devel
168
178
 
169
179
  Pure Ruby parser with no native dependencies:
170
180
 
171
- ```ruby
181
+ ``` ruby
172
182
  # Add to your Gemfile for Citrus backend
173
183
  gem "citrus", "~> 3.0"
174
184
  ```
@@ -177,16 +187,54 @@ gem "citrus", "~> 3.0"
177
187
 
178
188
  No additional dependencies required beyond grammar JARs built for java-tree-sitter / jtreesitter.
179
189
 
190
+ ### Backend Platform Compatibility
191
+
192
+ Not all backends work on all Ruby platforms. Here's a complete compatibility matrix:
193
+
194
+ | Backend | MRI | JRuby | TruffleRuby | Notes |
195
+ | --- | :-: | :-: | :-: | --- |
196
+ | **MRI** ([ruby\_tree\_sitter](https://github.com/Faveod/ruby-tree-sitter)) | ✅ | ❌ | ❌ | C extension, MRI only |
197
+ | **Rust** ([tree\_stump](https://github.com/joker1007/tree_stump)) | ✅ | ❌ | ❌ | magnus/rb-sys incompatible with non-MRI |
198
+ | **FFI** | ✅ | ✅ | ❌ | TruffleRuby FFI doesn't support `STRUCT_BY_VALUE` |
199
+ | **Java** ([jtreesitter](https://central.sonatype.com/artifact/io.github.tree-sitter/jtreesitter)) | ❌ | ✅ | ❌ | JRuby only, requires grammar JARs |
200
+ | **Prism** | ✅ | ✅ | ✅ | Ruby parsing, stdlib in Ruby 3.4+ |
201
+ | **Psych** | ✅ | ✅ | ✅ | YAML parsing, stdlib |
202
+ | **Citrus** | ✅ | ✅ | ✅ | Pure Ruby, no native dependencies |
203
+ | **Commonmarker** | ✅ | ❌ | ❓ | Rust extension for Markdown |
204
+ | **Markly** | ✅ | ❌ | ❓ | C extension for Markdown |
205
+
206
+ **Legend**: ✅ = Works, ❌ = Does not work, ❓ = Untested
207
+
208
+ #### TruffleRuby Limitations
209
+
210
+ TruffleRuby has **no working tree-sitter backend**:
211
+
212
+ - **FFI**: TruffleRuby's FFI doesn't support `STRUCT_BY_VALUE` return types (used by `ts_tree_root_node`, `ts_node_child`, etc.)
213
+ - **MRI/Rust**: C and Rust extensions require MRI's C API internals (`RBasic.flags`, `rb_gc_writebarrier`, etc.) that TruffleRuby doesn't expose
214
+ TruffleRuby users should use: **Prism** (Ruby), **Psych** (YAML), **Citrus** (TOML via toml-rb), or potentially **Commonmarker/Markly** (Markdown).
215
+
216
+ #### JRuby Limitations
217
+
218
+ JRuby runs on the JVM and **cannot load native `.so` extensions**:
219
+
220
+ - **MRI/Rust**: C and Rust extensions simply cannot be loaded
221
+ - **FFI**: Works\! JRuby has excellent FFI support
222
+ JRuby users should use: **FFI backend** or **Java backend** for tree-sitter, plus **Prism**, **Psych**, **Citrus** for other formats.
223
+
224
+ [ruby_tree_sitter]: https://github.com/Faveod/ruby-tree-sitter
225
+ [tree_stump]: https://github.com/joker1007/tree_stump
226
+ [jtreesitter]: https://central.sonatype.com/artifact/io.github.tree-sitter/jtreesitter
227
+
180
228
  ### Why TreeHaver?
181
229
 
182
230
  tree-sitter is a powerful parser generator that creates incremental parsers for many programming languages. However, integrating it into Ruby applications can be challenging:
183
231
 
184
- - MRI-based C extensions don't work on JRuby
185
- - FFI-based solutions may not be optimal for MRI
186
- - Managing different backends for different Ruby implementations is cumbersome
187
-
232
+ - MRI-based C extensions don't work on JRuby
233
+ - FFI-based solutions may not be optimal for MRI
234
+ - Managing different backends for different Ruby implementations is cumbersome
188
235
  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.
189
236
 
237
+
190
238
  ### The `*-merge` Gem Family
191
239
 
192
240
  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.
@@ -210,9 +258,9 @@ The `*-merge` gem family provides intelligent, AST-based merging for various fil
210
258
  **Example implementations** for the gem templating use case:
211
259
 
212
260
  | Gem | Purpose | Description |
213
- |-----|---------|-------------|
214
- | [kettle-dev][kettle-dev] | Gem Development | Gem templating tool using `*-merge` gems |
215
- | [kettle-jem][kettle-jem] | Gem Templating | Gem template library with smart merge support |
261
+ | --- | --- | --- |
262
+ | [kettle-dev](https://github.com/kettle-rb/kettle-dev) | Gem Development | Gem templating tool using `*-merge` gems |
263
+ | [kettle-jem](https://github.com/kettle-rb/kettle-jem) | Gem Templating | Gem template library with smart merge support |
216
264
 
217
265
  [tree_haver]: https://github.com/kettle-rb/tree_haver
218
266
  [ast-merge]: https://github.com/kettle-rb/ast-merge
@@ -239,23 +287,27 @@ The `*-merge` gem family provides intelligent, AST-based merging for various fil
239
287
  [markly]: https://github.com/ioquatix/markly
240
288
  [commonmarker]: https://github.com/gjtorikian/commonmarker
241
289
 
290
+
291
+ [ts-jsonc]: https://gitlab.com/WhyNotHugo/tree-sitter-jsonc
292
+ [dotenv]: https://github.com/bkeepers/dotenv
293
+
242
294
  ### Comparison with Other Ruby AST / Parser Bindings
243
295
 
244
- | Feature | [tree_haver] (this gem) | [ruby_tree_sitter] | [tree_stump] | [citrus] |
245
- |---------------------------|----------------------------------------|--------------------|----------------|-------------|
246
- | **MRI Ruby** | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
247
- | **JRuby** | ✅ Yes (FFI, Java, or Citrus backend) | ❌ No | ❌ No | ✅ Yes |
248
- | **TruffleRuby** | ✅ Yes (FFI or Citrus) | ❌ No | ❓ Unknown | ✅ Yes |
249
- | **Backend** | Multi (MRI C, Rust, FFI, Java, Citrus) | C extension only | Rust extension | Pure Ruby |
250
- | **Incremental Parsing** | ✅ Via MRI C/Rust/Java backend | ✅ Yes | ✅ Yes | ❌ No |
251
- | **Query API** | ⚡ Via MRI/Rust/Java backend | ✅ Yes | ✅ Yes | ❌ No |
252
- | **Grammar Discovery** | ✅ Built-in `GrammarFinder` | ❌ Manual | ❌ Manual | ❌ Manual |
253
- | **Security Validations** | ✅ `PathValidator` | ❌ No | ❌ No | ❌ No |
254
- | **Language Registration** | ✅ Thread-safe registry | ❌ No | ❌ No | ❌ No |
255
- | **Native Performance** | ⚡ Backend-dependent | ✅ Native C | ✅ Native Rust | ❌ Pure Ruby |
256
- | **Precompiled Binaries** | ⚡ Via Rust backend | ✅ Yes | ✅ Yes | ✅ Pure Ruby |
257
- | **Zero Native Deps** | ⚡ Via Citrus backend | ❌ No | ❌ No | ✅ Yes |
258
- | **Minimum Ruby** | 3.2+ | 3.0+ | 3.1+ | 0+ |
296
+ | Feature | [tree\_haver](https://github.com/kettle-rb/tree_haver) (this gem) | [ruby\_tree\_sitter](https://github.com/Faveod/ruby-tree-sitter) | [tree\_stump](https://github.com/joker1007/tree_stump) | [citrus](https://github.com/mjackson/citrus) |
297
+ | --- | --- | --- | --- | --- |
298
+ | **MRI Ruby** | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
299
+ | **JRuby** | ✅ Yes (FFI, Java, or Citrus backend) | ❌ No | ❌ No | ✅ Yes |
300
+ | **TruffleRuby** | ✅ Yes (FFI or Citrus) | ❌ No | ❓ Unknown | ✅ Yes |
301
+ | **Backend** | Multi (MRI C, Rust, FFI, Java, Citrus) | C extension only | Rust extension | Pure Ruby |
302
+ | **Incremental Parsing** | ✅ Via MRI C/Rust/Java backend | ✅ Yes | ✅ Yes | ❌ No |
303
+ | **Query API** | ⚡ Via MRI/Rust/Java backend | ✅ Yes | ✅ Yes | ❌ No |
304
+ | **Grammar Discovery** | ✅ Built-in `GrammarFinder` | ❌ Manual | ❌ Manual | ❌ Manual |
305
+ | **Security Validations** | ✅ `PathValidator` | ❌ No | ❌ No | ❌ No |
306
+ | **Language Registration** | ✅ Thread-safe registry | ❌ No | ❌ No | ❌ No |
307
+ | **Native Performance** | ⚡ Backend-dependent | ✅ Native C | ✅ Native Rust | ❌ Pure Ruby |
308
+ | **Precompiled Binaries** | ⚡ Via Rust backend | ✅ Yes | ✅ Yes | ✅ Pure Ruby |
309
+ | **Zero Native Deps** | ⚡ Via Citrus backend | ❌ No | ❌ No | ✅ Yes |
310
+ | **Minimum Ruby** | 3.2+ | 3.0+ | 3.1+ | 0+ |
259
311
 
260
312
  [ruby_tree_sitter]: https://github.com/Faveod/ruby-tree-sitter
261
313
  [tree_stump]: https://github.com/joker1007/tree_stump
@@ -272,68 +324,64 @@ The `*-merge` gem family provides intelligent, AST-based merging for various fil
272
324
 
273
325
  **Choose TreeHaver when:**
274
326
 
275
- - You need JRuby or TruffleRuby support
276
- - You're building a library that should work across Ruby implementations
277
- - You want automatic grammar discovery and security validations
278
- - You want flexibility to switch backends without code changes
279
- - You need incremental parsing with a unified API
280
-
281
- **Choose ruby_tree_sitter directly when:**
282
-
283
- - You only target MRI Ruby
284
- - You need the full Query API without abstraction
285
- - You want the most battle-tested C bindings
286
- - You don't need TreeHaver's grammar discovery
287
-
288
- **Choose tree_stump directly when:**
289
-
290
- - You only target MRI Ruby
291
- - You prefer Rust-based native extensions
292
- - You want precompiled binaries without system dependencies
293
- - You don't need TreeHaver's grammar discovery
294
- - **Note:** `tree_stump` currently requires unreleased fixes in the `main` branch.
295
-
327
+ - You need JRuby or TruffleRuby support
328
+ - You're building a library that should work across Ruby implementations
329
+ - You want automatic grammar discovery and security validations
330
+ - You want flexibility to switch backends without code changes
331
+ - You need incremental parsing with a unified API
332
+ **Choose ruby\_tree\_sitter directly when:**
333
+
334
+ - You only target MRI Ruby
335
+ - You need the full Query API without abstraction
336
+ - You want the most battle-tested C bindings
337
+ - You don't need TreeHaver's grammar discovery
338
+ **Choose tree\_stump directly when:**
339
+
340
+ - You only target MRI Ruby
341
+ - You prefer Rust-based native extensions
342
+ - You want precompiled binaries without system dependencies
343
+ - You don't need TreeHaver's grammar discovery
344
+ - **Note:** `tree_stump` currently requires unreleased fixes in the `main` branch.
296
345
  **Choose citrus directly when:**
297
346
 
298
- - You need zero native dependencies (pure Ruby)
299
- - You're using a Citrus grammar (not tree-sitter grammars)
300
- - Performance is less critical than portability
301
- - You don't need TreeHaver's unified API
302
-
347
+ - You need zero native dependencies (pure Ruby)
348
+ - You're using a Citrus grammar (not tree-sitter grammars)
349
+ - Performance is less critical than portability
350
+ - You don't need TreeHaver's unified API
303
351
  ## 💡 Info you can shake a stick at
304
352
 
305
- | Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] |
306
- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
307
- | Works with JRuby | [![JRuby 10.0 Compat][💎jruby-c-i]][🚎11-c-wf] [![JRuby HEAD Compat][💎jruby-headi]][🚎3-hd-wf] |
308
- | 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] |
309
- | 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] |
310
- | 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] |
311
- | 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] |
312
- | 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] |
313
- | 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] |
314
- | 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] |
315
- | 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] |
316
- | `...` 💖 | [![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] |
353
+ | Tokens to Remember | [![Gem name](https://img.shields.io/badge/name-tree__haver-3C2D2D.svg?style=square&logo=rubygems&logoColor=red)](https://bestgems.org/gems/tree_haver) [![Gem namespace](https://img.shields.io/badge/namespace-TreeHaver-3C2D2D.svg?style=square&logo=ruby&logoColor=white)](https://github.com/kettle-rb/tree_haver) |
354
+ | --- | --- |
355
+ | Works with JRuby | [![JRuby 10.0 Compat](https://img.shields.io/badge/JRuby-current-FBE742?style=for-the-badge&logo=ruby&logoColor=green)](https://github.com/kettle-rb/tree_haver/actions/workflows/current.yml) [![JRuby HEAD Compat](https://img.shields.io/badge/JRuby-HEAD-FBE742?style=for-the-badge&logo=ruby&logoColor=blue)](https://github.com/kettle-rb/tree_haver/actions/workflows/heads.yml) |
356
+ | Works with Truffle Ruby | [![Truffle Ruby 23.1 Compat](https://img.shields.io/badge/Truffle_Ruby-23.1-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink)](https://github.com/kettle-rb/tree_haver/actions/workflows/truffle.yml) [![Truffle Ruby 24.1 Compat](https://img.shields.io/badge/Truffle_Ruby-current-34BCB1?style=for-the-badge&logo=ruby&logoColor=green)](https://github.com/kettle-rb/tree_haver/actions/workflows/current.yml) |
357
+ | Works with MRI Ruby 3 | [![Ruby 3.2 Compat](https://img.shields.io/badge/Ruby-3.2-CC342D?style=for-the-badge&logo=ruby&logoColor=white)](https://github.com/kettle-rb/tree_haver/actions/workflows/supported.yml) [![Ruby 3.3 Compat](https://img.shields.io/badge/Ruby-3.3-CC342D?style=for-the-badge&logo=ruby&logoColor=white)](https://github.com/kettle-rb/tree_haver/actions/workflows/supported.yml) [![Ruby 3.4 Compat](https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green)](https://github.com/kettle-rb/tree_haver/actions/workflows/current.yml) [![Ruby HEAD Compat](https://img.shields.io/badge/Ruby-HEAD-CC342D?style=for-the-badge&logo=ruby&logoColor=blue)](https://github.com/kettle-rb/tree_haver/actions/workflows/heads.yml) |
358
+ | Support & Community | [![Join Me on Daily.dev's RubyFriends](https://img.shields.io/badge/daily.dev-%F0%9F%92%8E_Ruby_Friends-0A0A0A?style=for-the-badge&logo=dailydotdev&logoColor=white)](https://app.daily.dev/squads/rubyfriends) [![Live Chat on Discord](https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord)](https://discord.gg/3qme4XHNKN) [![Get help from me on Upwork](https://img.shields.io/badge/UpWork-13544E?style=for-the-badge&logo=Upwork&logoColor=white)](https://www.upwork.com/freelancers/~014942e9b056abdf86?mp_source=share) [![Get help from me on Codementor](https://img.shields.io/badge/CodeMentor-Get_Help-1abc9c?style=for-the-badge&logo=CodeMentor&logoColor=white)](https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github) |
359
+ | Source | [![Source on GitLab.com](https://img.shields.io/badge/GitLab-FBA326?style=for-the-badge&logo=Gitlab&logoColor=orange)](https://gitlab.com/kettle-rb/tree_haver/) [![Source on CodeBerg.org](https://img.shields.io/badge/CodeBerg-4893CC?style=for-the-badge&logo=CodeBerg&logoColor=blue)](https://codeberg.org/kettle-rb/tree_haver) [![Source on Github.com](https://img.shields.io/badge/GitHub-238636?style=for-the-badge&logo=Github&logoColor=green)](https://github.com/kettle-rb/tree_haver) [![The best SHA: dQw4w9WgXcQ\!](https://img.shields.io/badge/KLOC-2.484-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue)](https://www.youtube.com/watch?v=dQw4w9WgXcQ) |
360
+ | Documentation | [![Current release on RubyDoc.info](https://img.shields.io/badge/RubyDoc-Current_Release-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white)](http://rubydoc.info/gems/tree_haver) [![YARD on Galtzo.com](https://img.shields.io/badge/YARD_on_Galtzo.com-HEAD-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white)](https://tree-haver.galtzo.com) [![Maintainer Blog](https://img.shields.io/badge/blog-railsbling-0093D0.svg?style=for-the-badge&logo=rubyonrails&logoColor=orange)](http://www.railsbling.com/tags/tree_haver) [![GitLab Wiki](https://img.shields.io/badge/wiki-examples-943CD2.svg?style=for-the-badge&logo=gitlab&logoColor=white)](https://gitlab.com/kettle-rb/tree_haver/-/wikis/home) [![GitHub Wiki](https://img.shields.io/badge/wiki-examples-943CD2.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/kettle-rb/tree_haver/wiki) |
361
+ | Compliance | [![License: MIT](https://img.shields.io/badge/License-MIT-259D6C.svg)](https://opensource.org/licenses/MIT) [![Compatible with Apache Software Projects: Verified by SkyWalking Eyes](https://img.shields.io/badge/Apache_Compatible:_Category_A-%E2%9C%93-259D6C.svg?style=flat&logo=Apache)](https://dev.to/galtzo/how-to-check-license-compatibility-41h0) [![📄ilo-declaration-img](https://img.shields.io/badge/ILO_Fundamental_Principles-✓-259D6C.svg?style=flat)](https://www.ilo.org/declaration/lang--en/index.htm) [![Security Policy](https://img.shields.io/badge/security-policy-259D6C.svg?style=flat)](SECURITY.md) [![Contributor Covenant 2.1](https://img.shields.io/badge/Contributor_Covenant-2.1-259D6C.svg)](CODE_OF_CONDUCT.md) [![SemVer 2.0.0](https://img.shields.io/badge/semver-2.0.0-259D6C.svg?style=flat)](https://semver.org/spec/v2.0.0.html) |
362
+ | Style | [![Enforced Code Style Linter](https://img.shields.io/badge/code_style_&_linting-rubocop--lts-34495e.svg?plastic&logo=ruby&logoColor=white)](https://github.com/rubocop-lts/rubocop-lts) [![Keep-A-Changelog 1.0.0](https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat)](https://keepachangelog.com/en/1.0.0/) [![Gitmoji Commits](https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square)](https://gitmoji.dev) [![Compatibility appraised by: appraisal2](https://img.shields.io/badge/appraised_by-appraisal2-34495e.svg?plastic&logo=ruby&logoColor=white)](https://github.com/appraisal-rb/appraisal2) |
363
+ | Maintainer 🎖️ | [![Follow Me on LinkedIn](https://img.shields.io/badge/PeterBoling-LinkedIn-0B66C2?style=flat&logo=newjapanprowrestling)](http://www.linkedin.com/in/peterboling) [![Follow Me on Ruby.Social](https://img.shields.io/mastodon/follow/109447111526622197?domain=https://ruby.social&style=flat&logo=mastodon&label=Ruby%20@galtzo)](https://ruby.social/@galtzo) [![Follow Me on Bluesky](https://img.shields.io/badge/@galtzo.com-0285FF?style=flat&logo=bluesky&logoColor=white)](https://bsky.app/profile/galtzo.com) [![Contact Maintainer](https://img.shields.io/badge/Contact-Maintainer-0093D0.svg?style=flat&logo=rubyonrails&logoColor=red)](http://www.railsbling.com/contact) [![My technical writing](https://img.shields.io/badge/dev.to-0A0A0A?style=flat&logo=devdotto&logoColor=white)](https://dev.to/galtzo) |
364
+ | `...` 💖 | [![Find Me on WellFound:](https://img.shields.io/badge/peter--boling-orange?style=flat&logo=wellfound)](https://wellfound.com/u/peter-boling) [![Find Me on CrunchBase](https://img.shields.io/badge/peter--boling-purple?style=flat&logo=crunchbase)](https://www.crunchbase.com/person/peter-boling) [![My LinkTree](https://img.shields.io/badge/galtzo-purple?style=flat&logo=linktree)](https://linktr.ee/galtzo) [![More About Me](https://img.shields.io/badge/about.me-0A0A0A?style=flat&logo=aboutme&logoColor=white)](https://about.me/peter.boling) [🧊](https://codeberg.org/pboling) [🐙](https://github.org/pboling) [🛖](https://sr.ht/~galtzo/) [🧪](https://gitlab.com/pboling) |
317
365
 
318
366
  ### Compatibility
319
367
 
320
368
  Compatible with MRI Ruby 3.2.0+, and concordant releases of JRuby, and TruffleRuby.
321
369
 
322
- | 🚚 _Amazing_ test matrix was brought to you by | 🔎 appraisal2 🔎 and the color 💚 green 💚 |
323
- | ---------------------------------------------- | -------------------------------------------------------- |
324
- | 👟 Check it out! | ✨ [github.com/appraisal-rb/appraisal2][💎appraisal2] ✨ |
370
+ | 🚚 *Amazing* test matrix was brought to you by | 🔎 appraisal2 🔎 and the color 💚 green 💚 |
371
+ | --- | --- |
372
+ | 👟 Check it out\! | ✨ [github.com/appraisal-rb/appraisal2](https://github.com/appraisal-rb/appraisal2) ✨ |
325
373
 
326
374
  ### Federated DVCS
327
375
 
328
376
  <details markdown="1">
329
377
  <summary>Find this repo on federated forges (Coming soon!)</summary>
330
378
 
331
- | Federated [DVCS][💎d-in-dvcs] Repository | Status | Issues | PRs | Wiki | CI | Discussions |
332
- | ----------------------------------------------- | --------------------------------------------------------------------- | ------------------------- | ------------------------ | ------------------------- | ------------------------ | ---------------------------- |
333
- | 🧪 [kettle-rb/tree_haver on GitLab][📜src-gl] | The Truth | [💚][🤝gl-issues] | [💚][🤝gl-pulls] | [💚][📜gl-wiki] | 🐭 Tiny Matrix | ➖ |
334
- | 🧊 [kettle-rb/tree_haver on CodeBerg][📜src-cb] | An Ethical Mirror ([Donate][🤝cb-donate]) | [💚][🤝cb-issues] | [💚][🤝cb-pulls] | ➖ | ⭕️ No Matrix | ➖ |
335
- | 🐙 [kettle-rb/tree_haver on GitHub][📜src-gh] | Another Mirror | [💚][🤝gh-issues] | [💚][🤝gh-pulls] | [💚][📜gh-wiki] | 💯 Full Matrix | [💚][gh-discussions] |
336
- | 🎮️ [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] |
379
+ | Federated [DVCS](https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/) Repository | Status | Issues | PRs | Wiki | CI | Discussions |
380
+ | --- | --- | --- | --- | --- | --- | --- |
381
+ | 🧪 [kettle-rb/tree\_haver on GitLab](https://gitlab.com/kettle-rb/tree_haver/) | The Truth | [💚](https://gitlab.com/kettle-rb/tree_haver/-/issues) | [💚](https://gitlab.com/kettle-rb/tree_haver/-/merge_requests) | [💚](https://gitlab.com/kettle-rb/tree_haver/-/wikis/home) | 🐭 Tiny Matrix | ➖ |
382
+ | 🧊 [kettle-rb/tree\_haver on CodeBerg](https://codeberg.org/kettle-rb/tree_haver) | An Ethical Mirror ([Donate](https://donate.codeberg.org/)) | [💚](https://codeberg.org/kettle-rb/tree_haver/issues) | [💚](https://codeberg.org/kettle-rb/tree_haver/pulls) | ➖ | ⭕️ No Matrix | ➖ |
383
+ | 🐙 [kettle-rb/tree\_haver on GitHub](https://github.com/kettle-rb/tree_haver) | Another Mirror | [💚](https://github.com/kettle-rb/tree_haver/issues) | [💚](https://github.com/kettle-rb/tree_haver/pulls) | [💚](https://github.com/kettle-rb/tree_haver/wiki) | 💯 Full Matrix | [💚](https://github.com/kettle-rb/tree_haver/discussions) |
384
+ | 🎮️ [Discord Server](https://discord.gg/3qme4XHNKN) | [![Live Chat on Discord](https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord)](https://discord.gg/3qme4XHNKN) | [Let's](https://discord.gg/3qme4XHNKN) | [talk](https://discord.gg/3qme4XHNKN) | [about](https://discord.gg/3qme4XHNKN) | [this](https://discord.gg/3qme4XHNKN) | [library\!](https://discord.gg/3qme4XHNKN) |
337
385
 
338
386
  </details>
339
387
 
@@ -348,31 +396,29 @@ Available as part of the Tidelift Subscription.
348
396
 
349
397
  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.
350
398
 
351
- [![Get help from me on Tidelift][🏙️entsup-tidelift-img]][🏙️entsup-tidelift]
352
-
353
- - 💡Subscribe for support guarantees covering _all_ your FLOSS dependencies
354
- - 💡Tidelift is part of [Sonar][🏙️entsup-tidelift-sonar]
355
- - 💡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
399
+ [![Get help from me on Tidelift](https://img.shields.io/badge/Tidelift_and_Sonar-Enterprise_Support-FD3456?style=for-the-badge&logo=sonar&logoColor=white)](https://tidelift.com/subscription/pkg/rubygems-tree_haver?utm_source=rubygems-tree_haver&utm_medium=referral&utm_campaign=readme)
356
400
 
401
+ - 💡Subscribe for support guarantees covering *all* your FLOSS dependencies
402
+ - 💡Tidelift is part of [Sonar](https://blog.tidelift.com/tidelift-joins-sonar)
403
+ - 💡Tidelift pays maintainers to maintain the software you depend on\!<br/>📊`@`Pointy Haired Boss: An [enterprise support](https://tidelift.com/subscription/pkg/rubygems-tree_haver?utm_source=rubygems-tree_haver&utm_medium=referral&utm_campaign=readme) subscription is "[never gonna let you down](https://www.youtube.com/watch?v=dQw4w9WgXcQ)", and *supports* open source maintainers
357
404
  Alternatively:
358
405
 
359
- - [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite]
360
- - [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork]
361
- - [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor]
362
-
406
+ - [![Live Chat on Discord](https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord)](https://discord.gg/3qme4XHNKN)
407
+ - [![Get help from me on Upwork](https://img.shields.io/badge/UpWork-13544E?style=for-the-badge&logo=Upwork&logoColor=white)](https://www.upwork.com/freelancers/~014942e9b056abdf86?mp_source=share)
408
+ - [![Get help from me on Codementor](https://img.shields.io/badge/CodeMentor-Get_Help-1abc9c?style=for-the-badge&logo=CodeMentor&logoColor=white)](https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github)
363
409
  </details>
364
410
 
365
411
  ## ✨ Installation
366
412
 
367
413
  Install the gem and add to the application's Gemfile by executing:
368
414
 
369
- ```console
415
+ ``` console
370
416
  bundle add tree_haver
371
417
  ```
372
418
 
373
419
  If bundler is not being used to manage dependencies, install the gem by executing:
374
420
 
375
- ```console
421
+ ``` console
376
422
  gem install tree_haver
377
423
  ```
378
424
 
@@ -381,19 +427,19 @@ gem install tree_haver
381
427
  <details markdown="1">
382
428
  <summary>For Medium or High Security Installations</summary>
383
429
 
384
- This gem is cryptographically signed, and has verifiable [SHA-256 and SHA-512][💎SHA_checksums] checksums by
385
- [stone_checksums][💎stone_checksums]. Be sure the gem you install hasn’t been tampered with
430
+ This gem is cryptographically signed, and has verifiable [SHA-256 and SHA-512](https://gitlab.com/kettle-rb/tree_haver/-/tree/main/checksums) checksums by
431
+ [stone\_checksums](https://github.com/galtzo-floss/stone_checksums). Be sure the gem you install hasn’t been tampered with
386
432
  by following the instructions below.
387
433
 
388
434
  Add my public key (if you haven’t already, expires 2045-04-29) as a trusted certificate:
389
435
 
390
- ```console
436
+ ``` console
391
437
  gem cert --add <(curl -Ls https://raw.github.com/galtzo-floss/certs/main/pboling.pem)
392
438
  ```
393
439
 
394
440
  You only need to do that once. Then proceed to install with:
395
441
 
396
- ```console
442
+ ``` console
397
443
  gem install tree_haver -P HighSecurity
398
444
  ```
399
445
 
@@ -401,7 +447,7 @@ The `HighSecurity` trust profile will verify signed gems, and not allow the inst
401
447
 
402
448
  If you want to up your security game full-time:
403
449
 
404
- ```console
450
+ ``` console
405
451
  bundle config set --global trust-policy MediumSecurity
406
452
  ```
407
453
 
@@ -420,17 +466,17 @@ TreeHaver supports 10 parsing backends, each with different trade-offs. The `aut
420
466
  #### Tree-sitter Backends (Universal Parsing)
421
467
 
422
468
  | Backend | Description | Performance | Portability | Examples |
423
- |---------|-------------|-------------|-------------|----------|
469
+ | --- | --- | --- | --- | --- |
424
470
  | **Auto** | Auto-selects best backend | Varies | ✅ Universal | [JSON](examples/auto_json.rb) · [JSONC](examples/auto_jsonc.rb) · [Bash](examples/auto_bash.rb) · [TOML](examples/auto_toml.rb) |
425
- | **MRI** | C extension via ruby_tree_sitter | ⚡ Fastest | MRI only | [JSON](examples/mri_json.rb) · [JSONC](examples/mri_jsonc.rb) · ~~Bash~~* · [TOML](examples/mri_toml.rb) |
426
- | **Rust** | Precompiled via tree_stump | ⚡ Very Fast | ✅ Good | [JSON](examples/rust_json.rb) · [JSONC](examples/rust_jsonc.rb) · ~~Bash~~* · [TOML](examples/rust_toml.rb) |
471
+ | **MRI** | C extension via ruby\_tree\_sitter | ⚡ Fastest | MRI only | [JSON](examples/mri_json.rb) · [JSONC](examples/mri_jsonc.rb) · \~\~Bash\~\~\* · [TOML](examples/mri_toml.rb) |
472
+ | **Rust** | Precompiled via tree\_stump | ⚡ Very Fast | ✅ Good | [JSON](examples/rust_json.rb) · [JSONC](examples/rust_jsonc.rb) · \~\~Bash\~\~\* · [TOML](examples/rust_toml.rb) |
427
473
  | **FFI** | Dynamic linking via FFI | 🔵 Fast | ✅ Universal | [JSON](examples/ffi_json.rb) · [JSONC](examples/ffi_jsonc.rb) · [Bash](examples/ffi_bash.rb) · [TOML](examples/ffi_toml.rb) |
428
474
  | **Java** | JNI bindings | ⚡ Very Fast | JRuby only | [JSON](examples/java_json.rb) · [JSONC](examples/java_jsonc.rb) · [Bash](examples/java_bash.rb) · [TOML](examples/java_toml.rb) |
429
475
 
430
476
  #### Language-Specific Backends (Native Parser Integration)
431
477
 
432
478
  | Backend | Description | Performance | Portability | Examples |
433
- |---------|-------------|-------------|-------------|----------|
479
+ | --- | --- | --- | --- | --- |
434
480
  | **Prism** | Ruby's official parser | ⚡ Very Fast | ✅ Universal | [Ruby](examples/prism_ruby.rb) |
435
481
  | **Psych** | Ruby's YAML parser (stdlib) | ⚡ Very Fast | ✅ Universal | [YAML](examples/psych_yaml.rb) |
436
482
  | **Commonmarker** | Markdown via comrak (Rust) | ⚡ Very Fast | ✅ Good | [Markdown](examples/commonmarker_markdown.rb) · [Merge](examples/commonmarker_merge_example.rb) |
@@ -440,12 +486,11 @@ TreeHaver supports 10 parsing backends, each with different trade-offs. The `aut
440
486
  **Selection Priority (Auto mode):** MRI → Rust → FFI → Java → Prism → Psych → Commonmarker → Markly → Citrus
441
487
 
442
488
  **Known Issues:**
443
- - *MRI + Bash: ABI incompatibility (use FFI instead)
444
- - *Rust + Bash: Version mismatch (use FFI instead)
445
-
489
+ - \*MRI + Bash: ABI incompatibility (use FFI instead)
490
+ - \*Rust + Bash: Version mismatch (use FFI instead)
446
491
  **Backend Requirements:**
447
492
 
448
- ```ruby
493
+ ``` ruby
449
494
  # Tree-sitter backends
450
495
  gem "ruby_tree_sitter", "~> 2.0" # MRI backend
451
496
  gem "tree_stump" # Rust backend
@@ -465,7 +510,7 @@ gem "citrus", "~> 3.0" # Citrus backend
465
510
 
466
511
  **Force Specific Backend:**
467
512
 
468
- ```ruby
513
+ ``` ruby
469
514
  # Tree-sitter backends
470
515
  TreeHaver.backend = :mri # Force MRI backend (ruby_tree_sitter)
471
516
  TreeHaver.backend = :rust # Force Rust backend (tree_stump)
@@ -491,7 +536,7 @@ Use `with_backend` to temporarily switch backends for a specific block of code.
491
536
  This is thread-safe and supports nesting—the previous backend is automatically
492
537
  restored when the block exits (even if an exception is raised).
493
538
 
494
- ```ruby
539
+ ``` ruby
495
540
  # Temporarily use a specific backend
496
541
  TreeHaver.with_backend(:mri) do
497
542
  parser = TreeHaver::Parser.new
@@ -514,12 +559,13 @@ end
514
559
 
515
560
  This is particularly useful for:
516
561
 
517
- - **Testing**: Test the same code with different backends
518
- - **Performance comparison**: Benchmark different backends
519
- - **Fallback scenarios**: Try one backend, fall back to another
520
- - **Thread isolation**: Each thread can use a different backend safely
562
+ - **Testing**: Test the same code with different backends
563
+ - **Performance comparison**: Benchmark different backends
564
+ - **Fallback scenarios**: Try one backend, fall back to another
565
+ - **Thread isolation**: Each thread can use a different backend safely
521
566
 
522
- ```ruby
567
+ <!-- end list -->
568
+ ``` ruby
523
569
  # Example: Testing with multiple backends
524
570
  [:mri, :rust, :citrus].each do |backend_name|
525
571
  TreeHaver.with_backend(backend_name) do
@@ -532,7 +578,7 @@ end
532
578
 
533
579
  **Check Backend Capabilities:**
534
580
 
535
- ```ruby
581
+ ``` ruby
536
582
  TreeHaver.backend # => :ffi
537
583
  TreeHaver.backend_module # => TreeHaver::Backends::FFI
538
584
  TreeHaver.capabilities # => { backend: :ffi, parse: true, query: false, ... }
@@ -550,17 +596,16 @@ TreeHaver provides defense-in-depth validations, but you should understand the r
550
596
 
551
597
  TreeHaver's `PathValidator` module protects against:
552
598
 
553
- - **Path traversal**: Paths containing `/../` or `/./` are rejected
554
- - **Null byte injection**: Paths containing null bytes are rejected
555
- - **Non-absolute paths**: Relative paths are rejected to prevent CWD-based attacks
556
- - **Invalid extensions**: Only `.so`, `.dylib`, and `.dll` files are accepted
557
- - **Malicious filenames**: Filenames must match a safe pattern (alphanumeric, hyphens, underscores)
558
- - **Invalid language names**: Language names must be lowercase alphanumeric with underscores
559
- - **Invalid symbol names**: Symbol names must be valid C identifiers
560
-
599
+ - **Path traversal**: Paths containing `/../` or `/./` are rejected
600
+ - **Null byte injection**: Paths containing null bytes are rejected
601
+ - **Non-absolute paths**: Relative paths are rejected to prevent CWD-based attacks
602
+ - **Invalid extensions**: Only `.so`, `.dylib`, and `.dll` files are accepted
603
+ - **Malicious filenames**: Filenames must match a safe pattern (alphanumeric, hyphens, underscores)
604
+ - **Invalid language names**: Language names must be lowercase alphanumeric with underscores
605
+ - **Invalid symbol names**: Symbol names must be valid C identifiers
561
606
  #### Secure Usage
562
607
 
563
- ```ruby
608
+ ``` ruby
564
609
  # Standard usage - paths from ENV are validated
565
610
  finder = TreeHaver::GrammarFinder.new(:toml)
566
611
  path = finder.find_library_path # Validates ENV path before returning
@@ -584,16 +629,15 @@ The `find_library_path_safe` method only returns paths in trusted directories.
584
629
 
585
630
  **Default trusted directories:**
586
631
 
587
- - `/usr/lib`, `/usr/lib64`
588
- - `/usr/lib/x86_64-linux-gnu`, `/usr/lib/aarch64-linux-gnu`
589
- - `/usr/local/lib`
590
- - `/opt/homebrew/lib`, `/opt/local/lib`
591
-
632
+ - `/usr/lib`, `/usr/lib64`
633
+ - `/usr/lib/x86_64-linux-gnu`, `/usr/lib/aarch64-linux-gnu`
634
+ - `/usr/local/lib`
635
+ - `/opt/homebrew/lib`, `/opt/local/lib`
592
636
  **Adding custom trusted directories:**
593
637
 
594
638
  For non-standard installations (Homebrew on Linux, luarocks, mise, asdf, etc.), register additional trusted directories:
595
639
 
596
- ```ruby
640
+ ``` ruby
597
641
  # Programmatically at application startup
598
642
  TreeHaver::PathValidator.add_trusted_directory("/home/linuxbrew/.linuxbrew/Cellar")
599
643
  TreeHaver::PathValidator.add_trusted_directory("~/.local/share/mise/installs/lua")
@@ -604,7 +648,7 @@ export TREE_HAVER_TRUSTED_DIRS = "/home/linuxbrew/.linuxbrew/Cellar,~/.local/sha
604
648
 
605
649
  **Example: Fedora Silverblue with Homebrew and luarocks**
606
650
 
607
- ```bash
651
+ ``` bash
608
652
  # In ~/.bashrc or ~/.zshrc
609
653
  export TREE_HAVER_TRUSTED_DIRS="/home/linuxbrew/.linuxbrew/Cellar,~/.local/share/mise/installs/lua"
610
654
 
@@ -617,17 +661,16 @@ export TREE_SITTER_TOML_PATH=~/.local/share/mise/installs/lua/5.4.8/luarocks/lib
617
661
 
618
662
  #### Recommendations
619
663
 
620
- 1. **Production**: Consider using `find_library_path_safe` to ignore ENV overrides
621
- 2. **Development**: Standard `find_library_path` is convenient for testing
622
- 3. **User Input**: Always validate paths before passing to `Language.from_library`
623
- 4. **CI/CD**: Be cautious of ENV vars that could be set by untrusted sources
624
- 5. **Custom installs**: Register trusted directories via `TREE_HAVER_TRUSTED_DIRS` or `add_trusted_directory`
625
-
664
+ 1. **Production**: Consider using `find_library_path_safe` to ignore ENV overrides
665
+ 2. **Development**: Standard `find_library_path` is convenient for testing
666
+ 3. **User Input**: Always validate paths before passing to `Language.from_library`
667
+ 4. **CI/CD**: Be cautious of ENV vars that could be set by untrusted sources
668
+ 5. **Custom installs**: Register trusted directories via `TREE_HAVER_TRUSTED_DIRS` or `add_trusted_directory`
626
669
  ### Backend Selection
627
670
 
628
671
  TreeHaver automatically selects the best backend for your Ruby implementation, but you can override this behavior:
629
672
 
630
- ```ruby
673
+ ``` ruby
631
674
  # Automatic backend selection (default)
632
675
  TreeHaver.backend = :auto
633
676
 
@@ -647,7 +690,7 @@ TreeHaver.backend = :citrus # Use Citrus pure Ruby parser
647
690
 
648
691
  You can also set the backend via environment variable:
649
692
 
650
- ```bash
693
+ ``` bash
651
694
  export TREE_HAVER_BACKEND=rust
652
695
  ```
653
696
 
@@ -659,39 +702,37 @@ TreeHaver recognizes several environment variables for configuration:
659
702
 
660
703
  #### Security Configuration
661
704
 
662
- - **`TREE_HAVER_TRUSTED_DIRS`**: Comma-separated list of additional trusted directories for grammar libraries
705
+ - **`TREE_HAVER_TRUSTED_DIRS`**: Comma-separated list of additional trusted directories for grammar libraries
663
706
 
664
- ```bash
665
- # For Homebrew on Linux and luarocks
666
- export TREE_HAVER_TRUSTED_DIRS="/home/linuxbrew/.linuxbrew/Cellar,~/.local/share/mise/installs/lua"
667
- ```
668
-
669
- Tilde (`~`) is expanded to the user's home directory. Directories listed here are considered safe for `find_library_path_safe`.
707
+ ``` bash
708
+ # For Homebrew on Linux and luarocks
709
+ export TREE_HAVER_TRUSTED_DIRS="/home/linuxbrew/.linuxbrew/Cellar,~/.local/share/mise/installs/lua"
710
+ ```
670
711
 
712
+ Tilde (`~`) is expanded to the user's home directory. Directories listed here are considered safe for `find_library_path_safe`.
671
713
  #### Core Runtime Library
672
714
 
673
- - **`TREE_SITTER_RUNTIME_LIB`**: Absolute path to the core `libtree-sitter` shared library
674
- ```bash
675
- export TREE_SITTER_RUNTIME_LIB=/usr/local/lib/libtree-sitter.so
676
- ```
677
-
715
+ - **`TREE_SITTER_RUNTIME_LIB`**: Absolute path to the core `libtree-sitter` shared library
716
+ ``` bash
717
+ export TREE_SITTER_RUNTIME_LIB=/usr/local/lib/libtree-sitter.so
718
+ ```
678
719
  If not set, TreeHaver tries these names in order:
679
720
 
680
- - `tree-sitter`
681
- - `libtree-sitter.so.0`
682
- - `libtree-sitter.so`
683
- - `libtree-sitter.dylib`
684
- - `libtree-sitter.dll`
685
-
721
+ - `tree-sitter`
722
+ - `libtree-sitter.so.0`
723
+ - `libtree-sitter.so`
724
+ - `libtree-sitter.dylib`
725
+ - `libtree-sitter.dll`
686
726
  #### Language Symbol Resolution
687
727
 
688
728
  When loading a language grammar, if you don't specify the `symbol:` parameter, TreeHaver resolves it in this precedence:
689
729
 
690
- 1. **`TREE_SITTER_LANG_SYMBOL`**: Explicit symbol override
691
- 2. Guessed from filename (e.g., `libtree-sitter-toml.so` → `tree_sitter_toml`)
692
- 3. Default fallback (`tree_sitter_toml`)
730
+ 1. **`TREE_SITTER_LANG_SYMBOL`**: Explicit symbol override
731
+ 2. Guessed from filename (e.g., `libtree-sitter-toml.so` → `tree_sitter_toml`)
732
+ 3. Default fallback (`tree_sitter_toml`)
693
733
 
694
- ```bash
734
+ <!-- end list -->
735
+ ``` bash
695
736
  export TREE_SITTER_LANG_SYMBOL=tree_sitter_toml
696
737
  ```
697
738
 
@@ -699,7 +740,7 @@ export TREE_SITTER_LANG_SYMBOL=tree_sitter_toml
699
740
 
700
741
  For specific languages, you can set environment variables to point to grammar libraries:
701
742
 
702
- ```bash
743
+ ``` bash
703
744
  export TREE_SITTER_TOML_PATH=/usr/local/lib/libtree-sitter-toml.so
704
745
  export TREE_SITTER_JSON_PATH=/usr/local/lib/libtree-sitter-json.so
705
746
  ```
@@ -708,7 +749,7 @@ export TREE_SITTER_JSON_PATH=/usr/local/lib/libtree-sitter-json.so
708
749
 
709
750
  For the Java backend on JRuby:
710
751
 
711
- ```bash
752
+ ``` bash
712
753
  export TREE_SITTER_JAVA_JARS_DIR=/path/to/java-tree-sitter/jars
713
754
  ```
714
755
 
@@ -718,7 +759,7 @@ For more see [docs](https://tree-sitter.github.io/java-tree-sitter/), [maven](ht
718
759
 
719
760
  Register languages once at application startup for convenient access:
720
761
 
721
- ```ruby
762
+ ``` ruby
722
763
  # Register a TOML grammar
723
764
  TreeHaver.register_language(
724
765
  :toml,
@@ -739,7 +780,7 @@ language = TreeHaver::Language.toml(
739
780
 
740
781
  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.
741
782
 
742
- ```ruby
783
+ ``` ruby
743
784
  # Create a finder for any language
744
785
  finder = TreeHaver::GrammarFinder.new(:toml)
745
786
 
@@ -762,25 +803,24 @@ language = TreeHaver::Language.toml
762
803
 
763
804
  Given just the language name, `GrammarFinder` automatically derives:
764
805
 
765
- | Property | Derived Value (for `:toml`) |
766
- | ---------------- | ---------------------------------------------------- |
767
- | ENV var | `TREE_SITTER_TOML_PATH` |
806
+ | Property | Derived Value (for `:toml`) |
807
+ | --- | --- |
808
+ | ENV var | `TREE_SITTER_TOML_PATH` |
768
809
  | Library filename | `libtree-sitter-toml.so` (Linux) or `.dylib` (macOS) |
769
- | Symbol name | `tree_sitter_toml` |
810
+ | Symbol name | `tree_sitter_toml` |
770
811
 
771
812
  #### Search Order
772
813
 
773
814
  `GrammarFinder` searches for grammars in this order:
774
815
 
775
- 1. **Environment variable**: `TREE_SITTER_<LANG>_PATH` (highest priority)
776
- 2. **Extra paths**: Custom paths provided at initialization
777
- 3. **System paths**: Common installation directories (`/usr/lib`, `/usr/local/lib`, `/opt/homebrew/lib`, etc.)
778
-
816
+ 1. **Environment variable**: `TREE_SITTER_<LANG>_PATH` (highest priority)
817
+ 2. **Extra paths**: Custom paths provided at initialization
818
+ 3. **System paths**: Common installation directories (`/usr/lib`, `/usr/local/lib`, `/opt/homebrew/lib`, etc.)
779
819
  #### Usage in \*-merge Gems
780
820
 
781
821
  The `GrammarFinder` pattern enables clean integration in language-specific merge gems:
782
822
 
783
- ```ruby
823
+ ``` ruby
784
824
  # In toml-merge
785
825
  finder = TreeHaver::GrammarFinder.new(:toml)
786
826
  finder.register! if finder.available?
@@ -800,7 +840,7 @@ Each gem uses the same API—only the language name changes.
800
840
 
801
841
  For non-standard installations, provide extra search paths:
802
842
 
803
- ```ruby
843
+ ``` ruby
804
844
  finder = TreeHaver::GrammarFinder.new(:toml, extra_paths: [
805
845
  "/opt/custom/lib",
806
846
  "/home/user/.local/lib",
@@ -811,7 +851,7 @@ finder = TreeHaver::GrammarFinder.new(:toml, extra_paths: [
811
851
 
812
852
  Get detailed information about the grammar search:
813
853
 
814
- ```ruby
854
+ ``` ruby
815
855
  finder = TreeHaver::GrammarFinder.new(:toml)
816
856
  puts finder.search_info
817
857
  # => {
@@ -830,7 +870,7 @@ puts finder.search_info
830
870
 
831
871
  Different backends may support different features:
832
872
 
833
- ```ruby
873
+ ``` ruby
834
874
  TreeHaver.capabilities
835
875
  # => { backend: :mri, query: true, bytes_field: true }
836
876
  # or
@@ -843,7 +883,7 @@ TreeHaver.capabilities
843
883
 
844
884
  For codebases migrating from `ruby_tree_sitter`, TreeHaver provides a compatibility shim:
845
885
 
846
- ```ruby
886
+ ``` ruby
847
887
  require "tree_haver/compat"
848
888
 
849
889
  # Now TreeSitter constants map to TreeHaver
@@ -854,13 +894,13 @@ This is safe and idempotent—if the real `TreeSitter` module is already loaded,
854
894
 
855
895
  #### ⚠️ Important: Exception Hierarchy
856
896
 
857
- **Both ruby_tree_sitter v2+ and TreeHaver exceptions inherit from `Exception` (not `StandardError`).**
897
+ **Both ruby\_tree\_sitter v2+ and TreeHaver exceptions inherit from `Exception` (not `StandardError`).**
858
898
 
859
- This design decision follows ruby_tree_sitter's lead for thread-safety and signal handling reasons. See [ruby_tree_sitter PR #83](https://github.com/Faveod/ruby-tree-sitter/pull/83) for the rationale.
899
+ This design decision follows ruby\_tree\_sitter's lead for thread-safety and signal handling reasons. See [ruby\_tree\_sitter PR \#83](https://github.com/Faveod/ruby-tree-sitter/pull/83) for the rationale.
860
900
 
861
901
  **What this means for exception handling:**
862
902
 
863
- ```ruby
903
+ ``` ruby
864
904
  # ⚠️ This will NOT catch TreeHaver errors
865
905
  begin
866
906
  TreeHaver::Language.from_library("/nonexistent.so")
@@ -885,18 +925,16 @@ end
885
925
 
886
926
  **TreeHaver Exception Hierarchy:**
887
927
 
888
- ```
889
- Exception
890
- └── TreeHaver::Error # Base error class
891
- ├── TreeHaver::NotAvailable # Backend/grammar not available
892
- └── TreeHaver::BackendConflict # Backend incompatibility detected
893
- ```
928
+ Exception
929
+ └── TreeHaver::Error # Base error class
930
+ ├── TreeHaver::NotAvailable # Backend/grammar not available
931
+ └── TreeHaver::BackendConflict # Backend incompatibility detected
894
932
 
895
933
  **Compatibility Mode Behavior:**
896
934
 
897
935
  The compat mode (`require "tree_haver/compat"`) creates aliases but **does not change the exception hierarchy**:
898
936
 
899
- ```ruby
937
+ ``` ruby
900
938
  require "tree_haver/compat"
901
939
 
902
940
  # TreeSitter constants are now aliases to TreeHaver
@@ -914,27 +952,26 @@ end
914
952
 
915
953
  **Best Practices:**
916
954
 
917
- 1. **Always use explicit rescue** for TreeHaver errors:
918
- ```ruby
919
- begin
920
- finder = TreeHaver::GrammarFinder.new(:toml)
921
- finder.register! if finder.available?
922
- language = TreeHaver::Language.toml
923
- rescue TreeHaver::NotAvailable => e
924
- warn("TOML grammar not available: #{e.message}")
925
- # Fallback to another backend or fail gracefully
926
- end
927
- `````
955
+ 1. **Always use explicit rescue** for TreeHaver errors:
928
956
 
929
- 2. **Never rely on `rescue => e`** to catch TreeHaver errors (it won't work)
957
+ ``` ruby
958
+ begin
959
+ finder = TreeHaver::GrammarFinder.new(:toml)
960
+ finder.register! if finder.available?
961
+ language = TreeHaver::Language.toml
962
+ rescue TreeHaver::NotAvailable => e
963
+ warn("TOML grammar not available: #{e.message}")
964
+ # Fallback to another backend or fail gracefully
965
+ end
966
+ ```
930
967
 
968
+ 2. **Never rely on `rescue => e`** to catch TreeHaver errors (it won't work)
931
969
  **Why inherit from Exception?**
932
970
 
933
- Following ruby_tree_sitter's reasoning:
934
- - **Thread safety**: Prevents accidental catching in thread cleanup code
935
- - **Signal handling**: Ensures parsing errors don't interfere with SIGTERM/SIGINT
936
- - **Intentional handling**: Forces developers to explicitly handle parsing errors
937
-
971
+ Following ruby\_tree\_sitter's reasoning:
972
+ - **Thread safety**: Prevents accidental catching in thread cleanup code
973
+ - **Signal handling**: Ensures parsing errors don't interfere with SIGTERM/SIGINT
974
+ - **Intentional handling**: Forces developers to explicitly handle parsing errors
938
975
  See `lib/tree_haver/compat.rb` for compatibility layer documentation.
939
976
 
940
977
  ## 🔧 Basic Usage
@@ -943,7 +980,7 @@ See `lib/tree_haver/compat.rb` for compatibility layer documentation.
943
980
 
944
981
  The simplest way to parse code is with `TreeHaver.parser_for`, which handles all the complexity of language loading, grammar discovery, and backend selection:
945
982
 
946
- ```ruby
983
+ ``` ruby
947
984
  require "tree_haver"
948
985
 
949
986
  # Parse TOML - auto-discovers grammar and falls back to Citrus if needed
@@ -969,12 +1006,11 @@ parser = TreeHaver.parser_for(
969
1006
  ```
970
1007
 
971
1008
  `TreeHaver.parser_for` handles:
972
- 1. Checking if the language is already registered
973
- 2. Auto-discovering tree-sitter grammar via `GrammarFinder`
974
- 3. Falling back to Citrus grammar if tree-sitter is unavailable
975
- 4. Creating and configuring the parser
976
- 5. Raising `NotAvailable` with a helpful message if nothing works
977
-
1009
+ 1. Checking if the language is already registered
1010
+ 2. Auto-discovering tree-sitter grammar via `GrammarFinder`
1011
+ 3. Falling back to Citrus grammar if tree-sitter is unavailable
1012
+ 4. Creating and configuring the parser
1013
+ 5. Raising `NotAvailable` with a helpful message if nothing works
978
1014
  ### Manual Parser Setup
979
1015
 
980
1016
  For more control, you can create parsers manually:
@@ -983,7 +1019,7 @@ TreeHaver works with any language through its 10 backends. Here are examples for
983
1019
 
984
1020
  #### Parsing with Tree-sitter (Universal Languages)
985
1021
 
986
- ```ruby
1022
+ ``` ruby
987
1023
  require "tree_haver"
988
1024
 
989
1025
  # Load a tree-sitter grammar (works with MRI, Rust, FFI, or Java backend)
@@ -1020,7 +1056,7 @@ end
1020
1056
 
1021
1057
  #### Parsing Ruby with Prism
1022
1058
 
1023
- ```ruby
1059
+ ``` ruby
1024
1060
  require "tree_haver"
1025
1061
 
1026
1062
  TreeHaver.backend = :prism
@@ -1054,7 +1090,7 @@ end
1054
1090
 
1055
1091
  #### Parsing YAML with Psych
1056
1092
 
1057
- ```ruby
1093
+ ``` ruby
1058
1094
  require "tree_haver"
1059
1095
 
1060
1096
  TreeHaver.backend = :psych
@@ -1082,7 +1118,7 @@ show_structure(root)
1082
1118
 
1083
1119
  #### Parsing Markdown with Commonmarker or Markly
1084
1120
 
1085
- ```ruby
1121
+ ``` ruby
1086
1122
  require "tree_haver"
1087
1123
 
1088
1124
  # Choose your backend
@@ -1122,7 +1158,7 @@ end
1122
1158
 
1123
1159
  For cleaner code, register languages at startup:
1124
1160
 
1125
- ```ruby
1161
+ ``` ruby
1126
1162
  # At application initialization
1127
1163
  TreeHaver.register_language(
1128
1164
  :toml,
@@ -1151,12 +1187,13 @@ and `symbol` parameters (for tree-sitter) or `grammar_module` (for Citrus).
1151
1187
 
1152
1188
  This flexibility is useful for:
1153
1189
 
1154
- - **Aliasing**: Register the same grammar under multiple names
1155
- - **Versioning**: Register different grammar versions (e.g., `:ruby_2`, `:ruby_3`)
1156
- - **Testing**: Use unique names to avoid collisions between tests
1157
- - **Context-specific naming**: Use names that make sense for your application
1190
+ - **Aliasing**: Register the same grammar under multiple names
1191
+ - **Versioning**: Register different grammar versions (e.g., `:ruby_2`, `:ruby_3`)
1192
+ - **Testing**: Use unique names to avoid collisions between tests
1193
+ - **Context-specific naming**: Use names that make sense for your application
1158
1194
 
1159
- ```ruby
1195
+ <!-- end list -->
1196
+ ``` ruby
1160
1197
  # Register the same TOML grammar under different names for different purposes
1161
1198
  TreeHaver.register_language(
1162
1199
  :config_parser, # Custom name for your app
@@ -1179,7 +1216,7 @@ versioned_lang = TreeHaver::Language.toml_v1
1179
1216
 
1180
1217
  TreeHaver works with any tree-sitter grammar:
1181
1218
 
1182
- ```ruby
1219
+ ``` ruby
1183
1220
  # Parse Ruby code
1184
1221
  ruby_lang = TreeHaver::Language.from_library(
1185
1222
  "/path/to/libtree-sitter-ruby.so",
@@ -1200,7 +1237,7 @@ tree = parser.parse("const x = 42;")
1200
1237
 
1201
1238
  TreeHaver provides simple node traversal:
1202
1239
 
1203
- ```ruby
1240
+ ``` ruby
1204
1241
  tree = parser.parse(source)
1205
1242
  root = tree.root_node
1206
1243
 
@@ -1217,7 +1254,7 @@ walk_tree(root)
1217
1254
 
1218
1255
  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.
1219
1256
 
1220
- ```ruby
1257
+ ``` ruby
1221
1258
  # Check if current backend supports incremental parsing
1222
1259
  if TreeHaver.capabilities[:incremental]
1223
1260
  puts "Incremental parsing is available!"
@@ -1247,13 +1284,13 @@ new_tree = parser.parse_string(tree, "x = 42")
1247
1284
 
1248
1285
  **Note:** `tree_stump` currently requires unreleased fixes in the `main` branch.
1249
1286
 
1250
- ```ruby
1287
+ ``` ruby
1251
1288
  tree.supports_editing? # => true if edit() is available
1252
1289
  ```
1253
1290
 
1254
1291
  ### Error Handling
1255
1292
 
1256
- ```ruby
1293
+ ``` ruby
1257
1294
  begin
1258
1295
  language = TreeHaver::Language.from_library("/path/to/grammar.so")
1259
1296
  rescue TreeHaver::NotAvailable => e
@@ -1273,7 +1310,7 @@ end
1273
1310
 
1274
1311
  On MRI, TreeHaver uses `ruby_tree_sitter` by default:
1275
1312
 
1276
- ```ruby
1313
+ ``` ruby
1277
1314
  # Gemfile
1278
1315
  gem "tree_haver"
1279
1316
  gem "ruby_tree_sitter" # MRI backend
@@ -1288,7 +1325,7 @@ On JRuby, TreeHaver can use the FFI backend, Java backend, or Citrus backend:
1288
1325
 
1289
1326
  **Option 1: FFI Backend (recommended for tree-sitter grammars)**
1290
1327
 
1291
- ```ruby
1328
+ ``` ruby
1292
1329
  # Gemfile
1293
1330
  gem "tree_haver"
1294
1331
  gem "ffi" # Required for FFI backend
@@ -1306,7 +1343,7 @@ parser = TreeHaver::Parser.new
1306
1343
 
1307
1344
  **Option 2: Java Backend (native JVM performance)**
1308
1345
 
1309
- ```bash
1346
+ ``` bash
1310
1347
  # 1. Download java-tree-sitter JAR from Maven Central
1311
1348
  mkdir -p vendor/jars
1312
1349
  curl -fSL -o vendor/jars/jtreesitter-0.23.2.jar \
@@ -1320,7 +1357,7 @@ export LD_LIBRARY_PATH="/path/to/libtree-sitter/lib:$LD_LIBRARY_PATH"
1320
1357
  JAVA_OPTS="--enable-native-access=ALL-UNNAMED" jruby your_script.rb
1321
1358
  ```
1322
1359
 
1323
- ```ruby
1360
+ ``` ruby
1324
1361
  # Force Java backend
1325
1362
  TreeHaver.backend = :java
1326
1363
 
@@ -1340,7 +1377,7 @@ This means grammar `.so` files with unresolved references to `libtree-sitter.so`
1340
1377
 
1341
1378
  **Recommended approach for JRuby:** Use the **FFI backend**:
1342
1379
 
1343
- ```ruby
1380
+ ``` ruby
1344
1381
  # On JRuby, use FFI backend (recommended)
1345
1382
  TreeHaver.backend = :ffi
1346
1383
  ```
@@ -1349,12 +1386,11 @@ The FFI backend uses Ruby's FFI gem which relies on the system's dynamic linker,
1349
1386
 
1350
1387
  The Java backend will work with:
1351
1388
 
1352
- - Grammar JARs built specifically for java-tree-sitter / jtreesitter (self-contained, [docs](https://tree-sitter.github.io/java-tree-sitter/), [maven](https://central.sonatype.com/artifact/io.github.tree-sitter/jtreesitter), [source](https://github.com/tree-sitter/java-tree-sitter))
1353
- - Grammar `.so` files that statically link tree-sitter
1354
-
1389
+ - Grammar JARs built specifically for java-tree-sitter / jtreesitter (self-contained, [docs](https://tree-sitter.github.io/java-tree-sitter/), [maven](https://central.sonatype.com/artifact/io.github.tree-sitter/jtreesitter), [source](https://github.com/tree-sitter/java-tree-sitter))
1390
+ - Grammar `.so` files that statically link tree-sitter
1355
1391
  **Option 3: Citrus Backend (pure Ruby, portable)**
1356
1392
 
1357
- ```ruby
1393
+ ``` ruby
1358
1394
  # Gemfile
1359
1395
  gem "tree_haver"
1360
1396
  gem "citrus" # Pure Ruby parser, zero native dependencies
@@ -1372,17 +1408,16 @@ end
1372
1408
 
1373
1409
  **⚠️ Citrus Backend Limitations:**
1374
1410
 
1375
- - Uses Citrus grammars (not tree-sitter grammars)
1376
- - No incremental parsing support
1377
- - No query API
1378
- - Pure Ruby performance (slower than native backends)
1379
- - Best for: prototyping, environments without native extension support, teaching
1380
-
1411
+ - Uses Citrus grammars (not tree-sitter grammars)
1412
+ - No incremental parsing support
1413
+ - No query API
1414
+ - Pure Ruby performance (slower than native backends)
1415
+ - Best for: prototyping, environments without native extension support, teaching
1381
1416
  #### TruffleRuby
1382
1417
 
1383
1418
  TruffleRuby can use the MRI, FFI, or Citrus backend:
1384
1419
 
1385
- ```ruby
1420
+ ``` ruby
1386
1421
  # Use FFI backend (recommended for tree-sitter grammars)
1387
1422
  TreeHaver.backend = :ffi
1388
1423
 
@@ -1403,7 +1438,7 @@ different contexts.
1403
1438
 
1404
1439
  Test the same code path with different backends using `with_backend`:
1405
1440
 
1406
- ```ruby
1441
+ ``` ruby
1407
1442
  # In your test setup
1408
1443
  RSpec.describe("MyParser") do
1409
1444
  # Test with each available backend
@@ -1426,7 +1461,7 @@ end
1426
1461
 
1427
1462
  Each thread can use a different backend safely—`with_backend` uses thread-local storage:
1428
1463
 
1429
- ```ruby
1464
+ ``` ruby
1430
1465
  threads = []
1431
1466
 
1432
1467
  threads << Thread.new do
@@ -1452,7 +1487,7 @@ threads.each(&:join)
1452
1487
 
1453
1488
  `with_backend` supports nesting—inner blocks override outer blocks:
1454
1489
 
1455
- ```ruby
1490
+ ``` ruby
1456
1491
  TreeHaver.with_backend(:rust) do
1457
1492
  puts TreeHaver.effective_backend # => :rust
1458
1493
 
@@ -1468,7 +1503,7 @@ end
1468
1503
 
1469
1504
  Try one backend, fall back to another on failure:
1470
1505
 
1471
- ```ruby
1506
+ ``` ruby
1472
1507
  def parse_with_fallback(source)
1473
1508
  TreeHaver.with_backend(:mri) do
1474
1509
  TreeHaver::Parser.new.tap { |p| p.language = load_language }.parse(source)
@@ -1485,7 +1520,7 @@ end
1485
1520
 
1486
1521
  Here's a practical example that extracts package names from a TOML file:
1487
1522
 
1488
- ```ruby
1523
+ ``` ruby
1489
1524
  require "tree_haver"
1490
1525
 
1491
1526
  # Setup
@@ -1532,14 +1567,14 @@ package_name = extract_package_name(toml)
1532
1567
 
1533
1568
  TreeHaver provides shared RSpec helpers for conditional test execution based on dependency availability. This is useful for testing code that uses optional backends.
1534
1569
 
1535
- ```ruby
1570
+ ``` ruby
1536
1571
  # In your spec_helper.rb
1537
1572
  require "tree_haver/rspec"
1538
1573
  ```
1539
1574
 
1540
1575
  This automatically configures RSpec with exclusion filters for all TreeHaver dependencies. Use tags to conditionally run tests:
1541
1576
 
1542
- ```ruby
1577
+ ``` ruby
1543
1578
  # Runs only when FFI backend is available
1544
1579
  it "parses with FFI", :ffi do
1545
1580
  # ...
@@ -1564,10 +1599,10 @@ end
1564
1599
  **Available Tags:**
1565
1600
 
1566
1601
  | Tag | Description |
1567
- |-----|-------------|
1602
+ | --- | --- |
1568
1603
  | `:ffi` | FFI backend available (dynamic check) |
1569
- | `:mri_backend` | ruby_tree_sitter gem available |
1570
- | `:rust_backend` | tree_stump gem available |
1604
+ | `:mri_backend` | ruby\_tree\_sitter gem available |
1605
+ | `:rust_backend` | tree\_stump gem available |
1571
1606
  | `:java_backend` | Java backend available (JRuby) |
1572
1607
  | `:prism_backend` | Prism gem available |
1573
1608
  | `:psych_backend` | Psych available (stdlib) |
@@ -1594,7 +1629,7 @@ All tags have negated versions (e.g., `:not_mri_backend`, `:not_jruby`) for test
1594
1629
 
1595
1630
  Set `TREE_HAVER_DEBUG=1` to print a dependency summary at the start of your test suite:
1596
1631
 
1597
- ```bash
1632
+ ``` bash
1598
1633
  TREE_HAVER_DEBUG=1 bundle exec rspec
1599
1634
  ```
1600
1635
 
@@ -1603,42 +1638,42 @@ TREE_HAVER_DEBUG=1 bundle exec rspec
1603
1638
  While kettle-rb tools are free software and will always be, the project would benefit immensely from some funding.
1604
1639
  Raising a monthly budget of... "dollars" would make the project more sustainable.
1605
1640
 
1606
- We welcome both individual and corporate sponsors! We also offer a
1641
+ We welcome both individual and corporate sponsors\! We also offer a
1607
1642
  wide array of funding channels to account for your preferences
1608
- (although currently [Open Collective][🖇osc] is our preferred funding platform).
1643
+ (although currently [Open Collective](https://opencollective.com/kettle-rb) is our preferred funding platform).
1609
1644
 
1610
1645
  **If you're working in a company that's making significant use of kettle-rb tools we'd
1611
1646
  appreciate it if you suggest to your company to become a kettle-rb sponsor.**
1612
1647
 
1613
1648
  You can support the development of kettle-rb tools via
1614
- [GitHub Sponsors][🖇sponsor],
1615
- [Liberapay][⛳liberapay],
1616
- [PayPal][🖇paypal],
1617
- [Open Collective][🖇osc]
1618
- and [Tidelift][🏙️entsup-tidelift].
1619
-
1620
- | 📍 NOTE |
1621
- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1649
+ [GitHub Sponsors](https://github.com/sponsors/pboling),
1650
+ [Liberapay](https://liberapay.com/pboling/donate),
1651
+ [PayPal](https://www.paypal.com/paypalme/peterboling),
1652
+ [Open Collective](https://opencollective.com/kettle-rb)
1653
+ and [Tidelift](https://tidelift.com/subscription/pkg/rubygems-tree_haver?utm_source=rubygems-tree_haver&utm_medium=referral&utm_campaign=readme).
1654
+
1655
+ | 📍 NOTE |
1656
+ | --- |
1622
1657
  | 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. |
1623
1658
 
1624
1659
  ### Open Collective for Individuals
1625
1660
 
1626
- Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/kettle-rb#backer)]
1661
+ Support us with a monthly donation and help us continue our activities. \[[Become a backer](https://opencollective.com/kettle-rb#backer)\]
1627
1662
 
1628
- NOTE: [kettle-readme-backers][kettle-readme-backers] updates this list every day, automatically.
1663
+ NOTE: [kettle-readme-backers](https://github.com/kettle-rb/tree_haver/blob/main/exe/kettle-readme-backers) updates this list every day, automatically.
1629
1664
 
1630
1665
  <!-- OPENCOLLECTIVE-INDIVIDUALS:START -->
1631
- No backers yet. Be the first!
1666
+ No backers yet. Be the first\!
1632
1667
  <!-- OPENCOLLECTIVE-INDIVIDUALS:END -->
1633
1668
 
1634
1669
  ### Open Collective for Organizations
1635
1670
 
1636
- 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)]
1671
+ 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)\]
1637
1672
 
1638
- NOTE: [kettle-readme-backers][kettle-readme-backers] updates this list every day, automatically.
1673
+ NOTE: [kettle-readme-backers](https://github.com/kettle-rb/tree_haver/blob/main/exe/kettle-readme-backers) updates this list every day, automatically.
1639
1674
 
1640
1675
  <!-- OPENCOLLECTIVE-ORGANIZATIONS:START -->
1641
- No sponsors yet. Be the first!
1676
+ No sponsors yet. Be the first\!
1642
1677
  <!-- OPENCOLLECTIVE-ORGANIZATIONS:END -->
1643
1678
 
1644
1679
  [kettle-readme-backers]: https://github.com/kettle-rb/tree_haver/blob/main/exe/kettle-readme-backers
@@ -1649,50 +1684,50 @@ I’m driven by a passion to foster a thriving open-source community – a space
1649
1684
 
1650
1685
  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`.
1651
1686
 
1652
- 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.
1687
+ I’m developing a new library, [floss\_funding](https://github.com/galtzo-floss/floss_funding), designed to empower open-source developers like myself to get paid for the work we do, in a sustainable way. Please give it a look.
1653
1688
 
1654
- **[Floss-Funding.dev][🖇floss-funding.dev]: 👉️ No network calls. 👉️ No tracking. 👉️ No oversight. 👉️ Minimal crypto hashing. 💡 Easily disabled nags**
1689
+ **[Floss-Funding.dev](https://floss-funding.dev): 👉️ No network calls. 👉️ No tracking. 👉️ No oversight. 👉️ Minimal crypto hashing. 💡 Easily disabled nags**
1655
1690
 
1656
- [![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]
1691
+ [![OpenCollective Backers](https://opencollective.com/kettle-rb/backers/badge.svg?style=flat)](https://opencollective.com/kettle-rb#backer) [![OpenCollective Sponsors](https://opencollective.com/kettle-rb/sponsors/badge.svg?style=flat)](https://opencollective.com/kettle-rb#sponsor) [![Sponsor Me on Github](https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github)](https://github.com/sponsors/pboling) [![Liberapay Goal Progress](https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat)](https://liberapay.com/pboling/donate) [![Donate on PayPal](https://img.shields.io/badge/donate-paypal-a51611.svg?style=flat&logo=paypal)](https://www.paypal.com/paypalme/peterboling) [![Buy me a coffee](https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat)](https://www.buymeacoffee.com/pboling) [![Donate on Polar](https://img.shields.io/badge/polar-donate-a51611.svg?style=flat)](https://polar.sh/pboling) [![Donate to my FLOSS efforts at ko-fi.com](https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat)](https://ko-fi.com/O5O86SNP4) [![Donate to my FLOSS efforts using Patreon](https://img.shields.io/badge/patreon-donate-a51611.svg?style=flat)](https://patreon.com/galtzo)
1657
1692
 
1658
1693
  ## 🔐 Security
1659
1694
 
1660
- See [SECURITY.md][🔐security].
1695
+ See [SECURITY.md](SECURITY.md).
1661
1696
 
1662
1697
  ## 🤝 Contributing
1663
1698
 
1664
1699
  If you need some ideas of where to help, you could work on adding more code coverage,
1665
- or if it is already 💯 (see [below](#code-coverage)) check [reek](REEK), [issues][🤝gh-issues], or [PRs][🤝gh-pulls],
1700
+ or if it is already 💯 (see [below](#code-coverage)) check [reek](REEK), [issues](https://github.com/kettle-rb/tree_haver/issues), or [PRs](https://github.com/kettle-rb/tree_haver/pulls),
1666
1701
  or use the gem and think about how it could be better.
1667
1702
 
1668
- We [![Keep A Changelog][📗keep-changelog-img]][📗keep-changelog] so if you make changes, remember to update it.
1703
+ We [![Keep A Changelog](https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat)](https://keepachangelog.com/en/1.0.0/) so if you make changes, remember to update it.
1669
1704
 
1670
- See [CONTRIBUTING.md][🤝contributing] for more detailed instructions.
1705
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for more detailed instructions.
1671
1706
 
1672
1707
  ### 🚀 Release Instructions
1673
1708
 
1674
- See [CONTRIBUTING.md][🤝contributing].
1709
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
1675
1710
 
1676
1711
  ### Code Coverage
1677
1712
 
1678
- [![Coverage Graph][🏀codecov-g]][🏀codecov]
1713
+ [![Coverage Graph](https://codecov.io/gh/kettle-rb/tree_haver/graphs/tree.svg)](https://codecov.io/gh/kettle-rb/tree_haver)
1679
1714
 
1680
- [![Coveralls Test Coverage][🏀coveralls-img]][🏀coveralls]
1715
+ [![Coveralls Test Coverage](https://coveralls.io/repos/github/kettle-rb/tree_haver/badge.svg?branch=main)](https://coveralls.io/github/kettle-rb/tree_haver?branch=main)
1681
1716
 
1682
- [![QLTY Test Coverage][🏀qlty-covi]][🏀qlty-cov]
1717
+ [![QLTY Test Coverage](https://qlty.sh/gh/kettle-rb/projects/tree_haver/coverage.svg)](https://qlty.sh/gh/kettle-rb/projects/tree_haver/metrics/code?sort=coverageRating)
1683
1718
 
1684
1719
  ### 🪇 Code of Conduct
1685
1720
 
1686
1721
  Everyone interacting with this project's codebases, issue trackers,
1687
- chat rooms and mailing lists agrees to follow the [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct].
1722
+ chat rooms and mailing lists agrees to follow the [![Contributor Covenant 2.1](https://img.shields.io/badge/Contributor_Covenant-2.1-259D6C.svg)](CODE_OF_CONDUCT.md).
1688
1723
 
1689
1724
  ## 🌈 Contributors
1690
1725
 
1691
- [![Contributors][🖐contributors-img]][🖐contributors]
1726
+ [![Contributors](https://contrib.rocks/image?repo=kettle-rb/tree_haver)](https://github.com/kettle-rb/tree_haver/graphs/contributors)
1692
1727
 
1693
- Made with [contributors-img][🖐contrib-rocks].
1728
+ Made with [contributors-img](https://contrib.rocks).
1694
1729
 
1695
- Also see GitLab Contributors: [https://gitlab.com/kettle-rb/tree_haver/-/graphs/main][🚎contributors-gl]
1730
+ Also see GitLab Contributors: <https://gitlab.com/kettle-rb/tree_haver/-/graphs/main>
1696
1731
 
1697
1732
  <details>
1698
1733
  <summary>⭐️ Star History</summary>
@@ -1709,23 +1744,23 @@ Also see GitLab Contributors: [https://gitlab.com/kettle-rb/tree_haver/-/graphs/
1709
1744
 
1710
1745
  ## 📌 Versioning
1711
1746
 
1712
- This Library adheres to [![Semantic Versioning 2.0.0][📌semver-img]][📌semver].
1747
+ This Library adheres to [![Semantic Versioning 2.0.0](https://img.shields.io/badge/semver-2.0.0-259D6C.svg?style=flat)](https://semver.org/spec/v2.0.0.html).
1713
1748
  Violations of this scheme should be reported as bugs.
1714
1749
  Specifically, if a minor or patch version is released that breaks backward compatibility,
1715
1750
  a new version should be immediately released that restores compatibility.
1716
1751
  Breaking changes to the public API will only be introduced with new major versions.
1717
1752
 
1718
1753
  > dropping support for a platform is both obviously and objectively a breaking change <br/>
1719
- > —Jordan Harband ([@ljharb](https://github.com/ljharb), maintainer of SemVer) [in SemVer issue 716][📌semver-breaking]
1754
+ > —Jordan Harband ([@ljharb](https://github.com/ljharb), maintainer of SemVer) [in SemVer issue 716](https://github.com/semver/semver/issues/716#issuecomment-869336139)
1720
1755
 
1721
- I understand that policy doesn't work universally ("exceptions to every rule!"),
1756
+ I understand that policy doesn't work universally ("exceptions to every rule\!"),
1722
1757
  but it is the policy here.
1723
1758
  As such, in many cases it is good to specify a dependency on this library using
1724
- the [Pessimistic Version Constraint][📌pvc] with two digits of precision.
1759
+ the [Pessimistic Version Constraint](http://guides.rubygems.org/patterns/#pessimistic-version-constraint) with two digits of precision.
1725
1760
 
1726
1761
  For example:
1727
1762
 
1728
- ```ruby
1763
+ ``` ruby
1729
1764
  spec.add_dependency("tree_haver", "~> 1.0")
1730
1765
  ```
1731
1766
 
@@ -1733,22 +1768,21 @@ spec.add_dependency("tree_haver", "~> 1.0")
1733
1768
  <summary>📌 Is "Platform Support" part of the public API? More details inside.</summary>
1734
1769
 
1735
1770
  SemVer should, IMO, but doesn't explicitly, say that dropping support for specific Platforms
1736
- is a _breaking change_ to an API, and for that reason the bike shedding is endless.
1771
+ is a *breaking change* to an API, and for that reason the bike shedding is endless.
1737
1772
 
1738
1773
  To get a better understanding of how SemVer is intended to work over a project's lifetime,
1739
1774
  read this article from the creator of SemVer:
1740
1775
 
1741
- - ["Major Version Numbers are Not Sacred"][📌major-versions-not-sacred]
1742
-
1776
+ - ["Major Version Numbers are Not Sacred"](https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html)
1743
1777
  </details>
1744
1778
 
1745
- See [CHANGELOG.md][📌changelog] for a list of releases.
1779
+ See [CHANGELOG.md](CHANGELOG.md) for a list of releases.
1746
1780
 
1747
1781
  ## 📄 License
1748
1782
 
1749
1783
  The gem is available as open source under the terms of
1750
- the [MIT License][📄license] [![License: MIT][📄license-img]][📄license-ref].
1751
- See [LICENSE.txt][📄license] for the official [Copyright Notice][📄copyright-notice-explainer].
1784
+ the [MIT License](LICENSE.txt) [![License: MIT](https://img.shields.io/badge/License-MIT-259D6C.svg)](https://opensource.org/licenses/MIT).
1785
+ See [LICENSE.txt](LICENSE.txt) for the official [Copyright Notice](https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year).
1752
1786
 
1753
1787
  ### © Copyright
1754
1788
 
@@ -1775,11 +1809,11 @@ Please consider sponsoring me or the project.
1775
1809
 
1776
1810
  To join the community or get help 👇️ Join the Discord.
1777
1811
 
1778
- [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite]
1812
+ [![Live Chat on Discord](https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord)](https://discord.gg/3qme4XHNKN)
1779
1813
 
1780
- To say "thanks!" ☝️ Join the Discord or 👇️ send money.
1814
+ To say "thanks\!" ☝️ Join the Discord or 👇️ send money.
1781
1815
 
1782
- [![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]
1816
+ [![Sponsor kettle-rb/tree\_haver on Open Source Collective](https://img.shields.io/opencollective/all/kettle-rb?style=for-the-badge)](https://opencollective.com/kettle-rb) 💌 [![Sponsor me on GitHub Sponsors](https://img.shields.io/badge/Sponsor_Me!-pboling-blue?style=for-the-badge&logo=github)](https://github.com/sponsors/pboling) 💌 [![Sponsor me on Liberapay](https://img.shields.io/liberapay/goal/pboling.svg?style=for-the-badge&logo=liberapay&color=a51611)](https://liberapay.com/pboling/donate) 💌 [![Donate on PayPal](https://img.shields.io/badge/donate-paypal-a51611.svg?style=for-the-badge&logo=paypal&color=0A0A0A)](https://www.paypal.com/paypalme/peterboling)
1783
1817
 
1784
1818
  ### Please give the project a star ⭐ ♥.
1785
1819
 
@@ -1943,7 +1977,7 @@ Thanks for RTFM. ☺️
1943
1977
  [📌gitmoji]: https://gitmoji.dev
1944
1978
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
1945
1979
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
1946
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-2.484-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1980
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-2.190-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1947
1981
  [🔐security]: SECURITY.md
1948
1982
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
1949
1983
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year