Merge pull request #1604 from middleman/frontmatter

Refactor frontmatter parsing
This commit is contained in:
Eliott Appleford 2015-09-21 13:33:43 +01:00
commit 11fc90f93c

View file

@ -1,153 +1,82 @@
# Core Pathname library used for traversal require 'yaml'
require 'json'
require 'pathname' require 'pathname'
require 'middleman-core/util'
# DbC
require 'middleman-core/contracts' require 'middleman-core/contracts'
# Shared util methods module Middleman::Util::Data
require 'middleman-core/util' include Contracts
# Parsing YAML data module_function
require 'yaml'
# Parsing JSON data # Get the frontmatter and plain content from a file
require 'json' # @param [String] path
# @return [Array<Hash, String>]
Contract Pathname, Maybe[Symbol] => [Hash, Maybe[String]]
def parse(full_path, known_type=nil)
return [{}, nil] if Middleman::Util.binary?(full_path)
module Middleman # Avoid weird race condition when a file is renamed
module Util begin
module Data content = File.read(full_path)
include Contracts rescue EOFError, IOError, Errno::ENOENT
return [{}, nil]
end
module_function case known_type
when :yaml
return [parse_yaml(content, full_path), nil]
when :json
return [parse_json(content, full_path), nil]
end
YAML_ERRORS = [StandardError] /
(?<start>^[-;]{3})[ ]*\r?\n
(?<frontmatter>.*?)[ ]*\r?\n
(?<stop>^[-.;]{3})[ ]*\r?\n?
(?<additional_content>.*)
/mx =~ content
# https://github.com/tenderlove/psych/issues/23 case [start, stop]
if defined?(Psych) && defined?(Psych::SyntaxError) when %w[--- ---], %w[--- ...]
YAML_ERRORS << Psych::SyntaxError [parse_yaml(frontmatter, full_path), additional_content]
end when %w[;;; ;;;]
[parse_json(frontmatter, full_path), additional_content]
else
[{}, content]
end
end
# Get the frontmatter and plain content from a file # Parse YAML frontmatter out of a string
# @param [String] path # @param [String] content
# @return [Array<Hash, String>] # @return [Array<Hash, String>]
Contract Pathname, Maybe[Symbol] => [Hash, Maybe[String]] Contract String, Pathname, Bool => Hash
def parse(full_path, known_type=nil) def parse_yaml(content, full_path)
data = {} symbolize_recursive(YAML.load(content))
rescue StandardError, Psych::SyntaxError => error
warn "YAML Exception parsing #{full_path}: #{error.message}"
{}
end
return [data, nil] if ::Middleman::Util.binary?(full_path) # Parse JSON frontmatter out of a string
# @param [String] content
# @return [Array<Hash, String>]
Contract String, Pathname => Hash
def parse_json(content, full_path)
symbolize_recursive(JSON.parse(content))
rescue StandardError => error
warn "JSON Exception parsing #{full_path}: #{error.message}"
{}
end
# Avoid weird race condition when a file is renamed. def symbolize_recursive(value)
content = begin case value
File.read(full_path) when Hash
rescue ::EOFError value.map { |k, v| [k.to_sym, symbolize_recursive(v)] }.to_h
rescue ::IOError when Array
rescue ::Errno::ENOENT value.map { |v| symbolize_recursive(v) }
'' else
end value
begin
if content =~ /\A.*coding:/
lines = content.split(/\n/)
lines.shift
content = lines.join("\n")
end
if known_type
if known_type == :yaml
result = parse_yaml(content, full_path, true)
elsif known_type == :json
result = parse_json(content, full_path)
end
else
result = parse_yaml(content, full_path, false)
end
return result if result
rescue
# Probably a binary file, move on
end
[data, content]
end
# Parse YAML frontmatter out of a string
# @param [String] content
# @return [Array<Hash, String>]
Contract String, Pathname, Bool => Maybe[[Hash, String]]
def parse_yaml(content, full_path, require_yaml=false)
total_delims = content.scan(/^(?:---|\.\.\.)\s*(?:\n|$)/).length
has_first_line_delim = !content.match(/\A(---\s*(?:\n|$))/).nil?
# has_closing_delim = (total_delims > 1 && has_first_line_delim) || (!has_first_line_delim && total_delims == 1)
parts = content.split(/^(?:---|\.\.\.)\s*(?:\n|$)/)
parts.shift if parts[0].empty?
yaml_string = nil
additional_content = nil
if require_yaml
yaml_string = parts[0]
additional_content = parts[1]
else
if total_delims > 1
if has_first_line_delim
yaml_string = parts[0]
additional_content = parts[1]
else
additional_content = content
end
else
additional_content = parts[0]
end
end
return [{}, additional_content] if yaml_string.nil?
begin
data = map_value(::YAML.load(yaml_string) || {})
rescue *YAML_ERRORS => e
$stderr.puts "YAML Exception parsing #{full_path}: #{e.message}"
return nil
end
[data, additional_content]
rescue
[{}, additional_content]
end
# Parse JSON frontmatter out of a string
# @param [String] content
# @return [Array<Hash, String>]
Contract String, Pathname => Maybe[[Hash, String]]
def parse_json(content, full_path)
begin
data = map_value(::JSON.parse(content))
rescue => e
$stderr.puts "JSON Exception parsing #{full_path}: #{e.message}"
return nil
end
[data, nil]
rescue
[{}, nil]
end
def symbolize_recursive(hash)
{}.tap do |h|
hash.each { |key, value| h[key.to_sym] = map_value(value) }
end
end
def map_value(thing)
case thing
when Hash
symbolize_recursive(thing)
when Array
thing.map { |v| map_value(v) }
else
thing
end
end
end end
end end
end end