tree_haver 3.2.2 → 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 +44 -1
- data/LICENSE.txt +1 -1
- data/README.md +2 -2
- data/lib/tree_haver/language.rb +25 -10
- 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,47 @@ 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
|
+
|
|
33
74
|
## [3.2.2] - 2026-01-01
|
|
34
75
|
|
|
35
76
|
- TAG: [v3.2.2][3.2.2t]
|
|
@@ -674,7 +715,9 @@ Despite the major version bump to 3.0.0 (following semver due to the breaking `L
|
|
|
674
715
|
|
|
675
716
|
- Initial release
|
|
676
717
|
|
|
677
|
-
[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
|
|
678
721
|
[3.2.2]: https://github.com/kettle-rb/tree_haver/compare/v3.2.1...v3.2.2
|
|
679
722
|
[3.2.2t]: https://github.com/kettle-rb/tree_haver/releases/tag/v3.2.2
|
|
680
723
|
[3.2.1]: https://github.com/kettle-rb/tree_haver/compare/v3.2.0...v3.2.1
|
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
|
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
|