tree_haver 4.0.5 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -1,16 +1,16 @@
1
- | 📍 NOTE |
2
- | --- |
3
- | RubyGems (the [GitHub org](https://github.com/rubygems/), not the website) [suffered](https://joel.drapper.me/p/ruby-central-security-measures/) a [hostile takeover](https://pup-e.com/blog/goodbye-rubygems/) in September 2025. |
4
- | Ultimately [4 maintainers](https://www.reddit.com/r/ruby/s/gOk42POCaV) were [hard removed](https://bsky.app/profile/martinemde.com/post/3m3occezxxs2q) and a reason has been given for only 1 of those, while 2 others resigned in protest. |
5
- | It is a [complicated story](https://joel.drapper.me/p/ruby-central-takeover/) which is difficult to [parse quickly](https://joel.drapper.me/p/ruby-central-fact-check/). |
6
- | Simply put - there was active policy for adding or removing maintainers/owners of [rubygems](https://github.com/ruby/rubygems/blob/b1ab33a3d52310a84d16b193991af07f5a6a07c0/doc/rubygems/POLICIES.md?plain=1#L187-L196) and [bundler](https://github.com/ruby/rubygems/blob/b1ab33a3d52310a84d16b193991af07f5a6a07c0/doc/bundler/playbooks/TEAM_CHANGES.md), and those [policies were not followed](https://www.reddit.com/r/ruby/comments/1ove9vp/rubycentral_hates_this_one_fact/). |
7
- | I'm adding notes like this to gems because I [don't condone theft](https://joel.drapper.me/p/ruby-central/) of repositories or gems from their rightful owners. |
8
- | If a similar theft happened with my repos/gems, I'd hope some would stand up for me. |
9
- | Disenfranchised former-maintainers have started [gem.coop](https://gem.coop). |
10
- | Once available I will publish there exclusively; unless RubyCentral makes amends with the community. |
11
- | The ["Technology for Humans: Joel Draper"](https://youtu.be/_H4qbtC5qzU?si=BvuBU90R2wAqD2E6) podcast episode by [reinteractive](https://reinteractive.com/ruby-on-rails) is the most cogent summary I'm aware of. |
12
- | See [here](https://github.com/gem-coop/gem.coop/issues/12), [here](https://gem.coop) and [here](https://martinemde.com/2025/10/05/announcing-gem-coop.html) for more info on what comes next. |
13
- | What I'm doing: A (WIP) proposal for [bundler/gem scopes](https://github.com/galtzo-floss/bundle-namespace), and a (WIP) proposal for a federated [gem server](https://github.com/galtzo-floss/gem-server). |
1
+ | 📍 NOTE |
2
+ |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
3
+ | RubyGems (the [GitHub org][rubygems-org], not the website) [suffered][draper-security] a [hostile takeover][ellen-takeover] in September 2025. |
4
+ | Ultimately [4 maintainers][simi-removed] were [hard removed][martin-removed] and a reason has been given for only 1 of those, while 2 others resigned in protest. |
5
+ | It is a [complicated story][draper-takeover] which is difficult to [parse quickly][draper-lies]. |
6
+ | Simply put - there was active policy for adding or removing maintainers/owners of [rubygems][rubygems-maint-policy] and [bundler][bundler-maint-policy], and those [policies were not followed][policy-fail]. |
7
+ | I'm adding notes like this to gems because I [don't condone theft][draper-theft] of repositories or gems from their rightful owners. |
8
+ | If a similar theft happened with my repos/gems, I'd hope some would stand up for me. |
9
+ | Disenfranchised former-maintainers have started [gem.coop][gem-coop]. |
10
+ | Once available I will publish there exclusively; unless RubyCentral makes amends with the community. |
11
+ | The ["Technology for Humans: Joel Draper"][reinteractive-podcast] podcast episode by [reinteractive][reinteractive] is the most cogent summary I'm aware of. |
12
+ | See [here][gem-naming], [here][gem-coop] and [here][martin-ann] for more info on what comes next. |
13
+ | What I'm doing: A (WIP) proposal for [bundler/gem scopes][gem-scopes], and a (WIP) proposal for a federated [gem server][gem-server]. |
14
14
 
15
15
  [rubygems-org]: https://github.com/rubygems/
16
16
  [draper-security]: https://joel.drapper.me/p/ruby-central-security-measures/
@@ -31,7 +31,7 @@
31
31
  [rubygems-maint-policy]: https://github.com/ruby/rubygems/blob/b1ab33a3d52310a84d16b193991af07f5a6a07c0/doc/rubygems/POLICIES.md?plain=1#L187-L196
32
32
  [policy-fail]: https://www.reddit.com/r/ruby/comments/1ove9vp/rubycentral_hates_this_one_fact/
33
33
 
34
- [![Galtzo FLOSS Logo by Aboling0, CC BY-SA 4.0](https://logos.galtzo.com/assets/images/galtzo-floss/avatar-192px.svg)](https://discord.gg/3qme4XHNKN) [![ruby-lang Logo, Yukihiro Matsumoto, Ruby Visual Identity Team, CC BY-SA 2.5](https://logos.galtzo.com/assets/images/ruby-lang/avatar-192px.svg)](https://www.ruby-lang.org/) [![kettle-rb Logo by Aboling0, CC BY-SA 4.0](https://logos.galtzo.com/assets/images/kettle-rb/avatar-192px.svg)](https://github.com/kettle-rb)
34
+ [![Galtzo FLOSS Logo by Aboling0, CC BY-SA 4.0][🖼️galtzo-i]](https://discord.gg/3qme4XHNKN) [![ruby-lang Logo, Yukihiro Matsumoto, Ruby Visual Identity Team, CC BY-SA 2.5][🖼️ruby-lang-i]](https://www.ruby-lang.org/) [![kettle-rb Logo by Aboling0, CC BY-SA 4.0][🖼️kettle-rb-i]](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,46 +42,47 @@
42
42
 
43
43
  # 🌴 TreeHaver
44
44
 
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)
45
+ [![Version][👽versioni]](https://bestgems.org/gems/tree_haver) [![GitHub tag (latest SemVer)][⛳️tag-img]](http://github.com/kettle-rb/tree_haver/releases) [![License: MIT][📄license-img]](https://opensource.org/licenses/MIT) [![Downloads Rank][👽dl-ranki]](https://bestgems.org/gems/tree_haver) [![Open Source Helpers][👽oss-helpi]](https://www.codetriage.com/kettle-rb/tree_haver) [![CodeCov Test Coverage][🏀codecovi]](https://codecov.io/gh/kettle-rb/tree_haver) [![Coveralls Test Coverage][🏀coveralls-img]](https://coveralls.io/github/kettle-rb/tree_haver?branch=main) [![QLTY Test Coverage][🏀qlty-covi]](https://qlty.sh/gh/kettle-rb/projects/tree_haver/metrics/code?sort=coverageRating) [![QLTY Maintainability][🏀qlty-mnti]](https://qlty.sh/gh/kettle-rb/projects/tree_haver) [![CI Heads][🚎3-hd-wfi]](https://github.com/kettle-rb/tree_haver/actions/workflows/heads.yml) [![CI Runtime Dependencies @ HEAD][🚎12-crh-wfi]](https://github.com/kettle-rb/tree_haver/actions/workflows/dep-heads.yml) [![CI Current][🚎11-c-wfi]](https://github.com/kettle-rb/tree_haver/actions/workflows/current.yml) [![CI Truffle Ruby][🚎9-t-wfi]](https://github.com/kettle-rb/tree_haver/actions/workflows/truffle.yml) [![Deps Locked][🚎13-🔒️-wfi]](https://github.com/kettle-rb/tree_haver/actions/workflows/locked_deps.yml) [![Deps Unlocked][🚎14-🔓️-wfi]](https://github.com/kettle-rb/tree_haver/actions/workflows/unlocked_deps.yml) [![CI Supported][🚎6-s-wfi]](https://github.com/kettle-rb/tree_haver/actions/workflows/supported.yml) [![CI Test Coverage][🚎2-cov-wfi]](https://github.com/kettle-rb/tree_haver/actions/workflows/coverage.yml) [![CI Style][🚎5-st-wfi]](https://github.com/kettle-rb/tree_haver/actions/workflows/style.yml) [![CodeQL][🖐codeQL-img]](https://github.com/kettle-rb/tree_haver/security/code-scanning) [![Apache SkyWalking Eyes License Compatibility Check][🚎15-🪪-wfi]](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](https://discord.gg/3qme4XHNKN), as I may have missed the [discord notification](https://discord.gg/3qme4XHNKN).
47
+ `if ci_badges.map(&:color).detect { it != "green"}` ☝️ [let me know][🖼️galtzo-discord], as I may have missed the [discord notification][🖼️galtzo-discord].
48
48
 
49
49
  -----
50
+
50
51
  `if ci_badges.map(&:color).all? { it == "green"}` 👇️ send money so I can do more of this. FLOSS maintenance is now my full-time job.
51
52
 
52
- [![OpenCollective Backers](https://opencollective.com/kettle-rb/backers/badge.svg?style=flat)](https://opencollective.com/kettle-rb#backer) [![OpenCollective Sponsors](https://opencollective.com/kettle-rb/sponsors/badge.svg?style=flat)](https://opencollective.com/kettle-rb#sponsor) [![Sponsor Me on Github](https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github)](https://github.com/sponsors/pboling) [![Liberapay Goal Progress](https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat)](https://liberapay.com/pboling/donate) [![Donate on PayPal](https://img.shields.io/badge/donate-paypal-a51611.svg?style=flat&logo=paypal)](https://www.paypal.com/paypalme/peterboling) [![Buy me a coffee](https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat)](https://www.buymeacoffee.com/pboling) [![Donate on Polar](https://img.shields.io/badge/polar-donate-a51611.svg?style=flat)](https://polar.sh/pboling) [![Donate at ko-fi.com](https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat)](https://ko-fi.com/O5O86SNP4)
53
+ [![OpenCollective Backers][🖇osc-backers-i]](https://opencollective.com/kettle-rb#backer) [![OpenCollective Sponsors][🖇osc-sponsors-i]](https://opencollective.com/kettle-rb#sponsor) [![Sponsor Me on Github][🖇sponsor-img]](https://github.com/sponsors/pboling) [![Liberapay Goal Progress][⛳liberapay-img]](https://liberapay.com/pboling/donate) [![Donate on PayPal][🖇paypal-img]](https://www.paypal.com/paypalme/peterboling) [![Buy me a coffee][🖇buyme-small-img]](https://www.buymeacoffee.com/pboling) [![Donate on Polar][🖇polar-img]](https://polar.sh/pboling) [![Donate at ko-fi.com][🖇kofi-img]](https://ko-fi.com/O5O86SNP4)
53
54
 
54
55
  ## 🌻 Synopsis
55
56
 
56
- TreeHaver is a cross-Ruby adapter for the [tree-sitter](https://tree-sitter.github.io/tree-sitter/) and [Citrus](https://github.com/mjackson/citrus) parsing libraries and other dedicated parsing tools that works seamlessly across MRI Ruby, JRuby, and TruffleRuby. It provides a unified API for parsing source code using grammars, regardless of your Ruby implementation.
57
+ TreeHaver is a cross-Ruby adapter for the [tree-sitter](https://tree-sitter.github.io/tree-sitter/), [Citrus][citrus], and [Parslet][parslet] parsing libraries and other dedicated parsing tools that works seamlessly across MRI Ruby, JRuby, and TruffleRuby. It provides a unified API for parsing source code using grammars, regardless of your Ruby implementation.
57
58
 
58
59
  ### The Adapter Pattern: Like Faraday, but for Parsing
59
60
 
60
61
  If you've used [Faraday](https://github.com/lostisland/faraday), [multi\_json](https://github.com/intridea/multi_json), or [multi\_xml](https://github.com/sferik/multi_xml), you'll feel right at home with TreeHaver. These gems share a common philosophy:
61
62
 
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.) |
68
-
69
- **Write once, run anywhere.**
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, Parslet |
70
69
 
71
70
  **Learn once, write anywhere.**
72
71
 
72
+ **Write once, run anywhere.**
73
+
73
74
  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.
74
75
 
75
- ``` ruby
76
+ ```ruby
76
77
  # Your code stays the same regardless of backend
77
78
  parser = TreeHaver::Parser.new
78
79
  parser.language = TreeHaver::Language.from_library("/path/to/grammar.so")
79
80
  tree = parser.parse(source_code)
80
81
 
81
82
  # 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
83
+ # - MRI: ruby_tree_sitter, tree_stump, ffi, prism, psych, commonmarker, markly, citrus, parslet
84
+ # - JRuby: ffi, java-tree-sitter (not a gem, but the jtreesitter maven package), prism, psych, commonmarker, markly, citrus, parslet
85
+ # - TruffleRuby: prism, psych, commonmarker, markly, citrus, parslet
85
86
  # (tree-sitter backends don't work on Truffleruby with ffi gem due to FFI STRUCT_BY_VALUE limitation)
86
87
  ```
87
88
 
@@ -90,24 +91,26 @@ tree = parser.parse(source_code)
90
91
  - **Universal Ruby Support**: Works on MRI Ruby, JRuby, and TruffleRuby
91
92
  - **10 Parsing Backends** - Choose the right backend for your needs:
92
93
  - **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)
94
+ - **MRI Backend**: Leverages [`ruby_tree_sitter`][ruby_tree_sitter] gem (C extension, fastest on MRI)
95
+ - **Rust Backend**: Uses [`tree_stump`][tree_stump] gem (Rust with precompiled binaries)
95
96
  - **Note**: `tree_stump` currently requires unreleased fixes in the `main` branch.
96
97
  - **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
+ - **Java Backend**: Native Java integration for JRuby with [`java-tree-sitter`](https://github.com/tree-sitter/java-tree-sitter) / [`jtreesitter`][jtreesitter] grammar JARs
98
99
  - **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)
100
+ - **Prism Backend**: Ruby's official parser ([Prism][prism], stdlib in Ruby 3.4+)
101
+ - **Psych Backend**: Ruby's YAML parser ([Psych][psych], stdlib)
102
+ - **Commonmarker Backend**: Fast Markdown parser ([Commonmarker][commonmarker], comrak Rust)
103
+ - **Markly Backend**: GitHub Flavored Markdown ([Markly][markly], cmark-gfm C)
103
104
  - **Pure Ruby Fallback**:
104
- - **Citrus Backend**: Pure Ruby parsing via [`citrus`](https://github.com/mjackson/citrus) (no native dependencies)
105
+ - **Citrus Backend**: Pure Ruby PEG parsing via [`citrus`][citrus] (no native dependencies)
106
+ - **Parslet Backend**: Pure Ruby PEG parsing via [`parslet`][parslet] (no native dependencies)
105
107
  - **Automatic Backend Selection**: Intelligently selects the best backend for your Ruby implementation
106
108
  - **Language Agnostic**: Parse any language - Ruby, Markdown, YAML, JSON, Bash, TOML, JavaScript, etc.
107
109
  - **Grammar Discovery**: Built-in `GrammarFinder` utility for platform-aware grammar library discovery
108
110
  - **Unified Position API**: Consistent `start_line`, `end_line`, `source_position` across all backends
109
111
  - **Thread-Safe**: Built-in language registry with thread-safe caching
110
112
  - **Minimal API Surface**: Simple, focused API that covers the most common use cases
113
+
111
114
  ### Backend Requirements
112
115
 
113
116
  TreeHaver has minimal dependencies and automatically selects the best backend for your Ruby implementation. Each backend has specific version requirements:
@@ -120,15 +123,15 @@ In ruby\_tree\_sitter v2.0, all TreeSitter exceptions were changed to inherit fr
120
123
 
121
124
  **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:
122
125
 
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 |
126
+ | ruby\_tree\_sitter Exception | TreeHaver Exception | When It Occurs |
127
+ |-----------------------------------|---------------------------|----------------------------------------------|
128
+ | `TreeSitter::ParserNotFoundError` | `TreeHaver::NotAvailable` | Parser library file cannot be loaded |
129
+ | `TreeSitter::LanguageLoadError` | `TreeHaver::NotAvailable` | Language symbol loads but returns nothing |
130
+ | `TreeSitter::SymbolNotFoundError` | `TreeHaver::NotAvailable` | Symbol not found in library |
131
+ | `TreeSitter::ParserVersionError` | `TreeHaver::NotAvailable` | Parser version incompatible with tree-sitter |
132
+ | `TreeSitter::QueryCreationError` | `TreeHaver::NotAvailable` | Query creation fails |
130
133
 
131
- ``` ruby
134
+ ```ruby
132
135
  # Add to your Gemfile for MRI backend
133
136
  gem "ruby_tree_sitter", "~> 2.0"
134
137
  ```
@@ -137,13 +140,13 @@ gem "ruby_tree_sitter", "~> 2.0"
137
140
 
138
141
  **MRI Ruby only** - Does not work on JRuby or TruffleRuby.
139
142
 
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.
143
+ The Rust backend uses [tree\_stump][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.
141
144
 
142
145
  - **JRuby**: Cannot load native `.so` extensions (runs on JVM)
143
146
  - **TruffleRuby**: magnus/rb-sys are incompatible with TruffleRuby's C API emulation
144
147
  NOTE: `tree_stump` currently requires unreleased fixes in the `main` branch.
145
- <!-- end list -->
146
- ``` ruby
148
+
149
+ ```ruby
147
150
  # Add to your Gemfile for Rust backend (MRI only)
148
151
  gem "tree_stump", github: "joker1007/tree_stump", branch: "main"
149
152
  ```
@@ -155,13 +158,13 @@ gem "tree_stump", github: "joker1007/tree_stump", branch: "main"
155
158
  Requires the `ffi` gem and a system installation of `libtree-sitter`.
156
159
 
157
160
  - **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
- <!-- end list -->
159
- ``` ruby
161
+
162
+ ```ruby
160
163
  # Add to your Gemfile for FFI backend (MRI and JRuby)
161
164
  gem "ffi", ">= 1.15", "< 2.0"
162
165
  ```
163
166
 
164
- ``` bash
167
+ ```bash
165
168
  # Install libtree-sitter on your system:
166
169
  # macOS
167
170
  brew install tree-sitter
@@ -175,18 +178,27 @@ dnf install tree-sitter tree-sitter-devel
175
178
 
176
179
  #### Citrus Backend
177
180
 
178
- Pure Ruby parser with no native dependencies:
181
+ Pure Ruby PEG parser with no native dependencies:
179
182
 
180
- ``` ruby
183
+ ```ruby
181
184
  # Add to your Gemfile for Citrus backend
182
185
  gem "citrus", "~> 3.0"
183
186
  ```
184
187
 
188
+ #### Parslet Backend
189
+
190
+ Pure Ruby PEG parser with no native dependencies:
191
+
192
+ ```ruby
193
+ # Add to your Gemfile for Parslet backend
194
+ gem "parslet", "~> 2.0"
195
+ ```
196
+
185
197
  #### Java Backend (JRuby only)
186
198
 
187
199
  **Requires jtreesitter \>= 0.26.0** from Maven Central. Older versions are not supported due to breaking API changes.
188
200
 
189
- ``` ruby
201
+ ```ruby
190
202
  # No gem dependency - uses JRuby's built-in Java integration
191
203
  # Download the JAR:
192
204
  # curl -L -o jtreesitter-0.26.0.jar \
@@ -197,27 +209,9 @@ gem "citrus", "~> 3.0"
197
209
  ```
198
210
 
199
211
  **Also requires**:
212
+
200
213
  - Tree-sitter runtime library (`libtree-sitter.so`) version 0.26+ (must match jtreesitter version)
201
214
  - Grammar `.so` files built against tree-sitter 0.26+ (or rebuilt with `tree-sitter generate`)
202
- ### Backend Platform Compatibility
203
-
204
- Not all backends work on all Ruby platforms. Here's a complete compatibility matrix:
205
-
206
- | Backend | MRI | JRuby | TruffleRuby | API Complete | Notes |
207
- | --- | :-: | :-: | :-: | :-: | --- |
208
- | **MRI** ([ruby\_tree\_sitter](https://github.com/Faveod/ruby-tree-sitter)) | ✅ | ❌ | ❌ | ✅ | C extension, MRI only |
209
- | **Rust** ([tree\_stump](https://github.com/joker1007/tree_stump)) | ✅ | ❌ | ❌ | ✅ | magnus/rb-sys incompatible with non-MRI |
210
- | **FFI** | ✅ | ✅ | ❌ | ⚠️ | TruffleRuby FFI doesn't support `STRUCT_BY_VALUE` |
211
- | **Java** ([jtreesitter](https://central.sonatype.com/artifact/io.github.tree-sitter/jtreesitter)) | ❌ | ✅ | ❌ | ✅ | JRuby only, requires jtreesitter \>= 0.26.0 |
212
- | **Prism** | ✅ | ✅ | ✅ | ✅ | Ruby parsing, stdlib in Ruby 3.4+ |
213
- | **Psych** | ✅ | ✅ | ✅ | ✅ | YAML parsing, stdlib |
214
- | **Citrus** | ✅ | ✅ | ✅ | ⚠️ | Pure Ruby, no native dependencies |
215
- | **Commonmarker** | ✅ | ❌ | ❓ | ✅ | Rust extension for Markdown |
216
- | **Markly** | ✅ | ❌ | ❓ | ✅ | C extension for Markdown |
217
-
218
- **Legend**: ✅ = Works / Complete, ❌ = Does not work, ❓ = Untested, ⚠️ = Partial (some optional methods missing)
219
-
220
- **API Complete** indicates whether the backend implements all optional Node methods (`parent`, `next_sibling`, `prev_sibling`, `named?`, `missing?`, `text`, `child_by_field_name`). Backends marked ⚠️ work but may be missing some advanced traversal methods. Use `TreeHaver::BackendAPI.validate(backend_module)` to check specific backends.
221
215
 
222
216
  ### Version Requirements for Tree-Sitter Backends
223
217
 
@@ -225,7 +219,7 @@ Not all backends work on all Ruby platforms. Here's a complete compatibility mat
225
219
 
226
220
  All tree-sitter backends (MRI, Rust, FFI, Java) require the tree-sitter runtime library. **Version 0.26+ is required** for the Java backend (to match jtreesitter 0.26.0). Other backends may work with 0.24+, but 0.26+ is recommended for consistency.
227
221
 
228
- ``` bash
222
+ ```bash
229
223
  # Check your tree-sitter version
230
224
  tree-sitter --version # Should be 0.26.0 or newer for Java backend
231
225
 
@@ -248,8 +242,8 @@ dnf install tree-sitter tree-sitter-devel
248
242
  - `Node.getChild()`, `getParent()`, `getNextSibling()`, `getPrevSibling()` return `Optional<Node>`
249
243
  - `Language.load(name)` was removed; use `SymbolLookup` API instead
250
244
  Older versions of jtreesitter are **NOT supported**.
251
- <!-- end list -->
252
- ``` bash
245
+
246
+ ```bash
253
247
  # Download jtreesitter 0.26.0 from Maven Central
254
248
  curl -L -o jtreesitter-0.26.0.jar \
255
249
  "https://repo1.maven.org/maven2/io/github/tree-sitter/jtreesitter/0.26.0/jtreesitter-0.26.0.jar"
@@ -260,7 +254,7 @@ bin/setup-jtreesitter
260
254
 
261
255
  Set the environment variable to point to your JAR directory:
262
256
 
263
- ``` bash
257
+ ```bash
264
258
  export TREE_SITTER_JAVA_JARS_DIR=/path/to/jars
265
259
  ```
266
260
 
@@ -272,9 +266,10 @@ Tree-sitter 0.24+ changed how language ABI versions are reported (from `ts_langu
272
266
 
273
267
  Failed to load tree_sitter_toml
274
268
  Version mismatch detected: The grammar was built against tree-sitter < 0.26
269
+
275
270
  You need to rebuild the grammar from source:
276
271
 
277
- ``` bash
272
+ ```bash
278
273
  # Use the provided build script
279
274
  bin/build-grammar toml
280
275
 
@@ -287,12 +282,12 @@ cc -shared -fPIC -o libtree-sitter-toml.so src/parser.c src/scanner.c -I src
287
282
 
288
283
  **Grammar sources for common languages:**
289
284
 
290
- | Language | Repository |
291
- | --- | --- |
292
- | TOML | [tree-sitter-grammars/tree-sitter-toml](https://github.com/tree-sitter-grammars/tree-sitter-toml) |
293
- | JSON | [tree-sitter/tree-sitter-json](https://github.com/tree-sitter/tree-sitter-json) |
294
- | JSONC | [WhyNotHugo/tree-sitter-jsonc](https://gitlab.com/WhyNotHugo/tree-sitter-jsonc) |
295
- | Bash | [tree-sitter/tree-sitter-bash](https://github.com/tree-sitter/tree-sitter-bash) |
285
+ | Language | Repository |
286
+ |----------|--------------------------------------------------|
287
+ | TOML | [tree-sitter-grammars/tree-sitter-toml][ts-toml] |
288
+ | JSON | [tree-sitter/tree-sitter-json][ts-json] |
289
+ | JSONC | [WhyNotHugo/tree-sitter-jsonc][ts-jsonc] |
290
+ | Bash | [tree-sitter/tree-sitter-bash][ts-bash] |
296
291
 
297
292
  #### TruffleRuby Limitations
298
293
 
@@ -300,7 +295,8 @@ TruffleRuby has **no working tree-sitter backend**:
300
295
 
301
296
  - **FFI**: TruffleRuby's FFI doesn't support `STRUCT_BY_VALUE` return types (used by `ts_tree_root_node`, `ts_node_child`, etc.)
302
297
  - **MRI/Rust**: C and Rust extensions require MRI's C API internals (`RBasic.flags`, `rb_gc_writebarrier`, etc.) that TruffleRuby doesn't expose
303
- TruffleRuby users should use: **Prism** (Ruby), **Psych** (YAML), **Citrus** (TOML via toml-rb), or potentially **Commonmarker/Markly** (Markdown).
298
+ TruffleRuby users should use: **Prism** (Ruby), **Psych** (YAML), **Citrus/Parslet** (e.g., TOML via toml-rb/toml), or potentially **Commonmarker/Markly** (Markdown).
299
+
304
300
  #### JRuby Limitations
305
301
 
306
302
  JRuby runs on the JVM and **cannot load native `.so` extensions via Ruby's C API**:
@@ -308,10 +304,11 @@ JRuby runs on the JVM and **cannot load native `.so` extensions via Ruby's C API
308
304
  - **MRI/Rust**: C and Rust extensions simply cannot be loaded
309
305
  - **FFI**: Works\! JRuby has excellent FFI support
310
306
  - **Java**: Works\! The Java backend uses jtreesitter (requires \>= 0.26.0)
311
- JRuby users should use: **Java backend** (best performance, full API) or **FFI backend** for tree-sitter, plus **Prism**, **Psych**, **Citrus** for other formats.
312
- [ruby\_tree\_sitter](https://github.com/Faveod/ruby-tree-sitter): https://github.com/Faveod/ruby-tree-sitter
313
- [tree\_stump](https://github.com/joker1007/tree_stump): https://github.com/joker1007/tree\_stump
307
+ JRuby users should use: **Java backend** (best performance, full API) or **FFI backend** for tree-sitter, plus **Prism**, **Psych**, **Citrus/Parslet** for other formats.
308
+ [ruby\_tree\_sitter][ruby_tree_sitter]: https://github.com/Faveod/ruby-tree-sitter
309
+ [tree\_stump][tree_stump]: https://github.com/joker1007/tree\_stump
314
310
  \[jtreesitter\]: https://central.sonatype.com/artifact/io.github.tree-sitter/jtreesitter
311
+
315
312
  ### Why TreeHaver?
316
313
 
317
314
  tree-sitter is a powerful parser generator that creates incremental parsers for many programming languages. However, integrating it into Ruby applications can be challenging:
@@ -325,21 +322,21 @@ tree-sitter is a powerful parser generator that creates incremental parsers for
325
322
 
326
323
  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.
327
324
 
328
- | Gem | Language<br>/ Format | Parser Backend(s) | Description |
329
- |------------------------------------------|----------------------|-----------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------|
330
- | [tree_haver][tree_haver] | Multi | MRI C, Rust, FFI, Java, Prism, Psych, Commonmarker, Markly, Citrus | **Foundation**: Cross-Ruby adapter for parsing libraries (like Faraday for HTTP) |
331
- | [ast-merge][ast-merge] | Text | internal | **Infrastructure**: Shared base classes and merge logic for all `*-merge` gems |
332
- | [bash-merge][bash-merge] | Bash | [tree-sitter-bash][ts-bash] (via tree_haver) | Smart merge for Bash scripts |
333
- | [commonmarker-merge][commonmarker-merge] | Markdown | [Commonmarker][commonmarker] (via tree_haver) | Smart merge for Markdown (CommonMark via comrak Rust) |
334
- | [dotenv-merge][dotenv-merge] | Dotenv | internal | Smart merge for `.env` files |
335
- | [json-merge][json-merge] | JSON | [tree-sitter-json][ts-json] (via tree_haver) | Smart merge for JSON files |
336
- | [jsonc-merge][jsonc-merge] | JSONC | [tree-sitter-jsonc][ts-jsonc] (via tree_haver) | ⚠️ Proof of concept; Smart merge for JSON with Comments |
337
- | [markdown-merge][markdown-merge] | Markdown | [Commonmarker][commonmarker] / [Markly][markly] (via tree_haver) | **Foundation**: Shared base for Markdown mergers with inner code block merging |
338
- | [markly-merge][markly-merge] | Markdown | [Markly][markly] (via tree_haver) | Smart merge for Markdown (CommonMark via cmark-gfm C) |
339
- | [prism-merge][prism-merge] | Ruby | [Prism][prism] (`prism` std lib gem) | Smart merge for Ruby source files |
340
- | [psych-merge][psych-merge] | YAML | [Psych][psych] (`psych` std lib gem) | Smart merge for YAML files |
341
- | [rbs-merge][rbs-merge] | RBS | [tree-sitter-bash][ts-rbs] (via tree_haver), [RBS][rbs] (`rbs` std lib gem) | Smart merge for Ruby type signatures |
342
- | [toml-merge][toml-merge] | TOML | [Citrus + toml-rb][toml-rb] (default, via tree_haver), [tree-sitter-toml][ts-toml] (via tree_haver) | Smart merge for TOML files |
325
+ | Gem | Version | CI | | Language<br>/ Format | Parser Backend(s) | Description |
326
+ |------------------------------------------|----------------------------------------------------------------|--------------------------------------------------------------|----------|-------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------|-------------|
327
+ | [tree_haver][tree_haver] | [![Version][tree_haver-gem-i]][tree_haver-gem] | [![Version][tree_haver-ci-i]][tree_haver-ci] | Multi | MRI C, Rust, FFI, Java, Prism, Psych, Commonmarker, Markly, Citrus, Parslet | **Foundation**: Cross-Ruby adapter for parsing libraries (like Faraday for HTTP) |
328
+ | [ast-merge][ast-merge] | [![Version][ast-merge-gem-i]][ast-merge-gem] | [![Version][ast-merge-ci-i]][ast-merge-ci] | Text | internal | **Infrastructure**: Shared base classes and merge logic for all `*-merge` gems |
329
+ | [bash-merge][bash-merge] | [![Version][bash-merge-gem-i]][bash-merge-gem] | [![Version][bash-merge-ci-i]][bash-merge-ci] | Bash | [tree-sitter-bash][ts-bash] (via tree_haver) | Smart merge for Bash scripts |
330
+ | [commonmarker-merge][commonmarker-merge] | [![Version][commonmarker-merge-gem-i]][commonmarker-merge-gem] | [![Version][commonmarker-merge-ci-i]][commonmarker-merge-ci] | Markdown | [Commonmarker][commonmarker] (via tree_haver) | Smart merge for Markdown (CommonMark via comrak Rust) |
331
+ | [dotenv-merge][dotenv-merge] | [![Version][dotenv-merge-gem-i]][dotenv-merge-gem] | [![Version][dotenv-merge-ci-i]][dotenv-merge-ci] | Dotenv | internal | Smart merge for `.env` files |
332
+ | [json-merge][json-merge] | [![Version][json-merge-gem-i]][json-merge-gem] | [![Version][json-merge-ci-i]][json-merge-ci] | JSON | [tree-sitter-json][ts-json] (via tree_haver) | Smart merge for JSON files |
333
+ | [jsonc-merge][jsonc-merge] | [![Version][jsonc-merge-gem-i]][jsonc-merge-gem] | [![Version][jsonc-merge-ci-i]][jsonc-merge-ci] | JSONC | [tree-sitter-jsonc][ts-jsonc] (via tree_haver) | ⚠️ Proof of concept; Smart merge for JSON with Comments |
334
+ | [markdown-merge][markdown-merge] | [![Version][markdown-merge-gem-i]][markdown-merge-gem] | [![Version][markdown-merge-ci-i]][markdown-merge-ci] | Markdown | [Commonmarker][commonmarker] / [Markly][markly] (via tree_haver) | **Foundation**: Shared base for Markdown mergers with inner code block merging |
335
+ | [markly-merge][markly-merge] | [![Version][markly-merge-gem-i]][markly-merge-gem] | [![Version][markly-merge-ci-i]][markly-merge-ci] | Markdown | [Markly][markly] (via tree_haver) | Smart merge for Markdown (CommonMark via cmark-gfm C) |
336
+ | [prism-merge][prism-merge] | [![Version][prism-merge-gem-i]][prism-merge-gem] | [![Version][prism-merge-ci-i]][prism-merge-ci] | Ruby | [Prism][prism] (`prism` std lib gem) | Smart merge for Ruby source files |
337
+ | [psych-merge][psych-merge] | [![Version][psych-merge-gem-i]][psych-merge-gem] | [![Version][psych-merge-ci-i]][psych-merge-ci] | YAML | [Psych][psych] (`psych` std lib gem) | Smart merge for YAML files |
338
+ | [rbs-merge][rbs-merge] | [![Version][rbs-merge-gem-i]][rbs-merge-gem] | [![Version][rbs-merge-ci-i]][rbs-merge-ci] | RBS | [tree-sitter-bash][ts-rbs] (via tree_haver), [RBS][rbs] (`rbs` std lib gem) | Smart merge for Ruby type signatures |
339
+ | [toml-merge][toml-merge] | [![Version][toml-merge-gem-i]][toml-merge-gem] | [![Version][toml-merge-ci-i]][toml-merge-ci] | TOML | [Parslet + toml][toml], [Citrus + toml-rb][toml-rb], [tree-sitter-toml][ts-toml] (all via tree_haver) | Smart merge for TOML files |
343
340
 
344
341
  #### Backend Platform Compatibility
345
342
 
@@ -353,7 +350,8 @@ tree_haver supports multiple parsing backends, but not all backends work on all
353
350
  | **Java** ([jtreesitter][jtreesitter]) | ❌ | ✅ | ❌ | JRuby only, requires grammar JARs |
354
351
  | **Prism** | ✅ | ✅ | ✅ | Ruby parsing, stdlib in Ruby 3.4+ |
355
352
  | **Psych** | ✅ | ✅ | ✅ | YAML parsing, stdlib |
356
- | **Citrus** | ✅ | ✅ | ✅ | Pure Ruby, no native dependencies |
353
+ | **Citrus** | ✅ | ✅ | ✅ | Pure Ruby PEG parser, no native dependencies |
354
+ | **Parslet** | ✅ | ✅ | ✅ | Pure Ruby PEG parser, no native dependencies |
357
355
  | **Commonmarker** | ✅ | ❌ | ❓ | Rust extension for Markdown |
358
356
  | **Markly** | ✅ | ❌ | ❓ | C extension for Markdown |
359
357
 
@@ -387,6 +385,66 @@ tree_haver supports multiple parsing backends, but not all backends work on all
387
385
  [commonmarker-merge]: https://github.com/kettle-rb/commonmarker-merge
388
386
  [kettle-dev]: https://github.com/kettle-rb/kettle-dev
389
387
  [kettle-jem]: https://github.com/kettle-rb/kettle-jem
388
+ [tree_haver-gem]: https://bestgems.org/gems/tree_haver
389
+ [ast-merge-gem]: https://bestgems.org/gems/ast-merge
390
+ [prism-merge-gem]: https://bestgems.org/gems/prism-merge
391
+ [psych-merge-gem]: https://bestgems.org/gems/psych-merge
392
+ [json-merge-gem]: https://bestgems.org/gems/json-merge
393
+ [jsonc-merge-gem]: https://bestgems.org/gems/jsonc-merge
394
+ [bash-merge-gem]: https://bestgems.org/gems/bash-merge
395
+ [rbs-merge-gem]: https://bestgems.org/gems/rbs-merge
396
+ [dotenv-merge-gem]: https://bestgems.org/gems/dotenv-merge
397
+ [toml-merge-gem]: https://bestgems.org/gems/toml-merge
398
+ [markdown-merge-gem]: https://bestgems.org/gems/markdown-merge
399
+ [markly-merge-gem]: https://bestgems.org/gems/markly-merge
400
+ [commonmarker-merge-gem]: https://bestgems.org/gems/commonmarker-merge
401
+ [kettle-dev-gem]: https://bestgems.org/gems/kettle-dev
402
+ [kettle-jem-gem]: https://bestgems.org/gems/kettle-jem
403
+ [tree_haver-gem-i]: https://img.shields.io/gem/v/tree_haver.svg
404
+ [ast-merge-gem-i]: https://img.shields.io/gem/v/ast-merge.svg
405
+ [prism-merge-gem-i]: https://img.shields.io/gem/v/prism-merge.svg
406
+ [psych-merge-gem-i]: https://img.shields.io/gem/v/psych-merge.svg
407
+ [json-merge-gem-i]: https://img.shields.io/gem/v/json-merge.svg
408
+ [jsonc-merge-gem-i]: https://img.shields.io/gem/v/jsonc-merge.svg
409
+ [bash-merge-gem-i]: https://img.shields.io/gem/v/bash-merge.svg
410
+ [rbs-merge-gem-i]: https://img.shields.io/gem/v/rbs-merge.svg
411
+ [dotenv-merge-gem-i]: https://img.shields.io/gem/v/dotenv-merge.svg
412
+ [toml-merge-gem-i]: https://img.shields.io/gem/v/toml-merge.svg
413
+ [markdown-merge-gem-i]: https://img.shields.io/gem/v/markdown-merge.svg
414
+ [markly-merge-gem-i]: https://img.shields.io/gem/v/markly-merge.svg
415
+ [commonmarker-merge-gem-i]: https://img.shields.io/gem/v/commonmarker-merge.svg
416
+ [kettle-dev-gem-i]: https://img.shields.io/gem/v/kettle-dev.svg
417
+ [kettle-jem-gem-i]: https://img.shields.io/gem/v/kettle-jem.svg
418
+ [tree_haver-ci-i]: https://github.com/kettle-rb/tree_haver/actions/workflows/current.yml/badge.svg
419
+ [ast-merge-ci-i]: https://github.com/kettle-rb/ast-merge/actions/workflows/current.yml/badge.svg
420
+ [prism-merge-ci-i]: https://github.com/kettle-rb/prism-merge/actions/workflows/current.yml/badge.svg
421
+ [psych-merge-ci-i]: https://github.com/kettle-rb/psych-merge/actions/workflows/current.yml/badge.svg
422
+ [json-merge-ci-i]: https://github.com/kettle-rb/json-merge/actions/workflows/current.yml/badge.svg
423
+ [jsonc-merge-ci-i]: https://github.com/kettle-rb/jsonc-merge/actions/workflows/current.yml/badge.svg
424
+ [bash-merge-ci-i]: https://github.com/kettle-rb/bash-merge/actions/workflows/current.yml/badge.svg
425
+ [rbs-merge-ci-i]: https://github.com/kettle-rb/rbs-merge/actions/workflows/current.yml/badge.svg
426
+ [dotenv-merge-ci-i]: https://github.com/kettle-rb/dotenv-merge/actions/workflows/current.yml/badge.svg
427
+ [toml-merge-ci-i]: https://github.com/kettle-rb/toml-merge/actions/workflows/current.yml/badge.svg
428
+ [markdown-merge-ci-i]: https://github.com/kettle-rb/markdown-merge/actions/workflows/current.yml/badge.svg
429
+ [markly-merge-ci-i]: https://github.com/kettle-rb/markly-merge/actions/workflows/current.yml/badge.svg
430
+ [commonmarker-merge-ci-i]: https://github.com/kettle-rb/commonmarker-merge/actions/workflows/current.yml/badge.svg
431
+ [kettle-dev-ci-i]: https://github.com/kettle-rb/kettle-dev/actions/workflows/current.yml/badge.svg
432
+ [kettle-jem-ci-i]: https://github.com/kettle-rb/kettle-jem/actions/workflows/current.yml/badge.svg
433
+ [tree_haver-ci]: https://github.com/kettle-rb/tree_haver/actions/workflows/current.yml
434
+ [ast-merge-ci]: https://github.com/kettle-rb/ast-merge/actions/workflows/current.yml
435
+ [prism-merge-ci]: https://github.com/kettle-rb/prism-merge/actions/workflows/current.yml
436
+ [psych-merge-ci]: https://github.com/kettle-rb/psych-merge/actions/workflows/current.yml
437
+ [json-merge-ci]: https://github.com/kettle-rb/json-merge/actions/workflows/current.yml
438
+ [jsonc-merge-ci]: https://github.com/kettle-rb/jsonc-merge/actions/workflows/current.yml
439
+ [bash-merge-ci]: https://github.com/kettle-rb/bash-merge/actions/workflows/current.yml
440
+ [rbs-merge-ci]: https://github.com/kettle-rb/rbs-merge/actions/workflows/current.yml
441
+ [dotenv-merge-ci]: https://github.com/kettle-rb/dotenv-merge/actions/workflows/current.yml
442
+ [toml-merge-ci]: https://github.com/kettle-rb/toml-merge/actions/workflows/current.yml
443
+ [markdown-merge-ci]: https://github.com/kettle-rb/markdown-merge/actions/workflows/current.yml
444
+ [markly-merge-ci]: https://github.com/kettle-rb/markly-merge/actions/workflows/current.yml
445
+ [commonmarker-merge-ci]: https://github.com/kettle-rb/commonmarker-merge/actions/workflows/current.yml
446
+ [kettle-dev-ci]: https://github.com/kettle-rb/kettle-dev/actions/workflows/current.yml
447
+ [kettle-jem-ci]: https://github.com/kettle-rb/kettle-jem/actions/workflows/current.yml
390
448
  [prism]: https://github.com/ruby/prism
391
449
  [psych]: https://github.com/ruby/psych
392
450
  [ts-json]: https://github.com/tree-sitter/tree-sitter-json
@@ -397,6 +455,7 @@ tree_haver supports multiple parsing backends, but not all backends work on all
397
455
  [dotenv]: https://github.com/bkeepers/dotenv
398
456
  [rbs]: https://github.com/ruby/rbs
399
457
  [toml-rb]: https://github.com/emancu/toml-rb
458
+ [toml]: https://github.com/jm/toml
400
459
  [markly]: https://github.com/ioquatix/markly
401
460
  [commonmarker]: https://github.com/gjtorikian/commonmarker
402
461
  [ruby_tree_sitter]: https://github.com/Faveod/ruby-tree-sitter
@@ -405,30 +464,30 @@ tree_haver supports multiple parsing backends, but not all backends work on all
405
464
 
406
465
  ### Comparison with Other Ruby AST / Parser Bindings
407
466
 
408
- | 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) |
409
- | --- | --- | --- | --- | --- |
410
- | **MRI Ruby** | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
411
- | **JRuby** | ✅ Yes (FFI, Java, or Citrus backend) | ❌ No | ❌ No | ✅ Yes |
412
- | **TruffleRuby** | ✅ Yes (FFI or Citrus) | ❌ No | ❓ Unknown | ✅ Yes |
413
- | **Backend** | Multi (MRI C, Rust, FFI, Java, Citrus) | C extension only | Rust extension | Pure Ruby |
414
- | **Incremental Parsing** | ✅ Via MRI C/Rust/Java backend | ✅ Yes | ✅ Yes | ❌ No |
415
- | **Query API** | ⚡ Via MRI/Rust/Java backend | ✅ Yes | ✅ Yes | ❌ No |
416
- | **Grammar Discovery** | ✅ Built-in `GrammarFinder` | ❌ Manual | ❌ Manual | ❌ Manual |
417
- | **Security Validations** | ✅ `PathValidator` | ❌ No | ❌ No | ❌ No |
418
- | **Language Registration** | ✅ Thread-safe registry | ❌ No | ❌ No | ❌ No |
419
- | **Native Performance** | ⚡ Backend-dependent | ✅ Native C | ✅ Native Rust | ❌ Pure Ruby |
420
- | **Precompiled Binaries** | ⚡ Via Rust backend | ✅ Yes | ✅ Yes | ✅ Pure Ruby |
421
- | **Zero Native Deps** | ⚡ Via Citrus backend | ❌ No | ❌ No | ✅ Yes |
422
- | **Minimum Ruby** | 3.2+ | 3.0+ | 3.1+ | 0+ |
467
+ | Feature | [tree\_haver][📜src-gh] (this gem) | [ruby\_tree\_sitter][ruby_tree_sitter] | [tree\_stump][tree_stump] | [citrus][citrus] | [parslet][parslet] |
468
+ |---------------------------|-----------------------------------------------|----------------------------------------|---------------------------|------------------|--------------------|
469
+ | **MRI Ruby** | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
470
+ | **JRuby** | ✅ Yes (FFI, Java, Citrus, or Parslet backend) | ❌ No | ❌ No | ✅ Yes | ✅ Yes |
471
+ | **TruffleRuby** | ✅ Yes (FFI, Citrus, or Parslet) | ❌ No | ❓ Unknown | ✅ Yes | ✅ Yes |
472
+ | **Backend** | Multi (MRI C, Rust, FFI, Java, Citrus, Parslet) | C extension only | Rust extension | Pure Ruby | Pure Ruby |
473
+ | **Incremental Parsing** | ✅ Via MRI C/Rust/Java backend | ✅ Yes | ✅ Yes | ❌ No | ❌ No |
474
+ | **Query API** | ⚡ Via MRI/Rust/Java backend | ✅ Yes | ✅ Yes | ❌ No | ❌ No |
475
+ | **Grammar Discovery** | ✅ Built-in `GrammarFinder` | ❌ Manual | ❌ Manual | ❌ Manual | ❌ Manual |
476
+ | **Security Validations** | ✅ `PathValidator` | ❌ No | ❌ No | ❌ No | ❌ No |
477
+ | **Language Registration** | ✅ Thread-safe registry | ❌ No | ❌ No | ❌ No | ❌ No |
478
+ | **Native Performance** | ⚡ Backend-dependent | ✅ Native C | ✅ Native Rust | ❌ Pure Ruby | ❌ Pure Ruby |
479
+ | **Precompiled Binaries** | ⚡ Via Rust backend | ✅ Yes | ✅ Yes | ✅ Pure Ruby | ✅ Pure Ruby |
480
+ | **Zero Native Deps** | ⚡ Via Citrus/Parslet backend | ❌ No | ❌ No | ✅ Yes | ✅ Yes |
481
+ | **Minimum Ruby** | 3.2+ | 3.0+ | 3.1+ | 0+ | 0+ |
423
482
 
424
483
  [ruby_tree_sitter]: https://github.com/Faveod/ruby-tree-sitter
425
484
  [tree_stump]: https://github.com/joker1007/tree_stump
426
485
  [citrus]: https://github.com/mjackson/citrus
486
+ [parslet]: https://github.com/kschiess/parslet
427
487
  [tree_haver]: https://github.com/kettle-rb/tree_haver
428
-
429
488
  **Note:** Java backend works with grammar `.so` files built against tree-sitter 0.24+. The grammars must be rebuilt with `tree-sitter generate` if they were compiled against older tree-sitter versions. FFI is recommended for JRuby as it's easier to set up.
430
489
 
431
- **Note:** TreeHaver can use `ruby_tree_sitter` (MRI) or `tree_stump` (MRI) as backends, or `java-tree-sitter` / `jtreesitter` \>= 0.26.0 ([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), JRuby), or FFI on any backend, giving you TreeHaver's unified API, grammar discovery, and security features, plus full access to incremental parsing when using those backends.
490
+ **Note:** TreeHaver can use `ruby_tree_sitter` (MRI) or `tree_stump` (MRI) as backends, or `java-tree-sitter` / `jtreesitter` \>= 0.26.0 ([docs](https://tree-sitter.github.io/java-tree-sitter/), [maven][jtreesitter], [source](https://github.com/tree-sitter/java-tree-sitter), JRuby), or FFI on any backend, giving you TreeHaver's unified API, grammar discovery, and security features, plus full access to incremental parsing when using those backends.
432
491
 
433
492
  **Note:** `tree_stump` currently requires unreleased fixes in the `main` branch.
434
493
 
@@ -436,78 +495,68 @@ tree_haver supports multiple parsing backends, but not all backends work on all
436
495
 
437
496
  **Choose TreeHaver when:**
438
497
 
439
- - You need JRuby or TruffleRuby support
440
-
441
- - You're building a library that should work across Ruby implementations
442
-
443
- - You want automatic grammar discovery and security validations
444
-
445
- - You want flexibility to switch backends without code changes
446
-
447
- - You need incremental parsing with a unified API
448
- **Choose ruby\_tree\_sitter directly when:**
449
-
450
- - You only target MRI Ruby
451
-
452
- - You need the full Query API without abstraction
498
+ - You need JRuby or TruffleRuby support
499
+ - You're building a library that should work across Ruby implementations
500
+ - You want automatic grammar discovery and security validations
501
+ - You want flexibility to switch backends without code changes
502
+ - You need incremental parsing with a unified API
453
503
 
454
- - You want the most battle-tested C bindings
504
+ **Choose ruby\_tree\_sitter directly when:**
455
505
 
456
- - You don't need TreeHaver's grammar discovery
457
- **Choose tree\_stump directly when:**
506
+ - You only target MRI Ruby
507
+ - You need the full Query API without abstraction
508
+ - You want the most battle-tested C bindings
509
+ - You don't need TreeHaver's grammar discovery
458
510
 
459
- - You only target MRI Ruby
511
+ **Choose tree\_stump directly when:**
460
512
 
461
- - You prefer Rust-based native extensions
513
+ - You only target MRI Ruby
514
+ - You prefer Rust-based native extensions
515
+ - You want precompiled binaries without system dependencies
516
+ - You don't need TreeHaver's grammar discovery
517
+ - **Note:** `tree_stump` currently requires unreleased fixes in the `main` branch.
462
518
 
463
- - You want precompiled binaries without system dependencies
519
+ **Choose citrus or parslet directly when:**
464
520
 
465
- - You don't need TreeHaver's grammar discovery
521
+ - You need zero native dependencies (pure Ruby)
522
+ - You're using a Citrus or Parslet grammar (not tree-sitter grammars)
523
+ - Performance is less critical than portability
524
+ - You don't need TreeHaver's unified API
466
525
 
467
- - **Note:** `tree_stump` currently requires unreleased fixes in the `main` branch.
468
- **Choose citrus directly when:**
469
-
470
- - You need zero native dependencies (pure Ruby)
471
-
472
- - You're using a Citrus grammar (not tree-sitter grammars)
473
-
474
- - Performance is less critical than portability
475
-
476
- - You don't need TreeHaver's unified API
477
526
  ## 💡 Info you can shake a stick at
478
527
 
479
- | 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) |
480
- | --- | --- |
481
- | 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) |
482
- | 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) |
483
- | 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) |
484
- | 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) |
485
- | 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) |
486
- | 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) |
487
- | 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) |
488
- | 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) |
489
- | 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) |
490
- | `...` 💖 | [![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) |
528
+ | Tokens to Remember | [![Gem name][⛳️name-img]](https://bestgems.org/gems/tree_haver) [![Gem namespace][⛳️namespace-img]](https://github.com/kettle-rb/tree_haver) |
529
+ |-------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
530
+ | Works with JRuby | [![JRuby 10.0 Compat][💎jruby-c-i]](https://github.com/kettle-rb/tree_haver/actions/workflows/current.yml) [![JRuby HEAD Compat][💎jruby-headi]](https://github.com/kettle-rb/tree_haver/actions/workflows/heads.yml) |
531
+ | Works with Truffle Ruby | [![Truffle Ruby 23.1 Compat][💎truby-23.1i]](https://github.com/kettle-rb/tree_haver/actions/workflows/truffle.yml) [![Truffle Ruby 24.1 Compat][💎truby-c-i]](https://github.com/kettle-rb/tree_haver/actions/workflows/current.yml) |
532
+ | Works with MRI Ruby 3 | [![Ruby 3.2 Compat][💎ruby-3.2i]](https://github.com/kettle-rb/tree_haver/actions/workflows/supported.yml) [![Ruby 3.3 Compat][💎ruby-3.3i]](https://github.com/kettle-rb/tree_haver/actions/workflows/supported.yml) [![Ruby 3.4 Compat][💎ruby-c-i]](https://github.com/kettle-rb/tree_haver/actions/workflows/current.yml) [![Ruby HEAD Compat][💎ruby-headi]](https://github.com/kettle-rb/tree_haver/actions/workflows/heads.yml) |
533
+ | Support & Community | [![Join Me on Daily.dev's RubyFriends][✉️ruby-friends-img]](https://app.daily.dev/squads/rubyfriends) [![Live Chat on Discord][✉️discord-invite-img-ftb]](https://discord.gg/3qme4XHNKN) [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]](https://www.upwork.com/freelancers/~014942e9b056abdf86?mp_source=share) [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]](https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github) |
534
+ | Source | [![Source on GitLab.com][📜src-gl-img]](https://gitlab.com/kettle-rb/tree_haver/) [![Source on CodeBerg.org][📜src-cb-img]](https://codeberg.org/kettle-rb/tree_haver) [![Source on Github.com][📜src-gh-img]](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) |
535
+ | Documentation | [![Current release on RubyDoc.info][📜docs-cr-rd-img]](http://rubydoc.info/gems/tree_haver) [![YARD on Galtzo.com][📜docs-head-rd-img]](https://tree-haver.galtzo.com) [![Maintainer Blog][🚂maint-blog-img]](http://www.railsbling.com/tags/tree_haver) [![GitLab Wiki][📜gl-wiki-img]](https://gitlab.com/kettle-rb/tree_haver/-/wikis/home) [![GitHub Wiki][📜gh-wiki-img]](https://github.com/kettle-rb/tree_haver/wiki) |
536
+ | Compliance | [![License: MIT][📄license-img]](https://opensource.org/licenses/MIT) [![Compatible with Apache Software Projects: Verified by SkyWalking Eyes][📄license-compat-img]](https://dev.to/galtzo/how-to-check-license-compatibility-41h0) [![📄ilo-declaration-img][📄ilo-declaration-img]](https://www.ilo.org/declaration/lang--en/index.htm) [![Security Policy][🔐security-img]](SECURITY.md) [![Contributor Covenant 2.1][🪇conduct-img]](CODE_OF_CONDUCT.md) [![SemVer 2.0.0][📌semver-img]](https://semver.org/spec/v2.0.0.html) |
537
+ | Style | [![Enforced Code Style Linter][💎rlts-img]](https://github.com/rubocop-lts/rubocop-lts) [![Keep-A-Changelog 1.0.0][📗keep-changelog-img]](https://keepachangelog.com/en/1.0.0/) [![Gitmoji Commits][📌gitmoji-img]](https://gitmoji.dev) [![Compatibility appraised by: appraisal2][💎appraisal2-img]](https://github.com/appraisal-rb/appraisal2) |
538
+ | Maintainer 🎖️ | [![Follow Me on LinkedIn][💖🖇linkedin-img]](http://www.linkedin.com/in/peterboling) [![Follow Me on Ruby.Social][💖🐘ruby-mast-img]](https://ruby.social/@galtzo) [![Follow Me on Bluesky][💖🦋bluesky-img]](https://bsky.app/profile/galtzo.com) [![Contact Maintainer][🚂maint-contact-img]](http://www.railsbling.com/contact) [![My technical writing][💖💁🏼‍♂️devto-img]](https://dev.to/galtzo) |
539
+ | `...` 💖 | [![Find Me on WellFound:][💖✌️wellfound-img]](https://wellfound.com/u/peter-boling) [![Find Me on CrunchBase][💖💲crunchbase-img]](https://www.crunchbase.com/person/peter-boling) [![My LinkTree][💖🌳linktree-img]](https://linktr.ee/galtzo) [![More About Me][💖💁🏼‍♂️aboutme-img]](https://about.me/peter.boling) [🧊][💖🧊berg] [🐙][💖🐙hub] [🛖][💖🛖hut] [🧪][💖🧪lab] |
491
540
 
492
541
  ### Compatibility
493
542
 
494
543
  Compatible with MRI Ruby 3.2.0+, and concordant releases of JRuby, and TruffleRuby.
495
544
 
496
- | 🚚 *Amazing* test matrix was brought to you by | 🔎 appraisal2 🔎 and the color 💚 green 💚 |
497
- | --- | --- |
498
- | 👟 Check it out\! | ✨ [github.com/appraisal-rb/appraisal2](https://github.com/appraisal-rb/appraisal2) ✨ |
545
+ | 🚚 *Amazing* test matrix was brought to you by | 🔎 appraisal2 🔎 and the color 💚 green 💚 |
546
+ |------------------------------------------------|--------------------------------------------------------|
547
+ | 👟 Check it out\! | ✨ [github.com/appraisal-rb/appraisal2][💎appraisal2] ✨ |
499
548
 
500
549
  ### Federated DVCS
501
550
 
502
551
  <details markdown="1">
503
552
  <summary>Find this repo on federated forges (Coming soon!)</summary>
504
553
 
505
- | Federated [DVCS](https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/) Repository | Status | Issues | PRs | Wiki | CI | Discussions |
506
- | --- | --- | --- | --- | --- | --- | --- |
507
- | 🧪 [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 | ➖ |
508
- | 🧊 [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 | ➖ |
509
- | 🐙 [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) |
510
- | 🎮️ [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) |
554
+ | Federated [DVCS][💎d-in-dvcs] Repository | Status | Issues | PRs | Wiki | CI | Discussions |
555
+ |--------------------------------------------------|------------------------------------------------------------------------------------|----------------------------|---------------------------|----------------------------|---------------------------|--------------------------------|
556
+ | 🧪 [kettle-rb/tree\_haver on GitLab][📜src-gl] | The Truth | [💚][🤝gl-issues] | [💚][🤝gl-pulls] | [💚][📜gl-wiki] | 🐭 Tiny Matrix | ➖ |
557
+ | 🧊 [kettle-rb/tree\_haver on CodeBerg][📜src-cb] | An Ethical Mirror ([Donate][🤝cb-donate]) | [💚][🤝cb-issues] | [💚][🤝cb-pulls] | ➖ | ⭕️ No Matrix | ➖ |
558
+ | 🐙 [kettle-rb/tree\_haver on GitHub][📜src-gh] | Another Mirror | [💚][🤝gh-issues] | [💚][🤝gh-pulls] | [💚][📜gh-wiki] | 💯 Full Matrix | [💚][gh-discussions] |
559
+ | 🎮️ [Discord Server][🖼️galtzo-discord] | [![Live Chat on Discord][✉️discord-invite-img-ftb]](https://discord.gg/3qme4XHNKN) | [Let's][🖼️galtzo-discord] | [talk][🖼️galtzo-discord] | [about][🖼️galtzo-discord] | [this][🖼️galtzo-discord] | [library\!][🖼️galtzo-discord] |
511
560
 
512
561
  </details>
513
562
 
@@ -522,33 +571,34 @@ Available as part of the Tidelift Subscription.
522
571
 
523
572
  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.
524
573
 
525
- [![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)
574
+ [![Get help from me on Tidelift][🏙️entsup-tidelift-img]](https://tidelift.com/subscription/pkg/rubygems-tree_haver?utm_source=rubygems-tree_haver&utm_medium=referral&utm_campaign=readme)
526
575
 
527
576
  - 💡Subscribe for support guarantees covering *all* your FLOSS dependencies
528
577
 
529
- - 💡Tidelift is part of [Sonar](https://blog.tidelift.com/tidelift-joins-sonar)
578
+ - 💡Tidelift is part of [Sonar][🏙️entsup-tidelift-sonar]
530
579
 
531
- - 💡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
580
+ - 💡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
532
581
  Alternatively:
533
582
 
534
- - [![Live Chat on Discord](https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord)](https://discord.gg/3qme4XHNKN)
583
+ - [![Live Chat on Discord][✉️discord-invite-img-ftb]](https://discord.gg/3qme4XHNKN)
584
+
585
+ - [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]](https://www.upwork.com/freelancers/~014942e9b056abdf86?mp_source=share)
535
586
 
536
- - [![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)
587
+ - [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]](https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github)
537
588
 
538
- - [![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)
539
589
  </details>
540
590
 
541
591
  ## ✨ Installation
542
592
 
543
593
  Install the gem and add to the application's Gemfile by executing:
544
594
 
545
- ``` console
595
+ ```console
546
596
  bundle add tree_haver
547
597
  ```
548
598
 
549
599
  If bundler is not being used to manage dependencies, install the gem by executing:
550
600
 
551
- ``` console
601
+ ```console
552
602
  gem install tree_haver
553
603
  ```
554
604
 
@@ -557,19 +607,19 @@ gem install tree_haver
557
607
  <details markdown="1">
558
608
  <summary>For Medium or High Security Installations</summary>
559
609
 
560
- 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
561
- [stone\_checksums](https://github.com/galtzo-floss/stone_checksums). Be sure the gem you install hasn’t been tampered with
610
+ This gem is cryptographically signed, and has verifiable [SHA-256 and SHA-512][💎SHA_checksums] checksums by
611
+ [stone\_checksums][💎stone_checksums]. Be sure the gem you install hasn’t been tampered with
562
612
  by following the instructions below.
563
613
 
564
614
  Add my public key (if you haven’t already, expires 2045-04-29) as a trusted certificate:
565
615
 
566
- ``` console
616
+ ```console
567
617
  gem cert --add <(curl -Ls https://raw.github.com/galtzo-floss/certs/main/pboling.pem)
568
618
  ```
569
619
 
570
620
  You only need to do that once. Then proceed to install with:
571
621
 
572
- ``` console
622
+ ```console
573
623
  gem install tree_haver -P HighSecurity
574
624
  ```
575
625
 
@@ -577,7 +627,7 @@ The `HighSecurity` trust profile will verify signed gems, and not allow the inst
577
627
 
578
628
  If you want to up your security game full-time:
579
629
 
580
- ``` console
630
+ ```console
581
631
  bundle config set --global trust-policy MediumSecurity
582
632
  ```
583
633
 
@@ -595,32 +645,34 @@ TreeHaver supports 10 parsing backends, each with different trade-offs. The `aut
595
645
 
596
646
  #### Tree-sitter Backends (Universal Parsing)
597
647
 
598
- | Backend | Description | Performance | Portability | Examples |
599
- | --- | --- | --- | --- | --- |
600
- | **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) |
601
- | **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) |
602
- | **Rust** | Precompiled via tree\_stump | ⚡ Very Fast | ✅ Good | [JSON](examples/rust_json.rb) · [JSONC](examples/rust_jsonc.rb) · \~\~Bash\~\~\* · [TOML](examples/rust_toml.rb) |
603
- | **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) |
604
- | **Java** | JNI bindings (jtreesitter \>= 0.26.0) | ⚡ Very Fast | JRuby only | [JSON](examples/java_json.rb) · [JSONC](examples/java_jsonc.rb) · [Bash](examples/java_bash.rb) · [TOML](examples/java_toml.rb) |
648
+ | Backend | Description | Performance | Portability | Examples |
649
+ |----------|---------------------------------------|-------------|-------------|---------------------------------------------------------------------------------------------------------------------------------|
650
+ | **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) |
651
+ | **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) |
652
+ | **Rust** | Precompiled via tree\_stump | ⚡ Very Fast | ✅ Good | [JSON](examples/rust_json.rb) · [JSONC](examples/rust_jsonc.rb) · \~\~Bash\~\~\* · [TOML](examples/rust_toml.rb) |
653
+ | **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) |
654
+ | **Java** | JNI bindings (jtreesitter \>= 0.26.0) | ⚡ Very Fast | JRuby only | [JSON](examples/java_json.rb) · [JSONC](examples/java_jsonc.rb) · [Bash](examples/java_bash.rb) · [TOML](examples/java_toml.rb) |
605
655
 
606
656
  #### Language-Specific Backends (Native Parser Integration)
607
657
 
608
- | Backend | Description | Performance | Portability | Examples |
609
- | --- | --- | --- | --- | --- |
610
- | **Prism** | Ruby's official parser | ⚡ Very Fast | ✅ Universal | [Ruby](examples/prism_ruby.rb) |
611
- | **Psych** | Ruby's YAML parser (stdlib) | ⚡ Very Fast | ✅ Universal | [YAML](examples/psych_yaml.rb) |
612
- | **Commonmarker** | Markdown via comrak (Rust) | ⚡ Very Fast | ✅ Good | [Markdown](examples/commonmarker_markdown.rb) · [Merge](examples/commonmarker_merge_example.rb) |
613
- | **Markly** | GFM via cmark-gfm (C) | ⚡ Very Fast | ✅ Good | [Markdown](examples/markly_markdown.rb) · [Merge](examples/markly_merge_example.rb) |
614
- | **Citrus** | Pure Ruby parsing | 🟡 Slower | ✅ Universal | [TOML](examples/citrus_toml.rb) · [Finitio](examples/citrus_finitio.rb) · [Dhall](examples/citrus_dhall.rb) |
658
+ | Backend | Description | Performance | Portability | Examples |
659
+ |------------------|-----------------------------|-------------|-------------|--------------------------------------------------------------------------------------------------------------|
660
+ | **Prism** | Ruby's official parser | ⚡ Very Fast | ✅ Universal | [Ruby](examples/prism_ruby.rb) |
661
+ | **Psych** | Ruby's YAML parser (stdlib) | ⚡ Very Fast | ✅ Universal | [YAML](examples/psych_yaml.rb) |
662
+ | **Commonmarker** | Markdown via comrak (Rust) | ⚡ Very Fast | ✅ Good | [Markdown](examples/commonmarker_markdown.rb) · [commonmarker-merge](examples/commonmarker_merge_example.rb) |
663
+ | **Markly** | GFM via cmark-gfm (C) | ⚡ Very Fast | ✅ Good | [Markdown](examples/markly_markdown.rb) · [Merge](examples/markly_merge_example.rb) |
664
+ | **Citrus** | Pure Ruby parsing | 🟡 Slower | ✅ Universal | [TOML](examples/citrus_toml.rb) · [Finitio](examples/citrus_finitio.rb) · [Dhall](examples/citrus_dhall.rb) |
665
+ | **Parslet** | Pure Ruby parsing | 🟡 Slower | ✅ Universal | [TOML](examples/parslet_toml.rb) |
615
666
 
616
- **Selection Priority (Auto mode):** MRI → Rust → FFI → Java → Prism → Psych → Commonmarker → Markly → Citrus
667
+ **Selection Priority (Auto mode):** MRI → Rust → FFI → Java → Prism → Psych → Commonmarker → Markly → Citrus → Parslet
617
668
 
618
669
  **Known Issues:**
670
+
619
671
  - \*MRI + Bash: ABI incompatibility (use FFI instead)
620
672
  - \*Rust + Bash: Version mismatch (use FFI instead)
621
673
  **Backend Requirements:**
622
- <!-- end list -->
623
- ``` ruby
674
+
675
+ ```ruby
624
676
  # Tree-sitter backends
625
677
  gem "ruby_tree_sitter", "~> 2.0" # MRI backend
626
678
  gem "tree_stump" # Rust backend
@@ -633,14 +685,15 @@ gem "prism", "~> 1.0" # Ruby parsing (stdlib in Ruby 3.4+)
633
685
  gem "commonmarker", ">= 0.23" # Markdown parsing (comrak)
634
686
  gem "markly", "~> 0.11" # GFM parsing (cmark-gfm)
635
687
 
636
- # Pure Ruby fallback
688
+ # Pure Ruby fallbacks
637
689
  gem "citrus", "~> 3.0" # Citrus backend
638
- # Plus grammar gems: toml-rb, dhall, finitio, etc.
690
+ gem "parslet", "~> 2.0" # Parslet backend
691
+ # Plus grammar gems: toml-rb (citrus), toml (parslet), dhall, finitio, etc.
639
692
  ```
640
693
 
641
694
  **Force Specific Backend:**
642
695
 
643
- ``` ruby
696
+ ```ruby
644
697
  # Tree-sitter backends
645
698
  TreeHaver.backend = :mri # Force MRI backend (ruby_tree_sitter)
646
699
  TreeHaver.backend = :rust # Force Rust backend (tree_stump)
@@ -652,9 +705,8 @@ TreeHaver.backend = :prism # Force Prism (Ruby parsing)
652
705
  TreeHaver.backend = :psych # Force Psych (YAML parsing)
653
706
  TreeHaver.backend = :commonmarker # Force Commonmarker (Markdown)
654
707
  TreeHaver.backend = :markly # Force Markly (GFM Markdown)
655
-
656
- # Pure Ruby fallback
657
- TreeHaver.backend = :citrus # Force Citrus backend
708
+ TreeHaver.backend = :citrus # Force Citrus (Pure Ruby PEG)
709
+ TreeHaver.backend = :parslet # Force Parslet (Pure Ruby PEG)
658
710
 
659
711
  # Auto-selection (default)
660
712
  TreeHaver.backend = :auto # Let TreeHaver choose
@@ -666,7 +718,7 @@ Use `with_backend` to temporarily switch backends for a specific block of code.
666
718
  This is thread-safe and supports nesting—the previous backend is automatically
667
719
  restored when the block exits (even if an exception is raised).
668
720
 
669
- ``` ruby
721
+ ```ruby
670
722
  # Temporarily use a specific backend
671
723
  TreeHaver.with_backend(:mri) do
672
724
  parser = TreeHaver::Parser.new
@@ -683,6 +735,11 @@ TreeHaver.with_backend(:rust) do
683
735
  parser = TreeHaver::Parser.new
684
736
  end
685
737
  # Back to :rust
738
+ TreeHaver.with_backend(:parslet) do
739
+ # Uses :parslet
740
+ parser = TreeHaver::Parser.new
741
+ end
742
+ # Back to :rust
686
743
  end
687
744
  # Back to original backend
688
745
  ```
@@ -693,10 +750,10 @@ This is particularly useful for:
693
750
  - **Performance comparison**: Benchmark different backends
694
751
  - **Fallback scenarios**: Try one backend, fall back to another
695
752
  - **Thread isolation**: Each thread can use a different backend safely
696
- <!-- end list -->
697
- ``` ruby
753
+
754
+ ```ruby
698
755
  # Example: Testing with multiple backends
699
- [:mri, :rust, :citrus].each do |backend_name|
756
+ [:mri, :rust, :citrus, :parslet].each do |backend_name|
700
757
  TreeHaver.with_backend(backend_name) do
701
758
  parser = TreeHaver::Parser.new
702
759
  result = parser.parse(source)
@@ -707,7 +764,7 @@ end
707
764
 
708
765
  **Check Backend Capabilities:**
709
766
 
710
- ``` ruby
767
+ ```ruby
711
768
  TreeHaver.backend # => :ffi
712
769
  TreeHaver.backend_module # => TreeHaver::Backends::FFI
713
770
  TreeHaver.capabilities # => { backend: :ffi, parse: true, query: false, ... }
@@ -732,9 +789,10 @@ TreeHaver's `PathValidator` module protects against:
732
789
  - **Malicious filenames**: Filenames must match a safe pattern (alphanumeric, hyphens, underscores)
733
790
  - **Invalid language names**: Language names must be lowercase alphanumeric with underscores
734
791
  - **Invalid symbol names**: Symbol names must be valid C identifiers
792
+
735
793
  #### Secure Usage
736
794
 
737
- ``` ruby
795
+ ```ruby
738
796
  # Standard usage - paths from ENV are validated
739
797
  finder = TreeHaver::GrammarFinder.new(:toml)
740
798
  path = finder.find_library_path # Validates ENV path before returning
@@ -764,8 +822,8 @@ The `find_library_path_safe` method only returns paths in trusted directories.
764
822
  - `/opt/homebrew/lib`, `/opt/local/lib`
765
823
  **Adding custom trusted directories:**
766
824
  For non-standard installations (Homebrew on Linux, luarocks, mise, asdf, etc.), register additional trusted directories:
767
- <!-- end list -->
768
- ``` ruby
825
+
826
+ ```ruby
769
827
  # Programmatically at application startup
770
828
  TreeHaver::PathValidator.add_trusted_directory("/home/linuxbrew/.linuxbrew/Cellar")
771
829
  TreeHaver::PathValidator.add_trusted_directory("~/.local/share/mise/installs/lua")
@@ -776,7 +834,7 @@ export TREE_HAVER_TRUSTED_DIRS = "/home/linuxbrew/.linuxbrew/Cellar,~/.local/sha
776
834
 
777
835
  **Example: Fedora Silverblue with Homebrew and luarocks**
778
836
 
779
- ``` bash
837
+ ```bash
780
838
  # In ~/.bashrc or ~/.zshrc
781
839
  export TREE_HAVER_TRUSTED_DIRS="/home/linuxbrew/.linuxbrew/Cellar,~/.local/share/mise/installs/lua"
782
840
 
@@ -794,11 +852,12 @@ export TREE_SITTER_TOML_PATH=~/.local/share/mise/installs/lua/5.4.8/luarocks/lib
794
852
  3. **User Input**: Always validate paths before passing to `Language.from_library`
795
853
  4. **CI/CD**: Be cautious of ENV vars that could be set by untrusted sources
796
854
  5. **Custom installs**: Register trusted directories via `TREE_HAVER_TRUSTED_DIRS` or `add_trusted_directory`
855
+
797
856
  ### Backend Selection
798
857
 
799
858
  TreeHaver automatically selects the best backend for your Ruby implementation, but you can override this behavior:
800
859
 
801
- ``` ruby
860
+ ```ruby
802
861
  # Automatic backend selection (default)
803
862
  TreeHaver.backend = :auto
804
863
 
@@ -812,13 +871,16 @@ TreeHaver.backend = :java # Use Java bindings (JRuby only, coming soon)
812
871
  TreeHaver.backend = :citrus # Use Citrus pure Ruby parser
813
872
  # NOTE: Portable, all Ruby implementations
814
873
  # CAVEAT: few major language grammars, but many esoteric grammars
874
+ TreeHaver.backend = :parslet # Use Parslet pure Ruby parser
875
+ # NOTE: Portable, all Ruby implementations
876
+ # CAVEAT: few major language grammars, but many esoteric grammars
815
877
  ```
816
878
 
817
- **Auto-selection priority on MRI:** MRI → Rust → FFI → Citrus
879
+ **Auto-selection priority on MRI:** MRI → Rust → FFI → Citrus → Parslet
818
880
 
819
881
  You can also set the backend via environment variable:
820
882
 
821
- ``` bash
883
+ ```bash
822
884
  export TREE_HAVER_BACKEND=rust
823
885
  ```
824
886
 
@@ -858,7 +920,7 @@ TreeHaver::BackendRegistry.registered_backends # => [:mri, :rust, :ffi, .
858
920
 
859
921
  #### How It Works
860
922
 
861
- 1. Built-in backends (MRI, Rust, FFI, Java, Prism, Psych, Citrus) automatically register their checkers when loaded
923
+ 1. Built-in backends (MRI, Rust, FFI, Java, Prism, Psych, Citrus, Parslet) automatically register their checkers when loaded
862
924
  2. External gems register their checkers when their backend module is loaded
863
925
  3. `TreeHaver::RSpec::DependencyTags` uses the registry to dynamically detect available backends
864
926
  4. Results are cached for performance (use `clear_cache!` to reset)
@@ -891,18 +953,20 @@ TreeHaver recognizes several environment variables for configuration:
891
953
 
892
954
  - **`TREE_HAVER_TRUSTED_DIRS`**: Comma-separated list of additional trusted directories for grammar libraries
893
955
 
894
- ``` bash
956
+ ```bash
895
957
  # For Homebrew on Linux and luarocks
896
958
  export TREE_HAVER_TRUSTED_DIRS="/home/linuxbrew/.linuxbrew/Cellar,~/.local/share/mise/installs/lua"
897
959
  ```
898
960
 
899
961
  Tilde (`~`) is expanded to the user's home directory. Directories listed here are considered safe for `find_library_path_safe`.
962
+
900
963
  #### Core Runtime Library
901
964
 
902
965
  - **`TREE_SITTER_RUNTIME_LIB`**: Absolute path to the core `libtree-sitter` shared library
903
- ``` bash
966
+ ```bash
904
967
  export TREE_SITTER_RUNTIME_LIB=/usr/local/lib/libtree-sitter.so
905
968
  ```
969
+
906
970
  If not set, TreeHaver tries these names in order:
907
971
 
908
972
  - `tree-sitter`
@@ -910,6 +974,7 @@ If not set, TreeHaver tries these names in order:
910
974
  - `libtree-sitter.so`
911
975
  - `libtree-sitter.dylib`
912
976
  - `libtree-sitter.dll`
977
+
913
978
  #### Language Symbol Resolution
914
979
 
915
980
  When loading a language grammar, if you don't specify the `symbol:` parameter, TreeHaver resolves it in this precedence:
@@ -917,8 +982,8 @@ When loading a language grammar, if you don't specify the `symbol:` parameter, T
917
982
  1. **`TREE_SITTER_LANG_SYMBOL`**: Explicit symbol override
918
983
  2. Guessed from filename (e.g., `libtree-sitter-toml.so` → `tree_sitter_toml`)
919
984
  3. Default fallback (`tree_sitter_toml`)
920
- <!-- end list -->
921
- ``` bash
985
+
986
+ ```bash
922
987
  export TREE_SITTER_LANG_SYMBOL=tree_sitter_toml
923
988
  ```
924
989
 
@@ -926,7 +991,7 @@ export TREE_SITTER_LANG_SYMBOL=tree_sitter_toml
926
991
 
927
992
  For specific languages, you can set environment variables to point to grammar libraries:
928
993
 
929
- ``` bash
994
+ ```bash
930
995
  export TREE_SITTER_TOML_PATH=/usr/local/lib/libtree-sitter-toml.so
931
996
  export TREE_SITTER_JSON_PATH=/usr/local/lib/libtree-sitter-json.so
932
997
  ```
@@ -938,8 +1003,8 @@ For the Java backend on JRuby, you need:
938
1003
  1. **jtreesitter \>= 0.26.0** JAR from Maven Central
939
1004
  2. **Tree-sitter runtime library** (`libtree-sitter.so`) version 0.26+
940
1005
  3. **Grammar `.so` files** built against tree-sitter 0.26+
941
- <!-- end list -->
942
- ``` bash
1006
+
1007
+ ```bash
943
1008
  # Download jtreesitter JAR (or use bin/setup-jtreesitter)
944
1009
  export TREE_SITTER_JAVA_JARS_DIR=/path/to/java-tree-sitter/jars
945
1010
 
@@ -954,20 +1019,20 @@ export TREE_SITTER_TOML_PATH=/path/to/libtree-sitter-toml.so
954
1019
 
955
1020
  If you get "version mismatch" errors, rebuild the grammar:
956
1021
 
957
- ``` bash
1022
+ ```bash
958
1023
  # Use the provided build script
959
1024
  bin/build-grammar toml
960
1025
 
961
1026
  # This regenerates parser.c for your tree-sitter version and compiles it
962
1027
  ```
963
1028
 
964
- For more see [docs](https://tree-sitter.github.io/java-tree-sitter/), [maven](https://central.sonatype.com/artifact/io.github.tree-sitter/jtreesitter), and [source](https://github.com/tree-sitter/java-tree-sitter).
1029
+ For more see [docs](https://tree-sitter.github.io/java-tree-sitter/), [maven][jtreesitter], and [source](https://github.com/tree-sitter/java-tree-sitter).
965
1030
 
966
1031
  ### Language Registration
967
1032
 
968
1033
  Register languages once at application startup for convenient access:
969
1034
 
970
- ``` ruby
1035
+ ```ruby
971
1036
  # Register a TOML grammar
972
1037
  TreeHaver.register_language(
973
1038
  :toml,
@@ -988,7 +1053,7 @@ language = TreeHaver::Language.toml(
988
1053
 
989
1054
  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.
990
1055
 
991
- ``` ruby
1056
+ ```ruby
992
1057
  # Create a finder for any language
993
1058
  finder = TreeHaver::GrammarFinder.new(:toml)
994
1059
 
@@ -1011,11 +1076,11 @@ language = TreeHaver::Language.toml
1011
1076
 
1012
1077
  Given just the language name, `GrammarFinder` automatically derives:
1013
1078
 
1014
- | Property | Derived Value (for `:toml`) |
1015
- | --- | --- |
1016
- | ENV var | `TREE_SITTER_TOML_PATH` |
1079
+ | Property | Derived Value (for `:toml`) |
1080
+ |------------------|------------------------------------------------------|
1081
+ | ENV var | `TREE_SITTER_TOML_PATH` |
1017
1082
  | Library filename | `libtree-sitter-toml.so` (Linux) or `.dylib` (macOS) |
1018
- | Symbol name | `tree_sitter_toml` |
1083
+ | Symbol name | `tree_sitter_toml` |
1019
1084
 
1020
1085
  #### Search Order
1021
1086
 
@@ -1024,11 +1089,12 @@ Given just the language name, `GrammarFinder` automatically derives:
1024
1089
  1. **Environment variable**: `TREE_SITTER_<LANG>_PATH` (highest priority)
1025
1090
  2. **Extra paths**: Custom paths provided at initialization
1026
1091
  3. **System paths**: Common installation directories (`/usr/lib`, `/usr/local/lib`, `/opt/homebrew/lib`, etc.)
1092
+
1027
1093
  #### Usage in \*-merge Gems
1028
1094
 
1029
1095
  The `GrammarFinder` pattern enables clean integration in language-specific merge gems:
1030
1096
 
1031
- ``` ruby
1097
+ ```ruby
1032
1098
  # In toml-merge
1033
1099
  finder = TreeHaver::GrammarFinder.new(:toml)
1034
1100
  finder.register! if finder.available?
@@ -1048,7 +1114,7 @@ Each gem uses the same API—only the language name changes.
1048
1114
 
1049
1115
  For non-standard installations, provide extra search paths:
1050
1116
 
1051
- ``` ruby
1117
+ ```ruby
1052
1118
  finder = TreeHaver::GrammarFinder.new(:toml, extra_paths: [
1053
1119
  "/opt/custom/lib",
1054
1120
  "/home/user/.local/lib",
@@ -1059,7 +1125,7 @@ finder = TreeHaver::GrammarFinder.new(:toml, extra_paths: [
1059
1125
 
1060
1126
  Get detailed information about the grammar search:
1061
1127
 
1062
- ``` ruby
1128
+ ```ruby
1063
1129
  finder = TreeHaver::GrammarFinder.new(:toml)
1064
1130
  puts finder.search_info
1065
1131
  # => {
@@ -1078,20 +1144,22 @@ puts finder.search_info
1078
1144
 
1079
1145
  Different backends may support different features:
1080
1146
 
1081
- ``` ruby
1147
+ ```ruby
1082
1148
  TreeHaver.capabilities
1083
1149
  # => { backend: :mri, query: true, bytes_field: true }
1084
1150
  # or
1085
1151
  # => { backend: :ffi, parse: true, query: false, bytes_field: true }
1086
1152
  # or
1087
1153
  # => { backend: :citrus, parse: true, query: false, bytes_field: false }
1154
+ # or
1155
+ # => { backend: :parslet, parse: true, query: false, bytes_field: false }
1088
1156
  ```
1089
1157
 
1090
1158
  ### Compatibility Mode
1091
1159
 
1092
1160
  For codebases migrating from `ruby_tree_sitter`, TreeHaver provides a compatibility shim:
1093
1161
 
1094
- ``` ruby
1162
+ ```ruby
1095
1163
  require "tree_haver/compat"
1096
1164
 
1097
1165
  # Now TreeSitter constants map to TreeHaver
@@ -1108,7 +1176,7 @@ This design decision follows ruby\_tree\_sitter's lead for thread-safety and sig
1108
1176
 
1109
1177
  **What this means for exception handling:**
1110
1178
 
1111
- ``` ruby
1179
+ ```ruby
1112
1180
  # ⚠️ This will NOT catch TreeHaver errors
1113
1181
  begin
1114
1182
  TreeHaver::Language.from_library("/nonexistent.so")
@@ -1137,11 +1205,12 @@ end
1137
1205
  └── TreeHaver::Error # Base error class
1138
1206
  ├── TreeHaver::NotAvailable # Backend/grammar not available
1139
1207
  └── TreeHaver::BackendConflict # Backend incompatibility detected
1208
+
1140
1209
  **Compatibility Mode Behavior:**
1141
1210
 
1142
1211
  The compat mode (`require "tree_haver/compat"`) creates aliases but **does not change the exception hierarchy**:
1143
1212
 
1144
- ``` ruby
1213
+ ```ruby
1145
1214
  require "tree_haver/compat"
1146
1215
 
1147
1216
  # TreeSitter constants are now aliases to TreeHaver
@@ -1161,7 +1230,7 @@ end
1161
1230
 
1162
1231
  1. **Always use explicit rescue** for TreeHaver errors:
1163
1232
 
1164
- ``` ruby
1233
+ ```ruby
1165
1234
  begin
1166
1235
  finder = TreeHaver::GrammarFinder.new(:toml)
1167
1236
  finder.register! if finder.available?
@@ -1175,18 +1244,19 @@ end
1175
1244
  2. **Never rely on `rescue => e`** to catch TreeHaver errors (it won't work)
1176
1245
  **Why inherit from Exception?**
1177
1246
  Following ruby\_tree\_sitter's reasoning:
1178
- <!-- end list -->
1247
+
1179
1248
  - **Thread safety**: Prevents accidental catching in thread cleanup code
1180
1249
  - **Signal handling**: Ensures parsing errors don't interfere with SIGTERM/SIGINT
1181
1250
  - **Intentional handling**: Forces developers to explicitly handle parsing errors
1182
1251
  See `lib/tree_haver/compat.rb` for compatibility layer documentation.
1252
+
1183
1253
  ## 🔧 Basic Usage
1184
1254
 
1185
1255
  ### Quick Start
1186
1256
 
1187
1257
  The simplest way to parse code is with `TreeHaver.parser_for`, which handles all the complexity of language loading, grammar discovery, and backend selection:
1188
1258
 
1189
- ``` ruby
1259
+ ```ruby
1190
1260
  require "tree_haver"
1191
1261
 
1192
1262
  # Parse TOML - auto-discovers grammar and falls back to Citrus if needed
@@ -1212,11 +1282,13 @@ parser = TreeHaver.parser_for(
1212
1282
  ```
1213
1283
 
1214
1284
  `TreeHaver.parser_for` handles:
1285
+
1215
1286
  1. Checking if the language is already registered
1216
1287
  2. Auto-discovering tree-sitter grammar via `GrammarFinder`
1217
1288
  3. Falling back to Citrus grammar if tree-sitter is unavailable
1218
1289
  4. Creating and configuring the parser
1219
1290
  5. Raising `NotAvailable` with a helpful message if nothing works
1291
+
1220
1292
  ### Manual Parser Setup
1221
1293
 
1222
1294
  For more control, you can create parsers manually:
@@ -1225,7 +1297,7 @@ TreeHaver works with any language through its 10 backends. Here are examples for
1225
1297
 
1226
1298
  #### Parsing with Tree-sitter (Universal Languages)
1227
1299
 
1228
- ``` ruby
1300
+ ```ruby
1229
1301
  require "tree_haver"
1230
1302
 
1231
1303
  # Load a tree-sitter grammar (works with MRI, Rust, FFI, or Java backend)
@@ -1262,7 +1334,7 @@ end
1262
1334
 
1263
1335
  #### Parsing Ruby with Prism
1264
1336
 
1265
- ``` ruby
1337
+ ```ruby
1266
1338
  require "tree_haver"
1267
1339
 
1268
1340
  TreeHaver.backend = :prism
@@ -1296,7 +1368,7 @@ end
1296
1368
 
1297
1369
  #### Parsing YAML with Psych
1298
1370
 
1299
- ``` ruby
1371
+ ```ruby
1300
1372
  require "tree_haver"
1301
1373
 
1302
1374
  TreeHaver.backend = :psych
@@ -1324,7 +1396,7 @@ show_structure(root)
1324
1396
 
1325
1397
  #### Parsing Markdown with Commonmarker or Markly
1326
1398
 
1327
- ``` ruby
1399
+ ```ruby
1328
1400
  require "tree_haver"
1329
1401
 
1330
1402
  # Choose your backend
@@ -1364,7 +1436,7 @@ end
1364
1436
 
1365
1437
  For cleaner code, register languages at startup:
1366
1438
 
1367
- ``` ruby
1439
+ ```ruby
1368
1440
  # At application initialization
1369
1441
  TreeHaver.register_language(
1370
1442
  :toml,
@@ -1389,16 +1461,16 @@ tree = parser.parse(toml_source)
1389
1461
 
1390
1462
  The `name` parameter in `register_language` is an arbitrary identifier you choose—it doesn't
1391
1463
  need to match the actual language name. The actual grammar identity comes from the `path`
1392
- and `symbol` parameters (for tree-sitter) or `grammar_module` (for Citrus).
1464
+ and `symbol` parameters (for tree-sitter) or `grammar_module` (for Citrus/Parslet).
1393
1465
 
1394
1466
  This flexibility is useful for:
1395
1467
 
1396
- - **Aliasing**: Register the same grammar under multiple names
1397
- - **Versioning**: Register different grammar versions (e.g., `:ruby_2`, `:ruby_3`)
1398
- - **Testing**: Use unique names to avoid collisions between tests
1399
- - **Context-specific naming**: Use names that make sense for your application
1400
- <!-- end list -->
1401
- ``` ruby
1468
+ - **Aliasing**: Register the same grammar under multiple names
1469
+ - **Versioning**: Register different grammar versions (e.g., `:ruby_2`, `:ruby_3`)
1470
+ - **Testing**: Use unique names to avoid collisions between tests
1471
+ - **Context-specific naming**: Use names that make sense for your application
1472
+
1473
+ ```ruby
1402
1474
  # Register the same TOML grammar under different names for different purposes
1403
1475
  TreeHaver.register_language(
1404
1476
  :config_parser, # Custom name for your app
@@ -1421,7 +1493,7 @@ versioned_lang = TreeHaver::Language.toml_v1
1421
1493
 
1422
1494
  TreeHaver works with any tree-sitter grammar:
1423
1495
 
1424
- ``` ruby
1496
+ ```ruby
1425
1497
  # Parse Ruby code
1426
1498
  ruby_lang = TreeHaver::Language.from_library(
1427
1499
  "/path/to/libtree-sitter-ruby.so",
@@ -1442,7 +1514,7 @@ tree = parser.parse("const x = 42;")
1442
1514
 
1443
1515
  TreeHaver provides simple node traversal:
1444
1516
 
1445
- ``` ruby
1517
+ ```ruby
1446
1518
  tree = parser.parse(source)
1447
1519
  root = tree.root_node
1448
1520
 
@@ -1459,7 +1531,7 @@ walk_tree(root)
1459
1531
 
1460
1532
  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.
1461
1533
 
1462
- ``` ruby
1534
+ ```ruby
1463
1535
  # Check if current backend supports incremental parsing
1464
1536
  if TreeHaver.capabilities[:incremental]
1465
1537
  puts "Incremental parsing is available!"
@@ -1485,17 +1557,17 @@ tree.edit(
1485
1557
  new_tree = parser.parse_string(tree, "x = 42")
1486
1558
  ```
1487
1559
 
1488
- **Note:** Incremental parsing requires the MRI (`ruby_tree_sitter`), Rust (`tree_stump`), or Java (`java-tree-sitter` / `jtreesitter`) backend. The FFI and Citrus backends do not currently support incremental parsing. You can check support with:
1560
+ **Note:** Incremental parsing requires the MRI (`ruby_tree_sitter`), Rust (`tree_stump`), or Java (`java-tree-sitter` / `jtreesitter`) backend. The FFI, Citrus, and Parslet backends do not currently support incremental parsing. You can check support with:
1489
1561
 
1490
1562
  **Note:** `tree_stump` currently requires unreleased fixes in the `main` branch.
1491
1563
 
1492
- ``` ruby
1564
+ ```ruby
1493
1565
  tree.supports_editing? # => true if edit() is available
1494
1566
  ```
1495
1567
 
1496
1568
  ### Error Handling
1497
1569
 
1498
- ``` ruby
1570
+ ```ruby
1499
1571
  begin
1500
1572
  language = TreeHaver::Language.from_library("/path/to/grammar.so")
1501
1573
  rescue TreeHaver::NotAvailable => e
@@ -1505,7 +1577,7 @@ end
1505
1577
  # Check if a backend is available
1506
1578
  if TreeHaver.backend_module.nil?
1507
1579
  puts "No TreeHaver backend is available!"
1508
- puts "Install ruby_tree_sitter (MRI), ffi gem with libtree-sitter, or citrus gem"
1580
+ puts "Install ruby_tree_sitter (MRI), ffi gem with libtree-sitter, citrus gem, or parslet gem"
1509
1581
  end
1510
1582
  ```
1511
1583
 
@@ -1515,7 +1587,7 @@ end
1515
1587
 
1516
1588
  On MRI, TreeHaver uses `ruby_tree_sitter` by default:
1517
1589
 
1518
- ``` ruby
1590
+ ```ruby
1519
1591
  # Gemfile
1520
1592
  gem "tree_haver"
1521
1593
  gem "ruby_tree_sitter" # MRI backend
@@ -1526,11 +1598,11 @@ parser = TreeHaver::Parser.new
1526
1598
 
1527
1599
  #### JRuby
1528
1600
 
1529
- On JRuby, TreeHaver can use the FFI backend, Java backend, or Citrus backend:
1601
+ On JRuby, TreeHaver can use the FFI backend, Java backend, Citrus backend, or Parslet backend:
1530
1602
 
1531
- **Option 1: FFI Backend (recommended for tree-sitter grammars)**
1603
+ ##### Option 1: FFI Backend (recommended for tree-sitter grammars)
1532
1604
 
1533
- ``` ruby
1605
+ ```ruby
1534
1606
  # Gemfile
1535
1607
  gem "tree_haver"
1536
1608
  gem "ffi" # Required for FFI backend
@@ -1546,9 +1618,9 @@ gem "ffi" # Required for FFI backend
1546
1618
  parser = TreeHaver::Parser.new
1547
1619
  ```
1548
1620
 
1549
- **Option 2: Java Backend (native JVM performance)**
1621
+ ##### Option 2: Java Backend (native JVM performance)
1550
1622
 
1551
- ``` bash
1623
+ ```bash
1552
1624
  # 1. Download java-tree-sitter JAR from Maven Central
1553
1625
  mkdir -p vendor/jars
1554
1626
  curl -fSL -o vendor/jars/jtreesitter-0.23.2.jar \
@@ -1562,7 +1634,7 @@ export LD_LIBRARY_PATH="/path/to/libtree-sitter/lib:$LD_LIBRARY_PATH"
1562
1634
  JAVA_OPTS="--enable-native-access=ALL-UNNAMED" jruby your_script.rb
1563
1635
  ```
1564
1636
 
1565
- ``` ruby
1637
+ ```ruby
1566
1638
  # Force Java backend
1567
1639
  TreeHaver.backend = :java
1568
1640
 
@@ -1582,7 +1654,7 @@ This means grammar `.so` files with unresolved references to `libtree-sitter.so`
1582
1654
 
1583
1655
  **Recommended approach for JRuby:** Use the **FFI backend**:
1584
1656
 
1585
- ``` ruby
1657
+ ```ruby
1586
1658
  # On JRuby, use FFI backend (recommended)
1587
1659
  TreeHaver.backend = :ffi
1588
1660
  ```
@@ -1591,11 +1663,12 @@ The FFI backend uses Ruby's FFI gem which relies on the system's dynamic linker,
1591
1663
 
1592
1664
  The Java backend will work with:
1593
1665
 
1594
- - 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))
1595
- - Grammar `.so` files that statically link tree-sitter
1596
- **Option 3: Citrus Backend (pure Ruby, portable)**
1597
- <!-- end list -->
1598
- ``` ruby
1666
+ - Grammar JARs built specifically for java-tree-sitter / jtreesitter (self-contained, [docs](https://tree-sitter.github.io/java-tree-sitter/), [maven][jtreesitter], [source](https://github.com/tree-sitter/java-tree-sitter))
1667
+ - Grammar `.so` files that statically link tree-sitter
1668
+
1669
+ ##### Option 3: Citrus Backend (pure Ruby, portable)
1670
+
1671
+ ```ruby
1599
1672
  # Gemfile
1600
1673
  gem "tree_haver"
1601
1674
  gem "citrus" # Pure Ruby parser, zero native dependencies
@@ -1618,11 +1691,38 @@ end
1618
1691
  - No query API
1619
1692
  - Pure Ruby performance (slower than native backends)
1620
1693
  - Best for: prototyping, environments without native extension support, teaching
1694
+
1695
+ ##### Option 4: Parslet Backend (pure Ruby, portable)
1696
+
1697
+ ```ruby
1698
+ # Gemfile
1699
+ gem "tree_haver"
1700
+ gem "parslet" # Pure Ruby parser, zero native dependencies
1701
+
1702
+ # Code - Force Parslet backend for maximum portability
1703
+ TreeHaver.backend = :parslet
1704
+
1705
+ # Check if Parslet backend is available
1706
+ if TreeHaver::Backends::Parslet.available?
1707
+ puts "Parslet backend is ready!"
1708
+ puts TreeHaver.capabilities
1709
+ # => { backend: :parslet, parse: true, query: false, bytes_field: false }
1710
+ end
1711
+ ```
1712
+
1713
+ **⚠️ Parslet Backend Limitations:**
1714
+
1715
+ - Uses Parslet grammars (not tree-sitter grammars)
1716
+ - No incremental parsing support
1717
+ - No query API
1718
+ - Pure Ruby performance (slower than native backends)
1719
+ - Best for: prototyping, environments without native extension support, teaching
1720
+
1621
1721
  #### TruffleRuby
1622
1722
 
1623
- TruffleRuby can use the MRI, FFI, or Citrus backend:
1723
+ TruffleRuby can use the MRI, FFI, Citrus, or Parslet backend:
1624
1724
 
1625
- ``` ruby
1725
+ ```ruby
1626
1726
  # Use FFI backend (recommended for tree-sitter grammars)
1627
1727
  TreeHaver.backend = :ffi
1628
1728
 
@@ -1631,6 +1731,9 @@ TreeHaver.backend = :mri
1631
1731
 
1632
1732
  # Or use Citrus backend for zero native dependencies
1633
1733
  TreeHaver.backend = :citrus
1734
+
1735
+ # Or use Parslet backend for zero native dependencies
1736
+ TreeHaver.backend = :parslet
1634
1737
  ```
1635
1738
 
1636
1739
  ### Advanced: Thread-Safe Backend Switching
@@ -1643,11 +1746,11 @@ different contexts.
1643
1746
 
1644
1747
  Test the same code path with different backends using `with_backend`:
1645
1748
 
1646
- ``` ruby
1749
+ ```ruby
1647
1750
  # In your test setup
1648
1751
  RSpec.describe("MyParser") do
1649
1752
  # Test with each available backend
1650
- [:mri, :rust, :citrus].each do |backend_name|
1753
+ [:mri, :rust, :citrus, :parslet].each do |backend_name|
1651
1754
  context "with #{backend_name} backend" do
1652
1755
  it "parses correctly" do
1653
1756
  TreeHaver.with_backend(backend_name) do
@@ -1666,7 +1769,7 @@ end
1666
1769
 
1667
1770
  Each thread can use a different backend safely—`with_backend` uses thread-local storage:
1668
1771
 
1669
- ``` ruby
1772
+ ```ruby
1670
1773
  threads = []
1671
1774
 
1672
1775
  threads << Thread.new do
@@ -1685,6 +1788,14 @@ threads << Thread.new do
1685
1788
  end
1686
1789
  end
1687
1790
 
1791
+ threads << Thread.new do
1792
+ TreeHaver.with_backend(:parslet) do
1793
+ # This thread uses Parslet backend simultaneously
1794
+ parser = TreeHaver::Parser.new
1795
+ 100.times { parser.parse("x = 1") }
1796
+ end
1797
+ end
1798
+
1688
1799
  threads.each(&:join)
1689
1800
  ```
1690
1801
 
@@ -1692,7 +1803,7 @@ threads.each(&:join)
1692
1803
 
1693
1804
  `with_backend` supports nesting—inner blocks override outer blocks:
1694
1805
 
1695
- ``` ruby
1806
+ ```ruby
1696
1807
  TreeHaver.with_backend(:rust) do
1697
1808
  puts TreeHaver.effective_backend # => :rust
1698
1809
 
@@ -1700,6 +1811,10 @@ TreeHaver.with_backend(:rust) do
1700
1811
  puts TreeHaver.effective_backend # => :citrus
1701
1812
  end
1702
1813
 
1814
+ TreeHaver.with_backend(:parslet) do
1815
+ puts TreeHaver.effective_backend # => :parslet
1816
+ end
1817
+
1703
1818
  puts TreeHaver.effective_backend # => :rust (restored)
1704
1819
  end
1705
1820
  ```
@@ -1708,7 +1823,7 @@ end
1708
1823
 
1709
1824
  Try one backend, fall back to another on failure:
1710
1825
 
1711
- ``` ruby
1826
+ ```ruby
1712
1827
  def parse_with_fallback(source)
1713
1828
  TreeHaver.with_backend(:mri) do
1714
1829
  TreeHaver::Parser.new.tap { |p| p.language = load_language }.parse(source)
@@ -1718,6 +1833,11 @@ rescue TreeHaver::NotAvailable
1718
1833
  TreeHaver.with_backend(:citrus) do
1719
1834
  TreeHaver::Parser.new.tap { |p| p.language = load_language }.parse(source)
1720
1835
  end
1836
+ rescue TreeHaver::NotAvailable
1837
+ # Fall back to Parslet if Citrus backend unavailable
1838
+ TreeHaver.with_backend(:parslet) do
1839
+ TreeHaver::Parser.new.tap { |p| p.language = load_language }.parse(source)
1840
+ end
1721
1841
  end
1722
1842
  ```
1723
1843
 
@@ -1725,7 +1845,7 @@ end
1725
1845
 
1726
1846
  Here's a practical example that extracts package names from a TOML file:
1727
1847
 
1728
- ``` ruby
1848
+ ```ruby
1729
1849
  require "tree_haver"
1730
1850
 
1731
1851
  # Setup
@@ -1772,14 +1892,14 @@ package_name = extract_package_name(toml)
1772
1892
 
1773
1893
  TreeHaver provides shared RSpec helpers for conditional test execution based on dependency availability. This is useful for testing code that uses optional backends.
1774
1894
 
1775
- ``` ruby
1895
+ ```ruby
1776
1896
  # In your spec_helper.rb
1777
1897
  require "tree_haver/rspec"
1778
1898
  ```
1779
1899
 
1780
1900
  This automatically configures RSpec with exclusion filters for all TreeHaver dependencies. Use tags to conditionally run tests:
1781
1901
 
1782
- ``` ruby
1902
+ ```ruby
1783
1903
  # Runs only when FFI backend is available
1784
1904
  it "parses with FFI", :ffi do
1785
1905
  # ...
@@ -1803,38 +1923,58 @@ end
1803
1923
 
1804
1924
  **Available Tags:**
1805
1925
 
1806
- | Tag | Description |
1807
- | --- | --- |
1808
- | `:ffi` | FFI backend available (dynamic check) |
1809
- | `:mri_backend` | ruby\_tree\_sitter gem available |
1810
- | `:rust_backend` | tree\_stump gem available |
1811
- | `:java_backend` | Java backend available (JRuby) |
1812
- | `:prism_backend` | Prism gem available |
1813
- | `:psych_backend` | Psych available (stdlib) |
1814
- | `:commonmarker` | commonmarker gem available |
1815
- | `:markly` | markly gem available |
1816
- | `:citrus_toml` | toml-rb with Citrus grammar available |
1817
- | `:jruby` | Running on JRuby |
1818
- | `:truffleruby` | Running on TruffleRuby |
1819
- | `:mri` | Running on MRI (CRuby) |
1820
- | `:tree_sitter_bash` | Bash grammar available and working |
1821
- | `:tree_sitter_toml` | TOML grammar available and working |
1822
- | `:tree_sitter_json` | JSON grammar available and working |
1823
- | `:tree_sitter_jsonc` | JSONC grammar available and working |
1824
- | `:toml_backend` | Any TOML backend available |
1825
- | `:markdown_backend` | Any markdown backend available |
1826
- | `:toml_merge` | toml-merge gem functional |
1827
- | `:json_merge` | json-merge gem functional |
1828
- | `:prism_merge` | prism-merge gem functional |
1829
- | `:psych_merge` | psych-merge gem functional |
1830
-
1831
- All tags have negated versions (e.g., `:not_mri_backend`, `:not_jruby`) for testing fallback behavior.
1926
+ Tags follow a naming convention:
1927
+
1928
+ - `*_backend` = TreeHaver backends (mri, rust, ffi, java, prism, psych, commonmarker, markly, citrus, parslet, rbs)
1929
+ - `*_engine` = Ruby engines (mri, jruby, truffleruby)
1930
+ - `*_grammar` = tree-sitter grammar files (.so)
1931
+ - `*_parsing` = any parsing capability for a language (combines multiple backends/grammars)
1932
+ - `*_gem` = specific library gems
1933
+
1934
+ | Tag | Description |
1935
+ |-------------------------|---------------------------------------------------------------------------|
1936
+ | **Backend Tags** | |
1937
+ | `:ffi_backend` | FFI backend available (dynamic check, legacy alias: `:ffi`) |
1938
+ | `:ffi_backend_only` | FFI backend in isolation (won't trigger MRI check) |
1939
+ | `:mri_backend` | ruby\_tree\_sitter gem available |
1940
+ | `:mri_backend_only` | MRI backend in isolation (won't trigger FFI check) |
1941
+ | `:rust_backend` | tree\_stump gem available |
1942
+ | `:java_backend` | Java backend available (JRuby + jtreesitter) |
1943
+ | `:prism_backend` | Prism gem available |
1944
+ | `:psych_backend` | Psych available (stdlib) |
1945
+ | `:commonmarker_backend` | commonmarker gem available |
1946
+ | `:markly_backend` | markly gem available |
1947
+ | `:citrus_backend` | Citrus gem available |
1948
+ | `:parslet_backend` | Parslet gem available |
1949
+ | `:rbs_backend` | RBS gem available (official RBS parser, MRI only) |
1950
+ | **Engine Tags** | |
1951
+ | `:mri_engine` | Running on MRI (CRuby) |
1952
+ | `:jruby_engine` | Running on JRuby |
1953
+ | `:truffleruby_engine` | Running on TruffleRuby |
1954
+ | **Grammar Tags** | |
1955
+ | `:libtree_sitter` | libtree-sitter.so is loadable via FFI |
1956
+ | `:bash_grammar` | tree-sitter-bash grammar available and parsing works |
1957
+ | `:toml_grammar` | tree-sitter-toml grammar available and parsing works |
1958
+ | `:json_grammar` | tree-sitter-json grammar available and parsing works |
1959
+ | `:jsonc_grammar` | tree-sitter-jsonc grammar available and parsing works |
1960
+ | `:rbs_grammar` | tree-sitter-rbs grammar available and parsing works |
1961
+ | **Parsing Tags** | |
1962
+ | `:toml_parsing` | Any TOML parser available (tree-sitter OR toml-rb/Citrus OR toml/Parslet) |
1963
+ | `:markdown_parsing` | Any markdown parser available (commonmarker OR markly) |
1964
+ | `:rbs_parsing` | Any RBS parser available (rbs gem OR tree-sitter-rbs) |
1965
+ | `:native_parsing` | Native tree-sitter backend and grammar available |
1966
+ | **Library Tags** | |
1967
+ | `:toml_rb_gem` | toml-rb gem available (Citrus backend for TOML) |
1968
+ | `:toml_gem` | toml gem available (Parslet backend for TOML) |
1969
+ | `:rbs_gem` | rbs gem available (official RBS parser) |
1970
+
1971
+ All tags have negated versions (e.g., `:not_mri_backend`, `:not_jruby_engine`, `:not_toml_parsing`) for testing fallback behavior.
1832
1972
 
1833
1973
  **Debug Output:**
1834
1974
 
1835
1975
  Set `TREE_HAVER_DEBUG=1` to print a dependency summary at the start of your test suite:
1836
1976
 
1837
- ``` bash
1977
+ ```bash
1838
1978
  TREE_HAVER_DEBUG=1 bundle exec rspec
1839
1979
  ```
1840
1980
 
@@ -1845,27 +1985,27 @@ Raising a monthly budget of... "dollars" would make the project more sustainable
1845
1985
 
1846
1986
  We welcome both individual and corporate sponsors\! We also offer a
1847
1987
  wide array of funding channels to account for your preferences
1848
- (although currently [Open Collective](https://opencollective.com/kettle-rb) is our preferred funding platform).
1988
+ (although currently [Open Collective][🖇osc] is our preferred funding platform).
1849
1989
 
1850
1990
  **If you're working in a company that's making significant use of kettle-rb tools we'd
1851
1991
  appreciate it if you suggest to your company to become a kettle-rb sponsor.**
1852
1992
 
1853
1993
  You can support the development of kettle-rb tools via
1854
- [GitHub Sponsors](https://github.com/sponsors/pboling),
1855
- [Liberapay](https://liberapay.com/pboling/donate),
1856
- [PayPal](https://www.paypal.com/paypalme/peterboling),
1857
- [Open Collective](https://opencollective.com/kettle-rb)
1858
- and [Tidelift](https://tidelift.com/subscription/pkg/rubygems-tree_haver?utm_source=rubygems-tree_haver&utm_medium=referral&utm_campaign=readme).
1859
-
1860
- | 📍 NOTE |
1861
- | --- |
1994
+ [GitHub Sponsors][🖇sponsor],
1995
+ [Liberapay][⛳liberapay],
1996
+ [PayPal][🖇paypal],
1997
+ [Open Collective][🖇osc]
1998
+ and [Tidelift][🏙️entsup-tidelift].
1999
+
2000
+ | 📍 NOTE |
2001
+ |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
1862
2002
  | 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. |
1863
2003
 
1864
2004
  ### Open Collective for Individuals
1865
2005
 
1866
- Support us with a monthly donation and help us continue our activities. \[[Become a backer](https://opencollective.com/kettle-rb#backer)\]
2006
+ Support us with a monthly donation and help us continue our activities. \[[Become a backer][🖇osc-backers]\]
1867
2007
 
1868
- NOTE: [kettle-readme-backers](https://github.com/kettle-rb/tree_haver/blob/main/exe/kettle-readme-backers) updates this list every day, automatically.
2008
+ NOTE: [kettle-readme-backers][kettle-readme-backers] updates this list every day, automatically.
1869
2009
 
1870
2010
  <!-- OPENCOLLECTIVE-INDIVIDUALS:START -->
1871
2011
  No backers yet. Be the first\!
@@ -1873,9 +2013,9 @@ No backers yet. Be the first\!
1873
2013
 
1874
2014
  ### Open Collective for Organizations
1875
2015
 
1876
- 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)\]
2016
+ Become a sponsor and get your logo on our README on GitHub with a link to your site. \[[Become a sponsor][🖇osc-sponsors]\]
1877
2017
 
1878
- NOTE: [kettle-readme-backers](https://github.com/kettle-rb/tree_haver/blob/main/exe/kettle-readme-backers) updates this list every day, automatically.
2018
+ NOTE: [kettle-readme-backers][kettle-readme-backers] updates this list every day, automatically.
1879
2019
 
1880
2020
  <!-- OPENCOLLECTIVE-ORGANIZATIONS:START -->
1881
2021
  No sponsors yet. Be the first\!
@@ -1889,48 +2029,48 @@ I’m driven by a passion to foster a thriving open-source community – a space
1889
2029
 
1890
2030
  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`.
1891
2031
 
1892
- 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.
2032
+ 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.
1893
2033
 
1894
- **[Floss-Funding.dev](https://floss-funding.dev): 👉️ No network calls. 👉️ No tracking. 👉️ No oversight. 👉️ Minimal crypto hashing. 💡 Easily disabled nags**
2034
+ **[Floss-Funding.dev][🖇floss-funding.dev]: 👉️ No network calls. 👉️ No tracking. 👉️ No oversight. 👉️ Minimal crypto hashing. 💡 Easily disabled nags**
1895
2035
 
1896
- [![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)
2036
+ [![OpenCollective Backers][🖇osc-backers-i]](https://opencollective.com/kettle-rb#backer) [![OpenCollective Sponsors][🖇osc-sponsors-i]](https://opencollective.com/kettle-rb#sponsor) [![Sponsor Me on Github][🖇sponsor-img]](https://github.com/sponsors/pboling) [![Liberapay Goal Progress][⛳liberapay-img]](https://liberapay.com/pboling/donate) [![Donate on PayPal][🖇paypal-img]](https://www.paypal.com/paypalme/peterboling) [![Buy me a coffee][🖇buyme-small-img]](https://www.buymeacoffee.com/pboling) [![Donate on Polar][🖇polar-img]](https://polar.sh/pboling) [![Donate to my FLOSS efforts at ko-fi.com][🖇kofi-img]](https://ko-fi.com/O5O86SNP4) [![Donate to my FLOSS efforts using Patreon][🖇patreon-img]](https://patreon.com/galtzo)
1897
2037
 
1898
2038
  ## 🔐 Security
1899
2039
 
1900
- See [SECURITY.md](SECURITY.md).
2040
+ See [SECURITY.md][🔐security].
1901
2041
 
1902
2042
  ## 🤝 Contributing
1903
2043
 
1904
2044
  If you need some ideas of where to help, you could work on adding more code coverage,
1905
- 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),
2045
+ or if it is already 💯 (see [below](#code-coverage)) check [reek](REEK), [issues][🤝gh-issues], or [PRs][🤝gh-pulls],
1906
2046
  or use the gem and think about how it could be better.
1907
2047
 
1908
- 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.
2048
+ We [![Keep A Changelog][📗keep-changelog-img]](https://keepachangelog.com/en/1.0.0/) so if you make changes, remember to update it.
1909
2049
 
1910
- See [CONTRIBUTING.md](CONTRIBUTING.md) for more detailed instructions.
2050
+ See [CONTRIBUTING.md][🤝contributing] for more detailed instructions.
1911
2051
 
1912
2052
  ### 🚀 Release Instructions
1913
2053
 
1914
- See [CONTRIBUTING.md](CONTRIBUTING.md).
2054
+ See [CONTRIBUTING.md][🤝contributing].
1915
2055
 
1916
2056
  ### Code Coverage
1917
2057
 
1918
- [![Coverage Graph](https://codecov.io/gh/kettle-rb/tree_haver/graphs/tree.svg)](https://codecov.io/gh/kettle-rb/tree_haver)
2058
+ [![Coverage Graph][🏀codecov-g]](https://codecov.io/gh/kettle-rb/tree_haver)
1919
2059
 
1920
- [![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)
2060
+ [![Coveralls Test Coverage][🏀coveralls-img]](https://coveralls.io/github/kettle-rb/tree_haver?branch=main)
1921
2061
 
1922
- [![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)
2062
+ [![QLTY Test Coverage][🏀qlty-covi]](https://qlty.sh/gh/kettle-rb/projects/tree_haver/metrics/code?sort=coverageRating)
1923
2063
 
1924
2064
  ### 🪇 Code of Conduct
1925
2065
 
1926
2066
  Everyone interacting with this project's codebases, issue trackers,
1927
- 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).
2067
+ chat rooms and mailing lists agrees to follow the [![Contributor Covenant 2.1][🪇conduct-img]](CODE_OF_CONDUCT.md).
1928
2068
 
1929
2069
  ## 🌈 Contributors
1930
2070
 
1931
- [![Contributors](https://contrib.rocks/image?repo=kettle-rb/tree_haver)](https://github.com/kettle-rb/tree_haver/graphs/contributors)
2071
+ [![Contributors][🖐contributors-img]](https://github.com/kettle-rb/tree_haver/graphs/contributors)
1932
2072
 
1933
- Made with [contributors-img](https://contrib.rocks).
2073
+ Made with [contributors-img][🖐contrib-rocks].
1934
2074
 
1935
2075
  Also see GitLab Contributors: <https://gitlab.com/kettle-rb/tree_haver/-/graphs/main>
1936
2076
 
@@ -1949,24 +2089,24 @@ Also see GitLab Contributors: <https://gitlab.com/kettle-rb/tree_haver/-/graphs/
1949
2089
 
1950
2090
  ## 📌 Versioning
1951
2091
 
1952
- 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).
2092
+ This Library adheres to [![Semantic Versioning 2.0.0][📌semver-img]](https://semver.org/spec/v2.0.0.html).
1953
2093
  Violations of this scheme should be reported as bugs.
1954
2094
  Specifically, if a minor or patch version is released that breaks backward compatibility,
1955
2095
  a new version should be immediately released that restores compatibility.
1956
2096
  Breaking changes to the public API will only be introduced with new major versions.
1957
2097
 
1958
2098
  > dropping support for a platform is both obviously and objectively a breaking change <br/>
1959
- > —Jordan Harband ([@ljharb](https://github.com/ljharb), maintainer of SemVer) [in SemVer issue 716](https://github.com/semver/semver/issues/716#issuecomment-869336139)
2099
+ > —Jordan Harband ([@ljharb](https://github.com/ljharb), maintainer of SemVer) [in SemVer issue 716][📌semver-breaking]
1960
2100
 
1961
2101
  I understand that policy doesn't work universally ("exceptions to every rule\!"),
1962
2102
  but it is the policy here.
1963
2103
  As such, in many cases it is good to specify a dependency on this library using
1964
- the [Pessimistic Version Constraint](http://guides.rubygems.org/patterns/#pessimistic-version-constraint) with two digits of precision.
2104
+ the [Pessimistic Version Constraint][📌pvc] with two digits of precision.
1965
2105
 
1966
2106
  For example:
1967
2107
 
1968
- ``` ruby
1969
- spec.add_dependency("tree_haver", "~> 1.0")
2108
+ ```ruby
2109
+ spec.add_dependency("tree_haver", "~> 5.0")
1970
2110
  ```
1971
2111
 
1972
2112
  <details markdown="1">
@@ -1978,16 +2118,17 @@ is a *breaking change* to an API, and for that reason the bike shedding is endle
1978
2118
  To get a better understanding of how SemVer is intended to work over a project's lifetime,
1979
2119
  read this article from the creator of SemVer:
1980
2120
 
1981
- - ["Major Version Numbers are Not Sacred"](https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html)
2121
+ - ["Major Version Numbers are Not Sacred"][📌major-versions-not-sacred]
2122
+
1982
2123
  </details>
1983
2124
 
1984
- See [CHANGELOG.md](CHANGELOG.md) for a list of releases.
2125
+ See [CHANGELOG.md][📌changelog] for a list of releases.
1985
2126
 
1986
2127
  ## 📄 License
1987
2128
 
1988
2129
  The gem is available as open source under the terms of
1989
- the [MIT License](LICENSE.txt) [![License: MIT](https://img.shields.io/badge/License-MIT-259D6C.svg)](https://opensource.org/licenses/MIT).
1990
- 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).
2130
+ the [MIT License][📄license] [![License: MIT][📄license-img]](https://opensource.org/licenses/MIT).
2131
+ See [LICENSE.txt][📄license] for the official [Copyright Notice][📄copyright-notice-explainer].
1991
2132
 
1992
2133
  ### © Copyright
1993
2134
 
@@ -2014,11 +2155,11 @@ Please consider sponsoring me or the project.
2014
2155
 
2015
2156
  To join the community or get help 👇️ Join the Discord.
2016
2157
 
2017
- [![Live Chat on Discord](https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord)](https://discord.gg/3qme4XHNKN)
2158
+ [![Live Chat on Discord][✉️discord-invite-img-ftb]](https://discord.gg/3qme4XHNKN)
2018
2159
 
2019
2160
  To say "thanks\!" ☝️ Join the Discord or 👇️ send money.
2020
2161
 
2021
- [![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)
2162
+ [![Sponsor kettle-rb/tree\_haver on Open Source Collective][🖇osc-all-bottom-img]](https://opencollective.com/kettle-rb) 💌 [![Sponsor me on GitHub Sponsors][🖇sponsor-bottom-img]](https://github.com/sponsors/pboling) 💌 [![Sponsor me on Liberapay][⛳liberapay-bottom-img]](https://liberapay.com/pboling/donate) 💌 [![Donate on PayPal][🖇paypal-bottom-img]](https://www.paypal.com/paypalme/peterboling)
2022
2163
 
2023
2164
  ### Please give the project a star ⭐ ♥.
2024
2165
 
@@ -2182,7 +2323,7 @@ Thanks for RTFM. ☺️
2182
2323
  [📌gitmoji]: https://gitmoji.dev
2183
2324
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
2184
2325
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
2185
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-2.201-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
2326
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-2.487-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
2186
2327
  [🔐security]: SECURITY.md
2187
2328
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
2188
2329
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year