dry-system 0.20.0 → 0.21.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
- data/CHANGELOG.md +32 -0
- data/README.md +1 -1
- data/lib/dry/system/auto_registrar.rb +1 -13
- data/lib/dry/system/booter/component_registry.rb +3 -3
- data/lib/dry/system/booter.rb +4 -4
- data/lib/dry/system/component.rb +103 -45
- data/lib/dry/system/component_dir.rb +112 -45
- data/lib/dry/system/components/bootable.rb +10 -19
- data/lib/dry/system/config/component_dir.rb +70 -38
- data/lib/dry/system/config/component_dirs.rb +28 -41
- data/lib/dry/system/config/namespace.rb +71 -0
- data/lib/dry/system/config/namespaces.rb +121 -0
- data/lib/dry/system/constants.rb +1 -1
- data/lib/dry/system/container.rb +25 -29
- data/lib/dry/system/errors.rb +11 -20
- data/lib/dry/system/identifier.rb +57 -80
- data/lib/dry/system/indirect_component.rb +65 -0
- data/lib/dry/system/loader.rb +2 -3
- data/lib/dry/system/manual_registrar.rb +4 -8
- data/lib/dry/system/plugins.rb +1 -3
- data/lib/dry/system/provider.rb +6 -6
- data/lib/dry/system/provider_registry.rb +4 -4
- data/lib/dry/system/settings.rb +1 -4
- data/lib/dry/system/version.rb +1 -1
- data/lib/dry/system.rb +5 -5
- metadata +5 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 1c0977524fa33277150e8611a851c557461c14357e1a5d9c1f1929c283f3c9b3
         | 
| 4 | 
            +
              data.tar.gz: 12de5eaa25ede9f5f959cd77e2af8f1d3b4b287aaebea1554c2915433743c290
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 6976cb749c98ed536c5fb3e6ca67674aee2707b40cd5d196b6b82472edadfa10877bebac77631b0ebcc5fabe008514ed60e7d9be17ccd1343e8967836f319a11
         | 
| 7 | 
            +
              data.tar.gz: fd894f982bf214dac3ccfa0de1d4fd7f95beeea1f91234cd2868dfab4687b76ea831c153b429d8ba9bd9f04bcb46dd2815f87a7829e6b347b2d8992cf0ebd478
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,5 +1,37 @@ | |
| 1 1 | 
             
            <!--- DO NOT EDIT THIS FILE - IT'S AUTOMATICALLY GENERATED VIA DEVTOOLS --->
         | 
| 2 2 |  | 
| 3 | 
            +
            ## 0.21.0 2021-11-01
         | 
| 4 | 
            +
             | 
| 5 | 
            +
             | 
| 6 | 
            +
            ### Added
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            - Added **component dir namespaces** as a way to specify multiple, ordered, independent namespace rules within a given component dir. This replaces and expands upon the namespace support we previously provided via the singular `default_namespace` component dir setting (@timriley in #181)
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            ### Changed
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            - `default_namespace` setting on component dirs has been deprecated. Add a component dir namespace instead, e.g. instead of:
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              ```ruby
         | 
| 15 | 
            +
              # Inside Dry::System::Container.configure
         | 
| 16 | 
            +
              config.component_dirs.add "lib" do |dir|
         | 
| 17 | 
            +
                dir.default_namespace = "admin"
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
              ```
         | 
| 20 | 
            +
             | 
| 21 | 
            +
              Add this:
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              ```ruby
         | 
| 24 | 
            +
              config.component_dirs.add "lib" do |dir|
         | 
| 25 | 
            +
                dir.namespaces.add "admin", key: nil
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
              ```
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              (@timriley in #181)
         | 
| 30 | 
            +
            - `Dry::System::Component#path` has been removed and replaced by `Component#require_path` and `Component#const_path` (@timriley in #181)
         | 
| 31 | 
            +
            - Unused `Dry::System::FileNotFoundError` and `Dry::System::InvalidComponentIdentifierTypeError` errors have been removed (@timriley in #194)
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            [Compare v0.20.0...v0.21.0](https://github.com/dry-rb/dry-system/compare/v0.20.0...v0.21.0)
         | 
| 34 | 
            +
             | 
| 3 35 | 
             
            ## 0.20.0 2021-09-12
         | 
| 4 36 |  | 
| 5 37 |  | 
    
        data/README.md
    CHANGED
    
    
| @@ -29,7 +29,7 @@ module Dry | |
| 29 29 |  | 
| 30 30 | 
             
                  # @api private
         | 
| 31 31 | 
             
                  def call(component_dir)
         | 
| 32 | 
            -
                     | 
| 32 | 
            +
                    component_dir.each_component do |component|
         | 
| 33 33 | 
             
                      next unless register_component?(component)
         | 
| 34 34 |  | 
| 35 35 | 
             
                      container.register(component.key, memoize: component.memoize?) { component.instance }
         | 
| @@ -38,18 +38,6 @@ module Dry | |
| 38 38 |  | 
| 39 39 | 
             
                  private
         | 
| 40 40 |  | 
| 41 | 
            -
                  def components(component_dir)
         | 
| 42 | 
            -
                    files(component_dir.full_path).map { |file_path|
         | 
| 43 | 
            -
                      component_dir.component_for_path(file_path)
         | 
| 44 | 
            -
                    }
         | 
| 45 | 
            -
                  end
         | 
| 46 | 
            -
             | 
| 47 | 
            -
                  def files(dir)
         | 
| 48 | 
            -
                    raise ComponentDirNotFoundError, dir unless Dir.exist?(dir)
         | 
| 49 | 
            -
             | 
| 50 | 
            -
                    Dir["#{dir}/**/#{RB_GLOB}"].sort
         | 
| 51 | 
            -
                  end
         | 
| 52 | 
            -
             | 
| 53 41 | 
             
                  def register_component?(component)
         | 
| 54 42 | 
             
                    !container.registered?(component.key) && component.auto_register?
         | 
| 55 43 | 
             
                  end
         | 
| @@ -21,13 +21,13 @@ module Dry | |
| 21 21 | 
             
                    end
         | 
| 22 22 |  | 
| 23 23 | 
             
                    def exists?(name)
         | 
| 24 | 
            -
                      components.any? { |component| component. | 
| 24 | 
            +
                      components.any? { |component| component.name == name }
         | 
| 25 25 | 
             
                    end
         | 
| 26 26 |  | 
| 27 27 | 
             
                    def [](name)
         | 
| 28 | 
            -
                      component = components.detect { | | 
| 28 | 
            +
                      component = components.detect { |c| c.name == name }
         | 
| 29 29 |  | 
| 30 | 
            -
                      component || raise( | 
| 30 | 
            +
                      component || raise(InvalidComponentNameError, name)
         | 
| 31 31 | 
             
                    end
         | 
| 32 32 | 
             
                  end
         | 
| 33 33 | 
             
                end
         | 
    
        data/lib/dry/system/booter.rb
    CHANGED
    
    | @@ -179,15 +179,15 @@ module Dry | |
| 179 179 | 
             
                  end
         | 
| 180 180 |  | 
| 181 181 | 
             
                  def load_component(path)
         | 
| 182 | 
            -
                     | 
| 182 | 
            +
                    name = Pathname(path).basename(RB_EXT).to_s.to_sym
         | 
| 183 183 |  | 
| 184 | 
            -
                    Kernel.require path unless components.exists?( | 
| 184 | 
            +
                    Kernel.require path unless components.exists?(name)
         | 
| 185 185 |  | 
| 186 186 | 
             
                    self
         | 
| 187 187 | 
             
                  end
         | 
| 188 188 |  | 
| 189 | 
            -
                  def require_boot_file( | 
| 190 | 
            -
                    boot_file = find_boot_file( | 
| 189 | 
            +
                  def require_boot_file(name)
         | 
| 190 | 
            +
                    boot_file = find_boot_file(name)
         | 
| 191 191 |  | 
| 192 192 | 
             
                    Kernel.require boot_file if boot_file
         | 
| 193 193 | 
             
                  end
         | 
    
        data/lib/dry/system/component.rb
    CHANGED
    
    | @@ -17,7 +17,7 @@ module Dry | |
| 17 17 | 
             
                #
         | 
| 18 18 | 
             
                # @api public
         | 
| 19 19 | 
             
                class Component
         | 
| 20 | 
            -
                  include Dry::Equalizer(:identifier, : | 
| 20 | 
            +
                  include Dry::Equalizer(:identifier, :namespace, :options)
         | 
| 21 21 |  | 
| 22 22 | 
             
                  DEFAULT_OPTIONS = {
         | 
| 23 23 | 
             
                    separator: DEFAULT_SEPARATOR,
         | 
| @@ -26,43 +26,34 @@ module Dry | |
| 26 26 | 
             
                  }.freeze
         | 
| 27 27 |  | 
| 28 28 | 
             
                  # @!attribute [r] identifier
         | 
| 29 | 
            -
                  #   @return [String] component's unique identifier
         | 
| 29 | 
            +
                  #   @return [String] the component's unique identifier
         | 
| 30 30 | 
             
                  attr_reader :identifier
         | 
| 31 31 |  | 
| 32 | 
            -
                  # @!attribute [r]  | 
| 33 | 
            -
                  #   @return [ | 
| 34 | 
            -
                  attr_reader : | 
| 32 | 
            +
                  # @!attribute [r] namespace
         | 
| 33 | 
            +
                  #   @return [Dry::System::Config::Namespace] the component's namespace
         | 
| 34 | 
            +
                  attr_reader :namespace
         | 
| 35 35 |  | 
| 36 36 | 
             
                  # @!attribute [r] options
         | 
| 37 | 
            -
                  #   @return [Hash] component's options
         | 
| 37 | 
            +
                  #   @return [Hash] the component's options
         | 
| 38 38 | 
             
                  attr_reader :options
         | 
| 39 39 |  | 
| 40 40 | 
             
                  # @api private
         | 
| 41 | 
            -
                  def  | 
| 42 | 
            -
                     | 
| 43 | 
            -
             | 
| 44 | 
            -
                     | 
| 45 | 
            -
                    separator = options.delete(:separator)
         | 
| 46 | 
            -
             | 
| 47 | 
            -
                    identifier =
         | 
| 48 | 
            -
                      if identifier.is_a?(Identifier)
         | 
| 49 | 
            -
                        identifier
         | 
| 50 | 
            -
                      else
         | 
| 51 | 
            -
                        Identifier.new(
         | 
| 52 | 
            -
                          identifier,
         | 
| 53 | 
            -
                          namespace: namespace,
         | 
| 54 | 
            -
                          separator: separator
         | 
| 55 | 
            -
                        )
         | 
| 56 | 
            -
                      end
         | 
| 57 | 
            -
             | 
| 58 | 
            -
                    super(identifier, **options)
         | 
| 41 | 
            +
                  def initialize(identifier, namespace:, **options)
         | 
| 42 | 
            +
                    @identifier = identifier
         | 
| 43 | 
            +
                    @namespace = namespace
         | 
| 44 | 
            +
                    @options = DEFAULT_OPTIONS.merge(options)
         | 
| 59 45 | 
             
                  end
         | 
| 60 46 |  | 
| 47 | 
            +
                  # Returns true, indicating that the component is directly loadable from the files
         | 
| 48 | 
            +
                  # managed by the container
         | 
| 49 | 
            +
                  #
         | 
| 50 | 
            +
                  # This is the inverse of {IndirectComponent#loadable?}
         | 
| 51 | 
            +
                  #
         | 
| 52 | 
            +
                  # @return [TrueClass]
         | 
| 53 | 
            +
                  #
         | 
| 61 54 | 
             
                  # @api private
         | 
| 62 | 
            -
                  def  | 
| 63 | 
            -
                     | 
| 64 | 
            -
                    @file_path = file_path
         | 
| 65 | 
            -
                    @options = options
         | 
| 55 | 
            +
                  def loadable?
         | 
| 56 | 
            +
                    true
         | 
| 66 57 | 
             
                  end
         | 
| 67 58 |  | 
| 68 59 | 
             
                  # Returns the component's instance
         | 
| @@ -74,39 +65,95 @@ module Dry | |
| 74 65 | 
             
                  end
         | 
| 75 66 | 
             
                  ruby2_keywords(:instance) if respond_to?(:ruby2_keywords, true)
         | 
| 76 67 |  | 
| 77 | 
            -
                  #  | 
| 78 | 
            -
                   | 
| 79 | 
            -
             | 
| 80 | 
            -
                   | 
| 81 | 
            -
             | 
| 68 | 
            +
                  # Returns the component's unique key
         | 
| 69 | 
            +
                  #
         | 
| 70 | 
            +
                  # @return [String] the key
         | 
| 71 | 
            +
                  #
         | 
| 72 | 
            +
                  # @see Identifier#key
         | 
| 73 | 
            +
                  #
         | 
| 74 | 
            +
                  # @api public
         | 
| 82 75 | 
             
                  def key
         | 
| 83 | 
            -
                    identifier. | 
| 84 | 
            -
                  end
         | 
| 85 | 
            -
             | 
| 86 | 
            -
                  def path
         | 
| 87 | 
            -
                    identifier.path
         | 
| 76 | 
            +
                    identifier.key
         | 
| 88 77 | 
             
                  end
         | 
| 89 78 |  | 
| 79 | 
            +
                  # Returns the root namespace segment of the component's key, as a symbol
         | 
| 80 | 
            +
                  #
         | 
| 81 | 
            +
                  # @see Identifier#root_key
         | 
| 82 | 
            +
                  #
         | 
| 83 | 
            +
                  # @return [Symbol] the root key
         | 
| 84 | 
            +
                  #
         | 
| 85 | 
            +
                  # @api public
         | 
| 90 86 | 
             
                  def root_key
         | 
| 91 87 | 
             
                    identifier.root_key
         | 
| 92 88 | 
             
                  end
         | 
| 93 89 |  | 
| 94 | 
            -
                  # Returns  | 
| 90 | 
            +
                  # Returns a path-delimited representation of the compnent, appropriate for passing
         | 
| 91 | 
            +
                  # to `Kernel#require` to require its source file
         | 
| 95 92 | 
             
                  #
         | 
| 96 | 
            -
                  #  | 
| 97 | 
            -
                  # | 
| 98 | 
            -
                   | 
| 99 | 
            -
             | 
| 93 | 
            +
                  # The path takes into account the rules of the namespace used to load the component.
         | 
| 94 | 
            +
                  #
         | 
| 95 | 
            +
                  # @example Component from a root namespace
         | 
| 96 | 
            +
                  #   component.key # => "articles.create"
         | 
| 97 | 
            +
                  #   component.require_path # => "articles/create"
         | 
| 98 | 
            +
                  #
         | 
| 99 | 
            +
                  # @example Component from an "admin/" path namespace (with `key: nil`)
         | 
| 100 | 
            +
                  #   component.key # => "articles.create"
         | 
| 101 | 
            +
                  #   component.require_path # => "admin/articles/create"
         | 
| 102 | 
            +
                  #
         | 
| 103 | 
            +
                  # @see Config::Namespaces#add
         | 
| 104 | 
            +
                  # @see Config::Namespace
         | 
| 105 | 
            +
                  #
         | 
| 106 | 
            +
                  # @return [String] the require path
         | 
| 107 | 
            +
                  #
         | 
| 108 | 
            +
                  # @api public
         | 
| 109 | 
            +
                  def require_path
         | 
| 110 | 
            +
                    if namespace.path
         | 
| 111 | 
            +
                      "#{namespace.path}#{PATH_SEPARATOR}#{path_in_namespace}"
         | 
| 112 | 
            +
                    else
         | 
| 113 | 
            +
                      path_in_namespace
         | 
| 114 | 
            +
                    end
         | 
| 115 | 
            +
                  end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                  # Returns an "underscored", path-delimited representation of the component,
         | 
| 118 | 
            +
                  # appropriate for passing to the inflector for constantizing
         | 
| 119 | 
            +
                  #
         | 
| 120 | 
            +
                  # The const path takes into account the rules of the namespace used to load the
         | 
| 121 | 
            +
                  # component.
         | 
| 122 | 
            +
                  #
         | 
| 123 | 
            +
                  # @example Component from a namespace with `const: nil`
         | 
| 124 | 
            +
                  #   component.key # => "articles.create_article"
         | 
| 125 | 
            +
                  #   component.const_path # => "articles/create_article"
         | 
| 126 | 
            +
                  #   component.inflector.constantize(component.const_path) # => Articles::CreateArticle
         | 
| 127 | 
            +
                  #
         | 
| 128 | 
            +
                  # @example Component from a namespace with `const: "admin"`
         | 
| 129 | 
            +
                  #   component.key # => "articles.create_article"
         | 
| 130 | 
            +
                  #   component.const_path # => "admin/articles/create_article"
         | 
| 131 | 
            +
                  #   component.inflector.constantize(component.const_path) # => Admin::Articles::CreateArticle
         | 
| 132 | 
            +
                  #
         | 
| 133 | 
            +
                  # @see Config::Namespaces#add
         | 
| 134 | 
            +
                  # @see Config::Namespace
         | 
| 135 | 
            +
                  #
         | 
| 136 | 
            +
                  # @return [String] the const path
         | 
| 137 | 
            +
                  #
         | 
| 138 | 
            +
                  # @api public
         | 
| 139 | 
            +
                  def const_path
         | 
| 140 | 
            +
                    namespace_const_path = namespace.const&.gsub(identifier.separator, PATH_SEPARATOR)
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                    if namespace_const_path
         | 
| 143 | 
            +
                      "#{namespace_const_path}#{PATH_SEPARATOR}#{path_in_namespace}"
         | 
| 144 | 
            +
                    else
         | 
| 145 | 
            +
                      path_in_namespace
         | 
| 146 | 
            +
                    end
         | 
| 100 147 | 
             
                  end
         | 
| 101 148 |  | 
| 102 149 | 
             
                  # @api private
         | 
| 103 150 | 
             
                  def loader
         | 
| 104 | 
            -
                    options | 
| 151 | 
            +
                    options.fetch(:loader)
         | 
| 105 152 | 
             
                  end
         | 
| 106 153 |  | 
| 107 154 | 
             
                  # @api private
         | 
| 108 155 | 
             
                  def inflector
         | 
| 109 | 
            -
                    options | 
| 156 | 
            +
                    options.fetch(:inflector)
         | 
| 110 157 | 
             
                  end
         | 
| 111 158 |  | 
| 112 159 | 
             
                  # @api private
         | 
| @@ -121,6 +168,17 @@ module Dry | |
| 121 168 |  | 
| 122 169 | 
             
                  private
         | 
| 123 170 |  | 
| 171 | 
            +
                  def path_in_namespace
         | 
| 172 | 
            +
                    identifier_in_namespace =
         | 
| 173 | 
            +
                      if namespace.key
         | 
| 174 | 
            +
                        identifier.namespaced(from: namespace.key, to: nil)
         | 
| 175 | 
            +
                      else
         | 
| 176 | 
            +
                        identifier
         | 
| 177 | 
            +
                      end
         | 
| 178 | 
            +
             | 
| 179 | 
            +
                    identifier_in_namespace.key_with_separator(PATH_SEPARATOR)
         | 
| 180 | 
            +
                  end
         | 
| 181 | 
            +
             | 
| 124 182 | 
             
                  def callable_option?(value)
         | 
| 125 183 | 
             
                    if value.respond_to?(:call)
         | 
| 126 184 | 
             
                      !!value.call(self)
         | 
| @@ -1,4 +1,7 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require "pathname"
         | 
| 4 | 
            +
            require "dry/system/constants"
         | 
| 2 5 | 
             
            require_relative "constants"
         | 
| 3 6 | 
             
            require_relative "identifier"
         | 
| 4 7 | 
             
            require_relative "magic_comments_parser"
         | 
| @@ -28,40 +31,101 @@ module Dry | |
| 28 31 | 
             
                    @container = container
         | 
| 29 32 | 
             
                  end
         | 
| 30 33 |  | 
| 31 | 
            -
                  # Returns a component for  | 
| 32 | 
            -
                  #  | 
| 34 | 
            +
                  # Returns a component for the given key if a matching source file is found within
         | 
| 35 | 
            +
                  # the component dir
         | 
| 33 36 | 
             
                  #
         | 
| 34 | 
            -
                  # This  | 
| 35 | 
            -
                  #  | 
| 37 | 
            +
                  # This searches according to the component dir's configured namespaces, in order of
         | 
| 38 | 
            +
                  # definition, with the first match returned as the component.
         | 
| 36 39 | 
             
                  #
         | 
| 37 | 
            -
                  # @param  | 
| 40 | 
            +
                  # @param key [String] the component's key
         | 
| 38 41 | 
             
                  # @return [Dry::System::Component, nil] the component, if found
         | 
| 39 42 | 
             
                  #
         | 
| 40 43 | 
             
                  # @api private
         | 
| 41 | 
            -
                  def  | 
| 42 | 
            -
                     | 
| 43 | 
            -
                      identifier,
         | 
| 44 | 
            -
             | 
| 45 | 
            -
                       | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 49 | 
            -
                       | 
| 44 | 
            +
                  def component_for_key(key)
         | 
| 45 | 
            +
                    namespaces.each do |namespace|
         | 
| 46 | 
            +
                      identifier = Identifier.new(key, separator: container.config.namespace_separator)
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                      next unless identifier.start_with?(namespace.key)
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                      if (file_path = find_component_file(identifier, namespace))
         | 
| 51 | 
            +
                        return build_component(identifier, namespace, file_path)
         | 
| 52 | 
            +
                      end
         | 
| 53 | 
            +
                    end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                    nil
         | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  def each_component
         | 
| 59 | 
            +
                    return enum_for(:each_component) unless block_given?
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                    each_file do |file_path, namespace|
         | 
| 62 | 
            +
                      yield component_for_path(file_path, namespace)
         | 
| 50 63 | 
             
                    end
         | 
| 64 | 
            +
                  end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                  private
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  def namespaces
         | 
| 69 | 
            +
                    config.namespaces.to_a.map { |namespace| normalize_namespace(namespace) }
         | 
| 70 | 
            +
                  end
         | 
| 51 71 |  | 
| 52 | 
            -
             | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 72 | 
            +
                  # Returns an array of "normalized" namespaces, safe for loading components
         | 
| 73 | 
            +
                  #
         | 
| 74 | 
            +
                  # This works around the issue of a namespace being added for a nested path but
         | 
| 75 | 
            +
                  # _without_ specifying a key namespace. In this case, the key namespace will defaut
         | 
| 76 | 
            +
                  # to match the path, meaning it will contain path separators instead of the
         | 
| 77 | 
            +
                  # container's configured `namespace_separator` (due to `Config::Namespaces` not
         | 
| 78 | 
            +
                  # being able to know the configured `namespace_separator`), so we need to replace
         | 
| 79 | 
            +
                  # the path separators with the proper `namespace_separator` here (where we _do_ know
         | 
| 80 | 
            +
                  # what it is).
         | 
| 81 | 
            +
                  def normalize_namespace(namespace)
         | 
| 82 | 
            +
                    if namespace.path&.include?(PATH_SEPARATOR) && namespace.default_key?
         | 
| 83 | 
            +
                      namespace = namespace.class.new(
         | 
| 84 | 
            +
                        path: namespace.path,
         | 
| 85 | 
            +
                        key: namespace.key.gsub(PATH_SEPARATOR, container.config.namespace_separator),
         | 
| 86 | 
            +
                        const: namespace.const
         | 
| 87 | 
            +
                      )
         | 
| 55 88 | 
             
                    end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                    namespace
         | 
| 91 | 
            +
                  end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                  def each_file
         | 
| 94 | 
            +
                    return enum_for(:each_file) unless block_given?
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                    raise ComponentDirNotFoundError, full_path unless Dir.exist?(full_path)
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                    namespaces.each do |namespace|
         | 
| 99 | 
            +
                      files(namespace).each do |file|
         | 
| 100 | 
            +
                        yield file, namespace
         | 
| 101 | 
            +
                      end
         | 
| 102 | 
            +
                    end
         | 
| 103 | 
            +
                  end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                  def files(namespace)
         | 
| 106 | 
            +
                    if namespace.path?
         | 
| 107 | 
            +
                      Dir[File.join(full_path, namespace.path, "**", RB_GLOB)].sort
         | 
| 108 | 
            +
                    else
         | 
| 109 | 
            +
                      non_root_paths = namespaces.to_a.reject(&:root?).map(&:path)
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                      Dir[File.join(full_path, "**", RB_GLOB)].reject { |file_path|
         | 
| 112 | 
            +
                        Pathname(file_path).relative_path_from(full_path).to_s.start_with?(*non_root_paths)
         | 
| 113 | 
            +
                      }.sort
         | 
| 114 | 
            +
                    end
         | 
| 115 | 
            +
                  end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                  # Returns the full path of the component directory
         | 
| 118 | 
            +
                  #
         | 
| 119 | 
            +
                  # @return [Pathname]
         | 
| 120 | 
            +
                  def full_path
         | 
| 121 | 
            +
                    container.root.join(path)
         | 
| 56 122 | 
             
                  end
         | 
| 57 123 |  | 
| 58 124 | 
             
                  # Returns a component for a full path to a Ruby source file within the component dir
         | 
| 59 125 | 
             
                  #
         | 
| 60 126 | 
             
                  # @param path [String] the full path to the file
         | 
| 61 127 | 
             
                  # @return [Dry::System::Component] the component
         | 
| 62 | 
            -
                   | 
| 63 | 
            -
                  # @api private
         | 
| 64 | 
            -
                  def component_for_path(path)
         | 
| 128 | 
            +
                  def component_for_path(path, namespace)
         | 
| 65 129 | 
             
                    separator = container.config.namespace_separator
         | 
| 66 130 |  | 
| 67 131 | 
             
                    key = Pathname(path).relative_path_from(full_path).to_s
         | 
| @@ -70,46 +134,49 @@ module Dry | |
| 70 134 | 
             
                      .join(separator)
         | 
| 71 135 |  | 
| 72 136 | 
             
                    identifier = Identifier.new(key, separator: separator)
         | 
| 137 | 
            +
                      .namespaced(
         | 
| 138 | 
            +
                        from: namespace.path&.gsub(PATH_SEPARATOR, separator),
         | 
| 139 | 
            +
                        to: namespace.key
         | 
| 140 | 
            +
                      )
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                    build_component(identifier, namespace, path)
         | 
| 143 | 
            +
                  end
         | 
| 73 144 |  | 
| 74 | 
            -
             | 
| 75 | 
            -
             | 
| 145 | 
            +
                  def find_component_file(identifier, namespace)
         | 
| 146 | 
            +
                    # To properly find the file within a namespace with a key, we should strip the key
         | 
| 147 | 
            +
                    # from beginning of our given identifier
         | 
| 148 | 
            +
                    if namespace.key
         | 
| 149 | 
            +
                      identifier = identifier.namespaced(from: namespace.key, to: nil)
         | 
| 76 150 | 
             
                    end
         | 
| 77 151 |  | 
| 78 | 
            -
                     | 
| 79 | 
            -
                  end
         | 
| 152 | 
            +
                    file_name = "#{identifier.key_with_separator(PATH_SEPARATOR)}#{RB_EXT}"
         | 
| 80 153 |  | 
| 81 | 
            -
             | 
| 82 | 
            -
             | 
| 83 | 
            -
             | 
| 84 | 
            -
             | 
| 85 | 
            -
             | 
| 86 | 
            -
             | 
| 87 | 
            -
                  end
         | 
| 154 | 
            +
                    component_file =
         | 
| 155 | 
            +
                      if namespace.path?
         | 
| 156 | 
            +
                        full_path.join(namespace.path, file_name)
         | 
| 157 | 
            +
                      else
         | 
| 158 | 
            +
                        full_path.join(file_name)
         | 
| 159 | 
            +
                      end
         | 
| 88 160 |  | 
| 89 | 
            -
             | 
| 90 | 
            -
                  def component_options
         | 
| 91 | 
            -
                    {
         | 
| 92 | 
            -
                      auto_register: auto_register,
         | 
| 93 | 
            -
                      loader: loader,
         | 
| 94 | 
            -
                      memoize: memoize
         | 
| 95 | 
            -
                    }
         | 
| 161 | 
            +
                    component_file if component_file.exist?
         | 
| 96 162 | 
             
                  end
         | 
| 97 163 |  | 
| 98 | 
            -
                   | 
| 99 | 
            -
             | 
| 100 | 
            -
                  def build_component(identifier, file_path)
         | 
| 164 | 
            +
                  def build_component(identifier, namespace, file_path)
         | 
| 101 165 | 
             
                    options = {
         | 
| 102 166 | 
             
                      inflector: container.config.inflector,
         | 
| 103 167 | 
             
                      **component_options,
         | 
| 104 168 | 
             
                      **MagicCommentsParser.(file_path)
         | 
| 105 169 | 
             
                    }
         | 
| 106 170 |  | 
| 107 | 
            -
                    Component.new(identifier,  | 
| 171 | 
            +
                    Component.new(identifier, namespace: namespace, **options)
         | 
| 108 172 | 
             
                  end
         | 
| 109 173 |  | 
| 110 | 
            -
                  def  | 
| 111 | 
            -
                     | 
| 112 | 
            -
             | 
| 174 | 
            +
                  def component_options
         | 
| 175 | 
            +
                    {
         | 
| 176 | 
            +
                      auto_register: auto_register,
         | 
| 177 | 
            +
                      loader: loader,
         | 
| 178 | 
            +
                      memoize: memoize
         | 
| 179 | 
            +
                    }
         | 
| 113 180 | 
             
                  end
         | 
| 114 181 |  | 
| 115 182 | 
             
                  def method_missing(name, *args, &block)
         | 
| @@ -47,9 +47,9 @@ module Dry | |
| 47 47 | 
             
                  class Bootable
         | 
| 48 48 | 
             
                    DEFAULT_FINALIZE = proc {}
         | 
| 49 49 |  | 
| 50 | 
            -
                    # @!attribute [r]  | 
| 51 | 
            -
                    #   @return [Symbol] component's unique  | 
| 52 | 
            -
                    attr_reader : | 
| 50 | 
            +
                    # @!attribute [r] key
         | 
| 51 | 
            +
                    #   @return [Symbol] component's unique name
         | 
| 52 | 
            +
                    attr_reader :name
         | 
| 53 53 |  | 
| 54 54 | 
             
                    # @!attribute [r] options
         | 
| 55 55 | 
             
                    #   @return [Hash] component's options
         | 
| @@ -66,10 +66,10 @@ module Dry | |
| 66 66 | 
             
                    TRIGGER_MAP = Hash.new { |h, k| h[k] = [] }.freeze
         | 
| 67 67 |  | 
| 68 68 | 
             
                    # @api private
         | 
| 69 | 
            -
                    def initialize( | 
| 69 | 
            +
                    def initialize(name, options = {}, &block)
         | 
| 70 70 | 
             
                      @config = nil
         | 
| 71 71 | 
             
                      @config_block = nil
         | 
| 72 | 
            -
                      @ | 
| 72 | 
            +
                      @name = name
         | 
| 73 73 | 
             
                      @triggers = {before: TRIGGER_MAP.dup, after: TRIGGER_MAP.dup}
         | 
| 74 74 | 
             
                      @options = block ? options.merge(block: block) : options
         | 
| 75 75 | 
             
                      @namespace = options[:namespace]
         | 
| @@ -149,7 +149,7 @@ module Dry | |
| 149 149 | 
             
                      if block
         | 
| 150 150 | 
             
                        @settings_block = block
         | 
| 151 151 | 
             
                      elsif @settings_block
         | 
| 152 | 
            -
                        @settings = Settings::DSL.new( | 
| 152 | 
            +
                        @settings = Settings::DSL.new(&@settings_block).call
         | 
| 153 153 | 
             
                      else
         | 
| 154 154 | 
             
                        @settings
         | 
| 155 155 | 
             
                      end
         | 
| @@ -211,8 +211,8 @@ module Dry | |
| 211 211 | 
             
                    # @return [Dry::Struct]
         | 
| 212 212 | 
             
                    #
         | 
| 213 213 | 
             
                    # @api private
         | 
| 214 | 
            -
                    def new( | 
| 215 | 
            -
                      self.class.new( | 
| 214 | 
            +
                    def new(name, new_options = EMPTY_HASH)
         | 
| 215 | 
            +
                      self.class.new(name, options.merge(new_options))
         | 
| 216 216 | 
             
                    end
         | 
| 217 217 |  | 
| 218 218 | 
             
                    # Return a new instance with updated options
         | 
| @@ -221,16 +221,7 @@ module Dry | |
| 221 221 | 
             
                    #
         | 
| 222 222 | 
             
                    # @api private
         | 
| 223 223 | 
             
                    def with(new_options)
         | 
| 224 | 
            -
                      self.class.new( | 
| 225 | 
            -
                    end
         | 
| 226 | 
            -
             | 
| 227 | 
            -
                    # Return true
         | 
| 228 | 
            -
                    #
         | 
| 229 | 
            -
                    # @return [TrueClass]
         | 
| 230 | 
            -
                    #
         | 
| 231 | 
            -
                    # @api private
         | 
| 232 | 
            -
                    def bootable?
         | 
| 233 | 
            -
                      true
         | 
| 224 | 
            +
                      self.class.new(name, options.merge(new_options))
         | 
| 234 225 | 
             
                    end
         | 
| 235 226 |  | 
| 236 227 | 
             
                    private
         | 
| @@ -256,7 +247,7 @@ module Dry | |
| 256 247 | 
             
                      when String, Symbol
         | 
| 257 248 | 
             
                        container.namespace(namespace) { |c| return c }
         | 
| 258 249 | 
             
                      when true
         | 
| 259 | 
            -
                        container.namespace( | 
| 250 | 
            +
                        container.namespace(name) { |c| return c }
         | 
| 260 251 | 
             
                      when nil
         | 
| 261 252 | 
             
                        container
         | 
| 262 253 | 
             
                      else
         |