require 'noumenon'
require 'liquid'
# Templates specify the structure and presentation of a particular piece of content in
# a Noumenon site, and are usually provided by a theme.
#
# A template is split into two parts, the metadata, which defines what data is needed for
# the template, and a Liquid template, which defines how the data should be presented. When
# loaded from a file, a template will look like this:
#
# title:
# label: Page title
# required: true
# type: string
# help: The title displayed at the top of the page
# author:
# label: Author
# required: false
# type: string
# help: The author of the article. If not provided, no byline will be shown.
# body:
# label: Body text
# required: true
# type: text
# help: The main article body. Will be processed with Textile for formatting.
# ---
#
{{ title }}
# {% if author %}
# By {{ author }}
# {% endif %}
# {{ body | textilize }}
#
# And can be rendered like this:
#
# Template.from_file("/path/to/template").render("title" => "An Example Page", "author" => "Jon Wood", "body" => "This is an article...")
#
# If any required fields are missing from the data provided then a MissingContentError will be raised.
#
# @api public
class Noumenon::Template
# Indicates one or more required fields were not provided to the template.
#
# The missing fields are listed in the error message.
#
# @api public
class MissingContentError < StandardError; end
# Indicates the requested template could not be found.
#
# @api public
class NotFoundError < StandardError; end
# The location this template was loaded from.
# @api public
attr_accessor :source
# The template view.
# @api public
attr_accessor :content
# The fields used by this template.
# @api public
attr_accessor :fields
# Loads a template from the specified path.
#
# @api public
#
# @param [ String, #to_s ] path the file to load the template from
# @raise [ Noumenon::Template::NotFoundError ] the template could not be found
# @return [ Noumenon::Template ] the loaded template
# @api public
def self.from_file(path)
raise NotFoundError.new("No template could be found at #{path}.") unless File.exists?(path)
content = File.read(path)
parts = content.split("\n---\n")
if parts.size == 1
fields = {}
else
fields = YAML.load(parts.first)
content = parts.last
end
self.new(path, content, fields)
end
# Creates a new Template instance.
#
# @api public
#
# @param [ #to_s ] source the location the template was loaded from
# @param [ #to_s ] content the Liquid template to render
# @param [ Hash, #each ] fields the list of fields used within the template
#
# @example Loading a template from a database
# fields = load_field_rows.inject({}) do |fields, row|
# fields[row[:name]] = { "required" => row[:required], "type" => row[:type] }
# end
#
# Noumenon::Template.new("mysql://localhost/database/table#row_32", row[:content], fields)
#
# @api public
def initialize(source = nil, content = nil, fields = {})
@source = source
@content = content
@fields = fields
end
# Renders the template.
#
# @param [ Hash, #each ] page_content the content to provide to the template
# @raise [ Noumenon::Template::MissingContentError ] one or more required fields were not provided
# @return [ #to_s ] the rendered template
# @api public
def render(page_content = {})
fields_from_page = page_content.stringify_keys
missing_fields = []
fields.each do |key, field|
missing_fields << key if field["required"] && !fields_from_page.key?(key)
end
raise MissingContentError.new("The following fields were missing from your content: #{missing_fields.sort.join(", ")}") unless missing_fields.empty?
Liquid::Template.parse(content).render(fields_from_page)
end
end