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
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,6 +70,9 @@ module Middleman
# @param [Hash] options Per-extension options hash
# @return [void]
def register(extension, 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
@ -80,6 +84,7 @@ module Middleman
extension
end
end
end
# Instance methods
module InstanceMethods
@ -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

View file

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

View file

@ -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,12 +149,19 @@ 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
@ -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

View file

@ -29,6 +29,7 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
# Rack middleware to look for CSS and compress it
class Rack
INLINE_CSS_REGEX = /(<style[^>]*>\s*(?:\/\*<!\[CDATA\[\*\/\n)?)(.*?)((?:(?:\n\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(/(<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
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