diff --git a/.gitignore b/.gitignore index 833a1503..2b4e408c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ pkg Gemfile.lock docs .rbenv-* +.ruby-version .*.swp build doc @@ -15,4 +16,4 @@ doc tmp Makefile .mm-pid-* -.idea \ No newline at end of file +.idea diff --git a/middleman-core/lib/middleman-core/core_extensions/extensions.rb b/middleman-core/lib/middleman-core/core_extensions/extensions.rb index 7515a631..12469eed 100644 --- a/middleman-core/lib/middleman-core/core_extensions/extensions.rb +++ b/middleman-core/lib/middleman-core/core_extensions/extensions.rb @@ -37,6 +37,7 @@ module Middleman class << self # @private def registered(app) + app.define_hook :initialized app.define_hook :after_configuration app.define_hook :before_configuration app.define_hook :build_config @@ -68,19 +69,15 @@ module Middleman # @param [Hash] options Per-extension options hash # @return [void] def register(extension, options={}, &block) - if extension.instance_of? Module - extend extension - if extension.respond_to?(:registered) - if extension.method(:registered).arity === 1 - extension.registered(self, &block) - else - extension.registered(self, options, &block) - end + extend extension + if extension.respond_to?(:registered) + if extension.method(:registered).arity === 1 + extension.registered(self, &block) + else + extension.registered(self, options, &block) end - extension - elsif extension.instance_of?(Class) && extension.ancestors.include?(::Middleman::Extension) - extension.new(self, options, &block) end + extension end end @@ -105,7 +102,22 @@ module Middleman logger.error "== Unknown Extension: #{ext}" else logger.debug "== Activating: #{ext}" - extensions[ext] = self.class.register(ext_module, options, &block) + + if ext_module.instance_of? Module + extensions[ext] = self.class.register(ext_module, options, &block) + elsif ext_module.instance_of?(Class) && ext_module.ancestors.include?(::Middleman::Extension) + if ext_module.supports_multiple_instances? + extensions[ext] ||= {} + key = "instance_#{extensions[ext].keys.length}" + extensions[ext][key] = ext_module.new(self.class, options, &block) + else + if extensions[ext] + logger.error "== #{ext} already activated. Overwriting." + end + + extensions[ext] = ext_module.new(self.class, options, &block) + end + end end end @@ -141,6 +153,8 @@ module Middleman instance_eval File.read(local_config), local_config, 1 end + run_hook :initialized + run_hook :build_config if build? run_hook :development_config if development? @@ -148,7 +162,13 @@ module Middleman logger.debug "Loaded extensions:" self.extensions.each do |ext,_| - logger.debug "== Extension: #{ext}" + if ext.is_a?(Hash) + ext.each do |k,_| + logger.debug "== Extension: #{k}" + end + else + logger.debug "== Extension: #{ext}" + end end end end diff --git a/middleman-core/lib/middleman-core/extensions.rb b/middleman-core/lib/middleman-core/extensions.rb index 6000877a..b5fbc33d 100644 --- a/middleman-core/lib/middleman-core/extensions.rb +++ b/middleman-core/lib/middleman-core/extensions.rb @@ -1,3 +1,5 @@ +require "active_support/core_ext/class/attribute" + module Middleman module Extensions @@ -102,6 +104,8 @@ module Middleman end class Extension + class_attribute :supports_multiple_instances, :instance_reader => false, :instance_writer => false + class << self def config @_config ||= ::Middleman::Configuration::ConfigurationManager.new @@ -125,10 +129,11 @@ module Middleman yield @options if block_given? ext = self - klass.after_configuration do + klass.initialized do ext.app = self - ext.after_configuration end + + klass.after_configuration(&method(:after_configuration)) end def after_configuration diff --git a/middleman-more/features/asset_host.feature b/middleman-more/features/asset_host.feature index 00e52b59..30746473 100644 --- a/middleman-more/features/asset_host.feature +++ b/middleman-more/features/asset_host.feature @@ -1,7 +1,56 @@ Feature: Alternate between multiple asset hosts In order to speed up page loading - - Scenario: Rendering html with the feature enabled - Given the Server is running at "asset-host-app" + + Scenario: Set single host globally + Given a fixture app "asset-host-app" + And a file named "config.rb" with: + """ + activate :asset_host + set :asset_host, "http://assets1.example.com" + """ + And the Server is running When I go to "/asset_host.html" + Then I should see "http://assets1" + When I go to "/stylesheets/asset_host.css" + Then I should see "http://assets1" + + Scenario: Set proc host globally + Given a fixture app "asset-host-app" + And a file named "config.rb" with: + """ + activate :asset_host + set :asset_host do |asset| + "http://assets%d.example.com" % (asset.hash % 4) + end + """ + And the Server is running + When I go to "/asset_host.html" + Then I should see "http://assets" + When I go to "/stylesheets/asset_host.css" + Then I should see "http://assets" + + Scenario: Set single host with inline-option + Given a fixture app "asset-host-app" + And a file named "config.rb" with: + """ + activate :asset_host, :host => "http://assets1.example.com" + """ + And the Server is running + When I go to "/asset_host.html" + Then I should see "http://assets1" + When I go to "/stylesheets/asset_host.css" + Then I should see "http://assets1" + + Scenario: Set proc host with inline-option + Given a fixture app "asset-host-app" + And a file named "config.rb" with: + """ + activate :asset_host, :host => Proc.new { |asset| + "http://assets%d.example.com" % (asset.hash % 4) + } + """ + And the Server is running + When I go to "/asset_host.html" + Then I should see "http://assets" + When I go to "/stylesheets/asset_host.css" Then I should see "http://assets" \ No newline at end of file diff --git a/middleman-more/features/asset_host_compass.feature b/middleman-more/features/asset_host_compass.feature deleted file mode 100644 index 7f477b63..00000000 --- a/middleman-more/features/asset_host_compass.feature +++ /dev/null @@ -1,7 +0,0 @@ -Feature: Alternate between multiple asset hosts - In order to speed up page loading - - Scenario: Rendering css with the feature enabled - Given the Server is running at "asset-host-app" - When I go to "/stylesheets/asset_host.css" - Then I should see "http://assets" \ No newline at end of file diff --git a/middleman-more/fixtures/asset-host-app/config.rb b/middleman-more/fixtures/asset-host-app/config.rb index 8d363a55..e69de29b 100644 --- a/middleman-more/fixtures/asset-host-app/config.rb +++ b/middleman-more/fixtures/asset-host-app/config.rb @@ -1,6 +0,0 @@ -set :layout, false - -activate :asset_host -set :asset_host do |asset| - "http://assets%d.example.com" % (asset.hash % 4) -end diff --git a/middleman-more/lib/middleman-more.rb b/middleman-more/lib/middleman-more.rb index c4c771fe..df73efa9 100644 --- a/middleman-more/lib/middleman-more.rb +++ b/middleman-more/lib/middleman-more.rb @@ -83,7 +83,7 @@ module Middleman # to avoid browser caches failing to update to your new content. Middleman::Extensions.register(:asset_hash) do require "middleman-more/extensions/asset_hash" - Middleman::Extensions::AssetHash::Extension + Middleman::Extensions::AssetHash end # AssetHost allows you to setup multiple domains to host your static diff --git a/middleman-more/lib/middleman-more/core_extensions/compass.rb b/middleman-more/lib/middleman-more/core_extensions/compass.rb index e06380be..584f67a0 100644 --- a/middleman-more/lib/middleman-more/core_extensions/compass.rb +++ b/middleman-more/lib/middleman-more/core_extensions/compass.rb @@ -52,8 +52,14 @@ module Middleman # No line-comments in test mode (changing paths mess with sha1) compass_config.line_comments = false if ENV["TEST"] - if config.defines_setting?(:asset_host) && config[:asset_host].is_a?(Proc) - compass_config.asset_host(&config[:asset_host]) + if extensions[:asset_host] && asset_host = extensions[:asset_host].host + if asset_host.is_a?(Proc) + compass_config.asset_host(&asset_host) + else + compass_config.asset_host do |asset| + asset_host + end + end end end diff --git a/middleman-more/lib/middleman-more/extensions/asset_hash.rb b/middleman-more/lib/middleman-more/extensions/asset_hash.rb index ca710a4b..93e86a06 100644 --- a/middleman-more/lib/middleman-more/extensions/asset_hash.rb +++ b/middleman-more/lib/middleman-more/extensions/asset_hash.rb @@ -1,119 +1,105 @@ module Middleman module Extensions - module AssetHash - class Extension < ::Middleman::Extension - option :exts, %w(.jpg .jpeg .png .gif .js .css .otf .woff .eot .ttf .svg), "List of extensions that get asset hashes appended to them." - option :ignore, [], "Regexes of filenames to skip adding asset hashes to" + class AssetHash < ::Middleman::Extension + option :exts, %w(.jpg .jpeg .png .gif .js .css .otf .woff .eot .ttf .svg), "List of extensions that get asset hashes appended to them." + option :ignore, [], "Regexes of filenames to skip adding asset hashes to" - def initialize(app, options_hash={}) - super + def initialize(app, options_hash={}) + super - require 'digest/sha1' - require 'rack/test' - require 'uri' - end - - def after_configuration - # Allow specifying regexes to ignore, plus always ignore apple touch icons - ignore = Array(options.ignore) + [/^apple-touch-icon/] - - app.sitemap.register_resource_list_manipulator( - :asset_hash, - AssetHashManager.new(app, options.exts, ignore) - ) - - app.use Middleware, :exts => options.exts, :middleman_app => app, :ignore => ignore - end + require 'digest/sha1' + require 'rack/test' + require 'uri' end - # Central class for managing asset_hash extension - class AssetHashManager - def initialize(app, exts, ignore) - @app = app - @exts = exts - @ignore = ignore - end + def after_configuration + # Allow specifying regexes to ignore, plus always ignore apple touch icons + @ignore = Array(options.ignore) + [/^apple-touch-icon/] - # Update the main sitemap resource list - # @return [void] - def manipulate_resource_list(resources) - # Process resources in order: binary images and fonts, then SVG, then JS/CSS. - # This is so by the time we get around to the text files (which may reference - # images and fonts) the static assets' hashes are already calculated. - rack_client = ::Rack::Test::Session.new(@app.class.to_rack_app) - resources.sort_by do |a| - if %w(.svg).include? a.ext - 0 - elsif %w(.js .css).include? a.ext - 1 - else - -1 - end - end.each do |resource| - next unless @exts.include? resource.ext - next if @ignore.any? { |ignore| Middleman::Util.path_match(ignore, resource.destination_path) } + app.sitemap.register_resource_list_manipulator(:asset_hash, self) - # Render through the Rack interface so middleware and mounted apps get a shot - response = rack_client.get(URI.escape(resource.destination_path), {}, { "bypass_asset_hash" => "true" }) - raise "#{resource.path} should be in the sitemap!" unless response.status == 200 + app.use Middleware, :exts => options.exts, :middleman_app => app, :ignore => @ignore + end - digest = Digest::SHA1.hexdigest(response.body)[0..7] - - resource.destination_path = resource.destination_path.sub(/\.(\w+)$/) { |ext| "-#{digest}#{ext}" } + # Update the main sitemap resource list + # @return [void] + def manipulate_resource_list(resources) + # Process resources in order: binary images and fonts, then SVG, then JS/CSS. + # This is so by the time we get around to the text files (which may reference + # images and fonts) the static assets' hashes are already calculated. + rack_client = ::Rack::Test::Session.new(@app.class.to_rack_app) + resources.sort_by do |a| + if %w(.svg).include? a.ext + 0 + elsif %w(.js .css).include? a.ext + 1 + else + -1 end + end.each do |resource| + next unless options.exts.include? resource.ext + next if @ignore.any? { |ignore| Middleman::Util.path_match(ignore, resource.destination_path) } + + # Render through the Rack interface so middleware and mounted apps get a shot + response = rack_client.get(URI.escape(resource.destination_path), {}, { "bypass_asset_hash" => "true" }) + raise "#{resource.path} should be in the sitemap!" unless response.status == 200 + + digest = Digest::SHA1.hexdigest(response.body)[0..7] + + resource.destination_path = resource.destination_path.sub(/\.(\w+)$/) { |ext| "-#{digest}#{ext}" } end end + end - # The asset hash middleware is responsible for rewriting references to - # assets to include their new, hashed name. - class Middleware - def initialize(app, options={}) - @rack_app = app - @exts = options[:exts] - @ignore = options[:ignore] - @exts_regex_text = @exts.map {|e| Regexp.escape(e) }.join('|') - @middleman_app = options[:middleman_app] - end + # The asset hash middleware is responsible for rewriting references to + # assets to include their new, hashed name. + class Middleware + def initialize(app, options={}) + @rack_app = app + @exts = options[:exts] + @ignore = options[:ignore] + @exts_regex_text = @exts.map {|e| Regexp.escape(e) }.join('|') + @middleman_app = options[:middleman_app] + end - def call(env) - status, headers, response = @rack_app.call(env) + def call(env) + status, headers, response = @rack_app.call(env) - # We don't want to use this middleware when rendering files to figure out their hash! - return [status, headers, response] if env["bypass_asset_hash"] == 'true' + # We don't want to use this middleware when rendering files to figure out their hash! + return [status, headers, response] if env["bypass_asset_hash"] == 'true' - path = @middleman_app.full_path(env["PATH_INFO"]) - dirpath = Pathname.new(File.dirname(path)) + path = @middleman_app.full_path(env["PATH_INFO"]) + dirpath = Pathname.new(File.dirname(path)) - if path =~ /(^\/$)|(\.(htm|html|php|css|js)$)/ - body = ::Middleman::Util.extract_response_text(response) + if path =~ /(^\/$)|(\.(htm|html|php|css|js)$)/ + body = ::Middleman::Util.extract_response_text(response) - if body - # TODO: This regex will change some paths in plan HTML (not in a tag) - is that OK? - body.gsub! /([=\'\"\(]\s*)([^\s\'\"\)]+(#{@exts_regex_text}))/ do |match| - opening_character = $1 - asset_path = $2 + if body + # TODO: This regex will change some paths in plan HTML (not in a tag) - is that OK? + body.gsub!(/([=\'\"\(]\s*)([^\s\'\"\)]+(#{@exts_regex_text}))/) do |match| + opening_character = $1 + asset_path = $2 - relative_path = Pathname.new(asset_path).relative? + relative_path = Pathname.new(asset_path).relative? - asset_path = dirpath.join(asset_path).to_s if relative_path + asset_path = dirpath.join(asset_path).to_s if relative_path - if @ignore.any? { |r| asset_path.match(r) } - match - elsif asset_page = @middleman_app.sitemap.find_resource_by_path(asset_path) - replacement_path = "/#{asset_page.destination_path}" - replacement_path = Pathname.new(replacement_path).relative_path_from(dirpath).to_s if relative_path + if @ignore.any? { |r| asset_path.match(r) } + match + elsif asset_page = @middleman_app.sitemap.find_resource_by_path(asset_path) + replacement_path = "/#{asset_page.destination_path}" + replacement_path = Pathname.new(replacement_path).relative_path_from(dirpath).to_s if relative_path - "#{opening_character}#{replacement_path}" - else - match - end + "#{opening_character}#{replacement_path}" + else + match end - - status, headers, response = Rack::Response.new(body, status, headers).finish end + + status, headers, response = Rack::Response.new(body, status, headers).finish end - [status, headers, response] end + [status, headers, response] end end end diff --git a/middleman-more/lib/middleman-more/extensions/asset_host.rb b/middleman-more/lib/middleman-more/extensions/asset_host.rb index deb708d3..8dfa4828 100644 --- a/middleman-more/lib/middleman-more/extensions/asset_host.rb +++ b/middleman-more/lib/middleman-more/extensions/asset_host.rb @@ -3,24 +3,19 @@ module Middleman module Extensions # Asset Host module - module AssetHost + class AssetHost < ::Middleman::Extension + option :host, nil, 'The asset host to use, or false for no asset host, or a Proc to determine asset host' - # Setup extension - class << self + def initialize(app, options_hash={}, &block) + super - # Once registered - def registered(app, options={}) - app.config.define_setting :asset_host, false, 'The asset host to use, or false for no asset host, or a Proc to determine asset host' + # Backwards compatible API + app.config.define_setting :asset_host, nil, 'The asset host to use, or false for no asset host, or a Proc to determine asset host' + app.send :include, InstanceMethods + end - if options[:host] - config[:asset_host] = options[:host] - end - - # Include methods - app.send :include, InstanceMethods - end - - alias :included :registered + def host + app.config[:asset_host] || options[:host] end # Asset Host Instance Methods @@ -32,13 +27,15 @@ module Middleman # @param [String] prefix # @return [String] def asset_url(path, prefix="") - original_output = super - return original_output unless config[:asset_host] + controller = extensions[:asset_host] - asset_prefix = if config[:asset_host].is_a?(Proc) - config[:asset_host].call(original_output) - elsif config[:asset_host].is_a?(String) - config[:asset_host] + original_output = super + return original_output unless controller.host + + asset_prefix = if controller.host.is_a?(Proc) + controller.host.call(original_output) + elsif controller.host.is_a?(String) + controller.host end File.join(asset_prefix, original_output)