class Qonfig::Settings

@api private @since 0.21.0

@api private @since 0.1.0 rubocop:disable Metrics/ClassLength, Layout/ClassStructure

Constants

BASIC_SETTING_KEY_TRANSFORMER

@return [Proc]

@api private @since 0.11.0

BASIC_SETTING_VALUE_TRANSFORMER

@return [Proc]

@api private @since 0.11.0

CORE_METHODS

@return [Array<String>]

@api private @since 0.2.0

DOT_NOTATION_SEPARATOR

@return [String]

@api private @since 0.19.0

REPRESENT_HASH_IN_DOT_STYLE

@return [Boolean]

@api private @since 0.25.0

Attributes

__lock__[R]

@return [Qonfig::Settings::Lock]

@api private @since 0.2.0

__mutation_callbacks__[R]

@return [Qonfig::Settings::Callbacks]

@api private @since 0.13.0

__options__[R]

@return [Hash]

@api private @since 0.1.0

Public Class Methods

new(__mutation_callbacks__) click to toggle source

@api private @since 0.1.0

# File lib/qonfig/settings.rb, line 51
def initialize(__mutation_callbacks__)
  @__options__ = {}
  @__lock__ = Lock.new
  @__mutation_callbacks__ = __mutation_callbacks__
end

Public Instance Methods

[](*keys) click to toggle source

@param key [Symbol, String] @return [Object]

@raise [Qonfig::ArgumentError]

@api public @since 0.1.0 @version 0.25.0

# File lib/qonfig/settings.rb, line 149
def [](*keys)
  __dig__(*keys)
end
[]=(key, value) click to toggle source

@param key [String, Symbol] @param value [Object] @return [void]

@api public @since 0.1.0 @version 0.21.0

# File lib/qonfig/settings.rb, line 160
def []=(key, value)
  __lock__.thread_safe_access { __assign_value__(key, value) }
end
__append_settings__(settings) click to toggle source

@param settings [Qonfig::Settings] @return [void]

@api private @since 0.1.0

# File lib/qonfig/settings.rb, line 125
def __append_settings__(settings)
  __lock__.thread_safe_merge do
    settings.__options__.each_pair do |key, value|
      __define_setting__(key, value)
    end
  end
end
__apply_values__(settings_map) click to toggle source

@param settings_map [Hash] @return [void]

@api private @since 0.3.0

# File lib/qonfig/settings.rb, line 169
def __apply_values__(settings_map)
  __lock__.thread_safe_access { __set_values_from_map__(settings_map) }
end
__clear__() click to toggle source

@return [void]

@api private @since 0.2.0

# File lib/qonfig/settings.rb, line 283
def __clear__
  __lock__.thread_safe_access { __clear_option_values__ }
end
__deep_each_setting__(initial_setting_key = nil, yield_all: false, &block) click to toggle source

@param initial_setting_key [String, NilClass] @param block [Proc] @option yield_all [Boolean] @return [Enumerable]

@yield [key, value] @yieldparam key [String] @yieldparam value [Object]

@api private @since 0.13.0

# File lib/qonfig/settings.rb, line 83
def __deep_each_setting__(initial_setting_key = nil, yield_all: false, &block)
  __lock__.thread_safe_access do
    __deep_each_key_value_pair__(initial_setting_key, yield_all: yield_all, &block)
  end
end
__define_setting__(key, value, with_redefinition: false) click to toggle source

@param key [Symbol, String] @param value [Object] @option with_redefinition [Boolean] @return [void]

@api private @since 0.1.0 @version 0.20.0

# File lib/qonfig/settings.rb, line 97
def __define_setting__(key, value, with_redefinition: false) # rubocop:disable Metrics/AbcSize
  __lock__.thread_safe_definition do
    key = __indifferently_accessable_option_key__(key)

    __prevent_core_method_intersection__(key)

    # rubocop:disable Lint/DuplicateBranch
    case
    when with_redefinition || !__options__.key?(key)
      __options__[key] = value
    when __is_a_setting__(__options__[key]) && __is_a_setting__(value)
      __options__[key].__append_settings__(value)
    else
      __options__[key] = value
    end
    # rubocop:enable Lint/DuplicateBranch

    __define_option_reader__(key)
    __define_option_writer__(key)
    __define_option_predicate__(key)
  end
end
__dig__(*keys) click to toggle source

@param keys [Array<String, Symbol>] @return [Object]

@api private @since 0.2.0

# File lib/qonfig/settings.rb, line 178
def __dig__(*keys)
  __lock__.thread_safe_access do
    begin
      __deep_access__(*keys)
    rescue Qonfig::UnknownSettingError
      if keys.size == 1
        __deep_access__(*__parse_dot_notated_key__(keys.first))
      else
        raise
      end
    end
  end
end
__each_setting__(&block) click to toggle source

@param block [Proc] @return [Enumerable]

@yield [key, value] @yieldparam key [String] @yieldparam value [Object]

@api private @since 0.13.0

# File lib/qonfig/settings.rb, line 66
def __each_setting__(&block)
  __lock__.thread_safe_access do
    __each_key_value_pair__(&block)
  end
end
__freeze__() click to toggle source

@return [void]

@api private @since 0.1.0

# File lib/qonfig/settings.rb, line 316
def __freeze__
  __lock__.thread_safe_access do
    __options__.freeze

    __options__.each_value do |value|
      value.__freeze__ if __is_a_setting__(value)
    end
  end
end
__has_key__(*key_path) click to toggle source

@param key_path [Array<Symbol, String>] @return [Boolean]

@api private @since 0.17.0

# File lib/qonfig/settings.rb, line 348
def __has_key__(*key_path)
  __lock__.thread_safe_access { __is_key_exists__(*key_path) }
end
__invoke_mutation_callbacks__() click to toggle source

@return [void]

@api private @since 0.13.0

# File lib/qonfig/settings.rb, line 137
def __invoke_mutation_callbacks__
  __mutation_callbacks__.call
end
__is_a_setting__(value) click to toggle source

@param value [Any] @return [Boolean]

@api private @since 0.13.0

# File lib/qonfig/settings.rb, line 339
def __is_a_setting__(value)
  value.is_a?(Qonfig::Settings)
end
__is_frozen__() click to toggle source

@return [Boolean]

@api private @since 0.2.0

# File lib/qonfig/settings.rb, line 330
def __is_frozen__
  __lock__.thread_safe_access { __options__.frozen? }
end
__keys__(all_variants: false) click to toggle source

@option all_variants [Boolean] @return [Array<String>]

@api private @since 0.18.0

# File lib/qonfig/settings.rb, line 267
def __keys__(all_variants: false)
  __lock__.thread_safe_access { __setting_keys__(all_variants: all_variants) }
end
__root_keys__() click to toggle source

@return [Array<String>]

@api private @since 0.18.0

# File lib/qonfig/settings.rb, line 275
def __root_keys__
  __lock__.thread_safe_access { __root_setting_keys__ }
end
__slice__(*keys) click to toggle source

@param keys [Array<String, Symbol>] @return [Hash]

@api private @since 0.9.0

# File lib/qonfig/settings.rb, line 197
def __slice__(*keys)
  __lock__.thread_safe_access { __deep_slice__(*keys) }
end
__slice_value__(*keys) click to toggle source

@param keys [Array<String, Symbol>] @return [Hash, Any]

@api private @since 0.10.0

# File lib/qonfig/settings.rb, line 206
def __slice_value__(*keys)
  __lock__.thread_safe_access { __deep_slice_value__(*keys) }
end
__subset__(*keys) click to toggle source

@param keys [Array<String, Symbol, Array<String, Symbol>>] @return [Hash]

@api private @since 0.16.0

# File lib/qonfig/settings.rb, line 215
def __subset__(*keys)
  __lock__.thread_safe_access { __deep_subset__(*keys) }
end
__to_h__( dot_notation: REPRESENT_HASH_IN_DOT_STYLE, transform_key: BASIC_SETTING_KEY_TRANSFORMER, transform_value: BASIC_SETTING_VALUE_TRANSFORMER )
Alias for: __to_hash__
__to_hash__( dot_notation: REPRESENT_HASH_IN_DOT_STYLE, transform_key: BASIC_SETTING_KEY_TRANSFORMER, transform_value: BASIC_SETTING_VALUE_TRANSFORMER ) click to toggle source

@option dot_notation [Boolean] @option transform_key [Proc] @option transform_value [Proc] @return [Hash]

@api private @since 0.1.0 @version 0.25.0

# File lib/qonfig/settings.rb, line 227
def __to_hash__(
  dot_notation: REPRESENT_HASH_IN_DOT_STYLE,
  transform_key: BASIC_SETTING_KEY_TRANSFORMER,
  transform_value: BASIC_SETTING_VALUE_TRANSFORMER
)
  unless transform_key.is_a?(Proc)
    ::Kernel.raise(
      Qonfig::IncorrectKeyTransformerError,
      'Key transformer should be a type of proc'
    )
  end

  unless transform_value.is_a?(Proc)
    ::Kernel.raise(
      Qonfig::IncorrectValueTransformerError,
      'Value transformer should be a type of proc'
    )
  end

  __lock__.thread_safe_access do
    if dot_notation
      __build_dot_notated_hash_representation__(
        transform_key: transform_key,
        transform_value: transform_value
      )
    else
      __build_basic_hash_representation__(
        transform_key: transform_key,
        transform_value: transform_value
      )
    end
  end
end
Also aliased as: __to_h__
method_missing(method_name, *arguments, &block) click to toggle source

@param method_name [String, Symbol] @param arguments [Array<Object>] @param block [Proc] @return [void]

@raise [Qonfig::UnknownSettingError]

@api private @since 0.1.0

Calls superclass method
# File lib/qonfig/settings.rb, line 296
def method_missing(method_name, *arguments, &block)
  super
rescue NoMethodError
  ::Kernel.raise(Qonfig::UnknownSettingError, "Setting with <#{method_name}> key doesnt exist!")
end
respond_to_missing?(method_name, include_private = false) click to toggle source

@return [Boolean]

@api private @since 0.1.0

Calls superclass method
# File lib/qonfig/settings.rb, line 306
def respond_to_missing?(method_name, include_private = false)
  # :nocov:
  __options__.key?(method_name.to_s) || __options__.key?(method_name.to_sym) || super
  # :nocov:
end

Private Instance Methods

__assign_value__(key, value) click to toggle source

@param key [String, Symbol] @param value [Any] @return [void]

@api private @since 0.21.0 rubocop:disable Naming/RescuedExceptionsVariableName, Style/SlicingWithRange

# File lib/qonfig/settings.rb, line 525
def __assign_value__(key, value)
  key = __indifferently_accessable_option_key__(key)
  __set_value__(key, value)
rescue Qonfig::UnknownSettingError => initial_error
  key_set = __parse_dot_notated_key__(key)

  # NOTE: key is not dot-notaed and original key does not exist
  raise(initial_error) if key_set.size == 1

  begin
    # TODO: rewrite with __deep_access__-like key resolving functionality
    setting_value = __get_value__(key_set.first)
    required_key = key_set[1..-1].join(DOT_NOTATION_SEPARATOR)
    setting_value[required_key] = value # NOTE: pseudo-recoursive assignment
  rescue Qonfig::UnknownSettingError
    raise(initial_error)
  end
end
__build_basic_hash_representation__(options_part = __options__, transform_key:, transform_value:) click to toggle source

@param options_part [Hash] @option transform_key [Proc] @option transform_value [Proc] @return [Hash]

@api private @since 0.25.0 rubocop:disable Layout/LineLength

# File lib/qonfig/settings.rb, line 714
def __build_basic_hash_representation__(options_part = __options__, transform_key:, transform_value:)
  options_part.each_with_object({}) do |(key, value), hash|
    final_key = transform_key.call(key)

    case
    when value.is_a?(Hash)
      hash[final_key] = __build_basic_hash_representation__(
        value,
        transform_key: transform_key,
        transform_value: transform_value
      )
    when __is_a_setting__(value)
      hash[final_key] = value.__to_hash__(
        transform_key: transform_key,
        transform_value: transform_value
      )
    else
      final_value = transform_value.call(value)
      hash[final_key] = final_value
    end
  end
end
__build_dot_notated_hash_representation__(transform_key:, transform_value:) click to toggle source

@option transform_key [Proc] @option transform_value [Proc] @return [Hash]

@api private @since 0.25.0

# File lib/qonfig/settings.rb, line 744
def __build_dot_notated_hash_representation__(transform_key:, transform_value:)
  {}.tap do |hash|
    __deep_each_key_value_pair__ do |setting_key, setting_value|
      final_key = transform_key.call(setting_key)
      final_value = transform_value.call(setting_value)

      hash[final_key] = final_value
    end
  end
end
__clear_option_values__() click to toggle source

@return [void]

@raise [Qonfig::FrozenSettingsError]

@api private @since 0.2.0

# File lib/qonfig/settings.rb, line 489
def __clear_option_values__
  ::Kernel.raise(
    Qonfig::FrozenSettingsError, 'Can not modify frozen settings'
  ) if __options__.frozen?

  __options__.each_pair do |key, value|
    __is_a_setting__(value) ? value.__clear__ : __options__[key] = nil
  end

  __invoke_mutation_callbacks__
end
__deep_access__(*keys) click to toggle source

@param keys [Array<Symbol, String>] @return [Object]

@raise [Qonfig::ArgumentError] @raise [Qonfig::UnknownSettingError]

@api private @since 0.2.0 rubocop:disable Metrics/AbcSize, Style/SlicingWithRange

# File lib/qonfig/settings.rb, line 587
def __deep_access__(*keys)
  ::Kernel.raise(Qonfig::ArgumentError, 'Key list can not be empty') if keys.empty?

  result = nil
  rest_keys = nil
  key_parts_boundary = keys.size - 1

  0.upto(key_parts_boundary) do |key_parts_slice_boundary|
    begin
      setting_key = keys[0..key_parts_slice_boundary].join(DOT_NOTATION_SEPARATOR)
      result = __get_value__(setting_key)
      rest_keys = Array(keys[(key_parts_slice_boundary + 1)..-1])
      break
    rescue Qonfig::UnknownSettingError => error
      key_parts_boundary == key_parts_slice_boundary ? raise(error) : next
    end
  end

  case
  when rest_keys.empty?
    result
  when !__is_a_setting__(result)
    ::Kernel.raise(
      Qonfig::UnknownSettingError,
      'Setting with required digging sequence does not exist!'
    )
  when __is_a_setting__(result)
    result.__dig__(*rest_keys)
  end
end
__deep_each_key_value_pair__(initial_setting_key = nil, yield_all: false, &block) click to toggle source

@param initial_setting_key [String, NilClass] @param block [Proc] @option yield_all [Boolean] @return [Enumerator]

@yield [setting_key, setting_value] @yieldparam setting_key [String] @yieldparam setting_value [Object]

@api private @since 0.13.0

# File lib/qonfig/settings.rb, line 434
def __deep_each_key_value_pair__(initial_setting_key = nil, yield_all: false, &block)
  enumerator = Enumerator.new do |yielder|
    __each_key_value_pair__ do |setting_key, setting_value|
      final_setting_key =
        initial_setting_key ? "#{initial_setting_key}.#{setting_key}" : setting_key

      if __is_a_setting__(setting_value)
        yielder.yield(final_setting_key, setting_value) if yield_all
        setting_value.__deep_each_setting__(final_setting_key, yield_all: yield_all, &block)
      else
        yielder.yield(final_setting_key, setting_value)
      end
    end
  end

  block_given? ? enumerator.each(&block) : enumerator
end
__deep_slice__(*keys) click to toggle source

@param keys [Array<Symbol, String>] @return [Hash]

@raise [Qonfig::ArgumentError] @raise [Qonfig::UnknownSettingError]

@api private @since 0.9.0 rubocop:disable Metrics/AbcSize

# File lib/qonfig/settings.rb, line 628
def __deep_slice__(*keys)
  {}.tap do |result|
    begin
      __deep_access__(*keys).tap do |setting|
        required_key = __indifferently_accessable_option_key__(keys.last)
        result[required_key] = __is_a_setting__(setting) ? setting.__to_h__ : setting
      end
    rescue Qonfig::UnknownSettingError
      if keys.size == 1
        key_set = __parse_dot_notated_key__(keys.first)
        __deep_access__(*key_set).tap do |setting|
          required_key = __indifferently_accessable_option_key__(key_set.last)
          result[required_key] = __is_a_setting__(setting) ? setting.__to_h__ : setting
        end
      else
        raise
      end
    end
  end
end
__deep_slice_value__(*keys) click to toggle source

@param keys [Array<Symbol, String>] @return [Hash]

@raise [Qonfig::ArgumentError] @raise [Qonfig::UnknownSettingError]

@api private @since 0.1.0

# File lib/qonfig/settings.rb, line 658
def __deep_slice_value__(*keys)
  required_key = __indifferently_accessable_option_key__(keys.last)
  sliced_data = __deep_slice__(*keys)

  case
  when sliced_data.key?(required_key)
    sliced_data[required_key]
  when keys.size == 1
    required_key = __parse_dot_notated_key__(required_key).last
    sliced_data[required_key]
  else # NOTE: possibly unreachable code
    # :nocov:
    raise(
      Qonfig::StrangeThingsError,
      "Strange things happpens with #{keys} keyset and value slicing"
    )
    # :nocov:
  end
end
__deep_subset__(*keys) click to toggle source

@param keys [Array<String, Symbol, Array<String, Symbol>>] @return [Hash]

@api private @since 0.16.0

# File lib/qonfig/settings.rb, line 683
def __deep_subset__(*keys)
  {}.tap do |result|
    keys.each do |key_set|
      required_keys =
        case key_set
        when String, Symbol
          # TODO: support for patterns
          __indifferently_accessable_option_key__(key_set)
        when Array
          key_set.map { |key| __indifferently_accessable_option_key__(key) }
        else
          raise(
            Qonfig::ArgumentError,
            'All setting keys should be a symbol/string or an array of symbols/strings!'
          )
        end

      required_options = __deep_slice__(*required_keys)
      result.merge!(required_options)
    end
  end
end
__define_option_predicate__(key) click to toggle source

@param key [Symbol, String] @return [void]

@api private @since 0.13.0

# File lib/qonfig/settings.rb, line 782
def __define_option_predicate__(key)
  define_singleton_method("#{key}?") do
    !!self.[](key)
  end
end
__define_option_reader__(key) click to toggle source

@param key [Symbol, String] @return [void]

@api private @since 0.13.0

# File lib/qonfig/settings.rb, line 760
def __define_option_reader__(key)
  define_singleton_method(key) do
    self.[](key)
  end
end
__define_option_writer__(key) click to toggle source

@param key [Symbol, String] @return [void]

@api private @since 0.13.0

# File lib/qonfig/settings.rb, line 771
def __define_option_writer__(key)
  define_singleton_method("#{key}=") do |value|
    self.[]=(key, value)
  end
end
__each_key_value_pair__(&block) click to toggle source

@param block [Proc] @return [Enumerator]

@yield [setting_key, setting_value] @yieldparam key [String] @yieldparam value [Object]

@api private @since 0.13.0

# File lib/qonfig/settings.rb, line 419
def __each_key_value_pair__(&block)
  __options__.each_pair(&block)
end
__get_value__(key) click to toggle source

@param key [String, Symbol] @return [Object]

@raise [Qonfig::UnknownSettingError]

@api private @since 0.2.0

# File lib/qonfig/settings.rb, line 508
def __get_value__(key)
  key = __indifferently_accessable_option_key__(key)

  unless __options__.key?(key)
    ::Kernel.raise(Qonfig::UnknownSettingError, "Setting with <#{key}> key does not exist!")
  end

  __options__[key]
end
__indifferently_accessable_option_key__(key) click to toggle source

@param key [Symbol, String] @return [String]

@raise [Qonfig::ArgumentError] @see Qonfig::Settings::KeyGuard

@api private @since 0.2.0

# File lib/qonfig/settings.rb, line 796
def __indifferently_accessable_option_key__(key)
  KeyGuard.new(key).prevent_incompatible_key_type!
  key.to_s
end
__is_key_exists__(*key_path) click to toggle source

@param key_path [Array<String, Symbol>] @return [Boolean]

@api private @since 0.17.0

# File lib/qonfig/settings.rb, line 394
def __is_key_exists__(*key_path)
  begin
    __deep_access__(*key_path)
  rescue Qonfig::UnknownSettingError
    if key_path.size == 1
      __deep_access__(*__parse_dot_notated_key__(key_path.first))
    else
      raise
    end
  end

  true
rescue Qonfig::UnknownSettingError
  false
end
__parse_dot_notated_key__(key) click to toggle source

@param key [String, Symbol] @return [Array<String>]

@api private @since 0.19.0

# File lib/qonfig/settings.rb, line 818
def __parse_dot_notated_key__(key)
  __indifferently_accessable_option_key__(key).split(DOT_NOTATION_SEPARATOR)
end
__prevent_core_method_intersection__(key) click to toggle source

@param key [Symbol, String] @return [void]

@raise [Qonfig::CoreMethodIntersectionError] @see Qonfig::Settings::KeyGuard

@api private @since 0.2.0

# File lib/qonfig/settings.rb, line 809
def __prevent_core_method_intersection__(key)
  KeyGuard.new(key).prevent_core_method_intersection!
end
__root_setting_keys__() click to toggle source

@return [Array<String>]

@api private @since 0.18.0

# File lib/qonfig/settings.rb, line 385
def __root_setting_keys__
  __options__.keys
end
__set_value__(key, value) click to toggle source

@param key [String, Symbol] @param value [Object] @return [void]

@raise [Qonfig::UnknownSettingError] @raise [Qonfig::FrozenSettingsError] @raise [Qonfig::AmbiguousSettingValueError]

@api private @since 0.2.0

# File lib/qonfig/settings.rb, line 555
def __set_value__(key, value)
  key = __indifferently_accessable_option_key__(key)

  unless __options__.key?(key)
    ::Kernel.raise(Qonfig::UnknownSettingError, "Setting with <#{key}> key does not exist!")
  end

  if __options__.frozen?
    ::Kernel.raise(Qonfig::FrozenSettingsError, 'Can not modify frozen settings')
  end

  if __is_a_setting__(__options__[key])
    ::Kernel.raise(
      Qonfig::AmbiguousSettingValueError,
      "Can not redefine option <#{key}> that contains nested options"
    )
  end

  (__options__[key] = value)

  __invoke_mutation_callbacks__
end
__set_values_from_map__(settings_map) click to toggle source

@param settings_map [Hash] @return [void]

@raise [Qonfig::ArgumentError] @raise [Qonfig::AmbiguousSettingValueError]

@api private @since 0.3.0

# File lib/qonfig/settings.rb, line 460
def __set_values_from_map__(settings_map)
  ::Kernel.raise(
    Qonfig::ArgumentError, 'Options map should be represented as a hash'
  ) unless settings_map.is_a?(Hash)

  settings_map.each_pair do |key, value|
    current_value = __get_value__(key)

    # NOTE: some duplications here was made only for the better code readability
    case
    when !__is_a_setting__(current_value)
      __set_value__(key, value)
    when __is_a_setting__(current_value) && value.is_a?(Hash)
      current_value.__apply_values__(value)
    when __is_a_setting__(current_value) && !value.is_a?(Hash)
      ::Kernel.raise(
        Qonfig::AmbiguousSettingValueError,
        "Can not redefine option <#{key}> that contains nested options"
      )
    end
  end
end
__setting_keys__(all_variants: false) click to toggle source

@option all_variants [Boolean] @return [Array<String>]

@api private @since 0.18.0

# File lib/qonfig/settings.rb, line 365
def __setting_keys__(all_variants: false)
  # NOTE: (if all_variants == true)
  #   We have { a: { b: { c: { d : 1 } } } }
  #   Its mean that we have these keys:
  #     - 'a' # => returns { b: { c: { d: 1 } } }
  #     - 'a.b' # => returns { c: { d: 1 } }
  #     - 'a.b.c' # => returns { d: 1 }
  #     - 'a.b.c.d' # => returns 1

  Set.new.tap do |setting_keys|
    __deep_each_key_value_pair__(yield_all: all_variants) do |setting_key, _setting_value|
      setting_keys << setting_key
    end
  end.to_a
end