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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +144 -1
- data/README.md +497 -356
- data/lib/tree_haver/backends/citrus.rb +98 -114
- data/lib/tree_haver/backends/ffi.rb +76 -13
- data/lib/tree_haver/backends/java.rb +99 -14
- data/lib/tree_haver/backends/mri.rb +25 -1
- data/lib/tree_haver/backends/parslet.rb +560 -0
- data/lib/tree_haver/backends/prism.rb +1 -1
- data/lib/tree_haver/backends/psych.rb +1 -1
- data/lib/tree_haver/backends/rust.rb +1 -1
- data/lib/tree_haver/base/node.rb +8 -1
- data/lib/tree_haver/language.rb +44 -13
- data/lib/tree_haver/parser.rb +128 -38
- data/lib/tree_haver/parslet_grammar_finder.rb +224 -0
- data/lib/tree_haver/point.rb +6 -44
- data/lib/tree_haver/rspec/dependency_tags.rb +40 -1
- data/lib/tree_haver/version.rb +1 -1
- data/lib/tree_haver.rb +100 -13
- data.tar.gz.sig +0 -0
- metadata +15 -14
- metadata.gz.sig +0 -0
data/README.md
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
| 📍 NOTE
|
|
2
|
-
|
|
3
|
-
| RubyGems (the [GitHub org]
|
|
4
|
-
| Ultimately [4 maintainers]
|
|
5
|
-
| It is a [complicated story]
|
|
6
|
-
| Simply put - there was active policy for adding or removing maintainers/owners of [rubygems]
|
|
7
|
-
| I'm adding notes like this to gems because I [don't condone theft]
|
|
8
|
-
| If a similar theft happened with my repos/gems, I'd hope some would stand up for me.
|
|
9
|
-
| Disenfranchised former-maintainers have started [gem.coop]
|
|
10
|
-
| Once available I will publish there exclusively; unless RubyCentral makes amends with the community.
|
|
11
|
-
| The ["Technology for Humans: Joel Draper"]
|
|
12
|
-
| See [here]
|
|
13
|
-
| What I'm doing: A (WIP) proposal for [bundler/gem scopes]
|
|
1
|
+
| 📍 NOTE |
|
|
2
|
+
|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
3
|
+
| RubyGems (the [GitHub org][rubygems-org], not the website) [suffered][draper-security] a [hostile takeover][ellen-takeover] in September 2025. |
|
|
4
|
+
| Ultimately [4 maintainers][simi-removed] were [hard removed][martin-removed] and a reason has been given for only 1 of those, while 2 others resigned in protest. |
|
|
5
|
+
| It is a [complicated story][draper-takeover] which is difficult to [parse quickly][draper-lies]. |
|
|
6
|
+
| Simply put - there was active policy for adding or removing maintainers/owners of [rubygems][rubygems-maint-policy] and [bundler][bundler-maint-policy], and those [policies were not followed][policy-fail]. |
|
|
7
|
+
| I'm adding notes like this to gems because I [don't condone theft][draper-theft] of repositories or gems from their rightful owners. |
|
|
8
|
+
| If a similar theft happened with my repos/gems, I'd hope some would stand up for me. |
|
|
9
|
+
| Disenfranchised former-maintainers have started [gem.coop][gem-coop]. |
|
|
10
|
+
| Once available I will publish there exclusively; unless RubyCentral makes amends with the community. |
|
|
11
|
+
| The ["Technology for Humans: Joel Draper"][reinteractive-podcast] podcast episode by [reinteractive][reinteractive] is the most cogent summary I'm aware of. |
|
|
12
|
+
| See [here][gem-naming], [here][gem-coop] and [here][martin-ann] for more info on what comes next. |
|
|
13
|
+
| What I'm doing: A (WIP) proposal for [bundler/gem scopes][gem-scopes], and a (WIP) proposal for a federated [gem server][gem-server]. |
|
|
14
14
|
|
|
15
15
|
[rubygems-org]: https://github.com/rubygems/
|
|
16
16
|
[draper-security]: https://joel.drapper.me/p/ruby-central-security-measures/
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
[rubygems-maint-policy]: https://github.com/ruby/rubygems/blob/b1ab33a3d52310a84d16b193991af07f5a6a07c0/doc/rubygems/POLICIES.md?plain=1#L187-L196
|
|
32
32
|
[policy-fail]: https://www.reddit.com/r/ruby/comments/1ove9vp/rubycentral_hates_this_one_fact/
|
|
33
33
|
|
|
34
|
-
[![Galtzo FLOSS Logo by Aboling0, CC BY-SA 4.0]
|
|
34
|
+
[![Galtzo FLOSS Logo by Aboling0, CC BY-SA 4.0][🖼️galtzo-i]](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]
|
|
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]
|
|
47
|
+
`if ci_badges.map(&:color).detect { it != "green"}` ☝️ [let me know][🖼️galtzo-discord], as I may have missed the [discord notification][🖼️galtzo-discord].
|
|
48
48
|
|
|
49
49
|
-----
|
|
50
|
+
|
|
50
51
|
`if ci_badges.map(&:color).all? { it == "green"}` 👇️ send money so I can do more of this. FLOSS maintenance is now my full-time job.
|
|
51
52
|
|
|
52
|
-
[![OpenCollective Backers]
|
|
53
|
+
[![OpenCollective Backers][🖇osc-backers-i]](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/)
|
|
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
|
|
63
|
-
|
|
64
|
-
| **Faraday**
|
|
65
|
-
| **multi\_json** | JSON parsing
|
|
66
|
-
| **multi\_xml**
|
|
67
|
-
| **TreeHaver**
|
|
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
|
-
```
|
|
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`]
|
|
94
|
-
- **Rust Backend**: Uses [`tree_stump`]
|
|
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`]
|
|
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]
|
|
100
|
-
- **Psych Backend**: Ruby's YAML parser ([Psych]
|
|
101
|
-
- **Commonmarker Backend**: Fast Markdown parser ([Commonmarker]
|
|
102
|
-
- **Markly Backend**: GitHub Flavored Markdown ([Markly]
|
|
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`]
|
|
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
|
|
124
|
-
|
|
125
|
-
| `TreeSitter::ParserNotFoundError` | `TreeHaver::NotAvailable` | Parser library file cannot be loaded
|
|
126
|
-
| `TreeSitter::LanguageLoadError`
|
|
127
|
-
| `TreeSitter::SymbolNotFoundError` | `TreeHaver::NotAvailable` | Symbol not found in library
|
|
128
|
-
| `TreeSitter::ParserVersionError`
|
|
129
|
-
| `TreeSitter::QueryCreationError`
|
|
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
|
-
```
|
|
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]
|
|
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
|
-
|
|
146
|
-
```
|
|
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
|
-
|
|
159
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
|
|
252
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
|
293
|
-
| JSON
|
|
294
|
-
| JSONC
|
|
295
|
-
| 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]
|
|
313
|
-
[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
|
|
329
|
-
|
|
330
|
-
| [tree_haver][tree_haver] | Multi
|
|
331
|
-
| [ast-merge][ast-merge] | Text
|
|
332
|
-
| [bash-merge][bash-merge] |
|
|
333
|
-
| [commonmarker-merge][commonmarker-merge] | Markdown
|
|
334
|
-
| [dotenv-merge][dotenv-merge] | Dotenv
|
|
335
|
-
| [json-merge][json-merge] |
|
|
336
|
-
| [jsonc-merge][jsonc-merge] | JSONC
|
|
337
|
-
| [markdown-merge][markdown-merge] | Markdown
|
|
338
|
-
| [markly-merge][markly-merge] |
|
|
339
|
-
| [prism-merge][prism-merge] | Ruby
|
|
340
|
-
| [psych-merge][psych-merge] | YAML
|
|
341
|
-
| [rbs-merge][rbs-merge] | RBS
|
|
342
|
-
| [toml-merge][toml-merge] |
|
|
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
|
|
409
|
-
|
|
410
|
-
| **MRI Ruby** | ✅ Yes
|
|
411
|
-
| **JRuby**
|
|
412
|
-
| **TruffleRuby**
|
|
413
|
-
| **Backend**
|
|
414
|
-
| **Incremental Parsing**
|
|
415
|
-
| **Query API**
|
|
416
|
-
| **Grammar Discovery**
|
|
417
|
-
| **Security Validations**
|
|
418
|
-
| **Language Registration** | ✅ Thread-safe registry | ❌ No
|
|
419
|
-
| **Native Performance**
|
|
420
|
-
| **Precompiled Binaries**
|
|
421
|
-
| **Zero Native Deps**
|
|
422
|
-
| **Minimum Ruby**
|
|
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]
|
|
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
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
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
|
-
|
|
504
|
+
**Choose ruby\_tree\_sitter directly when:**
|
|
455
505
|
|
|
456
|
-
|
|
457
|
-
|
|
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
|
-
|
|
511
|
+
**Choose tree\_stump directly when:**
|
|
460
512
|
|
|
461
|
-
|
|
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
|
-
|
|
519
|
+
**Choose citrus or parslet directly when:**
|
|
464
520
|
|
|
465
|
-
|
|
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
|
|
480
|
-
|
|
481
|
-
| Works with JRuby
|
|
482
|
-
| Works with Truffle Ruby | [![Truffle Ruby 23.1 Compat]
|
|
483
|
-
| Works with MRI Ruby 3
|
|
484
|
-
| Support & Community
|
|
485
|
-
| Source
|
|
486
|
-
| Documentation
|
|
487
|
-
| Compliance
|
|
488
|
-
| Style
|
|
489
|
-
| Maintainer 🎖️
|
|
490
|
-
| `...` 💖
|
|
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) [](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\!
|
|
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]
|
|
506
|
-
|
|
507
|
-
| 🧪 [kettle-rb/tree\_haver on GitLab]
|
|
508
|
-
| 🧊 [kettle-rb/tree\_haver on CodeBerg]
|
|
509
|
-
| 🐙 [kettle-rb/tree\_haver on GitHub]
|
|
510
|
-
| 🎮️ [Discord Server]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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
|
|
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
|
-
- [](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
|
-
```
|
|
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
|
-
```
|
|
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]
|
|
561
|
-
[stone\_checksums]
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
|
599
|
-
|
|
600
|
-
| **Auto** | Auto-selects best backend
|
|
601
|
-
| **MRI**
|
|
602
|
-
| **Rust** | Precompiled via tree\_stump
|
|
603
|
-
| **FFI**
|
|
604
|
-
| **Java** | JNI bindings (jtreesitter \>= 0.26.0) | ⚡ Very Fast | JRuby only
|
|
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
|
|
609
|
-
|
|
610
|
-
| **Prism**
|
|
611
|
-
| **Psych**
|
|
612
|
-
| **Commonmarker** | Markdown via comrak (Rust)
|
|
613
|
-
| **Markly**
|
|
614
|
-
| **Citrus**
|
|
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
|
-
|
|
623
|
-
```
|
|
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
|
|
688
|
+
# Pure Ruby fallbacks
|
|
637
689
|
gem "citrus", "~> 3.0" # Citrus backend
|
|
638
|
-
|
|
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
|
-
```
|
|
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
|
|
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
|
-
```
|
|
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
|
-
|
|
697
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
|
|
768
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
|
|
921
|
-
```
|
|
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
|
-
```
|
|
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
|
-
|
|
942
|
-
```
|
|
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
|
-
```
|
|
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]
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
|
1015
|
-
|
|
1016
|
-
| ENV var
|
|
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
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
|
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
|
-
```
|
|
1564
|
+
```ruby
|
|
1493
1565
|
tree.supports_editing? # => true if edit() is available
|
|
1494
1566
|
```
|
|
1495
1567
|
|
|
1496
1568
|
### Error Handling
|
|
1497
1569
|
|
|
1498
|
-
```
|
|
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
|
|
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
|
-
```
|
|
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
|
|
1601
|
+
On JRuby, TreeHaver can use the FFI backend, Java backend, Citrus backend, or Parslet backend:
|
|
1530
1602
|
|
|
1531
|
-
|
|
1603
|
+
##### Option 1: FFI Backend (recommended for tree-sitter grammars)
|
|
1532
1604
|
|
|
1533
|
-
```
|
|
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
|
-
|
|
1621
|
+
##### Option 2: Java Backend (native JVM performance)
|
|
1550
1622
|
|
|
1551
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
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
|
|
1723
|
+
TruffleRuby can use the MRI, FFI, Citrus, or Parslet backend:
|
|
1624
1724
|
|
|
1625
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
|
1815
|
-
|
|
1816
|
-
|
|
|
1817
|
-
| `:
|
|
1818
|
-
| `:
|
|
1819
|
-
| `:
|
|
1820
|
-
| `:
|
|
1821
|
-
| `:
|
|
1822
|
-
| `:
|
|
1823
|
-
| `:
|
|
1824
|
-
| `:
|
|
1825
|
-
| `:
|
|
1826
|
-
| `:
|
|
1827
|
-
| `:
|
|
1828
|
-
| `:
|
|
1829
|
-
| `:
|
|
1830
|
-
|
|
1831
|
-
|
|
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
|
-
```
|
|
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]
|
|
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]
|
|
1855
|
-
[Liberapay]
|
|
1856
|
-
[PayPal]
|
|
1857
|
-
[Open Collective]
|
|
1858
|
-
and [Tidelift]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
2050
|
+
See [CONTRIBUTING.md][🤝contributing] for more detailed instructions.
|
|
1911
2051
|
|
|
1912
2052
|
### 🚀 Release Instructions
|
|
1913
2053
|
|
|
1914
|
-
See [CONTRIBUTING.md]
|
|
2054
|
+
See [CONTRIBUTING.md][🤝contributing].
|
|
1915
2055
|
|
|
1916
2056
|
### Code Coverage
|
|
1917
2057
|
|
|
1918
|
-
[![Coverage Graph]
|
|
2058
|
+
[![Coverage Graph][🏀codecov-g]](https://codecov.io/gh/kettle-rb/tree_haver)
|
|
1919
2059
|
|
|
1920
|
-
[![Coveralls Test Coverage]
|
|
2060
|
+
[![Coveralls Test Coverage][🏀coveralls-img]](https://coveralls.io/github/kettle-rb/tree_haver?branch=main)
|
|
1921
2061
|
|
|
1922
|
-
[![QLTY Test Coverage]
|
|
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]
|
|
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]
|
|
2071
|
+
[![Contributors][🖐contributors-img]](https://github.com/kettle-rb/tree_haver/graphs/contributors)
|
|
1932
2072
|
|
|
1933
|
-
Made with [contributors-img]
|
|
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]
|
|
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]
|
|
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]
|
|
2104
|
+
the [Pessimistic Version Constraint][📌pvc] with two digits of precision.
|
|
1965
2105
|
|
|
1966
2106
|
For example:
|
|
1967
2107
|
|
|
1968
|
-
```
|
|
1969
|
-
spec.add_dependency("tree_haver", "~>
|
|
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"]
|
|
2121
|
+
- ["Major Version Numbers are Not Sacred"][📌major-versions-not-sacred]
|
|
2122
|
+
|
|
1982
2123
|
</details>
|
|
1983
2124
|
|
|
1984
|
-
See [CHANGELOG.md]
|
|
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]
|
|
1990
|
-
See [LICENSE.txt]
|
|
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]
|
|
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]
|
|
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.
|
|
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
|