tree_haver 5.0.5 → 7.1.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/LICENSE.md +13 -0
- data/README.md +299 -660
- data/lib/tree_haver/backend_api.rb +78 -35
- data/lib/tree_haver/backend_context.rb +28 -0
- data/lib/tree_haver/backend_registry.rb +119 -382
- data/lib/tree_haver/backends/citrus.rb +55 -53
- data/lib/tree_haver/backends/ffi.rb +105 -101
- data/lib/tree_haver/backends/java.rb +92 -76
- data/lib/tree_haver/backends/mri.rb +44 -39
- data/lib/tree_haver/backends/parslet.rb +53 -48
- data/lib/tree_haver/backends/prism.rb +140 -43
- data/lib/tree_haver/backends/psych.rb +30 -26
- data/lib/tree_haver/backends/rust.rb +21 -17
- data/lib/tree_haver/backends/tslp.rb +274 -0
- data/lib/tree_haver/base/comment.rb +320 -0
- data/lib/tree_haver/base/language.rb +2 -2
- data/lib/tree_haver/base/node.rb +39 -31
- data/lib/tree_haver/base/parser.rb +4 -0
- data/lib/tree_haver/base/point.rb +3 -3
- data/lib/tree_haver/base/tree.rb +1 -1
- data/lib/tree_haver/citrus_grammar_finder.rb +21 -26
- data/lib/tree_haver/contracts.rb +1025 -0
- data/lib/tree_haver/grammar_finder.rb +125 -70
- data/lib/tree_haver/kaitai_backend.rb +30 -0
- data/lib/tree_haver/language.rb +36 -37
- data/lib/tree_haver/language_pack.rb +41 -0
- data/lib/tree_haver/language_registry.rb +34 -3
- data/lib/tree_haver/library_path_utils.rb +7 -7
- data/lib/tree_haver/node.rb +40 -31
- data/lib/tree_haver/parser.rb +44 -37
- data/lib/tree_haver/parslet_grammar_finder.rb +19 -26
- data/lib/tree_haver/path_validator.rb +39 -36
- data/lib/tree_haver/peg_backends.rb +76 -0
- data/lib/tree_haver/rspec/dependency_tags.rb +23 -1363
- data/lib/tree_haver/rspec.rb +1 -31
- data/lib/tree_haver/tree.rb +16 -7
- data/lib/tree_haver/version.rb +5 -14
- data/lib/tree_haver.rb +536 -1240
- data/sig/tree_haver.rbs +1 -229
- data.tar.gz.sig +0 -0
- metadata +154 -70
- 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/REEK +0 -0
- data/RUBOCOP.md +0 -71
- data/SECURITY.md +0 -21
- data/lib/tree_haver/base.rb +0 -12
- data/lib/tree_haver/compat.rb +0 -43
- data/lib/tree_haver/rspec/testable_node.rb +0 -217
- 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
|
@@ -1,457 +1,194 @@
|
|
|
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
|
-
|
|
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
|
|
5
|
+
CATEGORIES = %i[backend gem parsing grammar engine capability other].freeze
|
|
75
6
|
|
|
76
7
|
module_function
|
|
77
8
|
|
|
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)
|
|
9
|
+
def register(backend)
|
|
10
|
+
mutex.synchronize do
|
|
11
|
+
backends[backend.id] = deep_dup(backend.to_h)
|
|
130
12
|
end
|
|
13
|
+
nil
|
|
14
|
+
end
|
|
131
15
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
16
|
+
def fetch(id)
|
|
17
|
+
data = mutex.synchronize { backends[id] }
|
|
18
|
+
data && BackendReference.new(**deep_dup(data))
|
|
19
|
+
end
|
|
135
20
|
|
|
136
|
-
|
|
21
|
+
def all
|
|
22
|
+
mutex.synchronize do
|
|
23
|
+
backends.values.map { |backend| BackendReference.new(**deep_dup(backend)) }
|
|
24
|
+
end
|
|
137
25
|
end
|
|
138
26
|
|
|
139
|
-
|
|
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)
|
|
27
|
+
def register_availability_checker(name, checker = nil, &block)
|
|
172
28
|
callable = checker || block
|
|
173
|
-
raise ArgumentError,
|
|
174
|
-
raise ArgumentError,
|
|
29
|
+
raise ArgumentError, 'Must provide a checker callable or block' unless callable
|
|
30
|
+
raise ArgumentError, 'Checker must respond to #call' unless callable.respond_to?(:call)
|
|
175
31
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
@availability_cache.delete(backend_name.to_sym)
|
|
32
|
+
mutex.synchronize do
|
|
33
|
+
availability_checkers[name.to_sym] = callable
|
|
34
|
+
availability_cache.delete(name.to_sym)
|
|
180
35
|
end
|
|
181
36
|
nil
|
|
182
37
|
end
|
|
183
38
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
|
39
|
+
def available?(name)
|
|
40
|
+
key = name.to_sym
|
|
41
|
+
checker = mutex.synchronize do
|
|
42
|
+
return availability_cache[key] if availability_cache.key?(key)
|
|
226
43
|
|
|
227
|
-
|
|
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
|
|
44
|
+
availability_checkers[key]
|
|
232
45
|
end
|
|
46
|
+
return false unless checker
|
|
233
47
|
|
|
48
|
+
result = !!checker.call
|
|
49
|
+
mutex.synchronize { availability_cache[key] = result }
|
|
234
50
|
result
|
|
51
|
+
rescue StandardError
|
|
52
|
+
false
|
|
235
53
|
end
|
|
236
54
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
|
249
|
-
end
|
|
55
|
+
def register_tag(tag_name, category:, backend_name: nil, require_path: nil, checker: nil, &block)
|
|
56
|
+
callable = checker || block
|
|
57
|
+
raise ArgumentError, 'Must provide a checker callable or block' unless callable
|
|
58
|
+
raise ArgumentError, 'Checker must respond to #call' unless callable.respond_to?(:call)
|
|
59
|
+
raise ArgumentError, "Invalid category: #{category}" unless CATEGORIES.include?(category)
|
|
250
60
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
# @return [Array<Symbol>] list of registered backend names
|
|
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
|
|
261
|
-
end
|
|
262
|
-
end
|
|
61
|
+
tag = tag_name.to_sym
|
|
62
|
+
backend = backend_name || inferred_backend_name(tag)
|
|
263
63
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
# # Next call to available? will re-check
|
|
274
|
-
def clear_cache!
|
|
275
|
-
@mutex.synchronize do
|
|
276
|
-
@availability_cache.clear
|
|
64
|
+
mutex.synchronize do
|
|
65
|
+
tag_registry[tag] = {
|
|
66
|
+
category: category,
|
|
67
|
+
backend_name: backend.to_sym,
|
|
68
|
+
require_path: require_path,
|
|
69
|
+
checker: callable
|
|
70
|
+
}
|
|
71
|
+
availability_checkers[backend.to_sym] = callable
|
|
72
|
+
availability_cache.delete(backend.to_sym)
|
|
277
73
|
end
|
|
74
|
+
|
|
75
|
+
define_availability_method(backend.to_sym, tag)
|
|
278
76
|
nil
|
|
279
77
|
end
|
|
280
78
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
#
|
|
288
|
-
# @example
|
|
289
|
-
# TreeHaver::BackendRegistry.clear!
|
|
290
|
-
def clear!
|
|
291
|
-
@mutex.synchronize do
|
|
292
|
-
@availability_checkers.clear
|
|
293
|
-
@availability_cache.clear
|
|
294
|
-
@tag_registry.clear
|
|
295
|
-
end
|
|
296
|
-
nil
|
|
79
|
+
def registered?(name)
|
|
80
|
+
mutex.synchronize { availability_checkers.key?(name.to_sym) }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def registered_backends
|
|
84
|
+
mutex.synchronize { availability_checkers.keys.dup }
|
|
297
85
|
end
|
|
298
86
|
|
|
299
|
-
# ============================================================
|
|
300
|
-
# Tag Registry Methods
|
|
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
87
|
def registered_tags
|
|
311
|
-
|
|
312
|
-
@tag_registry.keys.dup
|
|
313
|
-
end
|
|
88
|
+
mutex.synchronize { tag_registry.keys.dup }
|
|
314
89
|
end
|
|
315
90
|
|
|
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
91
|
def tags_by_category(category)
|
|
325
|
-
|
|
326
|
-
|
|
92
|
+
mutex.synchronize do
|
|
93
|
+
tag_registry.select { |_, metadata| metadata[:category] == category }.keys
|
|
327
94
|
end
|
|
328
95
|
end
|
|
329
96
|
|
|
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
97
|
def tag_metadata(tag_name)
|
|
339
|
-
|
|
340
|
-
@tag_registry[tag_name.to_sym]&.dup
|
|
341
|
-
end
|
|
98
|
+
mutex.synchronize { tag_registry[tag_name.to_sym]&.dup }
|
|
342
99
|
end
|
|
343
100
|
|
|
344
|
-
# Check if a tag is registered
|
|
345
|
-
#
|
|
346
|
-
# @param tag_name [Symbol] the tag name
|
|
347
|
-
# @return [Boolean] true if the tag is registered
|
|
348
101
|
def tag_registered?(tag_name)
|
|
349
|
-
|
|
350
|
-
@tag_registry.key?(tag_name.to_sym)
|
|
351
|
-
end
|
|
102
|
+
mutex.synchronize { tag_registry.key?(tag_name.to_sym) }
|
|
352
103
|
end
|
|
353
104
|
|
|
354
|
-
# Check if a tag's dependency is available
|
|
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
105
|
def tag_available?(tag_name)
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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
|
|
106
|
+
tag = tag_name.to_sym
|
|
107
|
+
metadata = mutex.synchronize { tag_registry[tag]&.dup }
|
|
108
|
+
return available?(inferred_backend_name(tag)) unless metadata
|
|
378
109
|
|
|
379
|
-
|
|
380
|
-
if meta[:require_path]
|
|
110
|
+
if metadata[:require_path]
|
|
381
111
|
begin
|
|
382
|
-
require
|
|
112
|
+
require metadata[:require_path]
|
|
383
113
|
rescue LoadError
|
|
384
|
-
# Gem not available
|
|
385
114
|
return false
|
|
386
115
|
end
|
|
387
116
|
end
|
|
388
117
|
|
|
389
|
-
|
|
390
|
-
available?(meta[:backend_name])
|
|
118
|
+
available?(metadata[:backend_name])
|
|
391
119
|
end
|
|
392
120
|
|
|
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
121
|
def tag_summary
|
|
401
|
-
|
|
402
|
-
|
|
122
|
+
registered_tags.each_with_object({}) do |tag, summary|
|
|
123
|
+
summary[tag] = tag_available?(tag)
|
|
403
124
|
end
|
|
404
125
|
end
|
|
405
126
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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
|
|
127
|
+
def clear_cache!
|
|
128
|
+
mutex.synchronize { availability_cache.clear }
|
|
129
|
+
nil
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def clear!
|
|
133
|
+
mutex.synchronize do
|
|
134
|
+
backends.clear
|
|
135
|
+
availability_checkers.clear
|
|
136
|
+
availability_cache.clear
|
|
137
|
+
tag_registry.clear
|
|
138
|
+
end
|
|
424
139
|
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
140
|
|
|
440
|
-
|
|
141
|
+
def inferred_backend_name(tag_name)
|
|
142
|
+
tag = tag_name.to_s
|
|
143
|
+
tag = tag.delete_suffix('_backend')
|
|
144
|
+
tag.to_sym
|
|
145
|
+
end
|
|
146
|
+
private_class_method :inferred_backend_name
|
|
147
|
+
|
|
148
|
+
def define_availability_method(backend_name, tag_name)
|
|
441
149
|
return unless defined?(TreeHaver::RSpec::DependencyTags)
|
|
442
150
|
|
|
443
151
|
deps = TreeHaver::RSpec::DependencyTags
|
|
444
|
-
|
|
445
|
-
# Don't override existing methods (built-in backends have explicit methods)
|
|
152
|
+
method_name = :"#{backend_name}_available?"
|
|
446
153
|
return if deps.respond_to?(method_name)
|
|
447
154
|
|
|
448
|
-
# Define the method dynamically
|
|
449
155
|
ivar = :"@#{backend_name}_available"
|
|
450
156
|
deps.define_singleton_method(method_name) do
|
|
451
157
|
return instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
|
158
|
+
|
|
452
159
|
instance_variable_set(ivar, TreeHaver::BackendRegistry.tag_available?(tag_name))
|
|
453
160
|
end
|
|
454
161
|
end
|
|
455
162
|
private_class_method :define_availability_method
|
|
163
|
+
|
|
164
|
+
def deep_dup(value)
|
|
165
|
+
Marshal.load(Marshal.dump(value))
|
|
166
|
+
end
|
|
167
|
+
private_class_method :deep_dup
|
|
168
|
+
|
|
169
|
+
def backends
|
|
170
|
+
@backends ||= {}
|
|
171
|
+
end
|
|
172
|
+
private_class_method :backends
|
|
173
|
+
|
|
174
|
+
def mutex
|
|
175
|
+
@mutex ||= Mutex.new
|
|
176
|
+
end
|
|
177
|
+
private_class_method :mutex
|
|
178
|
+
|
|
179
|
+
def availability_checkers
|
|
180
|
+
@availability_checkers ||= {}
|
|
181
|
+
end
|
|
182
|
+
private_class_method :availability_checkers
|
|
183
|
+
|
|
184
|
+
def availability_cache
|
|
185
|
+
@availability_cache ||= {}
|
|
186
|
+
end
|
|
187
|
+
private_class_method :availability_cache
|
|
188
|
+
|
|
189
|
+
def tag_registry
|
|
190
|
+
@tag_registry ||= {}
|
|
191
|
+
end
|
|
192
|
+
private_class_method :tag_registry
|
|
456
193
|
end
|
|
457
194
|
end
|