fix auto image sizes, make default helpers first feature

This commit is contained in:
tdreyno 2010-09-05 19:33:57 -07:00
parent 8b00e830f9
commit d638fe8ce2
3 changed files with 239 additions and 234 deletions

View file

@ -25,10 +25,10 @@ module Middleman
end end
# livereload # livereload
%w(asset_host %w(default_helpers
asset_host
automatic_image_sizes automatic_image_sizes
cache_buster cache_buster
default_helpers
minify_css minify_css
minify_javascript minify_javascript
relative_assets relative_assets

View file

@ -2,8 +2,8 @@ class Middleman::Features::AutomaticImageSizes
def initialize(app, config) def initialize(app, config)
require "middleman/features/automatic_image_sizes/fastimage" require "middleman/features/automatic_image_sizes/fastimage"
Middleman::Server.send :alias_method, :pre_automatic_image_tag, :image_tag
Middleman::Server.helpers do Middleman::Server.helpers do
alias_method :pre_automatic_image_tag, :image_tag
def image_tag(path, params={}) def image_tag(path, params={})
if (!params[:width] || !params[:height]) && !path.include?("://") if (!params[:width] || !params[:height]) && !path.include?("://")
params[:alt] ||= "" params[:alt] ||= ""
@ -12,7 +12,7 @@ class Middleman::Features::AutomaticImageSizes
begin begin
real_path = File.join(settings.public, settings.images_dir, path) real_path = File.join(settings.public, settings.images_dir, path)
if File.exists? real_path if File.exists? real_path
dimensions = Middleman::FastImage.size(real_path, :raise_on_failure => true) dimensions = ::FastImage.size(real_path, :raise_on_failure => true)
params[:width] ||= dimensions[0] params[:width] ||= dimensions[0]
params[:height] ||= dimensions[1] params[:height] ||= dimensions[1]
end end

View file

@ -32,251 +32,256 @@
require 'net/https' require 'net/https'
require 'open-uri' require 'open-uri'
module Middleman class FastImage
class FastImage attr_reader :size, :type
attr_reader :size, :type
class FastImageException < StandardError # :nodoc: class FastImageException < StandardError # :nodoc:
end end
class MoreCharsNeeded < FastImageException # :nodoc: class MoreCharsNeeded < FastImageException # :nodoc:
end end
class UnknownImageType < FastImageException # :nodoc: class UnknownImageType < FastImageException # :nodoc:
end end
class ImageFetchFailure < FastImageException # :nodoc: class ImageFetchFailure < FastImageException # :nodoc:
end end
class SizeNotFound < FastImageException # :nodoc: class SizeNotFound < FastImageException # :nodoc:
end end
DefaultTimeout = 2 DefaultTimeout = 2
LocalFileChunkSize = 256 LocalFileChunkSize = 256
# Returns an array containing the width and height of the image. # Returns an array containing the width and height of the image.
# It will return nil if the image could not be fetched, or if the image type was not recognised. # It will return nil if the image could not be fetched, or if the image type was not recognised.
# #
# By default there is a timeout of 2 seconds for opening and reading from a remote server. # By default there is a timeout of 2 seconds for opening and reading from a remote server.
# This can be changed by passing a :timeout => number_of_seconds in the options. # This can be changed by passing a :timeout => number_of_seconds in the options.
# #
# If you wish FastImage to raise if it cannot size the image for any reason, then pass # If you wish FastImage to raise if it cannot size the image for any reason, then pass
# :raise_on_failure => true in the options. # :raise_on_failure => true in the options.
# #
# FastImage knows about GIF, JPEG, BMP and PNG files. # FastImage knows about GIF, JPEG, BMP and PNG files.
# #
# === Example # === Example
# #
# require 'fastimage' # require 'fastimage'
# #
# FastImage.size("http://stephensykes.com/images/ss.com_x.gif") # FastImage.size("http://stephensykes.com/images/ss.com_x.gif")
# => [266, 56] # => [266, 56]
# FastImage.size("http://stephensykes.com/images/pngimage") # FastImage.size("http://stephensykes.com/images/pngimage")
# => [16, 16] # => [16, 16]
# FastImage.size("http://farm4.static.flickr.com/3023/3047236863_9dce98b836.jpg") # FastImage.size("http://farm4.static.flickr.com/3023/3047236863_9dce98b836.jpg")
# => [500, 375] # => [500, 375]
# FastImage.size("http://www-ece.rice.edu/~wakin/images/lena512.bmp") # FastImage.size("http://www-ece.rice.edu/~wakin/images/lena512.bmp")
# => [512, 512] # => [512, 512]
# FastImage.size("test/fixtures/test.jpg") # FastImage.size("test/fixtures/test.jpg")
# => [882, 470] # => [882, 470]
# FastImage.size("http://pennysmalls.com/does_not_exist") # FastImage.size("http://pennysmalls.com/does_not_exist")
# => nil # => nil
# FastImage.size("http://pennysmalls.com/does_not_exist", :raise_on_failure=>true) # FastImage.size("http://pennysmalls.com/does_not_exist", :raise_on_failure=>true)
# => raises FastImage::ImageFetchFailure # => raises FastImage::ImageFetchFailure
# FastImage.size("http://stephensykes.com/favicon.ico", :raise_on_failure=>true) # FastImage.size("http://stephensykes.com/favicon.ico", :raise_on_failure=>true)
# => raises FastImage::UnknownImageType # => raises FastImage::UnknownImageType
# FastImage.size("http://stephensykes.com/favicon.ico", :raise_on_failure=>true, :timeout=>0.01) # FastImage.size("http://stephensykes.com/favicon.ico", :raise_on_failure=>true, :timeout=>0.01)
# => raises FastImage::ImageFetchFailure # => raises FastImage::ImageFetchFailure
# FastImage.size("http://stephensykes.com/images/faulty.jpg", :raise_on_failure=>true) # FastImage.size("http://stephensykes.com/images/faulty.jpg", :raise_on_failure=>true)
# => raises FastImage::SizeNotFound # => raises FastImage::SizeNotFound
# #
# === Supported options # === Supported options
# [:timeout] # [:timeout]
# Overrides the default timeout of 2 seconds. Applies both to reading from and opening the http connection. # Overrides the default timeout of 2 seconds. Applies both to reading from and opening the http connection.
# [:raise_on_failure] # [:raise_on_failure]
# If set to true causes an exception to be raised if the image size cannot be found for any reason. # If set to true causes an exception to be raised if the image size cannot be found for any reason.
# #
def self.size(uri, options={}) def self.size(uri, options={})
new(uri, options).size new(uri, options).size
end end
# Returns an symbol indicating the image type fetched from a uri. # Returns an symbol indicating the image type fetched from a uri.
# It will return nil if the image could not be fetched, or if the image type was not recognised. # It will return nil if the image could not be fetched, or if the image type was not recognised.
# #
# By default there is a timeout of 2 seconds for opening and reading from a remote server. # By default there is a timeout of 2 seconds for opening and reading from a remote server.
# This can be changed by passing a :timeout => number_of_seconds in the options. # This can be changed by passing a :timeout => number_of_seconds in the options.
# #
# If you wish FastImage to raise if it cannot find the type of the image for any reason, then pass # If you wish FastImage to raise if it cannot find the type of the image for any reason, then pass
# :raise_on_failure => true in the options. # :raise_on_failure => true in the options.
# #
# === Example # === Example
# #
# require 'fastimage' # require 'fastimage'
# #
# FastImage.type("http://stephensykes.com/images/ss.com_x.gif") # FastImage.type("http://stephensykes.com/images/ss.com_x.gif")
# => :gif # => :gif
# FastImage.type("http://stephensykes.com/images/pngimage") # FastImage.type("http://stephensykes.com/images/pngimage")
# => :png # => :png
# FastImage.type("http://farm4.static.flickr.com/3023/3047236863_9dce98b836.jpg") # FastImage.type("http://farm4.static.flickr.com/3023/3047236863_9dce98b836.jpg")
# => :jpeg # => :jpeg
# FastImage.type("http://www-ece.rice.edu/~wakin/images/lena512.bmp") # FastImage.type("http://www-ece.rice.edu/~wakin/images/lena512.bmp")
# => :bmp # => :bmp
# FastImage.type("test/fixtures/test.jpg") # FastImage.type("test/fixtures/test.jpg")
# => :jpeg # => :jpeg
# FastImage.type("http://pennysmalls.com/does_not_exist") # FastImage.type("http://pennysmalls.com/does_not_exist")
# => nil # => nil
# #
# === Supported options # === Supported options
# [:timeout] # [:timeout]
# Overrides the default timeout of 2 seconds. Applies both to reading from and opening the http connection. # Overrides the default timeout of 2 seconds. Applies both to reading from and opening the http connection.
# [:raise_on_failure] # [:raise_on_failure]
# If set to true causes an exception to be raised if the image type cannot be found for any reason. # If set to true causes an exception to be raised if the image type cannot be found for any reason.
# #
def self.type(uri, options={}) def self.type(uri, options={})
new(uri, options.merge(:type_only=>true)).type new(uri, options.merge(:type_only=>true)).type
end end
def initialize(uri, options={}) def initialize(uri, options={})
@property = options[:type_only] ? :type : :size @property = options[:type_only] ? :type : :size
@timeout = options[:timeout] || DefaultTimeout @timeout = options[:timeout] || DefaultTimeout
@uri = uri @uri = uri
@parsed_uri = URI.parse(uri.gsub(/\s/, "%20")) begin
@parsed_uri = URI.parse(uri)
rescue URI::InvalidURIError
fetch_using_open_uri
else
if @parsed_uri.scheme == "http" || @parsed_uri.scheme == "https" if @parsed_uri.scheme == "http" || @parsed_uri.scheme == "https"
fetch_using_http fetch_using_http
else else
fetch_using_open_uri fetch_using_open_uri
end end
raise SizeNotFound if options[:raise_on_failure] && @property == :size && !@size
rescue Timeout::Error, SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ECONNRESET, ImageFetchFailure
raise ImageFetchFailure if options[:raise_on_failure]
rescue Errno::ENOENT
raise ImageFetchFailure if options[:raise_on_failure]
rescue UnknownImageType
raise UnknownImageType if options[:raise_on_failure]
end end
raise SizeNotFound if options[:raise_on_failure] && @property == :size && !@size
rescue Timeout::Error, SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ECONNRESET,
ImageFetchFailure, Net::HTTPBadResponse, EOFError, Errno::ENOENT
raise ImageFetchFailure if options[:raise_on_failure]
rescue NoMethodError # 1.8.7p248 can raise this due to a net/http bug
raise ImageFetchFailure if options[:raise_on_failure]
rescue UnknownImageType
raise UnknownImageType if options[:raise_on_failure]
end
private private
def fetch_using_http def fetch_using_http
setup_http setup_http
@http.request_get(@parsed_uri.request_uri) do |res| @http.request_get(@parsed_uri.request_uri) do |res|
raise ImageFetchFailure unless res.is_a?(Net::HTTPSuccess) raise ImageFetchFailure unless res.is_a?(Net::HTTPSuccess)
res.read_body do |str| res.read_body do |str|
break if parse_packet(str) break if parse_packet(str)
end
end end
end end
end
def setup_http
@http = Net::HTTP.new(@parsed_uri.host, @parsed_uri.port) def setup_http
@http.use_ssl = (@parsed_uri.scheme == "https") @http = Net::HTTP.new(@parsed_uri.host, @parsed_uri.port)
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE @http.use_ssl = (@parsed_uri.scheme == "https")
@http.open_timeout = @timeout @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
@http.read_timeout = @timeout @http.open_timeout = @timeout
end @http.read_timeout = @timeout
end
def fetch_using_open_uri
open(@uri) do |s| def fetch_using_open_uri
while str = s.read(LocalFileChunkSize) open(@uri) do |s|
break if parse_packet(str) while str = s.read(LocalFileChunkSize)
end break if parse_packet(str)
end end
end end
end
# returns true once result is achieved
# # returns true once result is achieved
def parse_packet(str) #
@str = (@unused_str || "") + str def parse_packet(str)
@strpos = 0 @str = (@unused_str || "") + str
begin @strpos = 0
result = send("parse_#{@property}") begin
if result result = send("parse_#{@property}")
instance_variable_set("@#{@property}", result) if result
true instance_variable_set("@#{@property}", result)
end true
rescue MoreCharsNeeded end
false rescue MoreCharsNeeded
end false
end end
end
def parse_size
@type = parse_type unless @type def parse_size
send("parse_size_for_#{@type}") @type = parse_type unless @type
end @strpos = 0
send("parse_size_for_#{@type}")
def get_chars(n) end
if @strpos + n - 1 >= @str.size
@unused_str = @str[@strpos..-1] def get_chars(n)
raise MoreCharsNeeded if @strpos + n - 1 >= @str.size
else @unused_str = @str[@strpos..-1]
result = @str[@strpos..(@strpos + n - 1)] raise MoreCharsNeeded
@strpos += n else
result result = @str[@strpos..(@strpos + n - 1)]
end @strpos += n
end result
end
def get_byte end
get_chars(1).unpack("C")[0]
end def get_byte
get_chars(1).unpack("C")[0]
def read_int(str) end
size_bytes = str.unpack("CC")
(size_bytes[0] << 8) + size_bytes[1] def read_int(str)
end size_bytes = str.unpack("CC")
(size_bytes[0] << 8) + size_bytes[1]
def parse_type end
case get_chars(2)
when "BM" def parse_type
:bmp case get_chars(2)
when "GI" when "BM"
:gif :bmp
when 0xff.chr + 0xd8.chr when "GI"
:jpeg :gif
when 0x89.chr + "P" when 0xff.chr + 0xd8.chr
:png :jpeg
else when 0x89.chr + "P"
raise UnknownImageType :png
end else
end raise UnknownImageType
end
def parse_size_for_gif end
get_chars(9)[4..8].unpack('SS')
end def parse_size_for_gif
get_chars(11)[6..10].unpack('SS')
def parse_size_for_png end
get_chars(23)[14..22].unpack('NN')
end def parse_size_for_png
get_chars(25)[16..24].unpack('NN')
def parse_size_for_jpeg end
loop do
@state = case @state def parse_size_for_jpeg
when nil loop do
get_chars(2) @state = case @state
:started when nil
when :started get_chars(2)
get_byte == 0xFF ? :sof : :started :started
when :sof when :started
c = get_byte get_byte == 0xFF ? :sof : :started
if (0xe0..0xef).include?(c) when :sof
:skipframe c = get_byte
elsif [0xC0..0xC3, 0xC5..0xC7, 0xC9..0xCB, 0xCD..0xCF].detect {|r| r.include? c} if (0xe0..0xef).include?(c)
:readsize :skipframe
else elsif [0xC0..0xC3, 0xC5..0xC7, 0xC9..0xCB, 0xCD..0xCF].detect {|r| r.include? c}
:skipframe :readsize
end else
when :skipframe :skipframe
@skip_chars = read_int(get_chars(2)) - 2 end
:do_skip when :skipframe
when :do_skip @skip_chars = read_int(get_chars(2)) - 2
get_chars(@skip_chars) :do_skip
:started when :do_skip
when :readsize get_chars(@skip_chars)
s = get_chars(7) :started
return [read_int(s[5..6]), read_int(s[3..4])] when :readsize
end s = get_chars(7)
end return [read_int(s[5..6]), read_int(s[3..4])]
end end
end
def parse_size_for_bmp end
d = get_chars(27)[12..26]
d[0] == 40 ? d[4..-1].unpack('LL') : d[4..8].unpack('SS') def parse_size_for_bmp
end d = get_chars(29)[14..28]
d.unpack("C")[0] == 40 ? d[4..-1].unpack('LL') : d[4..8].unpack('SS')
end end
end end