#!/bin/bash

################################################################################################################################################################
#
# SBWG - steeph's bash website generator
# Last changed: 2021-08-24
# Version: See variable below
# https://log.steeph.de/SBWG.html
#
# You can place this file in a directory in your $PATH or execute it from anywhere. It works independently from the web site's source directory.
#
# Small overview of some functions and variables at the bottom of the script file.
#
################################################################################################################################################################

#set -o errexit									# Exit in the account of an error
#set -o nounset									# Have a little error when an unset variable is attempted to be used.
#set -o pipefail								# From lines with pipes exit with the exit status of the last command that threw a
										#   non-zero exit code.
shopt -s extglob								# Set extended globbing. Needed for various things and stuff, can't be in a function.
shopt -s globstar								# Set globstar. Needed for looping through source files io multiple directory levels.
set +o histexpand								# Disable history expansion because strings are not handled safely in this script.
histchars=									# Same effect as the line above. Not sure which one I should/want to use.

version=0.9.5									# The version number of this script. If it ends in -wip it's work in progress, a
										#   version not meant for release. Other endings have more subtle meanings.
										#   Major versions (e.g. 1.x.x): Declared to be usable/somewhat good.
										#   Minor versions (e.g. x.1.x): Declared to have relevant changes/new features.
										#   Small versions (e.g. x.x.1): Declared to be publishable. Motivational release.
sitename="placeholder title"							# Fallback in case $sitename is not set through settings file or option.
url="https://www.example.com/"							# Fallback in case $url is not set through settings file or option.
style=stinpell									# Set default style that is used if no style is set through settings file or option.
logfile=sbwg.log								# Set default log file in case logging will be enabled but no filename supplied.
perpage=10 									# Set default number of entries per page - If a list of entries (tagpage or all.html)
										#   contains more than this amount of entries, it's split into several pages.

# default width/height for image gallery thumbnails in pixels - I say default, but there's currently no option to overwrite this (except with styles).
thumbsize=200
thumbsizemini=60
previewsize=800

# default number of bytes after which an entry should be cut off if displayed on a tagpage. This is not implemented, yet. This will likely never get implemented.
#cutoff=500

# size of tag icons (in pixels, either one number for both width and height or two different numbers in the format "20x16")
#tagiconsize="20x16" # Is not used yet. This will likely never get implemented.

# extra line(s) that should be inserted in every html header
#headinsert="" # This will likely be removed in the future because there are hooks now and there is a hook that can be used to insert stuff in the HTML header.

scriptname="$(basename "${0}")"
readonly scriptname


error_exit() {									# Function to exit with error code after a cleanup attempt. Used to abort with a
										#   message in case of an error.
  printf '\xE2\x9D\x8C ' 1>&2							# Print error icon to stderr
  for str; do printf "%s" "${str}" 1>&2; done					#   ... and all of the message too.
  printf "\n" 1>&2
  if option_set l; then								# If the logging option is set
    printf '\xE2\x9D\x8C ' >> "${logfile}"					# print error icon and ...
    for str; do printf "%s" "${str}" >> "${logfile}"; done			#   ... all of the message to the log file.
    printf "\n" >> "${logfile}"
  fi
  call_function_if_declared hook_error "${@}"
  clean_up 1
}

clean_up() {									# Things that should be done if if the script crashes or is otherwise interupted.
  [[ -n ${tmpdir} ]] || rm --recursive --force "${tmpdir}"			# Delete our temporary directory with all contents (if $tmpdir was even set).
  if [[ -n ${1} ]]; then
    exit "${1}"									# Exit with provided error code, with 0 if none was provided.
  else
    d "Exiting without error code."
    exit
  fi
}

trap "clean_up 1" SIGHUP SIGINT SIGTERM						# Clean up if the script is killed.

# Function to output a string to stdout if verbose mode is enabled.
v() {
  if option_set l; then								# If the logging option is set
    printf "\xE2\x84\xB9  " >> "${logfile}"					#   ... then output an info symbol ...
    for str; do printf "%s" "${str}" >> "${logfile}"; done			#   ... and all of the message to the log file.
    printf "\n" >> "${logfile}"
  fi
  if option_set v; then								# Only if in verbose or very verbose or debug mode ...
    printf "\xE2\x84\xB9  "							#   ... then display an info symbol ...
    for str; do printf "%s" "${str}"; done					#   ... and all of the message.
    printf "\n"
    return 0
  else
    return 1									# This enables executing a different command instead if verbose mode is not enabled.
  fi
}

# Function to output a string to stdout if very verbose mode is enabled.
vv() {
  if option_set l; then								# If the logging option is set
    printf "\xE2\x84\xB9  " >> "${logfile}"					#   ... then output an info symbol ...
    for str; do printf "%s" "${str}" >> "${logfile}"; done			#   ... and all of the message to the log file.
    printf "\n" >> "${logfile}"
  fi
  if option_set_multi v; then							# Only if in very verbose or debug mode ...
    printf "\xE2\x84\xB9  "							#   ... then display an info symbol ...
    for str; do printf "%s" "${str}"; done					#   ... and all of the message.
    printf "\n"
    return 0
  else
    return 1									# This enables executing a different command instead if vv mode is not enabled.
  fi
}

# Function to output a string to stdout if debig mode is enabled.
# shellcheck disable=SC2032
d() {
  if option_set l; then								# If the logging option is set
    printf '\xF0\x9F\x90\x9B ' >> "${logfile}"					#   ... then output a bug symbol ...
    for str; do printf "%s" "${str}" >> "${logfile}"; done			#   ... and all of the message to the log file.
    printf "\n" >> "${logfile}"
  fi
  if option_set d; then								# Only if in debug mode ...
    >&2 printf '\xF0\x9F\x90\x9B '						#   ... then display this message with a bug symbol in front of it.
    for str; do >&2 printf "%s" "${str}"; done					#   ... and all of the message.
    >&2 printf "\n"
  fi
}

# Function to output a string to the current outfile (The outfile usually is an HTML file that is currently being generated.)
# Detail level is currently untested. The detail feature is not implemented, yet.
o() {										# $1: detail level, $2 ...: string to write to outfile
#  if [[ $1 =~ ^[0-9]+$ ]]; then
#    local detail="${1}"
#    shift									# Don't execute the detail value. That was just to check if anything should be done.
#  else
#    local detail=0								# If no detail value is provided, assume 0 (always print it).
#  fi
#  if [[ ${details} -ge ${detail} ]]; then					# If the detail level is set large enough for this output.
    "${@}" >> "${outfile}"							# Output the rest of the arguments to the previously set $outfile.
#  else
#    return 1									# Return 1 if the set detail level is too low.
#  fi
}

# Used for hooks. If the hook is declared anywhere (commonly in the web site's settings file) then it's called with all arguments starting with the second one.
call_function_if_declared() {							# $1: Name of the function to be called.
  local f="${1}"
  if declare -F "${f}" > /dev/null; then					# If $f is a function.
    # shellcheck disable=SC2016
    d "Found hook '${f}'.${outfile+ - Current output file is '$outfile'.}"	# Notify that a hook will be executed, print the $outfile if one is set.
    shift									# Remove the function name from the list of arguments ...
    ${f} "${@}"									# ... and call the function with the remaining arguments.
    return 0									# Return 0 after the function was called if the function exists.
  else
    return 1									# Return 1 of no function of that name is known.
  fi
}

# Check if the argument is a simple number.
is_number() {
  case ${1} in
    '') d "var is empty"; return 2 ;;
    *[!0123456789]*) d "${1} has a non-digit somewhere in it."; return 1 ;;
    *) s "${1} is strictly numeric."; return 0 ;;
  esac >&2
}

# This function is used to extract a single tag's value from a list of tag lines.
grab_tag() {									# $1: list of tags, $2: tag name/type, $3: default value (optional)
#  if [ ${#} -lt 2 ]; then return 1; fi						# If not at least 2 arguments are given, fail.
  if (( ${#} < 2 )); then return 1; fi						# If not at least 2 arguments are given, fail.
  local ret
  ret="$(printf "%s" "${1}" \
    | grep --max-count 1 "^${2}:" \
    | cut --characters $(( ${#2}+2 ))-)"					# Find tag if it exists, first match, cut off its name.
  ret="${ret:-$3}"								# If empty (no tag occurance found) use default/fallback value.
  printf "%s" "${ret}"
  return 0
}

# This function is used to determin how likely it is that the passed string is a tag line from an entry or page source header. See grab_tags().
looks_like_tag() {								# $1: string to check whether it looks like a tag definition
  local num=100
  case "${1:0:9}" in								# The first 9 characters of the line
    *([[:space:]])) num=1 ;;							# Contains only whitespaces/is practically empty
    author:*|cat:*|top:*|lang:*|created:*|edited:*|sort:*|title:*|note:*\
      |redirect:*|re:*|reply:*|ref:*) num=0;;					# Start with one of these tag types
    +([[:alnum:]]):[^[:space:]]*) num=1 ;;
    "<!--"*) num=1 ;;								# Start with an HTML comment opening (for comapibility with earlier versions)
    "-->") num=1 ;;								# Are only an HTML comment closing (for comapibility with earlier versions)
    *) num=2 ;;
  esac
  return ${num}
}

# Function echoes a list of tags that is contained in the variable passed to the funtion.
# This is called every time an entry or page is processed to get a list of tags for that piece of content.
# The list of tags can then be filtered or used to look up single tags without accessing the file every time.
grab_tags() {									# $1: file path/name from which the tag header should be extracted
  local linenum=1
  local nontaglines=0
  local tags=""
  while IFS= read -r line; do
    local n
    n="$(looks_like_tag "${line}";printf "%s" "${?}")"
    if (( n == 0 )); then
      tags="${tags}"$'\n'"${line}"						# ... add it to the block that will be echoed to where the function was called from.
      nontaglines=0								# Set counter back to 0. Only care about successive lines that don't look like tags.
    else
      nontaglines=$(( nontaglines + n))						# Keep track of how many lines in a row didn't look like a tagline.
    fi
    (( nontaglines >= 3 )) && break						# If it's three in a row already, don't bother to check the rest/following lines.
   linenum=$(( linenum + 1))							# If the end of the loop is reached, increase line counter for the next iteration.
  done < "${1}"
  linenum=$(( linenum - 1))							# Have to substract the line that has been counted despite not looking like a tag.
  tags="taglines:${linenum}${tags}"						# Use the always empty first line to store the line counter so that we know later
  tags="$(printf '%s\n' "${tags}"|sort --field-separator=: -k1,1 -k2,2|uniq)"	# Sort tags according to how I happened to like them today and remove duplicates.
										#   This way the author tag should always end up first, which is useful for
										#   substituting it with an avatar picture or using CSS for something similar.
  printf "%s" "${tags}"								# Echo the result, one tag per line to be parsed ba other funtions.
}



add_header() {									# Well. Just in case.
  gen_head									# gen_head() generates the header for the current page and adds it at the same time.
}

# Function appends the navigation bar, as previously generated by gen_navbar(), to the file whose path and name is currently stored in $outfile.
add_navbar() {
  o printf "<section>"								# This is just to enable styling.
  # shellcheck disable=SC2015
  [[ -f ${tmpdir}/navbar ]] \
    && o cat "${tmpdir}/navbar" \
    || error_exit "The navbar file is not accessible."				# Include the actual side bar from the temp dir, if the file exists.
}

add_content() {
  # shellcheck disable=SC2015
#  [[ -f ${shellbase}/entries/${entry} ]] \
#    && cat "${shellbase}/entries/${entry}" \
#      | tail --lines +"$(grab_tag "${tags}" taglines)" >> "${outfile}" \
#    || error_exit "Entry '${entry}' can't be used. Is the file accessible?"
  [[ -f ${shellbase}/entries/${entry} ]] \
    && < "${shellbase}/entries/${entry}" \
      tail --lines +"$(grab_tag "${tags}" taglines)" >> "${outfile}" \
    || error_exit "Entry '${entry}' can't be used. Is the file accessible?"
}

# print footer
add_footer() {									# The content of the footer file is included in every page, entry and tagpage.
  o printf "</section>"								# This was just to enable styling.
  # shellcheck disable=SC2015
  [[ -f ${shellbase}/footer ]] \
    && o cat "${shellbase}/footer" \
    || vv "The footer file is not accessible."
}



# CREATE HTML FILE THAT IMMEDIETLY REDIRECTS TO ANOTHER URL
html_redirect() {								# $1: Path of HTML file that should redirect
										# $2: Path of target file (where to redirect to)
  call_function_if_declared hook_redirect "${@}"
  printf \
    '<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="refresh" content="0; url=/%s" />
  </head>
  <body>
    <a href="/%s" title="Follow this link to continue.">Follow this link to continue.</a>
  </body>
</html>' \
    "${2}" "${2}" > "${odir}/${1}"						# Create simple HTML document with a link + automatic redirect to the target.
  return 0
}


# Preparations that will be performed regardless of the given command line options.
prepare() {
  tmpdir="$(mktemp --directory --suffix=.SBWG)"					# Generate a path were we will store temporary files during this run of the script.
  readonly tmpdir
  d "tmpdir: ${tmpdir}"
#  d "webbbase: ${webbase}"
  d "shellbase: ${shellbase}"
  d "odir: ${odir}"
#  if [ -d "${shellbase}"/tags/ ]; then rm -f "${shellbase}"/tags/*; fi		# Remove all tag lists created in the previous run (if any).
  IFS=$'\n'									# Is this really needed? I didn't actually change $IFS, did I?
  vv "Input directory '${shellbase}'"
  vv "Output directory '${odir}'"
  if ls "${odir}"/* &>/dev/null; then
    vv "The output directory is not empty. " \
       "If you want to start clean, empty or remove the directory."
  fi
  test -d "${odir}" \
    || mkdir --parents "${odir}" \
    || error_exit "Output directory not available"
  if option_set g; then								# If the gelleries are to be generated in this run then the following commands will
										#   be used. If they are not available, substitute them with aliases that basically
										#   do nothing just to prevent the commands from failing.
    command -v convert > /dev/null 2>&1 || alias convert='v "Image not converted. Please install ImageMagick or something compatible and run the the script again or convert the image file(s) manually."'
    command -v nice > /dev/null 2>&1 || nice() { while [[ $1 != -- ]]; do shift; done; shift; "$@"; }		# This only works if there is a -- after the options of nice.
  fi
  [[ $desired_author ]] \
    && v "Ignoring entries that are not tagged with author:${desired_author}."	# Inform if only content from only one author will be generated.
  if option_set b || option_set p || option_set e || option_set t \
    || option_set g || option_set r; then gen_navbar; fi			# Generate navbar if one of those option are set. Calls gen_navbar only once.
  call_function_if_declared hook_prepare
  v "Finished basic preparations. ✅"
}


# Called if option -s or -c is set. The assumption that every complete website contains at least one CSS file is almost true after all.
# The global variable $style has to be set. If it's not set through an option or setting in the settings file, the default that is set at the top of this script ...
# ... is used.
copy_styles() {									#  $1: Style prefix
  vv "Copying styles..."
  mkdir --parents "${odir}/css"
  d "style: ${style}"
  # Copy all files that end in .css and either start with the provided style name or are named just the style name to the styles directory in the output directory.
#  cp -u "${shellbase}/styles/${style}"{.css,-*.css} "${odir}/css/" &>/dev/null
  cp --update --no-preserve=all -- "${shellbase}/styles/${style}"{.css,-*.css} \
    "${odir}/css/" &>/dev/null
#  v "Using style '${style}': " \
#    "$(ls --indicator-style=slash "${shellbase}/styles/${style}"{.css,-*.css} \
#    2>/dev/null | tr "\n" " ")"							# Don't print errors because it's okay if only one of those files exists.
  v "Using style '${style}': " \
    "$(find "${shellbase}/styles/" \
      -type f \
      \( -name "${style}.css" -o -name "${style}-*.css" \) \
      -printf "%f\n" \
    2>/dev/null | tr "\n" " ")"							# Don't print errors. It's okay if there are files that can't be used or dirs that
										#   can't be searched.
  v "Finished copying stylesheets. ✅"
}


# Copy all the contents of the files directory in the input directory to the files directory on the poutput directory.
# Everything will be copied to the output directory and thus may be accessible publicly if not prevented e.g. by .htaccess files manually.
copy_files() {
  vv "Copying files..."
  mkdir --parents "${odir}/files"
#  cp -ru "${shellbase}"/files "${odir}/" || error_exit "Could not copy the files directory to the output directory. Aborting."
  cp --recursive --update --no-preserve=all -- "${shellbase}"/files "${odir}/" \
    || error_exit "Could not copy the files directory to the output directory."
  call_function_if_declared hook_files
  v "Finished copying files and downloads. ✅"
}


# Called whenever an HTML header is to be generated.
# Variable $pagetitle has to be always set before calling gen_head. Otherwise the variable may contain a previously used string.
# Variable $outfile also has to be set. gen_head assumes that this variable contains the path of the HTML file that is currently generated and empties it!
gen_head() {
  printf '' > "${outfile}"
  o printf '<!DOCTYPE html>\n'
  o printf '<html>\n'
  o printf '  <head>\n'
  o printf '    <meta charset="UTF-8">\n'
  o printf '    <meta name="generated" content="%s">\n' "$(date)"
  o printf '    <meta name="generator" content="SBWG %s">\n' "${version}"
  o printf '    <title>%s</title>\n' "${pagetitle}"
  o printf "%s %s\n" "    <link rel=\"alternate\" type=\"application/rss+xml\"" \
    "title=\"RSS Feed for all entries from ${sitename}\" href=\"/all.rss\" />"
  shopt -s nullglob
  local sheet
  for sheet in "${shellbase}/styles/${style}"{.css,-*.css}; do
    local filename
    filename="$(basename "${sheet}")"
    if [[ -f ${sheet} ]]; then
      o printf "    <link rel=\"stylesheet\" href=\"/css/%s\">" \
        "${filename}"
    else
      v "Style sheet file '${sheet}' is not available."
    fi
  done
  option_set m && o printf "    <style>header,nav,div.entry-title-wrapper{background-color:#$(printf '%x\n' "$(shuf -i 100-255 -n 1)")$(printf '%x\n' "$(shuf -i 100-255 -n 1)")$(printf '%x\n' "$(shuf -i 100-255 -n 1)");}</style>\n"
  call_function_if_declared hook_head						# Hook can be used to insert lines in the <head> tag.
  o printf '  </head>\n'
  o printf '  <body>\n'
  o printf '    <header>\n'
  if [[ -f ${shellbase}/files/logo.svg ]]; then
    o printf '      <a href="/"><img id="logo" src="/files/logo.svg" /></a>\n'
  else
    d "Logo file not available."
  fi
  o printf '      <a href="/"><h1>%s</h1></a>\n' "${sitename}"
  o printf '    </header>\n'
  pagetitle="${sitename}"							# Set back to default in case $pagetitle it not set before calling gen_head the next time.
}


# above_entry_content is called at the beginning of the content div of an entry, whether it's on an entry page or tagpage.
above_entry_content() {
  local re
  re="$(grab_tag "${tags}" re)"
  if [[ -n ${re} ]]; then
    local reentry
#    reentry="${shellbase}/entries/${re}"
    reentry="$(find "${shellbase}/entries/" -name "${re}" -print -quit)"
    local retags
    retags="$(grab_tags "${reentry}")"
    local retitle
    retitle="$(grab_tag "${retags}" title)"
    o printf "%s%s%s" "<p><div class=\"note\">This entry is a reply to or " \
      "continuation of the entry <a href=\"/entries/${re}.html\">" \
      "<em>'${retitle}'</em></a>.</div></p>"
  fi
  re="$(grab_tag "${tags}" reply)"
  if [[ -n ${re} ]]; then
    local reentry
#    reentry="${shellbase}/entries/${re}"
    reentry="$(find "${shellbase}/entries/" -name "${re}" -print -quit)"
    local retags
    retags="$(grab_tags "${reentry}")"
    local retitle
    retitle="$(grab_tag "${retags}" title)"
    o printf "%s%s%s" "<p><div class=\"note\">This is a reply to the entry " \
      "<a href=\"/entries/${re}.html\">" \
      "<em>'${retitle}'</em></a>.</div></p>"
  fi
  re="$(grab_tag "${tags}" ref)"
  if [[ -n ${re} ]]; then
    local reentry
#    reentry="${shellbase}/entries/${re}"
    reentry="$(find "${shellbase}/entries/" -name "${re}" -print -quit)"
    local retags
    retags="$(grab_tags "${reentry}")"
    local retitle
    retitle="$(grab_tag "${retags}" title)"
    o printf "%s%s%s" "<p><div class=\"note\">This entry is referencing the entry " \
      "<a href=\"/entries/${re}.html\">" \
      "<em>'${retitle}'</em></a>.</div></p>"
  fi
  call_function_if_declared hook_above_entry_content				# Oops. Undocumented hook.
}


# CREATE A SINGLE ENTRY
# This generates the HTML page for an entry (as in /entries/entryname.html) and does not care about tagpage generation or what tagpages exist.
# gen_entries is called first and then calls gen_entry for every entry that is to be generated.
gen_entry() {									# $1: The entry for which its page should be generated.
  local entry="${1}"
  if [[ $(file --brief --mime "${shellbase}/entries/${entry}") != text* ]]	# Check again for individually generated entries.
  then
    v "${entry} is not a text file. Not generating it."
    return 2									# Don't generate anything if the file does not seem to be a text file of any sort.
  fi
#  local entryfile="${odir}/entries/${entry}.html"
  local outfile
  outfile="${odir}/entries/$(basename "${entry}").html"				# The HTML file that this entry will be generated into. Using basename because
										#   subdirectories of the source file structure are not used in the resulting URLs.
  if [[ -f ${outfile} ]]; then							# If the file already exists ...
    [[ -w ${outfile} ]] || error_exit "Can not write to ${outfile}. " \
      "Please get your permission straight or report this if you think " \
      "it's a bug. Thank you very much!"
  else										#   ... otherwise check if its directory is writable.
    [[ -w ${outfile%/*} ]] || error_exit "Can not write to ${outfile}. " \
      "Please get your permission straight or report this if you think " \
      "it's a bug. Thank you very much!"
  fi
  local tags
  tags="$(grab_tags "${shellbase}/entries/${entry}")"
  local author
  author="$(grab_tag "${tags}" author)"						# Get the author tag (if there is one).
  if [[ ${desired_author} ]] && [[ ${author} != "${desired_author}" ]]; then	# If option -a was used and this is not an entry by that author.
    return									# Don't continue with this entry.
  fi
  vv "- Generating entry '${entry}'."
  call_function_if_declared hook_entry_start
  local target
  target="$(grab_tag "${tags}" redirect)"					# Check if there is an entry to which this entry should redirect. ...
  if [[ -n ${target} ]]; then
    html_redirect "entries/$(basename "${entry}").html" \
      "entries/${target}.html"							#   ... If so, redirect the page ...
    return 2									#   ... and don't generate anything into this file.
  fi
  local title
  title="$(grab_tag "${tags}" title)"
  # print header
  if [[ ${title} ]]; then
    pagetitle="${title} | ${sitename}"
  else
    pagetitle="(Entry without title) | ${sitename}"
  fi
  gen_head
  # print side bar
  add_navbar
  o printf "<main id=\"entry"							# This is just to enable styling.
  o printf "\" class=\"${tags}" | grep --extended-regexp -- \
    '^cat:|^top:|^lang:|^author:' | tr " " "-" | tr "\n" " "			# This is just to enable styling.
  o printf "\">\n"								# This is just to enable styling.
  o printf "<div class=\"entry-title-wrapper\">\n"				# This is just to enable styling.
  call_function_if_declared hook_entry_title_start
  # print title
  o printf "<h2 id=\"entry-title\">"
  o printf "%s" "${title}"
  o printf "</h2>"
  # print last modified date
  local created
  created="$(grab_tag "${tags}" created)"
  local edited
  edited="$(grab_tag "${tags}" edited)"
  o printf "<div class=\"page-info\">\n"					# This is just to enable styling.
  o printf "%s\n" "${created}"
  if [[ -n ${edited} ]]; then o printf " (edited ${edited})"; fi
  call_function_if_declared hook_entry_title_tags_before
  local tag
  for tag in ${tags}; do
    call_function_if_declared hook_entry_title_tag_before
    case ${tag} in (author:*|cat:*|top:*|lang:*)				# If the tag starts with either of those (the tag types that visitors should be able to filter by
      if [[ -f ${shellbase}/tagicons/${tag}.png ]]				# Print a list of tags (or icons if existing) for this entry, ...
      then									# ... linked to the corresponding tagpage.
        o printf "<span class=\"tag icon %s\">" "${tag}"
        o printf "%s %s" "<a href=\"/tags/${tag}.html\"" \
          "title=\"Show all entries tagged with ${tag}\">"
        o printf "<img src=\"/tags/%s.png\" />" "${tag}"
        o printf "</a>"
        o printf "</span>\n"
      else
        o printf "<span class=\"tag %s\">" "${tag}"
        o printf "<a href=\"/tags/%s.html\">" "${tag}"
        o printf "%s" "${tag}"
        o printf "</a>"
        o printf "</span>\n"
      fi
    call_function_if_declared hook_entry_title_tag_after
    ;;
    esac
  done
  call_function_if_declared hook_entry_title_tags_after
  o printf "</div>\n"								# /page-info
  o printf "</div>\n"								# /title-wrapper
  # print entry content
  o printf "<div id=\"content\">\n"						# This is just to enable styling.
  above_entry_content								# Add reply, ref and re tags
  call_function_if_declared hook_entry_before
  add_content
  call_function_if_declared hook_entry_after
  o printf "</div>\n"								# /content
  # attach gallery (if there is one for this entry)
  o printf "<div id=\"gallery\">\n"
  local gallerypath
  gallerypath="${shellbase}/galleries/$(basename "${entry}")"
  shopt -s nocaseglob
  shopt -u nullglob								# Just to be sure. (It would assume there is a gallery for every post with nullglob enabled.)
#  ls "${gallerypath}" &> /dev/null						# See if there if a gallery folder for this entry.
#  if (( ${?} == 0 )); then								# If there is a gallery directory
  if ls "${gallerypath}" &> /dev/null; then						# If there is a gallery folder for this entry.
    local images=""
    images="$(ls -t --directory "${gallerypath}"/*.{jpeg,jpg,png} 2> /dev/null)"
    if [[ -n ${images} ]];							# If $images is not still empty
    then									#   (In other words: if there is at least one file with one of the extensions)
      o printf "<p><a href=\"/galleries/%s.html\">View Gallery</a></p>" \
        "$(basename "${entry}")"
      local image
      for image in ${images}; do						# todo: replace this line with find command, lose the lines above and the $images var
        local img
        img="$(basename "${image}")"
        o printf "%s%s%s" "<a class=\"gallery-image thumb\"" \
          "href=\"/galleries/$(basename "${entry}")/${img}\"><img class=\"thumb\"" \
          " src=\"/galleries/$(basename "${entry}")/thumbnails/${img}\"/></a>"
      done
    fi
  fi
  o printf "</div>\n"								# /gallery
  o printf "</main>\n"
  add_footer

  call_function_if_declared hook_entry_end
}


# CREATE ENTRIES
# Function that is called once if entries are to be generated in this run. gen_entry() will then be called for each entry that is to be generated.
gen_entries() {
  vv "Generating entries..."
  mkdir --parents "${odir}/entries"
  call_function_if_declared hook_entries_start
  if [[ -n ${desired_entry} ]] || [[ -n ${desired_entry_file} ]]; then
    if [[ -n ${desired_entry} ]]; then						# If an entry name was given with option -e to generate a single entry.
#      if [[ -f ${shellbase}/entries/${desired_entry} ]]; then
#      if find "${shellbase}/entries/" -name "${desired_entry}" 2> /dev/null \
#        | grep . > /dev/null; then						# See whether a file of this name is in or somewhere below the entries directory.
#        gen_entry "${desired_entry}"
      local desired_entry_full
      if desired_entry_full="$(find "${shellbase}/entries/" \
        -name "${desired_entry}" 2> /dev/null | grep .)"; then			# See whether a file of this name is in or somewhere below the entries directory.
        gen_entry "${desired_entry_full#$shellbase/entries/}"			# Generate only the entry passed as an argument to option -e
										#   (the file name found by the find command from the if line but without the
										#   beginnging of the path)
      else
        error_exit "The desired entry '${desired_entry}' " \
        "does not exist in the entries directory."
      fi
    fi
    if [[ -n ${desired_entry_file} ]]; then					# If an entry file name was given with option -E to generate a single entry.
      if [[ -f ${desired_entry_file} ]]; then					# Only absolute paths are possible that way. I'll accept that for now.
        gen_entry "${desired_entry#$shellbase/entries/}"			# Generate only the entry passed as an argument to option -E
      else
        error_exit "The desired entry '${desired_entry_file}' " \
        "does not exist in the entries directory."
      fi
    fi
  else										# If no argument (single entry name) was given to option -e or -E
    local entry
    for entry in "${entrylist[@]}"; do						# Look into each entry again.
#      gen_entry "$(basename ${entry}")"						# Generate the page for that entry.
      gen_entry "${entry#$shellbase/entries}"					# Generate the page for that entry.
    done
  fi
  call_function_if_declared hook_entries_end
  v "Finished generating entries. ✅"
}



# CREATE SINGLE GALLERY
# This generates the HTML file for a singe gallery. gen_galleries is called first and then calls gen_gallery for every gallery that is to be generated.
gen_gallery() {									# $1: Gallery name
  local gallery="${1}"
  if option_set v; then printf " - %s " "${gallery}"; fi
  mkdir --parents "${odir}/galleries/${gallery}"				# Create the gallery directory in the output directory before copying any files.
  local imagefile
  for imagefile in "${shellbase}/galleries/${gallery}"/*; do
    if [[ $(file --brief --mime "${imagefile}") == image* ]]; then		# If the file is indeed some image file (regardless of file name) ...
      cp --update --no-preserve=all -- "${imagefile}" \
        "${odir}/galleries/${gallery}/" \
        || error_exit "Could not copy '${imagefile}' to " \
        "'${odir}/galleries/${gallery}/'. Aborting."				#   ... then copy the original to the output gallery directory.
    else
      v ""; v "${imagefile} does not seem to be an image file. I'll ignore it." # Note that the convertation to various image sizes is handled separately below.
    fi
  done

#  find /path/to/files -iname '*.jpg.........' -exec mogrify ........... {} +	# Better alternative to the above line.
  local images=""
  images="$(find -- "${gallerypath}" \
    -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png")"			# Get the image files from the current gallery directory.
  if [[ -n ${images} ]];							# If $images is not still empty ...
  then										#   ... (In other words: if there is at least one file with one of the extensions)
#    if [[ -s ${shellbase}/entries/${gallery} ]]; then				# If a corresponding blog entry exists
#    if local entry="$(find "${shellbase}/entries/" -name "${gallery}" | grep .)"; then						# If a corresponding blog entry exists, put the path to it in $entry.
    local entry
    entry="$(find "${shellbase}/entries/" -name "${gallery}" | grep .)"
    if [[ -s ${entry} ]]; then							# If a corresponding blog entry exists
      local tags
      tags="$(grab_tags "${entry}")"
      local title
      title="$(grab_tag "${tags}" title)"
      pagetitle="${title} | ${sitename}"
    else
      pagetitle="${gallery} | ${sitename}"
    fi
    outfile="${odir}/galleries/${gallery}.html"
    if [[ -f ${outfile} ]]; then							# If the file already exists, check if it is writable ...
      [[ -w ${outfile} ]] || error_exit "Can not write to '${outfile}'. " \
        "Please check your permission or report this if you think may be a " \
        "bug. Thank you!"
    else									#   ... otherwise check if its directory is writable.
      [[ -w ${outfile%/*} ]] || error_exit "Can not write to '${outfile}'. " \
        "Please check your permission or report this if you think may be a " \
        "bug. Thank you!"
    fi
    call_function_if_declared hook_gallery_start
    gen_head
    add_navbar
    o printf "<main id=\"gallery\">\n"						# This is to enable styling for gallery pages.
    if [[ -s ${entry} ]]; then							# If a corresponding blog entry exists
      o printf "<a name=\"top\"><h2 id=\"page-title\">%s</h2></a>\n" "${title}"	# Print title of the blog entry because it's neater than the gallery directory name.
      o printf "%s %s %s %s\n" "<p><a title=\"There is a blog entry" \
        "appertaining to this gallery. Click here to go to the blog entry.\"" \
        "href=\"/entries/${gallery}.html\">View corresponding" \
        "blog entry</a></p>"							# Also link to that blog entry.
    else									# If no correstponding blog entry exists
      o printf "%s %s\n" "<a name=\"top\"><h2 id=\"page-title\">Image" \
        "Gallery: ${gallery}</h2></a>"						# Print a title based on the gallery directory name.
    fi
    o printf "<div id=\"gallery-minis\">\n"

    mkdir --parents "${odir}/galleries/${gallery}/minis/"			# Make sure the subdirectory for this image size exists in this gallery directory.
    mkdir --parents "${odir}/galleries/${gallery}/thumbnails/"
    mkdir --parents "${odir}/galleries/${gallery}/previews/"

    local image
    for image in ${images}; do							# Create thumbnails and other sizes for all the gallery images.
      [[ -f ${image} ]] || error_exit "'${image}' not an existing file."
      [[ $(file --brief --mime "${image}") == image* ]] || continue		# Skip this file if it's not an image.
      local img
      img="$(basename "${image}")"

      # I use this script on shared hosting. This is why I use nice for the conversions. There is no other reason.
      if [[ ! -f ${odir}/galleries/${gallery}/minis/${img} ]]; then		# If the mini of this image already exists, skip generating it.
        nice --adjustment=19 -- convert "${image}" \
          -resize "${thumbsizemini}"x"${thumbsizemini}" \
          "${odir}/galleries/${gallery}/minis/${img}" \
          || error_exit "Could not convert '${image}' to " \
          "'${odir}/galleries/${gallery}/minis/${img}'. Aborting."
        if option_set v; then printf "."; fi
      else
        if option_set v; then printf ","; fi					# Print . for a created file and , for a skipped, existing one.
      fi
      if [[ ! -f ${odir}/galleries/${gallery}/thumbnails/${img} ]]; then	# If the thumbnail of this image already exists, skip generating it.
        nice --adjustment=19 -- convert "${image}" \
          -resize "${thumbsize}"x"${thumbsize}" \
          "${odir}/galleries/${gallery}/thumbnails/${img}" \
          || error_exit "Could not convert '${image}' to " \
          "'${odir}/galleries/${gallery}/minis/${img}'. Aborting."
        if option_set v; then printf "."; fi \
      else
        if option_set v; then printf ","; fi					# Print . for a created file and , for a skipped, existing one.
      fi
      if [[ ! -f ${odir}/galleries/${gallery}/previews/${img} ]]; then 		# If the preview of this image already exists, skip generating it
        nice --adjustment=19 -- convert "${image}" \
          -resize "${previewsize}"x"${previewsize}" \
          "${odir}/galleries/${gallery}/previews/${img}" \
          || error_exit "Could not convert '${image}' to" \
          "'${odir}/galleries/${gallery}/minis/${img}'. Aborting."
        if option_set v; then printf "."; fi \
      else
        if option_set v; then printf ","; fi					# Print . for a created file and , for a skipped, existing one.
      fi
      o printf "%s %s\n" "<a class=\"gallery-image mini\" href=\"#${img}\">" \
        "<img src=\"/galleries/${gallery}/minis/${img}\"/></a>"
    done

    o printf "</div>\n"								# /gallery-minis
    call_function_if_declared hook_gallery_before
    o printf "<div id=\"gallery-previews\">\n"
    local image
    for image in ${images}; do
      [[ -f ${image} ]] || error_exit "${image} not an existing file."
      local img
      img="$(basename "${image}")"
      call_function_if_declared hook_gallery_image_before
      o printf "<br />\n"
      o printf "%s %s %s\n" "<a name=\"${img}\" class=\"gallery-image" \
        "preview\" href=\"/galleries/${gallery}/${img}\"><img" \
        "src=\"/galleries/${gallery}/previews/${img}\"/></a>"
      call_function_if_declared hook_gallery_image_after
      o printf "<br />\n"
      o printf "%s %s\n" "<a title=\"Do clickery here to go to the top of" \
        "the gallery page.\" href=\"#top\" class=\"gototop\"> Go to top ⬆</a>"
    done
    o printf "</div>\n"								# /gallery-previews
    call_function_if_declared hook_gallery_after
    o printf "</main>\n"							# /gallery
    add_footer
  else										# If there is no file with one of the supported extensions in the directory
    v "does not contain any supported image files."
  fi
  if option_set v; then printf "\n"; fi
						# Point out every file in the gallery folders that has not one of the supported file extensions, ignore directories.
  call_function_if_declared hook_gallery_end
}


# CREATE IMAGE GALLERY PAGES
gen_galleries() {
  mkdir --parents "${odir}/galleries"
  v "Generating various image sizes of galleries:"
  vv ". = Generated one file			, = Skipped an existing file"
  call_function_if_declared hook_galleries_start
  if [[ -n ${desired_gallery} ]]; then						# If the option to (re-)generate a single gallery is used ...
    if [[ -d ${shellbase}/galleries/${desired_gallery} ]]; then
      local gallerypath="${shellbase}/galleries/${desired_gallery}/"
      gen_gallery "${desired_gallery}"						#   ... generate just this single gallery ...
    else
      error_exit "The desired gallery '${desired_gallery}'" \
        "does not exist in the galleries directory."
    fi
  else
    shopt -s nocaseglob								# Should already be set, but to be save, set it again. (Because .jpg/.JPG etc.)
    local gallerypath
    for gallerypath in "${shellbase}"/galleries/*/; do
      local gallery
      gallery="$(basename "${gallerypath}")"
      gen_gallery "${gallery}"
    done
  fi
  call_function_if_declared hook_galleries_end
  v "Finished generating image gallery pages. ✅"
}



# CREATE SINGLE PAGE
# This generated the HTML file for a single page source file (from the source directory/pages/).
# gen_pages is called first and then calls gen_pages for every page that is to be generated.
gen_page() {
  local page="${1}"
  vv "- Generating page '${page}'."
  if [[ $(file --brief --mime "${shellbase}/pages/${page}") != text* ]]; then	# Check again for individually generated pages.
    v "${page} is not a text file. Not generating it."
    return 2									# Don't generate anything if the file does not seem to be a text file of any sort.
  fi
  local outfile
  outfile="${odir}/${page}.html"
  if [[ -f ${outfile} ]]; then							# If the file already exists, check if it is writable ...
    [[ -w ${outfile} ]] || error_exit "Can not write to ${outfile}." \
      "Are you sure the directory permission are set correctly?"
  else										#   ... otherwise check if its directory is writable.
    [[ -w ${outfile%/*} ]] || error_exit "Can not write to ${outfile}." \
      "Are you sure the directory permission are set correctly?"
  fi
  local tags
  tags="$(grab_tags "${shellbase}/pages/${page}")"
  local title
  title="$(grab_tag "${tags}" title)"						# The title is used as <title> tag and printed as <h2> in the body
  # print header
  call_function_if_declared hook_page_start
  if [[ -n ${title} ]]; then							# If the page has a title tag that is not empty in its source file header ...
    pagetitle="${title} | ${sitename}"						#   ... then include that title in the HTML page title.
  else										# If there is no title tag or it's empty ...
    pagetitle="${sitename}"							#   ... then use just the site name as the HTML page title.
  fi
  gen_head
  add_navbar
  o printf "<main id=\"page\">\n"						# This is just to enable styling.
  # print title
  if [[ -n ${title} ]]; then							# If the page has a title tag that is not empty in its source file header ...
    call_function_if_declared hook_page_title_before
    o printf "<h2 id=\"page-title\">%s</h2>\n" "${title}"			#   ... print that tile as h2 heading.
    call_function_if_declared hook_page_title_after
  fi
  # print page content
  call_function_if_declared hook_page_content_before
  o printf "<div id=\"content\">\n"						# This is just to enable styling.
  call_function_if_declared hook_page_content_after
  # shellcheck disable=SC2015
  [[ -f ${shellbase}/pages/${page} ]] \
    && o printf "%s" "$(< "${shellbase}/pages/${page}" \
    tail --lines=+"$(grab_tag "${tags}" taglines)")" \
    || error_exit "The page '${page}' is missing."
  o printf "</div>\n"								# /content
  o printf "</main>\n"
  add_footer

  call_function_if_declared hook_page_end
}


# CREATE PAGES
gen_pages() {
  vv "Generating pages..."

  call_function_if_declared hook_pages_start
  if [[ -n ${desired_page} ]] || [[ -n ${desired_page_file} ]]; then
    if [[ -n ${desired_page} ]]; then						# If a page name was given with option -p to generate a single page.
#      if [[ -f ${shellbase}/pages/${desired_page} ]]; then
#      if find "${shellbase}/pages/" -name "${desired_page}" 2> /dev/null \
#        | grep . > /dev/null; then						# See whether a file of this name is in or somewhere below the pages directory.
#        gen_page "${desired_page}"
      local desired_page_full
      if desired_page_full="$(find "${shellbase}/pages/" \
        -name "${desired_page}" 2> /dev/null | grep .)"; then			# See whether a file of this name is in or somewhere below the pages directory.
        gen_page "${desired_page_full#$shellbase/page/}"			# Generate only the page passed as an argument to option -p
										#   (the file name found by the find command from the if line but without the
										#   beginnging of the path)
      else
        error_exit "The desired page '${desired_page}' does not exist" \
          "in the pages directory."
      fi
    fi
    if [[ -n ${desired_page_file} ]]; then					# If a page file name was given with option -P to generate a single page.
      if [[ -f ${desired_page_file} ]]; then					# Only absolute paths are possible that way. I'll accept that for now.
        gen_page "${desired_page#$shellbase/page/}"				# Generate only the page passed as an argument to option -P
      else
        error_exit "The desired page file '${desired_page_file}'" \
          "does not exist."
      fi
    fi
  else
    local page
    for page in "${shellbase}"/pages/**; do
      [[ -d ${page} ]] && continue						# Don't use directories, only the files in them.
      [[ -f ${page} ]] \
        || error_exit "${page} is not an existing file."
d "page: $page"
d "base: $(basename "$page")"
      gen_page "$(basename "${page}")"						### This will have to be changed in order to allow multiple levels.
    done
  fi
  call_function_if_declared hook_pages_end
  v "Finished generating pages. ✅"
}





# CREATE navbar
# This function looks for all existing tags in all entries and generates a sorted list of those tags, linked to their corrosponding tagpages. Below that a sorted
# list of all galleries is placed, with each gallery name linked to its gallery page. Below that there are currently hardcoded other links, which may in future be
# replaced by a news tag type "menu:" in page source files and possibly entry source files.
# Currently menu entries and other HTML code can be inserted into the side bar using the hook_navbar() function in the settings file.
gen_navbar() {
  vv "Generating tagslist for navbar..."
  call_function_if_declared hook_navbar_start
  entrylist=()									# Start with empty array to store the entry names in.
  local entry
  for entry in "${shellbase}"/entries/**; do
    [[ -d ${shellbase}/entries/${entry} ]] && continue				# Don't use directories, only the files in them.
    local filetype
    filetype="$(file --brief --mime-type "${entry}")"				# Determine mime type of the supposed entry source file.
    if [[ ${filetype} == text/* ]]; then					# If it is a plain text file ...
      entrylist+=("${entry}")							#   ... append it to the array so that it will be used in the blog generation.
      if [[ ${filetype} != text/plain && ${filetype} != text/html ]]; then
       vv "${entry} is neither plain text nor HTML. Using it anyway."		# Let stdout know if it's a text file but not an expected type.
      fi
    else									# If it's not even a text file ...
      vv "${entry} is not a text file. Not using that thing."			#   ... let stdout know that there is a wrong file in the entries directory.
    fi
  done

  declare -a tagslist_unsorted=()						# Start with empty array
  local num=0									# Start at item 0 in the array.
  for entry in "${entrylist[@]}"; do						# Look into each entry
    local tags
    tags="$(grab_tags "${entry}")"
    local author
    author="$(grab_tag "${tags}" author)"
    if [[ ${desired_author} ]] && [[ ${author} != "${desired_author}" ]]; then	# If option -a was used and this is not an entry by that author.
      continue									# Don't include this entry.
    fi
    local tag
    for tag in ${tags}; do							# For each tag that's found in this entry
      local tagslist_unsorted[${num}]=${tag}					#   put the tag as a new item into the array.
      if [[ ${tag} == top:* ]]; then
        while [[ ${tag} == *:*:* ]]; do						# If the tag line contains two colons, it's a sub-tag.
          tag="${tag%:*}"							# To make sure all parents of existing sub-tags are also listed in the navbar ...
          (( num+=1 ))								#   ... cut off the child to make the currect tag the parent tag and ...
          local tagslist_unsorted[${num}]=${tag}				#   ... put the tag as a new item into the array.
        done
      fi
      (( num+=1 ))								# Next time next item.
    done
  done
  readarray -t tagslist < <(printf '%s\n' "${tagslist_unsorted[@]}"|sort \
    --field-separator=: -k1r,1 -k2,2|uniq)					# Put unsorted tagslist sorted into array tagslist, one item per line.

  v "Finished generating tagslist for navbar. ✅"


  local firstcat=false								# Turns true when the first category tag has been found in the below loop.
  local firstlang=false								# Turns true when the first language tag has been found in the below loop.
  local firsttop=false								# Turns true when the first topic tag has been found in the below loop.
  local firstauthor=false							# Turns true when the first author tag has been found in the below loop.
  local outfile="${tmpdir}/navbar"
  :>"${outfile}"								# Empty the navbar file in case there already is one. (Why would there... whatever.)
  # Turn the sorted list into HTML code for the navbar
  o printf "<nav>\n"
  call_function_if_declared hook_navbar_before
  o printf "%s %s %s\n" "<h3 class=\"navheading\"><a title=\"A page listing" \
    "all blog entries regardless of language, author, category and topic in" \
    "full.\" href=\"/all.html\">Weblog</a></h3>"
  local oddity=odd
  local item
  for (( i=0; i <= ${#tagslist[@]}; i++ )); do
    item="${tagslist[${i}]}"
    call_function_if_declared hook_navbar_item_before
    case ${item} in
      cat:*)									# If the item starts with "cat"
        if ! ${firstcat}; then							# Add heading before first occurance
          firstcat=true
          o printf "%s %s\n" "<div class=\"navheading\" id=\"cat\"><h3>Blog" \
            "Categories</h3><ul>"
        fi
        local tag
        tag="$(printf "%s" "${item}" | cut --characters=5-)"
        if [[ ${tag} == *:* ]]; then						# If the tag still contains a colon (meaning it is a subcatagory)
          o printf "%s%s%s" "<li class=\"cat subcat ${oddity}\"><a " \
            "title=\"Show blog entries in the category '${item}'.\"" \
            " href=\"/tags/${item}.html\">"
          o printf "${tag}" | sed 's/^[^:]*://g'					# Remove the parent tag name.
          o printf "</a></li>\n"
        else
          o printf "%s %s %s\n" "<li class=\"cat ${oddity}\"><a title=\"Show" \
            "blog entries in the category '${item}'.\"" \
            "href=\"/tags/${item}.html\">${tag}</a></li>"
        fi
        if [[ ${oddity} == odd ]]; then oddity=even; else oddity=odd; fi	# Alternate between odd and even for CSS class.
        ;;
      top:*)									# If the item starts with "top"
        if ! ${firsttop}; then							# Add heading before first occurance
          firsttop=true
          o printf "%s%s %s\n" "<div class=\"navheading\" id=\"top\"><h3>" \
            "<a href=\"/tags/top.html\" title=\"List all entries" \
            "that a topic is assigned to.\">Topics</a></h3><ul>"
        fi
        local tag
        tag="$(printf "%s" "${item}" | cut --characters=5-)"
        o printf "<li class=\""
        while [[ ${tag} == *:* ]]; do						# If the tag still contains a colon (meaning it is a subtopic)
          o printf "sub"
          tag="${tag#*:}"
        done
        o printf "%s%s%s" "top ${oddity}\"><a title=\"List blog entries" \
          "regarding the topic '${item}'.\" " \
          "href=\"/tags/${item}.html\">"
#        o printf "${tag/*:/}"
        o printf "%s" "${tag}"
        o printf "</a></li>\n"
        if [[ ${oddity} == odd ]]; then oddity=even; else oddity=odd; fi	# Alternate between odd and even for CSS class.
        ;;
      lang:*)									# If the item starts with "lang"
        if ! ${firstlang}; then							# Add heading before first occurance
          firstlang=true
          o printf "%s %s\n" "<div class=\"navheading\" id=\"lang\"><h3>By" \
            "Language</h3><ul>"
        fi
        local tag
        tag="$(printf "%s" "${item}" | cut --characters=6-)"
        o printf "%s %s %s\n" "<li class=\"lang ${oddity}\"><a title=\"Show" \
          "blog entries with the language tag ${item}.\"" \
          " href=\"/tags/${item}.html\">${tag}</a></li>"
        if [[ ${oddity} == odd ]]; then oddity=even; else oddity=odd; fi	# Alternate between odd and even for CSS class.
        ;;
      author:*)									# If the item starts with "author"
        if [[ ! ${desired_author} ]]; then					# Only include this menu item if option -a was not used
          if ! ${firstauthor}; then						# Add heading before first occurance
            firstauthor=true
            o printf "%s%s\n" "<div class=\"navheading\" id=\"author\"><h3>" \
              "By Author</h3><ul>"
          fi
          local tag
          tag="$(printf "%s" "${item}" | cut --characters=8-)"
          o printf "%s%s %s\n" "<li class=\"author ${oddity}\"><a title=\"" \
            "Show blog entries supposedly written by ${item}.\"" \
            "href=\"/tags/${item}.html\">${tag}</a></li>"
          if [[ ${oddity} == odd ]]; then oddity=even; else oddity=odd; fi	# Alternate between odd and even for CSS class.
        else
          continue								# Just don't do anything of the following with author tags if option -a was used.
        fi
        ;;
      *)									# Every other item
        ;;
    esac
    call_function_if_declared hook_navbar_item_after
    if [[ ${item%%:*} != "${tagslist[${i}+1]%%:*}" ]]; then			# If the next item in the tagslist is of a different tag type than the current one.
      case ${item} in
       top:*|cat:*|lang:*|author:*)						# If the current item is of one of the tag types that are listed in the navbar.
          call_function_if_declared hook_navbar_item_change
          o printf "</ul></div>\n"						# /.navheading
        ;;
      esac
    fi
  done

  o printf "<div class=\"navheading\" id=\"galleries\"><h3>Galleries</h3><ul>\n"
  gallerylist=()
  # shellcheck disable=SC2033
  for gallery in \
    $(find -- "${shellbase}"/galleries/ -maxdepth 1 -mindepth 1 -type d|sort)
    do
    local has_pics=false
    for pic in "${gallery}"/*; do
      if [[ $(file --brief --mime "${pic}") == image* ]]; then			# Accept any file types that start with image.
        has_pics=true								# To enable skipping empty and non-picture directories.
        break									# Sometimes it's just good to break free of the loop that is our everyday life.
      fi
    done
    if ! ${has_pics}; then
      vv "Gallery directory '$(basename "${gallery}")' contains no images."
    else
      gallerylist+=("$(basename "${gallery}")")
    fi
  done
  call_function_if_declared hook_navbar_galleries_before
  for gallery in "${gallerylist[@]}"; do
    call_function_if_declared hook_navbar_gallery_before
    if [[ -s ${shellbase}/entries/${gallery} ]]; then				# If a corresponding blog entry exists
      local tags
      tags="$(grab_tags "${shellbase}/entries/${gallery}")"
      local title
      title="$(grab_tag "${tags}" title)"
      local gallerytitle="${title}"
    else
      local gallerytitle="${gallery}"
    fi
    o printf "%s %s %s\n" "<li><a title=\"Show what's in the gallery" \
      "'${gallerytitle}'.\" href=\"/galleries/${gallery}.html\">" \
      "${gallerytitle}</a></li>"						# Add each gallery to the list
    call_function_if_declared hook_navbar_gallery_after
  done
  call_function_if_declared hook_navbar_galleries_after
  o printf "</ul></div>\n"							# /.navheading

  call_function_if_declared hook_navbar_after

  o printf "</nav>\n"

  call_function_if_declared hook_navbar_end

  v "Finished generating navbar file. ✅"

  return 0
}




# CREATE SINGLE TAGPAGE
# Generates the tagpage for the tag that's passed as an argument. This function is only called from gen_tagpages(), where the $entrylists array is prepared.
# So gen_tagpages is called first and then calls gen_tagpage for every tagpage that is to be generated, including "all" and "top".
gen_tagpage() {									# $1: Tag name (Generate all.html and its pages when no argument is given.)
										#   That was the idea at first. But to generate all.html, "all" should be
										#   given as the argument.
  local tag="${1:-all}"								# Use provided tag name. If unset, use "all", generating all.html.
  vv "- Generating tagpage '${tag}'."
  local entrycount=-1								# For paging we will count the number of entries processed so far for each tag.
  local lastentry
  lastentry=$(( $(printf "%s" "${entrylists[${tag}]}" | wc --lines) - 1 ))	# The number ob lines in the list of entries for this tag is the number of
										#   entries that will be included in this (possibly multi-page) tagpage.
  local lastpage=0								# Will be used to store the number of the last page that will be generated.
  local pagecount=0								#   The first page is page 0.
  lastpage=$(( (( lastentry + (perpage - 1)) / perpage) - 1 )) 			# Determine the number of pages necessary (number of entries devided by $perpage,
										#   always rounded up)
  if [[ ${tag} == all* ]]; then
    local outfile="${odir}/${tag}.html"
  else
    local outfile="${odir}/tags/${tag}.html"
  fi
  if [[ -f ${outfile} ]]; then							# If the file already exists, check if it is writable ...
    [[ -w ${outfile} ]] || error_exit "Can not write to ${outfile}. " \
      "Do you have write permission to that directory? Please check " \
      "and report any bugs if you wish."
  else										#   ... otherwise check if its directory is writable.
    [[ -w ${outfile%/*} ]] || error_exit "Can not write to ${outfile}. " \
      "Do you have write permission to that directory? Please check " \
      "and report any bugs if you wish."
  fi

  call_function_if_declared hook_tagpage_start
  if [[ ${tag} == all ]]; then
    local tagname="All Entries"
  else
    local tagname="Entries tagged '${tag}'"					# all.html gets a title, all other tagpages get a title with the tag in it.
  fi
  pagetitle="${tagname} | ${sitename}"
  gen_head
  add_navbar
  o printf "<main id=\"tagpage\">\n"						# This is just to enable styling.
  o printf "<h2 id=\"page-title\">%s</h2>\n" "${tagname}"
  o printf "<div id=\"filters\">\n"						# It is needed for styling.
  if [[ ! $desired_author ]] && [[ ${tag} != all ]] && [[ ${tag} != author:* ]]	# If this is not all.html and not an author tagpage (or combined author tagpage)
										#   and there was no author set with option -a.
  then										# Generate filter links where entries from more than one author are available.
    local secs
    secs="$(printf '%s\n' "${!entrylists[@]}" \
      | grep ^author:|grep --invert-match + )"					# Get all existing authors (no combined tags)
    local sec
    local filters
    local num
    for sec in ${secs}; do							# Cycle through all authors to look for entries with the current tag.
      num="$(printf "%s" "${entrylists[${sec}+${tag}]}" \
        | grep --invert-match --count ^$)"					# Number of non-empty lines of entries for the combined tagpage of
										#   this author + this tag.
      (( num > 0 )) && filters="${filters}<span class=\"filter\"><a title=\"${num} entries from ${sec} tagged with ${tag}\" href=\"/tags/${sec}+${tag}.html\">${sec}</a></span>"
    done
    [[ ${filters} == *href*href* ]] && o printf "%s%s\n" "<details><summary>" \
      "Filter further</summary> by ${filters}</details>"			# Only display the filter links if there are at least two 'href's in the string
										#   of filters, meaning that there is probably more than one filter to display.
  fi
  [[ ${tag} == author:*+*:* ]] && o printf "%s%s\n" "<span class=\"remove-fi" \
    "lter\"><a href=\"/tags/${tag#*+}.html\">Remove Filter</a></span>"	# If this is a combined author tagpage, include a link to the tagpage of the original
										#   tag (without the author filter applied).
  o printf "</div>\n"								# / #filters
  o printf "<div id=\"content\">\n"						# This is just to enable styling.
  call_function_if_declared hook_tagpage_before

  local oddity=odd								# Used for zebra stripes in CSS. (I know, new CSS versions can do that on their own.)
  local entry
  while IFS= read -r entry; do							# Reading each entry name from the current tag's array item.
										# Every line starts with the date, followed by a colon and the title of the entry.
    local entryname="${entry##*:}"						# Cut off the date/sortby string and the colon from the beginning.
    if [[ -z ${entryname} ]]; then continue; fi					# If the line is empty, stop right there.
    entrycount=$(( entrycount + 1 ))						# Counting how many entries have been processed so far for paging.
    local tags
    tags="$(grab_tags "${shellbase}/entries/${entryname}")"

    entrybasename="$(basename "${entryname}")"

    local author
    author="$(grab_tag "${tags}" author)"
    local target
    target="$(grab_tag "${tags}" redirect)"					# Check if there is an entry to which this entry should redirect.
    local title=""
    if [[ -f ${shellbase}/entries/${target} ]]; then				# If there is one and an entry by that name exists
      o printf "%s %s\n" "<!-- The following is an entry redirection caused" \
        "by the entry '${entryname}'. -->"
      entryname="${target}"							# Switch this entry generating iteration to the redirect target entry, keeping...
      tags="$(grab_tags "${shellbase}/entries/${entryname}")"			# ... the already set output file name but getting the tags of the target entry.
      title="[Redirection to:] "
#      title=$(grab_tag "${tags}" title)" ➔ "
    fi
    local created
    created="$(grab_tag "${tags}" created)"
    local edited
    edited="$(grab_tag "${tags}" edited)"
    title="${title}$(grab_tag "${tags}" title)"					# By adding the title at the end of the possibly empty string, the prefix that
										#   marks a redirection is kept.
    if [[ ${tag} == top:* ]] || [[ ${tag} == *+top:* ]] || [[ ${tag} == top ]]	# Topic tagpages (combined author tagpage or not) are handled differently:
    then									#   The content of the entries is not printed to the tagpage of a topic.
      local sortby="${entry%:*}"						# Get the sort criterium for this entry.
      										# $sortby here is the sort criterium of the entry - as it was saved in front of the
										#   entry name in the entry's line in the entrylists array - for the topic tagpage
										#   as well as the sub-heading (for sub-topics) on the sorted topic tagpage.
      if [[ ${sortby// - /:} != "${tag}" ]] \
        && [[ ${previous_sortby} != "${sortby}" ]]; then			# If the current entry's sort criterium (being its complete tag name with
										#   parents) is not exactly this tagpage's tag and is different than the one
										#   of the previously processed entry.
        o printf "<h3 class=\"sub-top-heading\">%s</h3>\n" "${sortby}"		# Print a heading to mark the beginning of a new sub-topic.
      fi
      local previous_sortby="${sortby}"						# Save the sort criterium for comparing it with the next entry's.

      if [[ ${oddity} == odd ]]; then oddity=even; else oddity=odd; fi		# Alternate between odd and even for CSS class.
      call_function_if_declared hook_tagpage_entry_top_before
      o printf "<div class=\"top-title-wrapper ${oddity} "			# This is just to enable styling.
      o printf "%s" "${tags}" | grep --extended-regexp -- \
        '^cat:|^top:|^lang:|^author:' | tr " " "-" | tr "\n" " "		# This is just to enable styling.
      o printf "\">\n"								# This is just to enable styling.
      o printf "%s %s %s\n" "<div class=\"top-entry\"><a" \
        "href=\"/entries/${entrybasename}.html\">" \
        "${title:-(Entry Without Title)}</a></div>"				# Add linked entry title
      o printf "%s%s" "<div class=\"entry-info\"><a href=\"" \
        "/entries/${entrybasename}.html\">created on ${created}"	# Add created date
      if [[ -n ${edited} ]]; then o printf " (last edited on ${edited})"; fi	# Add edited date if one was set in the entry's header.
      o printf "</a></div>\n"							# /entry-info
      call_function_if_declared hook_tagpage_entry_top_after
      o printf "</div>\n"								# /top-title-wrapper
    else									# If this is a tagpage of a tag type other than topic.
      o printf "<!-- next entry -->\n"
      o printf "<div class=\"entry-wrapper "					# This is just to enable styling.
      o printf "%s" "${tags}" | grep --extended-regexp -- \
        '^cat:|^top:|^lang:|^author:' | tr " " "-" | tr "\n" " "		# This is just to enable styling.
      o printf "\">\n"								# This is just to enable styling.
      local sortby
      sortby="$(grab_tag "${tags}" sort)"
      if [[ ${sortby} != about* ]] && [[ ${sortby} != stick* ]]; then		# If this is a stickied entry, don't print its title-wrapper.
        # print entry content and stuff to tagpage
        o printf "<div class=\"entry-title-wrapper\">\n"			# This is just to enable styling.
        call_function_if_declared hook_tagpage_entry_title_before
        o printf "%s%s\n" "<a href=\"/entries/${entrybasename}" \
          ".html\"> <h3 class=\"entry-title\">${title}</h3></a>"
        # Add last modified date of the entry, link to entry so that entries without titles can be clicked
        o printf "%s%s" "<a class=\"date\" " \
          "href=\"/entries/${entrybasename}.html\">${created}"
        if [[ -n ${edited} ]]; then o printf " (edited ${edited})"; fi
        o printf "</a>\n"								# closing created/edited line
        local tag2
        for tag2 in ${tags}; do							# For each tag of the currently processed entry print a linked tag name or icon.
          case ${tag2} in author:*|cat:*|top:*|lang:*)				# If the tag starts with either of those (the tag types that visitors should be able to filter by
            if [[ -f ${shellbase}/tagicons/${tag2}.png ]]			# If a PNG file for this tag exists, use this instead of it's name.
            then								# Tag icons must already be of the correct size.
              o printf "%s %s %s %s" "<span class=\"tag icon ${tag2}\">" \
                "<a title=\"Show all entries tagged with ${tag2}\"" \
                "href=\"/tags/${tag2}.html\"><img" \
                "src=\"/tags/${tag2}.png\" /></a></span>"		# Print a list of tags for this entry, linked to the corresponding tagpage
            else								# Print a list of tags for this entry, linked to the corresponding tagpage.
              o printf "%s %s %s" "<span class=\"tag ${tag2}\"><a title=\"" \
                "Show all entries tagged with ${tag2}\"" \
                "href=\"/tags/${tag2}.html\">${tag2}</a></span>"
            fi
          ;;
          esac
        done
        call_function_if_declared hook_tagpage_entry_title_after
        o printf "</div>\n"							# /title-wrapper
      else									# If this is a stickied entry.
        call_function_if_declared hook_tagpage_entry_stickied
      fi
      o printf "\n"
      o printf "<div class=\"entry-content-wrapper\">\n"			# This is just to enable styling.
      above_entry_content							# Add reply, ref and re tags
      call_function_if_declared hook_tagpage_entry_content_before
      # shellcheck disable=SC2015
      [[ -f ${shellbase}/entries/${entryname} ]] \
        && < "${shellbase}/entries/${entryname}" \
        tail --lines=+"$(grab_tag "${tags}" taglines)" >> "${outfile}" \
        || error_exit "Entry '${entryname}' missing."
      call_function_if_declared hook_tagpage_entry_content_after
      o printf "</div>\n"								# /entry-content-wrapper
      o printf "</div>\n"

      local gallerypath="${shellbase}/galleries/${entrybasename}"
      shopt -s nocaseglob
#      ls "${gallerypath}" &> /dev/null						# See if there if a gallery folder for this entry.
#      if (( ${?} == 0 )); then							# If there is a gallery directory
      if ls "${gallerypath}" &> /dev/null; then					# If there is a gallery folder for this entry.
        local images=""
        images="$(ls -t --directory \
          "${gallerypath}"/*.{jpeg,jpg,png} 2> /dev/null)"
        if [[ -n ${images} ]]							# If $images is not still empty
        then									#   (In other words: if there is at least one file with one of the extensions)
          o printf "<div class=\"gallery\">\n"					# This is just to enable styling.
          o printf "%s %s %s %s\n" "<p><a title=\"This blog entry has pictures" \
            "attached. Click here to view those pictures in the gallery" \
            "view.\" href=\"/galleries/${entrybasename}.html\">View" \
            "Gallery</a></p>"
          local image
          for image in ${images}; do
            local img
            img="$(basename "${image}")"
            o printf "%s %s %s %s %s %s %s\n" \
              "<a title=\"Click to view the original image file. If" \
              "you wish to obtain a larger resolution image contact the admin" \
              "of this site. They may be saving space on their webserver by" \
              "not uploading large image files.\" class=\"gallery-image" \
              "mini\" href=\"/galleries/${entrybasename}/${img}\"><img" \
              "class=\"mini\"" \
              "src=\"/galleries/${entrybasename}/minis/${img}\"/></a>"
          done
          o printf "</div>\n"							# /gallery
        fi
      fi
      o echo
    fi

# The following block generates the pager at the bottom of the tagpage, if there are more entries to list than should be listed per page (set by $perpage), and
# separates the following entries into the next file. The way the pager generation works is this:
# - This function loops through all entries that belong on the currently generated tagpage.
# - When an entry is reached that is supposed to be the last entry on this page but there are more entries to include,
#    - a pager is added,
#    - the HTML page footer is added,
#    - the output file is changed,
#    - the beginning of the new file (header, navbar) is added to the new output file.
# - In the next iteration (the next entry, one that is supposed to be at the top of the next page) the entry lands in this new HTML file.
# - After the last entry has been added, add the footer, whether ther was a pager or not.

    if [[ ${tag} != top* ]]; then						# Tags of the type top: (and the tagage top.html) don't get page breaks.
      if (( lastpage > 0 )); then						# If there is more than 1 page to generate.
        if (( $(( (entrycount + 1) % perpage )) == 0 )) \
          || [[ ${lastentry} == $(( entrycount + 1 )) ]]; then			# If the next entry would be supposed to be on the next page or this is the last
										#   entry of this tag (last entries of single-page tags are excluded
										#   through below conditions anyway)
          o printf "<div class=\"pager\">\n"					# This is just to enable styling.
          o printf "Page %d of %d" "${pagecount}" "${lastpage}"			# Print current page number and last page number.
          if (( pagecount > 0 )); then						# If this is not the first page
            if [[ ${tag} == all ]]; then					# If we are generating all.html instead of a single tagpage.
              o printf "%s%s" " - <a href=\"/${tag}-" \
                "$(( pagecount - 1 )).html\">Go to previous page</a>"		# Print link to previous page
            else								# If we are generating a tagpage for an individual tag as opposed to all.html
              o printf "%s%s" " - <a href=\"/tags/${tag}-" \
                "$(( pagecount - 1)).html\">Go to previous page</a>"		# Print link to previous page
            fi
          fi
          if (( pagecount < lastpage )); then					# If this is not the last page
            if [[ ${tag} == all ]]; then					# If we are generating all.html instead of a single tagpage.
              o printf "%s%s" " - <a href=\"/${tag}-" \
                "$(( pagecount + 1)).html\">Go to next page</a>"		# Print link to next page
            else								# If we are generating a tagpage for an individual tag as opposed to all.html
              o printf "%s%s" " - <a href=\"/tags/${tag}-" \
                "$(( pagecount + 1)).html\">Go to next page</a>"		# Print link to next page
            fi
          fi
          o printf "</div>\n"							# /pager
          o printf "</div>\n"							# /content
          o printf "</main>\n"
          add_footer
          pagecount=$(( pagecount + 1 ))
          if [[ ${tag} == all* ]]; then
            outfile="${odir}/${tag}-${pagecount}.html"				# Update the output filename so that every following entry will go to a new page.
          else
            outfile="${odir}/tags/${tag}-${pagecount}.html"			# Update the output filename so that every following entry will go to a new page.
          fi
          if [[ -f ${outfile} ]]; then						# If the file already exists, check if it is writable ...
            [[ -w ${outfile} ]] || error_exit "Can not write to" \
              "${outfile}. Permissions? Please check. Bug? Please" \
              "report. Thank you very much!"
          else									#   ... otherwise check if its directory is writable so the file can be created.
            [[ -w ${outfile%/*} ]] || error_exit "Can not write" \
              "to ${outfile}. Permissions? Please check. Bug? Please" \
              "report. Thank you very much!"
          fi

          pagetitle="${tagname} | ${sitename}"
          gen_head
          add_navbar
          o printf "<main id=\"tagpage\">"					# This is just to enable styling.
          if [[ ${tag} != all* ]]; then						# Only print title and pageinfo if it's tagpage of individual tag and not all.html.
            o printf "<h2 id=\"page-title\">%s</h2>" "${tagname}"
#            if [[ ${tag} == top* ]]; then					# Tagpages of 'topic' tags are different from those of other tag types.
#                o echo "<em class=\"pageinfo\">These entries are available" \
#                  "on the topic ${tagname}.</em>"
#              else								# If this tagpage is not for a 'topic' tag
#                o echo "<em class=\"pageinfo\">This page shows all entries" \
#                  "tagged with ${tagname}, newest/last edited entries" \
#                  "first.</em>"
#            fi
          fi
          o printf "<div id=\"content\">\n"					# This is just to enable styling.

        fi
      fi
    fi
  done <<< "${entrylists[${tag}]}"						# End of while IFS= loop.
  call_function_if_declared hook_tagpage_after
  o printf "</div>\n"								# /content
  o printf "</main>\n"
  add_footer

  if [[ ${tag} == all ]]; then							# If we are generating all.html instead of a single tagpage.
    if (( lastpage > 0 )); then
      html_redirect "all-0.html" "all.html"
    fi										# The "previous" link on page 1 links to ...-0.html instead of ....html.
  else
    if (( lastpage > 0 )); then
      html_redirect "tags/${tag}-0.html" "tags/${tag}.html"
     fi										# The "previous" link on page 1 links to ...-0.html instead of ....html.
  fi

  call_function_if_declared hook_tagpage_end
}



# CREATE TAGPAGES
# Generates first an array ($entrylists) with one array item per tag, then adds lines to these items: one line per entry, consisting of the entry's sort criteria
# and its name, then sorts these lists and then either generates
#  - a single tagpage if one was desired with option -t or
#  - all tagpages
gen_tagpages() {
  vv "Preparing tagpage generation..."
  declare -A entrylists=()							# Array used to store entry names (one per line) for each tag.
  local entry
  for entry in "${shellbase}"/entries/**; do
    entry="${entry#$shellbase/entries/}"					# Remove '$shellbase/entries/' from $entry - This leaves file name (with the rest
										#   of their path if they are in a sub-directory of entries/
    [[ -d ${shellbase}/entries/${entry} ]] && continue				# Don't use directories, only the files in them.
    [[ -f ${shellbase}/entries/${entry} ]] \
      || error_exit "${shellbase}/entries/${entry}: no file there"
    local tags
    tags="$(grab_tags "${shellbase}/entries/${entry}")"
    local created
    created="$(grab_tag "${tags}" created)"
    local author
    author="$(grab_tag "${tags}" author)"
    if [[ ${desired_author} ]] && [[ ${author} != "${desired_author}" ]]; then	# If option -a was used and this is not an entry by that author.
      continue									# Don't include this entry.
    fi
    local edited
    edited="$(grab_tag "${tags}" edited)"
    local sort
    sort="$(grab_tag "${tags}" sort)"
    if [[ -n ${sort} ]]; then
      local sortby="${sort}"
    else
      local sortby
      sortby="$(printf "%s\n${edited}" "${created:-1970-01-01}" \
        | sort --reverse | head --lines=1)"					# Pick the larger/later one of the two values, regardless of their formats ...
    fi
    call_function_if_declared hook_tagpages_entry
    local tag
    for tag in ${tags}; do							# ... and put its name into all taglists it belongs in.
      case ${tag}
      in
        (top:*)									# If this is a topic tag.
          local parenttag="${tag}"
										# For topic tag pages, their tags instead of $sortby gets used for sorting.
          entrylists[${tag}]+=$'\n'"${tag//:/ - }:${entry}"			# Add a new line with that entry to the list.
          while [[ ${parenttag} == *:* ]]; do					# If the tag still contains a colon, it's a sub-tag.
            parenttag="${parenttag%:*}"						# Cut off the youngest child and ...
            entrylists[${parenttag}]+=$'\n'"${tag//:/ - }:${entry}"		#   ... add a new line with this entry to the parent tag list as well
										# This should also include "top" as a tag, leading in the generation of top.html
										#   when there is at least one topic tag in an entry.
          done
        ;;
        (author:*|cat:*|lang:*)							# If the tag starts with either of those, then add a new line with the
          entrylists[${tag}]="${entrylists[${tag}]}"$'\n'"${sortby}:${entry}"	#  entry name to the array item corresponding to each tag of this entry.
        ;;&
        (author:*)
          if [[ ! ${desired_author} ]]; then					# Only include combined author tagpages if option -a (--author) was not used.
            local second_tags
            second_tags="$(printf "%s" "${tags}" \
              | grep --extended-regexp -- '^cat:|^top:|^lang:|^author:')"	# Get the other tags from this entry, but only these four types.
            local second_tag
            while IFS= read -r second_tag; do					# For each of them add to combined author tagpage to the list in the array so that
										#   tagpages filtered by author and one other tag at the same time are generated.
              [[ ${tag} != "${second_tag}" ]] \
                && entrylists[${tag}+${second_tag}]+=$'\n'"${sortby}:${entry}"
            done <<< "${second_tags}"
          fi
        ;;
      esac
    done
    if [[ ${sortby} =~ ^[0-9] ]] || [[ ${sort} =~ ^[0-9] ]]; then		# If the date starts with a number (i.e. is a date in the first place)
      entrylists[all]="${entrylists[all]}"$'\n'"${sortby}:${entry}"		# Add new line with entry name to "all", too.
    fi
#  done < <(find -- "${shellbase}/entries" -type f -print0)
  done
#  done
  mkdir --parents "${odir}/tags/"						# Create tags directory if it doesn't already exit.
  cp --update --no-preserve=all -- "${shellbase}/tagicons/"* "${odir}/tags/"	# Copy all tag icons - if there are any - to the html directory.
  v "Finished preparing tagpage generation. ✅"
  call_function_if_declared hook_tagpages_start
  if [[ -n ${desired_tagpage} ]]; then						# If a tagpage name was given with option -t to generate a single tagpage.
    if [[ ${desired_tagpage} == top* ]]; then
      entrylists[${desired_tagpage}]="$(printf "%s"\
        "${entrylists[${desired_tagpage}]}" | sort --key=1,1 \
        --field-separator=: | sed -e "s/^${desired_tagpage//:/ - } - //")"
    else
      entrylists[${desired_tagpage}]="$(printf "%s" \
        "${entrylists[${desired_tagpage}]}"|sort --reverse)"			# Sort topic tagpages differently from other tagpages.
    fi
    gen_tagpage "${desired_tagpage}"
  else										# If no tagpage name was given with option -t to generate a single tagpage.
    local tag
    for tag in "${!entrylists[@]}"; do						# Cycle through all tags.
      if [[ ${tag} == top* ]]; then						# This checks for top* and not top:* so that top.html gets sorted correctly, too.
        entrylists[${tag}]="$(printf "%s" "${entrylists[${tag}]}"|sort \
          --key=1,1 --field-separator=:|sed -e "s/^${tag//:/ - } - //")"
      else
        entrylists[${tag}]="$(printf "%s" "${entrylists[${tag}]}" \
          | sort --reverse)"							# Sort topic tagpages differently from other tagpages.
      fi
      gen_tagpage "${tag}"							# Generate the tagpage for that tag.
    done
  fi
  call_function_if_declared hook_tagpages_end
  v "Finished generating tagpages. ✅"
  return 0
}




# CREATE RSS FEED
# This generates a single RSS file that contains all entries that all.html contains.
# The entry's author name is used for the author tag. If none is provided, the site name is used. (This should be an e-mail address according to the standard.)
gen_rss() {
  outfile="${odir}/all.rss"
  call_function_if_declared hook_rss_start
  {
    printf "<?xml version=\"1.0\"?>\n"
    printf "%s %s\n" "<rss version=\"2.0\"" \
         "xmlns:blogChannel=\"http://backend.userland.com/blogChannelModule\">"
    printf "  <channel>\n"
    printf "    <title>%s</title>\n" "${sitename}"
    printf "    <link>%s</link>\n" "${url}"
    printf "    <description>All entries from %s (%s)</description>\n" "${sitename}" "(${url}"
#    echo "    <language>en-uk</language>"
    printf "    <lastBuildDate>%s</lastBuildDate>\n" "$(date --rfc-2822)"
    printf "    <docs>https://www.rssboard.org/rss-specification</docs>\n"
    printf "    <generator>SBWG %s</generator>\n" "${version}"
#    echo "    <webMaster></webMaster>"
    printf "    <ttl>60</ttl>\n"
  } > "${outfile}"
  call_function_if_declared hook_rss_channel

  local entry
  for entry in "${entrylist[@]}"; do
    local entryname
    entryname="$(basename "${entry}")"
    local tags
    tags="$(grab_tags "${entry}")"
    local author
    author="$(grab_tag "${tags}" author)"
    if [[ ${desired_author} ]] && [[ ${author} != "${desired_author}" ]]; then	# If option -a was used and this is not an entry by that author.
      continue									# Don't include this entry in the feed.
    fi
    vv "- Generating RSS entry '${entryname}'."
    local title
    title="$(grab_tag "${tags}" title)"
    local pubdate
    pubdate="$(grab_tag "${tags}" created)"
    test -z "${pubdate}" && pubdate="$(grab_tag "${tags}" edited)"
    [[ ! "${pubdate}" =~ ^[0-9] ]] && continue
    pubdate="$(date --rfc-2822 --date="${pubdate}")"
    local content
    content="$(tail "${entry}" --lines=+"$(grab_tag "${tags}" taglines)")"
#    local author
#    author=$(grab_tag "${tags}" author)
    o printf "    <item>\n"
    call_function_if_declared hook_rss_entry
    o printf "      <title>%s</title>" "${title:-(ᵔᴥᵔ) Untitled Post}"
    o printf "      <link>%sentries/%s.html</link>" "${url}" "${entryname}"
    o printf "      <author>%s</author>\n" "${author:-$sitename}"
    o printf "      %s%s\n" "<description><![CDATA[" \
      "${content:-(ᵔᴥᵔ) Empty Entry}]]></description>"
    o printf "      <pubDate>%s</pubDate>\n" "${pubdate}"
    o printf "      <guid>%sentries/%s.html</guid>" "${url}" "${entryname}"
    o printf "      <category>%s</category>\n" "${tags}" | tr "\n" " "
    o printf "    </item>\n"
  done
  o printf "  </channel>\n"
  call_function_if_declared hook_rss_end
  o printf "</rss>"
  v "Finished generating all.rss. ✅"
}



# Just print the help/usage and exit.
print_help() {
  case ${1} in
    "")										# In case no argument for the help option has been provided.
      cat << END_OF_HELP
Generates a web site from text files in a SBWG source directory.

Usage:
	${scriptname} [-c] [-b] [-g [GALLERY]] [-e [ENTRY]] [-p [PAGE]]
	  [-t [TAGPAGE]] [-r] [-f] [-s [STYLE]] [-i INPUT_DIR]
	  [-o OUTPUT_DIR] [-w WEBPATH] [-n ENTRIES_PER_PAGE]
	  [-v[v]] [-h [HELP_PAGE]] [-d]

More help with:

	--help|-h|-? HELP_PAGE

Possible options for HELP_PAGE:	options, input, output, pages, blog, entries, tagpages, feeds, galleries, files, styles, settings, hooks
END_OF_HELP
    ;;
    options)									# In case help option argument is "options".
      more << END_OF_HELP
At least one option is required to let the script know what it should do. Short (e.g. '-c') and long options (e.g. '--complete') are treated equaly. Options are passed to options separated by a space. Some options require an argument after them, some have optional arguments. Long and short options can be combined in one command.
The following options are available. There are additional help pagges for many of them.

	-c|--complete
		(Re-)generate the complete website. Further options to generate
		individual parts of the website are useless.

	-b|--blog
		(Re-)generate the blog, including entries, tagpages and rss
		feed.

	-g|--gallery|--galleries [GALLERY]
		(Re-)generate the gallery GALLERY. If GALLERY is not provided,
		(re-)generate all galleries.

	-e|--entry|--entries [ENTRY]
		(Re-)generate the entry ENTRY. If ENTRY is not provided,
		(re-)generate all entries.

	-p|--page|--pages [PAGE]
		(Re-)generate the page PAGE. If PAGE is not provided,
		(re-)generate all pages.

	-t|--tagpage [TAGPAGE]
		(Re-)generate the tagpage TAGPAGE. If TAGPAGE is not provided,
		(re-)generate all tagpages.

	-f|--files
		Copy the files/ direcrtory from the input directory to the
		output directory.

	-s|--style|--styles [STYLE]
		Copy stylesheet files that belong to the style STYLE from the
		input directory to the output directory. If (parts of) the site
		is generated as well, link stylesheet files belonging toSTYLE
		in the HTML headof every generated HTML file. If STYLE is not
		provided the default style as set in the site's settings file
		is used.

	-i|--input INPUT_DIR
		Read the website source files from INPUT_DIR. If this option is
		not provided, the current working directory is used.

	-o|--output OUTPUT_DIR
		Write the generated website to OUTPUT_DIR. If this option is
		not provided, use the default output directory that is set using
		the setting 'odir' in the site's settings file.

	-w|--webpath WEBPATH
		Prepent WEBPATH to all HTML links that are generated by the
		script. This feature is not functional yet!

	-n|--perpage N
		Don't put more than N entries on one tagpage.

	-v|--verbose
		Be verbose. Writes interesting messages to stdout. Providing this
		option more than once equals the option -vv (--very-verbose).

	-vv|--very-verbose
		Be very verbose. Writes many interesting messages to stdout.
		This option also sets the option -v (--verbose).

	-l|--log [FILENAME]
		Log all messages to a file. Logging verbosity is equivalent to
		debug mode and independent of whether verbose mode, very verbose
		mode, debug mode, or none of them is enabled. If the file name is
		omitted, $logfile (default: sbwg.log in the current directory ) is
		used.

	-V|--version
		Prints the version of SBWG and exits.

	-h|-?|--help
		Display this usage message on stdout and exit successfully.

	-d|--debug)
		Included messages intended for debugging purposes on stdout.
		This option also sets the options -v (--verbose) and -vv
		(--very-verbose).
END_OF_HELP
    ;;

    input)									# In case help option argument is "input".
      printf "%s %s %s %s %s\n" \
        "'--input' or '-i' INPUTDIR\nSpecifies the input directory" \
        "- the directory that contains the source file structure of the web" \
        "site. INPUTDIR can be a relative or absolute path to the" \
        "directory. If the option is omitted, SBWG will attempt to generate" \
        "a web site from the file structure in the current working directory."
    ;;

    output)									# In case help option argument is "output".
      printf "%s %s %s %s %s %s\n" \
        "'--output' or '-o' OUTPUTDIR\nSpecifies the output directory" \
        "- the directory where the generated HTML site will be placed." \
        "OUTPUTDIR can be a relative or absolute path to the directory. If" \
        "the option is omitted, the output directory specified in the" \
        "settings file will be used. If not output directory is specified" \
        "there, either, 'html/' relative to the input directory will be used."
    ;;

    pages|page)									# In case help option argument is "pages".
      printf "%s %s %s\n" \
        "'--page' or '-p' [PAGENAME]\nGenerates/updates all SBWG" \
        "pages from the pages/ directory. If PAGE is specified: Genertes/"\
        "updates only that page."
    ;;

    blog|blogs)									# In case help option argument is "blog".
      printf "%s %s\n" \
        "'--blog' or '-b'\nAlias for -e -t -r. Generates/Updates" \
        "entries, tagpages and the RSS feed."
    ;;

    entries|entry)								# In case help option argument is "entries".
      printf "%s %s %s\n" \
        "'--entry' or '-e' [ENTRYNAME]\nGenerates/updates all" \
        "entries from the entries/ directory but not the corrosponding" \
        "tagpages. If ENTRY is specified: Generates/updates only that entry."
    ;;

    tagpages|tagpage)								# In case help option argument is "tagpages".
      printf "%s %s %s %s\n" \
        "'--tagpage' or '-t' [TAGNAME]\nGenerates/updates tagpages" \
        "(topic, category, language and author tagpages as well as combined" \
        "tagpages) for all entries but not the entries themselves. If" \
        "TAGNAME is specified: Genertes/updates only the tagpage for that tag."
    ;;

    feeds|feed|rss)								# In case help option argument is "feeds".
      printf "%s %s\n" \
        "'--rss' or '-r'\nGenerates/Updates the rss feed all.rss." \
        "Other feed formats might be added in the future."
    ;;

    galleries|gallery)								# In case help option argument is "galleries".
      printf "%s %s %s\n" \
        "'--gallery' or '-g' [GALLERYNAME]\nGenerates/Updates the" \
        "gallery pages. If GALLERYNAME is specified: Generate only that" \
        "gallery."
    ;;

    files)									# In case help option argument is "files".
      printf "%s %s\n" \
        "'--files' or '-f'\nCopies the files from the files/directory" \
        "to the root of the output directory."
    ;;

    styles|style)								# In case help option argument is "styles".
      printf "%s %s %s %s %s %s\n" \
        "'--style' or '-s' [STYLESETNAME]\nCopy/update the CSS files" \
        "for the style set specified in the settings file to the output" \
        "directory. If STYLESRTNAME is specified: Copy/update the files" \
        "belonging to that style set to the output directory and use that" \
        "style in the header of HTML files if any HTML files are generated" \
        "in this run of the script."
    ;;

    settings)									# In case help option argument is "settings".
      printf "%s %s %s %s %s %s %s %s %s %s %s %s %s %s\n" \
        "The 'settings' file must exist in the web site's source" \
        "directory in order for SBWG to recognise the directory as a SBWG" \
        "web site source directory. SBWG will refuse to generate the website" \
        "if this file isn't there and it will assume that there is a valid" \
        "SBWG web site source structure in the same directory if the file" \
        "exists.\nYou can use the file to inject any Bash code that should" \
        "be sourced before the web site is generated. Commonly it is only" \
        "used to set a few global variables and declare hooks. You can copy" \
        "the settings file from the example directory or peek at it for" \
        "explanations of the settings that should be set for any new web" \
        "site. I suggest that you include at least the 'sitename' and 'url'" \
        "settings. There are more settings worth knowing about. Have a look" \
        "at the settings file in the example directory that comes with this" \
        "package."
    ;;

    hooks)									# In case help option argument is "hooks".
      printf "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n" \
        "A hook is a function that is declared in the settings file " \
        "of a web site. It does not exist in the SBWG script and therefore " \
        "is not executed if it is not included in the settings file. No " \
        "hook is required. There are many hooks that get executed at " \
        "different points in the generation of a web site. Therefore they " \
        "can be used to inject code at certain points of the script for " \
        "only the web site who's settings file contains the hook. This way " \
        "the script does not need to be edited for customisation and " \
        "different web sites can be customised differently.\nInside a hook " \
        "all variables that exist at the point at which the hook is " \
        "executed can be read and changed. Output can be added to the file " \
        "that is being generated at that point, additional commands can be " \
        "executed, and so on. This makes the settings file a very flexible " \
        "tool for the customisation of a web site and SBWG itself, and a " \
        "possible source of errors when not used carefully.\nWhen writing " \
        "an elaborate new hook (as opposed to copying an example hook or " \
        "hook from an existing settings file) you will probably not be able " \
        "to avoid reading at least some of the SBWG script to see how your " \
        "goal can be accomplished. There are a couple of helper functions " \
        "in the script that of course can also be used in hooks. I will not " \
        "explain here every part of the code that could possibly be of " \
        "relevance when writing a new hook. I don't know what you want to " \
        "do with your hook, so I wouldn't know what to limit myself to.\n "\
        "There is a table that explains all hooks in the README file that " \
        "comes with this package."
    ;;

    debug|debugging)								# In case help option argument is "debug".
      printf "%s %s %s %s %s %s\n" \
        "The --debug option (short version: -d) is meant for myself to" \
        "debug the script. I use it to display additional output while" \
        "adding new features. Most but not all of this output is removed in" \
        "the published versions. You can use it if you want the maximum" \
        "amount of output when using the script. But it's likely that you" \
        "won't find it useful."
    ;;

    messages|verbosity|logging|log)						# In case help option argument is "messages" or "verbosity" or "logging".
      # shellcheck disable=SC2016
      printf "%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s\n" \
        "There are four levels of verbosity to stdout: none, verbose," \
        "very verbose and debugging. The corrosponding options are:" \
        "No additional option for no output except errors, option --verbose" \
        "or -v for verbose mode, option --very-verbose or -vv for very" \
        "verbose mode and option --debug or -d for debugging mode. The" \
        "is mainly meant for myself and will likely add no helpful output" \
        "for the user. Additionally there is a logging option: --log or -l" \
        "for creating a log file that will contain all messages (as in" \
        "debug mode) independently of the chosen verbosity to stdout. If" \
        "no argument is passed to option --log/-l, a file by the name" \
        'specified in the global variable $logfile will be used. By default' \
        "this will be sbwg.log in the current directory but this can be" \
        "changed in a web site's settings file. If an argument is added" \
        "after the option --log/-l it will be used as path/filename for" \
        "the log file."
    ;;
    *)
      error_exit "There is no help page on the topic '${1}'. Try -h/--help" \
        " without an argument or one of the following help pages: options," \
        " input, output, pages, blog, entries, tagpages, feeds, galleries," \
        " files, styles, settings, hooks"
    ;;
  esac
  exit
}

# Check if a certain option is set.
option_set() {									# $1: letter(s) of the short option
  printf "%s" "${options}" | grep --quiet "${1}"				# Check whether the provided option letter ($1) is in the string
}										#   which would mean it was set by set_option.

# Check if a certain option is set more than once.
option_set_multi() {								# $1: letter(s) of the short option
  printf "%s" "${options}" | grep --quiet "\(${1}\).*\1"			# Returns 0 if the provided character appears more than once in $options.
}

# Check for conflicting options and stuff.
check_options() {
  d "options: ${options}"
  if ! printf "%s" "${options}" | grep --quiet "e\|p\|t\|r\|f\|s\|g"; then	# If none of these options are set then don't continue because there would be
										#   no point and it would be confusing to get no output, no error, exit code 0
										#   but also nothing done.
    error_exit "None of the used options do anything on their own. " \
               "Maybe see --help."
  fi
  if option_set_multi e; then v "More than one option to generate entries is set. I'll only use the last one that has an argument."; fi
  if option_set_multi p; then v "More than one option to generate pages is set. I'll only use the last one that has an argument."; fi
  if option_set_multi t; then v "More than one option to generate tagpages is set. I'll only use the last one that has an argument."; fi
  if option_set_multi s; then v "More than one style option is set. I'll only use the last one that has an argument."; fi
  if option_set_multi i; then v "More than one option for the input directory is set. I'll only use the last one that has an argument."; fi
  if option_set_multi o; then v "More than one option for the output directory is set. I'll only use the last one that has an argument."; fi
  if option_set_multi w; then v "More than one option for the webpath is set. I'll only use the last one that has an argument."; fi
  if option_set_multi n; then v "More than one option to set the number of entries per page is set. I'll only use the last one that has an argument."; fi
  if option_set_multi z; then v "More than one option to set the thumbnail size is set. I'll only use the last one that has an argument."; fi
  if option_set_multi a; then v "More than one author has been defined. I'll only use the last one that has an argument."; fi
  if option_set_multi h; then v "I can only display one help topic at a time. '${scriptname} --help' to get all the help."; fi
}


# Adds an option letter to the string so that we can check later what options are set/if a certain option is set.
# Also sets a global variable if the passed argument is one that belongs to the option that is being set.
set_option() {
  options="$(printf "%s%s" "${options}" "${1}")"				# Add option letter to the string so we can easily check later whether it was set.
  d "Arg for set_option '${1}': '${2}'"
  # shellcheck disable=SC2015
  case "${1}" in
    g) [[ ! "${2}" =~ (^-|^$) ]] && desired_gallery="${2}" ;;
    e) [[ ! "${2}" =~ (^-|^$) ]] && desired_entry="${2}" ;;
    p) [[ ! "${2}" =~ (^-|^$) ]] && desired_page="${2}" ;;
    t) [[ ! "${2}" =~ (^-|^$) ]] && desired_tagpage="${2}" ;;
    r) [[ ! "${2}" =~ (^-|^$) ]] && desired_feed="${2}" ;;			# This is not actually tested, yet. ööööööö Argument is not actually used. Please fix.
    s) [[ ! "${2}" =~ (^-|^$) ]] && style="${2:-style}" ;;			# Do nothing if no argument for option -s is provided. This ensures that the option
										#   will not change the style sheet set to be used from the default to being empty.
    l) [[ ! "${2}" =~ (^-|^$) ]] && logfile="${2}" ;;
    i) [[ ! "${2}" =~ (^-|^$) ]] && shellbase="${2}" || error_exit "Option -i or --input needs an argument." ;;
    o) [[ ! "${2}" =~ (^-|^$) ]] && odir="${2}" || error_exit "Option -o or --output needs an argument." ;;
    w) [[ ! "${2}" =~ (^-|^$) ]] && webbase="${2}" || error_exit "Option -w or --webpath needs an argument." ;;
    n) [[ ! "${2}" =~ (^-|^$) ]] && perpage="${2}" || error_exit "Option -n or --perpage needs an argument." ;;
    a) [[ ! "${2}" =~ (^-|^$) ]] && desired_author="${2}" || error_exit "Option -a or --author needs an argument." ;;
    h) print_help "${2}" ;;
  esac
  [[ ! "${2}" =~ (^-|^$) ]]							# Return 0 if the next argument belongs to the first one.
										#   This enables the possibility to use this function to check whether there is an
										#   argument provided for an option. (Whether the next argument exists but does
										#   not start with "-".)
}

###############################################################################################
# Fuctions end here, script starts here (apart from variables that have been set at the top). #
###############################################################################################

(( ${#} == 0 )) && error_exit "I don't know what to do. Please --help!"		# Don't do anything if no argument/option is provided.

n=0
for arg										# Use this counter instead of shift because the arguments will be parsed properly
do										#   a second time below. This here is just to get two options (-i and -h) done
  n=$(( n+1 ))									#   before the other options are determined/checked.
  case ${arg} in								# The -i/--input command line option has to be found first, if it is present.
    -i|--input) i=$(( n+1 )) ;;							# $i will be used after this loop to set $shellbase.
    -h|-\?|--help) h=$(( n+1 )); print_help "${!h}"; exit;;			# While we're at it, also check for the help option, which should work and print the
										# help screen independently and regardless of other options and absence of optionns.
    -V|--version) printf "%s\n" "${version}"; exit;;				# Oh, and the --version/-V option too.
  esac
done
shellbase="${!i:-$(pwd)}"							# If no shellbase has been set yet, set it to the current working directory.
										# This has to be set first. Otherwise the settings file with settings that are to
										# be set before the command line options are set can not be found.

if [[ -f ${shellbase}/settings ]]; then						# If there is a settings file in directory where the website source resides ...
  source "${shellbase}/settings"						#   ... overwrite the variables that are being set at the top of this script file.
else
  error_exit "There is no settings file in \"${shellbase}\". Are you sure " \
    "this is where the source files of the website you want to generate " \
    "are? If the path is correct and you don't want to set any setting, " \
    "touch \"${shellbase}/settings\" to create an empty settings file."
fi
d "Input directory from command line option: ${!i}"				# It is better to have this here, after sourcing the settings file, in case the log
										#   file gets changed by the settings file.
#while [ ${#} -gt 0 ]
while (( ${#} > 0 ))
do
  case "${1}" in
    -c|--complete) set_option petrsfg ;;					# A complete re-build consists of generating galleries, blog and pages and copying files and styles.
    -b|--blog|--blogs) set_option etr ;;
    -g|--gallery|--galleries) set_option g "${2}" && shift ;;			# Set option g, skip next argument if it's an argument belonging to -g
    -e|--entry|--entries) set_option e "${2}" && shift ;;			# Set option e, skip next argument if it's an argument belonging to -e
    -p|--page|--pages) set_option p "${2}" && shift ;;				# Set option p, skip next argument if it's an argument belonging to -p
    -t|--tagpage|--tagpages) set_option t "${2}" && shift ;;			# Set option t, skip next argument if it's an argument belonging to -t
    -r|--rss) set_option r ;;
    -f|--files) set_option f ;;
    -s|--style|--styles) set_option s "${2}" && shift ;;			# Set option s, skip next argument if it's an argument belonging to -s
    -i|--input) set_option i "${2}" && shift ;;					# This is left in because it handles all eventual cases nicely even though ...
										#   ... option i is actually checked and set before the loop now.
    -o|--output) set_option o "${2}" && shift ;;				# Set option o, skip next argument if it's an argument belonging to -o
    -w|--webpath) set_option w "${2}" && shift ;;				# Set option w, skip next argument if it's an argument belonging to -w
    -n|--perpage) set_option n "${2}" && shift ;;				# Set option n, skip next argument if it's an argument belonging to -n
    -a|--author) set_option a "${2}" && shift ;;				# Set option a, skip next argument if it's an argument belonging to -a
    -m|--magic) set_option m; v "Mixing in some magic color. 🌈" ;;		# There is no comment in this line.
    -F|--force) set_option F; vv "Forcing generation if necessary." ;;		# This is not implemented, yet.
    -v|--verbose) set_option v ;;
    -vv|--very-verbose|--veryverbose) set_option vv ;;
    -l|--log|--logging) set_option l "${2}" && shift ;;
#    -V|--version) set_option V ;;
#    -d|--debug) set_option dvv; set -o xtrace ;;				# Set debug mode, which includes very verbose mode.
    -d|--debug) set_option dvv ;;						# Set debug mode, which includes very verbose mode.
#    -h|-\?|--help) set_option h "${2}" ;;					# Include second argument because I may want to have different help pages in future.
    -*) error_exit "Unfamiliar option '${1}' - see '${scriptname} --help'" ;;
    *) error_exit "Undesired argument '${1}' - see '${scriptname} --help'" ;;
  esac
  shift
done

d "odir: ${odir}"
[[ -z ${odir} ]] && odir="${shellbase}/html"					# Set default output directory if none has been set.

check_options									# Check for colliding and redundant options.

#dir="${BASH_SOURCE%/*}"							# This block was used to source shell files from the same directory.
#if [[ ! -d ${dir} ]]; then dir="${PWD}"; fi
#d dir: ${dir}

date="$(date --iso-8601)"
#[[ $(cat "${shellbase}/last") > $(stat --format=%y "${shellbase}") ]] && [[ ! $(option_set F) ]] && d "exit because nothing has changed since last generation"

prepare
option_set s && copy_styles							# Option -s or --style or --styles
option_set f && copy_files							# Option -f or --files
#if option_set r
#then
#  [[ -f ${tmpdir}/navbar ]] || gen_navbar					# The navbar has to be generated first if it doesn't exist yet because the
#										#   entrylist array gets created there.
#  gen_rss									# Option -r or --rss
#fi
option_set r && gen_rss								# Option -r or --rss
option_set p && gen_pages							# Option -p or --page or --pages
option_set e && gen_entries							# Option -e or --entry or --entries
option_set t && gen_tagpages							# Option -t or --tagpage or --tagpages
option_set g && gen_galleries							# Option -g or --gallery or --galleries
printf "%s" "${date}" > "${shellbase}/last"					# Log last complete generation date and time.
call_function_if_declared hook_end
v "I'm done now. ✅"
clean_up 0
error_exit "Script ended unexpededly-ish."


################################################################################################################################################################
#
# Function overview
#
# Helper functions
# o()				Runs a command and redirects/appends its output to the file that's currently being generated (path stored in $outfile)
# v()				Prints a string to stdout if verbose mode, very verbose mode or debug mode is on.
# vv()				Prints a string to stdout if very verbose mode or debug mode is on.
# d()				Prints a string to stdout if debug mode is on.
# option_set()			Returns true if the passed letter is among the currently set script options.
# option_set_multi()		Returns true if the passed letter is at least twice among the currently set script options.
# set_option()			Adds an option letter to the script options. (Same as command line option letters.) Is meant to be used only at start of script.
# html_redirect()		Creates an HTML file that only redirects to another URL.
# gen_head()			Generates the HTML head for the current $outfile using $pagetitle as the page title.
# add_header()			Currently same as gen_head().
# add_navbar()			Appends the previously generated navbar file to the current $outfile.
# add_footer()			Appends the footer file's content to the current $outfile.
# error_exit()			Prints an error icon, its arguments and a newline before cleaning up exiting the script.
# call_function_if_declared()	Checks whether a function exists and if so calls it. Used for optional hooks in the sourced settings file.
# grab_tag			Outputs the first occurance of a tag in an entry or page source file.
# grab_tags			Outputs the header (consisting of tag lines) of an entry or page source file.
#
# Functions that generate a part of the web site
# prepare()			Miscellaneous preparations. Is assumed to run once before any content generation is done.
# gen_navbar()			Generates the navbar files in the temporary directory. The file is included in every HTML file that is generated.
# copy_files()			Copies the files from the input directory to the output directory.
# copy_styles()			Copied the files belonging to the style set $style to the output directory.
# gen_entries()			Prepared/initiates generation of entries. Calls gen_entry on every entry that should be generated in this run.
# gen_entry()			Generates the HTML file for one entry.
# gen_gelleries()		Prepared/initiates generation of gelleries. Calls gen_gallery on every gallery that should be generated in this run.
# gen_gallery()			Generates the HTML file and various image sizes for one gallery.
# gen_pages()			Prepared/initiates generation of pages. Calls gen_page on every page that should be generated in this run.
# gen_page()			Generates the HTML file for one page.
# gen_tagpages()		Prepared/initiates generation of tagpages. Calls gen_tagpage on every tagpage that should be generated in this run.
# gen_tagpage			Generates the HTML file(s) for one tagpage.
# gen_rss()			Generates the RSS feed (all.rss file).
#
################################################################################################################################################################


################################################################################################################################################################
#
# Variable overview
#
# Global variables:	Description:											Usually set in:
# $version		The version string/version number of this script.						script file
# $sitename		The name/title of the web site as it may appear in the title bar or page header.		settings file (fallback in script)
# $url			The domain name under which the web site can be found - used for links in RSS and ATOM feeds.	settings file (fallback in script)
# $style		The name of the style set/set of CSS files that will be used.					settings file (fallback in script)
# $perpage		The number of entries that will be listed per page on tagpages with more than $perpage entries.	settings file (fallback in script)
# $webbase		Usage of this variable is not supported, yet. Supposed to be used for directory prefix later.	script file (constant)
# $thumbsize		Size into which thumbnails will be resized (format MAXWIDTHxMAXHEIGHT)				settings file (fallback in script)
# $thumbsizemini	Size into which minis will be resized (format MAXWIDTHxMAXHEIGHT)				settings file (fallback in script)
# $previewsize		Size into which previews will be resized (format MAXWIDTHxMAXHEIGHT)				settings file (fallback in script)
# $cutoff		Variable not supported/used. Was supposed to be used for the maximum length of entry previews.	-
# $tagiconsize		Size into which tagicons will be resized (format MAXWIDTHxMAXHEIGHT)				settings file (fallback in script)
# $headinsert		Depricated. Was used to insert code in HTML header of a web site. Usage of hook recommended.	-
# $scriptname		Just the name of the script, for reference on help pages, error messages, etc.			script file (constant)
# $tmpdir		Path of the temporary directory that is used during and removed after this run of the script.	script file (constant)
# $pagetitle		Line of single page, set before HTML head is generated. Defaults to $sitename after each usage.	settings file (changed each page)
# $entrylist		Array/list of all entries that have been found in the site's source directory.			gen_navbar()
# $gallerylist		Array/list of all image galleries that have been found in the site's source directory.		gen_navbar()
# $tagslist_unsorted	Array/list of all tags that have been found in all entries, before deduplication and sorting	gen_navbar()
# $entrylists		Associative array with lists of all entries that have a certain tag - key is the tag		gen_tagpages()
# $options		String of all option letters that are set in this run of the script.				set_option() (used through command line)
# $shellbase		Path of the web site's source directory. Base path of all source files. 			script file
# $logfile		Path/filename of the log file, in case logging is enabled.					script file
#
# Commonly used local variables:
# $outfile		Path and name of the file that's currently being processed. Used by many functions, mainly through o().
#
################################################################################################################################################################



################################################################################################################################################################
# todo:
#
# v1.0.0 milestones:
# - get rid of $webbase (state somewhere that odir can be set accordingly instead). links in the html will never be created dynamically so this var is useless
#     - replace variable with "/"
#     - remove/change description in all documentation
# - check if html redirects still work and their html source look okay
# - fix all html output (links) to only use basenames of entrys/entrynames
# - entries (and everything else with multi-level directory support) should be found with find when only the entry name is passed to option -e etc.
# - extensive testing of all sorts of things with all sorts of content and styles and files and options and arguments
# - shellcheck again (example site, draft0, steephlog)
# - come up with errors that could occur and try to catch them
# - fill tensum with a responsive or mobile style
# - fill the new help pages at least a bit more
# - settings file durch shellcheck hindurchjagen
#
# (new) features:
# - some tag to hide an entry on tagpages (get rid of the useless (on the tagpages) duplicate redirect entries)
# - (2) audio player at the end of an entry with a relevant audio file
# - (2) comment/email link per author below entries
# - (2) generate an rss feed for each tagpage
# - (2) new tag type 'menu:' for pages and entries
# - (2) a tag type or file nameing convention for entries that should be ignored as of now (drafts)
#         - better: a setting, which tags should be excluded from the generation (e.g. cat:draft)
# - (1) option improvements: check for multiple letters after a dash, use equal sign for lomg version options
# - (1) create install script
# - (1) option --settings (--setting, -S) to pass variable setting from the command line to overwrite settings set in the settings file
# - (1) support multiple arguments for desired_tagpage, desired_entry, etc. this should also enable using "subdirectory/*" as an argument (when subdorectories are supported in the future).
# - (1) scan subdirectories in the pages directory of the input
# - (1) also look for entries, pages, galleries in subdirectories
#         - list galleries sorted according to the subdirectories they are in
# - (1) list entries with no top: tag under top:none or something similar? Or make a page that lists all entries that don't have any tags that enable them to be found? (or just that don't have any cat:, top: or redirect: tags?
# - (1) create cat.html that lists all existing category tags in such a way that it can be made a tag cloud with css
# - (0) new option: --list (-l) for listing entries with a certain tag? could just use grep though
# - (0) stickied entries for topic tagpages
# - (0) also generate tagpages that exclude entries that have a specific tag? (like cat:incomplete) no, too much.
# - (0) generate atom feeds
# - (0) create galleries.html that lists all galleries
#         - just an idea: icons for each gallery for: number of images, corrosponding entry exists
#         - link to corrosponding entries as well
# - (0) video support in galleries
# - (0) Add a FAQ file
# - (0?) new tag type: book: (or call it something else) for entries (and/or pages?) that represent chapters, kind of like top: but with a more book-like index and sorted solely by chapter. this could be done manually by using menu: though
# - (?) if no file in the input directory was touched since the last generation, don't do anything
#         - or just print v or vv to inform of the unnecessarity
#             - no
#         - should be possible to be overwritten with option --force
#             - --force could also be used to ignore excluded tags
# - (?) maybe: only generate new/edited entries, pages, ... by default, use -a to force to generate all entries/galleries/pages
#         - yeah, but "how" is the problem
#         - maybe also use option --force to overwrite that
# - setting in settings file for combined tagpages: which combinations should be generated? default: combos=author
#
# particularities:
# - (4) reduce redundancy in sidebar and footer output
#         - new functions for those
#         - new hooks in those functions
# - (4) test substitudes for when nice and convert are not available
# - (?) reduce redundancy in entry title wrapper generation by outsourcing to a new function
# - (3) use more printf instead of echo
# - (3) use format specifiers when using printf
# - (3) make sure script does not return 0 when nothing has been done (e.g. option -v was set but nothing else)
# - (2) don't generate desired tagpage if there will be no entries on it
# - (2) error when non-existing author is desired by option -a
# - (2) don't generate combined tagpages for authors if option -a was used
# - (2) don't generate anything if no posts by desired author exist
# - (1) use of css should be optional
# - (2) replace header and footer files by hooks? maybe a hook in the example site that reads those files.
# - (1) support BMP and GIF files (and other types?) in galleries? (JPEG2000, HEIC, TIFF, and so on)
# - (1) make sure no web log related things are generated if there are no entires. a web site without a web log should be possible.
# - (1) link from sub-top tagpage to parent
# - (1) sub-topic h3 headings should be linked to their corrosponding sub-topic tagpages
# - (0) don't use nice in the script itself. create an alias for convert in settings file instead (what implications does that have for the convert substitution?)
# - (0) Remove the hardcoded site logo. This should be a style or settings file thing.
# - (0) make certain variables readonly? ... think about that
# - should mkdir -m be used to set directory permissions in the odir?
# - update FAQ about file and directory permissions
# - use rsync instead of cp for files files and gallery images and style sheets and tagicons?
# - remove option -z/--thumbnail from documentation (readme, howto, faq, example site)
# - check that all entities that can ever be passed as a command line option argument (entries, tagpages, pages, (...?)) are handled/looped through with their full file name and make sure that globs can be used as command line option arguments
# - use path for $style so that style sets can be used from sbwg directory or from custom site source directory or anywhere really?
# - include a manual redirect link in the redirect page 0 html files
# - use --mime-type instead of --mime for file checks?
# - finalise the different help pages
# - create helper functions that check whether a file exist, is a regular files, is writable, ... and also error_exits with a message if not
# - create helper function "isimage" or something to check the format of a file to decide whether to proceed - this could reduce redundancy a little bit
# - also copy files files if size is different?
# - check for unwanted characters in tags (chars that can not be in a url) and exclude them (better: leave them in but don't link them but use title to show reason)
# - only output gallery generation information in very-verbose mode
# - create functions for generating re-usable html snippets like entries with wrappers
#     - cache output of these functions to tmpdir
#
#
# nishnash:
# - check first line of settings file instead of whether the file exists
# - include filter(s) on pages other than 0
# - responsive css
# - test what happens if no convert is installed. Should still finish generation but print a bunch of message if in verbose mode instead of converting the images
# - check file type before using tagicons
# - html attributes are currently not checked for unwanted character - e.g. an tags or entry titles that starts with " will lead to title attributes that are closed too early
# - change variables of output files to the same everywhere ($outfile) (hooks will be able to trust that that's the variable that's used everywhere
#    - and also make sure that these variables are always only local
# - ignore taglines but consider them as tags if they start with a #?
# - note tags should be acceptable in page headers, not just entries
# - check and improve rss validity?
# - find solution for the outputs that get written to the log file before it gets changed by the -l option with an argument. maybe have the logfile in the tmp dir at first, then move it to the right location when it is set.
# - check file format when generating desired_(something)
#    - maybe change functions to have the desired_... variable set the list of items to be generated to only that one, before these things are tested
# - outsorce repetetive code into functions
# - maybe make obvious by name that a variable is global
# - make sure all functions return a (meaningful) exit code
# - (1) outsource the arrays to a something separated values file in tmpdir to get ready for huge content bases
# - check if option -F / --force is set when generating/skippig galleries
# - think about where option --force/-F can be used/implemented
# - allow more file name extensions for gallery image files (upper/lower, ..., more types, SVG?, JPEG2000?)
# - RSS:
#    - exclude stickied posts from feeds
#    - exclude redirections from feeds
#    - properly escape special XML characters in RSS feed
#    - append thumbnails of related pictures to rss items
#    - convert/properly escape content/description in rss feed (html escape stuff for spaces, <, >, ...)
#    - convert/escape urls in rss tags (no spaces and stuff)
#    - create an rss file for each tagpage
#    - maybe introduce a variable to define the maximum amount of entries in the all.rss feed. paging can be implemented in the atom feed. rss is for making compromises https://datatracker.ietf.org/doc/html/rfc5005#section-3
# - improve image gallery generator
#    - gallery page html and styling
#    - create index.html in html/galleries/ with list of galleries?
# - use SVGs für tagicons? At least allow it. and also allow GIF and others. best support the same as with entrypics and galleries. would "all types starting with "image" work?
# - introduce an array for entries that contains a newline separated list of tags for that entry (all tags including author, sort and title and everything)
# - make different help pages (use argument for option -h)
# - convert images in parallel if parallel is available. or what about xargs?
# - use find instead of ls to be safer and include files in subdirectories but not the directories themselves
# - display complete path of input and output directory in vv mode, no "../html" and the like - actually, just convert $shellbase to absolute and make sure $odir is absolute
# - option --fix for re-generating one entry and all tagpages and feeds it's on?
# - line 1017 (commented "Only print title and pageinfo if it's tagpage of individual tag and not all.html."): Use case instead of if, include a line for all.html like "This page shows..."
# - remove sub-cat support or extend to infinite depth as with subtops
# - don't display parent cat of sub-cat in list in navbar
#
################################################################################################################################################################
