lib/httpthumbnailer/thumbnail_specs.rb in httpthumbnailer-1.2.0 vs lib/httpthumbnailer/thumbnail_specs.rb in httpthumbnailer-1.3.0

- old
+ new

@@ -1,10 +1,10 @@ class ThumbnailSpecs < Array - def self.from_uri(specs) + def self.from_string(specs) ts = ThumbnailSpecs.new specs.split('/').each do |spec| - ts << ThumbnailSpec.from_uri(spec) + ts << ThumbnailSpec.from_string(spec) end ts end def max_width @@ -21,63 +21,156 @@ end.max end end class ThumbnailSpec - class BadThubnailSpecError < ArgumentError - class MissingArgumentError < BadThubnailSpecError - def initialize(spec) - super "missing argument in: #{spec}" - end + class InvalidFormatError < ArgumentError + def for_edit(name) + exception "#{message} for edit '#{name}'" end - class MissingOptionKeyOrValueError < BadThubnailSpecError - def initialize(option) - super "missing option key or value in: #{option}" - end + def in_spec(spec) + exception "#{message} in spec '#{spec}'" end + end - class BadDimensionValueError < BadThubnailSpecError - def initialize(value) - super "bad dimension value: #{value}" - end + class MissingArgumentError < InvalidFormatError + def initialize(argument) + super "missing #{argument} argument" end end - def initialize(method, width, height, format, options = {}) - @method = method - @width = cast_dimension(width) - @height = cast_dimension(height) - @format = (format == 'input' ? :input : format.upcase) - @options = options + class InvalidArgumentValueError < InvalidFormatError + def initialize(name, value, reason) + super "#{name} value '#{value}' is not #{reason}" + end end - def self.from_uri(spec) - method, width, height, format, *options = *spec.split(',') - raise BadThubnailSpecError::MissingArgumentError.new(spec) unless method and width and height and format + class MissingOptionKeyValuePairError < InvalidFormatError + def initialize(index) + super "missing key-value pair on position #{index + 1}" + end + end - opts = {} - options.each do |option| - key, value = option.split(':') - raise BadThubnailSpecError::MissingOptionKeyOrValueError.new(option) unless key and value - opts[key] = value + class MissingOptionKeyNameError < InvalidFormatError + def initialize(value) + super "missing option key name for value '#{value}'" end + end - ThumbnailSpec.new(method, width, height, format, opts) + class MissingOptionKeyValueError < InvalidFormatError + def initialize(key) + super "missing option value for key '#{key}'" + end end + class EditSpec + attr_reader :name, :args, :options - attr_reader :method, :width, :height, :format, :options + def self.from_string(string) + args = ThumbnailSpec.split_args(string) + args, options = ThumbnailSpec.partition_args_options(args) + name = args.shift + begin + options = ThumbnailSpec.parse_options(options) + rescue InvalidFormatError => error + raise error.for_edit(name) + end + new(name, args, options) + end + + def initialize(name, args, options = {}) + name.nil? or name.empty? and raise MissingArgumentError, 'edit name' + + @name = name + @args = args + @options = options + end + + def to_s + begin + [@name, *@args, *ThumbnailSpec.options_to_s(@options)].join(',') + rescue InvalidFormatError => error + raise error.for_edit(name) + end + end + end + + attr_reader :method, :width, :height, :format, :options, :edits + + def self.from_string(string) + edits = split_edits(string) + spec = edits.shift + args = split_args(spec) + method, width, height, format, *options = *args + + options = parse_options(options) + edits = edits.map{|e| EditSpec.from_string(e)} + + new(method, width, height, format, options, edits) + rescue InvalidFormatError => error + raise error.in_spec(string) + end + + def initialize(method, width, height, format, options = {}, edits = []) + method.nil? or method.empty? and raise MissingArgumentError, 'method' + width.nil? or width.empty? and raise MissingArgumentError, 'width' + height.nil? or height.empty? and raise MissingArgumentError, 'height' + format.nil? or format.empty? and raise MissingArgumentError, 'format' + + width !~ /^([0-9]+|input)$/ and raise InvalidArgumentValueError.new('width', width, "an integer or 'input'") + height !~ /^([0-9]+|input)$/ and raise InvalidArgumentValueError.new('height', height, "an integer or 'input'") + + width = width == 'input' ? :input : width.to_i + height = height == 'input' ? :input : height.to_i + + format = format == 'input' ? :input : format.upcase + + @method = method + @width = width + @height = height + @format = format + @options = options + @edits = edits + end + def to_s - "#{method} #{width}x#{height} (#{format.downcase}) #{options.inspect}" + [[@method, @width, @height, @format, *self.class.options_to_s(@options)].join(','), *@edits.map(&:to_s)].join('!') end - private + def self.split_edits(string) + string.split('!') + end - def cast_dimension(string) - return :input if string == 'input' - raise BadThubnailSpecError::BadDimensionValueError.new(string) unless string =~ /^\d+$/ - string.to_i + def self.split_args(string) + string.split(',') + end + + def self.partition_args_options(args) + options = args.drop_while{|a| not a.include?(':')} + args = args.take_while{|a| not a.include?(':')} + [args, options] + end + + def self.parse_options(options) + Hash[options.map.with_index do |pair, index| + pair.empty? and raise MissingOptionKeyValuePairError, index + pair.split(':', 2) + end].tap do |map| + map.each do |key, value| + key.nil? or key.empty? and raise MissingOptionKeyNameError, value + value.nil? or value.empty? and raise MissingOptionKeyValueError, key + end + end + end + + def self.options_to_s(options) + options.sort_by{|k,v| k}.map do |key, value| + raise MissingOptionKeyNameError, value if key.nil? or key.to_s.empty? + raise MissingOptionKeyValueError, key if value.nil? or value.to_s.empty? + key = key.to_s.gsub('_', '-') if key.kind_of? Symbol + "#{key}:#{value}" + end end end