fix auto image sizes, make default helpers first feature
This commit is contained in:
parent
8b00e830f9
commit
d638fe8ce2
3 changed files with 239 additions and 234 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
Loading…
Reference in a new issue