tree_haver 3.1.0 → 3.1.2
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 +78 -1
- data/README.md +187 -26
- data/lib/tree_haver/backends/citrus.rb +3 -0
- data/lib/tree_haver/backends/commonmarker.rb +2 -1
- data/lib/tree_haver/backends/ffi.rb +51 -10
- data/lib/tree_haver/backends/java.rb +2 -1
- data/lib/tree_haver/backends/markly.rb +2 -1
- data/lib/tree_haver/backends/mri.rb +10 -0
- data/lib/tree_haver/backends/prism.rb +11 -10
- data/lib/tree_haver/backends/psych.rb +25 -0
- data/lib/tree_haver/backends/rust.rb +1 -1
- data/lib/tree_haver/grammar_finder.rb +24 -2
- data/lib/tree_haver/language_registry.rb +6 -0
- data/lib/tree_haver/node.rb +1 -34
- data/lib/tree_haver/point.rb +65 -0
- data/lib/tree_haver/rspec/dependency_tags.rb +744 -0
- data/lib/tree_haver/rspec.rb +23 -0
- data/lib/tree_haver/tree.rb +1 -1
- data/lib/tree_haver/version.rb +1 -1
- data/lib/tree_haver.rb +108 -0
- data.tar.gz.sig +0 -0
- metadata +28 -5
- metadata.gz.sig +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1f88732dc79b665917e925613ebeed944e011bb3ecb20248ebc940a48069fd41
|
|
4
|
+
data.tar.gz: '093f3bc6b818083e4205598dcef5782d2fb201e742464758c9c00f99ff3fba50'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2290e9e88865420ba8c39bb26af72e866797a5efeff30177b6c34d10d066014daf4e473f2a240647428d6d452bd0a46aa8828da5b2161723a37b286508c075c6
|
|
7
|
+
data.tar.gz: f31f3e85b30e4558cfbd7c6beb5307938c0a2aceb369b660bf16a3291beac095e5d2eb71291057c3ed49c280c56a85968d0d9bdb04b2d34406ec68c356372338
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,79 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
30
30
|
|
|
31
31
|
### Security
|
|
32
32
|
|
|
33
|
+
## [3.1.2] - 2025-12-29
|
|
34
|
+
|
|
35
|
+
- TAG: [v3.1.2][3.1.2t]
|
|
36
|
+
- COVERAGE: 87.40% -- 2171/2484 lines in 22 files
|
|
37
|
+
- BRANCH COVERAGE: 67.04% -- 726/1083 branches in 22 files
|
|
38
|
+
- 90.03% documented
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- Enhanced `TreeHaver::RSpec::DependencyTags` debugging
|
|
43
|
+
- `env_summary` method returns relevant environment variables for diagnosis
|
|
44
|
+
- `grammar_works?` now logs detailed trace when `TREE_HAVER_DEBUG=1`
|
|
45
|
+
- `before(:suite)` prints both env vars and dependency status when debugging
|
|
46
|
+
- Helps diagnose differences between local and CI environments
|
|
47
|
+
- Many new specs for:
|
|
48
|
+
- TreeHaver::GrammarFinder
|
|
49
|
+
- TreeHaver::Node
|
|
50
|
+
- TreeHaver::Tree
|
|
51
|
+
|
|
52
|
+
## [3.1.1] - 2025-12-28
|
|
53
|
+
|
|
54
|
+
- TAG: [v3.1.1][3.1.1t]
|
|
55
|
+
- COVERAGE: 87.44% -- 2152/2461 lines in 22 files
|
|
56
|
+
- BRANCH COVERAGE: 66.67% -- 710/1065 branches in 22 files
|
|
57
|
+
- 90.02% documented
|
|
58
|
+
|
|
59
|
+
### Added
|
|
60
|
+
|
|
61
|
+
- **`TreeHaver::RSpec::DependencyTags`**: Shared RSpec dependency detection for the entire gem family
|
|
62
|
+
- New `lib/tree_haver/rspec.rb` entry point - other gems can simply `require "tree_haver/rspec"`
|
|
63
|
+
- Detects all TreeHaver backends: FFI, MRI, Rust, Java, Prism, Psych, Commonmarker, Markly, Citrus
|
|
64
|
+
- Ruby engine detection: `jruby?`, `truffleruby?`, `mri?`
|
|
65
|
+
- Language grammar detection: `tree_sitter_bash_available?`, `tree_sitter_toml_available?`, `tree_sitter_json_available?`, `tree_sitter_jsonc_available?`
|
|
66
|
+
- Inner-merge dependency detection: `toml_merge_available?`, `json_merge_available?`, `prism_merge_available?`, `psych_merge_available?`
|
|
67
|
+
- Composite checks: `any_toml_backend_available?`, `any_markdown_backend_available?`
|
|
68
|
+
- Records MRI backend usage when checking availability (critical for FFI conflict detection)
|
|
69
|
+
- Configures RSpec exclusion filters for all dependency tags automatically
|
|
70
|
+
- Supports debug output via `TREE_HAVER_DEBUG=1` environment variable
|
|
71
|
+
- Comprehensive documentation with usage examples
|
|
72
|
+
|
|
73
|
+
- **`TreeHaver.parser_for`**: New high-level factory method for creating configured parsers
|
|
74
|
+
- Handles all language loading complexity in one call
|
|
75
|
+
- Auto-discovers tree-sitter grammar via `GrammarFinder`
|
|
76
|
+
- Falls back to Citrus grammar if tree-sitter unavailable
|
|
77
|
+
- Accepts `library_path` for explicit grammar location
|
|
78
|
+
- Accepts `citrus_config` for Citrus fallback configuration
|
|
79
|
+
- Raises `NotAvailable` with helpful message if no backend works
|
|
80
|
+
- Example: `parser = TreeHaver.parser_for(:toml)`
|
|
81
|
+
- Raises `NotAvailable` if the specified path doesn't exist (Principle of Least Surprise)
|
|
82
|
+
- Does not back to auto-discovery when an explicit path is provided
|
|
83
|
+
- Re-raises with context-rich error message if loading from explicit path fails
|
|
84
|
+
- Auto-discovery still works normally when no `library_path` is provided
|
|
85
|
+
|
|
86
|
+
### Changed
|
|
87
|
+
|
|
88
|
+
- **Backend sibling navigation**: Backends that don't support sibling/parent navigation now raise `NotImplementedError` instead of returning `nil`
|
|
89
|
+
- This distinguishes "not implemented" from "no sibling exists"
|
|
90
|
+
- Affected backends: Prism, Psych
|
|
91
|
+
- Affected methods: `next_sibling`, `prev_sibling`, `parent`
|
|
92
|
+
|
|
93
|
+
- **Canonical sibling method name**: All backends now use `prev_sibling` as the canonical method name (not `previous_sibling`)
|
|
94
|
+
- Matches the universal `TreeHaver::Node` API
|
|
95
|
+
|
|
96
|
+
### Fixed
|
|
97
|
+
|
|
98
|
+
- **Backend conflict detection**: Fixed bug where MRI backend usage wasn't being recorded during availability checks
|
|
99
|
+
- `mri_backend_available?` now calls `TreeHaver.record_backend_usage(:mri)` after successfully loading ruby_tree_sitter
|
|
100
|
+
- This ensures FFI conflict detection works correctly even when MRI is loaded indirectly
|
|
101
|
+
|
|
102
|
+
- **GrammarFinder#not_found_message**: Improved error message when grammar file exists but no tree-sitter runtime is available
|
|
103
|
+
- Now suggests adding `ruby_tree_sitter`, `ffi`, or `tree_stump` gem to Gemfile
|
|
104
|
+
- Clearer guidance for users who have grammar files but are missing the Ruby tree-sitter bindings
|
|
105
|
+
|
|
33
106
|
## [3.1.0] - 2025-12-18
|
|
34
107
|
|
|
35
108
|
- TAG: [v3.1.0][3.1.0t]
|
|
@@ -365,7 +438,11 @@ Despite the major version bump to 3.0.0 (following semver due to the breaking `L
|
|
|
365
438
|
|
|
366
439
|
- Initial release
|
|
367
440
|
|
|
368
|
-
[Unreleased]: https://github.com/kettle-rb/tree_haver/compare/v3.1.
|
|
441
|
+
[Unreleased]: https://github.com/kettle-rb/tree_haver/compare/v3.1.2...HEAD
|
|
442
|
+
[3.1.2]: https://github.com/kettle-rb/tree_haver/compare/v3.1.1...v3.1.2
|
|
443
|
+
[3.1.2t]: https://github.com/kettle-rb/tree_haver/releases/tag/v3.1.2
|
|
444
|
+
[3.1.1]: https://github.com/kettle-rb/tree_haver/compare/v3.1.0...v3.1.1
|
|
445
|
+
[3.1.1t]: https://github.com/kettle-rb/tree_haver/releases/tag/v3.1.1
|
|
369
446
|
[3.1.0]: https://github.com/kettle-rb/tree_haver/compare/v3.0.0...v3.1.0
|
|
370
447
|
[3.1.0t]: https://github.com/kettle-rb/tree_haver/releases/tag/v3.1.0
|
|
371
448
|
[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`]
|
|
95
|
-
- **Note**:
|
|
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]
|
|
100
|
-
- **Psych Backend**: Ruby's YAML parser ([Psych]
|
|
101
|
-
- **Commonmarker Backend**: Fast Markdown parser ([Commonmarker]
|
|
102
|
-
- **Markly Backend**: GitHub Flavored Markdown ([Markly]
|
|
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`]
|
|
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
|
-
|
|
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: "
|
|
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/
|
|
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
|
|
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 `
|
|
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
|
|
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:**
|
|
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:
|
|
586
|
-
# See: https://github.com/
|
|
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
|
|
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-
|
|
1946
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-2.484-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
|
|
@@ -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
|
|
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], :
|
|
172
|
-
attach_function(:ts_node_end_point, [:ts_node], :
|
|
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 [
|
|
644
|
+
# @return [TreeHaver::Point] with row and column
|
|
625
645
|
def start_point
|
|
626
|
-
|
|
627
|
-
#
|
|
628
|
-
|
|
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 [
|
|
653
|
+
# @return [TreeHaver::Point] with row and column
|
|
634
654
|
def end_point
|
|
635
|
-
|
|
636
|
-
#
|
|
637
|
-
|
|
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://
|
|
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
|
|
501
|
+
def prev_sibling
|
|
501
502
|
sibling = begin
|
|
502
503
|
@inner_node.previous
|
|
503
504
|
rescue
|
|
@@ -18,10 +18,20 @@ module TreeHaver
|
|
|
18
18
|
#
|
|
19
19
|
# Attempts to require ruby_tree_sitter on first call and caches the result.
|
|
20
20
|
#
|
|
21
|
+
# @note When this method returns true, the FFI backend becomes permanently
|
|
22
|
+
# unavailable for the remainder of the process. This is because loading
|
|
23
|
+
# ruby_tree_sitter defines `::TreeSitter::Parser`, which the FFI backend
|
|
24
|
+
# checks to detect conflicts. The MRI backend statically links tree-sitter,
|
|
25
|
+
# while FFI dynamically links libtree-sitter.so - when both are loaded,
|
|
26
|
+
# FFI will segfault when trying to set a language on a parser due to
|
|
27
|
+
# incompatible pointer types from different tree-sitter builds.
|
|
28
|
+
#
|
|
21
29
|
# @return [Boolean] true if ruby_tree_sitter is available
|
|
30
|
+
# @see TreeHaver::Backends::FFI.available? FFI availability check
|
|
22
31
|
# @example
|
|
23
32
|
# if TreeHaver::Backends::MRI.available?
|
|
24
33
|
# puts "MRI backend is ready"
|
|
34
|
+
# # Note: FFI backend is now blocked for this process
|
|
25
35
|
# end
|
|
26
36
|
class << self
|
|
27
37
|
def available?
|