# coding: utf-8 module Configurations # Configuration is a blank object in order to allow configuration of # various properties including keywords # class ArbitraryConfiguration < Configuration # Initialize a new configuration # @param [Hash] options The options to initialize a configuration with # @option options [Hash] methods a hash of method names pointing to procs # @option options [Proc] not_configured a proc to evaluate for # not_configured properties # @param [Proc] block a block to configure this configuration with # @yield [HostModule::Configuration] a configuration # @return [HostModule::Configuration] a configuration # @note An arbitrary configuration has to control its writeable state, # therefore configuration is only possible in the initialization block # def initialize(options = {}, &block) self.__writeable__ = true super self.__writeable__ = false if block end # Method missing gives access for reading and writing to the underlying # configuration hash via dot notation # def method_missing(method, *args, &block) if __respond_to_writer?(method) __assign!(method.to_s[0..-2].to_sym, args.first) elsif __respond_to_method_for_write?(method) @data[method] elsif __respond_to_method_for_read?(method, *args, &block) @data.fetch(method, &__not_configured_callback_for__(method)) else super end end # Respond to missing according to the method_missing implementation # def respond_to_missing?(method, include_private = false) __respond_to_writer?(method) || __respond_to_method_for_read?(method, *args, &block) || __respond_to_method_for_write?(method) || super end # A convenience accessor to instantiate a configuration from a hash # @param [Hash] h the hash to read into the configuration # @return [Configuration] the configuration with values assigned # @note can only be accessed during writeable state (in configure block). # Unassignable values are ignored # @raise [ArgumentError] unless used in writeable state (in configure block) # def from_h(h) unless @__writeable__ fail ::ArgumentError, 'can not dynamically assign values from a hash' end super end # @param [Symbol] property The property to test for configurability # @return [Boolean] whether the given property is configurable # def __configurable?(_property) true end # Set the configuration to writeable or read only. Access to writer methods # is only allowed within the configure block, this method is used to invoke # writeability for subconfigurations. # @param [Boolean] data true if the configuration should be writeable, false # otherwise # def __writeable__=(data) @__writeable__ = data return if @data.nil? @data.each do |_k, v| v.__writeable__ = data if v.is_a?(__class__) end end private # @param [Symbol] property The property to test for # @return [Boolean] whether the given property has been configured # def __configured?(_property) true end # @param [Symbol] method the method to test for # @return [Boolean] whether the given method is a writer # def __is_writer?(method) method.to_s.end_with?('=') end # @param [Symbol] method the method to test for # @return [Boolean] whether the configuration responds to the given property # as a method during writeable state # def __respond_to_method_for_write?(method) !__is_writer?(method) && @__writeable__ && @data[method].is_a?(__class__) end # @param [Symbol] method the method to test for # @return [Boolean] whether the configuration responds to the given property # def __respond_to_method_for_read?(method, *args, &block) !__is_writer?(method) && args.empty? && block.nil? end # @param [Symbol] method the method to test for # @return [Boolean] whether the method is a writer and is used in writeable # state # def __respond_to_writer?(method) @__writeable__ && __is_writer?(method) end end end