Bringing back the extends feature

Adding in my own merge key implementation that allows you to bring in
other keys and modfiy the data. Makes moving boxes around easier.

A quasi-revert of 00e8b59144
...but with something useful!
dev
Andy Meneely 2014-09-21 21:56:18 -04:00
parent b5ff2890d0
commit f2a2ab0573
13 changed files with 186 additions and 29 deletions

View File

@ -173,7 +173,7 @@ Layouts will override Squib's defaults, but are overriden by anything specified
Since Layouts are in Yaml, we have the full power of that data format. One particular feature you should look into are ["merge keys"](http://www.yaml.org/YAML_for_ruby.html#merge_key). With merge keys, you can define base styles in one entry, then include those keys elsewhere. For example:
```sh
```yaml
icon: &icon
width: 50
height: 50
@ -183,6 +183,18 @@ icon_left
# The layout for icon_left will have the width/height from icon!
```
Also!! Squib provides a more feature-rich way of merging: the `extends` key in layouts. When defining an extends key, we can merge in another key and modify data coming in if we want to. This allows us to do things like set an inner object that changes its location based on its parent.
```yaml
yin:
x: 100
y: 100
radius: 100
yang:
extends: yin
x: += 50
```
See the `use_layout` sample found [here](https://github.com/andymeneely/squib/tree/master/samples/)
{include:file:samples/use_layout.rb}

View File

@ -49,8 +49,7 @@ module Squib
# @param height: [Integer] the height of each card in pixels
# @param cards: [Integer] the number of cards in the deck
# @param dpi: [Integer] the pixels per inch when rendering out to PDF or calculating using inches.
# @param config: [String] the Yaml file used for global settings of this deck
# @param layout: [String] the Yaml file used for layouts.
# @param config: [String] the file used for global settings of this deck
# @param block [Block] the main body of the script.
# @api public
def initialize(width: 825, height: 1125, cards: 1, dpi: 300, config: 'config.yml', layout: nil, &block)
@ -63,7 +62,7 @@ module Squib
@progress_bar = Squib::Progress.new(false)
cards.times{ @cards << Squib::Card.new(self, width, height) }
load_config(config)
@layout = YAML.load_file(layout) unless layout.nil?
load_layout(layout)
if block_given?
instance_eval(&block)
end
@ -101,6 +100,60 @@ module Squib
end
end
# Load the layout configuration file, if exists
# @api private
def load_layout(file)
return if file.nil?
@layout = {}
yml = YAML.load_file(file)
yml.each do |key, value|
@layout[key] = recurse_extends(yml, key, {})
end
end
# Process the extends recursively
# :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_keys = [yml[key]['extends']].flatten
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?('+=')
parent_val + child_val.sub("+=",'').strip.to_f
elsif child_val.to_s.strip.start_with?('-=')
parent_val - child_val.sub("-=",'').strip.to_f
else
child_val #child overrides parent when merging, no +=
end
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
end
end
return h
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
# Safeguard against malformed circular extends
# :nodoc:
# @api private
def assert_not_visited(key, visited)
if visited.key? key
raise "Invalid layout: circular extends with '#{key}'"
end
end
##################
### PUBLIC API ###
##################

View File

@ -18,22 +18,42 @@ subtitle:
height: 60
align: !ruby/symbol center
valign: !ruby/symbol middle
# Yaml has this beautfiul feature for us: merge keys
# http://www.yaml.org/YAML_for_ruby.html#merge_key
# Define an alias with &foo, then use <<: *foo to include it
# Everything gets merged, with later merges overriding
icon: &icon
icon:
width: 125
height: 125
y: 250
icon_left:
<<: *icon
extends: icon
x: 150
icon_middle:
<<: *icon
extends: icon
x: 350
y: 400 #overrides the y inherited from icon
icon_right:
<<: *icon
extends: icon
x: 550
# Squib also supports its own merging-and-modify feature
# Called "extends"
# Any layout can extend another layout, so long as it's not a circle
# Order doesn't matter since it's done after YAML procesing
# And, if the entry overrides
bonus: #becomes our bonus rectangle
x: 250
y: 600
width: 300
height: 200
radius: 32
bonus_inner:
extends: bonus
x: += 10 # i.e. 260
y: += 10 # i.e. 610
width: -= 20 # i.e. 180
height: -= 20 # i.e. 180
radius: -= 8
bonus_text:
extends: bonus_inner
x: +=10
y: +=10
width: -= 20
height: -= 20

View File

@ -13,11 +13,16 @@ Squib::Deck.new(layout: 'custom-layout.yml') do
# Lots of commands have the :layout option
text str: 'The Title', layout: :title
# Layouts also support an "extends" attribute to reuse settings
# Layouts also support YAML merge keys toreuse settings
svg file: 'spanner.svg', layout: :icon_left
png file: 'shiny-purse.png', layout: :icon_middle
svg file: 'spanner.svg', layout: :icon_right
# Squib has its own, richer merge key: "extends"
rect fill_color: :black, layout: :bonus
rect fill_color: :white, layout: :bonus_inner
text str: 'Extends!', layout: :bonus_text
# Strings can also be used to specify a layout (e.g. from a data file)
text str: 'subtitle', layout: 'subtitle'

View File

@ -0,0 +1,6 @@
a:
extends: b
x: 50
b:
extends: a
x: 150

View File

@ -0,0 +1,9 @@
a:
extends: c
x: 50
b:
extends: a
x: 150
c:
extends: b
y: 250

View File

@ -1,13 +1,14 @@
aunt: &aunt
aunt:
a: 101
b: 102
c: 103
uncle: &uncle
uncle:
x: 104
y: 105
b: 106
child:
<<: *uncle
<<: *aunt
extends:
- uncle
- aunt
a: 107
x: 108

View File

@ -1,10 +1,10 @@
frame: &frame
frame:
x: 38
y: 38
title: &title
<<: *frame
title:
extends: frame
y: 50
width: 100
subtitle:
<<: *title
extends: title
y: 150

View File

@ -0,0 +1,7 @@
title:
extends: frame
y: 50
width: 100
frame:
x: 38
y: 38

View File

@ -0,0 +1,3 @@
a:
extends: a
x: 50

View File

@ -1,7 +1,7 @@
frame: &frame
frame:
x: 38
y: 38
title:
<<: *frame
extends: frame
y: 50
width: 100

View File

@ -1,12 +1,12 @@
frame: &frame
frame:
x: 38
y: 38
title:
<<: *frame
extends: frame
y: 50
width: 100
title2:
<<: *frame
extends: frame
x: 75
y: 150
width: 150

View File

@ -68,6 +68,24 @@ describe Squib::Deck do
'y' => 38,
},
'title' => {
'extends' => 'frame',
'x' => 38,
'y' => 50,
'width' => 100,
}
}
)
end
it "applies the extends regardless of order" do
d = Squib::Deck.new(layout: test_file('pre-extends.yml'))
expect(d.layout).to \
eq({'frame' => {
'x' => 38,
'y' => 38,
},
'title' => {
'extends' => 'frame',
'x' => 38,
'y' => 50,
'width' => 100,
@ -84,11 +102,13 @@ describe Squib::Deck do
'y' => 38,
},
'title' => {
'extends' => 'frame',
'x' => 38,
'y' => 50,
'width' => 100,
},
'title2' => {
'extends' => 'frame',
'x' => 75,
'y' => 150,
'width' => 150,
@ -111,6 +131,7 @@ describe Squib::Deck do
'b' => 106,
},
'child' => {
'extends' => ['uncle','aunt'],
'a' => 107, # my own
'b' => 102, # from the younger aunt
'c' => 103, # from aunt
@ -129,11 +150,13 @@ describe Squib::Deck do
'y' => 38,
},
'title' => {
'extends' => 'frame',
'x' => 38,
'y' => 50,
'width' => 100,
},
'subtitle' => {
'extends' => 'title',
'x' => 38,
'y' => 150,
'width' => 100,
@ -142,6 +165,24 @@ describe Squib::Deck do
)
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
end