tree_haver 3.2.6 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +41 -3
- data/README.md +94 -12
- data/lib/tree_haver/backend_registry.rb +234 -0
- data/lib/tree_haver/backends/citrus.rb +7 -1
- data/lib/tree_haver/backends/ffi.rb +36 -9
- data/lib/tree_haver/backends/java.rb +59 -61
- data/lib/tree_haver/backends/mri.rb +38 -11
- data/lib/tree_haver/backends/prism.rb +67 -234
- data/lib/tree_haver/backends/psych.rb +75 -346
- data/lib/tree_haver/backends/rust.rb +32 -11
- data/lib/tree_haver/base/language.rb +98 -0
- data/lib/tree_haver/base/node.rb +315 -0
- data/lib/tree_haver/base/parser.rb +24 -0
- data/lib/tree_haver/base/point.rb +48 -0
- data/lib/tree_haver/base/tree.rb +128 -0
- data/lib/tree_haver/base.rb +12 -0
- data/lib/tree_haver/node.rb +14 -10
- data/lib/tree_haver/rspec/dependency_tags.rb +9 -5
- data/lib/tree_haver/tree.rb +4 -5
- data/lib/tree_haver/version.rb +2 -2
- data/lib/tree_haver.rb +30 -18
- data.tar.gz.sig +0 -0
- metadata +11 -6
- metadata.gz.sig +0 -0
- data/lib/tree_haver/backends/commonmarker.rb +0 -516
- data/lib/tree_haver/backends/markly.rb +0 -590
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6a3704393a089cd839f41b5bd1b84c159bfef219ea41ba993c73266c00183004
|
|
4
|
+
data.tar.gz: 149a7ff9f6812c0173b17e6a0646d802843da9bce67e6c29054b422e98bb867e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 03dc62871eac66744c11ca4adbcad33913d52bb9e1134175bdae549faf339ff6206fb4e98087441ffe74a3cba8f2f49684649f8591442e8a38ed74ce8ada50f8
|
|
7
|
+
data.tar.gz: ef5413e0bb4e2b147fe9e97d560a71994b4324f9e26837752783b27d59fa935da5890c9b1b1a193f2867164f349d522a42e5ec009fabff3b07786d35fdddd928
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
# Changelog
|
|
2
2
|
|
|
3
3
|
[![SemVer 2.0.0][📌semver-img]][📌semver] [![Keep-A-Changelog 1.0.0][📗keep-changelog-img]][📗keep-changelog]
|
|
4
4
|
|
|
@@ -30,6 +30,42 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
30
30
|
|
|
31
31
|
### Security
|
|
32
32
|
|
|
33
|
+
## [4.0.0] - 2026-01-08
|
|
34
|
+
|
|
35
|
+
- TAG: [v4.0.0][4.0.0t]
|
|
36
|
+
- COVERAGE: 95.31% -- 2031/2131 lines in 28 files
|
|
37
|
+
- BRANCH COVERAGE: 84.21% -- 805/956 branches in 28 files
|
|
38
|
+
- 95.48% documented
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- **BackendRegistry**: New `TreeHaver::BackendRegistry` module for registering backend availability checkers
|
|
43
|
+
- Allows external gems (like `commonmarker-merge`, `markly-merge`, `rbs-merge`) to register their availability checkers
|
|
44
|
+
- `register_availability_checker(backend_name, &block)` - Register a callable that returns true if backend is available
|
|
45
|
+
- `available?(backend_name)` - Check if a backend is available (results are cached)
|
|
46
|
+
- `registered?(backend_name)` - Check if a checker is registered
|
|
47
|
+
- `registered_backends` - Get all registered backend names
|
|
48
|
+
- Used by `TreeHaver::RSpec::DependencyTags` for dynamic backend detection
|
|
49
|
+
- **Plugin System**: `commonmarker-merge` and `markly-merge` now provide their own backends via `TreeHaver`'s registry system, removing them from `TreeHaver` core.
|
|
50
|
+
- **Backend Architecture Documentation**: Added comprehensive documentation to base classes and all tree-sitter backends explaining the two backend categories:
|
|
51
|
+
- Tree-sitter backends (MRI, Rust, FFI, Java): Use `TreeHaver::Tree` and `TreeHaver::Node` wrappers for raw tree-sitter objects
|
|
52
|
+
- Pure-Ruby/Plugin backends (Citrus, Prism, Psych, Commonmarker, Markly): Define own `Backend::X::Tree` and `Backend::X::Node` classes
|
|
53
|
+
|
|
54
|
+
### Changed
|
|
55
|
+
|
|
56
|
+
- **Base Class Inheritance**: `TreeHaver::Tree` and `TreeHaver::Node` now properly inherit from their respective `Base::` classes
|
|
57
|
+
- `TreeHaver::Tree < Base::Tree` - inherits `inner_tree`, `source`, `lines` attributes and default implementations
|
|
58
|
+
- `TreeHaver::Node < Base::Node` - inherits `inner_node`, `source`, `lines` attributes and API contract
|
|
59
|
+
- Base classes document the API contract; subclasses document divergence
|
|
60
|
+
- **Base::Node#initialize**: Now accepts keyword arguments `source:` and `lines:` instead of positional for consistency with subclasses
|
|
61
|
+
- **DependencyTags**: Now uses `BackendRegistry.available?(:backend_name)` instead of hardcoded `TreeHaver::Backends::*` checks
|
|
62
|
+
- **TreeHaver**: `commonmarker` and `markly` backends are no longer built-in. Use `commonmarker-merge` and `markly-merge` gems which register themselves.
|
|
63
|
+
- **All backends**: Now register their availability checkers with `BackendRegistry` when loaded (MRI, Rust, FFI, Java, Prism, Psych, Citrus)
|
|
64
|
+
|
|
65
|
+
### Removed
|
|
66
|
+
|
|
67
|
+
- **TreeHaver**: Removed `TreeHaver::Backends::Commonmarker` and `TreeHaver::Backends::Markly` modules. These implementations have moved to their respective gems.
|
|
68
|
+
|
|
33
69
|
## [3.2.6] - 2026-01-06
|
|
34
70
|
|
|
35
71
|
- TAG: [v3.2.6][3.2.6t]
|
|
@@ -915,7 +951,9 @@ Despite the major version bump to 3.0.0 (following semver due to the breaking `L
|
|
|
915
951
|
|
|
916
952
|
- Initial release
|
|
917
953
|
|
|
918
|
-
[Unreleased]: https://github.com/kettle-rb/tree_haver/compare/
|
|
954
|
+
[Unreleased]: https://github.com/kettle-rb/tree_haver/compare/v4.0.0...HEAD
|
|
955
|
+
[4.0.0]: https://github.com/kettle-rb/tree_haver/compare/v3.2.6...v4.0.0
|
|
956
|
+
[4.0.0t]: https://github.com/kettle-rb/tree_haver/releases/tag/v4.0.0
|
|
919
957
|
[3.2.6]: https://github.com/kettle-rb/tree_haver/compare/v3.2.5...v3.2.6
|
|
920
958
|
[3.2.6t]: https://github.com/kettle-rb/tree_haver/releases/tag/v3.2.6
|
|
921
959
|
[3.2.5]: https://github.com/kettle-rb/tree_haver/compare/v3.2.4...v3.2.5
|
|
@@ -941,4 +979,4 @@ Despite the major version bump to 3.0.0 (following semver due to the breaking `L
|
|
|
941
979
|
[2.0.0]: https://github.com/kettle-rb/tree_haver/compare/v1.0.0...v2.0.0
|
|
942
980
|
[2.0.0t]: https://github.com/kettle-rb/tree_haver/releases/tag/v2.0.0
|
|
943
981
|
[1.0.0]: https://github.com/kettle-rb/tree_haver/compare/a89211bff10f4440b96758a8ac9d7d539001b0c8...v1.0.0
|
|
944
|
-
[1.0.0t]: https://github.com/kettle-rb/tree_haver/tags/v1.0.0
|
|
982
|
+
[1.0.0t]: https://github.com/kettle-rb/tree_haver/tags/v1.0.0
|
data/README.md
CHANGED
|
@@ -341,12 +341,36 @@ The `*-merge` gem family provides intelligent, AST-based merging for various fil
|
|
|
341
341
|
| [rbs-merge][rbs-merge] | RBS | [tree-sitter-bash][ts-rbs] (via tree_haver), [RBS][rbs] (`rbs` std lib gem) | Smart merge for Ruby type signatures |
|
|
342
342
|
| [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 |
|
|
343
343
|
|
|
344
|
+
#### Backend Platform Compatibility
|
|
345
|
+
|
|
346
|
+
tree_haver supports multiple parsing backends, but not all backends work on all Ruby platforms:
|
|
347
|
+
|
|
348
|
+
| Platform 👉️<br> TreeHaver Backend 👇️ | MRI | JRuby | TruffleRuby | Notes |
|
|
349
|
+
|------------------------------------------------|:---:|:-----:|:-----------:|-----------------------------------------------------|
|
|
350
|
+
| **MRI** ([ruby_tree_sitter][ruby_tree_sitter]) | ✅ | ❌ | ❌ | C extension, MRI only |
|
|
351
|
+
| **Rust** ([tree_stump][tree_stump]) | ✅ | ❌ | ❌ | Rust extension via magnus/rb-sys, MRI only |
|
|
352
|
+
| **FFI** | ✅ | ✅ | ❌ | TruffleRuby's FFI doesn't support `STRUCT_BY_VALUE` |
|
|
353
|
+
| **Java** ([jtreesitter][jtreesitter]) | ❌ | ✅ | ❌ | JRuby only, requires grammar JARs |
|
|
354
|
+
| **Prism** | ✅ | ✅ | ✅ | Ruby parsing, stdlib in Ruby 3.4+ |
|
|
355
|
+
| **Psych** | ✅ | ✅ | ✅ | YAML parsing, stdlib |
|
|
356
|
+
| **Citrus** | ✅ | ✅ | ✅ | Pure Ruby, no native dependencies |
|
|
357
|
+
| **Commonmarker** | ✅ | ❌ | ❓ | Rust extension for Markdown |
|
|
358
|
+
| **Markly** | ✅ | ❌ | ❓ | C extension for Markdown |
|
|
359
|
+
|
|
360
|
+
**Legend**: ✅ = Works, ❌ = Does not work, ❓ = Untested
|
|
361
|
+
|
|
362
|
+
**Why some backends don't work on certain platforms**:
|
|
363
|
+
|
|
364
|
+
- **JRuby**: Runs on the JVM; cannot load native C/Rust extensions (`.so` files)
|
|
365
|
+
- **TruffleRuby**: Has C API emulation via Sulong/LLVM, but it doesn't expose all MRI internals that native extensions require (e.g., `RBasic.flags`, `rb_gc_writebarrier`)
|
|
366
|
+
- **FFI on TruffleRuby**: TruffleRuby's FFI implementation doesn't support returning structs by value, which tree-sitter's C API requires
|
|
367
|
+
|
|
344
368
|
**Example implementations** for the gem templating use case:
|
|
345
369
|
|
|
346
|
-
| Gem
|
|
347
|
-
|
|
348
|
-
| [kettle-dev]
|
|
349
|
-
| [kettle-jem]
|
|
370
|
+
| Gem | Purpose | Description |
|
|
371
|
+
|--------------------------|-----------------|-----------------------------------------------|
|
|
372
|
+
| [kettle-dev][kettle-dev] | Gem Development | Gem templating tool using `*-merge` gems |
|
|
373
|
+
| [kettle-jem][kettle-jem] | Gem Templating | Gem template library with smart merge support |
|
|
350
374
|
|
|
351
375
|
[tree_haver]: https://github.com/kettle-rb/tree_haver
|
|
352
376
|
[ast-merge]: https://github.com/kettle-rb/ast-merge
|
|
@@ -366,16 +390,18 @@ The `*-merge` gem family provides intelligent, AST-based merging for various fil
|
|
|
366
390
|
[prism]: https://github.com/ruby/prism
|
|
367
391
|
[psych]: https://github.com/ruby/psych
|
|
368
392
|
[ts-json]: https://github.com/tree-sitter/tree-sitter-json
|
|
393
|
+
[ts-jsonc]: https://gitlab.com/WhyNotHugo/tree-sitter-jsonc
|
|
369
394
|
[ts-bash]: https://github.com/tree-sitter/tree-sitter-bash
|
|
395
|
+
[ts-rbs]: https://github.com/joker1007/tree-sitter-rbs
|
|
370
396
|
[ts-toml]: https://github.com/tree-sitter-grammars/tree-sitter-toml
|
|
397
|
+
[dotenv]: https://github.com/bkeepers/dotenv
|
|
371
398
|
[rbs]: https://github.com/ruby/rbs
|
|
372
399
|
[toml-rb]: https://github.com/emancu/toml-rb
|
|
373
400
|
[markly]: https://github.com/ioquatix/markly
|
|
374
401
|
[commonmarker]: https://github.com/gjtorikian/commonmarker
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
[
|
|
378
|
-
[dotenv]: https://github.com/bkeepers/dotenv
|
|
402
|
+
[ruby_tree_sitter]: https://github.com/Faveod/ruby-tree-sitter
|
|
403
|
+
[tree_stump]: https://github.com/joker1007/tree_stump
|
|
404
|
+
[jtreesitter]: https://central.sonatype.com/artifact/io.github.tree-sitter/jtreesitter
|
|
379
405
|
|
|
380
406
|
### Comparison with Other Ruby AST / Parser Bindings
|
|
381
407
|
|
|
@@ -796,6 +822,65 @@ You can also set the backend via environment variable:
|
|
|
796
822
|
export TREE_HAVER_BACKEND=rust
|
|
797
823
|
```
|
|
798
824
|
|
|
825
|
+
### Backend Registry
|
|
826
|
+
|
|
827
|
+
TreeHaver provides a `BackendRegistry` module that allows external gems to register their backend availability checkers. This enables dynamic backend detection without hardcoding dependencies.
|
|
828
|
+
|
|
829
|
+
#### Registering a Backend Availability Checker
|
|
830
|
+
|
|
831
|
+
External gems (like `commonmarker-merge`, `markly-merge`, `rbs-merge`) can register their availability checker when loaded:
|
|
832
|
+
|
|
833
|
+
```ruby
|
|
834
|
+
# In your gem's backend module
|
|
835
|
+
TreeHaver::BackendRegistry.register_availability_checker(:my_backend) do
|
|
836
|
+
# Return true if backend is available
|
|
837
|
+
require "my_backend_gem"
|
|
838
|
+
true
|
|
839
|
+
rescue LoadError
|
|
840
|
+
false
|
|
841
|
+
end
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
#### Checking Backend Availability
|
|
845
|
+
|
|
846
|
+
```ruby
|
|
847
|
+
# Check if a backend is available
|
|
848
|
+
TreeHaver::BackendRegistry.available?(:commonmarker) # => true/false
|
|
849
|
+
TreeHaver::BackendRegistry.available?(:markly) # => true/false
|
|
850
|
+
TreeHaver::BackendRegistry.available?(:rbs) # => true/false
|
|
851
|
+
|
|
852
|
+
# Check if a checker is registered
|
|
853
|
+
TreeHaver::BackendRegistry.registered?(:my_backend) # => true/false
|
|
854
|
+
|
|
855
|
+
# Get all registered backend names
|
|
856
|
+
TreeHaver::BackendRegistry.registered_backends # => [:mri, :rust, :ffi, ...]
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
#### How It Works
|
|
860
|
+
|
|
861
|
+
1. Built-in backends (MRI, Rust, FFI, Java, Prism, Psych, Citrus) automatically register their checkers when loaded
|
|
862
|
+
2. External gems register their checkers when their backend module is loaded
|
|
863
|
+
3. `TreeHaver::RSpec::DependencyTags` uses the registry to dynamically detect available backends
|
|
864
|
+
4. Results are cached for performance (use `clear_cache!` to reset)
|
|
865
|
+
|
|
866
|
+
#### RSpec Integration
|
|
867
|
+
|
|
868
|
+
The `BackendRegistry` is used by `TreeHaver::RSpec::DependencyTags` to configure RSpec exclusion filters:
|
|
869
|
+
|
|
870
|
+
```ruby
|
|
871
|
+
# In your spec_helper.rb
|
|
872
|
+
require "tree_haver/rspec/dependency_tags"
|
|
873
|
+
|
|
874
|
+
# Then in specs, use tags to skip tests when backends aren't available
|
|
875
|
+
it "requires commonmarker", :commonmarker_backend do
|
|
876
|
+
# This test only runs when commonmarker is available
|
|
877
|
+
end
|
|
878
|
+
|
|
879
|
+
it "requires markly", :markly_backend do
|
|
880
|
+
# This test only runs when markly is available
|
|
881
|
+
end
|
|
882
|
+
```
|
|
883
|
+
|
|
799
884
|
### Environment Variables
|
|
800
885
|
|
|
801
886
|
TreeHaver recognizes several environment variables for configuration:
|
|
@@ -2097,7 +2182,7 @@ Thanks for RTFM. ☺️
|
|
|
2097
2182
|
[📌gitmoji]: https://gitmoji.dev
|
|
2098
2183
|
[📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
|
|
2099
2184
|
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
2100
|
-
[🧮kloc-img]: https://img.shields.io/badge/KLOC-2.
|
|
2185
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-2.131-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
|
2101
2186
|
[🔐security]: SECURITY.md
|
|
2102
2187
|
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
|
2103
2188
|
[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
|
|
@@ -2117,6 +2202,3 @@ Thanks for RTFM. ☺️
|
|
|
2117
2202
|
[💎appraisal2]: https://github.com/appraisal-rb/appraisal2
|
|
2118
2203
|
[💎appraisal2-img]: https://img.shields.io/badge/appraised_by-appraisal2-34495e.svg?plastic&logo=ruby&logoColor=white
|
|
2119
2204
|
[💎d-in-dvcs]: https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/
|
|
2120
|
-
|
|
2121
|
-
[ts-jsonc]: https://gitlab.com/WhyNotHugo/tree-sitter-jsonc
|
|
2122
|
-
[dotenv]: https://github.com/bkeepers/dotenv
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TreeHaver
|
|
4
|
+
# Registry for backend dependency tag availability checkers
|
|
5
|
+
#
|
|
6
|
+
# This module allows external gems (like commonmarker-merge, markly-merge, rbs-merge)
|
|
7
|
+
# to register their availability checker for RSpec dependency tags without
|
|
8
|
+
# TreeHaver needing to know about them directly.
|
|
9
|
+
#
|
|
10
|
+
# == Purpose
|
|
11
|
+
#
|
|
12
|
+
# When running RSpec tests with dependency tags (e.g., `:commonmarker_backend`),
|
|
13
|
+
# TreeHaver needs to know if each backend is available. Rather than hardcoding
|
|
14
|
+
# checks like `TreeHaver::Backends::Commonmarker.available?` (which would fail
|
|
15
|
+
# if the backend module doesn't exist), the BackendRegistry provides a dynamic
|
|
16
|
+
# way for backends to register their availability checkers.
|
|
17
|
+
#
|
|
18
|
+
# == Built-in vs External Backends
|
|
19
|
+
#
|
|
20
|
+
# - **Built-in backends** (MRI, Rust, FFI, Java, Prism, Psych, Citrus) register
|
|
21
|
+
# their checkers automatically when loaded from `tree_haver/backends/*.rb`
|
|
22
|
+
# - **External backends** (commonmarker-merge, markly-merge, rbs-merge) register
|
|
23
|
+
# their checkers when their backend module is loaded
|
|
24
|
+
#
|
|
25
|
+
# == Thread Safety
|
|
26
|
+
#
|
|
27
|
+
# All operations are thread-safe using a Mutex for synchronization.
|
|
28
|
+
# Results are cached after first check for performance.
|
|
29
|
+
#
|
|
30
|
+
# @example Registering a backend availability checker (in your gem)
|
|
31
|
+
# # In commonmarker-merge/lib/commonmarker/merge/backend.rb
|
|
32
|
+
# TreeHaver::BackendRegistry.register_availability_checker(:commonmarker) do
|
|
33
|
+
# available?
|
|
34
|
+
# end
|
|
35
|
+
#
|
|
36
|
+
# @example Checking backend availability
|
|
37
|
+
# TreeHaver::BackendRegistry.available?(:commonmarker) # => true/false
|
|
38
|
+
# TreeHaver::BackendRegistry.available?(:markly) # => true/false
|
|
39
|
+
# TreeHaver::BackendRegistry.available?(:rbs) # => true/false
|
|
40
|
+
#
|
|
41
|
+
# @example Checking if a checker is registered
|
|
42
|
+
# TreeHaver::BackendRegistry.registered?(:commonmarker) # => true/false
|
|
43
|
+
#
|
|
44
|
+
# @example Getting all registered backends
|
|
45
|
+
# TreeHaver::BackendRegistry.registered_backends # => [:mri, :rust, :ffi, ...]
|
|
46
|
+
#
|
|
47
|
+
# @see TreeHaver::RSpec::DependencyTags Uses BackendRegistry for dynamic backend detection
|
|
48
|
+
# @api public
|
|
49
|
+
module BackendRegistry
|
|
50
|
+
@mutex = Mutex.new
|
|
51
|
+
@availability_checkers = {} # rubocop:disable ThreadSafety/MutableClassInstanceVariable
|
|
52
|
+
@availability_cache = {} # rubocop:disable ThreadSafety/MutableClassInstanceVariable
|
|
53
|
+
|
|
54
|
+
module_function
|
|
55
|
+
|
|
56
|
+
# Register an availability checker for a backend
|
|
57
|
+
#
|
|
58
|
+
# The checker should be a callable (lambda/proc/block) that returns true if
|
|
59
|
+
# the backend is available and can be used. The checker is called lazily
|
|
60
|
+
# (only when {available?} is first called for this backend).
|
|
61
|
+
#
|
|
62
|
+
# @param backend_name [Symbol, String] the backend name (e.g., :commonmarker, :markly)
|
|
63
|
+
# @param checker [#call, nil] a callable that returns true if the backend is available
|
|
64
|
+
# @yield Block form of checker (alternative to passing a callable)
|
|
65
|
+
# @yieldreturn [Boolean] true if the backend is available
|
|
66
|
+
# @return [void]
|
|
67
|
+
# @raise [ArgumentError] if no checker callable or block is provided
|
|
68
|
+
# @raise [ArgumentError] if checker doesn't respond to #call
|
|
69
|
+
#
|
|
70
|
+
# @example Register with a block
|
|
71
|
+
# TreeHaver::BackendRegistry.register_availability_checker(:commonmarker) do
|
|
72
|
+
# require "commonmarker"
|
|
73
|
+
# true
|
|
74
|
+
# rescue LoadError
|
|
75
|
+
# false
|
|
76
|
+
# end
|
|
77
|
+
#
|
|
78
|
+
# @example Register with a lambda
|
|
79
|
+
# checker = -> { Commonmarker::Merge::Backend.available? }
|
|
80
|
+
# TreeHaver::BackendRegistry.register_availability_checker(:commonmarker, checker)
|
|
81
|
+
#
|
|
82
|
+
# @example Register referencing the module's available? method
|
|
83
|
+
# TreeHaver::BackendRegistry.register_availability_checker(:my_backend) do
|
|
84
|
+
# available? # Calls the enclosing module's available? method
|
|
85
|
+
# end
|
|
86
|
+
def register_availability_checker(backend_name, checker = nil, &block)
|
|
87
|
+
callable = checker || block
|
|
88
|
+
raise ArgumentError, "Must provide a checker callable or block" unless callable
|
|
89
|
+
raise ArgumentError, "Checker must respond to #call" unless callable.respond_to?(:call)
|
|
90
|
+
|
|
91
|
+
@mutex.synchronize do
|
|
92
|
+
@availability_checkers[backend_name.to_sym] = callable
|
|
93
|
+
# Clear cache for this backend when re-registering
|
|
94
|
+
@availability_cache.delete(backend_name.to_sym)
|
|
95
|
+
end
|
|
96
|
+
nil
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Check if a backend is available
|
|
100
|
+
#
|
|
101
|
+
# If a checker was registered via {register_availability_checker}, it is called
|
|
102
|
+
# (and the result cached). If no checker is registered, falls back to checking
|
|
103
|
+
# `TreeHaver::Backends::<Name>.available?` for built-in backends.
|
|
104
|
+
#
|
|
105
|
+
# Results are cached to avoid repeated expensive checks (e.g., requiring gems).
|
|
106
|
+
# Use {clear_cache!} to reset the cache if backend availability may have changed.
|
|
107
|
+
#
|
|
108
|
+
# @param backend_name [Symbol, String] the backend name to check
|
|
109
|
+
# @return [Boolean] true if the backend is available, false otherwise
|
|
110
|
+
#
|
|
111
|
+
# @example
|
|
112
|
+
# TreeHaver::BackendRegistry.available?(:commonmarker) # => true
|
|
113
|
+
# TreeHaver::BackendRegistry.available?(:nonexistent) # => false
|
|
114
|
+
def available?(backend_name)
|
|
115
|
+
key = backend_name.to_sym
|
|
116
|
+
|
|
117
|
+
# First, check cache and get checker without holding mutex for long
|
|
118
|
+
checker = nil
|
|
119
|
+
@mutex.synchronize do
|
|
120
|
+
# Return cached result if available
|
|
121
|
+
return @availability_cache[key] if @availability_cache.key?(key)
|
|
122
|
+
|
|
123
|
+
# Get registered checker (if any)
|
|
124
|
+
checker = @availability_checkers[key]
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Compute result OUTSIDE the mutex to avoid deadlock when loading backends
|
|
128
|
+
# (loading a backend module triggers register_availability_checker which needs the mutex)
|
|
129
|
+
result = if checker
|
|
130
|
+
# Use the registered checker
|
|
131
|
+
begin
|
|
132
|
+
checker.call
|
|
133
|
+
rescue StandardError
|
|
134
|
+
false
|
|
135
|
+
end
|
|
136
|
+
else
|
|
137
|
+
# Fall back to checking TreeHaver::Backends::<Name>
|
|
138
|
+
# This may load the backend module, which will register its checker
|
|
139
|
+
check_builtin_backend(key)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Cache the result
|
|
143
|
+
@mutex.synchronize do
|
|
144
|
+
# Double-check cache in case another thread computed it
|
|
145
|
+
return @availability_cache[key] if @availability_cache.key?(key)
|
|
146
|
+
@availability_cache[key] = result
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
result
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Check if an availability checker is registered for a backend
|
|
153
|
+
#
|
|
154
|
+
# @param backend_name [Symbol, String] the backend name
|
|
155
|
+
# @return [Boolean] true if a checker is registered
|
|
156
|
+
#
|
|
157
|
+
# @example
|
|
158
|
+
# TreeHaver::BackendRegistry.registered?(:commonmarker) # => true (if loaded)
|
|
159
|
+
# TreeHaver::BackendRegistry.registered?(:nonexistent) # => false
|
|
160
|
+
def registered?(backend_name)
|
|
161
|
+
@mutex.synchronize do
|
|
162
|
+
@availability_checkers.key?(backend_name.to_sym)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Get all registered backend names
|
|
167
|
+
#
|
|
168
|
+
# @return [Array<Symbol>] list of registered backend names
|
|
169
|
+
#
|
|
170
|
+
# @example
|
|
171
|
+
# TreeHaver::BackendRegistry.registered_backends
|
|
172
|
+
# # => [:mri, :rust, :ffi, :java, :prism, :psych, :citrus, :commonmarker, :markly]
|
|
173
|
+
def registered_backends
|
|
174
|
+
@mutex.synchronize do
|
|
175
|
+
@availability_checkers.keys.dup
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Clear the availability cache
|
|
180
|
+
#
|
|
181
|
+
# Useful for testing or when backend availability may have changed
|
|
182
|
+
# (e.g., after installing a gem mid-process).
|
|
183
|
+
#
|
|
184
|
+
# @return [void]
|
|
185
|
+
#
|
|
186
|
+
# @example
|
|
187
|
+
# TreeHaver::BackendRegistry.clear_cache!
|
|
188
|
+
# # Next call to available? will re-check
|
|
189
|
+
def clear_cache!
|
|
190
|
+
@mutex.synchronize do
|
|
191
|
+
@availability_cache.clear
|
|
192
|
+
end
|
|
193
|
+
nil
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Clear all registrations and cache
|
|
197
|
+
#
|
|
198
|
+
# Removes all registered checkers and cached results.
|
|
199
|
+
# Primarily useful for testing to reset state between test cases.
|
|
200
|
+
#
|
|
201
|
+
# @return [void]
|
|
202
|
+
#
|
|
203
|
+
# @example
|
|
204
|
+
# TreeHaver::BackendRegistry.clear!
|
|
205
|
+
def clear!
|
|
206
|
+
@mutex.synchronize do
|
|
207
|
+
@availability_checkers.clear
|
|
208
|
+
@availability_cache.clear
|
|
209
|
+
end
|
|
210
|
+
nil
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Check a built-in TreeHaver backend
|
|
214
|
+
#
|
|
215
|
+
# Attempts to find the backend module at `TreeHaver::Backends::<Name>` and
|
|
216
|
+
# call its `available?` method. This is the fallback when no explicit
|
|
217
|
+
# checker has been registered.
|
|
218
|
+
#
|
|
219
|
+
# @param backend_name [Symbol] the backend name (e.g., :mri, :rust, :ffi)
|
|
220
|
+
# @return [Boolean] true if the backend module exists and reports available
|
|
221
|
+
# @api private
|
|
222
|
+
def check_builtin_backend(backend_name)
|
|
223
|
+
# Convert backend_name to PascalCase constant name
|
|
224
|
+
# e.g., :mri -> "MRI", :ffi -> "FFI", :commonmarker -> "Commonmarker"
|
|
225
|
+
const_name = backend_name.to_s.split("_").map(&:capitalize).join
|
|
226
|
+
backend_mod = TreeHaver::Backends.const_get(const_name)
|
|
227
|
+
backend_mod.respond_to?(:available?) && backend_mod.available?
|
|
228
|
+
rescue NameError
|
|
229
|
+
# Backend module doesn't exist
|
|
230
|
+
false
|
|
231
|
+
end
|
|
232
|
+
private_class_method :check_builtin_backend
|
|
233
|
+
end
|
|
234
|
+
end
|
|
@@ -42,10 +42,11 @@ module TreeHaver
|
|
|
42
42
|
@load_attempted = true # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
43
43
|
begin
|
|
44
44
|
require "citrus"
|
|
45
|
-
|
|
46
45
|
@loaded = true # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
47
46
|
rescue LoadError
|
|
48
47
|
@loaded = false # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
48
|
+
rescue StandardError
|
|
49
|
+
@loaded = false # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
49
50
|
end
|
|
50
51
|
@loaded # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
51
52
|
end
|
|
@@ -492,6 +493,11 @@ module TreeHaver
|
|
|
492
493
|
{row: lines_before, column: column}
|
|
493
494
|
end
|
|
494
495
|
end
|
|
496
|
+
|
|
497
|
+
# Register availability checker for RSpec dependency tags
|
|
498
|
+
TreeHaver::BackendRegistry.register_availability_checker(:citrus) do
|
|
499
|
+
available?
|
|
500
|
+
end
|
|
495
501
|
end
|
|
496
502
|
end
|
|
497
503
|
end
|
|
@@ -15,6 +15,23 @@ module TreeHaver
|
|
|
15
15
|
# Not yet supported:
|
|
16
16
|
# - Query API (tree-sitter queries/patterns)
|
|
17
17
|
#
|
|
18
|
+
# == Tree/Node Architecture
|
|
19
|
+
#
|
|
20
|
+
# This backend (like all tree-sitter backends: MRI, Rust, FFI, Java) does NOT
|
|
21
|
+
# define its own Tree or Node classes. Instead:
|
|
22
|
+
#
|
|
23
|
+
# - Parser#parse returns raw FFI-wrapped tree objects
|
|
24
|
+
# - These are wrapped by `TreeHaver::Tree` (inherits from `Base::Tree`)
|
|
25
|
+
# - `TreeHaver::Tree#root_node` wraps raw nodes in `TreeHaver::Node`
|
|
26
|
+
#
|
|
27
|
+
# This differs from pure-Ruby backends (Citrus, Prism, Psych) which define
|
|
28
|
+
# their own `Backend::X::Tree` and `Backend::X::Node` classes.
|
|
29
|
+
#
|
|
30
|
+
# @see TreeHaver::Tree The wrapper class for tree-sitter Tree objects
|
|
31
|
+
# @see TreeHaver::Node The wrapper class for tree-sitter Node objects
|
|
32
|
+
# @see TreeHaver::Base::Tree Base class documenting the Tree API contract
|
|
33
|
+
# @see TreeHaver::Base::Node Base class documenting the Node API contract
|
|
34
|
+
#
|
|
18
35
|
# == Platform Compatibility
|
|
19
36
|
#
|
|
20
37
|
# - MRI Ruby: ✓ Full support
|
|
@@ -56,19 +73,24 @@ module TreeHaver
|
|
|
56
73
|
# @note Returns false on TruffleRuby because TruffleRuby's FFI doesn't support
|
|
57
74
|
# STRUCT_BY_VALUE return types (used by ts_tree_root_node, ts_node_child, etc.)
|
|
58
75
|
def ffi_gem_available?
|
|
59
|
-
return @loaded if @load_attempted
|
|
76
|
+
return @loaded if @load_attempted # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
77
|
+
@load_attempted = true # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
60
78
|
|
|
61
|
-
@
|
|
62
|
-
@loaded = begin
|
|
79
|
+
@loaded = begin # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
63
80
|
# TruffleRuby's FFI doesn't support STRUCT_BY_VALUE return types
|
|
64
81
|
# which tree-sitter uses extensively (ts_tree_root_node, ts_node_child, etc.)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
82
|
+
if RUBY_ENGINE == "truffleruby"
|
|
83
|
+
false
|
|
84
|
+
else
|
|
85
|
+
require "ffi"
|
|
86
|
+
true
|
|
87
|
+
end
|
|
69
88
|
rescue LoadError
|
|
70
89
|
false
|
|
90
|
+
rescue StandardError
|
|
91
|
+
false
|
|
71
92
|
end
|
|
93
|
+
@loaded # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
72
94
|
end
|
|
73
95
|
|
|
74
96
|
# Reset the load state (primarily for testing)
|
|
@@ -76,8 +98,8 @@ module TreeHaver
|
|
|
76
98
|
# @return [void]
|
|
77
99
|
# @api private
|
|
78
100
|
def reset!
|
|
79
|
-
@load_attempted = false
|
|
80
|
-
@loaded = false
|
|
101
|
+
@load_attempted = false # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
102
|
+
@loaded = false # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
81
103
|
end
|
|
82
104
|
|
|
83
105
|
# Get capabilities supported by this backend
|
|
@@ -717,6 +739,11 @@ module TreeHaver
|
|
|
717
739
|
type <=> other.type
|
|
718
740
|
end
|
|
719
741
|
end
|
|
742
|
+
|
|
743
|
+
# Register availability checker for RSpec dependency tags
|
|
744
|
+
TreeHaver::BackendRegistry.register_availability_checker(:ffi) do
|
|
745
|
+
available?
|
|
746
|
+
end
|
|
720
747
|
end
|
|
721
748
|
end
|
|
722
749
|
end
|