Merge pull request #2205 from jekyll/maul-esel-frontmatter-defaults
This commit is contained in:
commit
9db5a1a6ce
|
@ -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.description}} |
|
||||
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.description}} |
|
||||
|
||||
And I have an "index.html" page with title "overview" that contains "Overview for {{page.description}}"
|
||||
And I have an "special/index.html" page with title "section overview" that contains "Overview for {{page.description}}"
|
||||
|
||||
And I have a configuration file with "defaults" set to "[{scope: {path: "special"}, values: {layout: "subfolder", description: "the special section"}}, {scope: {path: ""}, values: {layout: "root", description: "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
|
|
@ -37,6 +37,7 @@ require 'jekyll/configuration'
|
|||
require 'jekyll/document'
|
||||
require 'jekyll/collection'
|
||||
require 'jekyll/plugin_manager'
|
||||
require 'jekyll/frontmatter_defaults'
|
||||
require 'jekyll/site'
|
||||
require 'jekyll/convertible'
|
||||
require 'jekyll/url'
|
||||
|
|
|
@ -46,6 +46,8 @@ module Jekyll
|
|||
|
||||
'excerpt_separator' => "\n\n",
|
||||
|
||||
'defaults' => [],
|
||||
|
||||
'maruku' => {
|
||||
'use_tex' => false,
|
||||
'use_divs' => false,
|
||||
|
@ -251,6 +253,5 @@ module Jekyll
|
|||
|
||||
config
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,6 +14,8 @@ require 'set'
|
|||
# self.output=
|
||||
# self.name
|
||||
# self.path
|
||||
# self.type -> :page, :post or :draft
|
||||
|
||||
module Jekyll
|
||||
module Convertible
|
||||
# Returns the contents as a String.
|
||||
|
@ -107,7 +109,19 @@ module Jekyll
|
|||
further_data = Hash[(attrs || self.class::ATTRIBUTES_FOR_LIQUID).map { |attribute|
|
||||
[attribute, send(attribute)]
|
||||
}]
|
||||
Utils.deep_merge_hashes(data, further_data)
|
||||
|
||||
defaults = site.frontmatter_defaults.all(relative_path, type)
|
||||
Utils.deep_merge_hashes defaults, 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
|
||||
|
||||
# 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
|
||||
@name = name
|
||||
|
||||
|
||||
process(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
|
||||
|
||||
# The generated directory into which the page will be placed
|
||||
|
|
|
@ -56,6 +56,10 @@ module Jekyll
|
|||
process(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')
|
||||
self.date = Time.parse(data["date"].to_s)
|
||||
end
|
||||
|
@ -64,6 +68,14 @@ module Jekyll
|
|||
populate_tags
|
||||
end
|
||||
|
||||
def published?
|
||||
if data.has_key?('published') && data['published'] == false
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def populate_categories
|
||||
if categories.empty?
|
||||
self.categories = Utils.pluralized_array_from_hash(data, 'category', 'categories').map {|c| c.to_s.downcase}
|
||||
|
|
|
@ -412,6 +412,10 @@ module Jekyll
|
|||
end
|
||||
end
|
||||
|
||||
def frontmatter_defaults
|
||||
@frontmatter_defaults ||= Configuration::FrontmatterDefaults.new(self)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
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>
|
||||
</td>
|
||||
</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>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -264,6 +276,43 @@ before your site is served.
|
|||
</p>
|
||||
</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
|
||||
|
||||
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>
|
||||
</table>
|
||||
</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