class Jekyll::FrontmatterDefaults

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.

Public Class Methods

new(site) click to toggle source

Initializes a new instance.

# File lib/jekyll/frontmatter_defaults.rb, line 11
def initialize(site)
  @site = site
end

Public Instance Methods

all(path, type) click to toggle source

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)

# File lib/jekyll/frontmatter_defaults.rb, line 78
def all(path, type)
  defaults = {}
  old_scope = nil
  matching_sets(path, type).each do |set|
    if has_precedence?(old_scope, set["scope"])
      defaults = Utils.deep_merge_hashes(defaults, set["values"])
      old_scope = set["scope"]
    else
      defaults = Utils.deep_merge_hashes(set["values"], defaults)
    end
  end
  defaults
end
ensure_time!(set) click to toggle source
# File lib/jekyll/frontmatter_defaults.rb, line 40
def ensure_time!(set)
  return set unless set.key?("values") && set["values"].key?("date")
  return set if set["values"]["date"].is_a?(Time)

  set["values"]["date"] = Utils.parse_date(
    set["values"]["date"],
    "An invalid date format was found in a front-matter default set: #{set}"
  )
  set
end
find(path, type, setting) click to toggle source

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

# File lib/jekyll/frontmatter_defaults.rb, line 59
def find(path, type, setting)
  value = nil
  old_scope = nil

  matching_sets(path, type).each do |set|
    if set["values"].key?(setting) && has_precedence?(old_scope, set["scope"])
      value = set["values"][setting]
      old_scope = set["scope"]
    end
  end
  value
end
reset() click to toggle source
# File lib/jekyll/frontmatter_defaults.rb, line 15
def reset
  @glob_cache = {} if @glob_cache
end
update_deprecated_types(set) click to toggle source
# File lib/jekyll/frontmatter_defaults.rb, line 19
def update_deprecated_types(set)
  return set unless set.key?("scope") && set["scope"].key?("type")

  set["scope"]["type"] =
    case set["scope"]["type"]
    when "page"
      Deprecator.defaults_deprecate_type("page", "pages")
      "pages"
    when "post"
      Deprecator.defaults_deprecate_type("post", "posts")
      "posts"
    when "draft"
      Deprecator.defaults_deprecate_type("draft", "drafts")
      "drafts"
    else
      set["scope"]["type"]
    end

  set
end

Private Instance Methods

applies?(scope, path, type) click to toggle source

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 type and path

# File lib/jekyll/frontmatter_defaults.rb, line 101
def applies?(scope, path, type)
  applies_type?(scope, type) && applies_path?(scope, path)
end
applies_path?(scope, path) click to toggle source
# File lib/jekyll/frontmatter_defaults.rb, line 105
def applies_path?(scope, path)
  rel_scope_path = scope["path"]
  return true if !rel_scope_path.is_a?(String) || rel_scope_path.empty?

  sanitized_path = sanitize_path(path)

  if rel_scope_path.include?("*")
    glob_scope(sanitized_path, rel_scope_path)
  else
    path_is_subpath?(sanitized_path, strip_collections_dir(rel_scope_path))
  end
end
applies_type?(scope, type) click to toggle source

Determines whether the scope applies to type. The scope applies to the type if:

1. no 'type' is specified
2. the 'type' in the scope is the same as the type asked about

scope - the Hash defaults set being asked about application type - the type of the document being processed / asked about

its defaults.

Returns true if either of the above conditions are satisfied,

otherwise returns false
# File lib/jekyll/frontmatter_defaults.rb, line 159
def applies_type?(scope, type)
  !scope.key?("type") || type&.to_sym.eql?(scope["type"].to_sym)
end
glob_cache(path) click to toggle source
# File lib/jekyll/frontmatter_defaults.rb, line 131
def glob_cache(path)
  @glob_cache ||= {}
  @glob_cache[path] ||= Dir.glob(path)
end
glob_scope(sanitized_path, rel_scope_path) click to toggle source
# File lib/jekyll/frontmatter_defaults.rb, line 118
def glob_scope(sanitized_path, rel_scope_path)
  site_source    = Pathname.new(@site.source)
  abs_scope_path = site_source.join(rel_scope_path).to_s

  glob_cache(abs_scope_path).each do |scope_path|
    scope_path = Pathname.new(scope_path).relative_path_from(site_source).to_s
    scope_path = strip_collections_dir(scope_path)
    Jekyll.logger.debug "Globbed Scope Path:", scope_path
    return true if path_is_subpath?(sanitized_path, scope_path)
  end
  false
end
has_precedence?(old_scope, new_scope) click to toggle source

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 rubocop: disable Naming/PredicateName

# File lib/jekyll/frontmatter_defaults.rb, line 179
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.key?("type")
    true
  else
    !old_scope.key? "type"
  end
end
matching_sets(path, type) click to toggle source

Collects a list of sets that match the given path and type

Returns an array of hashes

# File lib/jekyll/frontmatter_defaults.rb, line 198
def matching_sets(path, type)
  @matched_set_cache ||= {}
  @matched_set_cache[path] ||= {}
  @matched_set_cache[path][type] ||= valid_sets.select do |set|
    !set.key?("scope") || applies?(set["scope"], path, type)
  end
end
path_is_subpath?(path, parent_path) click to toggle source
# File lib/jekyll/frontmatter_defaults.rb, line 136
def path_is_subpath?(path, parent_path)
  path.start_with?(parent_path)
end
sanitize_path(path) click to toggle source

Sanitizes the given path by removing a leading slash

# File lib/jekyll/frontmatter_defaults.rb, line 228
def sanitize_path(path)
  if path.nil? || path.empty?
    ""
  elsif path.start_with?("/")
    path.gsub(%r!\A/|(?<=[^/])\z!, "")
  else
    path
  end
end
strip_collections_dir(path) click to toggle source
# File lib/jekyll/frontmatter_defaults.rb, line 140
def strip_collections_dir(path)
  collections_dir  = @site.config["collections_dir"]
  slashed_coll_dir = collections_dir.empty? ? "/" : "#{collections_dir}/"
  return path if collections_dir.empty? || !path.to_s.start_with?(slashed_coll_dir)

  path.sub(slashed_coll_dir, "")
end
valid?(set) click to toggle source

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

# File lib/jekyll/frontmatter_defaults.rb, line 168
def valid?(set)
  set.is_a?(Hash) && set["values"].is_a?(Hash)
end
valid_sets() click to toggle source

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

# File lib/jekyll/frontmatter_defaults.rb, line 212
def valid_sets
  sets = @site.config["defaults"]
  return [] unless sets.is_a?(Array)

  sets.map do |set|
    if valid?(set)
      ensure_time!(update_deprecated_types(set))
    else
      Jekyll.logger.warn "Defaults:", "An invalid front-matter default set was found:"
      Jekyll.logger.warn set.to_s
      nil
    end
  end.tap(&:compact!)
end