Redesigning how config files are read, stored, and delegated

With the new design, we take the load off of deck.rb and simply delegate methods over to the new conf.rb. This means that things like `antialias` is now available as a method to the normal Squib scripts for easy checking (without being mutable). Squib::Conf also handles parsing and defaults, and any potential input validation we need to do in the future.

Typographer is also set up now as a deck-wide configuration. This may change in the future if we want typography customization per-command, although that seems like a strange use case.

Lots of tests for this one, and lots of cross-cutting concerns in this commit.

This commit also includes some tweaks to rspec tests, including tagging of slow tests for a `rake spec_fastonly`

Conflicts:
	spec/samples/samples_regression_spec.rb
	spec/spec_helper.rb
	squib.sublime-project
This commit is contained in:
Andy Meneely
2015-04-28 20:31:53 -04:00
parent 74865cb10f
commit 923f346575
21 changed files with 332 additions and 204 deletions
+2 -2
View File
@@ -22,7 +22,7 @@ module Squib
# @api public
def png(opts = {})
opts = needs(opts, [:range, :files, :x, :y, :width, :height, :alpha, :layout, :blend, :angle, :mask])
Dir.chdir(@img_dir) do
Dir.chdir(img_dir) do
@progress_bar.start('Loading PNG(s)', opts[:range].size) do |bar|
opts[:range].each do |i|
@cards[i].png(opts[:file][i],
@@ -57,7 +57,7 @@ module Squib
# @api public
def svg(opts = {})
p = needs(opts,[:range, :files, :svgid, :force_svgid, :x, :y, :width, :height, :layout, :alpha, :blend, :angle, :mask])
Dir.chdir(@img_dir) do
Dir.chdir(img_dir) do
@progress_bar.start('Loading SVG(s)', p[:range].size) do |bar|
p[:range].each do |i|
unless p[:force_id][i] && p[:id][i].to_s.empty?
+19 -16
View File
@@ -3,13 +3,16 @@ module Squib
module Args
class Typographer
def initialize(config = CONFIG_DEFAULTS)
@config = config
def initialize(config = Conf::DEFAULTS)
%w(lsquote ldquote rsquote rdquote smart_quotes
em_dash en_dash ellipsis).each do |var|
instance_variable_set("@#{var}", config[var])
end
end
def process(str)
str = explicit_replacements(str)
str = smart_quotes(str) if @config['smart_quotes']
str = explicit_replacements(str.to_s)
str = smart_quotes(str) if @smart_quotes
str
end
@@ -53,58 +56,58 @@ module Squib
# Straightforward replace
def left_curly(str)
str.gsub('``', @config['ldquote'])
str.gsub('``', @ldquote)
end
# Straightforward replace
def right_curly(str)
str.gsub(%{''}, @config['rdquote'])
str.gsub(%{''}, @rdquote)
end
# A quote between two letters is an apostraphe
def apostraphize(str)
str.gsub(/(\w)(\')(\w)/, '\1' + @config['rsquote'] + '\3')
str.gsub(/(\w)(\')(\w)/, '\1' + @rsquote + '\3')
end
# Straightforward replace
def ellipsificate(str)
str.gsub('...', @config['ellipsis'])
str.gsub('...', @ellipsis)
end
# Straightforward replace
def en_dash(str)
str.gsub('--', @config['en_dash'])
str.gsub('--', @en_dash)
end
# Straightforward replace
def em_dash(str)
str.gsub('---', @config['em_dash'])
str.gsub('---', @em_dash)
end
# Quote next to non-whitespace curls
def right_double_quote(str)
str.gsub(/(\S)(\")/, '\1' + @config['rdquote'])
str.gsub(/(\S)(\")/, '\1' + @rdquote)
end
# Quote next to non-whitespace curls
def left_double_quote(str)
str.gsub(/(\")(\S)/, @config['ldquote'] + '\2')
str.gsub(/(\")(\S)/, @ldquote + '\2')
end
# Handle the cases where a double quote is next to a single quote
def single_inside_double_quote(str)
str.gsub(/(\")(\')(\S)/, @config['ldquote'] + @config['lsquote'] + '\3')
.gsub(/(\")(\')(\S)/, '\1' + @config['rsquote'] + @config['rdquote'])
str.gsub(/(\")(\')(\S)/, @ldquote + @lsquote + '\3')
.gsub(/(\")(\')(\S)/, '\1' + @rsquote + @rdquote)
end
# Quote next to non-whitespace curls
def right_single_quote(str)
str.gsub(/(\S)(\')/, '\1' + @config['rsquote'])
str.gsub(/(\S)(\')/, '\1' + @rsquote)
end
# Quote next to non-whitespace curls
def left_single_quote(str)
str.gsub(/(\')(\S)/, @config['lsquote'] + '\2')
str.gsub(/(\')(\S)/, @lsquote + '\2')
end
end
+5 -5
View File
@@ -16,21 +16,21 @@ module Squib
# :nodoc:
# @api private
def initialize(deck, width, height, backend=:memory, index=-1)
def initialize(deck, width, height, index=-1)
@deck = deck
@width = width
@height = height
@backend = backend
@backend = deck.backend
@svgfile = "#{deck.dir}/#{deck.prefix}#{deck.count_format % index}.svg"
@cairo_surface = make_surface(@svgfile, backend)
@cairo_surface = make_surface(@svgfile, @backend)
@cairo_context = Squib::Graphics::CairoContextWrapper.new(Cairo::Context.new(@cairo_surface))
@cairo_context.antialias = ANTIALIAS_OPTS[(@deck.antialias.downcase)] || 'subpixel'
@cairo_context.antialias = deck.antialias
end
# :nodoc:
# @api private
def make_surface(svgfile, backend)
case backend
case backend.downcase.to_sym
when :memory
Cairo::ImageSurface.new(@width, @height)
when :svg
+114
View File
@@ -0,0 +1,114 @@
require 'squib'
require 'forwardable'
require 'squib/args/typographer'
module Squib
# @api private
class Conf
DEFAULTS = {
'antialias' => 'best',
'backend' => 'memory',
'count_format' => SYSTEM_DEFAULTS[:count_format],
'custom_colors' => {},
'dir' => SYSTEM_DEFAULTS[:dir],
'hint' => :none,
'img_dir' => '.',
'progress_bars' => false,
'ldquote' => "\u201C", # UTF8 chars
'rdquote' => "\u201D",
'lsquote' => "\u2018",
'rsquote' => "\u2019",
'em_dash' => "\u2014",
'en_dash' => "\u2013",
'ellipsis' => "\u2026",
'smart_quotes' => true,
'text_hint' => 'off',
}
#Translate the hints to the methods.
ANTIALIAS_OPTS = {
nil => 'subpixel',
'best' => 'subpixel',
'good' => 'gray',
'fast' => 'gray',
'gray' => 'gray',
'subpixel' => 'subpixel'
}
def initialize(config_hash = DEFAULTS)
@config_hash = config_hash
@typographer = Args::Typographer.new(config_hash)
normalize_antialias
end
# FIXME REMOVE THIS as part of refactoring
# Delegate [] to our hash
# @api private
# def [](key)
# @config_hash[key]
# end
# Load the configuration file, if exists, overriding hardcoded defaults
# @api private
def self.load(file)
yaml = {}
if File.exists? file
Squib::logger.info { " using config: #{file}" }
yaml = YAML.load_file(file) || {}
end
Conf.new(DEFAULTS.merge(yaml))
end
def to_s
"Conf: #{@config_hash.to_s}"
end
def img_dir
@config_hash['img_dir']
end
def text_hint
@config_hash['text_hint']
end
def progress_bars
@config_hash['progress_bars']
end
def typographer
@typographer
end
def dir
@config_hash['dir']
end
def prefix
@config_hash['prefix']
end
def count_format
@config_hash['count_format']
end
def antialias
@config_hash['antialias']
end
def backend
@config_hash['backend']
end
def custom_colors
@config_hash['custom_colors']
end
private
def normalize_antialias
@config_hash['antialias'] = ANTIALIAS_OPTS[@config_hash['antialias'].downcase.strip]
end
end
end
-32
View File
@@ -67,38 +67,6 @@ module Squib
:y_radius => 0,
}
# Squib's configuration defaults
#
# @api public
CONFIG_DEFAULTS = {
'antialias' => 'best',
'backend' => 'memory',
'count_format' => SYSTEM_DEFAULTS[:count_format],
'custom_colors' => {},
'dir' => SYSTEM_DEFAULTS[:dir],
'dpi' => 300,
'hint' => :none,
'img_dir' => '.',
'progress_bar' => false,
'ldquote' => "\u201C", # UTF8 chars
'rdquote' => "\u201D",
'lsquote' => "\u2018",
'rsquote' => "\u2019",
'em_dash' => "\u2014",
'en_dash' => "\u2013",
'ellipsis' => "\u2026",
'smart_quotes' => true,
'text_hint' => 'off',
}
#Translate the hints to the methods.
ANTIALIAS_OPTS = {
'best' => 'subpixel',
'good' => 'gray',
'fast' => 'gray',
'gray' => 'gray',
'subpixel' => 'subpixel'
}
# These are parameters that are intended to be "expanded" across
# range if they are singletons.
#
+15 -49
View File
@@ -1,5 +1,6 @@
require 'yaml'
require 'pp'
require 'forwardable'
require 'squib'
require 'squib/card'
require 'squib/progress'
@@ -7,6 +8,7 @@ require 'squib/input_helpers'
require 'squib/constants'
require 'squib/layout_parser'
require 'squib/args/unit_conversion'
require 'squib/conf'
# The project module
#
@@ -19,24 +21,19 @@ module Squib
class Deck
include Enumerable
include Squib::InputHelpers
extend Forwardable
# Attributes for the width, height (in pixels) and number of cards
# These are expected to be immuatble for the life of Deck
# @api private
attr_reader :width, :height, :cards
# Delegate these configuration options to the Squib::Conf object
def_delegators :conf, :antialias, :backend, :count_format, :custom_colors, :dir,
:img_dir, :prefix, :text_hint, :typographer
# :nodoc:
# @api private
attr_reader :width, :height
# :nodoc:
# @api private
attr_reader :cards
# :nodoc:
# @api private
attr_reader :text_hint, :antialias
# :nodoc:
# @api private
attr_reader :layout, :config, :quote_chars
attr_reader :dir, :prefix, :count_format
attr_reader :layout, :conf
# Squib's constructor that sets the immutable properties.
#
@@ -58,24 +55,16 @@ module Squib
# @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)
@antialias = CONFIG_DEFAULTS['antialias']
@dpi = dpi
@font = SYSTEM_DEFAULTS[:default_font]
@cards = []
@custom_colors = {}
@img_dir = '.'
@progress_bar = Progress.new(false)
@text_hint = :off
@backend = :memory
@dir = SYSTEM_DEFAULTS[:dir]
@prefix = SYSTEM_DEFAULTS[:prefix]
@count_format = SYSTEM_DEFAULTS[:count_format]
@quote_chars = CONFIG_DEFAULTS.select {|k,v| %w(lsquote rsquote ldquote rdquote em_dash en_dash ellipsis smart_quotes).include?(k) }
@conf = Conf.load(config)
@progress_bar = Progress.new(@conf.progress_bars) # FIXME this is evil. Using something different with @ and non-@
show_info(config, layout)
load_config(config)
@width = Args::UnitConversion.parse width, dpi
@height = Args::UnitConversion.parse height, dpi
cards.times{ |i| @cards << Squib::Card.new(self, @width, @height, @backend, i) }
cards.times{ |i| @cards << Squib::Card.new(self, @width, @height, i) }
@layout = LayoutParser.load_layout(layout)
if block_given?
instance_eval(&block) # here we go. wheeeee!
@@ -96,29 +85,6 @@ module Squib
@cards.each { |card| block.call(card) }
end
# Load the configuration file, if exists, overriding hardcoded defaults
# @api private
def load_config(file)
if File.exists?(file) && config = YAML.load_file(file)
Squib::logger.info { " using config: #{file}" }
config = CONFIG_DEFAULTS.merge(config)
@dpi = config['dpi'].to_i
@text_hint = config['text_hint']
@progress_bar.enabled = config['progress_bars']
@custom_colors = config['custom_colors']
@img_dir = config['img_dir']
@backend = (config['backend'].to_s.downcase.strip == 'svg') ? :svg : :memory
@dir = config['dir']
@prefix = config['prefix']
@count_format = config['count_format']
@antialias = config['antialias']
@quote_chars ||= {}
%w(lsquote rsquote ldquote rdquote smart_quotes em_dash en_dash ellipsis).each do |key|
@quote_chars[key] = config[key]
end
end
end
# Use Logger to show more detail on the run
# :nodoc:
# @api private
+1 -1
View File
@@ -33,7 +33,7 @@ module Squib
cc.translate(x,y)
cc.rectangle(p[:trim], p[:trim], card_width, card_height)
cc.clip
case card.backend
case card.backend.downcase.to_sym
when :memory
cc.set_source(card.cairo_surface, 0, 0)
cc.paint
+3 -3
View File
@@ -78,7 +78,7 @@ module Squib
def set_font_rendering_opts!(layout)
font_options = Cairo::FontOptions.new
font_options.antialias = ANTIALIAS_OPTS[(@deck.antialias.downcase)] || 'gray'
font_options.antialias = Conf::ANTIALIAS_OPTS[(@deck.antialias || 'gray').downcase]
font_options.hint_metrics = 'on' # TODO make this configurable
font_options.hint_style = 'full' # TODO make this configurable
layout.context.font_options = font_options
@@ -127,7 +127,7 @@ module Squib
while (key = next_embed(embed.rules.keys, clean_str)) != nil
rule = embed.rules[key]
spacing = rule[:width] * Pango::SCALE
index = clean_str.index(key)
index = clean_str.index(key)
index = clean_str[0..index].bytesize #convert to byte index (bug #57)
str = str.sub(key, "<span size=\"#{ZERO_WIDTH_CHAR_SIZE}\">a<span letter_spacing=\"#{spacing.to_i}\">a</span>a</span>")
layout.markup = str
@@ -164,7 +164,7 @@ module Squib
layout.font_description = font_desc
layout.text = str
if markup
str = Args::Typographer.new(@deck.quote_chars).process(layout.text)
str = @deck.typographer.process(layout.text)
layout.markup = str
end
+2 -2
View File
@@ -130,8 +130,8 @@ module Squib
def colorify(opts, nillable=false, key=:color)
opts[key].each_with_index do |color, i|
unless nillable && color.nil?
if @custom_colors.key? color.to_s
color = @custom_colors[color.to_s]
if custom_colors.key? color.to_s
color = custom_colors[color.to_s]
end
opts[key][i] = color
end