Merge branch 'frontmatter-defaults' of git://github.com/maul-esel/jekyll into maul-esel-frontmatter-defaults
* 'frontmatter-defaults' of git://github.com/maul-esel/jekyll: move self.type to convertible oops, fix minor indentation quirk document frontmatter defaults precedence adjust frontmatter defaults precedence handling change cucumber feature to test for precedence too fix frontmatter defaults for custom paths more robust cucumber features fix slash handling for paths move cucumber features to own file fix minor docs quirk improve path checking, now using Pathname instead of regex Add site documentation for the new feature add inline code docs improve validation code fix for Ruby 1.8 Add basic cucumber features for frontmatter defaults Retrieve frontmatter defaults when retrieved internally make frontmatter defaults available to liquid add a class `FrontmatterDefaults` for handling of frontmatter defaults Add a method to retrieve type to post, page and draft Conflicts: lib/jekyll.rb lib/jekyll/convertible.rb lib/jekyll/core_ext.rb lib/jekyll/page.rb lib/jekyll/post.rb
This commit is contained in:
commit
6bd07501e8
|
@ -0,0 +1,79 @@
|
||||||
|
Feature: frontmatter defaults
|
||||||
|
Scenario: Use default for frontmatter variables internally
|
||||||
|
Given I have a _layouts directory
|
||||||
|
And I have a pretty layout that contains "THIS IS THE LAYOUT: {{content}}"
|
||||||
|
|
||||||
|
And I have a _posts directory
|
||||||
|
And I have the following post:
|
||||||
|
| title | date | content |
|
||||||
|
| default layout | 2013-09-11 | just some post |
|
||||||
|
And I have an "index.html" page with title "some title" that contains "just some page"
|
||||||
|
|
||||||
|
And I have a configuration file with "defaults" set to "[{scope: {path: ""}, values: {layout: "pretty"}}]"
|
||||||
|
|
||||||
|
When I run jekyll
|
||||||
|
Then the _site directory should exist
|
||||||
|
And I should see "THIS IS THE LAYOUT: <p>just some post</p>" in "_site/2013/09/11/default-layout.html"
|
||||||
|
And I should see "THIS IS THE LAYOUT: just some page" in "_site/index.html"
|
||||||
|
|
||||||
|
Scenario: Use default for frontmatter variables in Liquid
|
||||||
|
Given I have a _posts directory
|
||||||
|
And I have the following post:
|
||||||
|
| title | date | content |
|
||||||
|
| default data | 2013-09-11 | <p>{{page.custom}}</p><div>{{page.author}}</div> |
|
||||||
|
And I have an "index.html" page that contains "just {{page.custom}} by {{page.author}}"
|
||||||
|
And I have a configuration file with "defaults" set to "[{scope: {path: ""}, values: {custom: "some special data", author: "Ben"}}]"
|
||||||
|
When I run jekyll
|
||||||
|
Then the _site directory should exist
|
||||||
|
And I should see "<p>some special data</p><div>Ben</div>" in "_site/2013/09/11/default-data.html"
|
||||||
|
And I should see "just some special data by Ben" in "_site/index.html"
|
||||||
|
|
||||||
|
Scenario: Override frontmatter defaults by path
|
||||||
|
Given I have a _layouts directory
|
||||||
|
And I have a root layout that contains "root: {{ content }}"
|
||||||
|
And I have a subfolder layout that contains "subfolder: {{ content }}"
|
||||||
|
|
||||||
|
And I have a _posts directory
|
||||||
|
And I have the following post:
|
||||||
|
| title | date | content |
|
||||||
|
| about | 2013-10-14 | info on {{page.name}} |
|
||||||
|
And I have a special/_posts directory
|
||||||
|
And I have the following post in "special":
|
||||||
|
| title | date | path | content |
|
||||||
|
| about | 2013-10-14 | local | info on {{page.name}} |
|
||||||
|
|
||||||
|
And I have an "index.html" page with title "overview" that contains "Overview for {{page.name}}"
|
||||||
|
And I have an "special/index.html" page with title "section overview" that contains "Overview for {{page.name}}"
|
||||||
|
|
||||||
|
And I have a configuration file with "defaults" set to "[{scope: {path: "special"}, values: {layout: "subfolder", name: "the special section"}}, {scope: {path: ""}, values: {layout: "root", name: "the webpage"}}]"
|
||||||
|
|
||||||
|
When I run jekyll
|
||||||
|
Then the _site directory should exist
|
||||||
|
And I should see "root: <p>info on the webpage</p>" in "_site/2013/10/14/about.html"
|
||||||
|
And I should see "subfolder: <p>info on the special section</p>" in "_site/special/2013/10/14/about.html"
|
||||||
|
And I should see "root: Overview for the webpage" in "_site/index.html"
|
||||||
|
And I should see "subfolder: Overview for the special section" in "_site/special/index.html"
|
||||||
|
|
||||||
|
Scenario: Override frontmatter defaults by type
|
||||||
|
Given I have a _posts directory
|
||||||
|
And I have the following post:
|
||||||
|
| title | date | content |
|
||||||
|
| this is a post | 2013-10-14 | blabla |
|
||||||
|
And I have an "index.html" page that contains "interesting stuff"
|
||||||
|
And I have a configuration file with "defaults" set to "[{scope: {path: "", type: "post"}, values: {permalink: "/post.html"}}, {scope: {path: "", type: "page"}, values: {permalink: "/page.html"}}, {scope: {path: ""}, values: {permalink: "/perma.html"}}]"
|
||||||
|
When I run jekyll
|
||||||
|
Then I should see "blabla" in "_site/post.html"
|
||||||
|
And I should see "interesting stuff" in "_site/page.html"
|
||||||
|
But the "_site/perma.html" file should not exist
|
||||||
|
|
||||||
|
Scenario: Actual frontmatter overrides defaults
|
||||||
|
Given I have a _posts directory
|
||||||
|
And I have the following post:
|
||||||
|
| title | date | permalink | author | content |
|
||||||
|
| override | 2013-10-14 | /frontmatter.html | some guy | a blog by {{page.author}} |
|
||||||
|
And I have an "index.html" page with permalink "override.html" that contains "nothing"
|
||||||
|
And I have a configuration file with "defaults" set to "[{scope: {path: ""}, values: {permalink: "/perma.html", author: "Chris"}}]"
|
||||||
|
When I run jekyll
|
||||||
|
Then I should see "a blog by some guy" in "_site/frontmatter.html"
|
||||||
|
And I should see "nothing" in "_site/override.html"
|
||||||
|
But the "_site/perma.html" file should not exist
|
|
@ -35,6 +35,7 @@ require 'jekyll/stevenson'
|
||||||
require 'jekyll/deprecator'
|
require 'jekyll/deprecator'
|
||||||
require 'jekyll/configuration'
|
require 'jekyll/configuration'
|
||||||
require 'jekyll/plugin_manager'
|
require 'jekyll/plugin_manager'
|
||||||
|
require 'jekyll/frontmatter_defaults'
|
||||||
require 'jekyll/site'
|
require 'jekyll/site'
|
||||||
require 'jekyll/convertible'
|
require 'jekyll/convertible'
|
||||||
require 'jekyll/url'
|
require 'jekyll/url'
|
||||||
|
|
|
@ -44,6 +44,8 @@ module Jekyll
|
||||||
|
|
||||||
'excerpt_separator' => "\n\n",
|
'excerpt_separator' => "\n\n",
|
||||||
|
|
||||||
|
'defaults' => [],
|
||||||
|
|
||||||
'maruku' => {
|
'maruku' => {
|
||||||
'use_tex' => false,
|
'use_tex' => false,
|
||||||
'use_divs' => false,
|
'use_divs' => false,
|
||||||
|
@ -249,6 +251,5 @@ module Jekyll
|
||||||
|
|
||||||
config
|
config
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,6 +14,8 @@ require 'set'
|
||||||
# self.output=
|
# self.output=
|
||||||
# self.name
|
# self.name
|
||||||
# self.path
|
# self.path
|
||||||
|
# self.type -> :page, :post or :draft
|
||||||
|
|
||||||
module Jekyll
|
module Jekyll
|
||||||
module Convertible
|
module Convertible
|
||||||
# Returns the contents as a String.
|
# Returns the contents as a String.
|
||||||
|
@ -107,7 +109,19 @@ module Jekyll
|
||||||
further_data = Hash[(attrs || self.class::ATTRIBUTES_FOR_LIQUID).map { |attribute|
|
further_data = Hash[(attrs || self.class::ATTRIBUTES_FOR_LIQUID).map { |attribute|
|
||||||
[attribute, send(attribute)]
|
[attribute, send(attribute)]
|
||||||
}]
|
}]
|
||||||
Utils.deep_merge_hashes(data, further_data)
|
|
||||||
|
defaults = site.frontmatter_defaults.all(relative_path, type)
|
||||||
|
defaults.merge Utils.deep_merge_hashes(data, further_data)
|
||||||
|
end
|
||||||
|
|
||||||
|
def type
|
||||||
|
if is_a?(Post)
|
||||||
|
:post
|
||||||
|
elsif is_a?(Page)
|
||||||
|
:page
|
||||||
|
elsif is_a?(Draft)
|
||||||
|
:draft
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Recursively render layouts
|
# Recursively render layouts
|
||||||
|
|
|
@ -0,0 +1,148 @@
|
||||||
|
module Jekyll
|
||||||
|
class Configuration
|
||||||
|
# This class handles custom defaults for YAML frontmatter settings.
|
||||||
|
# These are set in _config.yml and apply both to internal use (e.g. layout)
|
||||||
|
# and the data available to liquid.
|
||||||
|
#
|
||||||
|
# It is exposed via the frontmatter_defaults method on the site class.
|
||||||
|
class FrontmatterDefaults
|
||||||
|
# Initializes a new instance.
|
||||||
|
def initialize(site)
|
||||||
|
@site = site
|
||||||
|
end
|
||||||
|
|
||||||
|
# Finds a default value for a given setting, filtered by path and type
|
||||||
|
#
|
||||||
|
# path - the path (relative to the source) of the page, post or :draft the default is used in
|
||||||
|
# type - a symbol indicating whether a :page, a :post or a :draft calls this method
|
||||||
|
#
|
||||||
|
# Returns the default value or nil if none was found
|
||||||
|
def find(path, type, setting)
|
||||||
|
value = nil
|
||||||
|
old_scope = nil
|
||||||
|
|
||||||
|
matching_sets(path, type).each do |set|
|
||||||
|
if set['values'].has_key?(setting) && has_precedence?(old_scope, set['scope'])
|
||||||
|
value = set['values'][setting]
|
||||||
|
old_scope = set['scope']
|
||||||
|
end
|
||||||
|
end
|
||||||
|
value
|
||||||
|
end
|
||||||
|
|
||||||
|
# Collects a hash with all default values for a page or post
|
||||||
|
#
|
||||||
|
# path - the relative path of the page or post
|
||||||
|
# type - a symbol indicating the type (:post, :page or :draft)
|
||||||
|
#
|
||||||
|
# Returns a hash with all default values (an empty hash if there are none)
|
||||||
|
def all(path, type)
|
||||||
|
defaults = {}
|
||||||
|
old_scope = nil
|
||||||
|
matching_sets(path, type).each do |set|
|
||||||
|
if has_precedence?(old_scope, set['scope'])
|
||||||
|
defaults.merge! set['values']
|
||||||
|
old_scope = set['scope']
|
||||||
|
else
|
||||||
|
defaults = set['values'].merge(defaults)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
defaults
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Checks if a given default setting scope matches the given path and type
|
||||||
|
#
|
||||||
|
# scope - the hash indicating the scope, as defined in _config.yml
|
||||||
|
# path - the path to check for
|
||||||
|
# type - the type (:post, :page or :draft) to check for
|
||||||
|
#
|
||||||
|
# Returns true if the scope applies to the given path and type
|
||||||
|
def applies?(scope, path, type)
|
||||||
|
applies_path?(scope, path) && applies_type?(scope, type)
|
||||||
|
end
|
||||||
|
|
||||||
|
def applies_path?(scope, path)
|
||||||
|
return true if scope['path'].empty?
|
||||||
|
|
||||||
|
scope_path = Pathname.new(scope['path'])
|
||||||
|
Pathname.new(sanitize_path(path)).ascend do |path|
|
||||||
|
if path == scope_path
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def applies_type?(scope, type)
|
||||||
|
!scope.has_key?('type') || scope['type'] == type.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
# Checks if a given set of default values is valid
|
||||||
|
#
|
||||||
|
# set - the default value hash, as defined in _config.yml
|
||||||
|
#
|
||||||
|
# Returns true if the set is valid and can be used in this class
|
||||||
|
def valid?(set)
|
||||||
|
set.is_a?(Hash) && set['scope'].is_a?(Hash) && set['scope']['path'].is_a?(String) && set['values'].is_a?(Hash)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Determines if a new scope has precedence over an old one
|
||||||
|
#
|
||||||
|
# old_scope - the old scope hash, or nil if there's none
|
||||||
|
# new_scope - the new scope hash
|
||||||
|
#
|
||||||
|
# Returns true if the new scope has precedence over the older
|
||||||
|
def has_precedence?(old_scope, new_scope)
|
||||||
|
return true if old_scope.nil?
|
||||||
|
|
||||||
|
new_path = sanitize_path(new_scope['path'])
|
||||||
|
old_path = sanitize_path(old_scope['path'])
|
||||||
|
|
||||||
|
if new_path.length != old_path.length
|
||||||
|
new_path.length >= old_path.length
|
||||||
|
elsif new_scope.has_key? 'type'
|
||||||
|
true
|
||||||
|
else
|
||||||
|
!old_scope.has_key? 'type'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Collects a list of sets that match the given path and type
|
||||||
|
#
|
||||||
|
# Returns an array of hashes
|
||||||
|
def matching_sets(path, type)
|
||||||
|
valid_sets.select do |set|
|
||||||
|
applies?(set['scope'], path, type)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns a list of valid sets
|
||||||
|
#
|
||||||
|
# This is not cached to allow plugins to modify the configuration
|
||||||
|
# and have their changes take effect
|
||||||
|
#
|
||||||
|
# Returns an array of hashes
|
||||||
|
def valid_sets
|
||||||
|
sets = @site.config['defaults']
|
||||||
|
return [] unless sets.is_a?(Array)
|
||||||
|
|
||||||
|
sets.select do |set|
|
||||||
|
unless valid?(set)
|
||||||
|
Jekyll.logger.warn "Default:", "An invalid default set was found"
|
||||||
|
end
|
||||||
|
valid?(set)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Sanitizes the given path by removing a leading and addding a trailing slash
|
||||||
|
def sanitize_path(path)
|
||||||
|
if path.nil? || path.empty?
|
||||||
|
""
|
||||||
|
else
|
||||||
|
path.gsub(/\A\//, '').gsub(/([^\/])\z/, '\1/')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -28,8 +28,13 @@ module Jekyll
|
||||||
@dir = dir
|
@dir = dir
|
||||||
@name = name
|
@name = name
|
||||||
|
|
||||||
|
|
||||||
process(name)
|
process(name)
|
||||||
read_yaml(File.join(base, dir), name)
|
read_yaml(File.join(base, dir), name)
|
||||||
|
|
||||||
|
data.default_proc = proc do |hash, key|
|
||||||
|
site.frontmatter_defaults.find(File.join(dir, name), type, key)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# The generated directory into which the page will be placed
|
# The generated directory into which the page will be placed
|
||||||
|
|
|
@ -56,14 +56,28 @@ module Jekyll
|
||||||
process(name)
|
process(name)
|
||||||
read_yaml(@base, name)
|
read_yaml(@base, name)
|
||||||
|
|
||||||
|
data.default_proc = proc do |hash, key|
|
||||||
|
site.frontmatter_defaults.find(File.join(dir, name), type, key)
|
||||||
|
end
|
||||||
|
|
||||||
if data.has_key?('date')
|
if data.has_key?('date')
|
||||||
self.date = Time.parse(data["date"].to_s)
|
self.date = Time.parse(data["date"].to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
self.published = published?
|
||||||
|
|
||||||
populate_categories
|
populate_categories
|
||||||
populate_tags
|
populate_tags
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def published?
|
||||||
|
if data.has_key?('published') && data['published'] == false
|
||||||
|
false
|
||||||
|
else
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def populate_categories
|
def populate_categories
|
||||||
if categories.empty?
|
if categories.empty?
|
||||||
self.categories = Utils.pluralized_array_from_hash(data, 'category', 'categories').map {|c| c.to_s.downcase}
|
self.categories = Utils.pluralized_array_from_hash(data, 'category', 'categories').map {|c| c.to_s.downcase}
|
||||||
|
|
|
@ -367,6 +367,10 @@ module Jekyll
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def frontmatter_defaults
|
||||||
|
@frontmatter_defaults ||= Configuration::FrontmatterDefaults.new(self)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def has_relative_page?
|
def has_relative_page?
|
||||||
|
|
|
@ -121,6 +121,18 @@ class="flag">flags</code> (specified on the command-line) that control them.
|
||||||
<p><code class="option">encoding: ENCODING</code></p>
|
<p><code class="option">encoding: ENCODING</code></p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<p class='name'><strong>Defaults</strong></p>
|
||||||
|
<p class='description'>
|
||||||
|
Set defaults for <a href="../frontmatter/" title="YAML frontmatter">YAML frontmatter</a>
|
||||||
|
variables.
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
<td class='align-center'>
|
||||||
|
<p>see <a href="#frontmatter_defaults" title="details">below</a></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
@ -264,6 +276,43 @@ before your site is served.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
## Frontmatter defaults
|
||||||
|
|
||||||
|
You can set default values for your [YAML frontmatter](../frontmatter/) variables
|
||||||
|
in your configuration. This way, you can for example set default layouts or define
|
||||||
|
defaults for your custom variables. Of course, any variable actually specified in
|
||||||
|
the front matter overrides the defaults.
|
||||||
|
|
||||||
|
All defaults go under the `defaults` key, which holds a list of scope-values combinations ("default sets").
|
||||||
|
The `scope` key defines for which files the defaults apply, limiting them by their `path` and
|
||||||
|
optionally by their `type` (`page`, `post` or `draft`). The `values` key holds the actual list of defaults.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
{% highlight yaml %}
|
||||||
|
defaults:
|
||||||
|
-
|
||||||
|
scope:
|
||||||
|
path: "" # empty string for all files
|
||||||
|
values:
|
||||||
|
layout: "my-site"
|
||||||
|
-
|
||||||
|
scope:
|
||||||
|
path: "about/blog"
|
||||||
|
type: "post"
|
||||||
|
values:
|
||||||
|
layout: "meta-blog" # overrides previous default layout
|
||||||
|
author: "Dr. Hyde"
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
With these defaults, all pages and posts would default to the `my-site` layout except for the posts under `about/blog`,
|
||||||
|
who would default to the `meta-blog` layout and also have the `page.author` [liquid variable](../variables/) set to `Dr. Hyde` by default.
|
||||||
|
|
||||||
|
### Precedence
|
||||||
|
You can have multiple sets of frontmatter defaults that specify defaults for the same setting. In this case, for each page or post,
|
||||||
|
the default set with the more specific scope takes precedence. This way, you can specify defaults for a path like `/site/blog` that would
|
||||||
|
override any defaults for `/site`. Also, if the paths are equal, a scope with a specified type is more specific. If two sets are equally
|
||||||
|
specific, the bottom-most takes precedence.
|
||||||
|
|
||||||
## Default Configuration
|
## Default Configuration
|
||||||
|
|
||||||
Jekyll runs with the following configuration options by default. Unless
|
Jekyll runs with the following configuration options by default. Unless
|
||||||
|
|
|
@ -179,3 +179,13 @@ These are available out-of-the-box to be used in the front-matter for a post.
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="note">
|
||||||
|
<h5>ProTip™: Don't repeat yourself</h5>
|
||||||
|
<p>
|
||||||
|
If you don't want to repeat your frequently used front-matter variables over and over,
|
||||||
|
just define <a href="../configuration/#frontmatter_defaults" title="frontmatter defaults">defaults</a>
|
||||||
|
for them and only override them where necessary (or not at all). This works both for predefined
|
||||||
|
and custom variables.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue