Browse Source

Ugh, Yaml has a <<: merge key. facepalm: true

dev
Andy Meneely 11 years ago
parent
commit
00e8b59144
  1. 16
      README.md
  2. 53
      lib/squib/deck.rb
  3. 13
      samples/custom-layout.yml
  4. 6
      spec/data/easy-circular-extends.yml
  5. 9
      spec/data/hard-circular-extends.yml
  6. 9
      spec/data/multi-extends-single-entry.yml
  7. 10
      spec/data/multi-level-extends.yml
  8. 7
      spec/data/pre-extends.yml
  9. 3
      spec/data/self-circular-extends.yml
  10. 4
      spec/data/single-extends.yml
  11. 6
      spec/data/single-level-multi-extends.yml
  12. 41
      spec/deck_spec.rb

16
README.md

@ -163,7 +163,7 @@ Working with x-y coordinates all the time can be tiresome, and ideally everythin
To use a layout, set the `layout:` option on a `Deck.new` command to point to a YAML file. Any command that allows a `layout` option can be set with a Ruby symbol or String, and the command will then load the specified `x`, `y`, `width`, and `height`. The individual command can also override these options.
Note: YAML is very finnicky about having tabs in the file. Use two spaces for indentation instead.
Note: YAML is very finnicky about having not allowing tabs. Use two spaces for indentation instead. If you get a `Psych` syntax error, this is likely the culprit. Indendation is also strongly enforced in Yaml too. See the [Yaml docs](http://www.yaml.org/YAML_for_ruby.html).
Layouts will override Squib's defaults, but are overriden by anything specified in the command itself. Thus, the order of precedence looks like this:
@ -171,7 +171,17 @@ Layouts will override Squib's defaults, but are overriden by anything specified
* If anything was not yet specified, use what was given in a layout (if a layout was specified in the command and the file was given to the Deck)
* If still anything was not yet specified, use what was given in Squib's defaults.
Layouts also allow for a special `extends` field that will copy all of the settings from another entry. Only a single level of extends are supported currently (contact the developer if you want multiple levels).
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
icon: &icon
width: 50
height: 50
icon_left
<<: *icon
x: 100
# The layout for icon_left will have the width/height from icon!
```
See the `use_layout` sample found [here](https://github.com/andymeneely/squib/tree/master/samples/)
@ -196,7 +206,7 @@ See the `custom_config` sample found [here](https://github.com/andymeneely/squib
Squib tries to keep you DRY with the following features:
* Custom layouts allow you to specify various arguments in a separate file. This is great for x-y coordinates and alignment properties that would otherwise clutter up perfectly readable code. The `extends` takes this a step further and lets you specify base styles that can then be extended by other styles.
* Custom layouts allow you to specify various arguments in a separate file. This is great for x-y coordinates and alignment properties that would otherwise clutter up perfectly readable code. Yaml's "merge keys" takes this a step further and lets you specify base styles that can then be extended by other styles.
* Flexible ranges and array handling: the `range` parameter in Squib is very flexible, meaning that one `text` command can specify different text in different fonts, styles, colors, etc. for each card. If you find yourself doing multiple `text` command for the same field across different ranges of cards, there's probably a better way to condense.
* Custom colors keep you from hardcoding magic color strings everywhere. Custom colors are entered into the `config.yml` file.

53
lib/squib/deck.rb

@ -49,7 +49,8 @@ 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 file used for global settings of this deck
# @param config: [String] the Yaml file used for global settings of this deck
# @param layout: [String] the Yaml file used for layouts.
# @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)
@ -61,7 +62,7 @@ module Squib
@progress_bar = Squib::Progress.new(false)
cards.times{ @cards << Squib::Card.new(self, width, height) }
load_config(config)
load_layout(layout)
@layout = YAML.load_file(layout) unless layout.nil?
if block_given?
instance_eval(&block)
end
@ -98,54 +99,6 @@ 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|
child_val #child overrides parent when merging
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 ###
##################

13
samples/custom-layout.yml

@ -18,17 +18,22 @@ subtitle:
height: 60
align: !ruby/symbol center
valign: !ruby/symbol middle
icon:
# 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
width: 125
height: 125
y: 250
icon_left:
extends: icon
<<: *icon
x: 150
icon_middle:
extends: icon
<<: *icon
x: 350
y: 400 #overrides the y inherited from icon
icon_right:
extends: icon
<<: *icon
x: 550

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

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

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

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

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

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

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

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

7
spec/data/pre-extends.yml

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

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

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

4
spec/data/single-extends.yml

@ -1,7 +1,7 @@
frame:
frame: &frame
x: 38
y: 38
title:
extends: frame
<<: *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:
extends: frame
<<: *frame
y: 50
width: 100
title2:
extends: frame
<<: *frame
x: 75
y: 150
width: 150

41
spec/deck_spec.rb

@ -68,24 +68,6 @@ 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,
@ -102,13 +84,11 @@ describe Squib::Deck do
'y' => 38,
},
'title' => {
'extends' => 'frame',
'x' => 38,
'y' => 50,
'width' => 100,
},
'title2' => {
'extends' => 'frame',
'x' => 75,
'y' => 150,
'width' => 150,
@ -131,7 +111,6 @@ describe Squib::Deck do
'b' => 106,
},
'child' => {
'extends' => ['uncle','aunt'],
'a' => 107, # my own
'b' => 102, # from the younger aunt
'c' => 103, # from aunt
@ -150,13 +129,11 @@ describe Squib::Deck do
'y' => 38,
},
'title' => {
'extends' => 'frame',
'x' => 38,
'y' => 50,
'width' => 100,
},
'subtitle' => {
'extends' => 'title',
'x' => 38,
'y' => 150,
'width' => 100,
@ -165,24 +142,6 @@ 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