require 'concurrent' require 'dry/configurable/config' require 'dry/configurable/error' require 'dry/configurable/nested_config' require 'dry/configurable/argument_parser' require 'dry/configurable/config/value' require 'dry/configurable/version' # A collection of micro-libraries, each intended to encapsulate # a common task in Ruby module Dry # A simple configuration mixin # # @example # # class App # extend Dry::Configurable # # setting :database do # setting :dsn, 'sqlite:memory' # end # end # # App.configure do |config| # config.database.dsn = 'jdbc:sqlite:memory' # end # # App.config.database.dsn # # => "jdbc:sqlite:memory'" # # @api public module Configurable # @private def self.extended(base) base.class_eval do @_config_mutex = ::Mutex.new @_settings = ::Concurrent::Array.new @_reader_attributes = ::Concurrent::Array.new end end # @private def inherited(subclass) subclass.instance_variable_set(:@_config_mutex, ::Mutex.new) subclass.instance_variable_set(:@_settings, @_settings.clone) subclass.instance_variable_set(:@_reader_attributes, @_reader_attributes.clone) subclass.instance_variable_set(:@_config, @_config.clone) if defined?(@_config) super end # Return configuration # # @return [Dry::Configurable::Config] # # @api public def config return @_config if defined?(@_config) create_config end # Return configuration # # @yield [Dry::Configuration::Config] # # @return [Dry::Configurable::Config] # # @api public def configure yield(config) if block_given? end # Add a setting to the configuration # # @param [Mixed] key # The accessor key for the configuration value # @param [Mixed] default # The default config value # # @yield # If a block is given, it will be evaluated in the context of # and new configuration class, and bound as the default value # # @return [Dry::Configurable::Config] # # @api public def setting(key, *args, &block) raise_already_defined_config(key) if defined?(@_config) value, options = ArgumentParser.call(args) if block if block.parameters.empty? value = _config_for(&block) else processor = block end end _settings << ::Dry::Configurable::Config::Value.new( key, value || ::Dry::Configurable::Config::Value::NONE, processor || ::Dry::Configurable::Config::DEFAULT_PROCESSOR ) store_reader_options(key, options) if options.any? end # Return an array of setting names # # @return [Array] # # @api public def settings _settings.map(&:name) end # @private no, really... def _settings @_settings end def _reader_attributes @_reader_attributes end private # @private def _config_for(&block) ::Dry::Configurable::NestedConfig.new(&block) end # @private def create_config @_config_mutex.synchronize do create_config_for_nested_configurations @_config = ::Dry::Configurable::Config.create(_settings) unless _settings.empty? end end # @private def create_config_for_nested_configurations nested_configs.map(&:create_config) end # @private def nested_configs _settings.select { |setting| setting.value.is_a?(::Dry::Configurable::NestedConfig) }.map(&:value) end # @private def raise_already_defined_config(key) raise AlreadyDefinedConfig, "Cannot add setting +#{key}+, #{self} is already configured" end # @private def store_reader_options(key, options) _reader_attributes << key if options.fetch(:reader, false) end # @private def method_missing(method, *args, &block) _reader_attributes.include?(method) ? config.public_send(method, *args, &block) : super end # @private def respond_to_missing?(method, _include_private = false) _reader_attributes.include?(method) || super end end end