diff --git a/middleman-core/lib/middleman-core/core_extensions/extensions.rb b/middleman-core/lib/middleman-core/core_extensions/extensions.rb index 7b541383..580dfc7d 100644 --- a/middleman-core/lib/middleman-core/core_extensions/extensions.rb +++ b/middleman-core/lib/middleman-core/core_extensions/extensions.rb @@ -38,6 +38,7 @@ module Middleman # @private def registered(app) app.define_hook :initialized + app.define_hook :instance_available app.define_hook :after_configuration app.define_hook :before_configuration app.define_hook :build_config @@ -69,15 +70,19 @@ module Middleman # @param [Hash] options Per-extension options hash # @return [void] def register(extension, options={}, &block) - extend extension - if extension.respond_to?(:registered) - if extension.method(:registered).arity === 1 - extension.registered(self, &block) - else - extension.registered(self, options, &block) + if extension.instance_of?(Class) && extension.ancestors.include?(::Middleman::Extension) + extension.new(self, options, &block) + else + extend extension + if extension.respond_to?(:registered) + if extension.method(:registered).arity === 1 + extension.registered(self, &block) + else + extension.registered(self, options, &block) + end end + extension end - extension end end @@ -133,11 +138,12 @@ module Middleman super self.class.inst = self - run_hook :before_configuration # Search the root of the project for required files $LOAD_PATH.unshift(root) + run_hook :initialized + if config[:autoload_sprockets] begin require "middleman-sprockets" @@ -146,6 +152,8 @@ module Middleman end end + run_hook :before_configuration + # Check for and evaluate local configuration local_config = File.join(root, "config.rb") if File.exists? local_config @@ -156,7 +164,7 @@ module Middleman run_hook :build_config if build? run_hook :development_config if development? - run_hook :initialized + run_hook :instance_available # This is for making the tests work - since the tests # don't completely reload middleman, I18n.load_path can get diff --git a/middleman-core/lib/middleman-core/core_extensions/front_matter.rb b/middleman-core/lib/middleman-core/core_extensions/front_matter.rb index 07fb328f..57044ce6 100644 --- a/middleman-core/lib/middleman-core/core_extensions/front_matter.rb +++ b/middleman-core/lib/middleman-core/core_extensions/front_matter.rb @@ -1,187 +1,55 @@ require "active_support/core_ext/hash/keys" require 'pathname' +# Parsing YAML frontmatter +require "yaml" + +# Parsing JSON frontmatter +require "active_support/json" + # Extensions namespace module Middleman::CoreExtensions - # Frontmatter namespace - module FrontMatter + class FrontMatter < ::Middleman::Extension - # Setup extension - class << self + YAML_ERRORS = [ StandardError ] - # Once registered - def registered(app) - # Parsing YAML frontmatter - require "yaml" - - # Parsing JSON frontmatter - require "active_support/json" - - app.send :include, InstanceMethods - - app.before_configuration do - files.changed { |file| frontmatter_manager.clear_data(file) } - files.deleted { |file| frontmatter_manager.clear_data(file) } - end - - app.after_configuration do - ::Middleman::Sitemap::Resource.send :include, ResourceInstanceMethods - - ignore %r{\.frontmatter$} - - sitemap.provides_metadata do |path| - fmdata = frontmatter_manager.data(path).first || {} - - data = {} - [:layout, :layout_engine].each do |opt| - data[opt] = fmdata[opt] unless fmdata[opt].nil? - end - - { :options => data, :page => ::Middleman::Util.recursively_enhance(fmdata).freeze } - end - end - end - alias :included :registered + # https://github.com/tenderlove/psych/issues/23 + if defined?(Psych) && defined?(Psych::SyntaxError) + YAML_ERRORS << Psych::SyntaxError end - class FrontmatterManager - attr_reader :app - delegate :logger, :to => :app + def initialize(app, options_hash={}, &block) + super - def initialize(app) - @app = app - @cache = {} - end + @cache = {} + end - def data(path) - p = normalize_path(path) - @cache[p] ||= begin - file_data, content = frontmatter_and_content(p) + def before_configuration + ext = self + app.files.changed { |file| ext.clear_data(file) } + app.files.deleted { |file| ext.clear_data(file) } + end - if @app.files.exists?("#{path}.frontmatter") - external_data, _ = frontmatter_and_content("#{p}.frontmatter") + def after_configuration + app.extensions[:frontmatter] = self + app.ignore %r{\.frontmatter$} - [ - external_data.deep_merge(file_data), - content - ] - else - [file_data, content] - end - end - end + ::Middleman::Sitemap::Resource.send :include, ResourceInstanceMethods - def clear_data(file) - # Copied from Sitemap::Store#file_to_path, but without - # removing the file extension - file = File.join(@app.root, file) - prefix = @app.source_dir.sub(/\/$/, "") + "/" - return unless file.include?(prefix) - path = file.sub(prefix, "").sub(/\.frontmatter$/, "") - - @cache.delete(path) - end - - YAML_ERRORS = [ StandardError ] - - # https://github.com/tenderlove/psych/issues/23 - if defined?(Psych) && defined?(Psych::SyntaxError) - YAML_ERRORS << Psych::SyntaxError - end - - # Parse YAML frontmatter out of a string - # @param [String] content - # @return [Array] - def parse_yaml_front_matter(content) - yaml_regex = /\A(---\s*\n.*?\n?)^(---\s*$\n?)/m - if content =~ yaml_regex - content = content.sub(yaml_regex, "") - - begin - data = YAML.load($1) || {} - data = data.symbolize_keys - rescue *YAML_ERRORS => e - logger.error "YAML Exception: #{e.message}" - return false - end - else - return false - end - - [data, content] - rescue - [{}, content] - end - - def parse_json_front_matter(content) - json_regex = /\A(;;;\s*\n.*?\n?)^(;;;\s*$\n?)/m - - if content =~ json_regex - content = content.sub(json_regex, "") - - begin - json = ($1+$2).sub(";;;", "{").sub(";;;", "}") - data = ActiveSupport::JSON.decode(json).symbolize_keys - rescue => e - logger.error "JSON Exception: #{e.message}" - return false - end - - else - return false - end - - [data, content] - rescue - [{}, content] - end - - # Get the frontmatter and plain content from a file - # @param [String] path - # @return [Array] - def frontmatter_and_content(path) - full_path = if Pathname(path).relative? - File.join(@app.source_dir, path) - else - path - end + app.sitemap.provides_metadata do |path| + fmdata = data(path).first || {} data = {} - content = nil - - return [data, content] unless @app.files.exists?(full_path) - - if !::Middleman::Util.binary?(full_path) - content = File.read(full_path) - - begin - if content =~ /\A.*coding:/ - lines = content.split(/\n/) - lines.shift - content = lines.join("\n") - end - - if result = parse_yaml_front_matter(content) - data, content = result - elsif result = parse_json_front_matter(content) - data, content = result - end - rescue - # Probably a binary file, move on - end + [:layout, :layout_engine].each do |opt| + data[opt] = fmdata[opt] unless fmdata[opt].nil? end - [data, content] - end - - def normalize_path(path) - path.sub(%r{^#{@app.source_dir}\/}, "") + { :options => data, :page => ::Middleman::Util.recursively_enhance(fmdata).freeze } end end - + module ResourceInstanceMethods - def ignored? if !proxy? && raw_data[:ignored] == true true @@ -195,7 +63,7 @@ module Middleman::CoreExtensions # @private # @return [Hash] def raw_data - app.frontmatter_manager.data(source_file).first + app.extensions[:frontmatter].data(source_file).first end # This page's frontmatter @@ -217,19 +85,132 @@ module Middleman::CoreExtensions end end - module InstanceMethods - - # Access the Frontmatter API - def frontmatter_manager - @_frontmatter_manager ||= FrontmatterManager.new(self) - end - + helpers do # Get the template data from a path # @param [String] path # @return [String] def template_data_for_file(path) - frontmatter_manager.data(path).last + extensions[:frontmatter].data(path).last end end + + def data(path) + p = normalize_path(path) + @cache[p] ||= begin + file_data, content = frontmatter_and_content(p) + + if app.files.exists?("#{path}.frontmatter") + external_data, _ = frontmatter_and_content("#{p}.frontmatter") + + [ + external_data.deep_merge(file_data), + content + ] + else + [file_data, content] + end + end + end + + def clear_data(file) + # Copied from Sitemap::Store#file_to_path, but without + # removing the file extension + file = File.join(app.root, file) + prefix = app.source_dir.sub(/\/$/, "") + "/" + return unless file.include?(prefix) + path = file.sub(prefix, "").sub(/\.frontmatter$/, "") + + @cache.delete(path) + end + + private + # Parse YAML frontmatter out of a string + # @param [String] content + # @return [Array] + def parse_yaml_front_matter(content) + yaml_regex = /\A(---\s*\n.*?\n?)^(---\s*$\n?)/m + if content =~ yaml_regex + content = content.sub(yaml_regex, "") + + begin + data = YAML.load($1) || {} + data = data.symbolize_keys + rescue *YAML_ERRORS => e + app.logger.error "YAML Exception: #{e.message}" + return false + end + else + return false + end + + [data, content] + rescue + [{}, content] + end + + def parse_json_front_matter(content) + json_regex = /\A(;;;\s*\n.*?\n?)^(;;;\s*$\n?)/m + + if content =~ json_regex + content = content.sub(json_regex, "") + + begin + json = ($1+$2).sub(";;;", "{").sub(";;;", "}") + data = ActiveSupport::JSON.decode(json).symbolize_keys + rescue => e + app.logger.error "JSON Exception: #{e.message}" + return false + end + + else + return false + end + + [data, content] + rescue + [{}, content] + end + + # Get the frontmatter and plain content from a file + # @param [String] path + # @return [Array] + def frontmatter_and_content(path) + full_path = if Pathname(path).relative? + File.join(app.source_dir, path) + else + path + end + + data = {} + content = nil + + return [data, content] unless app.files.exists?(full_path) + + if !::Middleman::Util.binary?(full_path) + content = File.read(full_path) + + begin + if content =~ /\A.*coding:/ + lines = content.split(/\n/) + lines.shift + content = lines.join("\n") + end + + if result = parse_yaml_front_matter(content) + data, content = result + elsif result = parse_json_front_matter(content) + data, content = result + end + rescue + # Probably a binary file, move on + end + end + + [data, content] + end + + def normalize_path(path) + path.sub(%r{^#{app.source_dir}\/}, "") + end end end diff --git a/middleman-core/lib/middleman-core/extensions.rb b/middleman-core/lib/middleman-core/extensions.rb index aceabbad..1956bf6a 100644 --- a/middleman-core/lib/middleman-core/extensions.rb +++ b/middleman-core/lib/middleman-core/extensions.rb @@ -133,7 +133,8 @@ module Middleman end end - attr_accessor :app, :options + attr_accessor :options + attr_reader :app def initialize(klass, options_hash={}) @_helpers = [] @@ -148,14 +149,21 @@ module Middleman yield @options if block_given? ext = self + klass.initialized do ext.app = self - - (ext.class.defined_helpers || []).each do |m| - ext.app.class.send(:include, m) + end + + if ext.respond_to?(:before_configuration) + klass.before_configuration do + ext.before_configuration end end + klass.instance_available do + ext.app ||= self + end + klass.after_configuration do if ext.respond_to?(:after_configuration) ext.after_configuration @@ -176,5 +184,13 @@ module Middleman end end end + + def app=(app) + @app = app + + (self.class.defined_helpers || []).each do |m| + app.class.send(:include, m) + end + end end end diff --git a/middleman-core/lib/middleman-more/extensions/gzip.rb b/middleman-core/lib/middleman-more/extensions/gzip.rb index f5ff6d38..165161fe 100644 --- a/middleman-core/lib/middleman-more/extensions/gzip.rb +++ b/middleman-core/lib/middleman-more/extensions/gzip.rb @@ -14,7 +14,7 @@ class Middleman::Extensions::Gzip < ::Middleman::Extension def initialize(app, options_hash={}) super - + require 'zlib' require 'stringio' require 'find' diff --git a/middleman-core/lib/middleman-more/extensions/minify_css.rb b/middleman-core/lib/middleman-more/extensions/minify_css.rb index 7cec5461..87e5fafe 100644 --- a/middleman-core/lib/middleman-more/extensions/minify_css.rb +++ b/middleman-core/lib/middleman-more/extensions/minify_css.rb @@ -29,6 +29,7 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension # Rack middleware to look for CSS and compress it class Rack + INLINE_CSS_REGEX = /(]*>\s*(?:\/\*\*\/)?\s*<\/style>)/m # Init # @param [Class] app @@ -46,26 +47,16 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension def call(env) status, headers, response = @app.call(env) - path = env["PATH_INFO"] - - if (path.end_with?('.html') || path.end_with?('.php')) && @inline - uncompressed_source = ::Middleman::Util.extract_response_text(response) - - minified = uncompressed_source.gsub(/(]*>\s*(?:\/\*\*\/)?\s*<\/style>)/m) do |match| - first = $1 - css = $2 - last = $3 - - minified_css = @compressor.compress(css) - - first << minified_css << last + if inline_html_content?(env["PATH_INFO"]) + minified = ::Middleman::Util.extract_response_text(response) + minified.gsub!(INLINE_CSS_REGEX) do |match| + $1 << @compressor.compress($2) << $3 end headers["Content-Length"] = ::Rack::Utils.bytesize(minified).to_s response = [minified] - elsif path.end_with?('.css') && @ignore.none? {|ignore| Middleman::Util.path_match(ignore, path) } - uncompressed_source = ::Middleman::Util.extract_response_text(response) - minified_css = @compressor.compress(uncompressed_source) + elsif standalone_css_content?(env["PATH_INFO"]) + minified_css = @compressor.compress(::Middleman::Util.extract_response_text(response)) headers["Content-Length"] = ::Rack::Utils.bytesize(minified_css).to_s response = [minified_css] @@ -73,5 +64,14 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension [status, headers, response] end + + private + def inline_html_content?(path) + (path.end_with?('.html') || path.end_with?('.php')) && @inline + end + + def standalone_css_content?(path) + path.end_with?('.css') && @ignore.none? {|ignore| Middleman::Util.path_match(ignore, path) } + end end end