dry-system 0.19.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 +75 -0
- data/README.md +5 -4
- data/dry-system.gemspec +16 -15
- data/lib/dry/system/auto_registrar.rb +3 -15
- 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 +74 -42
- 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 +39 -42
- data/lib/dry/system/errors.rb +11 -20
- data/lib/dry/system/identifier.rb +57 -79
- data/lib/dry/system/indirect_component.rb +65 -0
- data/lib/dry/system/loader.rb +3 -4
- data/lib/dry/system/manual_registrar.rb +4 -8
- data/lib/dry/system/plugins/bootsnap.rb +1 -1
- data/lib/dry/system/plugins/dependency_graph.rb +4 -4
- data/lib/dry/system/plugins/env.rb +1 -1
- data/lib/dry/system/plugins/logging.rb +7 -6
- 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/stubs.rb +1 -1
- data/lib/dry/system/version.rb +1 -1
- data/lib/dry/system.rb +5 -5
- metadata +14 -11
    
        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,80 @@ | |
| 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 | 
            +
             | 
| 35 | 
            +
            ## 0.20.0 2021-09-12
         | 
| 36 | 
            +
             | 
| 37 | 
            +
             | 
| 38 | 
            +
            ### Fixed
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            - Fixed dependency graph plugin to work with internal changes introduced in 0.19.0 (@wuarmin in #173)
         | 
| 41 | 
            +
            - Fixed behavior of `Dry::System::Identifier#start_with?` for components identified by a single segment, or if all matching segments are provided (@wuarmin in #177)
         | 
| 42 | 
            +
            - Fixed compatibility of `finalize!` signature provided in `Container::Stubs` (@mpokrywka in #178)
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            ### Changed
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            - [internal] Upgraded to new `setting` API provided in dry-configurable 0.13.0 (@timriley in #179)
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            [Compare v0.19.2...v0.20.0](https://github.com/dry-rb/dry-system/compare/v0.19.2...v0.20.0)
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            ## 0.19.2 2021-08-30
         | 
| 51 | 
            +
             | 
| 52 | 
            +
             | 
| 53 | 
            +
            ### Changed
         | 
| 54 | 
            +
             | 
| 55 | 
            +
            - [internal] Improved compatibility with upcoming dry-configurable 0.13.0 release (@timriley in #186)
         | 
| 56 | 
            +
             | 
| 57 | 
            +
            [Compare v0.18.2...v0.19.2](https://github.com/dry-rb/dry-system/compare/v0.18.2...v0.19.2)
         | 
| 58 | 
            +
             | 
| 59 | 
            +
            ## 0.18.2 2021-08-30
         | 
| 60 | 
            +
             | 
| 61 | 
            +
             | 
| 62 | 
            +
            ### Changed
         | 
| 63 | 
            +
             | 
| 64 | 
            +
            - [internal] Improved compatibility with upcoming dry-configurable 0.13.0 release (@timriley in #187)
         | 
| 65 | 
            +
             | 
| 66 | 
            +
            [Compare v0.19.1...v0.18.2](https://github.com/dry-rb/dry-system/compare/v0.19.1...v0.18.2)
         | 
| 67 | 
            +
             | 
| 68 | 
            +
            ## 0.19.1 2021-07-11
         | 
| 69 | 
            +
             | 
| 70 | 
            +
             | 
| 71 | 
            +
            ### Fixed
         | 
| 72 | 
            +
             | 
| 73 | 
            +
            - Check for registered components (@timriley in #175)
         | 
| 74 | 
            +
             | 
| 75 | 
            +
             | 
| 76 | 
            +
            [Compare v0.19.0...v0.19.1](https://github.com/dry-rb/dry-system/compare/v0.19.0...v0.19.1)
         | 
| 77 | 
            +
             | 
| 3 78 | 
             
            ## 0.19.0 2021-04-22
         | 
| 4 79 |  | 
| 5 80 | 
             
            This release marks a huge step forward for dry-system, bringing support for Zeitwerk and other autoloaders, plus clearer configuration and improved consistency around component resolution for both finalized and lazy loading containers. [Read the announcement post](https://dry-rb.org/news/2021/04/22/dry-system-0-19-released-with-zeitwerk-support-and-more-leading-the-way-for-hanami-2-0/) for a high-level tour of the new features.
         | 
    
        data/README.md
    CHANGED
    
    | @@ -1,3 +1,4 @@ | |
| 1 | 
            +
            <!--- this file is synced from dry-rb/template-gem project -->
         | 
| 1 2 | 
             
            [gem]: https://rubygems.org/gems/dry-system
         | 
| 2 3 | 
             
            [actions]: https://github.com/dry-rb/dry-system/actions
         | 
| 3 4 | 
             
            [codacy]: https://www.codacy.com/gh/dry-rb/dry-system
         | 
| @@ -7,22 +8,22 @@ | |
| 7 8 | 
             
            # dry-system [][chat]
         | 
| 8 9 |  | 
| 9 10 | 
             
            [][gem]
         | 
| 10 | 
            -
            [][actions]
         | 
| 11 12 | 
             
            [][codacy]
         | 
| 12 13 | 
             
            [][codacy]
         | 
| 13 14 | 
             
            [][inchpages]
         | 
| 14 15 |  | 
| 15 16 | 
             
            ## Links
         | 
| 16 17 |  | 
| 17 | 
            -
            * [User documentation]( | 
| 18 | 
            +
            * [User documentation](https://dry-rb.org/gems/dry-system)
         | 
| 18 19 | 
             
            * [API documentation](http://rubydoc.info/gems/dry-system)
         | 
| 19 20 |  | 
| 20 21 | 
             
            ## Supported Ruby versions
         | 
| 21 22 |  | 
| 22 23 | 
             
            This library officially supports the following Ruby versions:
         | 
| 23 24 |  | 
| 24 | 
            -
            * MRI  | 
| 25 | 
            -
            * jruby  | 
| 25 | 
            +
            * MRI `>= 2.6.0`
         | 
| 26 | 
            +
            * jruby `>= 9.3`
         | 
| 26 27 |  | 
| 27 28 | 
             
            ## License
         | 
| 28 29 |  | 
    
        data/dry-system.gemspec
    CHANGED
    
    | @@ -1,37 +1,38 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 | 
            -
            # this file is managed by dry-rb/devtools project
         | 
| 3 2 |  | 
| 4 | 
            -
             | 
| 3 | 
            +
            # this file is synced from dry-rb/template-gem project
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            lib = File.expand_path("lib", __dir__)
         | 
| 5 6 | 
             
            $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
         | 
| 6 | 
            -
            require  | 
| 7 | 
            +
            require "dry/system/version"
         | 
| 7 8 |  | 
| 8 9 | 
             
            Gem::Specification.new do |spec|
         | 
| 9 | 
            -
              spec.name          =  | 
| 10 | 
            +
              spec.name          = "dry-system"
         | 
| 10 11 | 
             
              spec.authors       = ["Piotr Solnica"]
         | 
| 11 12 | 
             
              spec.email         = ["piotr.solnica@gmail.com"]
         | 
| 12 | 
            -
              spec.license       =  | 
| 13 | 
            +
              spec.license       = "MIT"
         | 
| 13 14 | 
             
              spec.version       = Dry::System::VERSION.dup
         | 
| 14 15 |  | 
| 15 16 | 
             
              spec.summary       = "Organize your code into reusable components"
         | 
| 16 17 | 
             
              spec.description   = spec.summary
         | 
| 17 | 
            -
              spec.homepage      =  | 
| 18 | 
            +
              spec.homepage      = "https://dry-rb.org/gems/dry-system"
         | 
| 18 19 | 
             
              spec.files         = Dir["CHANGELOG.md", "LICENSE", "README.md", "dry-system.gemspec", "lib/**/*"]
         | 
| 19 | 
            -
              spec.bindir        =  | 
| 20 | 
            +
              spec.bindir        = "bin"
         | 
| 20 21 | 
             
              spec.executables   = []
         | 
| 21 | 
            -
              spec.require_paths = [ | 
| 22 | 
            +
              spec.require_paths = ["lib"]
         | 
| 22 23 |  | 
| 23 | 
            -
              spec.metadata[ | 
| 24 | 
            -
              spec.metadata[ | 
| 25 | 
            -
              spec.metadata[ | 
| 26 | 
            -
              spec.metadata[ | 
| 24 | 
            +
              spec.metadata["allowed_push_host"] = "https://rubygems.org"
         | 
| 25 | 
            +
              spec.metadata["changelog_uri"]     = "https://github.com/dry-rb/dry-system/blob/master/CHANGELOG.md"
         | 
| 26 | 
            +
              spec.metadata["source_code_uri"]   = "https://github.com/dry-rb/dry-system"
         | 
| 27 | 
            +
              spec.metadata["bug_tracker_uri"]   = "https://github.com/dry-rb/dry-system/issues"
         | 
| 27 28 |  | 
| 28 | 
            -
              spec.required_ruby_version = ">= 2. | 
| 29 | 
            +
              spec.required_ruby_version = ">= 2.6.0"
         | 
| 29 30 |  | 
| 30 31 | 
             
              # to update dependencies edit project.yml
         | 
| 31 32 | 
             
              spec.add_runtime_dependency "concurrent-ruby", "~> 1.0"
         | 
| 32 33 | 
             
              spec.add_runtime_dependency "dry-auto_inject", ">= 0.4.0"
         | 
| 33 | 
            -
              spec.add_runtime_dependency "dry-configurable", "~> 0. | 
| 34 | 
            -
              spec.add_runtime_dependency "dry-container", "~> 0. | 
| 34 | 
            +
              spec.add_runtime_dependency "dry-configurable", "~> 0.13", ">= 0.13.0"
         | 
| 35 | 
            +
              spec.add_runtime_dependency "dry-container", "~> 0.9", ">= 0.9.0"
         | 
| 35 36 | 
             
              spec.add_runtime_dependency "dry-core", "~> 0.5", ">= 0.5"
         | 
| 36 37 | 
             
              spec.add_runtime_dependency "dry-inflector", "~> 0.1", ">= 0.1.2"
         | 
| 37 38 | 
             
              spec.add_runtime_dependency "dry-struct", "~> 1.0"
         | 
| @@ -29,29 +29,17 @@ 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 | 
            -
                      container.register(component. | 
| 35 | 
            +
                      container.register(component.key, memoize: component.memoize?) { component.instance }
         | 
| 36 36 | 
             
                    end
         | 
| 37 37 | 
             
                  end
         | 
| 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 | 
            -
                    !container.registered?(component) && component.auto_register?
         | 
| 42 | 
            +
                    !container.registered?(component.key) && component.auto_register?
         | 
| 55 43 | 
             
                  end
         | 
| 56 44 | 
             
                end
         | 
| 57 45 | 
             
              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)
         |