tree_haver 1.0.0 → 2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 425303ccfb502053b4ee9a9169cc069e5162f31ec492c8dbf93df475d54a645a
4
- data.tar.gz: 76d6e16e81818f8e4da1510f9d7c4da947a8b3d2c7394c58582c75948b6aaa22
3
+ metadata.gz: f711d2013566d14c8ae9d5f3609de3c32f8621e04d573543841c88e04fb6b1fb
4
+ data.tar.gz: '09c5054535b0399848c728302631daf9fdebb6c9d6a1aa3fa7d5428e52875b36'
5
5
  SHA512:
6
- metadata.gz: b1fbc2bf591fef1bd6cd9b2c8e5efb6f3a67e71ff310b2b0830a8b32c261acf5fb2acc67272c527d0b47c2507b3008ae98248916c24ac18f58dcd6b56f6248fa
7
- data.tar.gz: 1d0831f85e2b8a92a2ad3823e51aadc265c26914786a5a0a8ae95730c22aca41ec1a5c5d897f958a1b1718e27f8b5ad50a8161d8304442ae5b643366b93e546b
6
+ metadata.gz: fd50cd072f0e37bd051e184eb4e8fb2f198f3645f403732d04e73d08990890c28cf30d678a40142b459ea361577cfa1c22a68e1b0050f71d5cc9fe631d1a4f0d
7
+ data.tar.gz: 46072b508a3a538750b202a948a8a9e14dcc0134e659bc481df66de28d334daee69e55cd4c3d7b66bf01fe193f3fed9a419c785eea0a79266680261186367c29
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,50 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [2.0.0] - 2025-12-15
34
+
35
+ - TAG: [v2.0.0][2.0.0t]
36
+ - COVERAGE: 82.78% -- 601/726 lines in 11 files
37
+ - BRANCH COVERAGE: 70.45% -- 186/264 branches in 11 files
38
+ - 91.90% documented
39
+
40
+ ### Added
41
+
42
+ - Added support for Citrus backend (`backends/citrus.rb`) - a pure Ruby grammar parser with its own distinct grammar structure
43
+ - Added `TreeHaver::Tree` unified wrapper class providing consistent API across all backends
44
+ - Added `TreeHaver::Node` unified wrapper class providing consistent API across all backends
45
+ - Added `TreeHaver::Point` class that works as both object and hash for position compatibility
46
+ - Added passthrough mechanism via `method_missing` for accessing backend-specific features
47
+ - Added `inner_node` accessor on `TreeHaver::Node` for advanced backend-specific usage
48
+ - Added `inner_tree` accessor on `TreeHaver::Tree` for advanced backend-specific usage
49
+ - Added comprehensive test suite for `TreeHaver::Node` wrapper class (88 examples)
50
+ - Added comprehensive test suite for `TreeHaver::Tree` wrapper class (17 examples)
51
+ - Added comprehensive test suite for `TreeHaver::Parser` class (12 examples)
52
+ - Added complete test coverage for Citrus backend (41 examples)
53
+ - Enhanced `TreeHaver::Language` tests for dynamic language helpers
54
+
55
+ ### Changed
56
+
57
+ - **BREAKING:** All backends now return `TreeHaver::Tree` from `Parser#parse` and `Parser#parse_string`
58
+ - **BREAKING:** `TreeHaver::Tree#root_node` now returns `TreeHaver::Node` instead of backend-specific node
59
+ - **BREAKING:** All child/sibling/parent methods on nodes now return `TreeHaver::Node` wrappers
60
+ - Updated MRI backend (`backends/mri.rb`) to return wrapped `TreeHaver::Tree` with source
61
+ - Updated Rust backend (`backends/rust.rb`) to return wrapped `TreeHaver::Tree` with source
62
+ - Updated FFI backend (`backends/ffi.rb`) to return wrapped `TreeHaver::Tree` with source
63
+ - Updated Java backend (`backends/java.rb`) to return wrapped `TreeHaver::Tree` with source
64
+ - Updated Citrus backend (`backends/citrus.rb`) to return wrapped `TreeHaver::Tree` with source
65
+ - Disabled old pass-through stub classes in `tree_haver.rb` (wrapped in `if false` for reference)
66
+
67
+ ### Fixed
68
+
69
+ - Fixed `TreeHaver::Tree#supports_editing?` and `#edit` to handle Delegator wrappers correctly by using `.method(:edit)` check instead of `respond_to?`
70
+ - Fixed `PathValidator` to accept versioned `.so` files (e.g., `.so.0`, `.so.14`) which are standard on Linux systems
71
+ - Fixed backend portability - code now works identically across MRI, Rust, FFI, Java, and Citrus backends
72
+ - Fixed inconsistent API - `node.type` now works on all backends (was `node.kind` on TreeStump)
73
+ - Fixed position objects - `start_point` and `end_point` now return objects that work as both `.row` and `[:row]`
74
+ - Fixed child iteration - `node.each` and `node.children` now consistently return `TreeHaver::Node` objects
75
+ - Fixed text extraction - `node.text` now works consistently by storing source in `TreeHaver::Tree`
76
+
33
77
  ## [1.0.0] - 2025-12-15
34
78
 
35
79
  - TAG: [v1.0.0][1.0.0t]
@@ -41,8 +85,8 @@ Please file a bug if you notice a violation of semantic versioning.
41
85
 
42
86
  - Initial release
43
87
 
44
- ### Security
45
-
46
- [Unreleased]: https://github.com/kettle-rb/tree_haver/compare/v1.0.0...HEAD
88
+ [Unreleased]: https://github.com/kettle-rb/tree_haver/compare/v2.0.0...HEAD
89
+ [2.0.0]: https://github.com/kettle-rb/tree_haver/compare/v1.0.0...v2.0.0
90
+ [2.0.0t]: https://github.com/kettle-rb/tree_haver/releases/tag/v2.0.0
47
91
  [1.0.0]: https://github.com/kettle-rb/tree_haver/compare/a89211bff10f4440b96758a8ac9d7d539001b0c8...v1.0.0
48
92
  [1.0.0t]: https://github.com/kettle-rb/tree_haver/tags/v1.0.0
data/README.md CHANGED
@@ -54,20 +54,20 @@
54
54
 
55
55
  ## 🌻 Synopsis
56
56
 
57
- TreeHaver is a cross-Ruby adapter for the [Tree-sitter](https://tree-sitter.github.io/tree-sitter/) parsing library that works seamlessly across MRI Ruby, JRuby, and TruffleRuby. It provides a unified API for parsing source code using Tree-sitter grammars, regardless of your Ruby implementation.
57
+ TreeHaver is a cross-Ruby adapter for the [tree-sitter](https://tree-sitter.github.io/tree-sitter/) parsing library that works seamlessly across MRI Ruby, JRuby, and TruffleRuby. It provides a unified API for parsing source code using tree-sitter grammars, regardless of your Ruby implementation.
58
58
 
59
59
  ### The Adapter Pattern: Like Faraday, but for Parsing
60
60
 
61
61
  If you've used [Faraday](https://github.com/lostisland/faraday), [multi_json](https://github.com/intridea/multi_json), or [multi_xml](https://github.com/sferik/multi_xml), you'll feel right at home with TreeHaver. These gems share a common philosophy:
62
62
 
63
- | Gem | Unified API for | Backend Examples |
64
- |-----|-----------------|------------------|
65
- | **Faraday** | HTTP requests | Net::HTTP, Typhoeus, Patron, Excon |
66
- | **multi_json** | JSON parsing | Oj, Yajl, JSON gem |
67
- | **multi_xml** | XML parsing | Nokogiri, LibXML, Ox |
68
- | **TreeHaver** | Tree-sitter parsing | ruby_tree_sitter, tree_stump, FFI, Java |
63
+ | Gem | Unified API for | Backend Examples |
64
+ |----------------|---------------------|------------------------------------------------------|
65
+ | **Faraday** | HTTP requests | Net::HTTP, Typhoeus, Patron, Excon |
66
+ | **multi_json** | JSON parsing | Oj, Yajl, JSON gem |
67
+ | **multi_xml** | XML parsing | Nokogiri, LibXML, Ox |
68
+ | **TreeHaver** | tree-sitter parsing | ruby_tree_sitter, tree_stump, FFI, Java JARs, Citrus |
69
69
 
70
- **Write once, run anywhere.** Just as Faraday lets you swap HTTP adapters without changing your code, TreeHaver lets you swap Tree-sitter backends. Your parsing code remains the same whether you're running on MRI with native C extensions, JRuby with FFI, or TruffleRuby.
70
+ **Write once, run anywhere.** Just as Faraday lets you swap HTTP adapters without changing your code, TreeHaver lets you swap tree-sitter backends. Your parsing code remains the same whether you're running on MRI with native C extensions, JRuby with FFI, or TruffleRuby.
71
71
 
72
72
  ```ruby
73
73
  # Your code stays the same regardless of backend
@@ -90,15 +90,16 @@ tree = parser.parse(source_code)
90
90
  - **Note**: Currently requires [pboling's fork](https://github.com/pboling/tree_stump/tree/tree_haver) until PRs [#5](https://github.com/joker1007/tree_stump/pull/5), [#7](https://github.com/joker1007/tree_stump/pull/7), [#11](https://github.com/joker1007/tree_stump/pull/11), and [#13 (inclusive of the others)](https://github.com/joker1007/tree_stump/pull/13) are merged
91
91
  - **FFI Backend**: Pure Ruby FFI bindings to `libtree-sitter` (ideal for JRuby)
92
92
  - **Java Backend**: Support for JRuby's native Java integration, and native java-tree-sitter grammar JARs
93
+ - **Citrus Backend**: Pure Ruby parser using [`citrus`](https://github.com/mjackson/citrus) gem (no native dependencies, portable)
93
94
  - **Automatic Backend Selection**: Intelligently selects the best backend for your Ruby implementation
94
- - **Language Agnostic**: Load any Tree-sitter grammar dynamically (TOML, JSON, Ruby, JavaScript, etc.)
95
+ - **Language Agnostic**: Load any tree-sitter grammar dynamically (TOML, JSON, Ruby, JavaScript, etc.)
95
96
  - **Grammar Discovery**: Built-in `GrammarFinder` utility for platform-aware grammar library discovery
96
97
  - **Thread-Safe**: Built-in language registry with thread-safe caching
97
- - **Minimal API Surface**: Simple, focused API that covers the most common Tree-sitter use cases
98
+ - **Minimal API Surface**: Simple, focused API that covers the most common tree-sitter use cases
98
99
 
99
100
  ### Why TreeHaver?
100
101
 
101
- Tree-sitter is a powerful parser generator that creates incremental parsers for many programming languages. However, integrating it into Ruby applications can be challenging:
102
+ tree-sitter is a powerful parser generator that creates incremental parsers for many programming languages. However, integrating it into Ruby applications can be challenging:
102
103
 
103
104
  - MRI-based C extensions don't work on JRuby
104
105
  - FFI-based solutions may not be optimal for MRI
@@ -106,25 +107,28 @@ Tree-sitter is a powerful parser generator that creates incremental parsers for
106
107
 
107
108
  TreeHaver solves these problems by providing a unified API that automatically selects the appropriate backend for your Ruby implementation, allowing you to write code once and run it anywhere.
108
109
 
109
- ### Comparison with Other Ruby Tree-sitter Bindings
110
-
111
- | Feature | TreeHaver | [ruby_tree_sitter] | [tree_stump] |
112
- |---------------------------|--------------------------------|--------------------|----------------|
113
- | **MRI Ruby** | ✅ Yes | ✅ Yes | ✅ Yes |
114
- | **JRuby** | ✅ Yes (FFI or Java\* backend) | ❌ No | ❌ No |
115
- | **TruffleRuby** | ✅ Yes (FFI) | ❌ No | ❓ Unknown |
116
- | **Backend** | Multi (MRI C, Rust, FFI, Java) | C extension only | Rust extension |
117
- | **Incremental Parsing** | ✅ Via MRI C/Rust backend | ✅ Yes | ✅ Yes |
118
- | **Query API** | ⚡ Via MRI/Rust backend | ✅ Yes | ✅ Yes |
119
- | **Grammar Discovery** | ✅ Built-in `GrammarFinder` | ❌ Manual | ❌ Manual |
120
- | **Security Validations** | ✅ `PathValidator` | ❌ No | ❌ No |
121
- | **Language Registration** | ✅ Thread-safe registry | ❌ No | ❌ No |
122
- | **Native Performance** | ⚡ Backend-dependent | ✅ Native C | ✅ Native Rust |
123
- | **Precompiled Binaries** | ⚡ Via Rust backend | ✅ Yes | ✅ Yes |
124
- | **Minimum Ruby** | 3.2+ | 3.0+ | 3.1+ |
110
+ ### Comparison with Other Ruby AST / Parser Bindings
111
+
112
+ | Feature | [tree_haver] (this gem) | [ruby_tree_sitter] | [tree_stump] | [citrus] |
113
+ |---------------------------|----------------------------------------|--------------------|----------------|-------------|
114
+ | **MRI Ruby** | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
115
+ | **JRuby** | ✅ Yes (FFI, Java, or Citrus backend) | ❌ No | ❌ No | ✅ Yes |
116
+ | **TruffleRuby** | ✅ Yes (FFI or Citrus) | ❌ No | ❓ Unknown | ✅ Yes |
117
+ | **Backend** | Multi (MRI C, Rust, FFI, Java, Citrus) | C extension only | Rust extension | Pure Ruby |
118
+ | **Incremental Parsing** | ✅ Via MRI C/Rust/Java backend | ✅ Yes | ✅ Yes | ❌ No |
119
+ | **Query API** | ⚡ Via MRI/Rust/Java backend | ✅ Yes | ✅ Yes | ❌ No |
120
+ | **Grammar Discovery** | ✅ Built-in `GrammarFinder` | ❌ Manual | ❌ Manual | ❌ Manual |
121
+ | **Security Validations** | ✅ `PathValidator` | ❌ No | ❌ No | ❌ No |
122
+ | **Language Registration** | ✅ Thread-safe registry | ❌ No | ❌ No | ❌ No |
123
+ | **Native Performance** | ⚡ Backend-dependent | ✅ Native C | ✅ Native Rust | ❌ Pure Ruby |
124
+ | **Precompiled Binaries** | ⚡ Via Rust backend | ✅ Yes | ✅ Yes | ✅ Pure Ruby |
125
+ | **Zero Native Deps** | ⚡ Via Citrus backend | ❌ No | ❌ No | ✅ Yes |
126
+ | **Minimum Ruby** | 3.2+ | 3.0+ | 3.1+ | 0+ |
125
127
 
126
128
  [ruby_tree_sitter]: https://github.com/Faveod/ruby-tree-sitter
127
129
  [tree_stump]: https://github.com/anthropics/tree_stump
130
+ [citrus]: https://github.com/mjackson/citrus
131
+ [tree_haver]: https://github.com/kettle-rb/tree_haver
128
132
 
129
133
  **Note:** Java backend works with grammar JARs built specifically for java-tree-sitter, or grammar .so files that statically link tree-sitter. This is why FFI is recommended for JRuby & TruffleRuby.
130
134
 
@@ -135,6 +139,7 @@ TreeHaver solves these problems by providing a unified API that automatically se
135
139
  #### When to Use Each
136
140
 
137
141
  **Choose TreeHaver when:**
142
+
138
143
  - You need JRuby or TruffleRuby support
139
144
  - You're building a library that should work across Ruby implementations
140
145
  - You want automatic grammar discovery and security validations
@@ -142,39 +147,48 @@ TreeHaver solves these problems by providing a unified API that automatically se
142
147
  - You need incremental parsing with a unified API
143
148
 
144
149
  **Choose ruby_tree_sitter directly when:**
150
+
145
151
  - You only target MRI Ruby
146
152
  - You need the full Query API without abstraction
147
153
  - You want the most battle-tested C bindings
148
154
  - You don't need TreeHaver's grammar discovery
149
155
 
150
156
  **Choose tree_stump directly when:**
157
+
151
158
  - You only target MRI Ruby
152
159
  - You prefer Rust-based native extensions
153
160
  - You want precompiled binaries without system dependencies
154
161
  - You don't need TreeHaver's grammar discovery
155
162
  - **Note:** Use [pboling's fork (tree_haver branch)](https://github.com/pboling/tree_stump/tree/tree_haver) until PRs [#5](https://github.com/joker1007/tree_stump/pull/5), [#7](https://github.com/joker1007/tree_stump/pull/7), [#11](https://github.com/joker1007/tree_stump/pull/11), [#13](https://github.com/joker1007/tree_stump/pull/13) are merged
156
163
 
164
+ **Choose citrus directly when:**
165
+
166
+ - You need zero native dependencies (pure Ruby)
167
+ - You're using a Citrus grammar (not tree-sitter grammars)
168
+ - Performance is less critical than portability
169
+ - You don't need TreeHaver's unified API
170
+
157
171
  ## 💡 Info you can shake a stick at
158
172
 
159
- | Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] |
160
- |-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
161
- | Works with JRuby | [![JRuby 10.0 Compat][💎jruby-c-i]][🚎11-c-wf] [![JRuby HEAD Compat][💎jruby-headi]][🚎3-hd-wf] |
162
- | Works with Truffle Ruby | [![Truffle Ruby 23.1 Compat][💎truby-23.1i]][🚎9-t-wf] [![Truffle Ruby 24.1 Compat][💎truby-c-i]][🚎11-c-wf] |
163
- | Works with MRI Ruby 3 | [![Ruby 3.2 Compat][💎ruby-3.2i]][🚎6-s-wf] [![Ruby 3.3 Compat][💎ruby-3.3i]][🚎6-s-wf] [![Ruby 3.4 Compat][💎ruby-c-i]][🚎11-c-wf] [![Ruby HEAD Compat][💎ruby-headi]][🚎3-hd-wf] |
164
- | Support & Community | [![Join Me on Daily.dev's RubyFriends][✉️ruby-friends-img]][✉️ruby-friends] [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite] [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork] [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor] |
165
- | Source | [![Source on GitLab.com][📜src-gl-img]][📜src-gl] [![Source on CodeBerg.org][📜src-cb-img]][📜src-cb] [![Source on Github.com][📜src-gh-img]][📜src-gh] [![The best SHA: dQw4w9WgXcQ!][🧮kloc-img]][🧮kloc] |
166
- | Documentation | [![Current release on RubyDoc.info][📜docs-cr-rd-img]][🚎yard-current] [![YARD on Galtzo.com][📜docs-head-rd-img]][🚎yard-head] [![Maintainer Blog][🚂maint-blog-img]][🚂maint-blog] [![GitLab Wiki][📜gl-wiki-img]][📜gl-wiki] [![GitHub Wiki][📜gh-wiki-img]][📜gh-wiki] |
173
+ | Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] |
174
+ | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
175
+ | Works with JRuby | [![JRuby 10.0 Compat][💎jruby-c-i]][🚎11-c-wf] [![JRuby HEAD Compat][💎jruby-headi]][🚎3-hd-wf] |
176
+ | Works with Truffle Ruby | [![Truffle Ruby 23.1 Compat][💎truby-23.1i]][🚎9-t-wf] [![Truffle Ruby 24.1 Compat][💎truby-c-i]][🚎11-c-wf] |
177
+ | Works with MRI Ruby 3 | [![Ruby 3.2 Compat][💎ruby-3.2i]][🚎6-s-wf] [![Ruby 3.3 Compat][💎ruby-3.3i]][🚎6-s-wf] [![Ruby 3.4 Compat][💎ruby-c-i]][🚎11-c-wf] [![Ruby HEAD Compat][💎ruby-headi]][🚎3-hd-wf] |
178
+ | Support & Community | [![Join Me on Daily.dev's RubyFriends][✉️ruby-friends-img]][✉️ruby-friends] [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite] [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork] [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor] |
179
+ | Source | [![Source on GitLab.com][📜src-gl-img]][📜src-gl] [![Source on CodeBerg.org][📜src-cb-img]][📜src-cb] [![Source on Github.com][📜src-gh-img]][📜src-gh] [![The best SHA: dQw4w9WgXcQ!][🧮kloc-img]][🧮kloc] |
180
+ | Documentation | [![Current release on RubyDoc.info][📜docs-cr-rd-img]][🚎yard-current] [![YARD on Galtzo.com][📜docs-head-rd-img]][🚎yard-head] [![Maintainer Blog][🚂maint-blog-img]][🚂maint-blog] [![GitLab Wiki][📜gl-wiki-img]][📜gl-wiki] [![GitHub Wiki][📜gh-wiki-img]][📜gh-wiki] |
167
181
  | Compliance | [![License: MIT][📄license-img]][📄license-ref] [![Compatible with Apache Software Projects: Verified by SkyWalking Eyes][📄license-compat-img]][📄license-compat] [![📄ilo-declaration-img]][📄ilo-declaration] [![Security Policy][🔐security-img]][🔐security] [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct] [![SemVer 2.0.0][📌semver-img]][📌semver] |
168
- | Style | [![Enforced Code Style Linter][💎rlts-img]][💎rlts] [![Keep-A-Changelog 1.0.0][📗keep-changelog-img]][📗keep-changelog] [![Gitmoji Commits][📌gitmoji-img]][📌gitmoji] [![Compatibility appraised by: appraisal2][💎appraisal2-img]][💎appraisal2] |
169
- | Maintainer 🎖️ | [![Follow Me on LinkedIn][💖🖇linkedin-img]][💖🖇linkedin] [![Follow Me on Ruby.Social][💖🐘ruby-mast-img]][💖🐘ruby-mast] [![Follow Me on Bluesky][💖🦋bluesky-img]][💖🦋bluesky] [![Contact Maintainer][🚂maint-contact-img]][🚂maint-contact] [![My technical writing][💖💁🏼‍♂️devto-img]][💖💁🏼‍♂️devto] |
170
- | `...` 💖 | [![Find Me on WellFound:][💖✌️wellfound-img]][💖✌️wellfound] [![Find Me on CrunchBase][💖💲crunchbase-img]][💖💲crunchbase] [![My LinkTree][💖🌳linktree-img]][💖🌳linktree] [![More About Me][💖💁🏼‍♂️aboutme-img]][💖💁🏼‍♂️aboutme] [🧊][💖🧊berg] [🐙][💖🐙hub] [🛖][💖🛖hut] [🧪][💖🧪lab] |
182
+ | Style | [![Enforced Code Style Linter][💎rlts-img]][💎rlts] [![Keep-A-Changelog 1.0.0][📗keep-changelog-img]][📗keep-changelog] [![Gitmoji Commits][📌gitmoji-img]][📌gitmoji] [![Compatibility appraised by: appraisal2][💎appraisal2-img]][💎appraisal2] |
183
+ | Maintainer 🎖️ | [![Follow Me on LinkedIn][💖🖇linkedin-img]][💖🖇linkedin] [![Follow Me on Ruby.Social][💖🐘ruby-mast-img]][💖🐘ruby-mast] [![Follow Me on Bluesky][💖🦋bluesky-img]][💖🦋bluesky] [![Contact Maintainer][🚂maint-contact-img]][🚂maint-contact] [![My technical writing][💖💁🏼‍♂️devto-img]][💖💁🏼‍♂️devto] |
184
+ | `...` 💖 | [![Find Me on WellFound:][💖✌️wellfound-img]][💖✌️wellfound] [![Find Me on CrunchBase][💖💲crunchbase-img]][💖💲crunchbase] [![My LinkTree][💖🌳linktree-img]][💖🌳linktree] [![More About Me][💖💁🏼‍♂️aboutme-img]][💖💁🏼‍♂️aboutme] [🧊][💖🧊berg] [🐙][💖🐙hub] [🛖][💖🛖hut] [🧪][💖🧪lab] |
171
185
 
172
186
  ### Compatibility
173
187
 
174
188
  Compatible with MRI Ruby 3.2.0+, and concordant releases of JRuby, and TruffleRuby.
175
189
 
176
- | 🚚 _Amazing_ test matrix was brought to you by | 🔎 appraisal2 🔎 and the color 💚 green 💚 |
177
- |------------------------------------------------|--------------------------------------------------------|
190
+ | 🚚 _Amazing_ test matrix was brought to you by | 🔎 appraisal2 🔎 and the color 💚 green 💚 |
191
+ | ---------------------------------------------- | -------------------------------------------------------- |
178
192
  | 👟 Check it out! | ✨ [github.com/appraisal-rb/appraisal2][💎appraisal2] ✨ |
179
193
 
180
194
  ### Federated DVCS
@@ -183,9 +197,9 @@ Compatible with MRI Ruby 3.2.0+, and concordant releases of JRuby, and TruffleRu
183
197
  <summary>Find this repo on federated forges (Coming soon!)</summary>
184
198
 
185
199
  | Federated [DVCS][💎d-in-dvcs] Repository | Status | Issues | PRs | Wiki | CI | Discussions |
186
- |-------------------------------------------------|-----------------------------------------------------------------------|---------------------------|--------------------------|---------------------------|--------------------------|------------------------------|
187
- | 🧪 [kettle-rb/tree_haver on GitLab][📜src-gl] | The Truth | [💚][🤝gl-issues] | [💚][🤝gl-pulls] | [💚][📜gl-wiki] | 🐭 Tiny Matrix | ➖ |
188
- | 🧊 [kettle-rb/tree_haver on CodeBerg][📜src-cb] | An Ethical Mirror ([Donate][🤝cb-donate]) | [💚][🤝cb-issues] | [💚][🤝cb-pulls] | ➖ | ⭕️ No Matrix | ➖ |
200
+ | ----------------------------------------------- | --------------------------------------------------------------------- | ------------------------- | ------------------------ | ------------------------- | ------------------------ | ---------------------------- |
201
+ | 🧪 [kettle-rb/tree_haver on GitLab][📜src-gl] | The Truth | [💚][🤝gl-issues] | [💚][🤝gl-pulls] | [💚][📜gl-wiki] | 🐭 Tiny Matrix | ➖ |
202
+ | 🧊 [kettle-rb/tree_haver on CodeBerg][📜src-cb] | An Ethical Mirror ([Donate][🤝cb-donate]) | [💚][🤝cb-issues] | [💚][🤝cb-pulls] | ➖ | ⭕️ No Matrix | ➖ |
189
203
  | 🐙 [kettle-rb/tree_haver on GitHub][📜src-gh] | Another Mirror | [💚][🤝gh-issues] | [💚][🤝gh-pulls] | [💚][📜gh-wiki] | 💯 Full Matrix | [💚][gh-discussions] |
190
204
  | 🎮️ [Discord Server][✉️discord-invite] | [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite] | [Let's][✉️discord-invite] | [talk][✉️discord-invite] | [about][✉️discord-invite] | [this][✉️discord-invite] | [library!][✉️discord-invite] |
191
205
 
@@ -206,7 +220,7 @@ The maintainers of this and thousands of other packages are working with Tidelif
206
220
 
207
221
  - 💡Subscribe for support guarantees covering _all_ your FLOSS dependencies
208
222
  - 💡Tidelift is part of [Sonar][🏙️entsup-tidelift-sonar]
209
- - 💡Tidelift pays maintainers to maintain the software you depend on!<br/>📊`@`Pointy Haired Boss: An [enterprise support][🏙️entsup-tidelift] subscription is "[never gonna let you down][🧮kloc]", and *supports* open source maintainers
223
+ - 💡Tidelift pays maintainers to maintain the software you depend on!<br/>📊`@`Pointy Haired Boss: An [enterprise support][🏙️entsup-tidelift] subscription is "[never gonna let you down][🧮kloc]", and _supports_ open source maintainers
210
224
 
211
225
  Alternatively:
212
226
 
@@ -245,7 +259,7 @@ Add my public key (if you haven’t already, expires 2045-04-29) as a trusted ce
245
259
  gem cert --add <(curl -Ls https://raw.github.com/galtzo-floss/certs/main/pboling.pem)
246
260
  ```
247
261
 
248
- You only need to do that once. Then proceed to install with:
262
+ You only need to do that once. Then proceed to install with:
249
263
 
250
264
  ```console
251
265
  gem install tree_haver -P HighSecurity
@@ -359,15 +373,18 @@ TreeHaver automatically selects the best backend for your Ruby implementation, b
359
373
  TreeHaver.backend = :auto
360
374
 
361
375
  # Force a specific backend
362
- TreeHaver.backend = :mri # Use ruby_tree_sitter (MRI only, C extension)
363
- TreeHaver.backend = :rust # Use tree_stump (MRI, Rust extension with precompiled binaries)
364
- # Note: Requires pboling's fork until PRs #5, #7, #11, #13 are merged
365
- # See: https://github.com/pboling/tree_stump/tree/tree_haver
366
- TreeHaver.backend = :ffi # Use FFI bindings (works on MRI and JRuby)
367
- TreeHaver.backend = :java # Use Java bindings (JRuby only, coming soon)
376
+ TreeHaver.backend = :mri # Use ruby_tree_sitter (MRI only, C extension)
377
+ TreeHaver.backend = :rust # Use tree_stump (MRI, Rust extension with precompiled binaries)
378
+ # Note: Requires pboling's fork until PRs #5, #7, #11, #13 are merged
379
+ # See: https://github.com/pboling/tree_stump/tree/tree_haver
380
+ TreeHaver.backend = :ffi # Use FFI bindings (works on MRI and JRuby)
381
+ TreeHaver.backend = :java # Use Java bindings (JRuby only, coming soon)
382
+ TreeHaver.backend = :citrus # Use Citrus pure Ruby parser
383
+ # NOTE: Portable, all Ruby implementations
384
+ # CAVEAT: few major language grammars, but many esoteric grammars
368
385
  ```
369
386
 
370
- **Auto-selection priority on MRI:** MRI → Rust → FFI
387
+ **Auto-selection priority on MRI:** MRI → Rust → FFI → Citrus
371
388
 
372
389
  You can also set the backend via environment variable:
373
390
 
@@ -384,6 +401,7 @@ TreeHaver recognizes several environment variables for configuration:
384
401
  #### Security Configuration
385
402
 
386
403
  - **`TREE_HAVER_TRUSTED_DIRS`**: Comma-separated list of additional trusted directories for grammar libraries
404
+
387
405
  ```bash
388
406
  # For Homebrew on Linux and luarocks
389
407
  export TREE_HAVER_TRUSTED_DIRS="/home/linuxbrew/.linuxbrew/Cellar,~/.local/share/mise/installs/lua"
@@ -399,11 +417,12 @@ TreeHaver recognizes several environment variables for configuration:
399
417
  ```
400
418
 
401
419
  If not set, TreeHaver tries these names in order:
402
- - `tree-sitter`
403
- - `libtree-sitter.so.0`
404
- - `libtree-sitter.so`
405
- - `libtree-sitter.dylib`
406
- - `libtree-sitter.dll`
420
+
421
+ - `tree-sitter`
422
+ - `libtree-sitter.so.0`
423
+ - `libtree-sitter.so`
424
+ - `libtree-sitter.dylib`
425
+ - `libtree-sitter.dll`
407
426
 
408
427
  #### Language Symbol Resolution
409
428
 
@@ -468,7 +487,7 @@ if finder.available?
468
487
  puts "TOML grammar found at: #{finder.find_library_path}"
469
488
  else
470
489
  puts finder.not_found_message
471
- # => "Tree-sitter toml grammar not found. Searched: /usr/lib/libtree-sitter-toml.so, ..."
490
+ # => "tree-sitter toml grammar not found. Searched: /usr/lib/libtree-sitter-toml.so, ..."
472
491
  end
473
492
 
474
493
  # Register the language if available
@@ -482,11 +501,11 @@ language = TreeHaver::Language.toml
482
501
 
483
502
  Given just the language name, `GrammarFinder` automatically derives:
484
503
 
485
- | Property | Derived Value (for `:toml`) |
486
- |----------|----------------------------|
487
- | ENV var | `TREE_SITTER_TOML_PATH` |
504
+ | Property | Derived Value (for `:toml`) |
505
+ | ---------------- | ---------------------------------------------------- |
506
+ | ENV var | `TREE_SITTER_TOML_PATH` |
488
507
  | Library filename | `libtree-sitter-toml.so` (Linux) or `.dylib` (macOS) |
489
- | Symbol name | `tree_sitter_toml` |
508
+ | Symbol name | `tree_sitter_toml` |
490
509
 
491
510
  #### Search Order
492
511
 
@@ -496,7 +515,7 @@ Given just the language name, `GrammarFinder` automatically derives:
496
515
  2. **Extra paths**: Custom paths provided at initialization
497
516
  3. **System paths**: Common installation directories (`/usr/lib`, `/usr/local/lib`, `/opt/homebrew/lib`, etc.)
498
517
 
499
- #### Usage in *-merge Gems
518
+ #### Usage in \*-merge Gems
500
519
 
501
520
  The `GrammarFinder` pattern enables clean integration in language-specific merge gems:
502
521
 
@@ -555,6 +574,8 @@ TreeHaver.capabilities
555
574
  # => { backend: :mri, query: true, bytes_field: true }
556
575
  # or
557
576
  # => { backend: :ffi, parse: true, query: false, bytes_field: true }
577
+ # or
578
+ # => { backend: :citrus, parse: true, query: false, bytes_field: false }
558
579
  ```
559
580
 
560
581
  ### Compatibility Mode
@@ -638,7 +659,7 @@ tree = parser.parse(toml_source)
638
659
 
639
660
  ### Parsing Different Languages
640
661
 
641
- TreeHaver works with any Tree-sitter grammar:
662
+ TreeHaver works with any tree-sitter grammar:
642
663
 
643
664
  ```ruby
644
665
  # Parse Ruby code
@@ -704,7 +725,7 @@ tree.edit(
704
725
  new_tree = parser.parse_string(tree, "x = 42")
705
726
  ```
706
727
 
707
- **Note:** Incremental parsing requires the MRI (`ruby_tree_sitter`), Rust (`tree_stump`), or Java (`java-tree-sitter`) backend. The FFI backend does not currently support incremental parsing. You can check support with:
728
+ **Note:** Incremental parsing requires the MRI (`ruby_tree_sitter`), Rust (`tree_stump`), or Java (`java-tree-sitter`) backend. The FFI and Citrus backends do not currently support incremental parsing. You can check support with:
708
729
 
709
730
  **Note:** `tree_stump` requires [pboling's fork (tree_haver branch)](https://github.com/pboling/tree_stump/tree/tree_haver) until PRs [#5](https://github.com/joker1007/tree_stump/pull/5), [#7](https://github.com/joker1007/tree_stump/pull/7), [#11](https://github.com/joker1007/tree_stump/pull/11), [#13](https://github.com/joker1007/tree_stump/pull/13) are merged.
710
731
 
@@ -724,7 +745,7 @@ end
724
745
  # Check if a backend is available
725
746
  if TreeHaver.backend_module.nil?
726
747
  puts "No TreeHaver backend is available!"
727
- puts "Install ruby_tree_sitter (MRI) or ensure ffi gem and libtree-sitter are present"
748
+ puts "Install ruby_tree_sitter (MRI), ffi gem with libtree-sitter, or citrus gem"
728
749
  end
729
750
  ```
730
751
 
@@ -745,9 +766,9 @@ parser = TreeHaver::Parser.new
745
766
 
746
767
  #### JRuby
747
768
 
748
- On JRuby, TreeHaver can use either the FFI backend or the Java backend:
769
+ On JRuby, TreeHaver can use the FFI backend, Java backend, or Citrus backend:
749
770
 
750
- **Option 1: FFI Backend (simpler setup)**
771
+ **Option 1: FFI Backend (recommended for tree-sitter grammars)**
751
772
 
752
773
  ```ruby
753
774
  # Gemfile
@@ -809,19 +830,49 @@ TreeHaver.backend = :ffi
809
830
  The FFI backend uses Ruby's FFI gem which relies on the system's dynamic linker, correctly resolving symbol dependencies between `libtree-sitter.so` and grammar libraries.
810
831
 
811
832
  The Java backend will work with:
833
+
812
834
  - Grammar JARs built specifically for java-tree-sitter (self-contained)
813
835
  - Grammar `.so` files that statically link tree-sitter
814
836
 
837
+ **Option 3: Citrus Backend (pure Ruby, portable)**
838
+
839
+ ```ruby
840
+ # Gemfile
841
+ gem "tree_haver"
842
+ gem "citrus" # Pure Ruby parser, zero native dependencies
843
+
844
+ # Code - Force Citrus backend for maximum portability
845
+ TreeHaver.backend = :citrus
846
+
847
+ # Check if Citrus backend is available
848
+ if TreeHaver::Backends::Citrus.available?
849
+ puts "Citrus backend is ready!"
850
+ puts TreeHaver.capabilities
851
+ # => { backend: :citrus, parse: true, query: false, bytes_field: false }
852
+ end
853
+ ```
854
+
855
+ **⚠️ Citrus Backend Limitations:**
856
+
857
+ - Uses Citrus grammars (not tree-sitter grammars)
858
+ - No incremental parsing support
859
+ - No query API
860
+ - Pure Ruby performance (slower than native backends)
861
+ - Best for: prototyping, environments without native extension support, teaching
862
+
815
863
  #### TruffleRuby
816
864
 
817
- TruffleRuby can use either the MRI or FFI backend:
865
+ TruffleRuby can use the MRI, FFI, or Citrus backend:
818
866
 
819
867
  ```ruby
820
- # Use FFI backend (recommended)
868
+ # Use FFI backend (recommended for tree-sitter grammars)
821
869
  TreeHaver.backend = :ffi
822
870
 
823
871
  # Or try MRI backend if ruby_tree_sitter compiles on your TruffleRuby version
824
872
  TreeHaver.backend = :mri
873
+
874
+ # Or use Citrus backend for zero native dependencies
875
+ TreeHaver.backend = :citrus
825
876
  ```
826
877
 
827
878
  ### Advanced: Testing with Multiple Backends
@@ -912,7 +963,7 @@ You can support the development of kettle-rb tools via
912
963
  and [Tidelift][🏙️entsup-tidelift].
913
964
 
914
965
  | 📍 NOTE |
915
- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
966
+ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
916
967
  | If doing a sponsorship in the form of donation is problematic for your company <br/> from an accounting standpoint, we'd recommend the use of Tidelift, <br/> where you can get a support-like subscription instead. |
917
968
 
918
969
  ### Open Collective for Individuals
@@ -922,7 +973,9 @@ Support us with a monthly donation and help us continue our activities. [[Become
922
973
  NOTE: [kettle-readme-backers][kettle-readme-backers] updates this list every day, automatically.
923
974
 
924
975
  <!-- OPENCOLLECTIVE-INDIVIDUALS:START -->
976
+
925
977
  No backers yet. Be the first!
978
+
926
979
  <!-- OPENCOLLECTIVE-INDIVIDUALS:END -->
927
980
 
928
981
  ### Open Collective for Organizations
@@ -932,14 +985,16 @@ Become a sponsor and get your logo on our README on GitHub with a link to your s
932
985
  NOTE: [kettle-readme-backers][kettle-readme-backers] updates this list every day, automatically.
933
986
 
934
987
  <!-- OPENCOLLECTIVE-ORGANIZATIONS:START -->
988
+
935
989
  No sponsors yet. Be the first!
990
+
936
991
  <!-- OPENCOLLECTIVE-ORGANIZATIONS:END -->
937
992
 
938
993
  [kettle-readme-backers]: https://github.com/kettle-rb/tree_haver/blob/main/exe/kettle-readme-backers
939
994
 
940
995
  ### Another way to support open-source
941
996
 
942
- I’m driven by a passion to foster a thriving open-source community – a space where people can tackle complex problems, no matter how small. Revitalizing libraries that have fallen into disrepair, and building new libraries focused on solving real-world challenges, are my passions. I was recently affected by layoffs, and the tech jobs market is unwelcoming. I’m reaching out here because your support would significantly aid my efforts to provide for my family, and my farm (11 🐔 chickens, 2 🐶 dogs, 3 🐰 rabbits, 8 🐈‍ cats).
997
+ I’m driven by a passion to foster a thriving open-source community – a space where people can tackle complex problems, no matter how small. Revitalizing libraries that have fallen into disrepair, and building new libraries focused on solving real-world challenges, are my passions. I was recently affected by layoffs, and the tech jobs market is unwelcoming. I’m reaching out here because your support would significantly aid my efforts to provide for my family, and my farm (11 🐔 chickens, 2 🐶 dogs, 3 🐰 rabbits, 8 🐈‍ cats).
943
998
 
944
999
  If you work at a company that uses my work, please encourage them to support me as a corporate sponsor. My work on gems you use might show up in `bundle fund`.
945
1000
 
@@ -1010,7 +1065,7 @@ a new version should be immediately released that restores compatibility.
1010
1065
  Breaking changes to the public API will only be introduced with new major versions.
1011
1066
 
1012
1067
  > dropping support for a platform is both obviously and objectively a breaking change <br/>
1013
- >—Jordan Harband ([@ljharb](https://github.com/ljharb), maintainer of SemVer) [in SemVer issue 716][📌semver-breaking]
1068
+ > —Jordan Harband ([@ljharb](https://github.com/ljharb), maintainer of SemVer) [in SemVer issue 716][📌semver-breaking]
1014
1069
 
1015
1070
  I understand that policy doesn't work universally ("exceptions to every rule!"),
1016
1071
  but it is the policy here.
@@ -1027,7 +1082,7 @@ spec.add_dependency("tree_haver", "~> 1.0")
1027
1082
  <summary>📌 Is "Platform Support" part of the public API? More details inside.</summary>
1028
1083
 
1029
1084
  SemVer should, IMO, but doesn't explicitly, say that dropping support for specific Platforms
1030
- is a *breaking change* to an API, and for that reason the bike shedding is endless.
1085
+ is a _breaking change_ to an API, and for that reason the bike shedding is endless.
1031
1086
 
1032
1087
  To get a better understanding of how SemVer is intended to work over a project's lifetime,
1033
1088
  read this article from the creator of SemVer:
@@ -1114,7 +1169,6 @@ Thanks for RTFM. ☺️
1114
1169
  [✉️discord-invite-img-ftb]: https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord
1115
1170
  [✉️ruby-friends-img]: https://img.shields.io/badge/daily.dev-%F0%9F%92%8E_Ruby_Friends-0A0A0A?style=for-the-badge&logo=dailydotdev&logoColor=white
1116
1171
  [✉️ruby-friends]: https://app.daily.dev/squads/rubyfriends
1117
-
1118
1172
  [✇bundle-group-pattern]: https://gist.github.com/pboling/4564780
1119
1173
  [⛳️gem-namespace]: https://github.com/kettle-rb/tree_haver
1120
1174
  [⛳️namespace-img]: https://img.shields.io/badge/namespace-TreeHaver-3C2D2D.svg?style=square&logo=ruby&logoColor=white
@@ -1238,7 +1292,7 @@ Thanks for RTFM. ☺️
1238
1292
  [📌gitmoji]: https://gitmoji.dev
1239
1293
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
1240
1294
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
1241
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.501-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1295
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.726-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1242
1296
  [🔐security]: SECURITY.md
1243
1297
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
1244
1298
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year