diff --git a/lib/dragonfly/content.rb b/lib/dragonfly/content.rb index 271ae557f..dbf9f6233 100644 --- a/lib/dragonfly/content.rb +++ b/lib/dragonfly/content.rb @@ -1,8 +1,8 @@ -require 'base64' -require 'forwardable' -require 'dragonfly/has_filename' -require 'dragonfly/temp_object' -require 'dragonfly/utils' +require "base64" +require "forwardable" +require "dragonfly/has_filename" +require "dragonfly/temp_object" +require "dragonfly/utils" module Dragonfly @@ -16,11 +16,10 @@ module Dragonfly # It is acted upon in generator, processor, analyser and datastore methods and provides a standard interface for updating content, # no matter how that content first got there (whether in the form of a String/Pathname/File/etc.) class Content - include HasFilename extend Forwardable - def initialize(app, obj="", meta=nil) + def initialize(app, obj = "", meta = nil) @app = app @meta = {} @previous_temp_objects = [] @@ -79,7 +78,7 @@ def name=(name) # @example "image/jpeg" # @return [String] def mime_type - meta['mime_type'] || app.mime_type_for(ext) + meta["mime_type"] || app.mime_type_for(ext) end # Set the content using a pre-registered generator @@ -93,7 +92,7 @@ def generate!(name, *args) # Update the content using a pre-registered processor # @example - # content.process!(:convert, "-resize 300x300") + # content.process!(:thumb, "300x300") # @return [Content] self def process!(name, *args) app.get_processor(name).call(self, *args) @@ -111,10 +110,10 @@ def analyse(name) # @param obj [String, Pathname, Tempfile, File, Content, TempObject] can be any of these types # @param meta [Hash] - should be json-like, i.e. contain no types other than String, Number, Boolean # @return [Content] self - def update(obj, meta=nil) + def update(obj, meta = nil) meta ||= {} - self.temp_object = TempObject.new(obj, meta['name']) - self.meta['name'] ||= temp_object.name if temp_object.name + self.temp_object = TempObject.new(obj, meta["name"]) + self.meta["name"] ||= temp_object.name if temp_object.name clear_analyser_cache add_meta(obj.meta) if obj.respond_to?(:meta) add_meta(meta) @@ -135,7 +134,7 @@ def add_meta(meta) # "file --mime-type #{path}" # end # # ===> "beach.jpg: image/jpeg" - def shell_eval(opts={}) + def shell_eval(opts = {}) should_escape = opts[:escape] != false command = yield(should_escape ? shell.escape(path) : path) run command, :escape => should_escape @@ -148,7 +147,7 @@ def shell_eval(opts={}) # "/usr/local/bin/generate_text gumfry -o #{path}" # end # @return [Content] self - def shell_generate(opts={}) + def shell_generate(opts = {}) ext = opts[:ext] || self.ext should_escape = opts[:escape] != false tempfile = Utils.new_tempfile(ext) @@ -165,7 +164,7 @@ def shell_generate(opts={}) # "convert -resize 20x10 #{old_path} #{new_path}" # end # @return [Content] self - def shell_update(opts={}) + def shell_update(opts = {}) ext = opts[:ext] || self.ext should_escape = opts[:escape] != false tempfile = Utils.new_tempfile(ext) @@ -176,7 +175,7 @@ def shell_update(opts={}) update(tempfile) end - def store(opts={}) + def store(opts = {}) datastore.write(self, opts) end @@ -188,7 +187,7 @@ def b64_data end def close - previous_temp_objects.each{|temp_object| temp_object.close } + previous_temp_objects.each { |temp_object| temp_object.close } temp_object.close end @@ -199,6 +198,7 @@ def inspect private attr_reader :previous_temp_objects + def temp_object=(temp_object) previous_temp_objects.push(@temp_object) if @temp_object @temp_object = temp_object @@ -215,6 +215,5 @@ def clear_analyser_cache def run(command, opts) shell.run(command, opts) end - end end diff --git a/lib/dragonfly/image_magick/commands.rb b/lib/dragonfly/image_magick/commands.rb new file mode 100644 index 000000000..32a85339e --- /dev/null +++ b/lib/dragonfly/image_magick/commands.rb @@ -0,0 +1,35 @@ +module Dragonfly + module ImageMagick + module Commands + module_function + + def convert(content, args = "", opts = {}) + convert_command = content.env[:convert_command] || "convert" + format = opts["format"] + + input_args = opts["input_args"] if opts["input_args"] + delegate_string = "#{opts["delegate"]}:" if opts["delegate"] + frame_string = "[#{opts["frame"]}]" if opts["frame"] + + content.shell_update :ext => format do |old_path, new_path| + "#{convert_command} #{input_args} #{delegate_string}#{old_path}#{frame_string} #{args} #{new_path}" + end + + if format + content.meta["format"] = format.to_s + content.ext = format + content.meta["mime_type"] = nil # don't need it as we have ext now + end + end + + def generate(content, args, format) + format = format.to_s + convert_command = content.env[:convert_command] || "convert" + content.shell_generate :ext => format do |path| + "#{convert_command} #{args} #{path}" + end + content.add_meta("format" => format) + end + end + end +end diff --git a/lib/dragonfly/image_magick/generators/convert.rb b/lib/dragonfly/image_magick/generators/convert.rb deleted file mode 100644 index b49d487d6..000000000 --- a/lib/dragonfly/image_magick/generators/convert.rb +++ /dev/null @@ -1,19 +0,0 @@ -module Dragonfly - module ImageMagick - module Generators - class Convert - - def call(content, args, format) - format = format.to_s - convert_command = content.env[:convert_command] || 'convert' - content.shell_generate :ext => format do |path| - "#{convert_command} #{args} #{path}" - end - content.add_meta('format' => format) - end - - end - end - end -end - diff --git a/lib/dragonfly/image_magick/generators/plain.rb b/lib/dragonfly/image_magick/generators/plain.rb index 9d15de2fe..f0ed84559 100644 --- a/lib/dragonfly/image_magick/generators/plain.rb +++ b/lib/dragonfly/image_magick/generators/plain.rb @@ -1,25 +1,31 @@ +require "dragonfly/image_magick/commands" +require "dragonfly/param_validators" + module Dragonfly module ImageMagick module Generators class Plain + include ParamValidators - def call(content, width, height, opts={}) + def call(content, width, height, opts = {}) + validate_all!([width, height], &is_number) + validate_all_keys!(opts, %w(colour color format), &is_word) format = extract_format(opts) - colour = opts['colour'] || opts['color'] || 'white' - content.generate!(:convert, "-size #{width}x#{height} xc:#{colour}", format) - content.add_meta('format' => format, 'name' => "plain.#{format}") + + colour = opts["colour"] || opts["color"] || "white" + Commands.generate(content, "-size #{width}x#{height} xc:#{colour}", format) + content.add_meta("format" => format, "name" => "plain.#{format}") end - def update_url(url_attributes, width, height, opts={}) + def update_url(url_attributes, width, height, opts = {}) url_attributes.name = "plain.#{extract_format(opts)}" end private def extract_format(opts) - opts['format'] || 'png' + opts["format"] || "png" end - end end end diff --git a/lib/dragonfly/image_magick/generators/plasma.rb b/lib/dragonfly/image_magick/generators/plasma.rb index 4d590cb4b..93bcf21e8 100644 --- a/lib/dragonfly/image_magick/generators/plasma.rb +++ b/lib/dragonfly/image_magick/generators/plasma.rb @@ -1,24 +1,28 @@ +require "dragonfly/image_magick/commands" + module Dragonfly module ImageMagick module Generators class Plasma + include ParamValidators - def call(content, width, height, opts={}) + def call(content, width, height, opts = {}) + validate_all!([width, height], &is_number) + validate!(opts["format"], &is_word) format = extract_format(opts) - content.generate!(:convert, "-size #{width}x#{height} plasma:fractal", format) - content.add_meta('format' => format, 'name' => "plasma.#{format}") + Commands.generate(content, "-size #{width}x#{height} plasma:fractal", format) + content.add_meta("format" => format, "name" => "plasma.#{format}") end - def update_url(url_attributes, width, height, opts={}) + def update_url(url_attributes, width, height, opts = {}) url_attributes.name = "plasma.#{extract_format(opts)}" end private def extract_format(opts) - opts['format'] || 'png' + opts["format"] || "png" end - end end end diff --git a/lib/dragonfly/image_magick/generators/text.rb b/lib/dragonfly/image_magick/generators/text.rb index 5c4a8659a..8f1e5a3b9 100644 --- a/lib/dragonfly/image_magick/generators/text.rb +++ b/lib/dragonfly/image_magick/generators/text.rb @@ -1,100 +1,111 @@ -require 'dragonfly/hash_with_css_style_keys' +require "dragonfly/hash_with_css_style_keys" +require "dragonfly/image_magick/commands" +require "dragonfly/param_validators" module Dragonfly module ImageMagick module Generators class Text + include ParamValidators FONT_STYLES = { - 'normal' => 'normal', - 'italic' => 'italic', - 'oblique' => 'oblique' + "normal" => "normal", + "italic" => "italic", + "oblique" => "oblique", } FONT_STRETCHES = { - 'normal' => 'normal', - 'semi-condensed' => 'semi-condensed', - 'condensed' => 'condensed', - 'extra-condensed' => 'extra-condensed', - 'ultra-condensed' => 'ultra-condensed', - 'semi-expanded' => 'semi-expanded', - 'expanded' => 'expanded', - 'extra-expanded' => 'extra-expanded', - 'ultra-expanded' => 'ultra-expanded' + "normal" => "normal", + "semi-condensed" => "semi-condensed", + "condensed" => "condensed", + "extra-condensed" => "extra-condensed", + "ultra-condensed" => "ultra-condensed", + "semi-expanded" => "semi-expanded", + "expanded" => "expanded", + "extra-expanded" => "extra-expanded", + "ultra-expanded" => "ultra-expanded", } FONT_WEIGHTS = { - 'normal' => 'normal', - 'bold' => 'bold', - 'bolder' => 'bolder', - 'lighter' => 'lighter', - '100' => 100, - '200' => 200, - '300' => 300, - '400' => 400, - '500' => 500, - '600' => 600, - '700' => 700, - '800' => 800, - '900' => 900 + "normal" => "normal", + "bold" => "bold", + "bolder" => "bolder", + "lighter" => "lighter", + "100" => 100, + "200" => 200, + "300" => 300, + "400" => 400, + "500" => 500, + "600" => 600, + "700" => 700, + "800" => 800, + "900" => 900, } - def update_url(url_attributes, string, opts={}) + IS_COLOUR = ->(param) { + /\A(#\w+|rgba?\([\d\.,]+\)|\w+)\z/ === param + } + + def update_url(url_attributes, string, opts = {}) url_attributes.name = "text.#{extract_format(opts)}" end - def call(content, string, opts={}) + def call(content, string, opts = {}) + validate_all_keys!(opts, %w(font font_family), &is_words) + validate_all_keys!(opts, %w(color background_color stroke_color), &IS_COLOUR) + validate!(opts["format"], &is_word) + opts = HashWithCssStyleKeys[opts] args = [] format = extract_format(opts) - background = opts['background_color'] || 'none' - font_size = (opts['font_size'] || 12).to_i + background = opts["background_color"] || "none" + font_size = (opts["font_size"] || 12).to_i + font_family = opts["font_family"] || opts["font"] escaped_string = "\"#{string.gsub(/"/, '\"')}\"" # Settings args.push("-gravity NorthWest") args.push("-antialias") args.push("-pointsize #{font_size}") - args.push("-font \"#{opts['font']}\"") if opts['font'] - args.push("-family '#{opts['font_family']}'") if opts['font_family'] - args.push("-fill #{opts['color']}") if opts['color'] - args.push("-stroke #{opts['stroke_color']}") if opts['stroke_color'] - args.push("-style #{FONT_STYLES[opts['font_style']]}") if opts['font_style'] - args.push("-stretch #{FONT_STRETCHES[opts['font_stretch']]}") if opts['font_stretch'] - args.push("-weight #{FONT_WEIGHTS[opts['font_weight']]}") if opts['font_weight'] + args.push("-family '#{font_family}'") if font_family + args.push("-fill #{opts["color"]}") if opts["color"] + args.push("-stroke #{opts["stroke_color"]}") if opts["stroke_color"] + args.push("-style #{FONT_STYLES[opts["font_style"]]}") if opts["font_style"] + args.push("-stretch #{FONT_STRETCHES[opts["font_stretch"]]}") if opts["font_stretch"] + args.push("-weight #{FONT_WEIGHTS[opts["font_weight"]]}") if opts["font_weight"] args.push("-background #{background}") args.push("label:#{escaped_string}") # Padding - pt, pr, pb, pl = parse_padding_string(opts['padding']) if opts['padding'] - padding_top = (opts['padding_top'] || pt || 0) - padding_right = (opts['padding_right'] || pr || 0) - padding_bottom = (opts['padding_bottom'] || pb || 0) - padding_left = (opts['padding_left'] || pl || 0) + pt, pr, pb, pl = parse_padding_string(opts["padding"]) if opts["padding"] + padding_top = (opts["padding_top"] || pt).to_i + padding_right = (opts["padding_right"] || pr).to_i + padding_bottom = (opts["padding_bottom"] || pb).to_i + padding_left = (opts["padding_left"] || pl).to_i - content.generate!(:convert, args.join(' '), format) + Commands.generate(content, args.join(" "), format) if (padding_top || padding_right || padding_bottom || padding_left) dimensions = content.analyse(:image_properties) - text_width = dimensions['width'] - text_height = dimensions['height'] - width = padding_left + text_width + padding_right - height = padding_top + text_height + padding_bottom + text_width = dimensions["width"] + text_height = dimensions["height"] + width = padding_left + text_width + padding_right + height = padding_top + text_height + padding_bottom args = args.slice(0, args.length - 2) args.push("-size #{width}x#{height}") args.push("xc:#{background}") args.push("-annotate 0x0+#{padding_left}+#{padding_top} #{escaped_string}") - content.generate!(:convert, args.join(' '), format) + Commands.generate(content, args.join(" "), format) end - content.add_meta('format' => format, 'name' => "text.#{format}") + content.add_meta("format" => format, "name" => "text.#{format}") end private def extract_format(opts) - opts['format'] || 'png' + opts["format"] || "png" end # Use css-style padding declaration, i.e. @@ -103,25 +114,23 @@ def extract_format(opts) # 10 5 10 (top, left/right, bottom) # 10 5 10 5 (top, right, bottom, left) def parse_padding_string(str) - padding_parts = str.gsub('px','').split(/\s+/).map{|px| px.to_i} + padding_parts = str.gsub("px", "").split(/\s+/).map { |px| px.to_i } case padding_parts.size when 1 p = padding_parts.first - [p,p,p,p] + [p, p, p, p] when 2 - p,q = padding_parts - [p,q,p,q] + p, q = padding_parts + [p, q, p, q] when 3 - p,q,r = padding_parts - [p,q,r,q] + p, q, r = padding_parts + [p, q, r, q] when 4 padding_parts else raise ArgumentError, "Couldn't parse padding string '#{str}' - should be a css-style string" end end end - end end end - diff --git a/lib/dragonfly/image_magick/plugin.rb b/lib/dragonfly/image_magick/plugin.rb index 41aa091fe..2a9f2a439 100644 --- a/lib/dragonfly/image_magick/plugin.rb +++ b/lib/dragonfly/image_magick/plugin.rb @@ -1,11 +1,11 @@ -require 'dragonfly/image_magick/analysers/image_properties' -require 'dragonfly/image_magick/generators/convert' -require 'dragonfly/image_magick/generators/plain' -require 'dragonfly/image_magick/generators/plasma' -require 'dragonfly/image_magick/generators/text' -require 'dragonfly/image_magick/processors/convert' -require 'dragonfly/image_magick/processors/encode' -require 'dragonfly/image_magick/processors/thumb' +require "dragonfly/image_magick/analysers/image_properties" +require "dragonfly/image_magick/generators/plain" +require "dragonfly/image_magick/generators/plasma" +require "dragonfly/image_magick/generators/text" +require "dragonfly/image_magick/processors/encode" +require "dragonfly/image_magick/processors/thumb" +require "dragonfly/image_magick/commands" +require "dragonfly/param_validators" module Dragonfly module ImageMagick @@ -13,37 +13,36 @@ module ImageMagick # The ImageMagick Plugin registers an app with generators, analysers and processors. # Look at the source code for #call to see exactly how it configures the app. class Plugin - - def call(app, opts={}) + def call(app, opts = {}) # ENV - app.env[:convert_command] = opts[:convert_command] || 'convert' - app.env[:identify_command] = opts[:identify_command] || 'identify' + app.env[:convert_command] = opts[:convert_command] || "convert" + app.env[:identify_command] = opts[:identify_command] || "identify" # Analysers app.add_analyser :image_properties, ImageMagick::Analysers::ImageProperties.new app.add_analyser :width do |content| - content.analyse(:image_properties)['width'] + content.analyse(:image_properties)["width"] end app.add_analyser :height do |content| - content.analyse(:image_properties)['height'] + content.analyse(:image_properties)["height"] end app.add_analyser :format do |content| - content.analyse(:image_properties)['format'] + content.analyse(:image_properties)["format"] end app.add_analyser :aspect_ratio do |content| attrs = content.analyse(:image_properties) - attrs['width'].to_f / attrs['height'] + attrs["width"].to_f / attrs["height"] end app.add_analyser :portrait do |content| attrs = content.analyse(:image_properties) - attrs['width'] <= attrs['height'] + attrs["width"] <= attrs["height"] end app.add_analyser :landscape do |content| !content.analyse(:portrait) end app.add_analyser :image do |content| begin - content.analyse(:image_properties)['format'] != 'pdf' + content.analyse(:image_properties)["format"] != "pdf" rescue Shell::CommandFailed false end @@ -55,29 +54,25 @@ def call(app, opts={}) app.define(:image?) { image } # Generators - app.add_generator :convert, ImageMagick::Generators::Convert.new app.add_generator :plain, ImageMagick::Generators::Plain.new app.add_generator :plasma, ImageMagick::Generators::Plasma.new app.add_generator :text, ImageMagick::Generators::Text.new # Processors - app.add_processor :convert, Processors::Convert.new app.add_processor :encode, Processors::Encode.new app.add_processor :thumb, Processors::Thumb.new app.add_processor :rotate do |content, amount| - content.process!(:convert, "-rotate #{amount}") + ParamValidators.validate!(amount, &ParamValidators.is_number) + Commands.convert(content, "-rotate #{amount}") end # Extra methods - app.define :identify do |cli_args=nil| + app.define :identify do |cli_args = nil| shell_eval do |path| "#{app.env[:identify_command]} #{cli_args} #{path}" end end - end - end end end - diff --git a/lib/dragonfly/image_magick/processors/convert.rb b/lib/dragonfly/image_magick/processors/convert.rb deleted file mode 100644 index edbf6a488..000000000 --- a/lib/dragonfly/image_magick/processors/convert.rb +++ /dev/null @@ -1,33 +0,0 @@ -module Dragonfly - module ImageMagick - module Processors - class Convert - - def call(content, args='', opts={}) - convert_command = content.env[:convert_command] || 'convert' - format = opts['format'] - - input_args = opts['input_args'] if opts['input_args'] - delegate_string = "#{opts['delegate']}:" if opts['delegate'] - frame_string = "[#{opts['frame']}]" if opts['frame'] - - content.shell_update :ext => format do |old_path, new_path| - "#{convert_command} #{input_args} #{delegate_string}#{old_path}#{frame_string} #{args} #{new_path}" - end - - if format - content.meta['format'] = format.to_s - content.ext = format - content.meta['mime_type'] = nil # don't need it as we have ext now - end - end - - def update_url(attrs, args='', opts={}) - format = opts['format'] - attrs.ext = format if format - end - - end - end - end -end diff --git a/lib/dragonfly/image_magick/processors/encode.rb b/lib/dragonfly/image_magick/processors/encode.rb index 37c027db3..d113ea7c9 100644 --- a/lib/dragonfly/image_magick/processors/encode.rb +++ b/lib/dragonfly/image_magick/processors/encode.rb @@ -1,18 +1,29 @@ +require "dragonfly/image_magick/commands" + module Dragonfly module ImageMagick module Processors class Encode + include ParamValidators + + WHITELISTED_ARGS = %w(quality) + + IS_IN_WHITELISTED_ARGS = ->(args_string) { + args_string.scan(/-\w+/).all? { |arg| + WHITELISTED_ARGS.include?(arg.sub("-", "")) + } + } - def update_url(attrs, format, args="") + def update_url(attrs, format, args = "") attrs.ext = format.to_s end - def call(content, format, args="") - content.process!(:convert, args, 'format' => format) + def call(content, format, args = "") + validate!(format, &is_word) + validate!(args, &IS_IN_WHITELISTED_ARGS) + Commands.convert(content, args, "format" => format) end - end end end end - diff --git a/lib/dragonfly/image_magick/processors/thumb.rb b/lib/dragonfly/image_magick/processors/thumb.rb index 5f28831bd..539ba799e 100644 --- a/lib/dragonfly/image_magick/processors/thumb.rb +++ b/lib/dragonfly/image_magick/processors/thumb.rb @@ -1,32 +1,40 @@ +require "dragonfly/image_magick/commands" + module Dragonfly module ImageMagick module Processors class Thumb + include ParamValidators GRAVITIES = { - 'nw' => 'NorthWest', - 'n' => 'North', - 'ne' => 'NorthEast', - 'w' => 'West', - 'c' => 'Center', - 'e' => 'East', - 'sw' => 'SouthWest', - 's' => 'South', - 'se' => 'SouthEast' + "nw" => "NorthWest", + "n" => "North", + "ne" => "NorthEast", + "w" => "West", + "c" => "Center", + "e" => "East", + "sw" => "SouthWest", + "s" => "South", + "se" => "SouthEast", } # Geometry string patterns - RESIZE_GEOMETRY = /\A\d*x\d*[><%^!]?\z|\A\d+@\z/ # e.g. '300x200!' + RESIZE_GEOMETRY = /\A\d*x\d*[><%^!]?\z|\A\d+@\z/ # e.g. '300x200!' CROPPED_RESIZE_GEOMETRY = /\A(\d+)x(\d+)#(\w{1,2})?\z/ # e.g. '20x50#ne' - CROP_GEOMETRY = /\A(\d+)x(\d+)([+-]\d+)?([+-]\d+)?(\w{1,2})?\z/ # e.g. '30x30+10+10' + CROP_GEOMETRY = /\A(\d+)x(\d+)([+-]\d+)?([+-]\d+)?(\w{1,2})?\z/ # e.g. '30x30+10+10' - def update_url(url_attributes, geometry, opts={}) - format = opts['format'] + def update_url(url_attributes, geometry, opts = {}) + format = opts["format"] url_attributes.ext = format if format end - def call(content, geometry, opts={}) - content.process!(:convert, args_for_geometry(geometry), opts) + def call(content, geometry, opts = {}) + validate!(opts["format"], &is_word) + validate!(opts["frame"], &is_number) + Commands.convert(content, args_for_geometry(geometry), { + "format" => opts["format"], + "frame" => opts["frame"], + }) end def args_for_geometry(geometry) @@ -37,11 +45,11 @@ def args_for_geometry(geometry) resize_and_crop_args($1, $2, $3) when CROP_GEOMETRY crop_args( - 'width' => $1, - 'height' => $2, - 'x' => $3, - 'y' => $4, - 'gravity' => $5 + "width" => $1, + "height" => $2, + "x" => $3, + "y" => $4, + "gravity" => $5, ) else raise ArgumentError, "Didn't recognise the geometry string #{geometry}" end @@ -54,26 +62,24 @@ def resize_args(geometry) end def crop_args(opts) - raise ArgumentError, "you can't give a crop offset and gravity at the same time" if opts['x'] && opts['gravity'] + raise ArgumentError, "you can't give a crop offset and gravity at the same time" if opts["x"] && opts["gravity"] - width = opts['width'] - height = opts['height'] - gravity = GRAVITIES[opts['gravity']] - x = "#{opts['x'] || 0}" - x = '+' + x unless x[/\A[+-]/] - y = "#{opts['y'] || 0}" - y = '+' + y unless y[/\A[+-]/] + width = opts["width"] + height = opts["height"] + gravity = GRAVITIES[opts["gravity"]] + x = "#{opts["x"] || 0}" + x = "+" + x unless x[/\A[+-]/] + y = "#{opts["y"] || 0}" + y = "+" + y unless y[/\A[+-]/] "#{"-gravity #{gravity} " if gravity}-crop #{width}x#{height}#{x}#{y} +repage" end def resize_and_crop_args(width, height, gravity) - gravity = GRAVITIES[gravity || 'c'] + gravity = GRAVITIES[gravity || "c"] "-resize #{width}x#{height}^^ -gravity #{gravity} -crop #{width}x#{height}+0+0 +repage" end - end end end end - diff --git a/lib/dragonfly/param_validators.rb b/lib/dragonfly/param_validators.rb new file mode 100644 index 000000000..ac17a23bf --- /dev/null +++ b/lib/dragonfly/param_validators.rb @@ -0,0 +1,37 @@ +module Dragonfly + module ParamValidators + class InvalidParameter < RuntimeError; end + + module_function + + IS_NUMBER = ->(param) { + param.is_a?(Numeric) || /\A[\d\.]+\z/ === param + } + + IS_WORD = ->(param) { + /\A\w+\z/ === param + } + + IS_WORDS = ->(param) { + /\A[\w ]+\z/ === param + } + + def is_number; IS_NUMBER; end + def is_word; IS_WORD; end + def is_words; IS_WORDS; end + + def validate!(parameter, &validator) + return if parameter.nil? + raise InvalidParameter unless validator.(parameter) + end + + def validate_all!(parameters, &validator) + parameters.each { |p| validate!(p, &validator) } + end + + def validate_all_keys!(obj, keys, &validator) + parameters = keys.map { |key| obj[key] } + validate_all!(parameters, &validator) + end + end +end diff --git a/spec/dragonfly/image_magick/commands_spec.rb b/spec/dragonfly/image_magick/commands_spec.rb new file mode 100644 index 000000000..cc0bacb54 --- /dev/null +++ b/spec/dragonfly/image_magick/commands_spec.rb @@ -0,0 +1,98 @@ +require "spec_helper" +require "dragonfly/image_magick/commands" + +describe Dragonfly::ImageMagick::Commands do + include Dragonfly::ImageMagick::Commands + + let(:app) { test_app } + + def sample_content(name) + Dragonfly::Content.new(app, SAMPLES_DIR.join(name)) + end + + describe "convert" do + let(:image) { sample_content("beach.png") } # 280x355 + + it "should allow for general convert commands" do + convert(image, "-scale 56x71") + image.should have_width(56) + image.should have_height(71) + end + + it "should allow for general convert commands with added format" do + convert(image, "-scale 56x71", "format" => "gif") + image.should have_width(56) + image.should have_height(71) + image.should have_format("gif") + image.meta["format"].should == "gif" + end + + it "should work for commands with parenthesis" do + convert(image, "\\( +clone -sparse-color Barycentric '0,0 black 0,%[fx:h-1] white' -function polynomial 2,-2,0.5 \\) -compose Blur -set option:compose:args 15 -composite") + image.should have_width(280) + end + + it "should work for files with spaces/apostrophes in the name" do + image = Dragonfly::Content.new(app, SAMPLES_DIR.join("mevs' white pixel.png")) + convert(image, "-resize 2x2!") + image.should have_width(2) + end + + it "allows converting specific frames" do + gif = sample_content("gif.gif") + convert(gif, "-resize 50x50") + all_frames_size = gif.size + + gif = sample_content("gif.gif") + convert(gif, "-resize 50x50", "frame" => 0) + one_frame_size = gif.size + + one_frame_size.should < all_frames_size + end + + it "accepts input arguments for convert commands" do + image2 = image.clone + convert(image, "") + convert(image2, "", "input_args" => "-extract 50x50+10+10") + + image.should_not equal_image(image2) + image2.should have_width(50) + end + + it "allows converting using specific delegates" do + expect { + convert(image, "", "format" => "jpg", "delegate" => "png") + }.to call_command(app.shell, %r{convert png:/[^']+?/beach\.png /[^']+?\.jpg}) + end + + it "maintains the mime_type meta if it exists already" do + convert(image, "-resize 10x") + image.meta["mime_type"].should be_nil + + image.add_meta("mime_type" => "image/png") + convert(image, "-resize 5x") + image.meta["mime_type"].should == "image/png" + image.mime_type.should == "image/png" # sanity check + end + + it "doesn't maintain the mime_type meta on format change" do + image.add_meta("mime_type" => "image/png") + convert(image, "", "format" => "gif") + image.meta["mime_type"].should be_nil + image.mime_type.should == "image/gif" # sanity check + end + end + + describe "generate" do + let (:image) { Dragonfly::Content.new(app) } + + before(:each) do + generate(image, "-size 1x1 xc:white", "png") + end + + it { image.should have_width(1) } + it { image.should have_height(1) } + it { image.should have_format("png") } + it { image.meta.should == { "format" => "png" } } + end +end diff --git a/spec/dragonfly/image_magick/generators/convert_spec.rb b/spec/dragonfly/image_magick/generators/convert_spec.rb deleted file mode 100644 index 3d8c235d7..000000000 --- a/spec/dragonfly/image_magick/generators/convert_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -require 'spec_helper' - -describe Dragonfly::ImageMagick::Generators::Convert do - let (:generator) { Dragonfly::ImageMagick::Generators::Convert.new } - let (:app) { test_app } - let (:image) { Dragonfly::Content.new(app) } - - describe "calling convert" do - before(:each) do - generator.call(image, "-size 1x1 xc:white", 'png') - end - it {image.should have_width(1)} - it {image.should have_height(1)} - it {image.should have_format('png')} - it {image.meta.should == {'format' => 'png'}} - end - -end - diff --git a/spec/dragonfly/image_magick/generators/plain_spec.rb b/spec/dragonfly/image_magick/generators/plain_spec.rb index 86ae8d3ab..7eafe8636 100644 --- a/spec/dragonfly/image_magick/generators/plain_spec.rb +++ b/spec/dragonfly/image_magick/generators/plain_spec.rb @@ -1,4 +1,5 @@ -require 'spec_helper' +require "spec_helper" +require "dragonfly/param_validators" describe Dragonfly::ImageMagick::Generators::Plain do let (:generator) { Dragonfly::ImageMagick::Generators::Plain.new } @@ -9,32 +10,32 @@ before(:each) do generator.call(image, 3, 2) end - it {image.should have_width(3)} - it {image.should have_height(2)} - it {image.should have_format('png')} - it {image.meta.should == {'format' => 'png', 'name' => 'plain.png'}} + it { image.should have_width(3) } + it { image.should have_height(2) } + it { image.should have_format("png") } + it { image.meta.should == { "format" => "png", "name" => "plain.png" } } end describe "specifying the format" do before(:each) do - generator.call(image, 1, 1, 'format'=> 'gif') + generator.call(image, 1, 1, "format" => "gif") end - it {image.should have_format('gif')} - it {image.meta.should == {'format' => 'gif', 'name' => 'plain.gif'}} + it { image.should have_format("gif") } + it { image.meta.should == { "format" => "gif", "name" => "plain.gif" } } end describe "specifying the colour" do it "works with English spelling" do - generator.call(image, 1, 1, 'colour' => 'red') + generator.call(image, 1, 1, "colour" => "red") end it "works with American spelling" do - generator.call(image, 1, 1, 'color' => 'red') + generator.call(image, 1, 1, "color" => "red") end it "blows up with a bad colour" do expect { - generator.call(image, 1, 1, 'colour' => 'lardoin') + generator.call(image, 1, 1, "colour" => "lardoin") }.to raise_error(Dragonfly::Shell::CommandFailed) end end @@ -42,9 +43,34 @@ describe "urls" do it "updates the url" do url_attributes = Dragonfly::UrlAttributes.new - generator.update_url(url_attributes, 1, 1, 'format' => 'gif') - url_attributes.name.should == 'plain.gif' + generator.update_url(url_attributes, 1, 1, "format" => "gif") + url_attributes.name.should == "plain.gif" end end + describe "param validations" do + { + "color" => "white -write bad.png", + "colour" => "white -write bad.png", + "format" => "png -write bad.png", + }.each do |opt, value| + it "validates bad opts like #{opt} = '#{value}'" do + expect { + generator.call(image, 1, 1, opt => value) + }.to raise_error(Dragonfly::ParamValidators::InvalidParameter) + end + end + + it "validates width" do + expect { + generator.call(image, "1 -write bad.png", 1) + }.to raise_error(Dragonfly::ParamValidators::InvalidParameter) + end + + it "validates height" do + expect { + generator.call(image, 1, "1 -write bad.png") + }.to raise_error(Dragonfly::ParamValidators::InvalidParameter) + end + end end diff --git a/spec/dragonfly/image_magick/generators/plasma_spec.rb b/spec/dragonfly/image_magick/generators/plasma_spec.rb index 0ecc1c629..2f91b9a72 100644 --- a/spec/dragonfly/image_magick/generators/plasma_spec.rb +++ b/spec/dragonfly/image_magick/generators/plasma_spec.rb @@ -1,4 +1,4 @@ -require 'spec_helper' +require "spec_helper" describe Dragonfly::ImageMagick::Generators::Plasma do let (:generator) { Dragonfly::ImageMagick::Generators::Plasma.new } @@ -10,23 +10,42 @@ generator.call(image, 5, 3) image.should have_width(5) image.should have_height(3) - image.should have_format('png') - image.meta.should == {'format' => 'png', 'name' => 'plasma.png'} + image.should have_format("png") + image.meta.should == { "format" => "png", "name" => "plasma.png" } end it "allows changing the format" do - generator.call(image, 1, 1, 'format' => 'jpg') - image.should have_format('jpeg') - image.meta.should == {'format' => 'jpg', 'name' => 'plasma.jpg'} + generator.call(image, 1, 1, "format" => "jpg") + image.should have_format("jpeg") + image.meta.should == { "format" => "jpg", "name" => "plasma.jpg" } end end describe "urls" do it "updates the url" do url_attributes = Dragonfly::UrlAttributes.new - generator.update_url(url_attributes, 1, 1, 'format' => 'jpg') - url_attributes.name.should == 'plasma.jpg' + generator.update_url(url_attributes, 1, 1, "format" => "jpg") + url_attributes.name.should == "plasma.jpg" end end -end + describe "param validations" do + it "validates format" do + expect { + generator.call(image, 1, 1, "format" => "png -write bad.png") + }.to raise_error(Dragonfly::ParamValidators::InvalidParameter) + end + + it "validates width" do + expect { + generator.call(image, "1 -write bad.png", 1) + }.to raise_error(Dragonfly::ParamValidators::InvalidParameter) + end + + it "validates height" do + expect { + generator.call(image, 1, "1 -write bad.png") + }.to raise_error(Dragonfly::ParamValidators::InvalidParameter) + end + end +end diff --git a/spec/dragonfly/image_magick/generators/text_spec.rb b/spec/dragonfly/image_magick/generators/text_spec.rb index 16eabd5b0..8ef5f8959 100644 --- a/spec/dragonfly/image_magick/generators/text_spec.rb +++ b/spec/dragonfly/image_magick/generators/text_spec.rb @@ -1,4 +1,4 @@ -require 'spec_helper' +require "spec_helper" describe Dragonfly::ImageMagick::Generators::Text do let (:generator) { Dragonfly::ImageMagick::Generators::Text.new } @@ -7,62 +7,62 @@ describe "creating a text image" do before(:each) do - generator.call(image, "mmm", 'font_size' => 12) + generator.call(image, "mmm", "font_size" => 12) end - it {image.should have_width(20..40)} # approximate - it {image.should have_height(10..20)} - it {image.should have_format('png')} - it {image.meta.should == {'format' => 'png', 'name' => 'text.png'}} + it { image.should have_width(20..40) } # approximate + it { image.should have_height(10..20) } + it { image.should have_format("png") } + it { image.meta.should == { "format" => "png", "name" => "text.png" } } end describe "specifying the format" do before(:each) do - generator.call(image, "mmm", 'format' => 'gif') + generator.call(image, "mmm", "format" => "gif") end - it {image.should have_format('gif')} - it {image.meta.should == {'format' => 'gif', 'name' => 'text.gif'}} + it { image.should have_format("gif") } + it { image.meta.should == { "format" => "gif", "name" => "text.gif" } } end describe "padding" do before(:each) do image_without_padding = image.clone - generator.call(image_without_padding, "mmm", 'font_size' => 12) + generator.call(image_without_padding, "mmm", "font_size" => 12) @width = image_properties(image_without_padding)[:width].to_i @height = image_properties(image_without_padding)[:height].to_i end it "1 number shortcut" do - generator.call(image, "mmm", 'padding' => '10') + generator.call(image, "mmm", "padding" => "10") image.should have_width(@width + 20) image.should have_height(@height + 20) end it "2 numbers shortcut" do - generator.call(image, "mmm", 'padding' => '10 5') + generator.call(image, "mmm", "padding" => "10 5") image.should have_width(@width + 10) image.should have_height(@height + 20) end it "3 numbers shortcut" do - generator.call(image, "mmm", 'padding' => '10 5 8') + generator.call(image, "mmm", "padding" => "10 5 8") image.should have_width(@width + 10) image.should have_height(@height + 18) end it "4 numbers shortcut" do - generator.call(image, "mmm", 'padding' => '1 2 3 4') + generator.call(image, "mmm", "padding" => "1 2 3 4") image.should have_width(@width + 6) image.should have_height(@height + 4) end it "should override the general padding declaration with the specific one (e.g. 'padding-left')" do - generator.call(image, "mmm", 'padding' => '10', 'padding-left' => 9) + generator.call(image, "mmm", "padding" => "10", "padding-left" => 9) image.should have_width(@width + 19) image.should have_height(@height + 20) end it "should ignore 'px' suffixes" do - generator.call(image, "mmm", 'padding' => '1px 2px 3px 4px') + generator.call(image, "mmm", "padding" => "1px 2px 3px 4px") image.should have_width(@width + 6) image.should have_height(@height + 4) end it "bad padding string" do - lambda{ - generator.call(image, "mmm", 'padding' => '1 2 3 4 5') + lambda { + generator.call(image, "mmm", "padding" => "1 2 3 4 5") }.should raise_error(ArgumentError) end end @@ -70,8 +70,39 @@ describe "urls" do it "updates the url" do url_attributes = Dragonfly::UrlAttributes.new - generator.update_url(url_attributes, "mmm", 'format' => 'gif') - url_attributes.name.should == 'text.gif' + generator.update_url(url_attributes, "mmm", "format" => "gif") + url_attributes.name.should == "text.gif" + end + end + + describe "param validations" do + { + "font" => "Times New Roman -write bad.png", + "font_family" => "Times New Roman -write bad.png", + "color" => "rgb(255, 34, 55) -write bad.png", + "background_color" => "rgb(255, 52, 55) -write bad.png", + "stroke_color" => "rgb(255, 52, 55) -write bad.png", + "format" => "png -write bad.png", + }.each do |opt, value| + it "validates bad opts like #{opt} = '#{value}'" do + expect { + generator.call(image, "some text", opt => value) + }.to raise_error(Dragonfly::ParamValidators::InvalidParameter) + end + end + + ["rgb(33,33,33)", "rgba(33,33,33,0.5)", "rgb(33.5,33.5,33.5)", "#fff", "#efefef", "blue"].each do |colour| + it "allows #{colour.inspect} as a colour specification" do + generator.call(image, "mmm", "color" => colour) + end + end + + ["rgb(33, 33, 33)", "something else", "blue:", "f#ff"].each do |colour| + it "disallows #{colour.inspect} as a colour specification" do + expect { + generator.call(image, "mmm", "color" => colour) + }.to raise_error(Dragonfly::ParamValidators::InvalidParameter) + end end end end diff --git a/spec/dragonfly/image_magick/plugin_spec.rb b/spec/dragonfly/image_magick/plugin_spec.rb index 3ceb31956..6f12c27db 100644 --- a/spec/dragonfly/image_magick/plugin_spec.rb +++ b/spec/dragonfly/image_magick/plugin_spec.rb @@ -1,25 +1,24 @@ -require 'spec_helper' +require "spec_helper" describe "a configured imagemagick app" do - - let(:app){ test_app.configure_with(:imagemagick) } - let(:image){ app.fetch_file(SAMPLES_DIR.join('beach.png')) } + let(:app) { test_app.configure_with(:imagemagick) } + let(:image) { app.fetch_file(SAMPLES_DIR.join("beach.png")) } describe "env variables" do - let(:app){ test_app } + let(:app) { test_app } it "allows setting the convert command" do app.configure do - plugin :imagemagick, :convert_command => '/bin/convert' + plugin :imagemagick, :convert_command => "/bin/convert" end - app.env[:convert_command].should == '/bin/convert' + app.env[:convert_command].should == "/bin/convert" end it "allows setting the identify command" do app.configure do - plugin :imagemagick, :identify_command => '/bin/identify' + plugin :imagemagick, :identify_command => "/bin/identify" end - app.env[:identify_command].should == '/bin/identify' + app.env[:identify_command].should == "/bin/identify" end end @@ -33,7 +32,7 @@ end it "should return the aspect ratio" do - image.aspect_ratio.should == (280.0/355.0) + image.aspect_ratio.should == (280.0 / 355.0) end it "should say if it's portrait" do @@ -60,39 +59,39 @@ end it "should return false for pdfs" do - image.encode('pdf').image?.should be_falsey - end unless ENV['SKIP_FLAKY_TESTS'] + image.encode("pdf").image?.should be_falsey + end unless ENV["SKIP_FLAKY_TESTS"] end describe "processors that change the url" do before do - app.configure{ url_format '/:name' } + app.configure { url_format "/:name" } end - describe "convert" do + describe "thumb" do it "sanity check with format" do - thumb = image.convert('-resize 1x1!', 'format' => 'jpg') + thumb = image.thumb("1x1!", "format" => "jpg") thumb.url.should =~ /^\/beach\.jpg\?.*job=\w+/ thumb.width.should == 1 - thumb.format.should == 'jpeg' - thumb.meta['format'].should == 'jpg' + thumb.format.should == "jpeg" + thumb.meta["format"].should == "jpg" end it "sanity check without format" do - thumb = image.convert('-resize 1x1!') + thumb = image.thumb("1x1!") thumb.url.should =~ /^\/beach\.png\?.*job=\w+/ thumb.width.should == 1 - thumb.format.should == 'png' - thumb.meta['format'].should be_nil + thumb.format.should == "png" + thumb.meta["format"].should be_nil end end describe "encode" do it "sanity check" do - thumb = image.encode('jpg') + thumb = image.encode("jpg") thumb.url.should =~ /^\/beach\.jpg\?.*job=\w+/ - thumb.format.should == 'jpeg' - thumb.meta['format'].should == 'jpg' + thumb.format.should == "jpeg" + thumb.meta["format"].should == "jpg" end end end @@ -100,13 +99,13 @@ describe "other processors" do describe "encode" do it "should encode the image to the correct format" do - image.encode!('gif') - image.format.should == 'gif' + image.encode!("gif") + image.format.should == "gif" end it "should allow for extra args" do - image.encode!('jpg', '-quality 1') - image.format.should == 'jpeg' + image.encode!("jpg", "-quality 1") + image.format.should == "jpeg" image.size.should < 2000 end end @@ -117,8 +116,13 @@ image.width.should == 355 image.height.should == 280 end - end + it "disallows bad parameters" do + expect { + image.rotate!("90 -write bad.png").apply + }.to raise_error(Dragonfly::ParamValidators::InvalidParameter) + end + end end describe "identify" do @@ -127,5 +131,4 @@ image.identify("-format %h").chomp.should == "355" end end - end diff --git a/spec/dragonfly/image_magick/processors/convert_spec.rb b/spec/dragonfly/image_magick/processors/convert_spec.rb deleted file mode 100644 index 48f29c8f8..000000000 --- a/spec/dragonfly/image_magick/processors/convert_spec.rb +++ /dev/null @@ -1,88 +0,0 @@ -require 'spec_helper' - -describe Dragonfly::ImageMagick::Processors::Convert do - - def sample_content(name) - Dragonfly::Content.new(app, SAMPLES_DIR.join(name)) - end - - let(:app){ test_app } - let(:image){ sample_content('beach.png') } # 280x355 - let(:processor){ Dragonfly::ImageMagick::Processors::Convert.new } - - it "should allow for general convert commands" do - processor.call(image, '-scale 56x71') - image.should have_width(56) - image.should have_height(71) - end - - it "should allow for general convert commands with added format" do - processor.call(image, '-scale 56x71', 'format' => 'gif') - image.should have_width(56) - image.should have_height(71) - image.should have_format('gif') - image.meta['format'].should == 'gif' - end - - it "should work for commands with parenthesis" do - processor.call(image, "\\( +clone -sparse-color Barycentric '0,0 black 0,%[fx:h-1] white' -function polynomial 2,-2,0.5 \\) -compose Blur -set option:compose:args 15 -composite") - image.should have_width(280) - end - - it "should work for files with spaces/apostrophes in the name" do - image = Dragonfly::Content.new(app, SAMPLES_DIR.join("mevs' white pixel.png")) - processor.call(image, "-resize 2x2!") - image.should have_width(2) - end - - it "updates the url with format if given" do - url_attributes = Dragonfly::UrlAttributes.new - processor.update_url(url_attributes, '-scale 56x71', 'format' => 'gif') - url_attributes.ext.should == 'gif' - end - - it "allows converting specific frames" do - gif = sample_content('gif.gif') - processor.call(gif, '-resize 50x50') - all_frames_size = gif.size - - gif = sample_content('gif.gif') - processor.call(gif, '-resize 50x50', 'frame' => 0) - one_frame_size = gif.size - - one_frame_size.should < all_frames_size - end - - it "accepts input arguments for convert commands" do - image2 = image.clone - processor.call(image, '') - processor.call(image2, '', 'input_args' => '-extract 50x50+10+10') - - image.should_not equal_image(image2) - image2.should have_width(50) - end - - it "allows converting using specific delegates" do - expect { - processor.call(image, '', 'format' => 'jpg', 'delegate' => 'png') - }.to call_command(app.shell, %r{convert png:/[^']+?/beach\.png /[^']+?\.jpg}) - end - - it "maintains the mime_type meta if it exists already" do - processor.call(image, '-resize 10x') - image.meta['mime_type'].should be_nil - - image.add_meta('mime_type' => 'image/png') - processor.call(image, '-resize 5x') - image.meta['mime_type'].should == 'image/png' - image.mime_type.should == 'image/png' # sanity check - end - - it "doesn't maintain the mime_type meta on format change" do - image.add_meta('mime_type' => 'image/png') - processor.call(image, '', 'format' => 'gif') - image.meta['mime_type'].should be_nil - image.mime_type.should == 'image/gif' # sanity check - end - -end diff --git a/spec/dragonfly/image_magick/processors/encode_spec.rb b/spec/dragonfly/image_magick/processors/encode_spec.rb new file mode 100644 index 000000000..c5269db6e --- /dev/null +++ b/spec/dragonfly/image_magick/processors/encode_spec.rb @@ -0,0 +1,30 @@ +require "spec_helper" + +describe Dragonfly::ImageMagick::Processors::Encode do + let (:app) { test_imagemagick_app } + let (:image) { Dragonfly::Content.new(app, SAMPLES_DIR.join("beach.png")) } # 280x355 + let (:processor) { Dragonfly::ImageMagick::Processors::Encode.new } + + it "encodes to a different format" do + processor.call(image, "jpeg") + image.should have_format("jpeg") + end + + describe "param validations" do + it "validates the format param" do + expect { + processor.call(image, "jpeg -write bad.png") + }.to raise_error(Dragonfly::ParamValidators::InvalidParameter) + end + + it "allows good args" do + processor.call(image, "jpeg", "-quality 10") + end + + it "disallows bad args" do + expect { + processor.call(image, "jpeg", "-write bad.png") + }.to raise_error(Dragonfly::ParamValidators::InvalidParameter) + end + end +end diff --git a/spec/dragonfly/image_magick/processors/thumb_spec.rb b/spec/dragonfly/image_magick/processors/thumb_spec.rb index 5fbdf6c70..e02131682 100644 --- a/spec/dragonfly/image_magick/processors/thumb_spec.rb +++ b/spec/dragonfly/image_magick/processors/thumb_spec.rb @@ -1,88 +1,80 @@ -require 'spec_helper' -require 'ostruct' +require "spec_helper" +require "ostruct" describe Dragonfly::ImageMagick::Processors::Thumb do - let (:app) { test_imagemagick_app } - let (:image) { Dragonfly::Content.new(app, SAMPLES_DIR.join('beach.png')) } # 280x355 + let (:image) { Dragonfly::Content.new(app, SAMPLES_DIR.join("beach.png")) } # 280x355 let (:processor) { Dragonfly::ImageMagick::Processors::Thumb.new } it "raises an error if an unrecognized string is given" do - expect{ - processor.call(image, '30x40#ne!') + expect { + processor.call(image, "30x40#ne!") }.to raise_error(ArgumentError) end describe "resizing" do - it "works with xNN" do - processor.call(image, 'x30') + processor.call(image, "x30") image.should have_width(24) image.should have_height(30) end it "works with NNx" do - processor.call(image, '30x') + processor.call(image, "30x") image.should have_width(30) image.should have_height(38) end it "works with NNxNN" do - processor.call(image, '30x30') + processor.call(image, "30x30") image.should have_width(24) image.should have_height(30) end it "works with NNxNN!" do - processor.call(image, '30x30!') + processor.call(image, "30x30!") image.should have_width(30) image.should have_height(30) end it "works with NNxNN%" do - processor.call(image, '25x50%') + processor.call(image, "25x50%") image.should have_width(70) image.should have_height(178) end describe "NNxNN>" do - it "doesn't resize if the image is smaller than specified" do - processor.call(image, '1000x1000>') + processor.call(image, "1000x1000>") image.should have_width(280) image.should have_height(355) end it "resizes if the image is larger than specified" do - processor.call(image, '30x30>') + processor.call(image, "30x30>") image.should have_width(24) image.should have_height(30) end - end describe "NNxNN<" do - it "doesn't resize if the image is larger than specified" do - processor.call(image, '10x10<') + processor.call(image, "10x10<") image.should have_width(280) image.should have_height(355) end it "resizes if the image is smaller than specified" do - processor.call(image, '400x400<') + processor.call(image, "400x400<") image.should have_width(315) image.should have_height(400) end - end - end describe "cropping" do # Difficult to test here other than dimensions - it "crops" do - processor.call(image, '10x20+30+30') + processor.call(image, "10x20+30+30") image.should have_width(10) image.should have_height(20) end @@ -90,11 +82,11 @@ it "crops with gravity" do image2 = image.clone - processor.call(image, '10x8nw') + processor.call(image, "10x8nw") image.should have_width(10) image.should have_height(8) - processor.call(image2, '10x8se') + processor.call(image2, "10x8se") image2.should have_width(10) image2.should have_height(8) @@ -103,77 +95,86 @@ it "raises if given both gravity and offset" do expect { - processor.call(image, '100x100+10+10se') + processor.call(image, "100x100+10+10se") }.to raise_error(ArgumentError) end it "works when the crop area is outside the image" do - processor.call(image, '100x100+250+300') + processor.call(image, "100x100+250+300") image.should have_width(30) image.should have_height(55) end it "crops twice in a row correctly" do - processor.call(image, '100x100+10+10') - processor.call(image, '50x50+0+0') + processor.call(image, "100x100+10+10") + processor.call(image, "50x50+0+0") image.should have_width(50) image.should have_height(50) end - end describe "resize_and_crop" do - it "crops to the correct dimensions" do - processor.call(image, '100x100#') + processor.call(image, "100x100#") image.should have_width(100) image.should have_height(100) end it "resizes before cropping" do image2 = image.clone - processor.call(image, '100x100#') - processor.call(image2, '100x100c') + processor.call(image, "100x100#") + processor.call(image2, "100x100c") image2.should_not equal_image(image) end it "works with gravity" do image2 = image.clone - processor.call(image, '10x10#nw') - processor.call(image, '10x10#se') + processor.call(image, "10x10#nw") + processor.call(image, "10x10#se") image2.should_not equal_image(image) end - end describe "format" do let (:url_attributes) { OpenStruct.new } it "changes the format if passed in" do - processor.call(image, '2x2', 'format' => 'jpeg') - image.should have_format('jpeg') + processor.call(image, "2x2", "format" => "jpeg") + image.should have_format("jpeg") end it "doesn't change the format if not passed in" do - processor.call(image, '2x2') - image.should have_format('png') + processor.call(image, "2x2") + image.should have_format("png") end it "updates the url ext if passed in" do - processor.update_url(url_attributes, '2x2', 'format' => 'png') - url_attributes.ext.should == 'png' + processor.update_url(url_attributes, "2x2", "format" => "png") + url_attributes.ext.should == "png" end it "doesn't update the url ext if not passed in" do - processor.update_url(url_attributes, '2x2') + processor.update_url(url_attributes, "2x2") url_attributes.ext.should be_nil end end describe "args_for_geometry" do it "returns the convert arguments used for a given geometry" do - expect(processor.args_for_geometry('30x40')).to eq('-resize 30x40') + expect(processor.args_for_geometry("30x40")).to eq("-resize 30x40") end end + describe "param validations" do + { + "format" => "png -write bad.png", + "frame" => "0] -write bad.png [", + }.each do |opt, value| + it "validates bad opts like #{opt} = '#{value}'" do + expect { + processor.call(image, "30x30", opt => value) + }.to raise_error(Dragonfly::ParamValidators::InvalidParameter) + end + end + end end diff --git a/spec/dragonfly/param_validators_spec.rb b/spec/dragonfly/param_validators_spec.rb new file mode 100644 index 000000000..3907f6d57 --- /dev/null +++ b/spec/dragonfly/param_validators_spec.rb @@ -0,0 +1,89 @@ +require "spec_helper" +require "dragonfly/param_validators" + +describe Dragonfly::ParamValidators do + include Dragonfly::ParamValidators + + describe "validate!" do + it "does nothing if the parameter meets the condition" do + validate!("thing") { |t| t === "thing" } + end + + it "raises if the parameter doesn't meet the condition" do + expect { + validate!("thing") { |t| t === "ting" } + }.to raise_error(Dragonfly::ParamValidators::InvalidParameter) + end + + it "does nothing if the parameter is nil" do + validate!(nil) { |t| t === "thing" } + end + end + + describe "validate_all!" do + it "allows passing an array of parameters to validate" do + validate_all!(["a", "b"]) { |p| /\w/ === p } + expect { + validate_all!(["a", " "]) { |p| /\w/ === p } + }.to raise_error(Dragonfly::ParamValidators::InvalidParameter) + end + end + + describe "validate_all_keys!" do + it "allows passing an array of parameters to validate" do + obj = { "a" => "A", "b" => "B" } + validate_all_keys!(obj, ["a", "b"]) { |p| /\w/ === p } + expect { + validate_all_keys!(obj, ["a", "b"]) { |p| /[a-z]/ === p } + }.to raise_error(Dragonfly::ParamValidators::InvalidParameter) + end + end + + describe "is_number" do + [3, 3.14, "3", "3.2"].each do |val| + it "validates #{val.inspect}" do + validate!(val, &is_number) + end + end + + ["", "3 2", "hello4", {}, []].each do |val| + it "validates #{val.inspect}" do + expect { + validate!(val, &is_number) + }.to raise_error(Dragonfly::ParamValidators::InvalidParameter) + end + end + end + + describe "is_word" do + ["hello", "helLo", "HELLO"].each do |val| + it "validates #{val.inspect}" do + validate!(val, &is_word) + end + end + + ["", "hel%$lo", "hel lo", "hel-lo", {}, []].each do |val| + it "validates #{val.inspect}" do + expect { + validate!(val, &is_word) + }.to raise_error(Dragonfly::ParamValidators::InvalidParameter) + end + end + end + + describe "is_words" do + ["hello there", "Hi", " What is Up "].each do |val| + it "validates #{val.inspect}" do + validate!(val, &is_words) + end + end + + ["", "hel%$lo", "What's up", "hel-lo", {}, []].each do |val| + it "validates #{val.inspect}" do + expect { + validate!(val, &is_words) + }.to raise_error(Dragonfly::ParamValidators::InvalidParameter) + end + end + end +end diff --git a/spec/functional/shell_commands_spec.rb b/spec/functional/shell_commands_spec.rb index 65f41781a..06c4f12be 100644 --- a/spec/functional/shell_commands_spec.rb +++ b/spec/functional/shell_commands_spec.rb @@ -1,33 +1,30 @@ -require 'spec_helper' +require "spec_helper" describe "using the shell" do - let (:app) { test_app } describe "shell injection" do it "should not allow it!" do app.configure_with(:imagemagick) begin - app.generate(:plain, 10, 10, 'white').convert("-resize 5x5 ; touch tmp/stuff").apply + app.generate(:plain, 10, 10, "white; touch tmp/stuff").apply rescue Dragonfly::Shell::CommandFailed end - File.exist?('tmp/stuff').should be_falsey + File.exist?("tmp/stuff").should be_falsey end end describe "env variables with imagemagick" do it "allows configuring the convert path" do - app.configure_with(:imagemagick, :convert_command => '/bin/convert') + app.configure_with(:imagemagick, :convert_command => "/bin/convert") app.shell.should_receive(:run).with(%r[/bin/convert], hash_including) - app.create("").thumb('30x30').apply + app.create("").thumb("30x30").apply end it "allows configuring the identify path" do - app.configure_with(:imagemagick, :identify_command => '/bin/identify') + app.configure_with(:imagemagick, :identify_command => "/bin/identify") app.shell.should_receive(:run).with(%r[/bin/identify], hash_including).and_return("JPG 1 1") app.create("").width end end - end - diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index fdaeba319..c3cd3986f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,19 +2,19 @@ require "bundler" Bundler.setup(:default, :test) -$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) +$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib")) $LOAD_PATH.unshift(File.dirname(__FILE__)) -require 'rspec' -require 'dragonfly' -require 'fileutils' -require 'tempfile' -require 'webmock/rspec' -require 'pry' +require "rspec" +require "dragonfly" +require "fileutils" +require "tempfile" +require "webmock/rspec" +require "pry" # Requires supporting files with custom matchers and macros, etc, -Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f} +Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } -SAMPLES_DIR = Pathname.new(File.expand_path('../../samples', __FILE__)) +SAMPLES_DIR = Pathname.new(File.expand_path("../../samples", __FILE__)) RSpec.configure do |c| c.include ModelHelpers @@ -25,8 +25,8 @@ def todo raise "TODO" end -require 'logger' -LOG_FILE = 'tmp/test.log' +require "logger" +LOG_FILE = "tmp/test.log" FileUtils.rm_rf(LOG_FILE) Dragonfly.logger = Logger.new(LOG_FILE) @@ -36,7 +36,7 @@ def todo end end -def test_app(name=nil) +def test_app(name = nil) app = Dragonfly::App.instance(name) app.datastore = Dragonfly::MemoryDataStore.new app.secret = "test secret" @@ -45,8 +45,6 @@ def test_app(name=nil) def test_imagemagick_app test_app.configure do - generator :convert, Dragonfly::ImageMagick::Generators::Convert.new - processor :convert, Dragonfly::ImageMagick::Processors::Convert.new analyser :image_properties, Dragonfly::ImageMagick::Analysers::ImageProperties.new end end