lib/frise/loader.rb in frise-0.3.0.pre vs lib/frise/loader.rb in frise-0.3.0
- old
+ new
@@ -8,16 +8,29 @@
module Frise
# The entrypoint for loading configs from files according to the conventions defined for Frise.
#
# The load method loads a configuration file, merges the applicable includes and validates its schema.
class Loader
- def initialize(include_sym: '$include', schema_sym: '$schema', pre_loaders: [], validators: nil, exit_on_fail: true)
+ def initialize(include_sym: '$include',
+ content_include_sym: '$content_include',
+ schema_sym: '$schema',
+ pre_loaders: [],
+ validators: nil,
+ exit_on_fail: true)
+
@include_sym = include_sym
+ @content_include_sym = content_include_sym
@schema_sym = schema_sym
@pre_loaders = pre_loaders
@validators = validators
@exit_on_fail = exit_on_fail
+
+ @defaults_loader = DefaultsLoader.new(
+ include_sym: include_sym,
+ content_include_sym: content_include_sym,
+ schema_sym: schema_sym
+ )
end
def load(config_file, global_vars = {})
config = Parser.parse(config_file, global_vars)
return nil unless config
@@ -34,39 +47,53 @@
private
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?
+ # process $content_include directives
+ config, content_include_confs = extract_content_include(config, at_path)
+ unless content_include_confs.empty?
+ raise "At #{build_path(at_path)}: a #{@content_include_sym} must not have any sibling key" unless config.empty?
+
+ content = ''
+ content_include_confs.each do |include_conf|
+ symbol_table = build_symbol_table(root_config, at_path, nil, global_vars, include_conf)
+ content += Parser.parse_as_text(include_conf['file'], symbol_table) || ''
+ end
+ return content
+ end
+
+ # process $include directives
+ config, include_confs = extract_include(config, at_path)
+ if include_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)
+ include_confs.each do |include_conf|
+ symbol_table = build_symbol_table(root_config, at_path, config, global_vars, include_conf)
+ included_config = Parser.parse(include_conf['file'], symbol_table)
- config = DefaultsLoader.merge_defaults_obj(config, Parser.parse(defaults_conf['file'], symbol_table))
+ config = @defaults_loader.merge_defaults_obj(config, included_config)
+ config = process_includes(config, at_path, merge_at(root_config, at_path, config), global_vars)
end
- process_includes(config, at_path, merge_at(root_config, at_path, config), global_vars)
+ updated_root_config = merge_at(root_config, at_path, config)
+ config.map { |k, v| [k, process_includes(v, at_path + [k], updated_root_config, global_vars)] }.to_h
end
end
end
- def process_schema_includes(schema, global_vars)
+ def process_schema_includes(schema, at_path, global_vars)
return schema unless schema.class == Hash
- schema, included_schemas = extract_include(schema)
+ schema, included_schemas = extract_include(schema, at_path)
if included_schemas.empty?
- schema.map { |k, v| [k, process_schema_includes(v, global_vars)] }.to_h
+ schema.map { |k, v| [k, process_schema_includes(v, at_path + [k], 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)
+ process_schema_includes(schema, at_path, global_vars)
end
end
def process_schemas(config, at_path, global_vars)
return config unless config.class == Hash
@@ -75,14 +102,14 @@
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)
+ config, schema_files = extract_schema(config, at_path)
schema_files.each do |schema_file|
schema = Parser.parse(schema_file, global_vars)
- schema = process_schema_includes(schema, global_vars)
+ schema = process_schema_includes(schema, at_path, global_vars)
errors = Validator.validate_obj(config,
schema,
path_prefix: at_path,
validators: @validators,
@@ -91,39 +118,70 @@
return nil if errors.any?
end
config
end
- def extract_schema(config)
- extract_special(config, @schema_sym) do |value|
+ def extract_schema(config, at_path)
+ extract_special(config, @schema_sym, at_path) do |value|
case value
when String then value
- else raise "Illegal value for a #{@schema_sym} element: #{value.inspect}"
+ else raise "At #{build_path(at_path)}: illegal value for a #{@schema_sym} element: #{value.inspect}"
end
end
end
- def extract_include(config)
- extract_special(config, @include_sym) do |value|
+ def extract_include(config, at_path)
+ extract_include_base(config, @include_sym, at_path)
+ end
+
+ def extract_content_include(config, at_path)
+ extract_include_base(config, @content_include_sym, at_path)
+ end
+
+ def extract_include_base(config, sym, at_path)
+ extract_special(config, sym, at_path) do |value|
case value
when Hash then value
when String then { 'file' => value }
- else raise "Illegal value for a #{@include_sym} element: #{value.inspect}"
+ else raise "At #{build_path(at_path)}: illegal value for a #{sym} element: #{value.inspect}"
end
end
end
- def extract_special(config, key)
+ def extract_special(config, key, at_path)
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}"
+ else raise "At #{build_path(at_path)}: illegal value for #{key}: #{config[key].inspect}"
end
end
+ # merges the `to_merge` value on `config` at path `at_path`
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
+
+ # builds the symbol table for the Liquid renderization of a file, based on:
+ # - `root_config`: the root of the whole config
+ # - `at_path`: the current path
+ # - `config`: the config subtree being built
+ # - `global_vars`: the global variables
+ # - `include_conf`: the $include or $content_include configuration
+ def build_symbol_table(root_config, at_path, config, global_vars, include_conf)
+ extra_vars = (include_conf['vars'] || {}).map { |k, v| [k, root_config.dig(*v.split('.'))] }.to_h
+ extra_consts = include_conf['constants'] || {}
+
+ (config ? merge_at(root_config, at_path, config) : root_config)
+ .merge(global_vars)
+ .merge(extra_vars)
+ .merge(extra_consts)
+ .merge('_this' => config)
+ end
+
+ # builds a user-friendly string indicating a path
+ def build_path(at_path)
+ at_path.empty? ? '<root>' : at_path.join('.')
end
end
end