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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3da36f96aa78d0995c76a3cbd6451889066b65d219414938e098ef9949df317f
4
- data.tar.gz: 11e11da6c9f53e439d4226700c981466c3b47f0b9411d2e97a5211edade8d81d
3
+ metadata.gz: 02ffefe8f7a777c04ca0682c3bae06f92d383eacdf2e852755dd6e517cff68bf
4
+ data.tar.gz: a44f91a685d4736f7eac5a81cc04f43e274e18f8af8904eaa4e1247bf1779764
5
5
  SHA512:
6
- metadata.gz: 1dce9daf70fc5573d579d699724601756db9758170fdb0e7be759ea9572a3d02fa491766786c708172e0f66c2473ae9e0da97fb20bddf7b185d93e285cc8bd3e
7
- data.tar.gz: eaf3a3649233933b523a7f8e49831b7d5c7f00da268d805c7cbc043176424b5b4214da607bbe7e57e03dcd9072269dfedf3feba4383a828e6cb1abb89394b5c8
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.1...HEAD
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
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2025 Peter H. Boling
3
+ Copyright (c) 2025-2026 Peter H. Boling
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
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.190-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
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
@@ -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
- citrus_reg = all_backends[:citrus]
216
- if citrus_reg && citrus_reg[:grammar_module]
217
- return Backends::Citrus::Language.new(citrus_reg[:grammar_module])
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
- citrus_reg = all_backends[:citrus]
247
- if citrus_reg && citrus_reg[:grammar_module]
248
- return Backends::Citrus::Language.new(citrus_reg[:grammar_module])
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 if any backend is blocked
851
- # (i.e., we're running isolated backend tests)
852
- if blocked_backends.none?
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
- if blocked_backends.none?
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
- if blocked_backends.none?
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?
@@ -10,7 +10,7 @@ module TreeHaver
10
10
  # Current version of the tree_haver gem
11
11
  #
12
12
  # @return [String] the version string (e.g., "3.0.0")
13
- VERSION = "3.2.1"
13
+ VERSION = "3.2.3"
14
14
  end
15
15
 
16
16
  # Traditional location for VERSION constant
data/lib/tree_haver.rb CHANGED
@@ -16,7 +16,7 @@ require_relative "tree_haver/version"
16
16
  #
17
17
  # == Backends
18
18
  #
19
- # Supports 10 backends:
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
- # This is the recommended high-level API for creating a parser. It handles:
840
- # 1. Checking if the language is already registered
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 With explicit library path
859
- # parser = TreeHaver.parser_for(:toml, library_path: "/custom/path/libtree-sitter-toml.so")
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
- # Step 1: Try to get the language (may already be registered)
870
- language = begin
871
- # Check if already registered and loadable
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
- # Step 2: If not registered, try GrammarFinder for tree-sitter
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
- # Step 3: Try Citrus fallback if tree-sitter failed
911
- unless language
912
- # Use explicit config, or fall back to built-in defaults for known languages
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
- # Step 4: Raise if nothing worked
935
- unless language
936
- raise NotAvailable,
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
- # Step 5: Create and configure parser
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.1
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.1
296
- changelog_uri: https://github.com/kettle-rb/tree_haver/blob/v3.2.1/CHANGELOG.md
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.1
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