lib/dry/system/config/component_dirs.rb in dry-system-0.21.0 vs lib/dry/system/config/component_dirs.rb in dry-system-0.22.0
- old
+ new
@@ -1,15 +1,17 @@
# frozen_string_literal: true
-require "concurrent/map"
require "dry/system/constants"
require "dry/system/errors"
require_relative "component_dir"
module Dry
module System
module Config
+ # The configured component dirs for a container
+ #
+ # @api public
class ComponentDirs
# @!group Settings
# @!method auto_register=(value)
#
@@ -61,86 +63,190 @@
#
# Returns the configured default `memoize`
#
# @see memoize=
+ # rubocop:disable Layout/LineLength
+
# @!method namespaces
#
# Returns the default configured namespaces for all added component dirs
#
- # Allows namespaces to added on the returned object via {Namespaces#add}.
+ # Allows namespaces to added on the returned object via {Dry::System::Config::Namespaces#add}.
#
- # @see Namespaces#add
+ # @see Dry::System::Config::Namespaces#add
#
# @return [Namespaces] the namespaces
+ # rubocop:enable Layout/LineLength
+
# @!endgroup
# A ComponentDir for configuring the default values to apply to all added
# component dirs
#
+ # @see #method_missing
# @api private
attr_reader :defaults
+ # Creates a new component dirs
+ #
# @api private
def initialize
- @dirs = Concurrent::Map.new
+ @dirs = {}
@defaults = ComponentDir.new(nil)
end
# @api private
def initialize_copy(source)
- @dirs = source.dirs.dup
+ @dirs = source.dirs.map { |path, dir| [path, dir.dup] }.to_h
@defaults = source.defaults.dup
end
- # Adds and configures a component dir
+ # Returns and optionally yields a previously added component dir
#
- # @param path [String] the path for the component dir, relative to the configured
- # container root
+ # @param path [String] the path for the component dir
+ # @yieldparam dir [ComponentDir] the component dir
#
- # @yieldparam dir [ComponentDir] the component dir to configure
+ # @return [ComponentDir] the component dir
#
- # @return [ComponentDir] the added component dir
+ # @api public
+ def dir(path)
+ dirs[path].tap do |dir|
+ # Defaults can be (re-)applied first, since the dir has already been added
+ apply_defaults_to_dir(dir) if dir
+ yield dir if block_given?
+ end
+ end
+ alias_method :[], :dir
+
+ # @overload add(path)
+ # Adds and configures a component dir for the given path
#
- # @example
- # component_dirs.add "lib" do |dir|
- # dir.default_namespace = "my_app"
- # end
+ # @param path [String] the path for the component dir, relative to the configured
+ # container root
+ # @yieldparam dir [ComponentDir] the component dir to configure
#
- # @see ComponentDir
- def add(path)
+ # @return [ComponentDir] the added component dir
+ #
+ # @example
+ # component_dirs.add "lib" do |dir|
+ # dir.default_namespace = "my_app"
+ # end
+ #
+ # @see ComponentDir
+ # @api public
+ #
+ # @overload add(dir)
+ # Adds a configured component dir
+ #
+ # @param dir [ComponentDir] the configured component dir
+ #
+ # @return [ComponentDir] the added component dir
+ #
+ # @example
+ # dir = Dry::System::ComponentDir.new("lib")
+ # component_dirs.add dir
+ #
+ # @see ComponentDir
+ # @api public
+ def add(path_or_dir)
+ path, dir_to_add = path_and_dir(path_or_dir)
+
raise ComponentDirAlreadyAddedError, path if dirs.key?(path)
- dirs[path] = ComponentDir.new(path).tap do |dir|
- yield dir if block_given?
+ dirs[path] = dir_to_add.tap do |dir|
+ # Defaults must be applied after yielding, since the dir is being newly added,
+ # and must have its configuration fully in place before we can know which
+ # defaults to apply
+ yield dir if path_or_dir == path && block_given?
apply_defaults_to_dir(dir)
end
end
- # Returns the added component dirs, with default settings applied
+ # Deletes and returns a previously added component dir
#
- # @return [Hash<String, ComponentDir>] the component dirs as a hash, keyed by path
- def dirs
- @dirs.each { |_, dir| apply_defaults_to_dir(dir) }
+ # @param path [String] the path for the component dir
+ #
+ # @return [ComponentDir] the removed component dir
+ #
+ # @api public
+ def delete(path)
+ dirs.delete(path)
end
+ # Returns the paths of the component dirs
+ #
+ # @return [Array<String>] the component dir paths
+ #
+ # @api public
+ def paths
+ dirs.keys
+ end
+
+ # Returns the count of component dirs
+ #
+ # @return [Integer]
+ #
+ # @api public
+ def length
+ dirs.length
+ end
+ alias_method :size, :length
+
# Returns the added component dirs, with default settings applied
#
# @return [Array<ComponentDir>]
+ #
+ # @api public
def to_a
+ dirs.each { |_, dir| apply_defaults_to_dir(dir) }
dirs.values
end
# Calls the given block once for each added component dir, passing the dir as an
# argument.
#
# @yieldparam dir [ComponentDir] the yielded component dir
+ #
+ # @api public
def each(&block)
to_a.each(&block)
end
+ protected
+
+ # Returns the hash of component dirs, keyed by their paths
+ #
+ # Recently changed default configuration may not be applied to these dirs. Use
+ # #to_a or #each to access dirs with default configuration fully applied.
+ #
+ # This method exists to encapsulate the instance variable and to serve the needs
+ # of #initialize_copy
+ #
+ # @return [Hash{String => ComponentDir}]
+ #
+ # @api private
+ attr_reader :dirs
+
private
+
+ # Converts a path string or pre-built component dir into a path and dir tuple
+ #
+ # @param path_or_dir [String,ComponentDir]
+ #
+ # @return [Array<(String, ComponentDir)>]
+ #
+ # @see #add
+ def path_and_dir(path_or_dir)
+ if path_or_dir.is_a?(ComponentDir)
+ dir = path_or_dir
+ [dir.path, dir]
+ else
+ path = path_or_dir
+ [path, ComponentDir.new(path)]
+ end
+ end
# Applies default settings to a component dir. This is run every time the dirs are
# accessed to ensure defaults are applied regardless of when new component dirs
# are added. This method must be idempotent.
#