From 1371bd81bd5bb25fa357ce0089f5ccf58fb74e3b Mon Sep 17 00:00:00 2001 From: Andy Meneely Date: Thu, 14 Aug 2014 16:05:12 -0400 Subject: [PATCH] Implementing multi-level extends, except for one case --- lib/squib/deck.rb | 38 +++++++++++--- spec/data/multi-extends-single-entry.yml | 14 +++++ spec/data/multi-level-extends.yml | 2 +- spec/deck_spec.rb | 65 +++++++++++++++++++++++- 4 files changed, 109 insertions(+), 10 deletions(-) create mode 100644 spec/data/multi-extends-single-entry.yml diff --git a/lib/squib/deck.rb b/lib/squib/deck.rb index 3d5b806..541affa 100644 --- a/lib/squib/deck.rb +++ b/lib/squib/deck.rb @@ -1,5 +1,6 @@ require 'yaml' require 'pp' +require 'squib' require 'squib/card' require 'squib/progress' require 'squib/input_helpers' @@ -101,14 +102,37 @@ module Squib # @api private def load_layout(file) return if file.nil? - prelayout = YAML.load_file(file) @layout = {} - prelayout.each do |key, value| - if value.key? "extends" - @layout[key] = prelayout[value["extends"]].merge prelayout[key] - else - @layout[key] = value - end + yml = YAML.load_file(file) + yml.each do |key, value| + @layout[key] = recurse_extends(yml, key, {}) + end + end + + # Process the extends + # :nodoc: + # @api private + def recurse_extends(yml, key, visited ) + assert_not_visited(key, visited) + return yml[key] unless has_extends?(yml, key) + visited[key] = key + parent_key = yml[key]['extends'] + return yml[key].merge(recurse_extends(yml, parent_key, visited)) do |key, child_val, parent_val| + child_val #child overrides parent when merging + end + end + + # Does this layout entry have an extends field? + # i.e. is it a base-case or will it need recursion? + # :nodoc: + # @api private + def has_extends?(yml, key) + yml[key].key?('extends') + end + + def assert_not_visited(key, visited) + if visited.key? key + raise "Invalid layout: circular extends with '#{key}'" end end diff --git a/spec/data/multi-extends-single-entry.yml b/spec/data/multi-extends-single-entry.yml new file mode 100644 index 0000000..fe90c6f --- /dev/null +++ b/spec/data/multi-extends-single-entry.yml @@ -0,0 +1,14 @@ +base: + a: 101 + b: 102 + c: 103 +frame: + x: 104 + y: 105 + b: 106 +title: + extends: + - base + - frame + a: 107 + x: 108 diff --git a/spec/data/multi-level-extends.yml b/spec/data/multi-level-extends.yml index ef5996d..9f8867e 100644 --- a/spec/data/multi-level-extends.yml +++ b/spec/data/multi-level-extends.yml @@ -6,5 +6,5 @@ title: y: 50 width: 100 subtitle: - extends: subtitle + extends: title y: 150 \ No newline at end of file diff --git a/spec/deck_spec.rb b/spec/deck_spec.rb index fee9644..ab28f83 100644 --- a/spec/deck_spec.rb +++ b/spec/deck_spec.rb @@ -94,7 +94,7 @@ describe Squib::Deck do ) end - it "applies the single-level extends multiple timess" do + it "applies the single-level extends multiple times" do d = Squib::Deck.new(layout: test_file('single-level-multi-extends.yml')) expect(d.layout).to \ eq({'frame' => { @@ -117,8 +117,69 @@ describe Squib::Deck do ) end - + # it "applies multiple extends in a single rule" do + # d = Squib::Deck.new(layout: test_file('multi-extends-single-entry.yml')) + # expect(d.layout).to \ + # eq({'base' => { + # 'x' => 38, + # 'y' => 38, + # }, + # 'frame' => { + # 'extends' => 'frame', + # 'x' => 38, + # 'y' => 50, + # 'width' => 100, + # }, + # 'title' => { + # 'extends' => 'frame', + # 'x' => 75, + # 'y' => 150, + # 'width' => 150, + # }, + # } + # ) + # end + it "applies multi-level extends" do + d = Squib::Deck.new(layout: test_file('multi-level-extends.yml')) + expect(d.layout).to \ + eq({'frame' => { + 'x' => 38, + 'y' => 38, + }, + 'title' => { + 'extends' => 'frame', + 'x' => 38, + 'y' => 50, + 'width' => 100, + }, + 'subtitle' => { + 'extends' => 'title', + 'x' => 38, + 'y' => 150, + 'width' => 100, + }, + } + ) + end + + it "fails on a self-circular extends" do + file = test_file('self-circular-extends.yml') + expect { Squib::Deck.new(layout: file) }.to \ + raise_error(RuntimeError, "Invalid layout: circular extends with 'a'") + end + + it "fails on a easy-circular extends" do + file = test_file('easy-circular-extends.yml') + expect { Squib::Deck.new(layout: file) }.to \ + raise_error(RuntimeError, "Invalid layout: circular extends with 'a'") + end + + it "hard on a easy-circular extends" do + file = test_file('hard-circular-extends.yml') + expect { Squib::Deck.new(layout: file) }.to \ + raise_error(RuntimeError, "Invalid layout: circular extends with 'a'") + end end