module Jekyll::Utils

def add_permalink_suffix(template, permalink_style)

Returns the updated permalink template

# => "/:basename"
add_permalink_suffix("/:basename", "/:year/:month/:title")

# => "/:basename/"
add_permalink_suffix("/:basename", "/:year/:month/:title/")

# => "/:basename:output_ext"
add_permalink_suffix("/:basename", :date)

# => "/:basename/"
add_permalink_suffix("/:basename", :pretty)
Examples:

template. Otherwise, template will be returned without modification.
":output_ext" (or is :none, :date, or :ordinal) then so will the returned
then so will the returned template. If permalink_style has a trailing
trailing slash (or is :pretty, which indirectly has a trailing slash),
specified in permalink_style. For example, if permalink_style contains a
The returned permalink template will use the same ending style as

permalink_style - permalink style, either built-in or custom
template - permalink template without trailing slash or file extension

permalink style.
Add an appropriate suffix to template so that it matches the specified
def add_permalink_suffix(template, permalink_style)
  template = template.dup
  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

def deep_merge_hashes(master_hash, other_hash)

Returns the merged hashes.

Non-destructive version of deep_merge_hashes! See that method.
def deep_merge_hashes(master_hash, other_hash)
  deep_merge_hashes!(master_hash.dup, other_hash)
end

def deep_merge_hashes!(target, overwrite)

Thanks to whoever made it.

http://gemjack.com/gems/tartan-0.1.1/classes/Hash.html
This code was lovingly stolen from some random gem:

other_hash - the other hash whose values will be persisted after the merge
master_hash - the "parent" hash whose values will be overridden

Merges a master hash with another hash, recursively.
def deep_merge_hashes!(target, overwrite)
  merge_values(target, overwrite)
  merge_default_proc(target, overwrite)
  duplicate_frozen_values(target)
  target
end

def duplicable?(obj)

def duplicable?(obj)
  case obj
  when nil, false, true, Symbol, Numeric
    false
  else
    true
  end
end

def duplicate_frozen_values(target)

def duplicate_frozen_values(target)
  target.each do |key, val|
    target[key] = val.dup if val.frozen? && duplicable?(val)
  end
end

def has_liquid_construct?(content)

Returns true is the string contains sequences of `{%` or `{{`

Determine whether the given content string contains Liquid Tags or Variables
def has_liquid_construct?(content)
  return false if content.nil? || content.empty?
  content.include?("{%") || content.include?("{{")
end

def has_yaml_header?(file)

rubocop: disable Naming/PredicateName
Returns true if the YAML front matter is present.

Determines whether a given file has
def has_yaml_header?(file)
  File.open(file, "rb", &:readline).match? %r!\A---\s*\r?\n!
rescue EOFError
  false
end

def mergable?(value)

def mergable?(value)
  value.is_a?(Hash) || value.is_a?(Drops::Drop)
end

def merge_default_proc(target, overwrite)

def merge_default_proc(target, overwrite)
  if target.is_a?(Hash) && overwrite.is_a?(Hash) && target.default_proc.nil?
    target.default_proc = overwrite.default_proc
  end
end

def merge_values(target, overwrite)

def merge_values(target, overwrite)
  target.merge!(overwrite) do |_key, old_val, new_val|
    if new_val.nil?
      old_val
    elsif mergable?(old_val) && mergable?(new_val)
      deep_merge_hashes(old_val, new_val)
    else
      new_val
    end
  end
end

def merged_file_read_opts(site, opts)

and a given param
Returns merged option hash for File.read of self.site (if exists)
def merged_file_read_opts(site, opts)
  merged = (site ? site.file_read_opts : {}).merge(opts)
  # always use BOM when reading UTF-encoded files
  if merged[:encoding]&.downcase&.start_with?("utf-")
    merged[:encoding] = "bom|#{merged[:encoding]}"
  end
  if merged["encoding"]&.downcase&.start_with?("utf-")
    merged["encoding"] = "bom|#{merged["encoding"]}"
  end
  merged
end

def parse_date(input, msg = "Input could not be parsed.")

if not
Returns the parsed date if successful, throws a FatalException

msg - (optional) the error message to show the user
input - the date/time to parse

Parse a date/time and throw an error if invalid
def parse_date(input, msg = "Input could not be parsed.")
  @parse_date_cache ||= {}
  @parse_date_cache[input] ||= Time.parse(input).localtime
rescue ArgumentError
  raise Errors::InvalidDateError, "Invalid date '#{input}': #{msg}"
end

def pluralized_array_from_hash(hash, singular_key, plural_key)

Returns an array

plural_key - the plural key
singular_key - the singular key
hash - the hash to read from

and then the plural key, and handling any nil entries.
Read array from the supplied hash favouring the singular key
def pluralized_array_from_hash(hash, singular_key, plural_key)
  array = []
  value = value_from_singular_key(hash, singular_key)
  value ||= value_from_plural_key(hash, plural_key)
  array << value
  array.flatten!
  array.compact!
  array
end

def replace_character_sequence_with_hyphen(string, mode: "default")

by each mode.
See Utils#slugify for a description of the character sequence specified

Replace each character sequence with a hyphen.
def replace_character_sequence_with_hyphen(string, mode: "default")
  replaceable_char =
    case mode
    when "raw"
      SLUGIFY_RAW_REGEXP
    when "pretty"
      # "._~!$&'()+,;=@" is human readable (not URI-escaped) in URL
      # and is allowed in both extN and NTFS.
      SLUGIFY_PRETTY_REGEXP
    when "ascii"
      # For web servers not being able to handle Unicode, the safe
      # method is to ditch anything else but latin letters and numeric
      # digits.
      SLUGIFY_ASCII_REGEXP
    else
      SLUGIFY_DEFAULT_REGEXP
    end
  # Strip according to the mode
  string.gsub(replaceable_char, "-")
end

def safe_glob(dir, patterns, flags = 0)

Returns matched paths

flags - the flags which will be applied to the pattern
patterns - the patterns (or the pattern) which will be applied under the dir
(the dir will be included to each result)
dir - the dir where glob will be executed under

# => ["path[/file1", "path[/folder/file2"]
safe_glob("path", ["**", "*"])

# => ["path/.", "path/..", "path/file1"]
safe_glob("path", "*", File::FNM_DOTMATCH)

# => ["path[/file1", "path[/file2"]
safe_glob("path[", "*")
Examples:

because the method fails to find the closing pattern to '[' which is ']'
For example, Dir.glob("path[/*") always returns an empty array,

as a pattern.
('dir' + '/' + 'pattern') to make sure the first part('dir') does not act
Work the same way as Dir.glob but separating the input into two parts
def safe_glob(dir, patterns, flags = 0)
  return [] unless Dir.exist?(dir)
  pattern = File.join(Array(patterns))
  return [dir] if pattern.empty?
  Dir.chdir(dir) do
    Dir.glob(pattern, flags).map { |f| File.join(dir, f) }
  end
end

def slugify(string, mode: nil, cased: false)

Returns the slugified string.

# => "the-config-yml-file"
slugify("The _config.yml file", "latin")

# => "the-config-yml-file"
slugify("The _config.yml file", "ascii")

# => "The-_config.yml file"
slugify("The _config.yml file", "pretty", true)

# => "the-_config.yml-file"
slugify("The _config.yml file", "pretty")

# => "the-config-yml-file"
slugify("The _config.yml file")
Examples:

replaced with their lowercase counterparts.
If cased is true, all uppercase letters in the result string are

it follows the "default" mode of operation.
any letters with accents are replaced with the plain letter. Afterwards,
When mode is "latin", the input string is first preprocessed so that

a-z (lowercase), A-Z (uppercase) and 0-9 (numbers) are not replaced with hyphen.
When mode is "ascii", some everything else except ASCII characters

are not replaced with hyphen.
When mode is "pretty", some non-alphabetic characters (._~!$&'()+,;=@)

replaced with a hyphen too.
When mode is "default" or nil, non-alphabetic characters are

with every sequence of spaces characters replaced with a hyphen.
When mode is "raw", return the given string,

When mode is "none", return the given string.

lowercase counterparts
cased - whether to replace all uppercase letters with their
mode - how string is slugified
string - the filename or title to slugify

Slugify a filename or title.
def slugify(string, mode: nil, cased: false)
  mode ||= "default"
  return nil if string.nil?
  unless SLUGIFY_MODES.include?(mode)
    return cased ? string : string.downcase
  end
  # Drop accent marks from latin characters. Everything else turns to ?
  if mode == "latin"
    I18n.config.available_locales = :en if I18n.config.available_locales.empty?
    string = I18n.transliterate(string)
  end
  slug = replace_character_sequence_with_hyphen(string, :mode => mode)
  # Remove leading/trailing hyphen
  slug.gsub!(%r!^-|-$!i, "")
  slug.downcase! unless cased
  Jekyll.logger.warn("Warning:", "Empty `slug` generated for '#{string}'.") if slug.empty?
  slug
end

def stringify_hash_keys(hash)

Returns a new hash with stringified keys

hash - the hash to which to apply this transformation

Apply #to_s to all keys in the Hash
def stringify_hash_keys(hash)
  transform_keys(hash) { |key| key.to_s rescue key }
end

def symbolize_hash_keys(hash)

Returns a new hash with symbolized keys

hash - the hash to which to apply this transformation

Apply #to_sym to all keys in the hash
def symbolize_hash_keys(hash)
  transform_keys(hash) { |key| key.to_sym rescue key }
end

def titleize_slug(slug)

Takes a slug and turns it into a simple title.
def titleize_slug(slug)
  slug.split("-").map!(&:capitalize).join(" ")
end

def transform_keys(hash)

def transform_keys(hash)
  result = {}
  hash.each_key do |key|
    result[yield(key)] = hash[key]
  end
  result
end

def value_from_plural_key(hash, key)

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 value_from_singular_key(hash, key)

def value_from_singular_key(hash, key)
  hash[key] if hash.key?(key) || (hash.default_proc && hash[key])
end