Make Frontmatter a class-based extension to simplify file

This commit is contained in:
Thomas Reynolds 2013-05-24 17:11:46 -07:00
parent 5104579623
commit 950aace674
5 changed files with 206 additions and 201 deletions

View file

@ -38,6 +38,7 @@ module Middleman
# @private # @private
def registered(app) def registered(app)
app.define_hook :initialized app.define_hook :initialized
app.define_hook :instance_available
app.define_hook :after_configuration app.define_hook :after_configuration
app.define_hook :before_configuration app.define_hook :before_configuration
app.define_hook :build_config app.define_hook :build_config
@ -69,6 +70,9 @@ module Middleman
# @param [Hash] options Per-extension options hash # @param [Hash] options Per-extension options hash
# @return [void] # @return [void]
def register(extension, options={}, &block) def register(extension, options={}, &block)
if extension.instance_of?(Class) && extension.ancestors.include?(::Middleman::Extension)
extension.new(self, options, &block)
else
extend extension extend extension
if extension.respond_to?(:registered) if extension.respond_to?(:registered)
if extension.method(:registered).arity === 1 if extension.method(:registered).arity === 1
@ -80,6 +84,7 @@ module Middleman
extension extension
end end
end end
end
# Instance methods # Instance methods
module InstanceMethods module InstanceMethods
@ -133,11 +138,12 @@ module Middleman
super super
self.class.inst = self self.class.inst = self
run_hook :before_configuration
# Search the root of the project for required files # Search the root of the project for required files
$LOAD_PATH.unshift(root) $LOAD_PATH.unshift(root)
run_hook :initialized
if config[:autoload_sprockets] if config[:autoload_sprockets]
begin begin
require "middleman-sprockets" require "middleman-sprockets"
@ -146,6 +152,8 @@ module Middleman
end end
end end
run_hook :before_configuration
# Check for and evaluate local configuration # Check for and evaluate local configuration
local_config = File.join(root, "config.rb") local_config = File.join(root, "config.rb")
if File.exists? local_config if File.exists? local_config
@ -156,7 +164,7 @@ module Middleman
run_hook :build_config if build? run_hook :build_config if build?
run_hook :development_config if development? run_hook :development_config if development?
run_hook :initialized run_hook :instance_available
# This is for making the tests work - since the tests # This is for making the tests work - since the tests
# don't completely reload middleman, I18n.load_path can get # don't completely reload middleman, I18n.load_path can get

View file

@ -1,37 +1,44 @@
require "active_support/core_ext/hash/keys" require "active_support/core_ext/hash/keys"
require 'pathname' require 'pathname'
# Extensions namespace
module Middleman::CoreExtensions
# Frontmatter namespace
module FrontMatter
# Setup extension
class << self
# Once registered
def registered(app)
# Parsing YAML frontmatter # Parsing YAML frontmatter
require "yaml" require "yaml"
# Parsing JSON frontmatter # Parsing JSON frontmatter
require "active_support/json" require "active_support/json"
app.send :include, InstanceMethods # Extensions namespace
module Middleman::CoreExtensions
app.before_configuration do class FrontMatter < ::Middleman::Extension
files.changed { |file| frontmatter_manager.clear_data(file) }
files.deleted { |file| frontmatter_manager.clear_data(file) } YAML_ERRORS = [ StandardError ]
# https://github.com/tenderlove/psych/issues/23
if defined?(Psych) && defined?(Psych::SyntaxError)
YAML_ERRORS << Psych::SyntaxError
end end
app.after_configuration do def initialize(app, options_hash={}, &block)
super
@cache = {}
end
def before_configuration
ext = self
app.files.changed { |file| ext.clear_data(file) }
app.files.deleted { |file| ext.clear_data(file) }
end
def after_configuration
app.extensions[:frontmatter] = self
app.ignore %r{\.frontmatter$}
::Middleman::Sitemap::Resource.send :include, ResourceInstanceMethods ::Middleman::Sitemap::Resource.send :include, ResourceInstanceMethods
ignore %r{\.frontmatter$} app.sitemap.provides_metadata do |path|
fmdata = data(path).first || {}
sitemap.provides_metadata do |path|
fmdata = frontmatter_manager.data(path).first || {}
data = {} data = {}
[:layout, :layout_engine].each do |opt| [:layout, :layout_engine].each do |opt|
@ -41,17 +48,50 @@ module Middleman::CoreExtensions
{ :options => data, :page => ::Middleman::Util.recursively_enhance(fmdata).freeze } { :options => data, :page => ::Middleman::Util.recursively_enhance(fmdata).freeze }
end end
end end
module ResourceInstanceMethods
def ignored?
if !proxy? && raw_data[:ignored] == true
true
else
super
end end
alias :included :registered
end end
class FrontmatterManager # This page's frontmatter without being enhanced for access by either symbols or strings.
attr_reader :app # Used internally
delegate :logger, :to => :app # @private
# @return [Hash]
def raw_data
app.extensions[:frontmatter].data(source_file).first
end
def initialize(app) # This page's frontmatter
@app = app # @return [Hash]
@cache = {} def data
@enhanced_data ||= {}
@enhanced_data[raw_data] ||= begin
::Middleman::Util.recursively_enhance(raw_data).freeze
end
end
# Override Resource#content_type to take into account frontmatter
def content_type
# Allow setting content type in frontmatter too
fm_type = raw_data[:content_type]
return fm_type if fm_type
super
end
end
helpers do
# Get the template data from a path
# @param [String] path
# @return [String]
def template_data_for_file(path)
extensions[:frontmatter].data(path).last
end
end end
def data(path) def data(path)
@ -59,7 +99,7 @@ module Middleman::CoreExtensions
@cache[p] ||= begin @cache[p] ||= begin
file_data, content = frontmatter_and_content(p) file_data, content = frontmatter_and_content(p)
if @app.files.exists?("#{path}.frontmatter") if app.files.exists?("#{path}.frontmatter")
external_data, _ = frontmatter_and_content("#{p}.frontmatter") external_data, _ = frontmatter_and_content("#{p}.frontmatter")
[ [
@ -75,21 +115,15 @@ module Middleman::CoreExtensions
def clear_data(file) def clear_data(file)
# Copied from Sitemap::Store#file_to_path, but without # Copied from Sitemap::Store#file_to_path, but without
# removing the file extension # removing the file extension
file = File.join(@app.root, file) file = File.join(app.root, file)
prefix = @app.source_dir.sub(/\/$/, "") + "/" prefix = app.source_dir.sub(/\/$/, "") + "/"
return unless file.include?(prefix) return unless file.include?(prefix)
path = file.sub(prefix, "").sub(/\.frontmatter$/, "") path = file.sub(prefix, "").sub(/\.frontmatter$/, "")
@cache.delete(path) @cache.delete(path)
end end
YAML_ERRORS = [ StandardError ] private
# 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 # Parse YAML frontmatter out of a string
# @param [String] content # @param [String] content
# @return [Array<Hash, String>] # @return [Array<Hash, String>]
@ -102,7 +136,7 @@ module Middleman::CoreExtensions
data = YAML.load($1) || {} data = YAML.load($1) || {}
data = data.symbolize_keys data = data.symbolize_keys
rescue *YAML_ERRORS => e rescue *YAML_ERRORS => e
logger.error "YAML Exception: #{e.message}" app.logger.error "YAML Exception: #{e.message}"
return false return false
end end
else else
@ -124,7 +158,7 @@ module Middleman::CoreExtensions
json = ($1+$2).sub(";;;", "{").sub(";;;", "}") json = ($1+$2).sub(";;;", "{").sub(";;;", "}")
data = ActiveSupport::JSON.decode(json).symbolize_keys data = ActiveSupport::JSON.decode(json).symbolize_keys
rescue => e rescue => e
logger.error "JSON Exception: #{e.message}" app.logger.error "JSON Exception: #{e.message}"
return false return false
end end
@ -142,7 +176,7 @@ module Middleman::CoreExtensions
# @return [Array<Thor::CoreExt::HashWithIndifferentAccess, String>] # @return [Array<Thor::CoreExt::HashWithIndifferentAccess, String>]
def frontmatter_and_content(path) def frontmatter_and_content(path)
full_path = if Pathname(path).relative? full_path = if Pathname(path).relative?
File.join(@app.source_dir, path) File.join(app.source_dir, path)
else else
path path
end end
@ -150,7 +184,7 @@ module Middleman::CoreExtensions
data = {} data = {}
content = nil content = nil
return [data, content] unless @app.files.exists?(full_path) return [data, content] unless app.files.exists?(full_path)
if !::Middleman::Util.binary?(full_path) if !::Middleman::Util.binary?(full_path)
content = File.read(full_path) content = File.read(full_path)
@ -176,60 +210,7 @@ module Middleman::CoreExtensions
end end
def normalize_path(path) def normalize_path(path)
path.sub(%r{^#{@app.source_dir}\/}, "") path.sub(%r{^#{app.source_dir}\/}, "")
end
end
module ResourceInstanceMethods
def ignored?
if !proxy? && raw_data[:ignored] == true
true
else
super
end
end
# This page's frontmatter without being enhanced for access by either symbols or strings.
# Used internally
# @private
# @return [Hash]
def raw_data
app.frontmatter_manager.data(source_file).first
end
# This page's frontmatter
# @return [Hash]
def data
@enhanced_data ||= {}
@enhanced_data[raw_data] ||= begin
::Middleman::Util.recursively_enhance(raw_data).freeze
end
end
# Override Resource#content_type to take into account frontmatter
def content_type
# Allow setting content type in frontmatter too
fm_type = raw_data[:content_type]
return fm_type if fm_type
super
end
end
module InstanceMethods
# Access the Frontmatter API
def frontmatter_manager
@_frontmatter_manager ||= FrontmatterManager.new(self)
end
# Get the template data from a path
# @param [String] path
# @return [String]
def template_data_for_file(path)
frontmatter_manager.data(path).last
end
end end
end end
end end

View file

@ -133,7 +133,8 @@ module Middleman
end end
end end
attr_accessor :app, :options attr_accessor :options
attr_reader :app
def initialize(klass, options_hash={}) def initialize(klass, options_hash={})
@_helpers = [] @_helpers = []
@ -148,12 +149,19 @@ module Middleman
yield @options if block_given? yield @options if block_given?
ext = self ext = self
klass.initialized do klass.initialized do
ext.app = self ext.app = self
(ext.class.defined_helpers || []).each do |m|
ext.app.class.send(:include, m)
end end
if ext.respond_to?(:before_configuration)
klass.before_configuration do
ext.before_configuration
end
end
klass.instance_available do
ext.app ||= self
end end
klass.after_configuration do klass.after_configuration do
@ -176,5 +184,13 @@ module Middleman
end end
end end
end end
def app=(app)
@app = app
(self.class.defined_helpers || []).each do |m|
app.class.send(:include, m)
end
end
end end
end end

View file

@ -29,6 +29,7 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
# Rack middleware to look for CSS and compress it # Rack middleware to look for CSS and compress it
class Rack class Rack
INLINE_CSS_REGEX = /(<style[^>]*>\s*(?:\/\*<!\[CDATA\[\*\/\n)?)(.*?)((?:(?:\n\s*)?\/\*\]\]>\*\/)?\s*<\/style>)/m
# Init # Init
# @param [Class] app # @param [Class] app
@ -46,26 +47,16 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
def call(env) def call(env)
status, headers, response = @app.call(env) status, headers, response = @app.call(env)
path = env["PATH_INFO"] if inline_html_content?(env["PATH_INFO"])
minified = ::Middleman::Util.extract_response_text(response)
if (path.end_with?('.html') || path.end_with?('.php')) && @inline minified.gsub!(INLINE_CSS_REGEX) do |match|
uncompressed_source = ::Middleman::Util.extract_response_text(response) $1 << @compressor.compress($2) << $3
minified = uncompressed_source.gsub(/(<style[^>]*>\s*(?:\/\*<!\[CDATA\[\*\/\n)?)(.*?)((?:(?:\n\s*)?\/\*\]\]>\*\/)?\s*<\/style>)/m) do |match|
first = $1
css = $2
last = $3
minified_css = @compressor.compress(css)
first << minified_css << last
end end
headers["Content-Length"] = ::Rack::Utils.bytesize(minified).to_s headers["Content-Length"] = ::Rack::Utils.bytesize(minified).to_s
response = [minified] response = [minified]
elsif path.end_with?('.css') && @ignore.none? {|ignore| Middleman::Util.path_match(ignore, path) } elsif standalone_css_content?(env["PATH_INFO"])
uncompressed_source = ::Middleman::Util.extract_response_text(response) minified_css = @compressor.compress(::Middleman::Util.extract_response_text(response))
minified_css = @compressor.compress(uncompressed_source)
headers["Content-Length"] = ::Rack::Utils.bytesize(minified_css).to_s headers["Content-Length"] = ::Rack::Utils.bytesize(minified_css).to_s
response = [minified_css] response = [minified_css]
@ -73,5 +64,14 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
[status, headers, response] [status, headers, response]
end 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
end end