# frozen_string_literal: true require "concurrent/map" require "dry/core/equalizer" require "dry/configurable/constants" require "dry/configurable/errors" module Dry module Configurable # Config exposes setting values through a convenient API # # @api public class Config include Dry::Equalizer(:values) # @api private attr_reader :_settings # @api private attr_reader :_resolved # @api private def initialize(settings) @_settings = settings @_resolved = Concurrent::Map.new end # Get config value by a key # # @param [String,Symbol] name # # @return Config value def [](name) name = name.to_sym raise ArgumentError, "+#{name}+ is not a setting name" unless _settings.key?(name) _settings[name].value end # Set config value. # Note that finalized configs cannot be changed. # # @param [String,Symbol] name # @param [Object] value def []=(name, value) public_send(:"#{name}=", value) end # Update config with new values # # @param values [Hash, #to_hash] A hash with new values # # @return [Config] # # @api public def update(values) values.each do |key, value| if self[key].is_a?(self.class) unless value.respond_to?(:to_hash) raise ArgumentError, "#{value.inspect} is not a valid setting value" end self[key].update(value.to_hash) else self[key] = value end end self end # Dump config into a hash # # @return [Hash] # # @api public def values _settings .map { |setting| [setting.name, setting.value] } .map { |key, value| [key, value.is_a?(self.class) ? value.to_h : value] } .to_h end alias_method :to_h, :values # @api private def finalize!(freeze_values: false) _settings.finalize!(freeze_values: freeze_values) freeze end # @api private def pristine self.class.new(_settings.pristine) end # @api private def respond_to_missing?(meth, include_private = false) super || _settings.key?(resolve(meth)) end private # @api private def method_missing(meth, *args) setting = _settings[resolve(meth)] super unless setting if setting.writer?(meth) raise FrozenConfig, "Cannot modify frozen config" if frozen? _settings << setting.with(input: args[0]) else setting.value end end # @api private def resolve(meth) _resolved.fetch(meth) { _resolved[meth] = meth.to_s.tr("=", "").to_sym } end # @api private def initialize_copy(source) super @_settings = source._settings.dup end end end end