Module: Ast::Merge::RSpec::MergeGemRegistry
- Defined in:
- lib/ast/merge/rspec/merge_gem_registry.rb
Overview
Registry for merge gem dependency tag availability checkers
This module allows merge gems (like markly-merge, prism-merge, json-merge)
to register their availability checker for RSpec dependency tags without
ast-merge needing to know about them directly.
== Purpose
When running RSpec tests with dependency tags (e.g., :markly_merge),
ast-merge needs to know if each merge gem is available. The MergeGemRegistry
provides a way for gems to register their availability checkers, and also
pre-configures known merge gems so they can be checked before being loaded.
== Pre-configured Gems
The following merge gems are pre-configured so that their availability can
be checked before they are loaded (e.g., during RSpec setup):
- :markly_merge, :commonmarker_merge, :markdown_merge (markdown)
- :prism_merge, :bash_merge, :rbs_merge (code)
- :json_merge, :jsonc_merge (data)
- :toml_merge, :psych_merge, :dotenv_merge (config)
External merge gems can also register themselves by calling MergeGemRegistry.register
when loaded.
== Registration
Each merge gem registers itself when loaded using MergeGemRegistry.register:
- Tag name (e.g., :markly_merge)
- Require path (e.g., “markly/merge”)
- Merger class name (e.g., “Markly::Merge::SmartMerger”)
- Test source code to verify the merger works
- Optional category for grouping (e.g., :markdown, :data, :code)
When a tag is registered, an availability method is automatically defined
on Ast::Merge::RSpec::DependencyTags.
== Thread Safety
All operations are thread-safe using a Mutex for synchronization.
Results are cached after first check for performance.
Constant Summary collapse
- CATEGORIES =
Valid categories for merge gems
%i[markdown data code config other].freeze
- KNOWN_GEMS =
Pre-configured known merge gems
These can be checked before the gems are actually loaded { # Markdown gems markly_merge: { require_path: "markly/merge", merger_class: "Markly::Merge::SmartMerger", test_source: "# Test\n\nParagraph", category: :markdown, skip_instantiation: false, }, commonmarker_merge: { require_path: "commonmarker/merge", merger_class: "Commonmarker::Merge::SmartMerger", test_source: "# Test\n\nParagraph", category: :markdown, skip_instantiation: false, }, markdown_merge: { require_path: "markdown/merge", merger_class: "Markdown::Merge::SmartMerger", test_source: "# Test\n\nParagraph", category: :markdown, skip_instantiation: true, # Requires backend }, # Code gems prism_merge: { require_path: "prism/merge", merger_class: "Prism::Merge::SmartMerger", test_source: "def foo; end", category: :code, skip_instantiation: false, }, bash_merge: { require_path: "bash/merge", merger_class: "Bash::Merge::SmartMerger", test_source: "#!/bin/bash\necho hello", category: :code, skip_instantiation: false, }, rbs_merge: { require_path: "rbs/merge", merger_class: "Rbs::Merge::SmartMerger", test_source: "class Foo\nend", category: :code, skip_instantiation: false, }, # Data gems json_merge: { require_path: "json/merge", merger_class: "Json::Merge::SmartMerger", test_source: '{"key": "value"}', category: :data, skip_instantiation: false, }, jsonc_merge: { require_path: "jsonc/merge", merger_class: "Jsonc::Merge::SmartMerger", test_source: "// comment\n{\"key\": \"value\"}", category: :data, skip_instantiation: false, }, # Config gems toml_merge: { require_path: "toml/merge", merger_class: "Toml::Merge::SmartMerger", test_source: "[section]\nkey = \"value\"", category: :config, skip_instantiation: false, }, psych_merge: { require_path: "psych/merge", merger_class: "Psych::Merge::SmartMerger", test_source: "key: value", category: :config, skip_instantiation: false, }, dotenv_merge: { require_path: "dotenv/merge", merger_class: "Dotenv::Merge::SmartMerger", test_source: "KEY=value", category: :config, skip_instantiation: false, }, }.freeze
Class Method Summary collapse
-
.available?(tag_name) ⇒ Boolean
Check if a merge gem is available and functional.
-
.clear! ⇒ void
Clear all registrations and cache.
-
.clear_cache! ⇒ void
Clear the availability cache.
-
.force_check_availability! ⇒ void
Force availability checking for all registered gems.
-
.gems_by_category(category) ⇒ Array<Symbol>
Get gems filtered by category.
-
.info(tag_name) ⇒ Hash?
Get registration info for a gem.
-
.register(tag_name, require_path:, merger_class:, test_source:, category: :other, skip_instantiation: false) ⇒ void
Register a merge gem for dependency tag support.
-
.register_known_gems(*gem_names) ⇒ void
Register one or more known gems for RSpec dependency tag support.
-
.registered?(tag_name) ⇒ Boolean
Check if a tag is registered.
-
.registered_gems ⇒ Array<Symbol>
Get all explicitly registered gem tag names.
-
.reset_availability! ⇒ void
Reset memoized availability on DependencyTags.
-
.summary ⇒ Hash{Symbol => Boolean}
Get a summary of all registered gems and their availability.
Class Method Details
.available?(tag_name) ⇒ Boolean
Check if a merge gem is available and functional
This method will try to load the gem if it’s not yet registered but
is known (in KNOWN_GEMS). This allows availability checking before
the gem is explicitly loaded.
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 |
# File 'lib/ast/merge/rspec/merge_gem_registry.rb', line 216 def available?(tag_name) tag_sym = tag_name.to_sym # Check cache first @mutex.synchronize do return @availability_cache[tag_sym] if @availability_cache.key?(tag_sym) end # Get registration info (from registry or known gems) info = @mutex.synchronize { @registry[tag_sym] } info ||= KNOWN_GEMS[tag_sym] return false unless info # Check if gem works result = gem_works?( info[:require_path], info[:merger_class], info[:test_source], info[:skip_instantiation], ) # Cache result @mutex.synchronize do @availability_cache[tag_sym] = result end result end |
.clear! ⇒ void
This method returns an undefined value.
Clear all registrations and cache
382 383 384 385 386 387 388 |
# File 'lib/ast/merge/rspec/merge_gem_registry.rb', line 382 def clear! @mutex.synchronize do @registry.clear @availability_cache.clear end nil end |
.clear_cache! ⇒ void
This method returns an undefined value.
Clear the availability cache
372 373 374 375 376 377 |
# File 'lib/ast/merge/rspec/merge_gem_registry.rb', line 372 def clear_cache! @mutex.synchronize do @availability_cache.clear end nil end |
.force_check_availability! ⇒ void
This method returns an undefined value.
Force availability checking for all registered gems
This method should be called AFTER SimpleCov is loaded (typically at the end
of spec_helper.rb) to trigger gem loading and availability checking. Calling
this ensures RSpec exclusion filters are properly configured based on which
gems are actually available.
This is necessary because register_known_gems() only registers gems without
checking availability. The actual availability check (which requires loading
the gem) must happen AFTER coverage instrumentation is set up.
340 341 342 343 344 345 346 347 |
# File 'lib/ast/merge/rspec/merge_gem_registry.rb', line 340 def force_check_availability! registered_gems.each do |tag| # This will trigger gem_works? which loads the gem # Results are cached, so subsequent calls are fast available?(tag) end nil end |
.gems_by_category(category) ⇒ Array<Symbol>
Get gems filtered by category
316 317 318 319 320 321 322 |
# File 'lib/ast/merge/rspec/merge_gem_registry.rb', line 316 def gems_by_category(category) @mutex.synchronize do known = KNOWN_GEMS.select { |_, info| info[:category] == category }.keys registered = @registry.select { |_, info| info[:category] == category }.keys (known + registered).uniq end end |
.info(tag_name) ⇒ Hash?
Get registration info for a gem
353 354 355 356 357 358 |
# File 'lib/ast/merge/rspec/merge_gem_registry.rb', line 353 def info(tag_name) tag_sym = tag_name.to_sym @mutex.synchronize do @registry[tag_sym]&.dup || KNOWN_GEMS[tag_sym]&.dup end end |
.register(tag_name, require_path:, merger_class:, test_source:, category: :other, skip_instantiation: false) ⇒ void
This method returns an undefined value.
Register a merge gem for dependency tag support
When a gem is registered, this also dynamically defines a *_available? method
on Ast::Merge::RSpec::DependencyTags if it doesn’t already exist.
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
# File 'lib/ast/merge/rspec/merge_gem_registry.rb', line 185 def register(tag_name, require_path:, merger_class:, test_source:, category: :other, skip_instantiation: false) raise ArgumentError, "Invalid category: #{category}" unless CATEGORIES.include?(category) tag_sym = tag_name.to_sym @mutex.synchronize do @registry[tag_sym] = { require_path: require_path, merger_class: merger_class, test_source: test_source, category: category, skip_instantiation: skip_instantiation, } # Clear cache when re-registering @availability_cache.delete(tag_sym) end # Define availability method on DependencyTags define_availability_method(tag_sym) nil end |
.register_known_gems(*gem_names) ⇒ void
This method returns an undefined value.
Register one or more known gems for RSpec dependency tag support
This allows test suites to explicitly register only the merge gems they need
for their tests, avoiding the overhead of registering all known gems.
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 |
# File 'lib/ast/merge/rspec/merge_gem_registry.rb', line 273 def register_known_gems(*gem_names) gem_names.each do |tag_name| tag_sym = tag_name.to_sym # Skip if not in KNOWN_GEMS unless KNOWN_GEMS.key?(tag_sym) warn("Unknown gem: #{tag_name}. Available: #{KNOWN_GEMS.keys.join(", ")}") next end # Skip if already registered next if registered?(tag_sym) = KNOWN_GEMS[tag_sym] register( tag_sym, require_path: [:require_path], merger_class: [:merger_class], test_source: [:test_source], category: [:category], skip_instantiation: [:skip_instantiation], ) end end |
.registered?(tag_name) ⇒ Boolean
Check if a tag is registered
250 251 252 253 254 |
# File 'lib/ast/merge/rspec/merge_gem_registry.rb', line 250 def registered?(tag_name) @mutex.synchronize do @registry.key?(tag_name.to_sym) end end |
.registered_gems ⇒ Array<Symbol>
Get all explicitly registered gem tag names
This returns ONLY gems that were explicitly registered via register() or
register_known_gems(), NOT all gems in KNOWN_GEMS. This prevents premature
loading of gems during RSpec tag setup, which would happen before SimpleCov
and ruin coverage reporting.
306 307 308 309 310 |
# File 'lib/ast/merge/rspec/merge_gem_registry.rb', line 306 def registered_gems @mutex.synchronize do @registry.keys end end |
.reset_availability! ⇒ void
This method returns an undefined value.
Reset memoized availability on DependencyTags
393 394 395 396 397 398 399 400 401 |
# File 'lib/ast/merge/rspec/merge_gem_registry.rb', line 393 def reset_availability! clear_cache! return unless defined?(DependencyTags) registered_gems.each do |tag| ivar = :"@#{tag}_available" DependencyTags.remove_instance_variable(ivar) if DependencyTags.instance_variable_defined?(ivar) end end |
.summary ⇒ Hash{Symbol => Boolean}
Get a summary of all registered gems and their availability
363 364 365 366 367 |
# File 'lib/ast/merge/rspec/merge_gem_registry.rb', line 363 def summary registered_gems.each_with_object({}) do |tag, result| result[tag] = available?(tag) end end |