Files
jekyll/lib/jekyll/utils.rb
Will Norris 0d1586a5c4 Improved permalinks for pages and collections
This updates the default permalink style for pages and collections to
match the site-wide 'permalink' setting.  If the permalink setting
contains a trailing slash, either explicitly or by being set to
':pretty', then pages and collections permalinks will contain trailing
slashes by default as well.  Similarly, if the permalink setting
contains a trailing ':output_ext', so will pages and collections.  If
the permalink setting contains neither a trailing slash or extension,
neither will pages or collections.

This impacts only the default permalink structure for pages and
collections.  Permalinks set in the frontmatter of an individual page
take precedence, as does the permalink setting for a specific
collection.

Fixes #2691
2015-03-04 09:38:23 -08:00

203 lines
6.2 KiB
Ruby

module Jekyll
module Utils
extend self
# Constants for use in #slugify
SLUGIFY_MODES = %w{raw default pretty}
SLUGIFY_RAW_REGEXP = Regexp.new('\\s+').freeze
SLUGIFY_DEFAULT_REGEXP = Regexp.new('[^[:alnum:]]+').freeze
SLUGIFY_PRETTY_REGEXP = Regexp.new("[^[:alnum:]._~!$&'()+,;=@]+").freeze
# Merges a master hash with another hash, recursively.
#
# master_hash - the "parent" hash whose values will be overridden
# other_hash - the other hash whose values will be persisted after the merge
#
# This code was lovingly stolen from some random gem:
# http://gemjack.com/gems/tartan-0.1.1/classes/Hash.html
#
# Thanks to whoever made it.
def deep_merge_hashes(master_hash, other_hash)
target = master_hash.dup
other_hash.each_key do |key|
if other_hash[key].is_a? Hash and target[key].is_a? Hash
target[key] = Utils.deep_merge_hashes(target[key], other_hash[key])
next
end
target[key] = other_hash[key]
end
target
end
# Read array from the supplied hash favouring the singular key
# and then the plural key, and handling any nil entries.
#
# hash - the hash to read from
# singular_key - the singular key
# plural_key - the plural key
#
# Returns an array
def pluralized_array_from_hash(hash, singular_key, plural_key)
[].tap do |array|
array << (value_from_singular_key(hash, singular_key) || value_from_plural_key(hash, plural_key))
end.flatten.compact
end
def value_from_singular_key(hash, key)
hash[key] if (hash.key?(key) || (hash.default_proc && hash[key]))
end
def value_from_plural_key(hash, key)
if hash.key?(key) || (hash.default_proc && hash[key])
val = hash[key]
case val
when String
val.split
when Array
val.compact
end
end
end
def transform_keys(hash)
result = {}
hash.each_key do |key|
result[yield(key)] = hash[key]
end
result
end
# Apply #to_sym to all keys in the hash
#
# hash - the hash to which to apply this transformation
#
# Returns a new hash with symbolized keys
def symbolize_hash_keys(hash)
transform_keys(hash) { |key| key.to_sym rescue key }
end
# Apply #to_s to all keys in the Hash
#
# hash - the hash to which to apply this transformation
#
# Returns a new hash with stringified keys
def stringify_hash_keys(hash)
transform_keys(hash) { |key| key.to_s rescue key }
end
# Parse a date/time and throw an error if invalid
#
# input - the date/time to parse
# msg - (optional) the error message to show the user
#
# Returns the parsed date if successful, throws a FatalException
# if not
def parse_date(input, msg = "Input could not be parsed.")
Time.parse(input).localtime
rescue ArgumentError
raise Errors::FatalException.new("Invalid date '#{input}': " + msg)
end
# Determines whether a given file has
#
# Returns true if the YAML front matter is present.
def has_yaml_header?(file)
!!(File.open(file, 'rb') { |f| f.read(5) } =~ /\A---\r?\n/)
end
# Slugify a filename or title.
#
# string - the filename or title to slugify
# mode - how string is slugified
#
# When mode is "none", return the given string in lowercase.
#
# When mode is "raw", return the given string in lowercase,
# with every sequence of spaces characters replaced with a hyphen.
#
# When mode is "default" or nil, non-alphabetic characters are
# replaced with a hyphen too.
#
# When mode is "pretty", some non-alphabetic characters (._~!$&'()+,;=@)
# are not replaced with hyphen.
#
# Examples:
# slugify("The _config.yml file")
# # => "the-config-yml-file"
#
# slugify("The _config.yml file", "pretty")
# # => "the-_config.yml-file"
#
# Returns the slugified string.
def slugify(string, mode=nil)
mode ||= 'default'
return nil if string.nil?
return string.downcase unless SLUGIFY_MODES.include?(mode)
# Replace each character sequence with a hyphen
re = case mode
when 'raw'
SLUGIFY_RAW_REGEXP
when 'default'
SLUGIFY_DEFAULT_REGEXP
when 'pretty'
# "._~!$&'()+,;=@" is human readable (not URI-escaped) in URL
# and is allowed in both extN and NTFS.
SLUGIFY_PRETTY_REGEXP
end
string.
# Strip according to the mode
gsub(re, '-').
# Remove leading/trailing hyphen
gsub(/^\-|\-$/i, '').
# Downcase
downcase
end
# Add an appropriate suffix to template so that it matches the specified
# permalink style.
#
# template - permalink template without trailing slash or file extension
# permalink_style - permalink style, either built-in or custom
#
# The returned permalink template will use the same ending style as
# specified in permalink_style. For example, if permalink_style contains a
# trailing slash (or is :pretty, which indirectly has a trailing slash),
# then so will the returned template. If permalink_style has a trailing
# ":output_ext" (or is :none, :date, or :ordinal) then so will the returned
# template. Otherwise, template will be returned without modification.
#
# Examples:
# add_permalink_suffix("/:basename", :pretty)
# # => "/:basename/"
#
# add_permalink_suffix("/:basename", :date)
# # => "/:basename:output_ext"
#
# add_permalink_suffix("/:basename", "/:year/:month/:title/")
# # => "/:basename/"
#
# add_permalink_suffix("/:basename", "/:year/:month/:title")
# # => "/:basename"
#
# Returns the updated permalink template
def add_permalink_suffix(template, permalink_style)
case permalink_style
when :pretty
template << "/"
when :date, :ordinal, :none
template << ":output_ext"
else
template << "/" if permalink_style.to_s.end_with?("/")
template << ":output_ext" if permalink_style.to_s.end_with?(":output_ext")
end
template
end
end
end