#!/bin/bash

################################################################################################################################################################
#
# SBWG - steeph's bash website generator
# Last changed: 2021-10-10
# 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.
declare -A entrytags

version=0.9.7									# 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

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


w() {										# Function to print a warning - This is not for error. Script will continue.
  printf '\xE2\x9A\xA0  ' 1>&2							# Print warning 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\x9A\xA0  ' >> "${logfile}"					#   ... print warning icon and ...
    printf '%s: ' "$(date --utc)" >> "${logfile}"				#   ... then output a bug symbol ...
    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_warning "${@}"					# todo: Document this hook in README
  warned=true
}

e() {										# 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 ...
    printf '%s: ' "$(date --utc)" >> "${logfile}"				#   ... then output a bug symbol ...
    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 "${@}"
  option_set F || clean_up 1							# Abort if force mode is not enabled.
  errored=true
}

clean_up() {									# Things that should be done if if the script crashes or is otherwise interupted.
  [[ -z ${tmpdir} ]] || rm --recursive --force "${tmpdir}"			# Delete our temporary directory with all contents (if $tmpdir was even set).
  [[ ${warned} ]] && w "There has been at least one warning. Check " \
    "error output or log and see what you can do about it."
  [[ ${errored} ]] && w "There has been at least one error that has " \
    "been ignored because force mode was enabled. Check error output " \
    "or log and see what you can do about it."
  if [[ -n ${1} ]]; then
    d "Exiting with code ${1}."
    exit "${1}"									# Exit with provided error code, with 0 if none was provided.
  else
    d "Exiting without error code. (Was there maybe no error?)"
    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 ...
    printf '%s: ' "$(date --utc)" >> "${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 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 ...
    printf '%s: ' "$(date --utc)" >> "${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_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 ...
    printf '%s: ' "$(date --utc)" >> "${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
}

# Check whether a string is an item in the tagslist (whether a specific tag exists in any entry of the site)
tag_exists() {
  local tag
  for tag in "${tagslist[@]}"; do
    [[ "${tag}" == "${1}" ]] && return 0
  done
  return 1
}

# 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 (( ${#} < 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 tags
  local cachefile="${cachedir}/${1#$shellbase/}"				# Cache tags in that file. It's just the simplest way to make it a unique file to ...
										#   ... go by the path of the source file that's already unique.
  if [[ -r ${cachefile} ]]; then						# If there already is a cache file for this entry/page and the file can be read ...
    cat "${cachefile}" || w "Could not use cache file '${cachefile}'."		#   ... just use the contents of the existing cache file and warn if not possible.
  else										# If there is no readable cache file for this entry/page existing, generate it.
    local linenum=1
    local nontaglines=0
    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.
    :|install -D /dev/stdin "${cachefile}" \
      || w "Cache file '${cachefile}' can not be created."			# Make sure the cache file and all directories above it exist.
    printf "%s" "${tags}" | tee "${cachefile}"					# Echo the result, one tag per line to be parsed by other funtions ...
										#   ... and save it to the cache file so that the tags don't need to be checked ...
										#   ... all again the next time the function is called.
  fi
}



add_header() {									# Well. Just in case I'll want to use that.
  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" \
    || e "The navbar file is not accessible or could not be added."		# Include the actual side bar/navbar from the temp dir, if the file exists.
}

add_content() {
  [[ -f ${shellbase}/entries/${entry} ]] \
    && < "${shellbase}/entries/${entry}" \
      tail --lines +"$(grab_tag "${tags}" taglines)" >> "${outfile}" \
    || e "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" \
    || w "The footer file is not accessible or could not be added."
}



# 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
  option_set C && cachedir="${shellbase}/cache" || cachedir="${tmpdir}/cache"	# Cache files will be only temporary unless cache mode is enabled.
  test -d "${cachedir=}" \
    || mkdir --parents "${cachedir=}" \
    || w "Cache directory not available."
  d "tmpdir: ${tmpdir}"
  d "shellbase: ${shellbase}"
  d "odir: ${odir}"
#  IFS=$'\n'									# Is this really needed? I didn't actually change $IFS, did I?
#  declare -A entrytags								# This associative array will be used to cache the tag headers of entry files.
  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}" \
    || e "Output directory not available."

  if [[ -n ${desired_entry_name} ]] || [[ -n ${desired_entry_file} ]]; then
    if [[ -n ${desired_entry_name} ]]; then					# If an entry name was given with option -e to generate a single entry.
      if desired_entry_full="$(find "${shellbase}/entries/" \
        -name "${desired_entry_name}" 2> /dev/null | grep .)"; then		# See whether a file of this name is in or somewhere below the entries directory.
        vv "Will generate the single entry named '${desired_entry_name}'."
      else
        e "The desired entry '${desired_entry_name}' " \
          "does not exist in the entries directory."				# Error if the entry does not exist.
      fi
    fi
    if [[ -n ${desired_entry_file} ]]; then					# If an entry file name was given with option -E to generate a single entry.
      desired_entry_file="$(readlink -f "${desired_entry_file}")"		# Turn the possibly relative path into an absolute path.
      if [[ -f ${desired_entry_file} ]]; then					# If there actually is a file there
        vv "Will generate the single entry from '${desired_entry_file}'."
      else
        e "The desired entry '${desired_entry_file}' does not exist."		# Error if the file does not exist.
      fi
    fi
  fi

  if [[ -n ${desired_page_name} ]] || [[ -n ${desired_page_file} ]]; then
    if [[ -n ${desired_page_name} ]]; then					# If a page name was given with option -p to generate a single page.
      if desired_page_full="$(find "${shellbase}/pages/" \
        -name "${desired_page_name}" 2> /dev/null | grep .)"; then		# See whether a file of this name is in or somewhere below the pages directory.
        vv "Will generate the single page named '${desired_page_name}'."
      else
        e "The desired page '${desired_page_name}' does not exist " \
          "in the pages directory."						# Error if the page does not exist.
      fi
    fi
    if [[ -n ${desired_page_file} ]]; then					# If a page file name was given with option -P to generate a single page.
      desired_entry_file="$(readlink -f "${desired_entry_file}")"		# Turn the possibly relative path into an absolute path.
      if [[ -f ${desired_page_file} ]]; then					# Only absolute paths are possible that way. I'll accept that for now.
        vv "Will generate the single page from '${desired_page_file}'."
      else
        e "The desired page file '${desired_page_file}' " \
          "does not exist."							# Error if the file does not exist.

      fi
    fi
  fi
  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."' #test
    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. #test
    if [[ -n ${desired_gallery} ]]; then					# If a gallery name was given with option -g to generate a single gallery.
      if [[ -d ${shellbase}/galleries/${desired_gallery} ]]; then		# See whether a directory of this name is in the galleries directory.
        vv "Will generate the single gallery named '${desired_gallery}'."
      else
        e "The desired gallery '${desired_gallery}' does not exist " \
          "in the galleries directory."						# Error if the gallery does not exist.
      fi
    fi
  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 P || option_set e \
    || 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}"
  cp --update --no-preserve=all -- \
    "${shellbase}/styles/${style}"{.css,-*.css} \
    "${odir}/css/" &>/dev/null							# 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.
  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 --recursive --update --no-preserve=all -- "${shellbase}"/files "${odir}/" \
    || e "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}"							# Empty the output file.
  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
      w "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'
  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="$(find "${shellbase}/entries/" -name "${re}" -print -quit)"
    if [[ -n ${reentry} ]]; then
      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>"
    else
      w "The entry with the name '${re}' was not found. Not including " \
        "re link to this entry."
    fi
  fi
  re="$(grab_tag "${tags}" reply)"
  if [[ -n ${re} ]]; then
    local reentry
    reentry="$(find "${shellbase}/entries/" -name "${re}" -print -quit)"
    if [[ -n ${reentry} ]]; then
      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>"
    else
      w "The entry with the name '${re}' was not found. Not including " \
        "reply link to this entry."
    fi
  fi
  re="$(grab_tag "${tags}" ref)"
  if [[ -n ${re} ]]; then
    local reentry
    reentry="$(find "${shellbase}/entries/" -name "${re}" -print -quit)"
    if [[ -n ${reentry} ]]; then
      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>"
    else
      w "The entry with the name '${re}' was not found. Not including " \
        "ref link to this entry."
    fi
  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 ! option_set F \
    && [[ $(file --brief --mime "${shellbase}/entries/${entry}") != text* ]]	# Check again for individually generated entries.
  then
    w "${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 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} ]] || e "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%/*} ]] || e "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)"
  if [[ ${title} ]]; then							# print header
    pagetitle="${title} | ${sitename}"
  else
    pagetitle="(Entry without title) | ${sitename}"
  fi
  gen_head
  add_navbar									# print side bar
  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
  o printf "<h2 id=\"entry-title\">%s</h2>" "${title}"
  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
  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
  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.)
  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)" #todo: change to find and accept all image files (see gen_gallery)
    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 id=\"gallery\">\n"
      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
      o printf "</div>\n"							# /gallery
    fi
  fi
  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

# todo: hier case-statement: de_name, de_file, alle.
#       find für de_name
#       das gleiche bei gen_pages checken und evtl. bei gen_tagpages?
    [[ -n ${desired_entry_name} ]] \
      && 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 in the prepare
										#   function but without the beginnging of the path)
    [[ -n ${desired_entry_file} ]] \
      && gen_entry "${desired_entry_file#$shellbase/entries/}"			# Generate only the entry passed as an argument to option -E

  if [[ -z ${desired_entry_name} ]] && [[ -z ${desired_entry_file} ]]; then	# If neither option -e nor -E was given an argument, generate all entries.
    local entry
    for entry in "${entrylist[@]}"; do						# Look into each entry again.
      if option_set P; then							# If parallel mode is enabled.
        gen_entry "${entry#$shellbase/entries}" &				# Generate the entrypages in parallel.
      else
        gen_entry "${entry#$shellbase/entries}"					# Generate entrypages.
      fi
    done
    wait									# Wait for any background processes from parallel generation to finish.
  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 option_set F || [[ $(file --brief --mime "${imagefile}") == image* ]]	# If the file is indeed some image file (regardless of file name) ...
    then
      cp --update --no-preserve=all -- "${imagefile}" \
        "${odir}/galleries/${gallery}/" \
        || e "Could not copy '${imagefile}' to " \
        "'${odir}/galleries/${gallery}/'. Aborting."				#   ... then copy the original to the output gallery directory.
    else
      w ""; w "${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

#  local images=""
#  images="$(find -- "${gallerypath}" \
#    -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png")"			# Get the image files from the current gallery directory.
#  local images="${gallerypath}"/*						# Store glob of all files in the gallery directory. This will be expanded later.
#  local images
#  images=$(find "${gallerypath}" -path "*")
  if [[ $(ls -A "${gallerypath}") ]]						# If $images is not still empty ...
  then										#   ... (In other words: if there is at least one file with one of the extensions)
    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} ]] || e "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%/*} ]] || e "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 "${gallerypath}"*; do						# Create thumbnails and other sizes for all the gallery images.
      [[ -f ${image} ]] || e "'${image}' not an existing file."
      if [[ $(file --brief --mime "${image}") == image* ]] && ! option_set F	# If this file is not an image file and force mode is not enabled ... #test
      then
        continue								#   ... skip it.
      fi
      local img
      img="$(basename "${image}")"

#todo: put the three constructs below into one for loop: for imgdir in minis thumbnails previews do; ...; done
      # Sometimes I use this script on shared hosting. This is why I use nice for the conversions. There is no other reason. I'm not in a hurry, after all.
      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}" \
          || e "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}" \
          || e "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}" \
          || e "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 "${gallerypath}"*; do
      [[ -f ${image} ]] || e "${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
    w "does not contain any supported image files."
  fi
  if option_set v; then printf "\n"; fi
  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
      e "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}")"
      if option_set P; then							# If parallel mode is enabled.
        gen_gallery "${gallery}" &						# Generate the gallery pages in parallel.
      else
        gen_gallery "${gallery}"						# Generate the gallary pages.
      fi
    done
    wait									# Wait for any background processes from parallel generation to finish.
  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 ! option_set F \
    && [[ $(file --brief --mime "${shellbase}/pages/${page}") != text* ]]
  then										# Check again for individually generated pages.
    w "${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}/$(basename "${page}").html"					# The HTML file that this pagey 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, check if it is writable ...
    [[ -w ${outfile} ]] || e "Can not write to ${outfile}." \
      "Are you sure the directory permission are set correctly?"
  else										#   ... otherwise check if its directory is writable.
    [[ -w ${outfile%/*} ]] || e "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)")" \
    || e "The page '${page}' is missing or not accessible."
  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

  [[ -n ${desired_page_name} ]] \
    && gen_page "${desired_page_full#$shellbase/pages/}"			# Generate only the page passed as an argument to option -p
										#   (the file name found by the find command from the if line in the prepare
										#   but without the function beginnging of the path)
  [[ -n ${desired_page_file} ]] \
    && gen_page "${desired_page#$shellbase/page/}"				# Generate only the page passed as an argument to option -P

  if [[ -z ${desired_page_name} ]] && [[ -z ${desired_page_file} ]]; then	# If neither option -P nor -p was given an argument, generate all pages.
    local page
    for page in "${shellbase}"/pages/**; do
      [[ -d ${page} ]] && continue						# Don't use directories, only the files in them.
      [[ -f ${page} ]] \
        || e "${page} is not an existing file."
      if option_set P; then							# If parallel mode is enabled.
        ### This will have to be changed in order to allow multiple levels.
        gen_page "$(basename "${page}")" &					# Generate the pages in parallel.
      else
        ### This will have to be changed in order to allow multiple levels.
        gen_page "$(basename "${page}")"					# Generate the pages.
      fi
    done
    wait									# Wait for any background processes from parallel generation to finish.
  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 ${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/* ]] || option_set F; then			# If it is a plain text file or force mode is enabled ... #test
      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 ...
      w "${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.

  process_this_entry() {							# Put this block into a function so it can be called in parallel or not as needed.
    local tags
    tags="$(grab_tags "${entry}")"
    local author
    author="$(grab_tag "${tags}" author)"
    if [[ ${desired_author} ]] && [[ ${author} != "${desired_author}" ]]	# If option -a was used and this is not an entry by that author.
    then
      return									# Don't include this entry.
    fi
    local tag
    while IFS= read -r tag; do							# For each tag that's been found in this entry.
      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 <<< "${tags}"
  }
  for entry in "${entrylist[@]}"; do						# Look into each entry
    if option_set P; then							# If parallel mode is enabled.
     process_this_entry &							# Process the entries in parallel.
    else
      process_this_entry							# Process the entries sequentially.
   fi
  done


  wait										# Wait for any background processes from parallel generation to finish.
  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 "%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=()
  for gallery in "${shellbase}"/galleries/*/; do
    local has_pics=false
    for pic in "${gallery}"*; do						# Check each file in the the gallery directory.
      if [[ $(file --brief --mime "${pic}") == image* ]] || option_set F; then	# Accept any file types that start with image or any file if force mode is enabled. #test
        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
      w "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

  # Generate HTML file from navbar file (This can be used as a separate menu file e.g. for mobile styles.)
  outfile="${odir}/navbar.html"
  gen_head
  o tee<"${tmpdir}/navbar"
#  o printf "<section>\n"
#  add_footer
  # shellcheck disable=SC2015
  [[ -f ${shellbase}/footer ]] \
    && o cat "${shellbase}/footer" \
    || w "The footer file is not accessible."

  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 special tagpage 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} ]] || e "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%/*} ]] || e "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:] "
    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}" \
        || e "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
      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} ]] || e "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%/*} ]] || e "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}"
          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..."
  if [[ -n ${desired_tagpage} ]]; then						# If a tagpage name was given with option -t to generate a single tagpage.
    if [[ "${desired_tagpage}" != all ]] \
      && [[ "${desired_tagpage}" != top ]]; then				# If the desired tagpage is not one of 'top' or 'all'.
      if [[ "${desired_tagpage}" != cat:* ]] \
        && [[ "${desired_tagpage}" != top:* ]] \
        && [[ "${desired_tagpage}" != author:* ]] \
        && [[ "${desired_tagpage}" != lang:* ]]; then				# If the desired tagpage is not for a tag of one of these tag types...
        w "The tag '${desired_tagpage}' is not of a supported tag type " \
          "(for generating a tagpage). Not generating it."
        return 1								#  ... then don't generate it.
      fi
    fi
  fi
  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} ]] \
      || e "${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
    while IFS= read -r tag; do
      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 <<< "${tags}"
    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
  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}" != all ]] \
      && [[ "${desired_tagpage}" != top ]]; then				# If the desired tagpage is not one of 'top' or 'all'.
      if ! tag_exists "${desired_tagpage}" && ! option_set F; then		# If the desired tagpage is not in the array of existing tags.
        e "The tag '${desired_tagpage}' does not seem to exist in any entry" \
          " of this web site."							# If desired tag doesn't exist: Ignore tag if force flag is set, abort otherwise.
          return 1
      fi
    fi
    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
      if option_set P; then							# If parallel mode is enabled.
        gen_tagpage "${tag}" &							# Generate the tagpage for that tag.
      else
        gen_tagpage "${tag}"							# Generate the tagpage for that tag.
      fi
    done
    wait									# Wait for any background processes from parallel generation to finish.
  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
#  local wait_pid=
  gen_this_entry() {									# Putting this block into a function so it can be called in the backround
											#  or not as needed. (For parallelising)
    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.
      return										# 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] ]] && return
    pubdate="$(date --rfc-2822 --date="${pubdate}")"
    local content
    content="$(tail "${entry}" --lines=+"$(grab_tag "${tags}" taglines)")"
    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"
  }
  for entry in "${entrylist[@]}"; do
    if option_set P; then							# If parallel mode is enabled.
#      gen_this_entry &								# Generate the entries in parallel.
      gen_this_entry								# Pralellisation for RSS feed generation stays disabled for now. I don't think it
										#   would work with the current rudimentary implementation.
    else
      gen_this_entry								# Generate the entries sequentially.
    fi
  done
  wait										# Wait for any background processes from parallel generation to finish.
  o printf "  </channel>\n"
  call_function_if_declared hook_rss_end
  o printf "</rss>"
  v "Finished generating all.rss. ✅"
}


print_usage() {
  printf "Usage:
	${scriptname} [-c] [-b] [-g [GALLERY]] [-e [ENTRY]] [-p [PAGE]]
	  [-t [TAGPAGE]] [-r] [-f] [-s [STYLE]] [-i INPUT_DIR]
	  [-o OUTPUT_DIR] [-n ENTRIES_PER_PAGE] [-F] [-v[v]] [-d]

	${scriptname} -h [HELP_PAGE]

	${scriptname} -V\n"
}


# Just print the help/usage and exit.
print_help() {
  case ${1} in
    "")										# In case no argument for the help option has been provided.
      printf 'Generates a web site from text files in a SBWG source directory.\n\n'
      print_usage
      printf '\nMore help with:

	--help|-h|-? HELP_PAGE

Possible options for HELP_PAGE:	options, input, output, pages, blog, entries, tagpages, feeds, galleries, files, styles, settings, hooks\n'
    ;;
    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.

	-a|--author AUTHOR
		Ignore all entries that do not have an author:AUTHOR tag in
		their header. (Re-)generates a site as if it didn't contain any
		entries by other authors/without an author tag.

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

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

	-P|--parallel
		This flag enables parallel mode. SBWG will try to process all
		entries, pages, tagpages, etc. at the same time in parallel.
		- This feature is experimental and has known bugs. It is
		recommended to only use it if you know what it is doing at
		this point.

	-C|--cache
		If this flag is set, the script will create and keep a cache
		of the existing tags to speed up the generation process. If this
		option has been used, the option -U/--update-only can be used
		when generating the web site again in the future to skip already
		cached content.

	-U|--update-only
		If this flag is set, the script will look for existing cache
		files from previous generatiions of the web site. If a cache
		of tags exists, it will not be generated again. Using this
		option can speed up the generation process a lot IF the option
		-C/--cache has been used in a previous run of the script.

	-F|--force
		Continue processing and generating the web site even when an
		error occured, when the script suspects that there is a mistake
		in the options or input files or when it foresees that it will
		not finish without an error. Without this flag the script will
		abort when a file is missing or not accessible, when the output
		can not be written or some other error occurs. Use this option
		if the script wrongly skips input files because it suspects them
		to be of a wrong format or expects parts of them (tags) to cause
		problems down the line. You should check whether there really is
		no problem with the input files. Or in other words: Only use this
		option if you know what you are doing. If you have to use this
		option despite there being no problem with the input files, this
		can be considered a bug in the script and should be reported so
		that in can be avoided for other web sites.

	-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".
      more << END_OF_HELP
'--input INPUTDIR' or '-i INPUTDIR'
  Specifies 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
  current working directory. If the option is omitted, SBWG will attempt to
  generate a web site from the file structure in the current working directory.
  The input directory has to contain a `settings` file in order to be recognised
  by SBWG as a SBWG source directory.
END_OF_HELP
    ;;

    output)									# In case help option argument is "output".
      more << END_OF_HELP
'--output OUTPUTDIR' or '-o OUTPUTDIR'
  Specifies the output directory - the directory where the generated HTML site
  will be placed. OUTPUTDIR can be a relative or absolute path to the current
  working directory.
  If the option is omitted, the output directory specified in the settings file
  will be used. If no output directory is specified there, either, 'html/'
  inside the input directory will be used.
  Provided that you either want to use the default 'html/' or the setting 'odir'
  is set in the settings file, you don't have to use this option to generade or
  update a web site. But it is useful if you want to (re-)generate the web site
  into a directory different from the one you usually use, e.g. to test
  someting or. For example you could create an alias for generating your web
  site into a directory where you can look at it before publishing it.
  Another example where this option is used is a multi-author blog of which one
  author also runs a personal blog with the same content. In thaat case the web
  site can be generated normally into one output directory and with the option
  `--author`/`-a` for a single-author version into a different directory.
END_OF_HELP
    ;;

    pages|page)									# In case help option argument is "pages".
      more << END_OF_HELP
'--page [PAGENAME]' or '-p [PAGENAME]'
  Generates/updates all SBWG pages from the 'pages/' directory. If PAGE is
  specified: Genertes/updates only the specified page."
  This option can be used to update the web site when the content(s) of (a)
  page(s) have been changed. If nothin else has been changed, this is the
  quickest way to update the web site.
END_OF_HELP
    ;;

    blog|blogs)									# In case help option argument is "blog".
      more << END_OF_HELP
'--blog' or '-b'
  Alias for -e -t -r. Generates/updates all parts of the weblog of a web site:
  entries, tagpages and the RSS feed. When a new blog entry has been added or
  existing entries or their tags have been edited and nothing else has been
  changed, only this option is necessary to get a complete update of the web
  site. If nothing else has been changed since the last update, this will lead
  to the same result as a complete re-build with option -c/--complete.
  If you deliberately don't want to (re-)generate the RSS feed, you can use
  both options `--tagpage`/`-t` and `--entries`/`-e` to generate/update the
  complete weblog without the RSS file.
END_OF_HELP
    ;;

    entries|entry)								# In case help option argument is "entries".
      more << END_OF_HELP
'--entry [ENTRYNAME]' or '-e [ENTRYNAME]'
  Generates/updates all entries from the 'entries/' directory but not the
  corrosponding tagpages. If ENTRYNAME is specified: Generates/updates only the
  specified entry.
  You will probably not find this optionvery useful on its own as it will
  generate/update the HTML files of entries themselves but not the tagpages of
  a weblog, where the content of the entries is also included. This option is
  part of the option `--blog`/`-b`, which (re-)generates the entrie weblog.
END_OF_HELP
    ;;

    tagpages|tagpage)								# In case help option argument is "tagpages".
      more << END_OF_HELP
'--tagpage' or '-t' [TAGNAME]
  Generates/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.
  As this option only generates the pages listing the blog content for a
  specific tag but not the entry pages, you will likely not find this option on
  its own very useful. It is part of the option `--blog`/`-b` that is
  recommended to be used for generating/updating a weblog.
END_OF_HELP
    ;;

    feeds|feed|rss)								# In case help option argument is "feeds".
      more << END_OF_HELP
'--rss' or '-r'
  Generates/updates the RSS feed all.rss. Other feed formats might be added in
  the future.
  If this option is specified, the file `all.rss` in the output directory (the
  generated web site's root) will be (re-)generated, containing all entries of
  the web site. This can be considered a rudimentary solution to the requirement
  that readers of the weblog can subscribe to it with a feedreader.
  Other feed formats and RSS files for individual tagpages are not supported,
  yet.
END_OF_HELP
    ;;

    galleries|gallery)								# In case help option argument is "galleries".
      more << END_OF_HELP
'--gallery [GALLERYNAME]' or '-g [GALLERYNAME]'
  Generates/updates the gallery pages. If GALLERYNAME is specified: Generate
  only the specified gallery.
  If this option is specified without an argument, all image files from all
  galleries in the web site's source directory will be copied to the output
  directory, any non-existing thumbnails and other sizes will be generated
  and all HTML files for all gelleries will be (re-)generared. Thumb nails
  and other image sizes generated by SBWG will not be updated if the files
  already exist.
  If the option is used with an argument (e.g. `-g my-gallery`), the same
  tasks are performed but only for that one gallery (my-gallery).
END_OF_HELP
    ;;

    files)									# In case help option argument is "files".
      more << END_OF_HELP
'--files' or '-f'
  Copies the files from the files/ directory to the root of the output
  directory.
  If the option is specified, all files from the files/ directory inside the
  input directory are blindly copied to the files/ directory in the output
  directory. This means that any files with the same name that already exist
  in the output directory's files/ directory will be overwritten if possible
  but files that exist don't exist in the input directory's files/ directory
  will not be touched.
END_OF_HELP
    ;;

    styles|style)								# In case help option argument is "styles".
      more << END_OF_HELP
'--style [STYLESETNAME]' or '-s [STYLESETNAME]'
  Copy/update the CSS files for the style set specified in the settings file to
  the output directory. If STYLESETNAME 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.
  Without this option, or the `--complete`/`-c` option, which includes the
  `--styles`/`-s` option, SBWG will not copy any style sets from the source
  directory to the output directory. Since the CSS files usually don't get
  changed as frequently as the content of a web site, you have to specify this
  option whenever you want to update the CSS in the output dirtectory. In the
  headers of the generated HTML files every file belonging to the style set
  specified in the web site's settings file will be linked. If no style set is
  specified in the settings file, the default set named 'stinpell' will be
  used.
  If the option `--styles`/`-s` is specified without an argument, the same
  style set is used and all files belonging to it are copied to the output
  directory.
  If the option is used with an argument, e.g. `-s my-style`, all files
  belonging to the style set with that name (my-style) are copied and linked
  in the header of every generated HTML file.
END_OF_HELP
    ;;

    settings)									# In case help option argument is "settings".
      more << END_OF_HELP
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.
You 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. The settings file can be empty if you don't want to declare any hooks and
only want to use default settings. But 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.
END_OF_HELP
    ;;

    hooks)									# In case help option argument is "hooks".
      more << END_OF_HELP
A hook is a function that is declared in the settings file of a web site. (See
'--help settings'.) 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 process
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.
Inside 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. When
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. There is a table
that explains all hooks in the README file that comes with this package.
END_OF_HELP
    ;;

    parallel)									# In case help option argument is "parallel".
      more << END_OF_HELP
If this flag is set, the script will try to process some parts of the web site
generation in parallel. Currently this feature is in a very rudimentary and
highly experimental state. The method of paralleling the generation process
will completely change when/if this feature is completed.
It is not recommended to use this option.
END_OF_HELP
    ;;

    force)									# In case help option argument is "force".
      more << END_OF_HELP
By default (if the flag '--force' or '-F' is not set) the script will suddenly
stop when it suspects something to be wrong, leading to an incomplete or not
fully updated web site. If the force flag is set, the script will try to
continue despite errors. This may also lead to an incomplete, but more likely
to a broken or otherwise unusable web site in the output directory.
Examples where the force flag takes effect: When generation of a single entry,
page or tagpage is desired (arguments to options '-e', '-E', '-p', '-P' or
'-t') but the desired file or tag does not exist or is otherwise not usable.
When the partition where the temprary files are stored is full.
When a tag is too long to be used as a file name.
In any other situation where SBWG detects an error and wants to abort.
It is not recommended to use this option. It is pretty much only useful to
circumvent undiscovered bugs that abort the script for no good reason. Use
this option only if you know why you need to use it and expect things to go
wrong. This feature is not fully implemented, yet.
END_OF_HELP
    ;;

    debug|debugging)								# In case help option argument is "debug".
      more << END_OF_HELP
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 of SBWG. 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. Note that more output the script generates, the
longer the generation proceess takes to finish.
END_OF_HELP
    ;;

    messages|verbosity|logging|log)						# In case help option argument is "messages" or "verbosity" or "logging".
      more << END_OF_HELP
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 warnings and 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 latter 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.
END_OF_HELP
    ;;
    *)
      e "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\|E\|p\|P\|t\|r\|f\|s\|g"	# If none of these options are set then don't continue because there would be
  then										#   no point and it would be confusing to get no output, no error, exit code 0
										#   but also nothing done.
    e "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 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 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_name="${2}" ;;
    E) [[ ! "${2}" =~ (^-|^$) ]] && desired_entry_file="${2}" ;;
    p) [[ ! "${2}" =~ (^-|^$) ]] && desired_page_name="${2}" ;;
    P) [[ ! "${2}" =~ (^-|^$) ]] && desired_page_file="${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}"
      :|install -D /dev/stdin "${logfile}" \
        || w "Log file '${logfile}' can not be created."			# Make sure the log file and all directories above it exist.
    };;
    i) [[ ! "${2}" =~ (^-|^$) ]] && shellbase="${2}" || e "Option -i or --input needs an argument." ;;
    o) [[ ! "${2}" =~ (^-|^$) ]] && odir="${2}" || e "Option -o or --output needs an argument." ;;
    n) [[ ! "${2}" =~ (^-|^$) ]] && perpage="${2}" || e "Option -n or --perpage needs an argument." ;;
    a) [[ ! "${2}" =~ (^-|^$) ]] && desired_author="${2}" || e "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 )) && e "I don't know what to do. Please --help!"			# Don't do anything if no argument/option is provided.
(( ${#} == 0 )) && { w 'No option was provided.'; print_usage; exit 0; }	# If no option/argument is provided, just print usage and exit.

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 %s\n" "${scriptname}" "${version}"; exit;;		# Oh, and the --version/-V option too.
    -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
  e "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
    -E) 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
    -P) 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
    -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.
    -P|--parallel) set_option P; v "Experimental parallel mode is enabled." ;;	# Enable parallel mode (experiemntal).
    -F|--force) set_option F; v "Force mode is enabled. Will ignore " \
      "warnings if any occur." ;;						# Set force mode: Force continuation when an error or mistake is suspected.
    -C|--cache) set_option C; v "Caching mode is enabled. Will create " \
      "caching files for future use." ;;					# Enable caching: Create cache files in the input directory for faster processing.
    -U|--update-only|--update) set_option U; v "Update-only mode is " \
      "enabled. Will look for existing cache files and only re-renerate " \
      "new content." ;;								# Set update only mode: Only re-renerate things that are not cached.
#    -h|-\?|--help) set_option h "${2}" ;;					# Include second argument because I may want to have different help pages in future.
    -*) e "Unfamiliar option '${1}' - see '${scriptname} --help'" ;;
    *) e "Unwanted 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"

#d "started"

prepare
option_set s && copy_styles							# Option -s or --style or --styles
option_set f && copy_files							# Option -f or --files
option_set r && gen_rss								# Option -r or --rss
option_set p || option_set P && gen_pages					# Option -p or --page or --pages
option_set e || 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
e "Script ended unexpededly-ish."


################################################################################################################################################################
#
# Conventions
#
# * No output is produced unless something seems or goes wrong (warning or error) or one of the options -v -vv or -d is used.
# * v	is used to print the rough progress of the script to let the user know through stdout which part of the generation process is undergoing or finished.
# * vv	is used to let the user knwo through stdout which item the script is currently processing as well as note finding which don't require action or examination.
# * d	is used to print all sorts of debugging output that a user usually does not need to see or know.
# * w	is used to print a warning whenever the script suspects something to be wrong or faulty. The script will continue if option -F is set, abort otherwise.
# * e	is used to print an error message and exit the script/abort regardless of the set option.
# * Custom hooks should note that they have been called and what they are doing at least with a debug message.
#
################################################################################################################################################################


################################################################################################################################################################
#
# 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.
# w()				Prints a warning message and aborts after clean-up if force mode (option -F/--force) is not set.
# e()				Prints an error message and aborts the script after clean-up.
# 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.
# call_function_if_declared()	Checks whether a function exists and if so calls it. Used for optional hooks in the sourced settings file.
# is_number()			Checks whether the passed argument is a simple integer or not.
# tag_exists()			Checks whether the passed string is an item in the $tagslist array (whether the tag exists)
# 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)
# $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()
# $tagslist		Array/list of all tags that have been found in all entries, sorted, no duplicates		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:
# - subdirs in pages do not work right now
# - how to handle multiple pages and entries with the same name
# - test all the things that have a #test comment
# - test warning function
# - test force mode in the case of at least one situation where it is used
# - search this file for: todo, ###, ööö and fix those things
# - change gen_gallery to accept argument for size (mini, preview, thumbnail) and use that for generating attached galleries in gen_entry and gen_tagpage
# - what happens if more than one files for the desired entry, page, etc. are found?
# - remove obsolete lines (commented out or debug messages)
# - extensive testing of all sorts of things with all sorts of content and styles and files and options and arguments
#     - multiple author tags
#     - multiple edited
#     - multiple created tags
#     - multiple redirect tags
#     - multiple re tags
#     - multiple ref tags
#     - multiple reply tags
#     - multiple sort tags
#     - multiple title tags
#     - multiple lang tags
#     - test all options with example site, draft0 and trap site
#         - script that runs them all in a row
#     - find functions that i can pass weird stuff to and pass weird stuff to them
#     - call functions from settings file and pass them stupid shit
#     - no write permission in odir
#     - no read permissions in input dir
#     - generate into non-existant odir (parent also doesn't exist)
#     - input dir disappears during generation
#     - odir disappears during generation
#     - running out of space/quota during generation
#     - more than just thousands of entries
#     - millions of different tags
#     - different LANG, LC_ALL and LC_CTYPE
#     - weirdly long tags, dates, etc
#     - recursive redirecting - or at least redirecting to another redirection
#     - run on other computers, with other bash verisons, slow computer
#     - with empty settings file
#     - tag names from /dev/urandom
# - come up with errors that could occur and try to catch them
# - settings file durch shellcheck hindurchjagen
# - shellcheck script again
#
# (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/question
#         - maybe also use option --force to overwrite that
# - setting in settings file for combined tagpages: which combinations should be generated? default: combos=author
# - idea for future restructuring of content handling:
#     - only one content directory named content
#     - subdirectories content/pages/, content/entries/, content/books/ (and galleries?)
#     - variables (or array?) for default input directories of the different content types
#     - tag type type: to declare a source file to be of that type
#         - if a file has the wrong type, ignore it
#         - if a file has no type and is in the right directory, use it
# - check version in setting file before doing anything. also error_exit if sourcing the settings file produces errors/an error
#
# 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?
# - test whether desired files exist before really starting (put check in check_options())
# - use all files of a style set name, not just css; or at least use css and js files, but better all files because images may be needed for a style, e.g. for background images
# - update FAQ about file and directory permissions
# - at least notify if no files are present for the chosen style set
# - use rsync instead of cp for files files and gallery images and style sheets and tagicons?
# - check ver early on whether the desired tagpage, entry, page exist (don't wait to abort/skip
# - create helper script to check for problems with tags in source files
# - rename helper scripts (sbwg-...)
# - 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?
# - add hooks: before generating starts, in add_navbar, in other helper functions, in help printing function (enable adding own help pages?)
# - finalise the different help pages
# - create helper functions that check whether a file exist, is a regular files, is writable, ... and also es 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
# - option -n for only generating navbar
#     - change option -t to include options t and n
# - 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
# - maybe: collect notable messages and print them at the end of output, e.g. items that weren't generated, files that were missing, tags and filemames with non-allowed character, ... (well, no, it's okay as it is as long as everything of that sort is printed in verbose mode)
# - 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)
# - create helper function that encodes urls for use in links. always using this function when printing links to outfile would enable the use of e.g. % and # characters in file names and tag values
# - 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
#
################################################################################################################################################################
