From 62f431b5ae5377084305cb781c55f9521f4ddba7 Mon Sep 17 00:00:00 2001 From: Eliott Appleford Date: Sat, 19 Sep 2015 14:53:12 +0000 Subject: [PATCH 1/2] Refactor frontmatter parsing Closes #1603 --- .../lib/middleman-core/util/data.rb | 130 ++++++------------ 1 file changed, 40 insertions(+), 90 deletions(-) diff --git a/middleman-core/lib/middleman-core/util/data.rb b/middleman-core/lib/middleman-core/util/data.rb index 781ed5c0..c37c7bf4 100644 --- a/middleman-core/lib/middleman-core/util/data.rb +++ b/middleman-core/lib/middleman-core/util/data.rb @@ -20,116 +20,66 @@ module Middleman module_function - YAML_ERRORS = [StandardError] - - # https://github.com/tenderlove/psych/issues/23 - if defined?(Psych) && defined?(Psych::SyntaxError) - YAML_ERRORS << Psych::SyntaxError - end - # Get the frontmatter and plain content from a file # @param [String] path # @return [Array] Contract Pathname, Maybe[Symbol] => [Hash, Maybe[String]] def parse(full_path, known_type=nil) - data = {} - - return [data, nil] if ::Middleman::Util.binary?(full_path) - - # Avoid weird race condition when a file is renamed. - content = begin - File.read(full_path) - rescue ::EOFError - rescue ::IOError - rescue ::Errno::ENOENT - '' - end + return [{}, nil] if Middleman::Util.binary?(full_path) + # Avoid weird race condition when a file is renamed 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 + content = File.read(full_path) + rescue EOFError, IOError, Errno::ENOENT + return [{}, nil] end - [data, content] + case known_type + when :yaml + return [parse_yaml(content, full_path), nil] + when :json + return [parse_json(content, full_path), nil] + end + + / + (?^[-;]{3})[ ]*\r?\n + (?.*?)[ ]*\r?\n + (?^[-.;]{3})[ ]*\r?\n? + (?.*) + /mx =~ content + + return [{}, content] unless frontmatter + + case [start, stop] + when %w[--- ---], %w[--- ...] + [parse_yaml(frontmatter, full_path), additional_content] + when %w[;;; ;;;] + [parse_json(frontmatter, full_path), additional_content] + else + [{}, content] + end end # Parse YAML frontmatter out of a string # @param [String] content # @return [Array] - 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] + Contract String, Pathname, Bool => Hash + def parse_yaml(content, full_path) + map_value(YAML.load(content)) + rescue StandardError, Psych::SyntaxError => error + warn "YAML Exception parsing #{full_path}: #{error.message}" + {} end # Parse JSON frontmatter out of a string # @param [String] content # @return [Array] - Contract String, Pathname => Maybe[[Hash, String]] + Contract String, Pathname => Hash 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] + map_value(JSON.parse(content)) + rescue StandardError => error + warn "JSON Exception parsing #{full_path}: #{error.message}" + {} end def symbolize_recursive(hash) From 91a06a1a35169e8efe6bfe11d5b419e0857cc541 Mon Sep 17 00:00:00 2001 From: Eliott Appleford Date: Sun, 20 Sep 2015 12:23:47 +0000 Subject: [PATCH 2/2] Refactor util/data.rb further --- .../lib/middleman-core/util/data.rb | 155 ++++++++---------- 1 file changed, 67 insertions(+), 88 deletions(-) diff --git a/middleman-core/lib/middleman-core/util/data.rb b/middleman-core/lib/middleman-core/util/data.rb index c37c7bf4..c1d1e572 100644 --- a/middleman-core/lib/middleman-core/util/data.rb +++ b/middleman-core/lib/middleman-core/util/data.rb @@ -1,103 +1,82 @@ -# Core Pathname library used for traversal +require 'yaml' +require 'json' require 'pathname' - -# DbC +require 'middleman-core/util' require 'middleman-core/contracts' -# Shared util methods -require 'middleman-core/util' +module Middleman::Util::Data + include Contracts -# Parsing YAML data -require 'yaml' + module_function -# Parsing JSON data -require 'json' + # Get the frontmatter and plain content from a file + # @param [String] path + # @return [Array] + Contract Pathname, Maybe[Symbol] => [Hash, Maybe[String]] + def parse(full_path, known_type=nil) + return [{}, nil] if Middleman::Util.binary?(full_path) -module Middleman - module Util - module Data - include Contracts + # Avoid weird race condition when a file is renamed + begin + content = File.read(full_path) + 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 - # Get the frontmatter and plain content from a file - # @param [String] path - # @return [Array] - Contract Pathname, Maybe[Symbol] => [Hash, Maybe[String]] - def parse(full_path, known_type=nil) - return [{}, nil] if Middleman::Util.binary?(full_path) + / + (?^[-;]{3})[ ]*\r?\n + (?.*?)[ ]*\r?\n + (?^[-.;]{3})[ ]*\r?\n? + (?.*) + /mx =~ content - # Avoid weird race condition when a file is renamed - begin - content = File.read(full_path) - rescue EOFError, IOError, Errno::ENOENT - return [{}, nil] - end + case [start, stop] + when %w[--- ---], %w[--- ...] + [parse_yaml(frontmatter, full_path), additional_content] + when %w[;;; ;;;] + [parse_json(frontmatter, full_path), additional_content] + else + [{}, content] + end + end - case known_type - when :yaml - return [parse_yaml(content, full_path), nil] - when :json - return [parse_json(content, full_path), nil] - end + # Parse YAML frontmatter out of a string + # @param [String] content + # @return [Array] + Contract String, Pathname, Bool => Hash + def parse_yaml(content, full_path) + symbolize_recursive(YAML.load(content)) + rescue StandardError, Psych::SyntaxError => error + warn "YAML Exception parsing #{full_path}: #{error.message}" + {} + end - / - (?^[-;]{3})[ ]*\r?\n - (?.*?)[ ]*\r?\n - (?^[-.;]{3})[ ]*\r?\n? - (?.*) - /mx =~ content + # Parse JSON frontmatter out of a string + # @param [String] content + # @return [Array] + 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 - return [{}, content] unless frontmatter - - case [start, stop] - when %w[--- ---], %w[--- ...] - [parse_yaml(frontmatter, full_path), additional_content] - when %w[;;; ;;;] - [parse_json(frontmatter, full_path), additional_content] - else - [{}, content] - end - end - - # Parse YAML frontmatter out of a string - # @param [String] content - # @return [Array] - Contract String, Pathname, Bool => Hash - def parse_yaml(content, full_path) - map_value(YAML.load(content)) - rescue StandardError, Psych::SyntaxError => error - warn "YAML Exception parsing #{full_path}: #{error.message}" - {} - end - - # Parse JSON frontmatter out of a string - # @param [String] content - # @return [Array] - Contract String, Pathname => Hash - def parse_json(content, full_path) - map_value(JSON.parse(content)) - rescue StandardError => error - warn "JSON Exception parsing #{full_path}: #{error.message}" - {} - 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 + def symbolize_recursive(value) + case value + when Hash + value.map { |k, v| [k.to_sym, symbolize_recursive(v)] }.to_h + when Array + value.map { |v| symbolize_recursive(v) } + else + value end end end