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