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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/LICENSE.md +13 -0
  4. data/README.md +299 -660
  5. data/lib/tree_haver/backend_api.rb +78 -35
  6. data/lib/tree_haver/backend_context.rb +28 -0
  7. data/lib/tree_haver/backend_registry.rb +119 -382
  8. data/lib/tree_haver/backends/citrus.rb +55 -53
  9. data/lib/tree_haver/backends/ffi.rb +105 -101
  10. data/lib/tree_haver/backends/java.rb +92 -76
  11. data/lib/tree_haver/backends/mri.rb +44 -39
  12. data/lib/tree_haver/backends/parslet.rb +53 -48
  13. data/lib/tree_haver/backends/prism.rb +140 -43
  14. data/lib/tree_haver/backends/psych.rb +30 -26
  15. data/lib/tree_haver/backends/rust.rb +21 -17
  16. data/lib/tree_haver/backends/tslp.rb +274 -0
  17. data/lib/tree_haver/base/comment.rb +320 -0
  18. data/lib/tree_haver/base/language.rb +2 -2
  19. data/lib/tree_haver/base/node.rb +39 -31
  20. data/lib/tree_haver/base/parser.rb +4 -0
  21. data/lib/tree_haver/base/point.rb +3 -3
  22. data/lib/tree_haver/base/tree.rb +1 -1
  23. data/lib/tree_haver/citrus_grammar_finder.rb +21 -26
  24. data/lib/tree_haver/contracts.rb +1025 -0
  25. data/lib/tree_haver/grammar_finder.rb +125 -70
  26. data/lib/tree_haver/kaitai_backend.rb +30 -0
  27. data/lib/tree_haver/language.rb +36 -37
  28. data/lib/tree_haver/language_pack.rb +41 -0
  29. data/lib/tree_haver/language_registry.rb +34 -3
  30. data/lib/tree_haver/library_path_utils.rb +7 -7
  31. data/lib/tree_haver/node.rb +40 -31
  32. data/lib/tree_haver/parser.rb +44 -37
  33. data/lib/tree_haver/parslet_grammar_finder.rb +19 -26
  34. data/lib/tree_haver/path_validator.rb +39 -36
  35. data/lib/tree_haver/peg_backends.rb +76 -0
  36. data/lib/tree_haver/rspec/dependency_tags.rb +23 -1363
  37. data/lib/tree_haver/rspec.rb +1 -31
  38. data/lib/tree_haver/tree.rb +16 -7
  39. data/lib/tree_haver/version.rb +5 -14
  40. data/lib/tree_haver.rb +536 -1240
  41. data/sig/tree_haver.rbs +1 -229
  42. data.tar.gz.sig +0 -0
  43. metadata +154 -70
  44. metadata.gz.sig +0 -0
  45. data/CHANGELOG.md +0 -1393
  46. data/CITATION.cff +0 -20
  47. data/CODE_OF_CONDUCT.md +0 -134
  48. data/CONTRIBUTING.md +0 -359
  49. data/FUNDING.md +0 -74
  50. data/LICENSE.txt +0 -21
  51. data/REEK +0 -0
  52. data/RUBOCOP.md +0 -71
  53. data/SECURITY.md +0 -21
  54. data/lib/tree_haver/base.rb +0 -12
  55. data/lib/tree_haver/compat.rb +0 -43
  56. data/lib/tree_haver/rspec/testable_node.rb +0 -217
  57. data/sig/tree_haver/backends.rbs +0 -352
  58. data/sig/tree_haver/grammar_finder.rbs +0 -29
  59. 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
- @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
5
+ CATEGORIES = %i[backend gem parsing grammar engine capability other].freeze
75
6
 
76
7
  module_function
77
8
 
78
- # Register a full dependency tag with all metadata
79
- #
80
- # This is the preferred method for external gems to register their availability
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
- # 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)
16
+ def fetch(id)
17
+ data = mutex.synchronize { backends[id] }
18
+ data && BackendReference.new(**deep_dup(data))
19
+ end
135
20
 
136
- nil
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
- # 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)
27
+ def register_availability_checker(name, checker = nil, &block)
172
28
  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)
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
- @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)
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
- # Check if a backend is available
185
- #
186
- # If a checker was registered via {register_availability_checker}, it is called
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
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
- # 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
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
- # 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
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
- # Get all registered backend names
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
- # 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
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
- # 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
- 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
- @mutex.synchronize do
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
- @mutex.synchronize do
326
- @tag_registry.select { |_, meta| meta[:category] == category }.keys
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
- @mutex.synchronize do
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
- @mutex.synchronize do
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
- 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
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
- # Try to load the gem if require_path is specified
380
- if meta[:require_path]
110
+ if metadata[:require_path]
381
111
  begin
382
- require meta[:require_path]
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
- # Check availability using the backend name
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
- @mutex.synchronize { @tag_registry.keys.dup }.each_with_object({}) do |tag, result|
402
- result[tag] = tag_available?(tag)
122
+ registered_tags.each_with_object({}) do |tag, summary|
123
+ summary[tag] = tag_available?(tag)
403
124
  end
404
125
  end
405
126
 
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
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
- # Only define if DependencyTags is loaded
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