Browse Source

shorthands for xywh! initial implementation

dev
Andy Meneely 6 years ago
parent
commit
2969957c5b
  1. 18
      lib/squib/args/box.rb
  2. 14
      lib/squib/args/coords.rb
  3. 7
      lib/squib/args/scale_box.rb
  4. 44
      lib/squib/args/xywh_shorthands.rb
  5. 10
      lib/squib/layout_parser.rb
  6. 13
      samples/shorthands/_shorthands.rb
  7. BIN
      samples/shorthands/shorthand_00_expected.png
  8. 79
      spec/args/box_spec.rb
  9. 23
      spec/args/scale_box_spec.rb

18
lib/squib/args/box.rb

@ -4,17 +4,16 @@ require_relative 'xywh_shorthands'
module Squib::Args module Squib::Args
module_function def extract_box(opts, deck, dsl_method_defaults = {}) module_function def extract_box(opts, deck, dsl_method_defaults = {})
Box.new(deck, dsl_method_defaults, opts).extract!(opts, deck) Box.new(deck, dsl_method_defaults).extract!(opts, deck)
end end
class Box class Box
include ArgLoader include ArgLoader
include XYWHShorthands include XYWHShorthands
def initialize(deck = nil, dsl_method_defaults = {}, opts = {}) def initialize(deck = nil, dsl_method_defaults = {})
@deck = deck @deck = deck
@dsl_method_defaults = dsl_method_defaults @dsl_method_defaults = dsl_method_defaults
@opts = opts # e.g. value of x can depend on the value of width
end end
def self.parameters def self.parameters
@ -32,22 +31,17 @@ module Squib::Args
parameters.keys # all of them parameters.keys # all of them
end end
def validate_x(arg, i) def validate_x(arg, i) apply_shorthands(arg, @deck, axis: :x) end
apply_x_shorthands(arg, @deck.width) def validate_y(arg,_i) apply_shorthands(arg, @deck, axis: :y) end
end
def validate_y(arg,_i)
apply_y_shorthands(arg, @deck.height)
end
def validate_width(arg, _i) def validate_width(arg, _i)
return arg if @deck.nil? return arg if @deck.nil?
apply_x_shorthands(arg, @deck.width) apply_shorthands(arg, @deck, axis: :x)
end end
def validate_height(arg, _i) def validate_height(arg, _i)
return arg if @deck.nil? return arg if @deck.nil?
apply_y_shorthands(arg, @deck.height) apply_shorthands(arg, @deck, axis: :y)
end end
def validate_x_radius(arg, i) def validate_x_radius(arg, i)

14
lib/squib/args/coords.rb

@ -1,4 +1,5 @@
require_relative 'arg_loader' require_relative 'arg_loader'
require_relative 'xywh_shorthands'
module Squib::Args module Squib::Args
module_function def extract_coords(opts, deck) module_function def extract_coords(opts, deck)
@ -7,6 +8,7 @@ module Squib::Args
class Coords class Coords
include ArgLoader include ArgLoader
include XYWHShorthands
def self.parameters def self.parameters
{ x: 0, y: 0, { x: 0, y: 0,
@ -30,6 +32,18 @@ module Squib::Args
parameters.keys # all of them parameters.keys # all of them
end end
def validate_x(arg, i) apply_shorthands(arg, @deck, axis: :x) end
def validate_y(arg,_i) apply_shorthands(arg, @deck, axis: :y) end
def validate_x1(arg, i) apply_shorthands(arg, @deck, axis: :x) end
def validate_y1(arg,_i) apply_shorthands(arg, @deck, axis: :y) end
def validate_x2(arg, i) apply_shorthands(arg, @deck, axis: :x) end
def validate_y2(arg,_i) apply_shorthands(arg, @deck, axis: :y)end
def validate_x3(arg, i) apply_shorthands(arg, @deck, axis: :x) end
def validate_y3(arg,_i) apply_shorthands(arg, @deck, axis: :y) end
def validate_cx1(arg, i) apply_shorthands(arg, @deck, axis: :x) end
def validate_cy1(arg,_i) apply_shorthands(arg, @deck, axis: :y) end
def validate_cx2(arg, i) apply_shorthands(arg, @deck, axis: :x) end
def validate_cy2(arg,_i) apply_shorthands(arg, @deck, axis: :y) end
end end
end end

7
lib/squib/args/scale_box.rb

@ -1,4 +1,5 @@
require_relative 'arg_loader' require_relative 'arg_loader'
require_relative 'xywh_shorthands'
module Squib::Args module Squib::Args
module_function def extract_scale_box(opts, deck) module_function def extract_scale_box(opts, deck)
@ -7,6 +8,7 @@ module Squib::Args
class ScaleBox class ScaleBox
include ArgLoader include ArgLoader
include XYWHShorthands
def self.parameters def self.parameters
{ {
@ -23,9 +25,13 @@ module Squib::Args
parameters.keys # all of them parameters.keys # all of them
end end
def validate_x(arg, i) apply_shorthands(arg, @deck, axis: :x) end
def validate_y(arg,_i) apply_shorthands(arg, @deck, axis: :y) end
def validate_width(arg, i) def validate_width(arg, i)
return @deck.width if arg.to_s == 'deck' return @deck.width if arg.to_s == 'deck'
return :native if arg.to_s == 'native' return :native if arg.to_s == 'native'
arg = apply_shorthands(arg, @deck, axis: :x)
return arg if arg.respond_to? :to_f return arg if arg.respond_to? :to_f
if arg.to_s == 'scale' if arg.to_s == 'scale'
raise 'if width is :scale, height must be a number' unless height[i].respond_to? :to_f raise 'if width is :scale, height must be a number' unless height[i].respond_to? :to_f
@ -37,6 +43,7 @@ module Squib::Args
def validate_height(arg, i) def validate_height(arg, i)
return @deck.height if arg.to_s == 'deck' return @deck.height if arg.to_s == 'deck'
return :native if arg.to_s == 'native' return :native if arg.to_s == 'native'
arg = apply_shorthands(arg, @deck, axis: :y)
return arg if arg.respond_to? :to_f return arg if arg.respond_to? :to_f
if arg.to_s == 'scale' if arg.to_s == 'scale'
raise 'if height is \'scale\', width must be a number' unless width[i].respond_to? :to_f raise 'if height is \'scale\', width must be a number' unless width[i].respond_to? :to_f

44
lib/squib/args/xywh_shorthands.rb

@ -1,30 +1,38 @@
require_relative 'unit_conversion'
module Squib module Squib
module Args module Args
module XYWHShorthands module XYWHShorthands
WIDTH_MINUS_REGEX = /^width\s*\-\s*/
HEIGHT_MINUS_REGEX = /^height\s*\-\s*/
WIDTH_DIV_REGEX = /^width\s*\/\s*/
HEIGHT_DIV_REGEX = /^height\s*\/\s*/
def apply_x_shorthands(arg, deck_width) # dimension is usually either deck_width or deck_height
arg_s = arg.to_s def apply_shorthands(arg, deck, axis: :x)
case arg_s dimension = (axis == :x) ? deck.width : deck.height
when 'middle'
deck_width / 2.0
when 'center'
deck_width / 2.0
when 'deck'
deck_width
else
arg
end
end
def apply_y_shorthands(arg, deck_height)
arg_s = arg.to_s arg_s = arg.to_s
case arg_s case arg_s
when 'middle' when 'middle'
deck_height / 2.0 dimension / 2.0
when 'center' when 'center'
deck_height / 2.0 dimension / 2.0
when 'deck' when 'deck'
deck_height dimension
when WIDTH_MINUS_REGEX # e.g. width - 1.5in
n = arg_s.sub WIDTH_MINUS_REGEX, ''
n = UnitConversion.parse(n)
deck.width - n
when HEIGHT_MINUS_REGEX # e.g. height - 1.5in
n = arg_s.sub HEIGHT_MINUS_REGEX, ''
n = UnitConversion.parse(n)
deck.height - n
when WIDTH_DIV_REGEX # e.g. width / 3
n = (arg_s.sub WIDTH_DIV_REGEX, '').to_f
deck.width / n
when HEIGHT_DIV_REGEX # e.g. height / 3
n = (arg_s.sub HEIGHT_DIV_REGEX, '').to_f
deck.height / n
else else
arg arg
end end

10
lib/squib/layout_parser.rb

@ -69,7 +69,7 @@ module Squib
end end
def handle_relative_operators(parent_val, child_val) def handle_relative_operators(parent_val, child_val)
unless has_digits?(parent_val) && has_digits?(child_val) if uses_operator?(child_val) && !has_digits?(parent_val) && !has_digits?(child_val)
raise "Layout parse error: can't combine #{parent_val} and #{child_val}" raise "Layout parse error: can't combine #{parent_val} and #{child_val}"
end end
if child_val.to_s.strip.start_with?('+=') if child_val.to_s.strip.start_with?('+=')
@ -112,8 +112,12 @@ module Squib
# For relative operators, it's difficult for us to handle # For relative operators, it's difficult for us to handle
# some of the shorthands - so let's just freak out if you're trying to use # some of the shorthands - so let's just freak out if you're trying to use
# relative operators with words, e.g. "middle += 0.5in" # relative operators with words, e.g. "middle += 0.5in"
def has_digits?(str) def has_digits?(arg)
str.match? /.*\d.*/ /.*\d.*/ === arg.to_s
end
def uses_operator?(arg)
/^[+=|\-=|\/=|*=].*/ === arg.to_s
end end
# Does this layout entry have an extends field? # Does this layout entry have an extends field?

13
samples/shorthands/_shorthands.rb

@ -12,10 +12,21 @@ Squib::Deck.new(width: '0.5in', height: '0.25in') do
rect width: 30, height: 30, rect width: 30, height: 30,
x: :center, y: 'center' x: :center, y: 'center'
# Applies to shapes
triangle x1: 20, y1: 20,
x2: 60, y2: 20,
x3: :middle, y3: :middle
# We can also do width-, height-, width/, height/
rect x: 20, y: 5, stroke_color: :green,
width: 'width - 0.1in', height: 10
rect x: 10, y: 50, width: 10, height: 'height / 3',
stroke_color: :purple
# Layouts apply this too. # Layouts apply this too.
use_layout file: 'shorthands.yml' use_layout file: 'shorthands.yml'
rect layout: :example rect layout: :example
# The x and y coordinates can also be "centered", assuming the # The x and y coordinates can also be "centered", assuming the
# HOWEVER! Shorthands don't combine in an "extends" situation, # HOWEVER! Shorthands don't combine in an "extends" situation,

BIN
samples/shorthands/shorthand_00_expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

79
spec/args/box_spec.rb

@ -2,38 +2,44 @@ require 'spec_helper'
require 'squib/args/box' require 'squib/args/box'
describe Squib::Args::Box do describe Squib::Args::Box do
subject(:box) { Squib::Args::Box.new } let(:deck) { OpenStruct.new(width: 123, height: 456, size: 1, dpi: 300.0) }
let(:expected_defaults) { { x: [0], y: [0], width: [:deck], height: [:deck] } } let(:expected_defaults) { { x: [0], y: [0], width: [123], height: [456] } }
it 'intitially has no params set' do it 'intitially has no params set' do
box = Squib::Args::Box.new
expect(box).not_to respond_to(:x, :y, :width, :height) expect(box).not_to respond_to(:x, :y, :width, :height)
end end
it 'extracts the defaults from Box on an empty hash' do it 'extracts the defaults from Box on an empty hash' do
box.load!({}) args = {}
box = Squib::Args.extract_box args, deck
expect(box).to have_attributes(expected_defaults) expect(box).to have_attributes(expected_defaults)
end end
it 'extracts what is specified and fills in defaults from Box' do it 'extracts what is specified and fills in defaults from Box' do
box.load!(x: 4, width: 40) args = {x: 4, width: 40}
expect(box).to have_attributes(x: [4], width: [40], y: [0], height: [:deck]) box = Squib::Args.extract_box args, deck
expect(box).to have_attributes(x: [4], width: [40], y: [0], height: [456])
end end
it 'extracts the defaults from Box on an empty hash' do it 'extracts the defaults from Box on an empty hash' do
box.load!({ foo: :bar }) args = { foo: :bar }
box = Squib::Args.extract_box args, deck
expect(box).to have_attributes(expected_defaults) expect(box).to have_attributes(expected_defaults)
expect(box).not_to respond_to(:foo) expect(box).not_to respond_to(:foo)
end end
context 'single expansion' do context 'single expansion' do
let(:args) { { x: [1, 2], y: 3 } } let(:args) { { x: [1, 2], y: 3 } }
before(:each) { box.load!(args, expand_by: 2) } let(:deck_of_2) { OpenStruct.new(width: 123, height: 456, size: 2) }
let(:box) { Squib::Args.extract_box args, deck_of_2 }
it 'expands box' do it 'expands box' do
expect(box).to have_attributes({ expect(box).to have_attributes({
x: [1, 2], x: [1, 2],
y: [3, 3], y: [3, 3],
height: [:deck, :deck], width: [123, 123],
width: [:deck, :deck] height: [456, 456],
}) })
end end
@ -41,21 +47,23 @@ describe Squib::Args::Box do
expect(box[0]).to have_attributes({ expect(box[0]).to have_attributes({
x: 1, x: 1,
y: 3, y: 3,
height: :deck, width: 123,
width: :deck height: 456,
}) })
end end
end end
context 'layouts' do context 'layouts' do
let(:layout) do let(:deck_of_2) do
{ 'attack' => { 'x' => 50 }, OpenStruct.new(width: 123, height: 456, size: 2, layout: {
'defend' => { 'x' => 60 } } 'attack' => { 'x' => 50 },
'defend' => { 'x' => 60 },
})
end end
it 'are used when not specified' do it 'are used when not specified' do
args = { layout: ['attack', 'defend'] } args = { layout: ['attack', 'defend'] }
box.load!(args, expand_by: 2, layout: layout) box = Squib::Args.extract_box args, deck_of_2
expect(box).to have_attributes( expect(box).to have_attributes(
x: [50, 60], # set by layout x: [50, 60], # set by layout
y: [0, 0], # Box default y: [0, 0], # Box default
@ -64,7 +72,7 @@ describe Squib::Args::Box do
it 'handle single expansion' do it 'handle single expansion' do
args = { layout: 'attack' } args = { layout: 'attack' }
box.load!(args, expand_by: 2, layout: layout) box = Squib::Args.extract_box args, deck_of_2
expect(box).to have_attributes( expect(box).to have_attributes(
x: [50, 50], # set by layout x: [50, 50], # set by layout
y: [0, 0], # Box default y: [0, 0], # Box default
@ -73,7 +81,7 @@ describe Squib::Args::Box do
it 'handles symbols' do it 'handles symbols' do
args = { layout: :attack } args = { layout: :attack }
box.load!(args, expand_by: 2, layout: layout) box = Squib::Args.extract_box args, deck_of_2
expect(box).to have_attributes( expect(box).to have_attributes(
x: [50, 50], # set by layout x: [50, 50], # set by layout
y: [0, 0], # Box default y: [0, 0], # Box default
@ -83,7 +91,7 @@ describe Squib::Args::Box do
it 'warns on non-existent layouts' do it 'warns on non-existent layouts' do
args = { layout: :heal } args = { layout: :heal }
expect(Squib.logger).to receive(:warn).with('Layout "heal" does not exist in layout file - using default instead').at_least(:once) expect(Squib.logger).to receive(:warn).with('Layout "heal" does not exist in layout file - using default instead').at_least(:once)
box.load!(args, expand_by: 2, layout: layout) box = Squib::Args.extract_box args, deck_of_2
expect(box).to have_attributes( expect(box).to have_attributes(
x: [0, 0], # Box default x: [0, 0], # Box default
y: [0, 0], # Box default y: [0, 0], # Box default
@ -92,49 +100,58 @@ describe Squib::Args::Box do
end end
context 'unit conversion' do context 'unit conversion' do
let(:deck_of_2) { OpenStruct.new(width: 123, height: 456, size: 2, dpi: 300) }
it 'converts units on all args' do it 'converts units on all args' do
args = { x: ['1in', '2in'], y: 300, width: '1in', height: '1in' } args = { x: ['1in', '2in'], y: 300, width: '1in', height: '1in' }
box.load!(args, expand_by: 2) box = Squib::Args.extract_box args, deck_of_2
expect(box).to have_attributes( expect(box).to have_attributes(
x: [300, 600], x: [300.0, 600.0],
y: [300, 300], y: [300, 300],
width: [300, 300], width: [300.0, 300.0],
height: [300, 300], height: [300.0, 300.0],
) )
end end
end end
context 'validation' do context 'validation' do
let(:deck) { OpenStruct.new(width: 123, height: 456, size: 1) }
it 'replaces with deck width and height' do it 'replaces with deck width and height' do
args = { width: :deck, height: :deck } args = { width: :deck, height: :deck }
box = Squib::Args::Box.new box = Squib::Args.extract_box args, deck
box.extract! args, deck
expect(box).to have_attributes(width: [123], height: [456]) expect(box).to have_attributes(width: [123], height: [456])
end end
it 'has radius override x_radius and y_radius' do it 'has radius override x_radius and y_radius' do
args = { x_radius: 1, y_radius: 2, radius: 3 } args = { x_radius: 1, y_radius: 2, radius: 3 }
box.extract! args, deck box = Squib::Args.extract_box args, deck
expect(box).to have_attributes(x_radius: [3], y_radius: [3]) expect(box).to have_attributes(x_radius: [3], y_radius: [3])
end end
it 'listens to middle' do it 'listens to middle' do
args = { width: :middle, height: 'middle' } args = { width: :middle, height: 'middle' }
box = Squib::Args::Box.new box = Squib::Args.extract_box args, deck
box.extract! args, deck
expect(box).to have_attributes(width: [61.5], height: [228.0]) expect(box).to have_attributes(width: [61.5], height: [228.0])
end end
it 'listens to center' do it 'listens to center' do
args = { width: 'center', height: :center } args = { width: 'center', height: :center }
box = Squib::Args::Box.new box = Squib::Args.extract_box args, deck
box.extract! args, deck
expect(box).to have_attributes(width: [61.5], height: [228.0]) expect(box).to have_attributes(width: [61.5], height: [228.0])
end end
it 'listens to height/2' do
args = { width: 'height / 2', height: :deck }
box = Squib::Args.extract_box args, deck
expect(box).to have_attributes(width: [228.0], height: [456])
end
it 'listens to width - 0.5in' do
args = { x: 'width - 0.5in'}
box = Squib::Args.extract_box args, deck
expect(box).to have_attributes(x: [ 123 - 150 ])
end
end end

23
spec/args/scale_box_spec.rb

@ -2,12 +2,13 @@ require 'spec_helper'
require 'squib/args/scale_box' require 'squib/args/scale_box'
describe Squib::Args::ScaleBox do describe Squib::Args::ScaleBox do
subject(:box) { Squib::Args::ScaleBox.new } let(:deck) { OpenStruct.new(width: 123, height: 456, size: 1, dpi: 300.0) }
context 'unit conversion' do context 'unit conversion' do
it 'converts units on all args' do it 'converts units on all args' do
deck_w_2 = OpenStruct.new(width: 123, height: 456, size: 2, dpi: 300.0)
args = { x: ['1in', '2in'], y: 300, width: '1in', height: '1in' } args = { x: ['1in', '2in'], y: 300, width: '1in', height: '1in' }
box.load!(args, expand_by: 2) box = Squib::Args.extract_scale_box args, deck_w_2
expect(box).to have_attributes( expect(box).to have_attributes(
x: [300, 600], x: [300, 600],
y: [300, 300], y: [300, 300],
@ -20,49 +21,47 @@ describe Squib::Args::ScaleBox do
context 'validation' do context 'validation' do
it 'replaces with deck width and height' do it 'replaces with deck width and height' do
args = { width: :deck, height: :deck } args = { width: :deck, height: :deck }
deck = OpenStruct.new(width: 123, height: 456) box = Squib::Args.extract_scale_box args, deck
box = Squib::Args::Box.new(deck)
box.load!(args, expand_by: 1)
expect(box).to have_attributes(width: [123], height: [456]) expect(box).to have_attributes(width: [123], height: [456])
end end
it 'allows :native' do it 'allows :native' do
args = { width: :native, height: :native } args = { width: :native, height: :native }
box.load!(args, expand_by: 1) box = Squib::Args.extract_scale_box args, deck
expect(box).to have_attributes(width: [:native], height: [:native]) expect(box).to have_attributes(width: [:native], height: [:native])
end end
it 'allows native to be a string' do it 'allows native to be a string' do
args = { width: 'native' } args = { width: 'native' }
box.load!(args, expand_by: 1) box = Squib::Args.extract_scale_box args, deck
expect(box).to have_attributes(width: [:native], height: [:native]) expect(box).to have_attributes(width: [:native], height: [:native])
end end
it 'allows :scale on width if height has to_f' do it 'allows :scale on width if height has to_f' do
args = { width: :scale, height: 75 } args = { width: :scale, height: 75 }
box.load!(args, expand_by: 1) box = Squib::Args.extract_scale_box args, deck
expect(box).to have_attributes(width: [:scale], height: [75]) expect(box).to have_attributes(width: [:scale], height: [75])
end end
it 'allows :scale on width if height has to_f' do it 'allows :scale on width if height has to_f' do
args = { width: 75, height: :scale } args = { width: 75, height: :scale }
box.load!(args, expand_by: 1) box = Squib::Args.extract_scale_box args, deck
expect(box).to have_attributes(width: [75], height: [:scale]) expect(box).to have_attributes(width: [75], height: [:scale])
end end
it 'disallows both :scale' do it 'disallows both :scale' do
args = { width: :scale, height: :scale } args = { width: :scale, height: :scale }
expect { box.load!(args, expand_by: 1) }.to raise_error('if width is :scale, height must be a number') expect { Squib::Args.extract_scale_box args, deck }.to raise_error('if width is :scale, height must be a number')
end end
it 'disallows non-to_f on width' do it 'disallows non-to_f on width' do
args = { width: :foo } args = { width: :foo }
expect { box.load!(args, expand_by: 1) }.to raise_error('width must be a number, :scale, :native, or :deck') expect { Squib::Args.extract_scale_box args, deck }.to raise_error('width must be a number, :scale, :native, or :deck')
end end
it 'disallows non-to_f on height' do it 'disallows non-to_f on height' do
args = { height: :foo } args = { height: :foo }
expect { box.load!(args, expand_by: 1) }.to raise_error('height must be a number, :scale, :native, or :deck') expect { Squib::Args.extract_scale_box args, deck }.to raise_error('height must be a number, :scale, :native, or :deck')
end end
end end

Loading…
Cancel
Save