Browse Source

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 11 years ago
parent
commit
f2a2ab0573
  1. 14
      README.md
  2. 59
      lib/squib/deck.rb
  3. 38
      samples/custom-layout.yml
  4. 7
      samples/use_layout.rb
  5. 6
      spec/data/easy-circular-extends.yml
  6. 9
      spec/data/hard-circular-extends.yml
  7. 9
      spec/data/multi-extends-single-entry.yml
  8. 8
      spec/data/multi-level-extends.yml
  9. 7
      spec/data/pre-extends.yml
  10. 3
      spec/data/self-circular-extends.yml
  11. 4
      spec/data/single-extends.yml
  12. 6
      spec/data/single-level-multi-extends.yml
  13. 41
      spec/deck_spec.rb

14
README.md

@ -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}

59
lib/squib/deck.rb

@ -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 ###
##################

38
samples/custom-layout.yml

@ -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

7
samples/use_layout.rb

@ -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'

6
spec/data/easy-circular-extends.yml

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

9
spec/data/hard-circular-extends.yml

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

9
spec/data/multi-extends-single-entry.yml

@ -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

8
spec/data/multi-level-extends.yml

@ -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

7
spec/data/pre-extends.yml

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

3
spec/data/self-circular-extends.yml

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

4
spec/data/single-extends.yml

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

6
spec/data/single-level-multi-extends.yml

@ -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

41
spec/deck_spec.rb

@ -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

Loading…
Cancel
Save