Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

End-to-end Ruby front matter, templates, and data files #285

Merged
merged 12 commits into from
Apr 25, 2021
1 change: 1 addition & 0 deletions bridgetown-core/lib/bridgetown-core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ module Bridgetown
# TODO: this is a poorly named, unclear class. Relocate to Utils:
autoload :External, "bridgetown-core/external"
autoload :FrontmatterDefaults, "bridgetown-core/frontmatter_defaults"
autoload :FrontMatterImporter, "bridgetown-core/concerns/front_matter_importer"
autoload :Hooks, "bridgetown-core/hooks"
autoload :Layout, "bridgetown-core/layout"
autoload :LayoutPlaceable, "bridgetown-core/concerns/layout_placeable"
Expand Down
23 changes: 13 additions & 10 deletions bridgetown-core/lib/bridgetown-core/collection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ def read # rubocop:todo Metrics/PerceivedComplexity, Metrics/CyclomaticComplexit
if site.uses_resource?
next if File.basename(file_path).starts_with?("_")

if label == "data" || Utils.has_yaml_header?(full_path)
if label == "data" || Utils.has_yaml_header?(full_path) ||
Utils.has_rbfm_header?(full_path)
read_resource(full_path)
else
read_static_file(file_path, full_path)
Expand Down Expand Up @@ -256,7 +257,7 @@ def merge_data_resources
sanitized_segment = sanitize_filename.(File.basename(segment, ".*"))
hsh = nested.empty? ? data_contents : data_contents.dig(*nested)
hsh[sanitized_segment] = if index == segments.length - 1
data_resource.data.array || data_resource.data
data_resource.data.rows || data_resource.data
else
{}
end
Expand All @@ -279,6 +280,16 @@ def merge_environment_specific_metadata(data_contents)
data_contents
end

# Read in resource from repo path
# @param full_path [String]
def read_resource(full_path)
id = "repo://#{label}.collection/" + Addressable::URI.escape(
Pathname(full_path).relative_path_from(Pathname(site.source)).to_s
)
resource = Bridgetown::Model::Base.find(id).to_resource.read!
resources << resource if site.unpublished || resource.published?
end

private

def container
Expand All @@ -290,14 +301,6 @@ def read_document(full_path)
docs << doc if site.unpublished || doc.published?
end

def read_resource(full_path)
id = "file://#{label}.collection/" + Addressable::URI.escape(
Pathname(full_path).relative_path_from(Pathname(site.source)).to_s
)
resource = Bridgetown::Model::Base.find(id).to_resource.read!
resources << resource if site.unpublished || resource.published?
end

def sort_docs!
if metadata["sort_by"].is_a?(String)
sort_docs_by_key!
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# frozen_string_literal: true

module Bridgetown
module FrontMatterImporter
# Requires klass#content and klass#front_matter_line_count accessors
def self.included(klass)
klass.include Bridgetown::Utils::RubyFrontMatterDSL
end

YAML_HEADER = %r!\A---\s*\n!.freeze
YAML_BLOCK = %r!\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)!m.freeze
RUBY_HEADER = %r!\A[~`#\-]{3,}(?:ruby|<%|{%)\s*\n!.freeze
RUBY_BLOCK =
%r!#{RUBY_HEADER.source}(.*?\n?)^((?:%>|%})?[~`#\-]{3,}\s*$\n?)!m.freeze

def read_front_matter(file_path) # rubocop:todo Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
file_contents = File.read(
file_path, **Bridgetown::Utils.merged_file_read_opts(Bridgetown::Current.site, {})
)
yaml_content = file_contents.match(YAML_BLOCK)
if !yaml_content && Bridgetown::Current.site.config.should_execute_inline_ruby?
ruby_content = file_contents.match(RUBY_BLOCK)
end

if yaml_content
self.content = yaml_content.post_match
self.front_matter_line_count = yaml_content[1].lines.size - 1
SafeYAML.load(yaml_content[1])
elsif ruby_content
# rbfm header + content underneath
self.content = ruby_content.post_match
self.front_matter_line_count = ruby_content[1].lines.size
process_ruby_data(ruby_content[1], file_path, 2)
elsif Bridgetown::Utils.has_rbfm_header?(file_path)
process_ruby_data(File.read(file_path).lines[1..-1].join("\n"), file_path, 2)
elsif is_a?(Layout)
self.content = file_contents
{}
else
yaml_data = SafeYAML.load_file(file_path)
yaml_data.is_a?(Array) ? { rows: yaml_data } : yaml_data
end
end

def process_ruby_data(rubycode, file_path, starting_line)
ruby_data = instance_eval(rubycode, file_path.to_s, starting_line)
ruby_data.is_a?(Array) ? { rows: ruby_data } : ruby_data.to_h
rescue StandardError => e
raise "Ruby code isn't returning an array, or object which responds to `to_h' (#{e.message})"
end
end
end
5 changes: 2 additions & 3 deletions bridgetown-core/lib/bridgetown-core/concerns/site/content.rb
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,8 @@ def docs_to_write
documents.select(&:write?)
end

# Get all documents.
# @return [Array<Document>] an array of documents from the
# configuration
# Get all loaded resources.
# @return [Array<Bridgetown::Resource::Base>] an array of resources
def resources
collections.each_with_object(Set.new) do |(_, collection), set|
set.merge(collection.resources)
Expand Down
4 changes: 0 additions & 4 deletions bridgetown-core/lib/bridgetown-core/concerns/validatable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@
module Bridgetown
# TODO: to be retired once the Resource engine is made official
module Validatable
# FIXME: there should be ONE TRUE METHOD to read the YAML frontmatter
# in the entire project. Both this and the equivalent Document method
# should be extracted and generalized.
#
# Read the YAML frontmatter.
#
# base - The String path to the dir containing the file.
Expand Down
19 changes: 10 additions & 9 deletions bridgetown-core/lib/bridgetown-core/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,16 @@ class Configuration < HashWithDotAccess::Hash
},

"kramdown" => {
"auto_ids" => true,
"toc_levels" => (1..6).to_a,
"entity_output" => "as_char",
"smart_quotes" => "lsquo,rsquo,ldquo,rdquo",
"input" => "GFM",
"hard_wrap" => false,
"guess_lang" => true,
"footnote_nr" => 1,
"show_warnings" => false,
"auto_ids" => true,
"toc_levels" => (1..6).to_a,
"entity_output" => "as_char",
"smart_quotes" => "lsquo,rsquo,ldquo,rdquo",
"input" => "GFM",
"hard_wrap" => false,
"guess_lang" => true,
"footnote_nr" => 1,
"show_warnings" => false,
"include_extraction_tags" => false,
},
}.each_with_object(Configuration.new) { |(k, v), hsh| hsh[k] = v.freeze }.freeze

Expand Down
9 changes: 9 additions & 0 deletions bridgetown-core/lib/bridgetown-core/converter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ def output_ext(_ext)
".html"
end

def line_start(convertible)
if convertible.is_a?(Bridgetown::Resource::Base) &&
convertible.model.origin.respond_to?(:front_matter_line_count)
convertible.model.origin.front_matter_line_count + 4
else
1
end
end

def inspect
"#<#{self.class}#{self.class.extname_list ? " #{self.class.extname_list.join(", ")}" : nil}>"
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ def convert(content, convertible)

erb_renderer = Tilt::ErubiTemplate.new(
convertible.relative_path,
line_start(convertible),
outvar: "@_erbout",
bufval: "Bridgetown::ERBBuffer.new",
engine_class: ERBEngine
Expand Down
2 changes: 1 addition & 1 deletion bridgetown-core/lib/bridgetown-core/converters/markdown.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def convert(content, convertible = nil)
end
else
output = @parser.convert(content)
if @parser.respond_to?(:extractions)
if convertible && @parser.respond_to?(:extractions)
convertible.data.markdown_extractions = @parser.extractions
end
output
Expand Down
17 changes: 17 additions & 0 deletions bridgetown-core/lib/bridgetown-core/converters/ruby_templates.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

module Bridgetown
module Converters
class RubyTemplates < Converter
priority :highest
input :rb

def convert(content, convertible)
erb_view = Bridgetown::ERBView.new(convertible)
erb_view.instance_eval(
content, convertible.relative_path.to_s, line_start(convertible)
).to_s
end
end
end
end
1 change: 1 addition & 0 deletions bridgetown-core/lib/bridgetown-core/drops/resource_drop.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class ResourceDrop < Drop
def_delegator :@obj, :relative_path, :path
def_delegators :@obj,
:id,
:data,
:output,
:content,
:to_s,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class UnifiedPayloadDrop < Drop
mutable true

attr_accessor :page, :layout, :content, :paginator
alias_method :resource, :page

def bridgetown
BridgetownDrop.global
Expand Down
39 changes: 27 additions & 12 deletions bridgetown-core/lib/bridgetown-core/layout.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
module Bridgetown
class Layout
include DataAccessible
include FrontMatterImporter
include LiquidRenderable
include Validatable

# Gets the Site object.
attr_reader :site
Expand All @@ -27,8 +27,12 @@ class Layout
attr_accessor :data

# Gets/Sets the content of this layout.
# @return [String]
attr_accessor :content

# @return [Integer]
attr_accessor :front_matter_line_count

# Gets/Sets the current document (for layout-compatible converters)
attr_accessor :current_document

Expand All @@ -54,9 +58,29 @@ def initialize(site, base, name, from_plugin: false)
@path = site.in_source_dir(base, name)
end
@relative_path = @path.sub(@base_dir, "")
@ext = File.extname(name)

@data = read_front_matter(@path)&.with_dot_access
rescue SyntaxError => e
Bridgetown.logger.error "Error:",
"Ruby Exception in #{e.message}"
rescue StandardError => e
handle_read_error(e)
ensure
@data ||= HashWithDotAccess::Hash.new
end

def handle_read_error(error)
if error.is_a? Psych::SyntaxError
Bridgetown.logger.warn "YAML Exception reading #{@path}: #{error.message}"
else
Bridgetown.logger.warn "Error reading file #{@path}: #{error.message}"
end

process(name)
read_yaml(base, name)
if site.config["strict_front_matter"] ||
error.is_a?(Bridgetown::Errors::FatalException)
raise error
end
end

# The inspect string for this document.
Expand All @@ -67,15 +91,6 @@ def inspect
"#<#{self.class} #{@path}>"
end

# Extract information from the layout filename.
#
# name - The String filename of the layout file.
#
# Returns nothing.
def process(name)
self.ext = File.extname(name)
end

# Provide this Layout's data to a Hash suitable for use by Liquid.
#
# Returns the Hash representation of this Layout.
Expand Down
2 changes: 1 addition & 1 deletion bridgetown-core/lib/bridgetown-core/model/origin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@ def exists?
end

require "bridgetown-core/model/builder_origin"
require "bridgetown-core/model/file_origin"
require "bridgetown-core/model/repo_origin"
Loading