lib/frise/loader.rb in frise-0.2.0 vs lib/frise/loader.rb in frise-0.3.0.pre

- old
+ new

@@ -1,60 +1,129 @@ +# frozen_string_literal: true + require 'frise/defaults_loader' +require 'frise/loader/lazy' require 'frise/parser' require 'frise/validator' module Frise # The entrypoint for loading configs from files according to the conventions defined for Frise. # - # The load method loads a configuration file, merges it with the applicable defaults and validates - # its schema. Other methods in Loader perform only parts of the process. + # The load method loads a configuration file, merges the applicable includes and validates its schema. class Loader - def initialize(schema_load_paths: [], defaults_load_paths: [], pre_loaders: [], validators: nil) - @schema_load_paths = schema_load_paths - @defaults_load_paths = defaults_load_paths + def initialize(include_sym: '$include', schema_sym: '$schema', pre_loaders: [], validators: nil, exit_on_fail: true) + @include_sym = include_sym + @schema_sym = schema_sym @pre_loaders = pre_loaders @validators = validators + @exit_on_fail = exit_on_fail end - def load(config_file, exit_on_fail = true, symbol_table = {}) - config = Parser.parse(config_file, symbol_table) || {} - config_name = File.basename(config_file) + def load(config_file, global_vars = {}) + config = Parser.parse(config_file, global_vars) + return nil unless config @pre_loaders.each do |pre_loader| config = pre_loader.call(config) end - config = merge_defaults(config, config_name, symbol_table) - validate(config, config_name, exit_on_fail) + config = process_includes(config, [], config, global_vars) if @include_sym + config = process_schemas(config, [], global_vars) if @schema_sym + config end - def merge_defaults(config, defaults_name, symbol_table = {}) - merge_defaults_at(config, [], defaults_name, symbol_table) - end + private - def merge_defaults_at(config, at_path, defaults_name, symbol_table = {}) - @defaults_load_paths.map do |defaults_dir| - defaults_file = File.join(defaults_dir, defaults_name) - config = DefaultsLoader.merge_defaults_at( - config, at_path, defaults_file, symbol_table.merge(config) - ) + def process_includes(config, at_path, root_config, global_vars) + return config unless config.class == Hash + + config, defaults_confs = extract_include(config) + if defaults_confs.empty? + config.map { |k, v| [k, process_includes(v, at_path + [k], root_config, global_vars)] }.to_h + else + Lazy.new do + defaults_confs.each do |defaults_conf| + extra_vars = (defaults_conf['vars'] || {}).map { |k, v| [k, root_config.dig(*v.split('.'))] }.to_h + extra_consts = defaults_conf['constants'] || {} + symbol_table = merge_at(root_config, at_path, config) + .merge(global_vars).merge(extra_vars).merge(extra_consts).merge('_this' => config) + + config = DefaultsLoader.merge_defaults_obj(config, Parser.parse(defaults_conf['file'], symbol_table)) + end + process_includes(config, at_path, merge_at(root_config, at_path, config), global_vars) + end end - config end - def validate(config, schema_name, exit_on_fail = true) - validate_at(config, [], schema_name, exit_on_fail) + def process_schema_includes(schema, global_vars) + return schema unless schema.class == Hash + + schema, included_schemas = extract_include(schema) + if included_schemas.empty? + schema.map { |k, v| [k, process_schema_includes(v, global_vars)] }.to_h + else + included_schemas.each do |defaults_conf| + schema = Parser.parse(defaults_conf['file'], global_vars).merge(schema) + end + process_schema_includes(schema, global_vars) + end end - def validate_at(config, at_path, schema_name, exit_on_fail = true) - @schema_load_paths.map do |schema_dir| - schema_file = File.join(schema_dir, schema_name) - errors = Validator.validate_at(config, at_path, schema_file, - validators: @validators, - print: exit_on_fail, - fatal: exit_on_fail) + def process_schemas(config, at_path, global_vars) + return config unless config.class == Hash + + config = config.map do |k, v| + new_v = process_schemas(v, at_path + [k], global_vars) + return nil if !v.nil? && new_v.nil? + [k, new_v] + end.to_h + + config, schema_files = extract_schema(config) + schema_files.each do |schema_file| + schema = Parser.parse(schema_file, global_vars) + schema = process_schema_includes(schema, global_vars) + + errors = Validator.validate_obj(config, + schema, + path_prefix: at_path, + validators: @validators, + print: @exit_on_fail, + fatal: @exit_on_fail) return nil if errors.any? end config + end + + def extract_schema(config) + extract_special(config, @schema_sym) do |value| + case value + when String then value + else raise "Illegal value for a #{@schema_sym} element: #{value.inspect}" + end + end + end + + def extract_include(config) + extract_special(config, @include_sym) do |value| + case value + when Hash then value + when String then { 'file' => value } + else raise "Illegal value for a #{@include_sym} element: #{value.inspect}" + end + end + end + + def extract_special(config, key) + case config[key] + when nil then [config, []] + when Array then [config.reject { |k| k == key }, config[key].map { |e| yield e }] + else raise "Illegal value for #{key}: #{config[key].inspect}" + end + end + + def merge_at(config, at_path, to_merge) + return config.merge(to_merge) if at_path.empty? + head, *tail = at_path + config.merge(head => merge_at(config[head], tail, to_merge)) end end end