tree_haver 3.1.0 → 3.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ad698ec9d939142aa2fd735e356ee50e83be0b2bc9ac16358f15b48f18d11d13
4
- data.tar.gz: 51d0e7f2cb6d39bd0e10ffca602e096e86d463ccc38cbce5e0997dce39ca1bb2
3
+ metadata.gz: 141cf04a77bd50b8802a20eb098ddb03352b08fa6b1592b95180dcf336cc25e1
4
+ data.tar.gz: 134418492ecd86ef348221dfb7cef3f7c459acee00318c2669bdd666dc37d4ba
5
5
  SHA512:
6
- metadata.gz: 0c0c55b6be53012357cd638e8997f1bd201310e494750b90a82a3f9588fb505a059ead9f056ffc443c424ca2a3cf6370d27e0b508a30faa90a39403bb067cb85
7
- data.tar.gz: 71d9577716ab0edb436fa784dbfc5d6c81360c745ed98ef156c57475b6cd1f11eccf0030f2565e12d7dcf5035fcdf8fdc2c1eca64f81623109b1318048f41db4
6
+ metadata.gz: bafe3a43549cecf6e38a3c8f68c4f072348050d2a3807c4ad8fa9ec2f1d073892abaf5b4300e750a2282849c1bdd081c9ef122b1f2dcb0c5044bddedb451c7b2
7
+ data.tar.gz: 62e7cd0947fdda50ea54f9e9a1b07a05c142388ef56a7a25a9ebb177f12804883cc3fc59dbfbbd3d5f08d062f59f89e850bb5d71733521344c53b5eec19ada78
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,60 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [3.1.1] - 2025-12-28
34
+
35
+ - TAG: [v3.1.1][3.1.1t]
36
+ - COVERAGE: 87.44% -- 2152/2461 lines in 22 files
37
+ - BRANCH COVERAGE: 66.67% -- 710/1065 branches in 22 files
38
+ - 90.02% documented
39
+
40
+ ### Added
41
+
42
+ - **`TreeHaver::RSpec::DependencyTags`**: Shared RSpec dependency detection for the entire gem family
43
+ - New `lib/tree_haver/rspec.rb` entry point - other gems can simply `require "tree_haver/rspec"`
44
+ - Detects all TreeHaver backends: FFI, MRI, Rust, Java, Prism, Psych, Commonmarker, Markly, Citrus
45
+ - Ruby engine detection: `jruby?`, `truffleruby?`, `mri?`
46
+ - Language grammar detection: `tree_sitter_bash_available?`, `tree_sitter_toml_available?`, `tree_sitter_json_available?`, `tree_sitter_jsonc_available?`
47
+ - Inner-merge dependency detection: `toml_merge_available?`, `json_merge_available?`, `prism_merge_available?`, `psych_merge_available?`
48
+ - Composite checks: `any_toml_backend_available?`, `any_markdown_backend_available?`
49
+ - Records MRI backend usage when checking availability (critical for FFI conflict detection)
50
+ - Configures RSpec exclusion filters for all dependency tags automatically
51
+ - Supports debug output via `TREE_HAVER_DEBUG=1` environment variable
52
+ - Comprehensive documentation with usage examples
53
+
54
+ - **`TreeHaver.parser_for`**: New high-level factory method for creating configured parsers
55
+ - Handles all language loading complexity in one call
56
+ - Auto-discovers tree-sitter grammar via `GrammarFinder`
57
+ - Falls back to Citrus grammar if tree-sitter unavailable
58
+ - Accepts `library_path` for explicit grammar location
59
+ - Accepts `citrus_config` for Citrus fallback configuration
60
+ - Raises `NotAvailable` with helpful message if no backend works
61
+ - Example: `parser = TreeHaver.parser_for(:toml)`
62
+ - Raises `NotAvailable` if the specified path doesn't exist (Principle of Least Surprise)
63
+ - Does not back to auto-discovery when an explicit path is provided
64
+ - Re-raises with context-rich error message if loading from explicit path fails
65
+ - Auto-discovery still works normally when no `library_path` is provided
66
+
67
+ ### Changed
68
+
69
+ - **Backend sibling navigation**: Backends that don't support sibling/parent navigation now raise `NotImplementedError` instead of returning `nil`
70
+ - This distinguishes "not implemented" from "no sibling exists"
71
+ - Affected backends: Prism, Psych
72
+ - Affected methods: `next_sibling`, `prev_sibling`, `parent`
73
+
74
+ - **Canonical sibling method name**: All backends now use `prev_sibling` as the canonical method name (not `previous_sibling`)
75
+ - Matches the universal `TreeHaver::Node` API
76
+
77
+ ### Fixed
78
+
79
+ - **Backend conflict detection**: Fixed bug where MRI backend usage wasn't being recorded during availability checks
80
+ - `mri_backend_available?` now calls `TreeHaver.record_backend_usage(:mri)` after successfully loading ruby_tree_sitter
81
+ - This ensures FFI conflict detection works correctly even when MRI is loaded indirectly
82
+
83
+ - **GrammarFinder#not_found_message**: Improved error message when grammar file exists but no tree-sitter runtime is available
84
+ - Now suggests adding `ruby_tree_sitter`, `ffi`, or `tree_stump` gem to Gemfile
85
+ - Clearer guidance for users who have grammar files but are missing the Ruby tree-sitter bindings
86
+
33
87
  ## [3.1.0] - 2025-12-18
34
88
 
35
89
  - TAG: [v3.1.0][3.1.0t]
@@ -365,7 +419,9 @@ Despite the major version bump to 3.0.0 (following semver due to the breaking `L
365
419
 
366
420
  - Initial release
367
421
 
368
- [Unreleased]: https://github.com/kettle-rb/tree_haver/compare/v3.1.0...HEAD
422
+ [Unreleased]: https://github.com/kettle-rb/tree_haver/compare/v3.1.1...HEAD
423
+ [3.1.1]: https://github.com/kettle-rb/tree_haver/compare/v3.1.0...v3.1.1
424
+ [3.1.1t]: https://github.com/kettle-rb/tree_haver/releases/tag/v3.1.1
369
425
  [3.1.0]: https://github.com/kettle-rb/tree_haver/compare/v3.0.0...v3.1.0
370
426
  [3.1.0t]: https://github.com/kettle-rb/tree_haver/releases/tag/v3.1.0
371
427
  [3.0.0]: https://github.com/kettle-rb/tree_haver/compare/v2.0.0...v3.0.0
data/README.md CHANGED
@@ -91,17 +91,17 @@ tree = parser.parse(source_code)
91
91
  - **10 Parsing Backends** - Choose the right backend for your needs:
92
92
  - **Tree-sitter Backends** (high-performance, incremental parsing):
93
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/anthropics/tree_stump) gem (Rust with precompiled binaries)
95
- - **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](https://github.com/joker1007/tree_stump/pull/13) are merged
94
+ - **Rust Backend**: Uses [`tree_stump`][tree_stump] gem (Rust with precompiled binaries)
95
+ - **Note**: `tree_stump` currently requires unreleased fixes in the `main` branch.
96
96
  - **FFI Backend**: Pure Ruby FFI bindings to `libtree-sitter` (ideal for JRuby, TruffleRuby)
97
- - **Java Backend**: Native Java integration for JRuby with java-tree-sitter grammar JARs
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
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)
99
+ - **Prism Backend**: Ruby's official parser ([Prism][prism], stdlib in Ruby 3.4+)
100
+ - **Psych Backend**: Ruby's YAML parser ([Psych][psych], stdlib)
101
+ - **Commonmarker Backend**: Fast Markdown parser ([Commonmarker][commonmarker], comrak Rust)
102
+ - **Markly Backend**: GitHub Flavored Markdown ([Markly][markly], cmark-gfm C)
103
103
  - **Pure Ruby Fallback**:
104
- - **Citrus Backend**: Pure Ruby parsing via [`citrus`](https://github.com/mjackson/citrus) (no native dependencies)
104
+ - **Citrus Backend**: Pure Ruby parsing via [`citrus`][citrus] (no native dependencies)
105
105
  - **Automatic Backend Selection**: Intelligently selects the best backend for your Ruby implementation
106
106
  - **Language Agnostic**: Parse any language - Ruby, Markdown, YAML, JSON, Bash, TOML, JavaScript, etc.
107
107
  - **Grammar Discovery**: Built-in `GrammarFinder` utility for platform-aware grammar library discovery
@@ -136,11 +136,11 @@ gem "ruby_tree_sitter", "~> 2.0"
136
136
 
137
137
  #### Rust Backend (tree_stump)
138
138
 
139
- Currently requires [pboling's fork](https://github.com/pboling/tree_stump/tree/tree_haver) until upstream PRs are merged.
139
+ NOTE: `tree_stump` currently requires unreleased fixes in the `main` branch.
140
140
 
141
141
  ```ruby
142
142
  # Add to your Gemfile for Rust backend
143
- gem "tree_stump", github: "pboling/tree_stump", branch: "tree_haver"
143
+ gem "tree_stump", github: "joker1007/tree_stump", branch: "main"
144
144
  ```
145
145
 
146
146
  #### FFI Backend
@@ -175,7 +175,7 @@ gem "citrus", "~> 3.0"
175
175
 
176
176
  #### Java Backend (JRuby only)
177
177
 
178
- No additional dependencies required beyond grammar JARs built for java-tree-sitter.
178
+ No additional dependencies required beyond grammar JARs built for java-tree-sitter / jtreesitter.
179
179
 
180
180
  ### Why TreeHaver?
181
181
 
@@ -187,6 +187,58 @@ tree-sitter is a powerful parser generator that creates incremental parsers for
187
187
 
188
188
  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
189
 
190
+ ### The `*-merge` Gem Family
191
+
192
+ 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.
193
+
194
+ | Gem | Format | Parser Backend(s) | Description |
195
+ |------------------------------------------|----------|-----------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------|
196
+ | [tree_haver][tree_haver] | Multi | MRI C, Rust, FFI, Java, Prism, Psych, Commonmarker, Markly, Citrus | **Foundation**: Cross-Ruby adapter for parsing libraries (like Faraday for HTTP) |
197
+ | [ast-merge][ast-merge] | Text | internal | **Infrastructure**: Shared base classes and merge logic for all `*-merge` gems |
198
+ | [prism-merge][prism-merge] | Ruby | [Prism][prism] | Smart merge for Ruby source files |
199
+ | [psych-merge][psych-merge] | YAML | [Psych][psych] | Smart merge for YAML files |
200
+ | [json-merge][json-merge] | JSON | [tree-sitter-json][ts-json] (via tree_haver) | Smart merge for JSON files |
201
+ | [jsonc-merge][jsonc-merge] | JSONC | [tree-sitter-jsonc][ts-jsonc] (via tree_haver) | ⚠️ Proof of concept; Smart merge for JSON with Comments |
202
+ | [bash-merge][bash-merge] | Bash | [tree-sitter-bash][ts-bash] (via tree_haver) | Smart merge for Bash scripts |
203
+ | [rbs-merge][rbs-merge] | RBS | [RBS][rbs] | Smart merge for Ruby type signatures |
204
+ | [dotenv-merge][dotenv-merge] | Dotenv | internal | Smart merge for `.env` files |
205
+ | [toml-merge][toml-merge] | TOML | [Citrus + toml-rb][toml-rb] (default, via tree_haver), [tree-sitter-toml][ts-toml] (via tree_haver) | Smart merge for TOML files |
206
+ | [markdown-merge][markdown-merge] | Markdown | [Commonmarker][commonmarker] / [Markly][markly] (via tree_haver) | **Foundation**: Shared base for Markdown mergers with inner code block merging |
207
+ | [markly-merge][markly-merge] | Markdown | [Markly][markly] (via tree_haver) | Smart merge for Markdown (CommonMark via cmark-gfm C) |
208
+ | [commonmarker-merge][commonmarker-merge] | Markdown | [Commonmarker][commonmarker] (via tree_haver) | Smart merge for Markdown (CommonMark via comrak Rust) |
209
+
210
+ **Example implementations** for the gem templating use case:
211
+
212
+ | Gem | Purpose | Description |
213
+ |-----|---------|-------------|
214
+ | [kettle-dev][kettle-dev] | Gem Development | Gem templating tool using `*-merge` gems |
215
+ | [kettle-jem][kettle-jem] | Gem Templating | Gem template library with smart merge support |
216
+
217
+ [tree_haver]: https://github.com/kettle-rb/tree_haver
218
+ [ast-merge]: https://github.com/kettle-rb/ast-merge
219
+ [prism-merge]: https://github.com/kettle-rb/prism-merge
220
+ [psych-merge]: https://github.com/kettle-rb/psych-merge
221
+ [json-merge]: https://github.com/kettle-rb/json-merge
222
+ [jsonc-merge]: https://github.com/kettle-rb/jsonc-merge
223
+ [bash-merge]: https://github.com/kettle-rb/bash-merge
224
+ [rbs-merge]: https://github.com/kettle-rb/rbs-merge
225
+ [dotenv-merge]: https://github.com/kettle-rb/dotenv-merge
226
+ [toml-merge]: https://github.com/kettle-rb/toml-merge
227
+ [markdown-merge]: https://github.com/kettle-rb/markdown-merge
228
+ [markly-merge]: https://github.com/kettle-rb/markly-merge
229
+ [commonmarker-merge]: https://github.com/kettle-rb/commonmarker-merge
230
+ [kettle-dev]: https://github.com/kettle-rb/kettle-dev
231
+ [kettle-jem]: https://github.com/kettle-rb/kettle-jem
232
+ [prism]: https://github.com/ruby/prism
233
+ [psych]: https://github.com/ruby/psych
234
+ [ts-json]: https://github.com/tree-sitter/tree-sitter-json
235
+ [ts-bash]: https://github.com/tree-sitter/tree-sitter-bash
236
+ [ts-toml]: https://github.com/tree-sitter-grammars/tree-sitter-toml
237
+ [rbs]: https://github.com/ruby/rbs
238
+ [toml-rb]: https://github.com/emancu/toml-rb
239
+ [markly]: https://github.com/ioquatix/markly
240
+ [commonmarker]: https://github.com/gjtorikian/commonmarker
241
+
190
242
  ### Comparison with Other Ruby AST / Parser Bindings
191
243
 
192
244
  | Feature | [tree_haver] (this gem) | [ruby_tree_sitter] | [tree_stump] | [citrus] |
@@ -206,15 +258,15 @@ TreeHaver solves these problems by providing a unified API that automatically se
206
258
  | **Minimum Ruby** | 3.2+ | 3.0+ | 3.1+ | 0+ |
207
259
 
208
260
  [ruby_tree_sitter]: https://github.com/Faveod/ruby-tree-sitter
209
- [tree_stump]: https://github.com/anthropics/tree_stump
261
+ [tree_stump]: https://github.com/joker1007/tree_stump
210
262
  [citrus]: https://github.com/mjackson/citrus
211
263
  [tree_haver]: https://github.com/kettle-rb/tree_haver
212
264
 
213
- **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.
265
+ **Note:** Java backend works with grammar JARs built specifically for `java-tree-sitter` / `jtreesitter`, or grammar .so files that statically link tree-sitter. This is why FFI is recommended for JRuby & TruffleRuby.
214
266
 
215
- **Note:** TreeHaver can use `ruby_tree_sitter` (MRI) or `tree_stump` (MRI, JRuby?) as backends, or `jruby-tree-sitter` (JRuby), giving you TreeHaver's unified API, grammar discovery, and security features, plus full access to incremental parsing when using those backends.
267
+ **Note:** TreeHaver can use `ruby_tree_sitter` (MRI) or `tree_stump` (MRI, JRuby?) as backends, or `java-tree-sitter` ([docs](https://tree-sitter.github.io/java-tree-sitter/), [maven](https://central.sonatype.com/artifact/io.github.tree-sitter/jtreesitter), [source](https://github.com/tree-sitter/java-tree-sitter), JRuby), or FFI on any backend, giving you TreeHaver's unified API, grammar discovery, and security features, plus full access to incremental parsing when using those backends.
216
268
 
217
- **Note:** `tree_stump` currently requires [pboling's fork (tree_haver branch)](https://github.com/pboling/tree_stump/tree/tree_haver) until upstream 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](https://github.com/joker1007/tree_stump/pull/13) are merged.
269
+ **Note:** `tree_stump` currently requires unreleased fixes in the `main` branch.
218
270
 
219
271
  #### When to Use Each
220
272
 
@@ -239,7 +291,7 @@ TreeHaver solves these problems by providing a unified API that automatically se
239
291
  - You prefer Rust-based native extensions
240
292
  - You want precompiled binaries without system dependencies
241
293
  - You don't need TreeHaver's grammar discovery
242
- - **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
294
+ - **Note:** `tree_stump` currently requires unreleased fixes in the `main` branch.
243
295
 
244
296
  **Choose citrus directly when:**
245
297
 
@@ -582,8 +634,8 @@ TreeHaver.backend = :auto
582
634
  # Force a specific backend
583
635
  TreeHaver.backend = :mri # Use ruby_tree_sitter (MRI only, C extension)
584
636
  TreeHaver.backend = :rust # Use tree_stump (MRI, Rust extension with precompiled binaries)
585
- # Note: Requires pboling's fork until PRs #5, #7, #11, #13 are merged
586
- # See: https://github.com/pboling/tree_stump/tree/tree_haver
637
+ # Note: `tree_stump` currently requires unreleased fixes in the `main` branch.
638
+ # See: https://github.com/joker1007/tree_stump
587
639
  TreeHaver.backend = :ffi # Use FFI bindings (works on MRI and JRuby)
588
640
  TreeHaver.backend = :java # Use Java bindings (JRuby only, coming soon)
589
641
  TreeHaver.backend = :citrus # Use Citrus pure Ruby parser
@@ -660,6 +712,8 @@ For the Java backend on JRuby:
660
712
  export TREE_SITTER_JAVA_JARS_DIR=/path/to/java-tree-sitter/jars
661
713
  ```
662
714
 
715
+ For more see [docs](https://tree-sitter.github.io/java-tree-sitter/), [maven](https://central.sonatype.com/artifact/io.github.tree-sitter/jtreesitter), and [source](https://github.com/tree-sitter/java-tree-sitter).
716
+
663
717
  ### Language Registration
664
718
 
665
719
  Register languages once at application startup for convenient access:
@@ -887,6 +941,44 @@ See `lib/tree_haver/compat.rb` for compatibility layer documentation.
887
941
 
888
942
  ### Quick Start
889
943
 
944
+ 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
+
946
+ ```ruby
947
+ require "tree_haver"
948
+
949
+ # Parse TOML - auto-discovers grammar and falls back to Citrus if needed
950
+ parser = TreeHaver.parser_for(:toml)
951
+ tree = parser.parse("[package]\nname = \"my-app\"")
952
+
953
+ # Parse JSON
954
+ parser = TreeHaver.parser_for(:json)
955
+ tree = parser.parse('{"key": "value"}')
956
+
957
+ # Parse Bash
958
+ parser = TreeHaver.parser_for(:bash)
959
+ tree = parser.parse("#!/bin/bash\necho hello")
960
+
961
+ # With explicit library path
962
+ parser = TreeHaver.parser_for(:toml, library_path: "/custom/path/libtree-sitter-toml.so")
963
+
964
+ # With Citrus fallback configuration
965
+ parser = TreeHaver.parser_for(
966
+ :toml,
967
+ citrus_config: {gem_name: "toml-rb", grammar_const: "TomlRB::Document"},
968
+ )
969
+ ```
970
+
971
+ `TreeHaver.parser_for` handles:
972
+ 1. Checking if the language is already registered
973
+ 2. Auto-discovering tree-sitter grammar via `GrammarFinder`
974
+ 3. Falling back to Citrus grammar if tree-sitter is unavailable
975
+ 4. Creating and configuring the parser
976
+ 5. Raising `NotAvailable` with a helpful message if nothing works
977
+
978
+ ### Manual Parser Setup
979
+
980
+ For more control, you can create parsers manually:
981
+
890
982
  TreeHaver works with any language through its 10 backends. Here are examples for different parsing needs:
891
983
 
892
984
  #### Parsing with Tree-sitter (Universal Languages)
@@ -1151,9 +1243,9 @@ tree.edit(
1151
1243
  new_tree = parser.parse_string(tree, "x = 42")
1152
1244
  ```
1153
1245
 
1154
- **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:
1246
+ **Note:** Incremental parsing requires the MRI (`ruby_tree_sitter`), Rust (`tree_stump`), or Java (`java-tree-sitter` / `jtreesitter`) backend. The FFI and Citrus backends do not currently support incremental parsing. You can check support with:
1155
1247
 
1156
- **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.
1248
+ **Note:** `tree_stump` currently requires unreleased fixes in the `main` branch.
1157
1249
 
1158
1250
  ```ruby
1159
1251
  tree.supports_editing? # => true if edit() is available
@@ -1257,7 +1349,7 @@ The FFI backend uses Ruby's FFI gem which relies on the system's dynamic linker,
1257
1349
 
1258
1350
  The Java backend will work with:
1259
1351
 
1260
- - Grammar JARs built specifically for java-tree-sitter (self-contained)
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))
1261
1353
  - Grammar `.so` files that statically link tree-sitter
1262
1354
 
1263
1355
  **Option 3: Citrus Backend (pure Ruby, portable)**
@@ -1436,6 +1528,76 @@ TOML
1436
1528
  package_name = extract_package_name(toml)
1437
1529
  ```
1438
1530
 
1531
+ ### 🧪 RSpec Integration
1532
+
1533
+ TreeHaver provides shared RSpec helpers for conditional test execution based on dependency availability. This is useful for testing code that uses optional backends.
1534
+
1535
+ ```ruby
1536
+ # In your spec_helper.rb
1537
+ require "tree_haver/rspec"
1538
+ ```
1539
+
1540
+ This automatically configures RSpec with exclusion filters for all TreeHaver dependencies. Use tags to conditionally run tests:
1541
+
1542
+ ```ruby
1543
+ # Runs only when FFI backend is available
1544
+ it "parses with FFI", :ffi do
1545
+ # ...
1546
+ end
1547
+
1548
+ # Runs only when ruby_tree_sitter gem is available
1549
+ it "uses MRI backend", :mri_backend do
1550
+ # ...
1551
+ end
1552
+
1553
+ # Runs only when tree-sitter-toml grammar works
1554
+ it "parses TOML", :tree_sitter_toml do
1555
+ # ...
1556
+ end
1557
+
1558
+ # Runs only when any markdown backend is available
1559
+ it "parses markdown", :markdown_backend do
1560
+ # ...
1561
+ end
1562
+ ```
1563
+
1564
+ **Available Tags:**
1565
+
1566
+ | Tag | Description |
1567
+ |-----|-------------|
1568
+ | `:ffi` | FFI backend available (dynamic check) |
1569
+ | `:mri_backend` | ruby_tree_sitter gem available |
1570
+ | `:rust_backend` | tree_stump gem available |
1571
+ | `:java_backend` | Java backend available (JRuby) |
1572
+ | `:prism_backend` | Prism gem available |
1573
+ | `:psych_backend` | Psych available (stdlib) |
1574
+ | `:commonmarker` | commonmarker gem available |
1575
+ | `:markly` | markly gem available |
1576
+ | `:citrus_toml` | toml-rb with Citrus grammar available |
1577
+ | `:jruby` | Running on JRuby |
1578
+ | `:truffleruby` | Running on TruffleRuby |
1579
+ | `:mri` | Running on MRI (CRuby) |
1580
+ | `:tree_sitter_bash` | Bash grammar available and working |
1581
+ | `:tree_sitter_toml` | TOML grammar available and working |
1582
+ | `:tree_sitter_json` | JSON grammar available and working |
1583
+ | `:tree_sitter_jsonc` | JSONC grammar available and working |
1584
+ | `:toml_backend` | Any TOML backend available |
1585
+ | `:markdown_backend` | Any markdown backend available |
1586
+ | `:toml_merge` | toml-merge gem functional |
1587
+ | `:json_merge` | json-merge gem functional |
1588
+ | `:prism_merge` | prism-merge gem functional |
1589
+ | `:psych_merge` | psych-merge gem functional |
1590
+
1591
+ All tags have negated versions (e.g., `:not_mri_backend`, `:not_jruby`) for testing fallback behavior.
1592
+
1593
+ **Debug Output:**
1594
+
1595
+ Set `TREE_HAVER_DEBUG=1` to print a dependency summary at the start of your test suite:
1596
+
1597
+ ```bash
1598
+ TREE_HAVER_DEBUG=1 bundle exec rspec
1599
+ ```
1600
+
1439
1601
  ## 🦷 FLOSS Funding
1440
1602
 
1441
1603
  While kettle-rb tools are free software and will always be, the project would benefit immensely from some funding.
@@ -1466,9 +1628,7 @@ Support us with a monthly donation and help us continue our activities. [[Become
1466
1628
  NOTE: [kettle-readme-backers][kettle-readme-backers] updates this list every day, automatically.
1467
1629
 
1468
1630
  <!-- OPENCOLLECTIVE-INDIVIDUALS:START -->
1469
-
1470
1631
  No backers yet. Be the first!
1471
-
1472
1632
  <!-- OPENCOLLECTIVE-INDIVIDUALS:END -->
1473
1633
 
1474
1634
  ### Open Collective for Organizations
@@ -1478,9 +1638,7 @@ Become a sponsor and get your logo on our README on GitHub with a link to your s
1478
1638
  NOTE: [kettle-readme-backers][kettle-readme-backers] updates this list every day, automatically.
1479
1639
 
1480
1640
  <!-- OPENCOLLECTIVE-ORGANIZATIONS:START -->
1481
-
1482
1641
  No sponsors yet. Be the first!
1483
-
1484
1642
  <!-- OPENCOLLECTIVE-ORGANIZATIONS:END -->
1485
1643
 
1486
1644
  [kettle-readme-backers]: https://github.com/kettle-rb/tree_haver/blob/main/exe/kettle-readme-backers
@@ -1785,7 +1943,7 @@ Thanks for RTFM. ☺️
1785
1943
  [📌gitmoji]: https://gitmoji.dev
1786
1944
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
1787
1945
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
1788
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-1.141-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1946
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-2.461-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1789
1947
  [🔐security]: SECURITY.md
1790
1948
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
1791
1949
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -1805,3 +1963,6 @@ Thanks for RTFM. ☺️
1805
1963
  [💎appraisal2]: https://github.com/appraisal-rb/appraisal2
1806
1964
  [💎appraisal2-img]: https://img.shields.io/badge/appraised_by-appraisal2-34495e.svg?plastic&logo=ruby&logoColor=white
1807
1965
  [💎d-in-dvcs]: https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/
1966
+
1967
+ [ts-jsonc]: https://gitlab.com/WhyNotHugo/tree-sitter-jsonc
1968
+ [dotenv]: https://github.com/bkeepers/dotenv
@@ -249,6 +249,9 @@ module TreeHaver
249
249
  #
250
250
  # @api private
251
251
  class Node
252
+ include Comparable
253
+ include Enumerable
254
+
252
255
  attr_reader :match, :source
253
256
 
254
257
  def initialize(match, source)
@@ -178,6 +178,7 @@ module TreeHaver
178
178
  # Wraps Commonmarker::Node to provide TreeHaver::Node-compatible interface.
179
179
  class Node
180
180
  include Comparable
181
+ include Enumerable
181
182
 
182
183
  attr_reader :inner_node, :source
183
184
 
@@ -428,7 +429,7 @@ module TreeHaver
428
429
 
429
430
  # Get the previous sibling
430
431
  # @return [Node, nil]
431
- def previous_sibling
432
+ def prev_sibling
432
433
  sibling = begin
433
434
  @inner_node.previous_sibling
434
435
  rescue
@@ -63,10 +63,28 @@ module TreeHaver
63
63
 
64
64
  extend(::FFI::Library)
65
65
 
66
+ define_ts_point_struct!
66
67
  define_ts_node_struct!
67
68
  @ffi_extended = true
68
69
  end
69
70
 
71
+ # Define the TSPoint struct lazily
72
+ # @api private
73
+ def define_ts_point_struct!
74
+ return if const_defined?(:TSPoint, false)
75
+
76
+ # FFI struct representation of TSPoint
77
+ # Mirrors the C struct layout: struct { uint32_t row; uint32_t column; }
78
+ ts_point_class = Class.new(::FFI::Struct) do
79
+ layout :row,
80
+ :uint32,
81
+ :column,
82
+ :uint32
83
+ end
84
+ const_set(:TSPoint, ts_point_class)
85
+ typedef(ts_point_class.by_value, :ts_point)
86
+ end
87
+
70
88
  # Define the TSNode struct lazily
71
89
  # @api private
72
90
  def define_ts_node_struct!
@@ -168,8 +186,8 @@ module TreeHaver
168
186
  attach_function(:ts_node_child, [:ts_node, :uint32], :ts_node)
169
187
  attach_function(:ts_node_start_byte, [:ts_node], :uint32)
170
188
  attach_function(:ts_node_end_byte, [:ts_node], :uint32)
171
- attach_function(:ts_node_start_point, [:ts_node], :pointer)
172
- attach_function(:ts_node_end_point, [:ts_node], :pointer)
189
+ attach_function(:ts_node_start_point, [:ts_node], :ts_point)
190
+ attach_function(:ts_node_end_point, [:ts_node], :ts_point)
173
191
  attach_function(:ts_node_is_null, [:ts_node], :bool)
174
192
  attach_function(:ts_node_is_named, [:ts_node], :bool)
175
193
  end
@@ -574,6 +592,8 @@ module TreeHaver
574
592
  # Wraps a TSNode by-value struct. TSNode is passed by value in the
575
593
  # tree-sitter C API, so we store the struct value directly.
576
594
  class Node
595
+ include Enumerable
596
+
577
597
  # @api private
578
598
  # @param ts_node_value [Native::TSNode] the TSNode struct (by value)
579
599
  def initialize(ts_node_value)
@@ -621,20 +641,20 @@ module TreeHaver
621
641
 
622
642
  # Get start point
623
643
  #
624
- # @return [Object] with row and column
644
+ # @return [TreeHaver::Point] with row and column
625
645
  def start_point
626
- # FFI backend would need to implement ts_node_start_point
627
- # For now, return a simple struct
628
- Struct.new(:row, :column).new(0, Native.ts_node_start_byte(@val))
646
+ point = Native.ts_node_start_point(@val)
647
+ # TSPoint is returned by value as an FFI::Struct with :row and :column fields
648
+ TreeHaver::Point.new(point[:row], point[:column])
629
649
  end
630
650
 
631
651
  # Get end point
632
652
  #
633
- # @return [Object] with row and column
653
+ # @return [TreeHaver::Point] with row and column
634
654
  def end_point
635
- # FFI backend would need to implement ts_node_end_point
636
- # For now, return a simple struct
637
- Struct.new(:row, :column).new(0, Native.ts_node_end_byte(@val))
655
+ point = Native.ts_node_end_point(@val)
656
+ # TSPoint is returned by value as an FFI::Struct with :row and :column fields
657
+ TreeHaver::Point.new(point[:row], point[:column])
638
658
  end
639
659
 
640
660
  # Check if node has error
@@ -661,6 +681,27 @@ module TreeHaver
661
681
  end
662
682
  nil
663
683
  end
684
+
685
+ # Compare nodes for ordering (used by Comparable module)
686
+ #
687
+ # Nodes are ordered by their position in the source:
688
+ # 1. First by start_byte (earlier nodes come first)
689
+ # 2. Then by end_byte for tie-breaking (shorter spans come first)
690
+ # 3. Then by type for deterministic ordering
691
+ #
692
+ # @param other [Node] node to compare with
693
+ # @return [Integer, nil] -1, 0, 1, or nil if not comparable
694
+ def <=>(other)
695
+ return unless other.is_a?(Node)
696
+
697
+ cmp = start_byte <=> other.start_byte
698
+ return cmp if cmp.nonzero?
699
+
700
+ cmp = end_byte <=> other.end_byte
701
+ return cmp if cmp.nonzero?
702
+
703
+ type <=> other.type
704
+ end
664
705
  end
665
706
  end
666
707
  end
@@ -25,7 +25,8 @@ module TreeHaver
25
25
  # jruby -e "require 'tree_haver'; puts TreeHaver::Backends::Java.available?"
26
26
  #
27
27
  # @note Only available on JRuby
28
- # @see https://tree-sitter.github.io/java-tree-sitter/ java-tree-sitter documentation
28
+ # @see https://github.com/tree-sitter/java-tree-sitter source
29
+ # @see https://tree-sitter.github.io/java-tree-sitter java-tree-sitter documentation
29
30
  # @see https://central.sonatype.com/artifact/io.github.tree-sitter/jtreesitter Maven Central
30
31
  module Java
31
32
  # The Java package for java-tree-sitter
@@ -200,6 +200,7 @@ module TreeHaver
200
200
  # - :html instead of :html_block
201
201
  class Node
202
202
  include Comparable
203
+ include Enumerable
203
204
 
204
205
  # Type normalization map (Markly → canonical)
205
206
  TYPE_MAP = {
@@ -497,7 +498,7 @@ module TreeHaver
497
498
 
498
499
  # Get the previous sibling
499
500
  # @return [Node, nil]
500
- def previous_sibling
501
+ def prev_sibling
501
502
  sibling = begin
502
503
  @inner_node.previous
503
504
  rescue
@@ -319,6 +319,8 @@ module TreeHaver
319
319
  #
320
320
  # @api private
321
321
  class Node
322
+ include Enumerable
323
+
322
324
  # @return [::Prism::Node] the underlying Prism node
323
325
  attr_reader :inner_node
324
326
 
@@ -553,27 +555,26 @@ module TreeHaver
553
555
 
554
556
  # Get the parent node
555
557
  #
556
- # @note Prism nodes don't have built-in parent references.
557
- # This always returns nil. Use tree traversal instead.
558
- # @return [nil]
558
+ # @raise [NotImplementedError] Prism nodes don't have parent references
559
+ # @return [void]
559
560
  def parent
560
- nil # Prism doesn't track parent references
561
+ raise NotImplementedError, "Prism backend does not support parent navigation"
561
562
  end
562
563
 
563
564
  # Get next sibling
564
565
  #
565
- # @note Prism nodes don't have sibling references.
566
- # @return [nil]
566
+ # @raise [NotImplementedError] Prism nodes don't have sibling references
567
+ # @return [void]
567
568
  def next_sibling
568
- nil
569
+ raise NotImplementedError, "Prism backend does not support sibling navigation"
569
570
  end
570
571
 
571
572
  # Get previous sibling
572
573
  #
573
- # @note Prism nodes don't have sibling references.
574
- # @return [nil]
574
+ # @raise [NotImplementedError] Prism nodes don't have sibling references
575
+ # @return [void]
575
576
  def prev_sibling
576
- nil
577
+ raise NotImplementedError, "Prism backend does not support sibling navigation"
577
578
  end
578
579
 
579
580
  # String representation for debugging