tree_haver 5.0.5 → 7.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/lib/tree_haver/backend_context.rb +28 -0
- data/lib/tree_haver/backend_registry.rb +19 -432
- data/lib/tree_haver/contracts.rb +460 -0
- data/lib/tree_haver/kaitai_backend.rb +30 -0
- data/lib/tree_haver/language_pack.rb +190 -0
- data/lib/tree_haver/peg_backends.rb +76 -0
- data/lib/tree_haver/version.rb +1 -12
- data/lib/tree_haver.rb +7 -1316
- data.tar.gz.sig +0 -0
- metadata +34 -251
- metadata.gz.sig +0 -0
- data/CHANGELOG.md +0 -1393
- data/CITATION.cff +0 -20
- data/CODE_OF_CONDUCT.md +0 -134
- data/CONTRIBUTING.md +0 -359
- data/FUNDING.md +0 -74
- data/LICENSE.txt +0 -21
- data/README.md +0 -2320
- data/REEK +0 -0
- data/RUBOCOP.md +0 -71
- data/SECURITY.md +0 -21
- data/lib/tree_haver/backend_api.rb +0 -349
- data/lib/tree_haver/backends/citrus.rb +0 -487
- data/lib/tree_haver/backends/ffi.rb +0 -1009
- data/lib/tree_haver/backends/java.rb +0 -893
- data/lib/tree_haver/backends/mri.rb +0 -362
- data/lib/tree_haver/backends/parslet.rb +0 -560
- data/lib/tree_haver/backends/prism.rb +0 -471
- data/lib/tree_haver/backends/psych.rb +0 -375
- data/lib/tree_haver/backends/rust.rb +0 -239
- data/lib/tree_haver/base/language.rb +0 -98
- data/lib/tree_haver/base/node.rb +0 -322
- data/lib/tree_haver/base/parser.rb +0 -24
- data/lib/tree_haver/base/point.rb +0 -48
- data/lib/tree_haver/base/tree.rb +0 -128
- data/lib/tree_haver/base.rb +0 -12
- data/lib/tree_haver/citrus_grammar_finder.rb +0 -218
- data/lib/tree_haver/compat.rb +0 -43
- data/lib/tree_haver/grammar_finder.rb +0 -374
- data/lib/tree_haver/language.rb +0 -295
- data/lib/tree_haver/language_registry.rb +0 -190
- data/lib/tree_haver/library_path_utils.rb +0 -80
- data/lib/tree_haver/node.rb +0 -579
- data/lib/tree_haver/parser.rb +0 -438
- data/lib/tree_haver/parslet_grammar_finder.rb +0 -224
- data/lib/tree_haver/path_validator.rb +0 -353
- data/lib/tree_haver/point.rb +0 -27
- data/lib/tree_haver/rspec/dependency_tags.rb +0 -1392
- data/lib/tree_haver/rspec/testable_node.rb +0 -217
- data/lib/tree_haver/rspec.rb +0 -33
- data/lib/tree_haver/tree.rb +0 -258
- data/sig/tree_haver/backends.rbs +0 -352
- data/sig/tree_haver/grammar_finder.rbs +0 -29
- data/sig/tree_haver/path_validator.rbs +0 -32
- data/sig/tree_haver.rbs +0 -234
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9252beb2837645a278018f2cc625f06e941ab68a7058f424963c37bfa7fab2fc
|
|
4
|
+
data.tar.gz: 140b2cfadb89f3f6d09d838053a667a2d10f278345e4d695dc15b439fff170eb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d1bc2f9f44b55dba4d026b0b27ee4c6f1e076dc42cd3759a2a43d2f1e745f4e3efd8a0d8c94d71c3cfc94a4df46770ab2be0315904cb3da3d6759468b530993f
|
|
7
|
+
data.tar.gz: 468d12010d1b8120e80744c8ef6fff01297c4085c9b359c411a3201dc94325e64661b00c6171c8e3436323f4b8d16450a83d40c07d17ec13cc6f182938f2402b
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TreeHaver
|
|
4
|
+
BACKEND_CONTEXT_KEY = :structured_merge_tree_haver_backend
|
|
5
|
+
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def current_backend_id
|
|
9
|
+
Thread.current[BACKEND_CONTEXT_KEY]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def with_backend(backend_id)
|
|
13
|
+
validate_backend_id!(backend_id)
|
|
14
|
+
|
|
15
|
+
previous_backend = Thread.current[BACKEND_CONTEXT_KEY]
|
|
16
|
+
Thread.current[BACKEND_CONTEXT_KEY] = backend_id.to_s
|
|
17
|
+
yield
|
|
18
|
+
ensure
|
|
19
|
+
Thread.current[BACKEND_CONTEXT_KEY] = previous_backend
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def validate_backend_id!(backend_id)
|
|
23
|
+
return if BackendRegistry.fetch(backend_id.to_s)
|
|
24
|
+
|
|
25
|
+
raise ArgumentError, "Unknown tree_haver backend #{backend_id}."
|
|
26
|
+
end
|
|
27
|
+
private_class_method :validate_backend_id!
|
|
28
|
+
end
|
|
@@ -1,457 +1,44 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
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
|
-
# == Full Tag Registration
|
|
26
|
-
#
|
|
27
|
-
# External gems can register complete tag support using {register_tag}:
|
|
28
|
-
# - Tag name (e.g., :commonmarker_backend)
|
|
29
|
-
# - Category (:backend, :gem, :parsing, :grammar)
|
|
30
|
-
# - Availability checker
|
|
31
|
-
# - Optional require path for lazy loading
|
|
32
|
-
#
|
|
33
|
-
# This enables tree_haver/rspec/dependency_tags to automatically configure
|
|
34
|
-
# RSpec exclusion filters for any registered tag without hardcoded knowledge.
|
|
35
|
-
#
|
|
36
|
-
# == Thread Safety
|
|
37
|
-
#
|
|
38
|
-
# All operations are thread-safe using a Mutex for synchronization.
|
|
39
|
-
# Results are cached after first check for performance.
|
|
40
|
-
#
|
|
41
|
-
# @example Registering a backend availability checker (simple form)
|
|
42
|
-
# # In commonmarker-merge/lib/commonmarker/merge/backend.rb
|
|
43
|
-
# TreeHaver::BackendRegistry.register_availability_checker(:commonmarker) do
|
|
44
|
-
# available?
|
|
45
|
-
# end
|
|
46
|
-
#
|
|
47
|
-
# @example Registering a full tag with require path (preferred for external gems)
|
|
48
|
-
# TreeHaver::BackendRegistry.register_tag(
|
|
49
|
-
# :commonmarker_backend,
|
|
50
|
-
# category: :backend,
|
|
51
|
-
# backend_name: :commonmarker,
|
|
52
|
-
# require_path: "commonmarker/merge"
|
|
53
|
-
# ) { Commonmarker::Merge::Backend.available? }
|
|
54
|
-
#
|
|
55
|
-
# @example Checking backend availability
|
|
56
|
-
# TreeHaver::BackendRegistry.available?(:commonmarker) # => true/false
|
|
57
|
-
# TreeHaver::BackendRegistry.available?(:markly) # => true/false
|
|
58
|
-
# TreeHaver::BackendRegistry.available?(:rbs) # => true/false
|
|
59
|
-
#
|
|
60
|
-
# @example Getting all registered tags
|
|
61
|
-
# TreeHaver::BackendRegistry.registered_tags # => [:commonmarker_backend, :markly_backend, ...]
|
|
62
|
-
# TreeHaver::BackendRegistry.tags_by_category(:backend) # => [...]
|
|
63
|
-
#
|
|
64
|
-
# @see TreeHaver::RSpec::DependencyTags Uses BackendRegistry for dynamic backend detection
|
|
65
|
-
# @api public
|
|
66
4
|
module BackendRegistry
|
|
67
|
-
@mutex = Mutex.new
|
|
68
|
-
@availability_checkers = {} # rubocop:disable ThreadSafety/MutableClassInstanceVariable
|
|
69
|
-
@availability_cache = {} # rubocop:disable ThreadSafety/MutableClassInstanceVariable
|
|
70
|
-
@tag_registry = {} # rubocop:disable ThreadSafety/MutableClassInstanceVariable
|
|
71
|
-
|
|
72
|
-
# Tag categories for organizing dependency tags
|
|
73
|
-
# @api private
|
|
74
|
-
CATEGORIES = %i[backend gem parsing grammar engine other].freeze
|
|
75
|
-
|
|
76
5
|
module_function
|
|
77
6
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
# with complete tag support. It registers both the availability checker and
|
|
82
|
-
# the tag metadata needed for RSpec configuration.
|
|
83
|
-
#
|
|
84
|
-
# When a tag is registered, this also dynamically defines a `*_available?` method
|
|
85
|
-
# on `TreeHaver::RSpec::DependencyTags` if it doesn't already exist.
|
|
86
|
-
#
|
|
87
|
-
# @param tag_name [Symbol] the RSpec tag name (e.g., :commonmarker_backend)
|
|
88
|
-
# @param category [Symbol] one of :backend, :gem, :parsing, :grammar, :engine, :other
|
|
89
|
-
# @param backend_name [Symbol, nil] the backend name for availability checks (defaults to tag without suffix)
|
|
90
|
-
# @param require_path [String, nil] optional require path to load before checking availability
|
|
91
|
-
# @param checker [#call, nil] a callable that returns true if available
|
|
92
|
-
# @yield Block form of checker (alternative to passing a callable)
|
|
93
|
-
# @yieldreturn [Boolean] true if the tag's dependency is available
|
|
94
|
-
# @return [void]
|
|
95
|
-
#
|
|
96
|
-
# @example Register a backend tag with require path
|
|
97
|
-
# TreeHaver::BackendRegistry.register_tag(
|
|
98
|
-
# :commonmarker_backend,
|
|
99
|
-
# category: :backend,
|
|
100
|
-
# require_path: "commonmarker/merge"
|
|
101
|
-
# ) { Commonmarker::Merge::Backend.available? }
|
|
102
|
-
#
|
|
103
|
-
# @example Register a gem tag
|
|
104
|
-
# TreeHaver::BackendRegistry.register_tag(
|
|
105
|
-
# :toml_gem,
|
|
106
|
-
# category: :gem,
|
|
107
|
-
# require_path: "toml"
|
|
108
|
-
# ) { defined?(TOML) }
|
|
109
|
-
def register_tag(tag_name, category:, backend_name: nil, require_path: nil, checker: nil, &block)
|
|
110
|
-
callable = checker || block
|
|
111
|
-
raise ArgumentError, "Must provide a checker callable or block" unless callable
|
|
112
|
-
raise ArgumentError, "Checker must respond to #call" unless callable.respond_to?(:call)
|
|
113
|
-
raise ArgumentError, "Invalid category: #{category}" unless CATEGORIES.include?(category)
|
|
114
|
-
|
|
115
|
-
tag_sym = tag_name.to_sym
|
|
116
|
-
# Derive backend_name from tag_name if not provided (e.g., :commonmarker_backend -> :commonmarker)
|
|
117
|
-
derived_backend = backend_name || tag_sym.to_s.sub(/_backend$/, "").to_sym
|
|
118
|
-
|
|
119
|
-
@mutex.synchronize do
|
|
120
|
-
@tag_registry[tag_sym] = {
|
|
121
|
-
category: category,
|
|
122
|
-
backend_name: derived_backend,
|
|
123
|
-
require_path: require_path,
|
|
124
|
-
checker: callable,
|
|
125
|
-
}
|
|
126
|
-
# Also register as availability checker for the backend name
|
|
127
|
-
@availability_checkers[derived_backend] = callable
|
|
128
|
-
# Clear caches
|
|
129
|
-
@availability_cache.delete(derived_backend)
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
# Dynamically define the availability method on DependencyTags
|
|
133
|
-
# This happens outside the mutex to avoid potential deadlock
|
|
134
|
-
define_availability_method(derived_backend, tag_sym)
|
|
135
|
-
|
|
136
|
-
nil
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
# Register an availability checker for a backend (simple form)
|
|
140
|
-
#
|
|
141
|
-
# The checker should be a callable (lambda/proc/block) that returns true if
|
|
142
|
-
# the backend is available and can be used. The checker is called lazily
|
|
143
|
-
# (only when {available?} is first called for this backend).
|
|
144
|
-
#
|
|
145
|
-
# For full tag support including require paths, use {register_tag} instead.
|
|
146
|
-
#
|
|
147
|
-
# @param backend_name [Symbol, String] the backend name (e.g., :commonmarker, :markly)
|
|
148
|
-
# @param checker [#call, nil] a callable that returns true if the backend is available
|
|
149
|
-
# @yield Block form of checker (alternative to passing a callable)
|
|
150
|
-
# @yieldreturn [Boolean] true if the backend is available
|
|
151
|
-
# @return [void]
|
|
152
|
-
# @raise [ArgumentError] if no checker callable or block is provided
|
|
153
|
-
# @raise [ArgumentError] if checker doesn't respond to #call
|
|
154
|
-
#
|
|
155
|
-
# @example Register with a block
|
|
156
|
-
# TreeHaver::BackendRegistry.register_availability_checker(:commonmarker) do
|
|
157
|
-
# require "commonmarker"
|
|
158
|
-
# true
|
|
159
|
-
# rescue LoadError
|
|
160
|
-
# false
|
|
161
|
-
# end
|
|
162
|
-
#
|
|
163
|
-
# @example Register with a lambda
|
|
164
|
-
# checker = -> { Commonmarker::Merge::Backend.available? }
|
|
165
|
-
# TreeHaver::BackendRegistry.register_availability_checker(:commonmarker, checker)
|
|
166
|
-
#
|
|
167
|
-
# @example Register referencing the module's available? method
|
|
168
|
-
# TreeHaver::BackendRegistry.register_availability_checker(:my_backend) do
|
|
169
|
-
# available? # Calls the enclosing module's available? method
|
|
170
|
-
# end
|
|
171
|
-
def register_availability_checker(backend_name, checker = nil, &block)
|
|
172
|
-
callable = checker || block
|
|
173
|
-
raise ArgumentError, "Must provide a checker callable or block" unless callable
|
|
174
|
-
raise ArgumentError, "Checker must respond to #call" unless callable.respond_to?(:call)
|
|
175
|
-
|
|
176
|
-
@mutex.synchronize do
|
|
177
|
-
@availability_checkers[backend_name.to_sym] = callable
|
|
178
|
-
# Clear cache for this backend when re-registering
|
|
179
|
-
@availability_cache.delete(backend_name.to_sym)
|
|
7
|
+
def register(backend)
|
|
8
|
+
mutex.synchronize do
|
|
9
|
+
backends[backend.id] = deep_dup(backend.to_h)
|
|
180
10
|
end
|
|
181
11
|
nil
|
|
182
12
|
end
|
|
183
13
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
# (and the result cached). If no checker is registered, falls back to checking
|
|
188
|
-
# `TreeHaver::Backends::<Name>.available?` for built-in backends.
|
|
189
|
-
#
|
|
190
|
-
# Results are cached to avoid repeated expensive checks (e.g., requiring gems).
|
|
191
|
-
# Use {clear_cache!} to reset the cache if backend availability may have changed.
|
|
192
|
-
#
|
|
193
|
-
# @param backend_name [Symbol, String] the backend name to check
|
|
194
|
-
# @return [Boolean] true if the backend is available, false otherwise
|
|
195
|
-
#
|
|
196
|
-
# @example
|
|
197
|
-
# TreeHaver::BackendRegistry.available?(:commonmarker) # => true
|
|
198
|
-
# TreeHaver::BackendRegistry.available?(:nonexistent) # => false
|
|
199
|
-
def available?(backend_name)
|
|
200
|
-
key = backend_name.to_sym
|
|
201
|
-
|
|
202
|
-
# First, check cache and get checker without holding mutex for long
|
|
203
|
-
checker = nil
|
|
204
|
-
@mutex.synchronize do
|
|
205
|
-
# Return cached result if available
|
|
206
|
-
return @availability_cache[key] if @availability_cache.key?(key)
|
|
207
|
-
|
|
208
|
-
# Get registered checker (if any)
|
|
209
|
-
checker = @availability_checkers[key]
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
# Compute result OUTSIDE the mutex to avoid deadlock when loading backends
|
|
213
|
-
# (loading a backend module triggers register_availability_checker which needs the mutex)
|
|
214
|
-
result = if checker
|
|
215
|
-
# Use the registered checker
|
|
216
|
-
begin
|
|
217
|
-
checker.call
|
|
218
|
-
rescue StandardError
|
|
219
|
-
false
|
|
220
|
-
end
|
|
221
|
-
else
|
|
222
|
-
# Fall back to checking TreeHaver::Backends::<Name>
|
|
223
|
-
# This may load the backend module, which will register its checker
|
|
224
|
-
check_builtin_backend(key)
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
# Cache the result
|
|
228
|
-
@mutex.synchronize do
|
|
229
|
-
# Double-check cache in case another thread computed it
|
|
230
|
-
return @availability_cache[key] if @availability_cache.key?(key)
|
|
231
|
-
@availability_cache[key] = result
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
result
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
# Check if an availability checker is registered for a backend
|
|
238
|
-
#
|
|
239
|
-
# @param backend_name [Symbol, String] the backend name
|
|
240
|
-
# @return [Boolean] true if a checker is registered
|
|
241
|
-
#
|
|
242
|
-
# @example
|
|
243
|
-
# TreeHaver::BackendRegistry.registered?(:commonmarker) # => true (if loaded)
|
|
244
|
-
# TreeHaver::BackendRegistry.registered?(:nonexistent) # => false
|
|
245
|
-
def registered?(backend_name)
|
|
246
|
-
@mutex.synchronize do
|
|
247
|
-
@availability_checkers.key?(backend_name.to_sym)
|
|
248
|
-
end
|
|
14
|
+
def fetch(id)
|
|
15
|
+
data = mutex.synchronize { backends[id] }
|
|
16
|
+
data && BackendReference.new(**deep_dup(data))
|
|
249
17
|
end
|
|
250
18
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
#
|
|
255
|
-
# @example
|
|
256
|
-
# TreeHaver::BackendRegistry.registered_backends
|
|
257
|
-
# # => [:mri, :rust, :ffi, :java, :prism, :psych, :citrus, :commonmarker, :markly]
|
|
258
|
-
def registered_backends
|
|
259
|
-
@mutex.synchronize do
|
|
260
|
-
@availability_checkers.keys.dup
|
|
19
|
+
def all
|
|
20
|
+
mutex.synchronize do
|
|
21
|
+
backends.values.map { |backend| BackendReference.new(**deep_dup(backend)) }
|
|
261
22
|
end
|
|
262
23
|
end
|
|
263
24
|
|
|
264
|
-
# Clear the availability cache
|
|
265
|
-
#
|
|
266
|
-
# Useful for testing or when backend availability may have changed
|
|
267
|
-
# (e.g., after installing a gem mid-process).
|
|
268
|
-
#
|
|
269
|
-
# @return [void]
|
|
270
|
-
#
|
|
271
|
-
# @example
|
|
272
|
-
# TreeHaver::BackendRegistry.clear_cache!
|
|
273
|
-
# # Next call to available? will re-check
|
|
274
|
-
def clear_cache!
|
|
275
|
-
@mutex.synchronize do
|
|
276
|
-
@availability_cache.clear
|
|
277
|
-
end
|
|
278
|
-
nil
|
|
279
|
-
end
|
|
280
|
-
|
|
281
|
-
# Clear all registrations and cache
|
|
282
|
-
#
|
|
283
|
-
# Removes all registered checkers and cached results.
|
|
284
|
-
# Primarily useful for testing to reset state between test cases.
|
|
285
|
-
#
|
|
286
|
-
# @return [void]
|
|
287
|
-
#
|
|
288
|
-
# @example
|
|
289
|
-
# TreeHaver::BackendRegistry.clear!
|
|
290
25
|
def clear!
|
|
291
|
-
|
|
292
|
-
@availability_checkers.clear
|
|
293
|
-
@availability_cache.clear
|
|
294
|
-
@tag_registry.clear
|
|
295
|
-
end
|
|
296
|
-
nil
|
|
26
|
+
mutex.synchronize { backends.clear }
|
|
297
27
|
end
|
|
298
28
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
# ============================================================
|
|
302
|
-
|
|
303
|
-
# Get all registered tag names
|
|
304
|
-
#
|
|
305
|
-
# @return [Array<Symbol>] list of registered tag names
|
|
306
|
-
#
|
|
307
|
-
# @example
|
|
308
|
-
# TreeHaver::BackendRegistry.registered_tags
|
|
309
|
-
# # => [:commonmarker_backend, :markly_backend, :toml_gem, ...]
|
|
310
|
-
def registered_tags
|
|
311
|
-
@mutex.synchronize do
|
|
312
|
-
@tag_registry.keys.dup
|
|
313
|
-
end
|
|
314
|
-
end
|
|
315
|
-
|
|
316
|
-
# Get tags filtered by category
|
|
317
|
-
#
|
|
318
|
-
# @param category [Symbol] one of :backend, :gem, :parsing, :grammar, :engine, :other
|
|
319
|
-
# @return [Array<Symbol>] list of tag names in that category
|
|
320
|
-
#
|
|
321
|
-
# @example
|
|
322
|
-
# TreeHaver::BackendRegistry.tags_by_category(:backend)
|
|
323
|
-
# # => [:commonmarker_backend, :markly_backend, :mri_backend, ...]
|
|
324
|
-
def tags_by_category(category)
|
|
325
|
-
@mutex.synchronize do
|
|
326
|
-
@tag_registry.select { |_, meta| meta[:category] == category }.keys
|
|
327
|
-
end
|
|
328
|
-
end
|
|
329
|
-
|
|
330
|
-
# Get tag metadata
|
|
331
|
-
#
|
|
332
|
-
# @param tag_name [Symbol] the tag name
|
|
333
|
-
# @return [Hash, nil] tag metadata or nil if not registered
|
|
334
|
-
#
|
|
335
|
-
# @example
|
|
336
|
-
# TreeHaver::BackendRegistry.tag_metadata(:commonmarker_backend)
|
|
337
|
-
# # => { category: :backend, backend_name: :commonmarker, require_path: "commonmarker/merge", checker: #<Proc> }
|
|
338
|
-
def tag_metadata(tag_name)
|
|
339
|
-
@mutex.synchronize do
|
|
340
|
-
@tag_registry[tag_name.to_sym]&.dup
|
|
341
|
-
end
|
|
29
|
+
def deep_dup(value)
|
|
30
|
+
Marshal.load(Marshal.dump(value))
|
|
342
31
|
end
|
|
32
|
+
private_class_method :deep_dup
|
|
343
33
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
# @param tag_name [Symbol] the tag name
|
|
347
|
-
# @return [Boolean] true if the tag is registered
|
|
348
|
-
def tag_registered?(tag_name)
|
|
349
|
-
@mutex.synchronize do
|
|
350
|
-
@tag_registry.key?(tag_name.to_sym)
|
|
351
|
-
end
|
|
34
|
+
def backends
|
|
35
|
+
@backends ||= {} # rubocop:disable ThreadSafety/MutableClassInstanceVariable
|
|
352
36
|
end
|
|
37
|
+
private_class_method :backends
|
|
353
38
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
# This method handles require paths: if the tag has a require_path, it will
|
|
357
|
-
# attempt to load the gem before checking availability. This enables lazy
|
|
358
|
-
# loading of external gems.
|
|
359
|
-
#
|
|
360
|
-
# @param tag_name [Symbol] the tag name to check
|
|
361
|
-
# @return [Boolean] true if the tag's dependency is available
|
|
362
|
-
#
|
|
363
|
-
# @example
|
|
364
|
-
# TreeHaver::BackendRegistry.tag_available?(:commonmarker_backend) # => true/false
|
|
365
|
-
def tag_available?(tag_name)
|
|
366
|
-
tag_sym = tag_name.to_sym
|
|
367
|
-
|
|
368
|
-
# Get tag metadata
|
|
369
|
-
meta = @mutex.synchronize { @tag_registry[tag_sym] }
|
|
370
|
-
|
|
371
|
-
# If tag not registered, check if it's a backend name with _backend suffix
|
|
372
|
-
unless meta
|
|
373
|
-
# Try to derive backend name (e.g., :commonmarker_backend -> :commonmarker)
|
|
374
|
-
backend_name = tag_sym.to_s.sub(/_backend$/, "").to_sym
|
|
375
|
-
return available?(backend_name) if backend_name != tag_sym
|
|
376
|
-
return false
|
|
377
|
-
end
|
|
378
|
-
|
|
379
|
-
# Try to load the gem if require_path is specified
|
|
380
|
-
if meta[:require_path]
|
|
381
|
-
begin
|
|
382
|
-
require meta[:require_path]
|
|
383
|
-
rescue LoadError
|
|
384
|
-
# Gem not available
|
|
385
|
-
return false
|
|
386
|
-
end
|
|
387
|
-
end
|
|
388
|
-
|
|
389
|
-
# Check availability using the backend name
|
|
390
|
-
available?(meta[:backend_name])
|
|
391
|
-
end
|
|
392
|
-
|
|
393
|
-
# Get a summary of all registered tags and their availability
|
|
394
|
-
#
|
|
395
|
-
# @return [Hash{Symbol => Boolean}] map of tag name to availability
|
|
396
|
-
#
|
|
397
|
-
# @example
|
|
398
|
-
# TreeHaver::BackendRegistry.tag_summary
|
|
399
|
-
# # => { commonmarker_backend: true, markly_backend: false, ... }
|
|
400
|
-
def tag_summary
|
|
401
|
-
@mutex.synchronize { @tag_registry.keys.dup }.each_with_object({}) do |tag, result|
|
|
402
|
-
result[tag] = tag_available?(tag)
|
|
403
|
-
end
|
|
404
|
-
end
|
|
405
|
-
|
|
406
|
-
# Check a built-in TreeHaver backend
|
|
407
|
-
#
|
|
408
|
-
# Attempts to find the backend module at `TreeHaver::Backends::<Name>` and
|
|
409
|
-
# call its `available?` method. This is the fallback when no explicit
|
|
410
|
-
# checker has been registered.
|
|
411
|
-
#
|
|
412
|
-
# @param backend_name [Symbol] the backend name (e.g., :mri, :rust, :ffi)
|
|
413
|
-
# @return [Boolean] true if the backend module exists and reports available
|
|
414
|
-
# @api private
|
|
415
|
-
def check_builtin_backend(backend_name)
|
|
416
|
-
# Convert backend_name to PascalCase constant name
|
|
417
|
-
# e.g., :mri -> "MRI", :ffi -> "FFI", :commonmarker -> "Commonmarker"
|
|
418
|
-
const_name = backend_name.to_s.split("_").map(&:capitalize).join
|
|
419
|
-
backend_mod = TreeHaver::Backends.const_get(const_name)
|
|
420
|
-
backend_mod.respond_to?(:available?) && backend_mod.available?
|
|
421
|
-
rescue NameError
|
|
422
|
-
# Backend module doesn't exist
|
|
423
|
-
false
|
|
424
|
-
end
|
|
425
|
-
private_class_method :check_builtin_backend
|
|
426
|
-
|
|
427
|
-
# Dynamically define an availability method on DependencyTags
|
|
428
|
-
#
|
|
429
|
-
# This creates a `*_available?` method that checks tag_available? with
|
|
430
|
-
# memoization. The method is only defined if DependencyTags is loaded
|
|
431
|
-
# and doesn't already have a method with that name.
|
|
432
|
-
#
|
|
433
|
-
# @param backend_name [Symbol] the backend name (e.g., :commonmarker)
|
|
434
|
-
# @param tag_name [Symbol] the tag name (e.g., :commonmarker_backend)
|
|
435
|
-
# @return [void]
|
|
436
|
-
# @api private
|
|
437
|
-
def define_availability_method(backend_name, tag_name)
|
|
438
|
-
method_name = :"#{backend_name}_available?"
|
|
439
|
-
|
|
440
|
-
# Only define if DependencyTags is loaded
|
|
441
|
-
return unless defined?(TreeHaver::RSpec::DependencyTags)
|
|
442
|
-
|
|
443
|
-
deps = TreeHaver::RSpec::DependencyTags
|
|
444
|
-
|
|
445
|
-
# Don't override existing methods (built-in backends have explicit methods)
|
|
446
|
-
return if deps.respond_to?(method_name)
|
|
447
|
-
|
|
448
|
-
# Define the method dynamically
|
|
449
|
-
ivar = :"@#{backend_name}_available"
|
|
450
|
-
deps.define_singleton_method(method_name) do
|
|
451
|
-
return instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
|
452
|
-
instance_variable_set(ivar, TreeHaver::BackendRegistry.tag_available?(tag_name))
|
|
453
|
-
end
|
|
39
|
+
def mutex
|
|
40
|
+
@mutex ||= Mutex.new # rubocop:disable ThreadSafety/MutableClassInstanceVariable
|
|
454
41
|
end
|
|
455
|
-
private_class_method :
|
|
42
|
+
private_class_method :mutex
|
|
456
43
|
end
|
|
457
44
|
end
|