diff --git a/features/frontmatter_defaults.feature b/features/frontmatter_defaults.feature new file mode 100644 index 00000000..fa9e0a4d --- /dev/null +++ b/features/frontmatter_defaults.feature @@ -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:
just some post
" 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 |{{page.custom}}
some special data
info on the webpage
" in "_site/2013/10/14/about.html" + And I should see "subfolder:info on the special section
" 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 diff --git a/lib/jekyll.rb b/lib/jekyll.rb index d7a2c2ea..caa492fe 100644 --- a/lib/jekyll.rb +++ b/lib/jekyll.rb @@ -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' diff --git a/lib/jekyll/configuration.rb b/lib/jekyll/configuration.rb index ffdb0a22..ca055329 100644 --- a/lib/jekyll/configuration.rb +++ b/lib/jekyll/configuration.rb @@ -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 diff --git a/lib/jekyll/convertible.rb b/lib/jekyll/convertible.rb index 4818328a..cce7460b 100644 --- a/lib/jekyll/convertible.rb +++ b/lib/jekyll/convertible.rb @@ -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 diff --git a/lib/jekyll/frontmatter_defaults.rb b/lib/jekyll/frontmatter_defaults.rb new file mode 100644 index 00000000..2c5755b5 --- /dev/null +++ b/lib/jekyll/frontmatter_defaults.rb @@ -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 \ No newline at end of file diff --git a/lib/jekyll/page.rb b/lib/jekyll/page.rb index 5f768cc6..d045b7c2 100644 --- a/lib/jekyll/page.rb +++ b/lib/jekyll/page.rb @@ -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 diff --git a/lib/jekyll/post.rb b/lib/jekyll/post.rb index 2308a33d..8ebe92b5 100644 --- a/lib/jekyll/post.rb +++ b/lib/jekyll/post.rb @@ -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} diff --git a/lib/jekyll/site.rb b/lib/jekyll/site.rb index 43618dc2..51d2be5a 100644 --- a/lib/jekyll/site.rb +++ b/lib/jekyll/site.rb @@ -412,6 +412,10 @@ module Jekyll end end + def frontmatter_defaults + @frontmatter_defaults ||= Configuration::FrontmatterDefaults.new(self) + end + private def has_relative_page? diff --git a/site/docs/configuration.md b/site/docs/configuration.md index 431177af..9051104a 100644 --- a/site/docs/configuration.md +++ b/site/docs/configuration.md @@ -121,6 +121,18 @@ class="flag">flags (specified on the command-line) that control them.encoding: ENCODING
Defaults
++ Set defaults for YAML frontmatter + variables. +
+see below
++ If you don't want to repeat your frequently used front-matter variables over and over, + just define defaults + for them and only override them where necessary (or not at all). This works both for predefined + and custom variables. +
+