loading
Generated 2025-10-08T23:59:15+00:00

All Files ( 100.0% covered at 27.52 hits/line )

6 files in total.
111 relevant lines, 111 lines covered and 0 lines missed. ( 100.0% )
22 total branches, 22 branches covered and 0 branches missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line Branch Coverage Branches Covered branches Missed branches
lib/infusible.rb 100.00 % 14 8 8 0 1.00 100.00 % 0 0 0
lib/infusible/actuator.rb 100.00 % 21 10 10 0 2.40 100.00 % 0 0 0
lib/infusible/builder.rb 100.00 % 113 61 61 0 30.54 100.00 % 16 16 0
lib/infusible/dependency_map.rb 100.00 % 44 22 22 0 51.95 100.00 % 6 6 0
lib/infusible/errors/duplicate_dependency.rb 100.00 % 12 5 5 0 2.00 100.00 % 0 0 0
lib/infusible/errors/invalid_dependency.rb 100.00 % 12 5 5 0 1.40 100.00 % 0 0 0

lib/infusible.rb

100.0% lines covered

100.0% branches covered

8 relevant lines. 8 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "infusible/actuator"
  3. 1 require "infusible/builder"
  4. 1 require "infusible/dependency_map"
  5. 1 require "infusible/errors/duplicate_dependency"
  6. 1 require "infusible/errors/invalid_dependency"
  7. # Main namespace.
  8. 1 module Infusible
  9. 1 METHOD_SCOPES = %i[public protected private].freeze
  10. 1 def self.[](container) = Actuator.new container
  11. end

lib/infusible/actuator.rb

100.0% lines covered

100.0% branches covered

10 relevant lines. 10 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Infusible
  3. # Associates the container with the builder for actualization.
  4. 1 class Actuator
  5. 1 def initialize container, builder: Infusible::Builder
  6. 8 @container = container
  7. 8 @builder = builder
  8. end
  9. 1 def [](*configuration) = builder.new container, *configuration
  10. 1 def public(*configuration) = builder.new container, *configuration, scope: __method__
  11. 1 def protected(*configuration) = builder.new container, *configuration, scope: __method__
  12. 1 private
  13. 1 attr_reader :container, :builder
  14. end
  15. end

lib/infusible/builder.rb

100.0% lines covered

100.0% branches covered

61 relevant lines. 61 lines covered and 0 lines missed.
16 total branches, 16 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "marameters"
  3. 1 module Infusible
  4. # Provides the automatic and complete resolution of all injected dependencies.
  5. # :reek:TooManyInstanceVariables
  6. 1 class Builder < Module
  7. 1 def self.define_instance_variables target, keys, keywords
  8. 41 else: 4 then: 37 unless target.instance_variable_defined? :@infused_keys
  9. 37 target.instance_variable_set :@infused_keys, keys
  10. end
  11. 41 keys.each do |key|
  12. 72 else: 70 then: 2 next unless keywords.key?(key) || !target.instance_variable_defined?(:"@#{key}")
  13. 70 target.instance_variable_set :"@#{key}", keywords[key]
  14. end
  15. end
  16. 1 private_class_method :define_instance_variables
  17. 1 def initialize container, *configuration, scope: :private
  18. 61 super()
  19. 61 @container = container
  20. 61 @dependencies = DependencyMap.new(*configuration)
  21. 61 @scope = scope
  22. 61 @class_module = Module.new
  23. 61 @instance_module = Module.new.set_temporary_name "infusible"
  24. 61 freeze
  25. end
  26. 1 def included descendant
  27. 43 else: 42 then: 1 unless descendant.is_a? Class
  28. 1 fail TypeError,
  29. "Can only infuse a class, invalid object: #{descendant} (#{descendant.class})."
  30. end
  31. 42 super
  32. 42 define descendant
  33. 42 descendant.extend class_module
  34. 42 descendant.include instance_module
  35. end
  36. 1 private
  37. 1 attr_reader :container, :dependencies, :scope, :class_module, :instance_module
  38. 1 def define descendant
  39. 42 define_new
  40. 42 define_initialize descendant
  41. 42 define_readers
  42. end
  43. 1 def define_new
  44. 42 class_module.module_exec container, dependencies.to_h do |container, collection|
  45. 42 define_method :new do |*positionals, **keywords, &block|
  46. 113 else: 4 then: 68 collection.each { |name, id| keywords[name] = container[id] unless keywords.key? name }
  47. 41 super(*positionals, **keywords, &block)
  48. end
  49. end
  50. end
  51. 1 def define_initialize descendant
  52. 42 super_parameters = Marameters.of(descendant, :initialize).map do |instance|
  53. 45 else: 3 then: 42 break instance unless instance.only_bare_splats?
  54. end
  55. 42 variablizer = self.class.method :define_instance_variables
  56. 42 then: 3 if super_parameters.positionals? || super_parameters.only_single_splats?
  57. 3 define_initialize_with_positionals super_parameters, variablizer
  58. else: 39 else
  59. 39 define_initialize_with_keywords super_parameters, variablizer
  60. end
  61. end
  62. 1 def define_initialize_with_positionals super_parameters, variablizer
  63. 3 instance_module.module_exec dependencies.keys, variablizer do |keys, definer|
  64. 3 define_method :initialize do |*positionals, **keywords, &block|
  65. 3 definer.call self, keys, keywords
  66. 3 then: 1 if super_parameters.only_single_splats?
  67. 1 super(*positionals, **keywords, &block)
  68. else: 2 else
  69. 2 super(*positionals, **super_parameters.keywords_for(*keys, **keywords), &block)
  70. end
  71. end
  72. end
  73. end
  74. 1 def define_initialize_with_keywords super_parameters, variablizer
  75. 39 instance_module.module_exec dependencies.keys, variablizer do |keys, definer|
  76. 39 define_method :initialize do |**keywords, &block|
  77. 38 definer.call self, keys, keywords
  78. 38 super(**super_parameters.keywords_for(*keys, **keywords), &block)
  79. end
  80. end
  81. end
  82. 1 def define_readers
  83. 116 methods = dependencies.keys.map { |key| ":#{key}" }
  84. 42 then: 41 else: 1 computed_scope = METHOD_SCOPES.include?(scope) ? scope : :private
  85. 42 instance_module.module_eval <<-READERS, __FILE__, __LINE__ + 1
  86. 1 attr_reader :infused_keys
  87. 1 #{computed_scope} attr_reader #{methods.join ", "}
  88. READERS
  89. end
  90. end
  91. end

lib/infusible/dependency_map.rb

100.0% lines covered

100.0% branches covered

22 relevant lines. 22 lines covered and 0 lines missed.
6 total branches, 6 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Infusible
  3. # Sanitizes and resolves dependencies for use.
  4. 1 class DependencyMap
  5. 1 PATTERNS = {key: /([a-z_][a-zA-Z_0-9]*)$/, valid: /^[\w.]+$/}.freeze
  6. 1 attr_reader :keys
  7. 1 def initialize *configuration, patterns: PATTERNS
  8. 75 @patterns = patterns
  9. 75 @collection = {}
  10. 75 then: 8 else: 67 aliases = configuration.last.is_a?(Hash) ? configuration.pop : {}
  11. 208 configuration.each { |identifier| add to_key(identifier), identifier }
  12. 79 aliases.each { |key, identifier| add key, identifier }
  13. 68 @keys = collection.keys.freeze
  14. end
  15. 1 def to_h = collection
  16. 1 private
  17. 1 attr_reader :patterns, :collection
  18. 1 def to_key identifier
  19. 133 key = identifier[patterns.fetch(:key)]
  20. 133 then: 131 else: 2 return key if key && key.match?(patterns.fetch(:valid))
  21. 2 fail Errors::InvalidDependency.new(identifier:)
  22. end
  23. 1 def add key, identifier
  24. 140 key = key.to_sym
  25. 140 else: 5 then: 135 return collection[key] = identifier unless collection.key? key
  26. 5 fail Errors::DuplicateDependency.new(key:, identifier:)
  27. end
  28. end
  29. end

lib/infusible/errors/duplicate_dependency.rb

100.0% lines covered

100.0% branches covered

5 relevant lines. 5 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Infusible
  3. 1 module Errors
  4. # Prevents duplicate dependencies from being injected.
  5. 1 class DuplicateDependency < StandardError
  6. 1 def initialize key:, identifier:
  7. 6 super "Remove #{identifier.inspect} since it's a duplicate of #{key.inspect}."
  8. end
  9. end
  10. end
  11. end

lib/infusible/errors/invalid_dependency.rb

100.0% lines covered

100.0% branches covered

5 relevant lines. 5 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Infusible
  3. 1 module Errors
  4. # Prevents improperly named dependencies from being injected.
  5. 1 class InvalidDependency < StandardError
  6. 1 def initialize identifier:
  7. 3 super "Cannot use #{identifier.inspect} as an identifier."
  8. end
  9. end
  10. end
  11. end