# frozen_string_literal: true require 'frise/parser' module Frise # Provides the logic for merging config defaults into pre-loaded configuration objects. # # The merge_defaults and merge_defaults_at entrypoint methods provide ways to read files with # defaults and apply them to configuration objects. class DefaultsLoader SYMBOLS = %w[$all $optional].freeze def initialize(include_sym: '$include', content_include_sym: '$content_include', schema_sym: '$schema', delete_sym: '$delete') @include_sym = include_sym @content_include_sym = content_include_sym @schema_sym = schema_sym @delete_sym = delete_sym end def widened_class(obj) class_name = obj.class.to_s return 'String' if class_name == 'Hash' && !obj[@content_include_sym].nil? return 'Boolean' if %w[TrueClass FalseClass].include? class_name return 'Integer' if %w[Fixnum Bignum].include? class_name class_name end def merge_defaults_obj(config, defaults) config_class = widened_class(config) defaults_class = widened_class(defaults) if defaults.nil? config elsif config.nil? if defaults_class != 'Hash' then defaults elsif defaults['$optional'] then nil else merge_defaults_obj({}, defaults) end elsif config == @delete_sym config elsif defaults_class == 'Array' && config_class == 'Array' defaults + config elsif defaults_class == 'Hash' && defaults['$all'] && config_class == 'Array' config.map { |elem| merge_defaults_obj(elem, defaults['$all']) } elsif defaults_class == 'Hash' && config_class == 'Hash' new_config = {} (config.keys + defaults.keys).uniq.each do |key| next if SYMBOLS.include?(key) new_config[key] = config[key] new_config[key] = merge_defaults_obj(new_config[key], defaults[key]) if defaults.key?(key) new_config[key] = merge_defaults_obj(new_config[key], defaults['$all']) unless new_config[key].nil? new_config.delete(key) if new_config[key].nil? end new_config elsif defaults_class != config_class raise "Cannot merge config #{config.inspect} (#{widened_class(config)}) " \ "with default #{defaults.inspect} (#{widened_class(defaults)})" else config end end def merge_defaults_obj_at(config, at_path, defaults) at_path.reverse.each { |key| defaults = { key => defaults } } merge_defaults_obj(config, defaults) end def merge_defaults(config, defaults_file, symbol_table = config) defaults = Parser.parse(defaults_file, symbol_table) || {} merge_defaults_obj(config, defaults) end def merge_defaults_at(config, at_path, defaults_file, symbol_table = config) defaults = Parser.parse(defaults_file, symbol_table) || {} merge_defaults_obj_at(config, at_path, defaults) end end end