Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add delete directive #20

Merged
merged 7 commits into from
Jul 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
### 0.4.1 (tbd)

- New features
- `$delete` directive is now available in config files, allowing users to delete parts of the
config sub-tree ([#20](https://github.com/velocidi/frise/pull/20)).

### 0.4.0 (November 29, 2019)

- Breaking changes
Expand Down
6 changes: 5 additions & 1 deletion lib/frise/defaults_loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ module Frise
class DefaultsLoader
SYMBOLS = %w[$all $optional].freeze

def initialize(include_sym: '$include', content_include_sym: '$content_include', schema_sym: '$schema')
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)
Expand All @@ -37,6 +38,9 @@ def merge_defaults_obj(config, defaults)
else merge_defaults_obj({}, defaults)
end

elsif config == @delete_sym
config

elsif defaults_class == 'Array' && config_class == 'Array'
defaults + config

Expand Down
47 changes: 31 additions & 16 deletions lib/frise/loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,24 @@ class Loader
def initialize(include_sym: '$include',
content_include_sym: '$content_include',
schema_sym: '$schema',
delete_sym: '$delete',
pre_loaders: [],
validators: nil,
exit_on_fail: true)

@include_sym = include_sym
@content_include_sym = content_include_sym
@schema_sym = schema_sym
@delete_sym = delete_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
schema_sym: schema_sym,
delete_sym: delete_sym
)
end

Expand All @@ -39,8 +42,8 @@ def load(config_file, global_vars = {})
config = pre_loader.call(config)
end

config = process_includes(config, [], config, global_vars) if @include_sym
config = process_schemas(config, [], global_vars) if @schema_sym
config = process_includes(config, [], config, global_vars) unless @include_sym.nil?
config = process_schemas(config, [], global_vars) unless @schema_sym.nil?
config
end

Expand All @@ -65,18 +68,19 @@ def process_includes(config, at_path, root_config, global_vars, include_confs_st
# process $include directives
config, next_include_confs = extract_include(config, at_path)
include_confs = next_include_confs + include_confs_stack
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
include_conf = include_confs.first
rest_include_confs = include_confs[1..-1]
symbol_table = build_symbol_table(root_config, at_path, config, global_vars, include_conf)
included_config = Parser.parse(include_conf['file'], symbol_table)
config = @defaults_loader.merge_defaults_obj(config, included_config)
process_includes(config, at_path, merge_at(root_config, at_path, config), global_vars, rest_include_confs)
end
end
res = 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
include_conf = include_confs.first
rest_include_confs = include_confs[1..-1]
symbol_table = build_symbol_table(root_config, at_path, config, global_vars, include_conf)
included_config = Parser.parse(include_conf['file'], symbol_table)
config = @defaults_loader.merge_defaults_obj(config, included_config)
process_includes(config, at_path, merge_at(root_config, at_path, config), global_vars, rest_include_confs)
end
end
@delete_sym.nil? ? res : omit_deleted(res)
end

def process_schema_includes(schema, at_path, global_vars)
Expand Down Expand Up @@ -160,6 +164,17 @@ def merge_at(config, at_path, to_merge)
config.merge(head => merge_at(config[head], tail, to_merge))
end

# returns the config without the keys whose values are @delete_sym
def omit_deleted(config)
config.each_with_object({}) do |(k, v), new_hash|
if v.is_a?(Hash)
new_hash[k] = omit_deleted(v)
else
new_hash[k] = v unless v == @delete_sym
end
end
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
Expand All @@ -170,7 +185,7 @@ 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)
omit_deleted(config ? merge_at(root_config, at_path, config) : root_config)
.merge(global_vars)
.merge(extra_vars)
.merge(extra_consts)
Expand Down
3 changes: 3 additions & 0 deletions spec/fixtures/_defaults/loader_test13.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
bar: "str3"
baz: "str4"
other: "str5"
2 changes: 2 additions & 0 deletions spec/fixtures/_defaults/loader_test2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ description: "Description of {{ name }} ({{ _extra.a }})"
{% else %}
description: "Description of {{ name }}"
{% endif %}

other: "plain"
1 change: 1 addition & 0 deletions spec/fixtures/_schemas/loader_test2.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
id: String
name: String
description: String
other: String?
6 changes: 6 additions & 0 deletions spec/fixtures/loader_test13.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
$include:
- "{{ _file_dir }}/loader_test13_include.yml"

foo: $delete
bar: "str"
other: $delete
7 changes: 7 additions & 0 deletions spec/fixtures/loader_test13_include.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
$include:
- "{{ _file_dir }}/defaults/loader_test13.yml"

foo: "str2"
bar: $delete
baz: $delete
liquid: {% if foo %}true{% endif %}
1 change: 1 addition & 0 deletions spec/fixtures/loader_test2_all_alt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ __custom_sch: ["{{ _file_dir }}/_schemas/loader_test2.yml"]
__custom_inc: ["{{ _file_dir }}/_defaults/loader_test2.yml"]

name: "My Object"
other: __custom_del
20 changes: 20 additions & 0 deletions spec/frise/defaults_loader_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,24 @@
.to raise_error 'Cannot merge config {"$content_include"=>["str.txt"]} (String) ' \
'with default {"str"=>"abc", "int"=>4, "bool"=>true} (Hash)'
end

it 'should override defaults when value is $delete' do
conf = {
'str' => '$delete',
'int' => '$delete',
'bool' => '$delete',
'arr' => '$delete',
'obj' => { 'key1' => '$delete', 'key3' => '$delete' }
}
conf = DefaultsLoader.new.merge_defaults(conf, fixture_path('all_types.yml'))
expect(conf['str']).to eq '$delete'
expect(conf['int']).to eq '$delete'
expect(conf['bool']).to eq '$delete'
expect(conf['arr']).to eq '$delete'
expect(conf['obj']).to eq(
'key1' => '$delete',
'key2' => 'value2',
'key3' => '$delete'
)
end
end
25 changes: 20 additions & 5 deletions spec/frise/loader_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,16 @@ def validators.short_string(_, str)
expect(conf).to eq(
'id' => 'myobj',
'name' => 'My Object',
'description' => 'Description of My Object (42)'
'description' => 'Description of My Object (42)',
'other' => 'plain'
)
end

it 'should allow using a different key or no key for inclusions and schemas' do
loader = Loader.new(include_sym: '__custom_inc', schema_sym: '__custom_sch', exit_on_fail: false)
it 'should allow using a different key or no key for inclusions/schemas/deletes' do
loader = Loader.new(include_sym: '__custom_inc',
schema_sym: '__custom_sch',
delete_sym: '__custom_del',
exit_on_fail: false)

conf = loader.load(fixture_path('loader_test2_all_alt.yml'), '_id' => 'myobj')
expect(conf).to eq(
Expand Down Expand Up @@ -158,7 +162,8 @@ def validators.short_string(_, str)
expect(conf).to eq(
'id' => 'myobj',
'name' => 'My Object',
'description' => 'Description of My Object'
'description' => 'Description of My Object',
'other' => 'plain'
)

conf = loader.load(fixture_path('loader_test2_templated.yml'), '_with_schema' => true)
Expand All @@ -171,7 +176,8 @@ def validators.short_string(_, str)
expect(conf).to eq(
'id' => 'myobj',
'name' => 'My Object',
'description' => 'Description of My Object'
'description' => 'Description of My Object',
'other' => 'plain'
)
end

Expand Down Expand Up @@ -257,4 +263,13 @@ def validators.short_string(_, str)
" - At variable1.value2: missing required value\n"
).to_stdout.and raise_error(SystemExit)
end

it 'should delete sub-tree when value is $delete' do
loader = Loader.new(exit_on_fail: false)

conf = loader.load(fixture_path('loader_test13.yml'))
expect(conf).to eq(
'bar' => 'str'
)
end
end