From 3ccd8dad3198a63cd4a1d63f47faa4eaebe9e119 Mon Sep 17 00:00:00 2001 From: "maul.esel" Date: Wed, 11 Sep 2013 00:48:05 +0200 Subject: [PATCH 01/23] Add a method to retrieve type to post, page and draft --- lib/jekyll/draft.rb | 4 ++++ lib/jekyll/page.rb | 4 ++++ lib/jekyll/post.rb | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/lib/jekyll/draft.rb b/lib/jekyll/draft.rb index 321a6e58..98fa7362 100644 --- a/lib/jekyll/draft.rb +++ b/lib/jekyll/draft.rb @@ -13,6 +13,10 @@ module Jekyll name =~ MATCHER end + def type + :draft + end + # Get the full path to the directory containing the draft files def containing_dir(source, dir) File.join(source, dir, '_drafts') diff --git a/lib/jekyll/page.rb b/lib/jekyll/page.rb index dd602a03..7c5c5802 100644 --- a/lib/jekyll/page.rb +++ b/lib/jekyll/page.rb @@ -30,6 +30,10 @@ module Jekyll self.read_yaml(File.join(base, dir), name) end + def type + :page + end + # The generated directory into which the page will be placed # upon generation. This is derived from the permalink or, if # permalink is absent, we be '/' diff --git a/lib/jekyll/post.rb b/lib/jekyll/post.rb index 291921ff..8d4418e4 100644 --- a/lib/jekyll/post.rb +++ b/lib/jekyll/post.rb @@ -65,6 +65,10 @@ module Jekyll self.populate_tags end + def type + :post + end + def published? if self.data.has_key?('published') && self.data['published'] == false false From b3fdaa97928d628aea67ffcbd07680e8d0d42590 Mon Sep 17 00:00:00 2001 From: "maul.esel" Date: Wed, 11 Sep 2013 00:55:28 +0200 Subject: [PATCH 02/23] add a class `FrontmatterDefaults` for handling of frontmatter defaults --- lib/jekyll.rb | 1 + lib/jekyll/configuration.rb | 3 +- lib/jekyll/frontmatter_defaults.rb | 53 ++++++++++++++++++++++++++++++ lib/jekyll/site.rb | 4 +++ 4 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 lib/jekyll/frontmatter_defaults.rb diff --git a/lib/jekyll.rb b/lib/jekyll.rb index a48d3460..fa2e87dd 100644 --- a/lib/jekyll.rb +++ b/lib/jekyll.rb @@ -31,6 +31,7 @@ require 'jekyll/core_ext' require 'jekyll/stevenson' require 'jekyll/deprecator' require 'jekyll/configuration' +require 'jekyll/frontmatter_defaults.rb' require 'jekyll/site' require 'jekyll/convertible' require 'jekyll/url' diff --git a/lib/jekyll/configuration.rb b/lib/jekyll/configuration.rb index 05a097c7..453c5427 100644 --- a/lib/jekyll/configuration.rb +++ b/lib/jekyll/configuration.rb @@ -40,6 +40,8 @@ module Jekyll 'excerpt_separator' => "\n\n", + 'defaults' => [], + 'maruku' => { 'use_tex' => false, 'use_divs' => false, @@ -217,6 +219,5 @@ module Jekyll config end - end end diff --git a/lib/jekyll/frontmatter_defaults.rb b/lib/jekyll/frontmatter_defaults.rb new file mode 100644 index 00000000..ace09985 --- /dev/null +++ b/lib/jekyll/frontmatter_defaults.rb @@ -0,0 +1,53 @@ +module Jekyll + class Configuration + class FrontmatterDefaults + def initialize(site) + @site = site + end + + def find(path, type, setting) + value = nil + matching_sets(path, type).each do |set| + value = set['values'][setting] if set['values'].has_key?(setting) + end + value + end + + def all(path, type) + defaults = {} + matching_sets(path, type).each do |set| + defaults.merge! set['values'] + end + defaults + end + + private + + def applies?(scope, path, type) + (scope['path'].empty? || path.starts_with?(scope['path'])) && (!scope.has_key?('type') || scope['type'] == type.to_s) + end + + def valid?(set) + set['scope'].is_a?(Hash) && set['scope'].has_key?('path') && set['values'].is_a?(Hash) + end + + def matching_sets(path, type) + valid_sets.select do |set| + applies?(set['scope'], path, type) + end + end + + 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 + end + end +end \ No newline at end of file diff --git a/lib/jekyll/site.rb b/lib/jekyll/site.rb index f4a5bd0d..ae2d2b56 100644 --- a/lib/jekyll/site.rb +++ b/lib/jekyll/site.rb @@ -369,6 +369,10 @@ module Jekyll end end + def frontmatter_defaults + @frontmatter_defaults ||= Configuration::FrontmatterDefaults.new(self) + end + private def has_yaml_header?(file) From 9d44d3290b95473516c398f48f4e176d228e2d6e Mon Sep 17 00:00:00 2001 From: "maul.esel" Date: Wed, 11 Sep 2013 00:55:57 +0200 Subject: [PATCH 03/23] make frontmatter defaults available to liquid --- lib/jekyll/convertible.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/jekyll/convertible.rb b/lib/jekyll/convertible.rb index 5f493800..4e1cdf15 100644 --- a/lib/jekyll/convertible.rb +++ b/lib/jekyll/convertible.rb @@ -13,6 +13,7 @@ require 'set' # self.ext= # self.output= # self.name +# self.type -> :page, :post or :draft module Jekyll module Convertible # Returns the contents as a String. @@ -91,7 +92,8 @@ module Jekyll further_data = Hash[(attrs || self.class::ATTRIBUTES_FOR_LIQUID).map { |attribute| [attribute, send(attribute)] }] - data.deep_merge(further_data) + defaults = site.frontmatter_defaults.all(self.path, self.type) + defaults.merge(data).deep_merge(further_data) end # Recursively render layouts From fb911af2cd7d7baeb96d347a10519d25433a8fc9 Mon Sep 17 00:00:00 2001 From: "maul.esel" Date: Wed, 11 Sep 2013 00:57:18 +0200 Subject: [PATCH 04/23] Retrieve frontmatter defaults when retrieved internally This is for example possible for layout defaults to take effect. --- lib/jekyll/page.rb | 4 ++++ lib/jekyll/post.rb | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/lib/jekyll/page.rb b/lib/jekyll/page.rb index 7c5c5802..a8483307 100644 --- a/lib/jekyll/page.rb +++ b/lib/jekyll/page.rb @@ -28,6 +28,10 @@ module Jekyll self.process(name) self.read_yaml(File.join(base, dir), name) + + self.data.default_proc = proc do |hash, key| + self.site.frontmatter_defaults.find(File.join(dir, name), self.type, key) + end end def type diff --git a/lib/jekyll/post.rb b/lib/jekyll/post.rb index 8d4418e4..f1a3a6a9 100644 --- a/lib/jekyll/post.rb +++ b/lib/jekyll/post.rb @@ -55,6 +55,10 @@ module Jekyll self.process(name) self.read_yaml(@base, name) + self.data.default_proc = proc do |hash, key| + self.site.frontmatter_defaults.find(File.join(dir, name), self.type, key) + end + if self.data.has_key?('date') self.date = Time.parse(self.data["date"].to_s) end From 2ba26f1bb634d51f7dae527dcd8162ffbe39f09b Mon Sep 17 00:00:00 2001 From: "maul.esel" Date: Wed, 11 Sep 2013 00:57:49 +0200 Subject: [PATCH 05/23] Add basic cucumber features for frontmatter defaults --- features/post_data.feature | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/features/post_data.feature b/features/post_data.feature index 34f58187..61714b0a 100644 --- a/features/post_data.feature +++ b/features/post_data.feature @@ -212,3 +212,25 @@ Feature: Post data Then the _site directory should exist And I should see "next post: Some like it hot" in "_site/2009/03/27/star-wars.html" And I should see "Previous post: Some like it hot" in "_site/2009/05/27/terminator.html" + + Scenario: Use default for frontmatter variables internally + Given I have a _posts directory + And I have a _layouts directory + And I have a configuration file with "defaults" set to "[{scope: {path: "", type: "post"}, values: {layout: "pretty"}}]" + And I have a pretty layout that contains "THIS IS THE LAYOUT: {{content}}" + And I have the following post: + | title | date | content | + | default layout | 2013-09-11 | Just some post | + 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" + + Scenario: Use default for frontmatter variables in Liquid + Given I have a _posts directory + And I have a configuration file with "defaults" set to "[{scope: {path: "", type: "post"}, values: {custom: "some special data", author: "Ben"}}]" + And I have the following post: + | title | date | author | content | + | default data | 2013-09-11 | Marc |

{{page.custom}}

{{page.author}}
| + When I run jekyll + Then the _site directory should exist + And I should see "

some special data

Marc
" in "_site/2013/09/11/default-data.html" From accea6648c73b79d09c20f9607a899a79fcc02a0 Mon Sep 17 00:00:00 2001 From: "maul.esel" Date: Wed, 11 Sep 2013 17:27:34 +0200 Subject: [PATCH 06/23] fix for Ruby 1.8 --- lib/jekyll/core_ext.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/jekyll/core_ext.rb b/lib/jekyll/core_ext.rb index 1a7b1816..c3bec80d 100644 --- a/lib/jekyll/core_ext.rb +++ b/lib/jekyll/core_ext.rb @@ -52,6 +52,20 @@ class Hash def symbolize_keys dup.symbolize_keys! end + + if RUBY_VERSION < '1.9' + attr_accessor :default_proc + + def [](key) + fetch(key) do |key| + if @default_proc.nil? + default(key) + else + @default_proc.call(self, key) + end + end + end + end end # Thanks, ActiveSupport! From 0874c14b2c875ef67cf90a366a04534a05b6349d Mon Sep 17 00:00:00 2001 From: "maul.esel" Date: Wed, 11 Sep 2013 17:37:15 +0200 Subject: [PATCH 07/23] improve validation code --- lib/jekyll/frontmatter_defaults.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jekyll/frontmatter_defaults.rb b/lib/jekyll/frontmatter_defaults.rb index ace09985..8839569c 100644 --- a/lib/jekyll/frontmatter_defaults.rb +++ b/lib/jekyll/frontmatter_defaults.rb @@ -28,7 +28,7 @@ module Jekyll end def valid?(set) - set['scope'].is_a?(Hash) && set['scope'].has_key?('path') && set['values'].is_a?(Hash) + set.is_a?(Hash) && set['scope'].is_a?(Hash) && set['scope']['path'].is_a?(String) && set['values'].is_a?(Hash) end def matching_sets(path, type) From 5775603f49d95f6c3861934b937917db683d012b Mon Sep 17 00:00:00 2001 From: "maul.esel" Date: Wed, 11 Sep 2013 17:49:34 +0200 Subject: [PATCH 08/23] add inline code docs --- lib/jekyll/frontmatter_defaults.rb | 39 ++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/lib/jekyll/frontmatter_defaults.rb b/lib/jekyll/frontmatter_defaults.rb index 8839569c..521aab4a 100644 --- a/lib/jekyll/frontmatter_defaults.rb +++ b/lib/jekyll/frontmatter_defaults.rb @@ -1,10 +1,22 @@ 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 matching_sets(path, type).each do |set| @@ -13,6 +25,12 @@ module Jekyll 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 = {} matching_sets(path, type).each do |set| @@ -23,20 +41,41 @@ module Jekyll 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) (scope['path'].empty? || path.starts_with?(scope['path'])) && (!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 + # 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) From 8060cb60a2ee17a7bfe306d98ba07398ebd201b9 Mon Sep 17 00:00:00 2001 From: "maul.esel" Date: Wed, 11 Sep 2013 18:39:59 +0200 Subject: [PATCH 09/23] Add site documentation for the new feature --- site/docs/configuration.md | 43 ++++++++++++++++++++++++++++++++++++++ site/docs/frontmatter.md | 10 +++++++++ 2 files changed, 53 insertions(+) diff --git a/site/docs/configuration.md b/site/docs/configuration.md index fa92c6d1..8f79d6c7 100644 --- a/site/docs/configuration.md +++ b/site/docs/configuration.md @@ -99,6 +99,18 @@ class="flag">flags (specified on the command-line) that control them.

timezone: TIMEZONE

+ + +

Defaults

+

+ Set defaults for YAML frontmatter + variables. +

+ + +

see below

+ + @@ -242,6 +254,37 @@ before your site is served.

+## 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. +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. + ## Default Configuration Jekyll runs with the following configuration options by default. Unless diff --git a/site/docs/frontmatter.md b/site/docs/frontmatter.md index 98c15cc4..e47718ca 100644 --- a/site/docs/frontmatter.md +++ b/site/docs/frontmatter.md @@ -178,3 +178,13 @@ These are available out-of-the-box to be used in the front-matter for a post. + +
+
ProTip™: Don't repeat yourself
+

+ 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 . +

+
From 299cb931470c8e3186baffacef4494d18e4705f5 Mon Sep 17 00:00:00 2001 From: "maul.esel" Date: Mon, 16 Sep 2013 15:42:59 +0200 Subject: [PATCH 10/23] improve path checking, now using Pathname instead of regex --- lib/jekyll/frontmatter_defaults.rb | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/jekyll/frontmatter_defaults.rb b/lib/jekyll/frontmatter_defaults.rb index 521aab4a..0badf3ee 100644 --- a/lib/jekyll/frontmatter_defaults.rb +++ b/lib/jekyll/frontmatter_defaults.rb @@ -49,7 +49,22 @@ module Jekyll # # Returns true if the scope applies to the given path and type def applies?(scope, path, type) - (scope['path'].empty? || path.starts_with?(scope['path'])) && (!scope.has_key?('type') || scope['type'] == type.to_s) + 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(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 From 1cb67932f6272f3817ff5e14570ea32fd4f3af6b Mon Sep 17 00:00:00 2001 From: "maul.esel" Date: Mon, 16 Sep 2013 15:46:15 +0200 Subject: [PATCH 11/23] fix minor docs quirk --- site/docs/configuration.md | 8 ++++---- site/docs/frontmatter.md | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/site/docs/configuration.md b/site/docs/configuration.md index 8f79d6c7..4105d184 100644 --- a/site/docs/configuration.md +++ b/site/docs/configuration.md @@ -102,10 +102,10 @@ class="flag">flags (specified on the command-line) that control them.

Defaults

-

- Set defaults for YAML frontmatter - variables. -

+

+ Set defaults for YAML frontmatter + variables. +

see below

diff --git a/site/docs/frontmatter.md b/site/docs/frontmatter.md index e47718ca..0d97a001 100644 --- a/site/docs/frontmatter.md +++ b/site/docs/frontmatter.md @@ -185,6 +185,6 @@ These are available out-of-the-box to be used in the front-matter for a post. 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 . + and custom variables.

From 80910293293c606de620f6f5092b9a649d475b80 Mon Sep 17 00:00:00 2001 From: "maul.esel" Date: Mon, 14 Oct 2013 16:48:26 +0200 Subject: [PATCH 12/23] move cucumber features to own file --- features/frontmatter_defaults.feature | 22 ++++++++++++++++++++++ features/post_data.feature | 22 ---------------------- 2 files changed, 22 insertions(+), 22 deletions(-) create mode 100644 features/frontmatter_defaults.feature diff --git a/features/frontmatter_defaults.feature b/features/frontmatter_defaults.feature new file mode 100644 index 00000000..36509876 --- /dev/null +++ b/features/frontmatter_defaults.feature @@ -0,0 +1,22 @@ +Feature: frontmatter defaults + Scenario: Use default for frontmatter variables internally + Given I have a _posts directory + And I have a _layouts directory + And I have a configuration file with "defaults" set to "[{scope: {path: "", type: "post"}, values: {layout: "pretty"}}]" + And I have a pretty layout that contains "THIS IS THE LAYOUT: {{content}}" + And I have the following post: + | title | date | content | + | default layout | 2013-09-11 | Just some post | + 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" + + Scenario: Use default for frontmatter variables in Liquid + Given I have a _posts directory + And I have a configuration file with "defaults" set to "[{scope: {path: "", type: "post"}, values: {custom: "some special data", author: "Ben"}}]" + And I have the following post: + | title | date | author | content | + | default data | 2013-09-11 | Marc |

{{page.custom}}

{{page.author}}
| + When I run jekyll + Then the _site directory should exist + And I should see "

some special data

Marc
" in "_site/2013/09/11/default-data.html" diff --git a/features/post_data.feature b/features/post_data.feature index 61714b0a..34f58187 100644 --- a/features/post_data.feature +++ b/features/post_data.feature @@ -212,25 +212,3 @@ Feature: Post data Then the _site directory should exist And I should see "next post: Some like it hot" in "_site/2009/03/27/star-wars.html" And I should see "Previous post: Some like it hot" in "_site/2009/05/27/terminator.html" - - Scenario: Use default for frontmatter variables internally - Given I have a _posts directory - And I have a _layouts directory - And I have a configuration file with "defaults" set to "[{scope: {path: "", type: "post"}, values: {layout: "pretty"}}]" - And I have a pretty layout that contains "THIS IS THE LAYOUT: {{content}}" - And I have the following post: - | title | date | content | - | default layout | 2013-09-11 | Just some post | - 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" - - Scenario: Use default for frontmatter variables in Liquid - Given I have a _posts directory - And I have a configuration file with "defaults" set to "[{scope: {path: "", type: "post"}, values: {custom: "some special data", author: "Ben"}}]" - And I have the following post: - | title | date | author | content | - | default data | 2013-09-11 | Marc |

{{page.custom}}

{{page.author}}
| - When I run jekyll - Then the _site directory should exist - And I should see "

some special data

Marc
" in "_site/2013/09/11/default-data.html" From 67a451ea8414761de811aff1eb427f932ae79645 Mon Sep 17 00:00:00 2001 From: "maul.esel" Date: Mon, 14 Oct 2013 18:43:12 +0200 Subject: [PATCH 13/23] fix slash handling for paths --- lib/jekyll/frontmatter_defaults.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/jekyll/frontmatter_defaults.rb b/lib/jekyll/frontmatter_defaults.rb index 0badf3ee..4f98286c 100644 --- a/lib/jekyll/frontmatter_defaults.rb +++ b/lib/jekyll/frontmatter_defaults.rb @@ -55,6 +55,7 @@ module Jekyll def applies_path?(scope, path) return true if scope['path'].empty? + path = path.gsub(/\A\//, '').gsub(/([^\/])\z/, '\1/') # add / remove slashes from path scope_path = Pathname.new(scope['path']) Pathname.new(path).ascend do |path| if path == scope_path From 1c52657d7df1a764a96243f4b37ac0c6963a3800 Mon Sep 17 00:00:00 2001 From: "maul.esel" Date: Mon, 14 Oct 2013 19:13:20 +0200 Subject: [PATCH 14/23] more robust cucumber features --- features/frontmatter_defaults.feature | 77 +++++++++++++++++++++++---- 1 file changed, 67 insertions(+), 10 deletions(-) diff --git a/features/frontmatter_defaults.feature b/features/frontmatter_defaults.feature index 36509876..3b96dd07 100644 --- a/features/frontmatter_defaults.feature +++ b/features/frontmatter_defaults.feature @@ -1,22 +1,79 @@ Feature: frontmatter defaults Scenario: Use default for frontmatter variables internally - Given I have a _posts directory - And I have a _layouts directory - And I have a configuration file with "defaults" set to "[{scope: {path: "", type: "post"}, values: {layout: "pretty"}}]" + 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 | + | 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 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 a configuration file with "defaults" set to "[{scope: {path: "", type: "post"}, values: {custom: "some special data", author: "Ben"}}]" And I have the following post: - | title | date | author | content | - | default data | 2013-09-11 | Marc |

{{page.custom}}

{{page.author}}
| + | title | date | content | + | default data | 2013-09-11 |

{{page.custom}}

{{page.author}}
| + 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 "

some special data

Marc
" in "_site/2013/09/11/default-data.html" + And I should see "

some special data

Ben
" 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 | content | + | about | 2013-10-14 | 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: ""}, values: {layout: "root", name: "the webpage"}}, {scope: {path: "special"}, values: {layout: "subfolder", name: "the special section"}}]" + + When I run jekyll + Then the _site directory should exist + And I should see "root:

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: ""}, values: {permalink: "/perma.html"}}, {scope: {path: "", type: "post"}, values: {permalink: "/post.html"}}, {scope: {path: "", type: "page"}, values: {permalink: "/page.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 From 699eeba9f02d0b59aced006be3979e15e4d9ac18 Mon Sep 17 00:00:00 2001 From: "maul.esel" Date: Mon, 14 Oct 2013 19:16:53 +0200 Subject: [PATCH 15/23] fix frontmatter defaults for custom paths The Page#path or Post#path can be overriden by by a frontmatter setting. This causes path-based frontmatter default detection to fail. Add test to demonstrate this and fix it. --- features/frontmatter_defaults.feature | 4 ++-- lib/jekyll/convertible.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/features/frontmatter_defaults.feature b/features/frontmatter_defaults.feature index 3b96dd07..ba00709c 100644 --- a/features/frontmatter_defaults.feature +++ b/features/frontmatter_defaults.feature @@ -39,8 +39,8 @@ Feature: frontmatter defaults | 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 | content | - | about | 2013-10-14 | info on {{page.name}} | + | 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}}" diff --git a/lib/jekyll/convertible.rb b/lib/jekyll/convertible.rb index 4e1cdf15..ecfa0f6a 100644 --- a/lib/jekyll/convertible.rb +++ b/lib/jekyll/convertible.rb @@ -92,7 +92,7 @@ module Jekyll further_data = Hash[(attrs || self.class::ATTRIBUTES_FOR_LIQUID).map { |attribute| [attribute, send(attribute)] }] - defaults = site.frontmatter_defaults.all(self.path, self.type) + defaults = site.frontmatter_defaults.all(self.relative_path, self.type) defaults.merge(data).deep_merge(further_data) end From 32b4de3ea6eb82eedc2177d1ac083e23fa1a2586 Mon Sep 17 00:00:00 2001 From: "maul.esel" Date: Tue, 15 Oct 2013 13:32:49 +0200 Subject: [PATCH 16/23] change cucumber feature to test for precedence too --- features/frontmatter_defaults.feature | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/frontmatter_defaults.feature b/features/frontmatter_defaults.feature index ba00709c..ac7e1ade 100644 --- a/features/frontmatter_defaults.feature +++ b/features/frontmatter_defaults.feature @@ -45,7 +45,7 @@ Feature: frontmatter defaults 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: ""}, values: {layout: "root", name: "the webpage"}}, {scope: {path: "special"}, values: {layout: "subfolder", name: "the special section"}}]" + 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 @@ -60,7 +60,7 @@ Feature: frontmatter defaults | 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: ""}, values: {permalink: "/perma.html"}}, {scope: {path: "", type: "post"}, values: {permalink: "/post.html"}}, {scope: {path: "", type: "page"}, values: {permalink: "/page.html"}}]" + 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" From dd851943c08a4a3eb2687138a7c7985839d5d60f Mon Sep 17 00:00:00 2001 From: "maul.esel" Date: Tue, 15 Oct 2013 14:18:44 +0200 Subject: [PATCH 17/23] adjust frontmatter defaults precedence handling Before, the bottom-most default set had highest precedence. Instead, now the set with a longer i.e. more specific path has precedence and sets with a type setting have precedence over those without one. In case of equal precedence the bottom-most still wins. --- lib/jekyll/frontmatter_defaults.rb | 48 +++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/lib/jekyll/frontmatter_defaults.rb b/lib/jekyll/frontmatter_defaults.rb index 4f98286c..80ee191d 100644 --- a/lib/jekyll/frontmatter_defaults.rb +++ b/lib/jekyll/frontmatter_defaults.rb @@ -19,8 +19,13 @@ module Jekyll # 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| - value = set['values'][setting] if set['values'].has_key?(setting) + if set['values'].has_key?(setting) && has_precedence?(old_scope, set['scope']) + value = set['values'][setting] + old_scope = set['scope'] + end end value end @@ -33,8 +38,14 @@ module Jekyll # 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| - defaults.merge! set['values'] + 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 @@ -55,9 +66,8 @@ module Jekyll def applies_path?(scope, path) return true if scope['path'].empty? - path = path.gsub(/\A\//, '').gsub(/([^\/])\z/, '\1/') # add / remove slashes from path scope_path = Pathname.new(scope['path']) - Pathname.new(path).ascend do |path| + Pathname.new(sanitize_path(path)).ascend do |path| if path == scope_path return true end @@ -77,6 +87,27 @@ module Jekyll 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 @@ -103,6 +134,15 @@ module Jekyll 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 From 96a37ac1abacf9bd84b526595ee47878609e97db Mon Sep 17 00:00:00 2001 From: "maul.esel" Date: Tue, 15 Oct 2013 14:25:47 +0200 Subject: [PATCH 18/23] document frontmatter defaults precedence --- site/docs/configuration.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/site/docs/configuration.md b/site/docs/configuration.md index 966e661a..94236ed1 100644 --- a/site/docs/configuration.md +++ b/site/docs/configuration.md @@ -277,7 +277,7 @@ in your configuration. This way, you can for example set default layouts or defi 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. +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. @@ -301,6 +301,12 @@ defaults: 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 From 2b275ef192214be0c6c00e7cc614c2e749ba9014 Mon Sep 17 00:00:00 2001 From: "maul.esel" Date: Tue, 15 Oct 2013 14:38:45 +0200 Subject: [PATCH 19/23] oops, fix minor indentation quirk --- lib/jekyll/frontmatter_defaults.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jekyll/frontmatter_defaults.rb b/lib/jekyll/frontmatter_defaults.rb index 80ee191d..2c5755b5 100644 --- a/lib/jekyll/frontmatter_defaults.rb +++ b/lib/jekyll/frontmatter_defaults.rb @@ -105,7 +105,7 @@ module Jekyll true else !old_scope.has_key? 'type' - end + end end # Collects a list of sets that match the given path and type From 960e01cba815b92918c602775202715c1c525b21 Mon Sep 17 00:00:00 2001 From: "maul.esel" Date: Thu, 5 Dec 2013 09:14:31 +0100 Subject: [PATCH 20/23] move self.type to convertible --- lib/jekyll/convertible.rb | 10 ++++++++++ lib/jekyll/draft.rb | 4 ---- lib/jekyll/page.rb | 4 ---- lib/jekyll/post.rb | 4 ---- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/jekyll/convertible.rb b/lib/jekyll/convertible.rb index 7a1b6eed..cedd1e62 100644 --- a/lib/jekyll/convertible.rb +++ b/lib/jekyll/convertible.rb @@ -106,6 +106,16 @@ module Jekyll defaults.merge(data).deep_merge(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 # # layouts - a list of the layouts diff --git a/lib/jekyll/draft.rb b/lib/jekyll/draft.rb index 98fa7362..321a6e58 100644 --- a/lib/jekyll/draft.rb +++ b/lib/jekyll/draft.rb @@ -13,10 +13,6 @@ module Jekyll name =~ MATCHER end - def type - :draft - end - # Get the full path to the directory containing the draft files def containing_dir(source, dir) File.join(source, dir, '_drafts') diff --git a/lib/jekyll/page.rb b/lib/jekyll/page.rb index a8483307..e9146597 100644 --- a/lib/jekyll/page.rb +++ b/lib/jekyll/page.rb @@ -34,10 +34,6 @@ module Jekyll end end - def type - :page - end - # The generated directory into which the page will be placed # upon generation. This is derived from the permalink or, if # permalink is absent, we be '/' diff --git a/lib/jekyll/post.rb b/lib/jekyll/post.rb index b03c31f3..fcf22ecf 100644 --- a/lib/jekyll/post.rb +++ b/lib/jekyll/post.rb @@ -69,10 +69,6 @@ module Jekyll self.populate_tags end - def type - :post - end - def published? if self.data.has_key?('published') && self.data['published'] == false false From 09c6ff4f9c9568c103788e2d7f0c9804c7706492 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Fri, 4 Apr 2014 15:34:42 -0400 Subject: [PATCH 21/23] Post#published is no longer a thing. --- lib/jekyll/post.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/jekyll/post.rb b/lib/jekyll/post.rb index a7ef64ac..8ebe92b5 100644 --- a/lib/jekyll/post.rb +++ b/lib/jekyll/post.rb @@ -64,8 +64,6 @@ module Jekyll self.date = Time.parse(data["date"].to_s) end - self.published = published? - populate_categories populate_tags end From 955dc38400485f7275612ac070f4168e9cf27cc8 Mon Sep 17 00:00:00 2001 From: Parker Moore Date: Sat, 5 Apr 2014 15:42:53 -0400 Subject: [PATCH 22/23] Deep merge data overrides into defaults. --- lib/jekyll/convertible.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jekyll/convertible.rb b/lib/jekyll/convertible.rb index ab5b92cc..cce7460b 100644 --- a/lib/jekyll/convertible.rb +++ b/lib/jekyll/convertible.rb @@ -111,7 +111,7 @@ module Jekyll }] defaults = site.frontmatter_defaults.all(relative_path, type) - defaults.merge Utils.deep_merge_hashes(data, further_data) + Utils.deep_merge_hashes defaults, Utils.deep_merge_hashes(data, further_data) end def type From 22f7380abe5f474271de43e70eb5285442c8fa5d Mon Sep 17 00:00:00 2001 From: Matt Rogers Date: Tue, 15 Apr 2014 20:15:16 -0500 Subject: [PATCH 23/23] Change the feature to not use `page.name` Use `page.description` in the cucumber feature instead. `page.name` isn't overridable anymore. --- features/frontmatter_defaults.feature | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/features/frontmatter_defaults.feature b/features/frontmatter_defaults.feature index ac7e1ade..fa9e0a4d 100644 --- a/features/frontmatter_defaults.feature +++ b/features/frontmatter_defaults.feature @@ -36,16 +36,16 @@ Feature: frontmatter defaults And I have a _posts directory And I have the following post: | title | date | content | - | about | 2013-10-14 | info on {{page.name}} | + | 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.name}} | + | about | 2013-10-14 | local | info on {{page.description}} | - 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 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", name: "the special section"}}, {scope: {path: ""}, values: {layout: "root", name: "the webpage"}}]" + 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