diff --git a/docs/layouts.rst b/docs/layouts.rst index 05e1463..abd15dd 100644 --- a/docs/layouts.rst +++ b/docs/layouts.rst @@ -169,7 +169,8 @@ If you want to extend multiple parents, it looks like this:: extends: - socrates - plato - x: += 50 # evaluates to 250 from plato + x: += 50 # evaluates to 150 from socrates + # y is going to be 200 too from Plato If multiple keys override the same keys in a parent, the later ("younger") child in the ``extends`` list takes precedent. Like this:: diff --git a/lib/squib/layout_parser.rb b/lib/squib/layout_parser.rb index d0ff1c1..f5ad58b 100644 --- a/lib/squib/layout_parser.rb +++ b/lib/squib/layout_parser.rb @@ -49,25 +49,37 @@ module Squib h = {} parent_keys.each do |parent_key| from_extends = yml[key].merge(recurse_extends(yml, parent_key, visited)) do |key, child_val, parent_val| - if child_val.to_s.strip.start_with?('+=') - add_parent_child(parent_val, child_val) - elsif child_val.to_s.strip.start_with?('-=') - sub_parent_child(parent_val, child_val) - elsif child_val.to_s.strip.start_with?('*=') - mul_parent_child(parent_val, child_val) - elsif child_val.to_s.strip.start_with?('/=') - div_parent_child(parent_val, child_val) - else - child_val # child overrides parent when merging, no += - end + handle_relative_operators(parent_val, child_val) end h = h.merge(from_extends) do |key, older_sibling, younger_sibling| - younger_sibling # when two siblings have the same entry, the "younger" (lower one) overrides + # In general, go with the younger sibling. + # UNLESS that younger sibling had a relative operator, in which use the + # (already computed) relative operator applied, which lands in older_sibling + # See bug 244. + sibling = younger_sibling + %w(+= -= *= /=).each do |op| + sibling = older_sibling if younger_sibling.to_s.strip.start_with? op + end + sibling end end return h end + def handle_relative_operators(parent_val, child_val) + if child_val.to_s.strip.start_with?('+=') + add_parent_child(parent_val, child_val) + elsif child_val.to_s.strip.start_with?('-=') + sub_parent_child(parent_val, child_val) + elsif child_val.to_s.strip.start_with?('*=') + mul_parent_child(parent_val, child_val) + elsif child_val.to_s.strip.start_with?('/=') + div_parent_child(parent_val, child_val) + else + child_val # child overrides parent when merging, no += + end + end + def add_parent_child(parent, child) parent_pixels = Args::UnitConversion.parse(parent, @dpi).to_f child_pixels = Args::UnitConversion.parse(child.sub('+=', ''), @dpi).to_f diff --git a/spec/data/layouts/multi-extends-operators-complex.yml b/spec/data/layouts/multi-extends-operators-complex.yml new file mode 100644 index 0000000..59229f0 --- /dev/null +++ b/spec/data/layouts/multi-extends-operators-complex.yml @@ -0,0 +1,12 @@ +# Here's a complex test case inspired by bug 244 +socrates: + x: 100 + y: 1000 +plato: + y: 2000 +aristotle: + extends: + - socrates + - plato + x: += 0.1in # 0.1in -> 30.0, so 100 + 30 = 130.0 + y: += 18 # From Plato, 2000 + 18 diff --git a/spec/data/layouts/multi-extends-operators-same.yml b/spec/data/layouts/multi-extends-operators-same.yml new file mode 100644 index 0000000..14d948e --- /dev/null +++ b/spec/data/layouts/multi-extends-operators-same.yml @@ -0,0 +1,9 @@ +socrates: + x: 100 +plato: + x: 200 +aristotle: + extends: + - socrates + - plato + x: += 50 # evaluates to 250 from plato diff --git a/spec/data/layouts/multi-extends-operators.yml b/spec/data/layouts/multi-extends-operators.yml new file mode 100644 index 0000000..a320d94 --- /dev/null +++ b/spec/data/layouts/multi-extends-operators.yml @@ -0,0 +1,9 @@ +socrates: + x: 100 +plato: + y: 200 +aristotle: + extends: + - socrates + - plato + x: += 50 # evaluates to 150 from socrates diff --git a/spec/layout_parser_spec.rb b/spec/layout_parser_spec.rb index 158888c..d798916 100644 --- a/spec/layout_parser_spec.rb +++ b/spec/layout_parser_spec.rb @@ -92,6 +92,57 @@ describe Squib::LayoutParser do ) end + it 'applies multiple extends with relative operators' do + layout = subject.load_layout(layout_file('multi-extends-operators.yml')) + expect(layout).to eq({ + 'socrates' => { + 'x' => 100, + }, + 'plato' => { + 'y' => 200, + }, + 'aristotle' => { + 'extends' => ['socrates', 'plato'], + 'x' => 150.0, # do the += 50 on socrates + 'y' => 200, + }, + }) + end + + it 'applies multiple extends with relative operators on same key' do + layout = subject.load_layout(layout_file('multi-extends-operators-same.yml')) + expect(layout).to eq({ + 'socrates' => { + 'x' => 100, + }, + 'plato' => { + 'x' => 200, + }, + 'aristotle' => { + 'extends' => ['socrates', 'plato'], + 'x' => 250, # do the += 50 from plato, NOT socrates + }, + }) + end + + it 'applies multiple extends with relative operators with ' do + layout = subject.load_layout(layout_file('multi-extends-operators-complex.yml')) + expect(layout).to eq({ + 'socrates' => { + 'x' => 100, + 'y' => 1000, + }, + 'plato' => { + 'y' => 2000, + }, + 'aristotle' => { + 'extends' => ['socrates', 'plato'], + 'x' => 130.0, # 0.1in -> 30.0, so 100 + 30 = 130.0 + 'y' => 2018.0, # From Plato, 2000 + 18 + }, + }) + end + it 'applies multi-level extends' do layout = subject.load_layout(layout_file('multi-level-extends.yml')) expect(layout).to eq({ 'frame' => { diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 41ee38b..4f8b912 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,6 +1,6 @@ require 'simplecov' require 'coveralls' -# require 'byebug' +require 'byebug' SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([ SimpleCov::Formatter::HTMLFormatter,