2012-10-25 00:02:32 +02:00
|
|
|
require "active_support/core_ext/hash/keys"
|
2013-02-06 22:19:09 +01:00
|
|
|
require 'pathname'
|
2012-10-25 00:02:32 +02:00
|
|
|
|
2012-05-09 06:05:55 +02:00
|
|
|
# Extensions namespace
|
|
|
|
module Middleman::CoreExtensions
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-09 06:05:55 +02:00
|
|
|
# Frontmatter namespace
|
|
|
|
module FrontMatter
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-09 06:05:55 +02:00
|
|
|
# Setup extension
|
|
|
|
class << self
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-09 06:05:55 +02:00
|
|
|
# Once registered
|
|
|
|
def registered(app)
|
|
|
|
# Parsing YAML frontmatter
|
|
|
|
require "yaml"
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-09 22:15:39 +02:00
|
|
|
# Parsing JSON frontmatter
|
|
|
|
require "active_support/json"
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-19 23:29:38 +02:00
|
|
|
app.send :include, InstanceMethods
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-19 23:29:38 +02:00
|
|
|
app.before_configuration do
|
2012-05-09 06:05:55 +02:00
|
|
|
files.changed { |file| frontmatter_manager.clear_data(file) }
|
|
|
|
files.deleted { |file| frontmatter_manager.clear_data(file) }
|
2012-05-19 23:29:38 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
app.after_configuration do
|
|
|
|
::Middleman::Sitemap::Resource.send :include, ResourceInstanceMethods
|
|
|
|
|
2013-04-13 01:14:16 +02:00
|
|
|
ignore %r{\.frontmatter$}
|
|
|
|
|
2012-05-09 06:05:55 +02:00
|
|
|
sitemap.provides_metadata do |path|
|
2012-12-03 01:15:36 +01:00
|
|
|
fmdata = frontmatter_manager.data(path).first || {}
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-09 06:05:55 +02:00
|
|
|
data = {}
|
2012-10-25 00:02:32 +02:00
|
|
|
[:layout, :layout_engine].each do |opt|
|
|
|
|
data[opt] = fmdata[opt] unless fmdata[opt].nil?
|
2012-05-09 06:05:55 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2013-02-06 08:34:18 +01:00
|
|
|
{ :options => data, :page => ::Middleman::Util.recursively_enhance(fmdata).freeze }
|
2012-05-09 06:05:55 +02:00
|
|
|
end
|
2012-05-07 23:41:39 +02:00
|
|
|
end
|
|
|
|
end
|
2012-05-09 06:05:55 +02:00
|
|
|
alias :included :registered
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-09 06:05:55 +02:00
|
|
|
class FrontmatterManager
|
2012-07-15 20:04:45 +02:00
|
|
|
attr_reader :app
|
|
|
|
delegate :logger, :to => :app
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-09 06:05:55 +02:00
|
|
|
def initialize(app)
|
|
|
|
@app = app
|
|
|
|
@cache = {}
|
2012-05-07 23:41:39 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-09 06:05:55 +02:00
|
|
|
def data(path)
|
|
|
|
p = normalize_path(path)
|
2013-04-13 01:14:16 +02:00
|
|
|
@cache[p] ||= begin
|
2013-05-23 08:39:56 +02:00
|
|
|
file_data, content = frontmatter_and_content(p)
|
2013-04-13 01:14:16 +02:00
|
|
|
|
2013-05-23 21:50:46 +02:00
|
|
|
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
|
2013-04-13 01:14:16 +02:00
|
|
|
end
|
2012-05-07 23:41:39 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-19 23:29:38 +02:00
|
|
|
def clear_data(file)
|
|
|
|
# Copied from Sitemap::Store#file_to_path, but without
|
|
|
|
# removing the file extension
|
2012-10-14 07:37:24 +02:00
|
|
|
file = File.join(@app.root, file)
|
2012-05-19 23:29:38 +02:00
|
|
|
prefix = @app.source_dir.sub(/\/$/, "") + "/"
|
|
|
|
return unless file.include?(prefix)
|
2013-04-13 01:14:16 +02:00
|
|
|
path = file.sub(prefix, "").sub(/\.frontmatter$/, "")
|
2012-05-19 23:29:38 +02:00
|
|
|
|
|
|
|
@cache.delete(path)
|
2012-05-09 06:05:55 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2013-01-28 02:11:54 +01:00
|
|
|
YAML_ERRORS = [ StandardError ]
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2013-01-28 02:11:54 +01:00
|
|
|
# https://github.com/tenderlove/psych/issues/23
|
2012-05-24 23:04:25 +02:00
|
|
|
if defined?(Psych) && defined?(Psych::SyntaxError)
|
|
|
|
YAML_ERRORS << Psych::SyntaxError
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-09 22:15:39 +02:00
|
|
|
# Parse YAML frontmatter out of a string
|
2012-05-09 06:05:55 +02:00
|
|
|
# @param [String] content
|
|
|
|
# @return [Array<Hash, String>]
|
2012-05-09 22:15:39 +02:00
|
|
|
def parse_yaml_front_matter(content)
|
2012-05-25 22:17:22 +02:00
|
|
|
yaml_regex = /\A(---\s*\n.*?\n?)^(---\s*$\n?)/m
|
2012-05-09 06:05:55 +02:00
|
|
|
if content =~ yaml_regex
|
2012-05-09 22:15:39 +02:00
|
|
|
content = content.sub(yaml_regex, "")
|
2011-11-28 07:04:19 +01:00
|
|
|
|
2012-05-09 06:05:55 +02:00
|
|
|
begin
|
2013-04-14 18:29:34 +02:00
|
|
|
data = YAML.load($1) || {}
|
|
|
|
data = data.symbolize_keys
|
2012-05-24 23:04:25 +02:00
|
|
|
rescue *YAML_ERRORS => e
|
2012-07-15 20:04:45 +02:00
|
|
|
logger.error "YAML Exception: #{e.message}"
|
2012-05-09 06:05:55 +02:00
|
|
|
return false
|
2012-05-07 23:41:39 +02:00
|
|
|
end
|
2012-05-09 06:05:55 +02:00
|
|
|
else
|
|
|
|
return false
|
|
|
|
end
|
2011-11-28 07:04:19 +01:00
|
|
|
|
2012-05-09 06:05:55 +02:00
|
|
|
[data, content]
|
|
|
|
rescue
|
|
|
|
[{}, content]
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-09 22:15:39 +02:00
|
|
|
def parse_json_front_matter(content)
|
2012-05-25 22:17:22 +02:00
|
|
|
json_regex = /\A(;;;\s*\n.*?\n?)^(;;;\s*$\n?)/m
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-09 22:15:39 +02:00
|
|
|
if content =~ json_regex
|
|
|
|
content = content.sub(json_regex, "")
|
|
|
|
|
|
|
|
begin
|
2012-05-25 21:52:04 +02:00
|
|
|
json = ($1+$2).sub(";;;", "{").sub(";;;", "}")
|
2012-10-25 00:02:32 +02:00
|
|
|
data = ActiveSupport::JSON.decode(json).symbolize_keys
|
2012-05-09 22:15:39 +02:00
|
|
|
rescue => e
|
2012-07-15 20:04:45 +02:00
|
|
|
logger.error "JSON Exception: #{e.message}"
|
2012-05-09 22:15:39 +02:00
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
else
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
[data, content]
|
|
|
|
rescue
|
|
|
|
[{}, content]
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-09 06:05:55 +02:00
|
|
|
# Get the frontmatter and plain content from a file
|
|
|
|
# @param [String] path
|
|
|
|
# @return [Array<Thor::CoreExt::HashWithIndifferentAccess, String>]
|
|
|
|
def frontmatter_and_content(path)
|
2013-02-06 22:19:09 +01:00
|
|
|
full_path = if Pathname(path).relative?
|
|
|
|
File.join(@app.source_dir, path)
|
|
|
|
else
|
|
|
|
path
|
|
|
|
end
|
2013-04-13 01:14:16 +02:00
|
|
|
|
2012-05-25 22:17:22 +02:00
|
|
|
data = {}
|
2013-01-12 01:58:02 +01:00
|
|
|
content = nil
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2013-05-23 08:39:56 +02:00
|
|
|
return [data, content] unless @app.files.exists?(full_path)
|
|
|
|
|
2013-01-12 01:58:02 +01:00
|
|
|
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
|
2012-04-21 08:38:59 +02:00
|
|
|
|
2013-01-12 01:58:02 +01:00
|
|
|
if result = parse_yaml_front_matter(content)
|
|
|
|
data, content = result
|
|
|
|
elsif result = parse_json_front_matter(content)
|
|
|
|
data, content = result
|
|
|
|
end
|
2013-04-06 23:48:00 +02:00
|
|
|
rescue
|
2013-01-12 01:58:02 +01:00
|
|
|
# Probably a binary file, move on
|
2012-05-25 22:17:22 +02:00
|
|
|
end
|
2012-05-07 23:41:39 +02:00
|
|
|
end
|
2011-11-28 07:04:19 +01:00
|
|
|
|
2012-10-25 00:02:32 +02:00
|
|
|
[data, content]
|
2012-05-09 06:05:55 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-09 06:05:55 +02:00
|
|
|
def normalize_path(path)
|
2013-02-06 22:19:09 +01:00
|
|
|
path.sub(%r{^#{@app.source_dir}\/}, "")
|
2012-05-09 06:05:55 +02:00
|
|
|
end
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-09 06:05:55 +02:00
|
|
|
module ResourceInstanceMethods
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-09 06:05:55 +02:00
|
|
|
def ignored?
|
2013-05-23 22:31:38 +02:00
|
|
|
if !proxy? && raw_data[:ignored] == true
|
2012-05-09 06:05:55 +02:00
|
|
|
true
|
|
|
|
else
|
|
|
|
super
|
2012-05-07 23:41:39 +02:00
|
|
|
end
|
2012-05-09 06:05:55 +02:00
|
|
|
end
|
|
|
|
|
2013-04-06 23:23:10 +02:00
|
|
|
# This page's frontmatter without being enhanced for access by either symbols or strings.
|
|
|
|
# Used internally
|
|
|
|
# @private
|
2012-05-09 06:05:55 +02:00
|
|
|
# @return [Hash]
|
2012-10-25 00:02:32 +02:00
|
|
|
def raw_data
|
2012-05-09 06:05:55 +02:00
|
|
|
app.frontmatter_manager.data(source_file).first
|
|
|
|
end
|
|
|
|
|
2013-04-06 23:23:10 +02:00
|
|
|
# This page's frontmatter
|
|
|
|
# @return [Hash]
|
2012-10-25 00:02:32 +02:00
|
|
|
def data
|
2013-05-23 22:31:38 +02:00
|
|
|
@enhanced_data ||= {}
|
|
|
|
@enhanced_data[raw_data] ||= begin
|
|
|
|
::Middleman::Util.recursively_enhance(raw_data).freeze
|
|
|
|
end
|
2012-10-25 00:02:32 +02:00
|
|
|
end
|
2013-04-06 23:05:26 +02:00
|
|
|
|
|
|
|
# Override Resource#content_type to take into account frontmatter
|
|
|
|
def content_type
|
|
|
|
# Allow setting content type in frontmatter too
|
2013-05-23 22:31:38 +02:00
|
|
|
fm_type = raw_data[:content_type]
|
2013-04-06 23:05:26 +02:00
|
|
|
return fm_type if fm_type
|
|
|
|
|
|
|
|
super
|
|
|
|
end
|
2012-05-09 06:05:55 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-09 06:05:55 +02:00
|
|
|
module InstanceMethods
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-09 06:05:55 +02:00
|
|
|
# Access the Frontmatter API
|
|
|
|
def frontmatter_manager
|
|
|
|
@_frontmatter_manager ||= FrontmatterManager.new(self)
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-09 06:05:55 +02:00
|
|
|
# Get the template data from a path
|
|
|
|
# @param [String] path
|
|
|
|
# @return [String]
|
|
|
|
def template_data_for_file(path)
|
|
|
|
frontmatter_manager.data(path).last
|
2012-05-07 23:41:39 +02:00
|
|
|
end
|
2011-06-06 20:39:57 +02:00
|
|
|
end
|
|
|
|
end
|
2012-05-19 22:27:38 +02:00
|
|
|
end
|