tree_haver 3.2.1 → 3.2.3
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 +61 -1
- data/LICENSE.txt +1 -1
- data/README.md +2 -2
- data/lib/tree_haver/language.rb +25 -10
- data/lib/tree_haver/rspec/dependency_tags.rb +30 -6
- data/lib/tree_haver/version.rb +1 -1
- data/lib/tree_haver.rb +82 -89
- data.tar.gz.sig +0 -0
- metadata +4 -4
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 02ffefe8f7a777c04ca0682c3bae06f92d383eacdf2e852755dd6e517cff68bf
|
|
4
|
+
data.tar.gz: a44f91a685d4736f7eac5a81cc04f43e274e18f8af8904eaa4e1247bf1779764
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e0d54671ac5cecea3e8496da4e5a9cf9f4ef4c3abd7bcb926f0d8830e230b2d850845e4a49e824e0144c79c35e7477abf6ee9f4a7284637c1fc0c0ff6ff15033
|
|
7
|
+
data.tar.gz: 8f7487bd3fba0f8d03128a32a485db360a1db5d97161f2885918992dd516140c37006d7bf2bc7d8f86867ad597fe5920683f126bd1d30c2257764aab19b37c88
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,62 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
30
30
|
|
|
31
31
|
### Security
|
|
32
32
|
|
|
33
|
+
## [3.2.3] - 2026-01-02
|
|
34
|
+
|
|
35
|
+
- TAG: [v3.2.3][3.2.3t]
|
|
36
|
+
- COVERAGE: 94.91% -- 2088/2200 lines in 22 files
|
|
37
|
+
- BRANCH COVERAGE: 81.37% -- 738/907 branches in 22 files
|
|
38
|
+
- 90.14% documented
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- **`parser_for` now respects explicitly requested non-native backends** - Previously,
|
|
43
|
+
`parser_for` would always try tree-sitter backends first and only fall back to alternative
|
|
44
|
+
backends if tree-sitter was unavailable. Now it checks `effective_backend` and skips
|
|
45
|
+
tree-sitter attempts entirely when a non-native backend is explicitly requested via:
|
|
46
|
+
- `TREE_HAVER_BACKEND=citrus` (or `prism`, `psych`, `commonmarker`, `markly`)
|
|
47
|
+
- `TreeHaver.backend = :citrus`
|
|
48
|
+
- `TreeHaver.with_backend(:citrus) { ... }`
|
|
49
|
+
|
|
50
|
+
Native backends (`:mri`, `:rust`, `:ffi`, `:java`) still use tree-sitter grammar discovery.
|
|
51
|
+
|
|
52
|
+
- **`load_tree_sitter_language` now correctly ignores Citrus registrations** - Previously,
|
|
53
|
+
if a language was registered with Citrus first, `load_tree_sitter_language` would
|
|
54
|
+
incorrectly try to use it even when a native backend was explicitly requested. Now it
|
|
55
|
+
only uses registrations that have a `:tree_sitter` key, allowing proper backend switching
|
|
56
|
+
between Citrus and native tree-sitter backends.
|
|
57
|
+
|
|
58
|
+
- **`load_tree_sitter_language` now validates registered paths exist** - Previously,
|
|
59
|
+
if a language had a stale/invalid tree-sitter registration with a non-existent path
|
|
60
|
+
(e.g., from a test), the code would try to use it and fail. Now it checks
|
|
61
|
+
`File.exist?(path)` before using a registered path, falling back to auto-discovery
|
|
62
|
+
via `GrammarFinder` if the registered path doesn't exist.
|
|
63
|
+
|
|
64
|
+
- **`Language.method_missing` no longer falls back to Citrus when native backend explicitly requested** -
|
|
65
|
+
Previously, when tree-sitter loading failed (e.g., .so file missing), the code would
|
|
66
|
+
silently fall back to Citrus even if the user explicitly requested `:mri`, `:rust`,
|
|
67
|
+
`:ffi`, or `:java`. Now fallback to Citrus only happens when `effective_backend` is `:auto`.
|
|
68
|
+
This is a **breaking change** for users who relied on silent fallback behavior.
|
|
69
|
+
|
|
70
|
+
- **Simplified `parser_for` implementation** - Refactored from complex nested conditionals to
|
|
71
|
+
cleaner helper methods (`load_tree_sitter_language`, `load_citrus_language`). The logic is
|
|
72
|
+
now easier to follow and maintain.
|
|
73
|
+
|
|
74
|
+
## [3.2.2] - 2026-01-01
|
|
75
|
+
|
|
76
|
+
- TAG: [v3.2.2][3.2.2t]
|
|
77
|
+
- COVERAGE: 94.79% -- 2076/2190 lines in 22 files
|
|
78
|
+
- BRANCH COVERAGE: 81.35% -- 733/901 branches in 22 files
|
|
79
|
+
- 90.14% documented
|
|
80
|
+
|
|
81
|
+
### Fixed
|
|
82
|
+
|
|
83
|
+
- RSpec dependency tags now respect `TREE_HAVER_BACKEND` environment variable
|
|
84
|
+
- When `TREE_HAVER_BACKEND=ffi` is set, MRI backend availability is not checked
|
|
85
|
+
- Prevents `BackendConflict` errors when loading gems that use tree-sitter grammars
|
|
86
|
+
- The `blocked_backends` set now includes backends that would conflict with the explicitly selected backend
|
|
87
|
+
- This allows `*-merge` gems to load correctly in test suites when a specific backend is selected
|
|
88
|
+
|
|
33
89
|
## [3.2.1] - 2025-12-31
|
|
34
90
|
|
|
35
91
|
- TAG: [v3.2.1][3.2.1t]
|
|
@@ -659,7 +715,11 @@ Despite the major version bump to 3.0.0 (following semver due to the breaking `L
|
|
|
659
715
|
|
|
660
716
|
- Initial release
|
|
661
717
|
|
|
662
|
-
[Unreleased]: https://github.com/kettle-rb/tree_haver/compare/v3.2.
|
|
718
|
+
[Unreleased]: https://github.com/kettle-rb/tree_haver/compare/v3.2.3...HEAD
|
|
719
|
+
[3.2.3]: https://github.com/kettle-rb/tree_haver/compare/v3.2.2...v3.2.3
|
|
720
|
+
[3.2.3t]: https://github.com/kettle-rb/tree_haver/releases/tag/v3.2.3
|
|
721
|
+
[3.2.2]: https://github.com/kettle-rb/tree_haver/compare/v3.2.1...v3.2.2
|
|
722
|
+
[3.2.2t]: https://github.com/kettle-rb/tree_haver/releases/tag/v3.2.2
|
|
663
723
|
[3.2.1]: https://github.com/kettle-rb/tree_haver/compare/v3.2.0...v3.2.1
|
|
664
724
|
[3.2.1t]: https://github.com/kettle-rb/tree_haver/releases/tag/v3.2.1
|
|
665
725
|
[3.2.0]: https://github.com/kettle-rb/tree_haver/compare/v3.1.2...v3.2.0
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
|
@@ -1788,7 +1788,7 @@ See [LICENSE.txt](LICENSE.txt) for the official [Copyright Notice](https://opens
|
|
|
1788
1788
|
|
|
1789
1789
|
<ul>
|
|
1790
1790
|
<li>
|
|
1791
|
-
Copyright (c) 2025 Peter H. Boling, of
|
|
1791
|
+
Copyright (c) 2025-2026 Peter H. Boling, of
|
|
1792
1792
|
<a href="https://discord.gg/3qme4XHNKN">
|
|
1793
1793
|
Galtzo.com
|
|
1794
1794
|
<picture>
|
|
@@ -1977,7 +1977,7 @@ Thanks for RTFM. ☺️
|
|
|
1977
1977
|
[📌gitmoji]: https://gitmoji.dev
|
|
1978
1978
|
[📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
|
|
1979
1979
|
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
1980
|
-
[🧮kloc-img]: https://img.shields.io/badge/KLOC-2.
|
|
1980
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-2.200-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
|
1981
1981
|
[🔐security]: SECURITY.md
|
|
1982
1982
|
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
|
1983
1983
|
[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
|
data/lib/tree_haver/language.rb
CHANGED
|
@@ -211,10 +211,13 @@ module TreeHaver
|
|
|
211
211
|
|
|
212
212
|
# No tree-sitter path registered - check for Citrus fallback
|
|
213
213
|
# This enables auto-fallback when tree-sitter grammar is not installed
|
|
214
|
-
# but a Citrus grammar (pure Ruby) is available
|
|
215
|
-
|
|
216
|
-
if
|
|
217
|
-
|
|
214
|
+
# but a Citrus grammar (pure Ruby) is available.
|
|
215
|
+
# Only fall back when backend is :auto - explicit native backend requests should fail.
|
|
216
|
+
if TreeHaver.effective_backend == :auto
|
|
217
|
+
citrus_reg = all_backends[:citrus]
|
|
218
|
+
if citrus_reg && citrus_reg[:grammar_module]
|
|
219
|
+
return Backends::Citrus::Language.new(citrus_reg[:grammar_module])
|
|
220
|
+
end
|
|
218
221
|
end
|
|
219
222
|
|
|
220
223
|
# No appropriate registration found
|
|
@@ -237,17 +240,29 @@ module TreeHaver
|
|
|
237
240
|
# - FFI can't find required symbols like ts_parser_new (FFI::NotFoundError)
|
|
238
241
|
# - Invalid arguments were provided (ArgumentError)
|
|
239
242
|
#
|
|
243
|
+
# Fallback to Citrus ONLY happens when:
|
|
244
|
+
# - The effective backend is :auto (user didn't explicitly request a native backend)
|
|
245
|
+
# - A Citrus grammar is registered for the language
|
|
246
|
+
#
|
|
247
|
+
# If the user explicitly requested a native backend (:mri, :rust, :ffi, :java),
|
|
248
|
+
# we should NOT silently fall back to Citrus - that would violate the user's intent.
|
|
249
|
+
#
|
|
240
250
|
# @param error [Exception] the original error
|
|
241
251
|
# @param all_backends [Hash] all registered backends for the language
|
|
242
|
-
# @return [Backends::Citrus::Language] if Citrus fallback available
|
|
243
|
-
# @raise [Exception] re-raises original error if no fallback
|
|
252
|
+
# @return [Backends::Citrus::Language] if Citrus fallback available and allowed
|
|
253
|
+
# @raise [Exception] re-raises original error if no fallback or fallback not allowed
|
|
244
254
|
# @api private
|
|
245
255
|
def handle_tree_sitter_load_failure(error, all_backends)
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
256
|
+
# Only fall back to Citrus when backend is :auto
|
|
257
|
+
# If user explicitly requested a native backend, respect that choice
|
|
258
|
+
effective = TreeHaver.effective_backend
|
|
259
|
+
if effective == :auto
|
|
260
|
+
citrus_reg = all_backends[:citrus]
|
|
261
|
+
if citrus_reg && citrus_reg[:grammar_module]
|
|
262
|
+
return Backends::Citrus::Language.new(citrus_reg[:grammar_module])
|
|
263
|
+
end
|
|
249
264
|
end
|
|
250
|
-
# No Citrus fallback available, re-raise the original error
|
|
265
|
+
# No Citrus fallback allowed or available, re-raise the original error
|
|
251
266
|
raise error
|
|
252
267
|
end
|
|
253
268
|
end
|
|
@@ -775,9 +775,30 @@ RSpec.configure do |config|
|
|
|
775
775
|
}
|
|
776
776
|
|
|
777
777
|
# Determine which backends should NOT have availability checked
|
|
778
|
-
# based on which *_backend_only tag is being run
|
|
778
|
+
# based on which *_backend_only tag is being run OR which backend is
|
|
779
|
+
# explicitly selected via TREE_HAVER_BACKEND environment variable.
|
|
779
780
|
blocked_backends = Set.new
|
|
780
781
|
|
|
782
|
+
# Track whether we're in isolated test mode (running *_backend_only tags).
|
|
783
|
+
# This is different from just having TREE_HAVER_BACKEND set.
|
|
784
|
+
# In isolated mode, we skip ALL grammar checks because they might trigger
|
|
785
|
+
# backend loading via TreeHaver.parser_for's auto-detection.
|
|
786
|
+
# When just TREE_HAVER_BACKEND is set, grammar checks are fine because
|
|
787
|
+
# parser_for will use the selected backend, not auto-detect.
|
|
788
|
+
isolated_test_mode = false
|
|
789
|
+
|
|
790
|
+
# First, check if TREE_HAVER_BACKEND explicitly selects a backend.
|
|
791
|
+
# If so, block all backends that would conflict with it.
|
|
792
|
+
# This prevents loading MRI when TREE_HAVER_BACKEND=ffi, for example.
|
|
793
|
+
env_backend = ENV["TREE_HAVER_BACKEND"]
|
|
794
|
+
if env_backend && !env_backend.empty? && env_backend != "auto"
|
|
795
|
+
backend_sym = env_backend.to_sym
|
|
796
|
+
blockers = TreeHaver::Backends::BLOCKED_BY[backend_sym]
|
|
797
|
+
if blockers
|
|
798
|
+
blockers.each { |blocker| blocked_backends << blocker }
|
|
799
|
+
end
|
|
800
|
+
end
|
|
801
|
+
|
|
781
802
|
# Check which *_backend_only tags are being run and block their conflicting backends
|
|
782
803
|
# config.inclusion_filter contains tags passed via --tag on command line
|
|
783
804
|
inclusion_rules = config.inclusion_filter.rules
|
|
@@ -806,6 +827,7 @@ RSpec.configure do |config|
|
|
|
806
827
|
# Check if we're running this backend's isolated tests
|
|
807
828
|
isolated_tag = :"#{backend}_backend_only"
|
|
808
829
|
if inclusion_rules[isolated_tag]
|
|
830
|
+
isolated_test_mode = true
|
|
809
831
|
# Add all backends that would block this one
|
|
810
832
|
blockers.each { |blocker| blocked_backends << blocker }
|
|
811
833
|
end
|
|
@@ -813,6 +835,7 @@ RSpec.configure do |config|
|
|
|
813
835
|
|
|
814
836
|
# Store blocked_backends in a module variable so before(:suite) can access it
|
|
815
837
|
TreeHaver::RSpec::DependencyTags.instance_variable_set(:@blocked_backends, blocked_backends)
|
|
838
|
+
TreeHaver::RSpec::DependencyTags.instance_variable_set(:@isolated_test_mode, isolated_test_mode)
|
|
816
839
|
|
|
817
840
|
# Now configure exclusions, skipping availability checks for blocked backends
|
|
818
841
|
backend_tags.each do |backend, tag|
|
|
@@ -847,9 +870,10 @@ RSpec.configure do |config|
|
|
|
847
870
|
# loading blocked backends. The grammar checks use TreeHaver.parser_for which
|
|
848
871
|
# would load the default backend (MRI) and block FFI.
|
|
849
872
|
|
|
850
|
-
# Skip grammar availability checks
|
|
851
|
-
# (
|
|
852
|
-
|
|
873
|
+
# Skip grammar availability checks only when in isolated test mode.
|
|
874
|
+
# When TREE_HAVER_BACKEND is explicitly set (but not using *_backend_only tags),
|
|
875
|
+
# grammar checks are fine because TreeHaver.parser_for respects the env var.
|
|
876
|
+
unless isolated_test_mode
|
|
853
877
|
config.filter_run_excluding(libtree_sitter: true) unless deps.libtree_sitter_available?
|
|
854
878
|
config.filter_run_excluding(bash_grammar: true) unless deps.tree_sitter_bash_available?
|
|
855
879
|
config.filter_run_excluding(toml_grammar: true) unless deps.tree_sitter_toml_available?
|
|
@@ -868,7 +892,7 @@ RSpec.configure do |config|
|
|
|
868
892
|
# NOTE: any_toml_backend_available? calls tree_sitter_toml_available? which
|
|
869
893
|
# triggers grammar_works? and loads MRI. Skip when running isolated tests.
|
|
870
894
|
|
|
871
|
-
|
|
895
|
+
unless isolated_test_mode
|
|
872
896
|
config.filter_run_excluding(toml_parsing: true) unless deps.any_toml_backend_available?
|
|
873
897
|
config.filter_run_excluding(markdown_parsing: true) unless deps.any_markdown_backend_available?
|
|
874
898
|
config.filter_run_excluding(native_parsing: true) unless deps.any_native_grammar_available?
|
|
@@ -907,7 +931,7 @@ RSpec.configure do |config|
|
|
|
907
931
|
config.filter_run_excluding(not_truffleruby_engine: true) if deps.truffleruby?
|
|
908
932
|
|
|
909
933
|
# Tree-sitter grammars - skip when running isolated backend tests
|
|
910
|
-
|
|
934
|
+
unless isolated_test_mode
|
|
911
935
|
config.filter_run_excluding(not_libtree_sitter: true) if deps.libtree_sitter_available?
|
|
912
936
|
config.filter_run_excluding(not_bash_grammar: true) if deps.tree_sitter_bash_available?
|
|
913
937
|
config.filter_run_excluding(not_toml_grammar: true) if deps.tree_sitter_toml_available?
|
data/lib/tree_haver/version.rb
CHANGED
data/lib/tree_haver.rb
CHANGED
|
@@ -16,7 +16,7 @@ require_relative "tree_haver/version"
|
|
|
16
16
|
#
|
|
17
17
|
# == Backends
|
|
18
18
|
#
|
|
19
|
-
# Supports
|
|
19
|
+
# Supports 9 backends:
|
|
20
20
|
# - Tree-sitter: MRI (C), Rust, FFI, Java
|
|
21
21
|
# - Native parsers: Prism (Ruby), Psych (YAML), Commonmarker (Markdown), Markly (GFM)
|
|
22
22
|
# - Pure Ruby: Citrus (portable fallback)
|
|
@@ -266,6 +266,11 @@ module TreeHaver
|
|
|
266
266
|
# Parser class for parsing source code into syntax trees
|
|
267
267
|
autoload :Parser, File.join(__dir__, "tree_haver", "parser")
|
|
268
268
|
|
|
269
|
+
# Native tree-sitter backends that support loading shared libraries (.so files)
|
|
270
|
+
# These backends wrap the tree-sitter C library via various bindings.
|
|
271
|
+
# Pure Ruby backends (Citrus, Prism, Psych, Commonmarker, Markly) are excluded.
|
|
272
|
+
NATIVE_BACKENDS = %i[mri rust ffi java].freeze
|
|
273
|
+
|
|
269
274
|
# Get the current backend selection
|
|
270
275
|
#
|
|
271
276
|
# @return [Symbol] one of :auto, :mri, :rust, :ffi, :java, or :citrus
|
|
@@ -590,11 +595,6 @@ module TreeHaver
|
|
|
590
595
|
mod
|
|
591
596
|
end
|
|
592
597
|
|
|
593
|
-
# Native tree-sitter backends that support loading shared libraries (.so files)
|
|
594
|
-
# These backends wrap the tree-sitter C library via various bindings.
|
|
595
|
-
# Pure Ruby backends (Citrus, Prism, Psych, Commonmarker, Markly) are excluded.
|
|
596
|
-
NATIVE_BACKENDS = %i[mri rust ffi java].freeze
|
|
597
|
-
|
|
598
598
|
# Resolve a native tree-sitter backend module (for from_library)
|
|
599
599
|
#
|
|
600
600
|
# This method is similar to resolve_backend_module but ONLY considers
|
|
@@ -836,114 +836,107 @@ module TreeHaver
|
|
|
836
836
|
|
|
837
837
|
# Create a parser configured for a specific language
|
|
838
838
|
#
|
|
839
|
-
#
|
|
840
|
-
#
|
|
841
|
-
# 2. Auto-discovering tree-sitter grammar via GrammarFinder
|
|
842
|
-
# 3. Falling back to Citrus grammar if tree-sitter is unavailable
|
|
843
|
-
# 4. Creating and configuring the parser
|
|
839
|
+
# Respects the effective backend setting (via TREE_HAVER_BACKEND env var,
|
|
840
|
+
# TreeHaver.backend=, or with_backend block).
|
|
844
841
|
#
|
|
845
842
|
# @param language_name [Symbol, String] the language to parse (e.g., :toml, :json, :bash)
|
|
846
843
|
# @param library_path [String, nil] optional explicit path to tree-sitter grammar library
|
|
847
844
|
# @param symbol [String, nil] optional tree-sitter symbol name (defaults to "tree_sitter_<name>")
|
|
848
845
|
# @param citrus_config [Hash, nil] optional Citrus fallback configuration
|
|
849
|
-
# @option citrus_config [String] :gem_name gem name for the Citrus grammar
|
|
850
|
-
# @option citrus_config [String] :grammar_const fully qualified constant name for grammar module
|
|
851
846
|
# @return [TreeHaver::Parser] configured parser with language set
|
|
852
847
|
# @raise [TreeHaver::NotAvailable] if no parser backend is available for the language
|
|
853
848
|
#
|
|
854
849
|
# @example Basic usage (auto-discovers grammar)
|
|
855
850
|
# parser = TreeHaver.parser_for(:toml)
|
|
856
|
-
# tree = parser.parse("[package]\nname = \"my-app\"")
|
|
857
851
|
#
|
|
858
|
-
# @example
|
|
859
|
-
#
|
|
860
|
-
#
|
|
861
|
-
# @example With Citrus fallback configuration
|
|
862
|
-
# parser = TreeHaver.parser_for(:toml,
|
|
863
|
-
# citrus_config: { gem_name: "toml-rb", grammar_const: "TomlRB::Document" }
|
|
864
|
-
# )
|
|
852
|
+
# @example Force Citrus backend
|
|
853
|
+
# TreeHaver.with_backend(:citrus) { TreeHaver.parser_for(:toml) }
|
|
865
854
|
def parser_for(language_name, library_path: nil, symbol: nil, citrus_config: nil)
|
|
866
855
|
name = language_name.to_sym
|
|
867
856
|
symbol ||= "tree_sitter_#{name}"
|
|
857
|
+
requested = effective_backend
|
|
868
858
|
|
|
869
|
-
#
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
if registered_language(name)
|
|
873
|
-
Language.public_send(name, path: library_path, symbol: symbol)
|
|
874
|
-
end
|
|
875
|
-
rescue NotAvailable, ArgumentError, LoadError
|
|
876
|
-
nil
|
|
877
|
-
end
|
|
859
|
+
# Determine which backends to try based on effective_backend
|
|
860
|
+
try_tree_sitter = (requested == :auto) || NATIVE_BACKENDS.include?(requested)
|
|
861
|
+
try_citrus = (requested == :auto) || (requested == :citrus)
|
|
878
862
|
|
|
879
|
-
|
|
880
|
-
unless language
|
|
881
|
-
# Principle of Least Surprise: If user provides an explicit path,
|
|
882
|
-
# it MUST exist. Don't silently fall back to auto-discovery.
|
|
883
|
-
if library_path && !library_path.empty?
|
|
884
|
-
unless File.exist?(library_path)
|
|
885
|
-
raise NotAvailable,
|
|
886
|
-
"Specified parser path does not exist: #{library_path}"
|
|
887
|
-
end
|
|
888
|
-
begin
|
|
889
|
-
register_language(name, path: library_path, symbol: symbol)
|
|
890
|
-
language = Language.public_send(name)
|
|
891
|
-
rescue NotAvailable, ArgumentError, LoadError => e
|
|
892
|
-
# Re-raise with more context since user explicitly provided this path
|
|
893
|
-
raise NotAvailable,
|
|
894
|
-
"Failed to load parser from specified path #{library_path}: #{e.message}"
|
|
895
|
-
end
|
|
896
|
-
else
|
|
897
|
-
# Auto-discover via GrammarFinder (no explicit path provided)
|
|
898
|
-
begin
|
|
899
|
-
finder = GrammarFinder.new(name)
|
|
900
|
-
if finder.available?
|
|
901
|
-
finder.register!
|
|
902
|
-
language = Language.public_send(name)
|
|
903
|
-
end
|
|
904
|
-
rescue NotAvailable, ArgumentError, LoadError
|
|
905
|
-
language = nil
|
|
906
|
-
end
|
|
907
|
-
end
|
|
908
|
-
end
|
|
863
|
+
language = nil
|
|
909
864
|
|
|
910
|
-
#
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
citrus_config ||= CITRUS_DEFAULTS[name] || {}
|
|
914
|
-
|
|
915
|
-
# Only attempt if we have the required configuration
|
|
916
|
-
if citrus_config[:gem_name] && citrus_config[:grammar_const]
|
|
917
|
-
begin
|
|
918
|
-
citrus_finder = CitrusGrammarFinder.new(
|
|
919
|
-
language: name,
|
|
920
|
-
gem_name: citrus_config[:gem_name],
|
|
921
|
-
grammar_const: citrus_config[:grammar_const],
|
|
922
|
-
require_path: citrus_config[:require_path],
|
|
923
|
-
)
|
|
924
|
-
if citrus_finder.available?
|
|
925
|
-
citrus_finder.register!
|
|
926
|
-
language = Language.public_send(name)
|
|
927
|
-
end
|
|
928
|
-
rescue NotAvailable, ArgumentError, LoadError, NameError, TypeError
|
|
929
|
-
language = nil
|
|
930
|
-
end
|
|
931
|
-
end
|
|
865
|
+
# Try tree-sitter if applicable
|
|
866
|
+
if try_tree_sitter && !language
|
|
867
|
+
language = load_tree_sitter_language(name, library_path: library_path, symbol: symbol)
|
|
932
868
|
end
|
|
933
869
|
|
|
934
|
-
#
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
"No parser available for #{name}. " \
|
|
938
|
-
"Install tree-sitter-#{name} or the appropriate Ruby gem. " \
|
|
939
|
-
"Set TREE_SITTER_#{name.to_s.upcase}_PATH for custom grammar location."
|
|
870
|
+
# Try Citrus if applicable
|
|
871
|
+
if try_citrus && !language
|
|
872
|
+
language = load_citrus_language(name, citrus_config: citrus_config)
|
|
940
873
|
end
|
|
941
874
|
|
|
942
|
-
#
|
|
875
|
+
# Raise if nothing worked
|
|
876
|
+
raise NotAvailable, "No parser available for #{name}. " \
|
|
877
|
+
"Install tree-sitter-#{name} or configure a Citrus grammar." unless language
|
|
878
|
+
|
|
879
|
+
# Create and configure parser
|
|
943
880
|
parser = Parser.new
|
|
944
881
|
parser.language = language
|
|
945
882
|
parser
|
|
946
883
|
end
|
|
884
|
+
|
|
885
|
+
private
|
|
886
|
+
|
|
887
|
+
# Load a tree-sitter language, either from registry or via auto-discovery
|
|
888
|
+
# @return [Language, nil]
|
|
889
|
+
# @raise [NotAvailable] if explicit library_path is provided but doesn't exist or can't load
|
|
890
|
+
def load_tree_sitter_language(name, library_path: nil, symbol: nil)
|
|
891
|
+
# If explicit path provided, it must work - don't swallow errors
|
|
892
|
+
if library_path && !library_path.empty?
|
|
893
|
+
raise NotAvailable, "Specified parser path does not exist: #{library_path}" unless File.exist?(library_path)
|
|
894
|
+
register_language(name, path: library_path, symbol: symbol)
|
|
895
|
+
return Language.public_send(name)
|
|
896
|
+
end
|
|
897
|
+
|
|
898
|
+
# Auto-discovery: errors are acceptable, just return nil
|
|
899
|
+
begin
|
|
900
|
+
# Try already-registered tree-sitter language (not Citrus)
|
|
901
|
+
# But only if the registered path actually exists - ignore stale/test registrations
|
|
902
|
+
registration = registered_language(name)
|
|
903
|
+
ts_reg = registration&.dig(:tree_sitter)
|
|
904
|
+
if ts_reg && ts_reg[:path] && File.exist?(ts_reg[:path])
|
|
905
|
+
return Language.public_send(name, symbol: symbol)
|
|
906
|
+
end
|
|
907
|
+
|
|
908
|
+
# Auto-discover via GrammarFinder
|
|
909
|
+
finder = GrammarFinder.new(name)
|
|
910
|
+
if finder.available?
|
|
911
|
+
finder.register!
|
|
912
|
+
return Language.public_send(name)
|
|
913
|
+
end
|
|
914
|
+
rescue NotAvailable, ArgumentError, LoadError
|
|
915
|
+
# Auto-discovery failed, that's okay
|
|
916
|
+
end
|
|
917
|
+
|
|
918
|
+
nil
|
|
919
|
+
end
|
|
920
|
+
|
|
921
|
+
# Load a Citrus language from configuration or defaults
|
|
922
|
+
# @return [Language, nil]
|
|
923
|
+
def load_citrus_language(name, citrus_config: nil)
|
|
924
|
+
config = citrus_config || CITRUS_DEFAULTS[name] || {}
|
|
925
|
+
return unless config[:gem_name] && config[:grammar_const]
|
|
926
|
+
|
|
927
|
+
finder = CitrusGrammarFinder.new(
|
|
928
|
+
language: name,
|
|
929
|
+
gem_name: config[:gem_name],
|
|
930
|
+
grammar_const: config[:grammar_const],
|
|
931
|
+
require_path: config[:require_path],
|
|
932
|
+
)
|
|
933
|
+
return unless finder.available?
|
|
934
|
+
|
|
935
|
+
finder.register!
|
|
936
|
+
Language.public_send(name)
|
|
937
|
+
rescue NotAvailable, ArgumentError, LoadError, NameError, TypeError
|
|
938
|
+
nil
|
|
939
|
+
end
|
|
947
940
|
end
|
|
948
941
|
|
|
949
942
|
# Language and Parser classes have been moved to separate files:
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tree_haver
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.2.
|
|
4
|
+
version: 3.2.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
@@ -292,10 +292,10 @@ licenses:
|
|
|
292
292
|
- MIT
|
|
293
293
|
metadata:
|
|
294
294
|
homepage_uri: https://tree-haver.galtzo.com/
|
|
295
|
-
source_code_uri: https://github.com/kettle-rb/tree_haver/tree/v3.2.
|
|
296
|
-
changelog_uri: https://github.com/kettle-rb/tree_haver/blob/v3.2.
|
|
295
|
+
source_code_uri: https://github.com/kettle-rb/tree_haver/tree/v3.2.3
|
|
296
|
+
changelog_uri: https://github.com/kettle-rb/tree_haver/blob/v3.2.3/CHANGELOG.md
|
|
297
297
|
bug_tracker_uri: https://github.com/kettle-rb/tree_haver/issues
|
|
298
|
-
documentation_uri: https://www.rubydoc.info/gems/tree_haver/3.2.
|
|
298
|
+
documentation_uri: https://www.rubydoc.info/gems/tree_haver/3.2.3
|
|
299
299
|
funding_uri: https://github.com/sponsors/pboling
|
|
300
300
|
wiki_uri: https://github.com/kettle-rb/tree_haver/wiki
|
|
301
301
|
news_uri: https://www.railsbling.com/tags/tree_haver
|
metadata.gz.sig
CHANGED
|
Binary file
|