#!/bin/bash

################################################################################################################################################################
#
# SBWG - steeph's bash website generator
# Last changed: 2021-12-11
# 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.14									# 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		# Default: stinpell					# Set default style that is used if no style is set through settings file or option.
perpage=10 		# Default: 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		# Default: 200
minisize=60		# Default: 60
previewsize=800		# Default: 800

maxfnl=255		# Default: 255	Minimum: 6				# Maximum filename length in bytes without filename extension (e.g. '.html')

scriptname="$(basename "${0}")"
readonly scriptname
readonly warningsign='\xE2\x9A\xA0  '
readonly errorsign='\xE2\x9D\x8C '
readonly infosign='\xE2\x84\xB9  '
readonly debugsign='\xF0\x9F\x90\x9B '

w() {										# Function to print a warning - This is not for error. Script will continue.
  if option_set l; then								# If the logging option is set ...
    printf "${warningsign}" >> "${logfile}"					#   ... print warning icon and ...
    printf '%s: ' "$(date --utc)" >> "${logfile}"				#   ... then output a bug symbol ...
#    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
  printf "${warningsign}" 1>&2							# Print warning icon to stdout
  for str; do printf "%s" "${str}" 1>&2; done					#   ... and all of the message to stderr.
  printf "\n" 1>&2
  call_function_if_declared hook_warning "${@}"
  warned=true
}

err() {
:
}


e() {										# Function to exit with error code after a cleanup attempt. Used to abort with a
										#   message in case of an error.
  if option_set l; then								# If the logging option is set ...
    printf "${errorsign}" >> "${logfile}"					#   ... print error icon and ...
    printf '%s: ' "$(date --utc)" >> "${logfile}"				#   ... then output a bug symbol ...
#    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
  printf "${errorsign}" 1>&2							# Print error icon to stdout
  for str; do printf "%s" "${str}" 1>&2; done					#   ... and all of the message to stderr.
  printf "\n" 1>&2
  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.
    ${logfile:+View warnings from logfile with: grep ^'$(printf "$warningsign")' '$logfile'}"
  [[ ${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.
    ${logfile:+View errors from logfile with: grep ^'$(printf "$errorsign")' '$logfile'}"
  if [[ -n ${1} ]]; then
    d "Exiting with code ${1}."
#    printf "x%s" "${date}" >> "${shellbase}/last"				# Log incomplete generation run with date.
    [[ -w ${shellbase}/last ]] && \
      printf "%s:%s" "${1}" "${date}" > "${shellbase}/last"			# Log complete or incomplete generation run with exid code and date.
    exit "${1}"									# Exit with provided error code, with 0 if none was provided.
  else
    d "Exiting without error code. (Was there maybe no error?)"
    [[ -w ${shellbase}/last ]] && \
      printf "%s" "${date}" >> "${shellbase}/last"				# Log date of last run without exid code.
    exit
  fi
}

trap "clean_up 1" SIGHUP SIGINT SIGTERM SIGQUIT					# 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 "${infosign}" >> "${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 "${infosign}" >> "${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 "${infosign}"							#   ... 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 "${debugsign}" >> "${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 "${debugsign}"							#   ... 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"
    printf "${debugsign}"							#   ... then display this message with a bug symbol in front of it.
    for str; do printf "%s" "${str}"; done					#   ... and all of the message.
    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() {										# $@: String(s) to write to outfile
#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}" \
      || e "Failed trying to execute a command that should write its output " \
      "to '${outfile}'. Please see error output for more information. Check " \
      "e.g. space, permissions and filename length. See '${scriptname} -h " \
      "options' on option '-R' for a solution for the latter or check your "\
      "tag values for forbidden characters."					# 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
}

# Function to output something to the current $cachefile as well as the current $outfile.
c() {
    "${@}" | tee --append -- "${cachefile}" "${outfile}" > /dev/null \
      || e "Failed trying to execute a command that should write its output " \
      "to '${cachefile}' and '${outfile}'. Please see error output for more " \
      "information. Check e.g. space, permissions and filename length. See " \
      "'${scriptname} -h options' on option '-R' for a solution for the " \
      "latter or check your tag values for forbidden characters."		# Output the rest of the arguments to the previously set $outfile.
}



# Add the current date and time to each line of something.
predate() {
  while read line ; do
    echo "$(date): ${line}"
  done
}



# Convert long path string into a path string whose elements each are at most $maxfnl bytes long while keeping them sort of unique.
pathreducer() {									# $1: Original (possibly long) path (relative or absolute). The last element should
										#     be a file name, not a directory name ($1 should not end with a directory name).
  call_function_if_declared hook_pathreducer
  if option_set R; then								# If the option -R/--reduce-paths is set.
    (										# Put this in a subshell so that the LANG and LC_ALL settings don't last.
#w "-m- ${maxfnl}"
      LANG=C LC_ALL=C
      IFS='/' read -ra path <<< "${1}"
      maxbl=${maxfnl%.*}							# Extract/set the max. basename length. Will be same as $maxfnl if there is no period
      partno=1
      for part in "${path[@]}"; do
        call_function_if_declared hook_pathreducer_part_start
        newsuff=''
        if (( ${partno} == ${#path[@]} )); then					# Filename extensions should only be handled for the last path element, which will
										#   presumably be a file name and not a directory name. Some filesystems can't have
										#   dots in directory names.
          suff="${part##*.}"
#          if [[ ${suff} == ?* ]] && [[ ${suff} != ${part} ]]; then		# If the element contains anything after its last period.
#          if [[ ${part} == *.* ]] && [[ ${suff} == ?* ]] \
          if [[ ${part} == *.* ]] && [[ -n ${suff} ]] \
            && ! [[ ${suff} =~ [^[:alnum:]] ]] && (( ${#suff} <= 12 )); then	# If the element contains a period and there is anything after its last period ...
          									#    ... and that anything is only numbers and letters and no longer than 12 bytes.
										#    ... then this suffix will be assumed to resemble the filename extension.
#            hassuffix=true
            # Reduced suffix cut off after $maxsl bytes. (+1 because the period is included)
            newsuff=".$(printf "%s" "${suff}" | tr --delete --complement '[:alnum:].' | tr '[:upper:]' '[:lower:]')"
#w
#w "-p- ${part}"
#w "-s- ${suff}"
#w "-n- ${newsuff}"
            if [[ ${maxfnl} == *?.?* ]]; then					# If there is a period in the definition of the maximum filename length.
              maxsl=${maxfnl#*.}							# Extract/set the maximum suffix length.
              # Cut off the suffix after $maxsl bytes.
              newsuff="$(printf "%s" "${newsuff}" | head --bytes $((${maxsl}+1)))"
              additional_cut_off_from_base=0
            else									# If no definition for the maximum suffix length is set
              additional_cut_off_from_base=${#newsuff}				# Remember the number of characters that have to be cut off the basename in order
            									#   to not exceed the maximum filename length when the suffix is included.
              if (( $((${maxfnl}-${additional_cut_off_from_base}<6)) )); then	# If the reduced filename would became shorter than 6 bytes (which is the minimum
            									#   that is required to add the hash)
                newsuff="$(printf "%s" "${newsuff}" | head --bytes $((${maxfnl}-6)))"	# Shorten the suffix to be short enough to be included.
                w "Pathreducer tried to shorten the path element '${part}' " \
                  "to be shorter than 0 bytes. You know that's not possible. " \
                  "Instead it cut off the filename extension" \
                  "${newsuff:+ to '$newsuff'}."					# Warn that the suffix was cut off despite there being no maximum suffix length
                additional_cut_off_from_base=${#newsuff}
              fi
              if [[ ${newsuff} == . ]]; then
                newsuff=''							# Result should never end in a dot. (e.g. when the suff had to be cut off)
                additional_cut_off_from_base=0
              fi
            fi
          else
            additional_cut_off_from_base=0
          fi
        else
          additional_cut_off_from_base=0
        fi
        if [[ -n ${newsuff} ]]; then
           base="${part%.*}"							# This is the basename if there is a suffix, the whole part if there is no suffix.
        else
          base="${part}"
        fi
#w "-b- ${base}"
#w "-a- ${additional_cut_off_from_base}"
        # Reduced basename.
        newbase="$(printf "%s" "${base}" | tr -dc '[:alnum:]._-' | tr '[:upper:]' '[:lower:]')"
#        newbase="$(printf "%s" "${base}" | tr --delete --complement '[:alnum:]._-' | tr '[:upper:]' '[:lower:]')"

        if [[ ${maxfnl} == *?.?* ]]; then					# If there is a period in the definition of the maximum filename length.
          newbase="$(printf "%s" "${newbase}" | tr --delete --complement '[:alnum:]._-')"		# Make sure there are no dots in the basename fot SFN FAT systems.
        fi
        if [[ ${newbase: -1} == . ]]; then newbase="${newbase::-1}"; fi		# Result should never end in a dot.
        call_function_if_declared hook_pathreducer_part_before
        if (( $((${#base}+${#newsuff})) > ${maxbl:-255} )) \
          || [[ ${newbase} != ${base} ]]					# If the basename is longer than the maximum basename length or would change
        then									#   when special characters are removed and all letters are made lower case.
          call_function_if_declared hook_pathreducer_part_between
          printf '%s' "${newbase}" \
            | head --bytes $((${maxbl:-255}-6-${additional_cut_off_from_base}))	# Use reduced version of this element of the path and print as much of the string
          									#   as will fit, no newline and ...
#          printf '%s' "$(crc32 <(printf '%s' "${base}${newsuff}"))${newsuff}"	#   ... add the CRC32 checksum, resulting in a string exactly $maxfnl bytes long.
          printf '%s' "$(sha1sum -- \
            <(printf '%s' "${base}${newsuff}")|head --bytes 6)${newsuff}"	#   ... add the hash, resulting in a string exactly $maxfnl bytes long.
          									#   The extra printf encapsulation is to prevent a newline at the end.
        else									# If the basename is not too long and does not contain any special characters or
        									#   ... uppercase letters
          printf '%s' "${base}${newsuff}"					#   ... print it as it is.
        fi

        call_function_if_declared hook_pathreducer_part_after
#w "-N- ${newbase}"
#w
        [[ ${partno} != ${#path[@]} ]] && printf '/'				# Add slash if this is not the last element of the path.
        partno=$(( partno + 1 ))
      done
    )
  else										# If option -R/--reduce-paths is not enabled
    printf '%s' "${1}"								# Just echo the string as it is.
  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() {
  if (( ${#1} > 19 )); then w "'${1}' is too long to be a number that is not overflowing in bash."; fi
  case ${1} in
    '') w "Supplied argument is empty. So, no, not a number."; return 2 ;;
    *[!0123456789]*) d "'${1}' has a non-digit somewhere in it."; return 1 ;;
    *) d "'${1}' is strictly numeric and positive."; 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: Newline separated 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 --text --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
      w "Ignored tagline in '${f}' (unfamiliar tag): '${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}
}


filter_tag() {
perl /dev/fd/3 3<<'EOPERL' 
use strict;
use warnings;
use Encode;
my $buffer = '';
while (1) {
  if (4 >= length $buffer and defined fileno STDIN) {
    read STDIN, $buffer, 65536, length $buffer
      or close STDIN;
  } elsif (length $buffer) {
    substr $buffer, 0, 1, '';
  } else {
    last;
  }
  $_ = Encode::decode 'UTF-8', $buffer, Encode::FB_QUIET;
  s/(?![\t\n])\pC//g;
  print Encode::encode 'UTF-8', $_;
}
EOPERL
}



# Function echoes a list of tags that is contained in the variable passed to the funtion.g# 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}/tags/$(pathreducer "${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.
#d "--s ${shellbase}"
#d "--1 ${1}"
#d "--c ${cachefile}"
  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.
#d "----- Used cache file: ${cachefile}"
  else										# If there is no readable cache file for this entry/page existing, generate it.
    local f="${1}"
    local linenum=1
    local nontaglines=0
    while IFS= read -r line; do
#      local n
      looks_like_tag "${line}"
      n=${?}
      if (( n == 0 )); then							# If the line looks like a tagline
        if command -v perl > /dev/null; then					# If perl is available
          line="$(printf "%s" "${line}"|filter_tag)"				# Filter out control characters and broken multibyte unicode characters.
        fi
#ööööööööööö filter, remove and replace characters that shouldn't be in the tag.
#ööö        line="$(printf %s "${line}" | tr --delete --complement )"
#        tags="$(printf "%s\n" "${tags}")${line}"				# ... add it to the block that will be echoed to where the function was called from
#        tags="$(printf "%s\n" "${tags}" | tr -d '[[:cntrl:]]')${line}"		# ... add it to the block that will be echoed to where the function was called from
#        tags="$(printf "%s\n" "${tags}" | tr -dc '[0-9A-Za-z[:punct:][:space:]]')${line}"		# ... add it to the block that will be echoed to where the function was called from
        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									# If the line does not look like a tagline
        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 < "${f}"
    linenum=$(( linenum - 2))							# 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.
#d "----- Made cache file: ${cachefile}"
  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.
}

# Function to print the content part of a source file to the current outfile. Expects the list of taglines of the source file in question to present in $tags.
add_content() {                                                                 # $1: Path to source file

#  [[ -f ${shellbase}/entries/${entry} ]] \
#    && o printf "%s" "$(< "${shellbase}/entries/${entry}" \
#      tr -d '\0' | tail --lines +"$(grab_tag "${tags}" taglines)")" \
#    || e "Entry '${entry}' can't be used. Is the file accessible?"

#  [[ -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?"		# Deliberately not usinf the o() dfunction here to output the entry content.

#  [[ -f ${shellbase}/entries/${entry} ]] \
#    && o sed --quiet p "${shellbase}/entries/${entry}" \
#    | tail --lines +"$(grab_tag "${tags}" taglines)" \
#    || e "Entry '${entry}' can't be used. Is the file accessible?"		# Print the content of the entry file (without the tag header) to the outfile.

#  [[ -f ${shellbase}/entries/${entry} ]] \
#    && o awk "NR > $(grab_tag "${tags}" taglines) { print }" \
#    < "${shellbase}/entries/${entry}" \
#    || e "Entry '${entry}' can't be used. Is the file accessible?"		# Print the content of the entry file (without the tag header) to the outfile.

  [[ -f ${1} ]] \
    && o awk "NR > $(grab_tag "${tags}" taglines) { print }" \
    < "${1}" \
    || e "Content from source file '${1}' can't be used. " \
    "Is the file accessible?"	                                        	# Print the content of the entry file (without the tag header) to the outfile.
}

# print footer
add_footer() {									# The content of the footer file is included in every page, entry and tagpage.
  o printf "</section>\n"								# 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() {
  d "SBWG version: ${version}"
  tmpdir="$(mktemp --directory --suffix=.SBWG)"					# Generate a path were we will store temporary files during this run of the script.
  if [[ ${tmpdir: -1} == / ]]; then tmpdir="${tmpdir::-1}"; fi			# Remove trailing slash if there was one so we can be sure what we have.
  readonly tmpdir
  d "tmpdir: ${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."
  if [[ ${shellbase: -1} == / ]]; then shellbase="${shellbase::-1}"; fi		# Remove trailing slash if there was one so we can be sure what we have.
  d "shellbase: ${shellbase}"
  if [[ ${odir: -1} == / ]]; then odir="${odir::-1}"; fi			# Remove trailing slash if there was one so we can be sure what we have.
  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."
  mkdir --parents -- "${odir}" \
    || e "Output directory not available."

  [[ -w ${odir} ]] \
    || e "Output directory is not writable."

  if option_set R; then
    d "given maxfnl: $maxfnl"
    local maxbl="${maxfnl%.*}"							# Extract/set the max. basename length. Will be same as $maxfnl if there is no period
    if ! is_number "${maxbl}"; then
#      w "The maximum filename length without filename extension was " \
#        "defined as '${maxbl}' bytes. '${maxbl}' is not a number, dude. " \
#        "Defaulting to 250 bytes."
      e "The maximum filename length without filename extension is " \
        "defined as '${maxbl}' bytes. '${maxbl}' is not a number, dude."
      maxbl=255
      w "Defaulting maximum filename length without filename extension to " \
        "${maxbl} bytes."
    fi
    if (( $maxbl < 6 )); then
#      w "The maximum filename length without filename extensions was " \
#        "defined as ${maxbl} bytes. It can not be smaller than 6 bytes " \
#        "though. Defaulting to 6 bytes."
      e "The maximum filename length without filename extensions is " \
        "defined as ${maxbl} bytes. It can not be smaller than 6 bytes " \
        "though."
      maxbl=6
      w "Defaulting maximum filename length without filename extensions to " \
        "${maxbl} bytes."
    fi
    if [[ ${maxfnl} == *.* ]]; then 						# If there is a period in the definition of the maximum filename length.
      local maxsl="${maxfnl#*.}"						# Extract/set the maximum suffix length.
      if ! is_number "${maxsl}"; then
#        w "The maximum filename extensions length was defined as '${maxsl}' " \
#          "bytes. '${maxsl}' is not a number, dude. Filename suffixes will not " \
#          "be shortened."
        e "The maximum filename extensions length is defined as '${maxsl}' " \
          "bytes. '${maxsl}' is not a number, dude."
        maxfnl="${maxbl}"
        w "Filename suffixes will not be shortened."
      else									# If there is a definition for the suffix length and it is a number.
        maxfnl="${maxbl}.${maxsl}"
      fi
    else									# If it's not a two-number value
      maxfnl="${maxbl}"								# Assign the maximum basename length that already has been checked above.
    fi
    d "using maxfnl: $maxfnl"
  fi


  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 filename 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 filename was given with option -P to generate a single page.
      desired_page_file="$(readlink -f "${desired_page_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 || option_set G; then
    command -v mogrify &>/dev/null || e "I need ImageMagick or some " \
      "compatible version of mogrify in order to create the different " \
      "gallery image sizes. If there is one, I didn't see it, sorry. Maybe " \
      "create an alias for 'mogrify' to point me to it."
    if [[ -n ${desired_gallery} ]]; then					# If a gallery name was given with option -g to generate a single gallery.
      if [[ -d ${shellbase}/gals/${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.
  if option_set n; then gen_navbar; fi						# Generate navbar if option n is set. Option n also gets set along with option
										#   c, b, p, P, e, E, t, g, G or r.
  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 / style set name
  vv "Copying styles..."
  mkdir --parents -- "${odir}/css" || e "css directory is not available."
  d "style: ${style}"
  if option_set R; then								# If pathreducer mode in enabled
    for file in "${shellbase}/styles/${style}"{.css,-*.css}; do
      cp --update --no-preserve=all -- "${file}" \
        "${odir}/css/$(pathreducer "$(basename "${file}")")" &>/dev/null	# Copy each file belonging to the chosen styleset individually, reducing the filename
										#   if necessary. Get rid of the output. Who cares that some of those don't exist
    done
  else
    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.
										#   Get rid of the output. Who cares that some of those don't exist
  fi
  v "Using style '${style}' consisting of: " \
    "$(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..."
  if option_set R; then								# If pathreducer mode in enabled.
    v "Pathrecuder mode is enabled. Please note that directory names and " \
      "filenames from the web site's files/ directory may be changed. " \
      "Links to those files from web pages may have to be changed " \
      "accordingly. If you don't want the files/ directory to be affected " \
      "by the pathreducer, don't use option --reduce-paths/-R when using " \
      "option --files/-f to copy the files/ directory."
    while IFS= read -r -d '' -u 9
    do
#d "--- ${odir}${REPLY#*$shellbase}"
        mkdir --parents -- "${odir}$(pathreducer "${REPLY#*$shellbase}")" \
          || e "Could not create the directory '${REPLY}' from the files dir."	# Create directory structure with reduced path names.
    done 9< <( find "${shellbase}"/files/ -type d -exec printf '%s\0' {} + )
#    done 9< <( find "${shellbase}"/files/ -type d -mindepth 1 -exec printf '%s\0' {} + )
    while IFS= read -r -d '' -u 9
    do
#d "--- ${odir}${REPLY#*$shellbase}"
        cp --update --no-preserve=all -- \
          "${REPLY}" "${odir}$(pathreducer "${REPLY#*$shellbase}")" \
          || e "Could not copy the file '${REPLY}' from the files directoryy."	# Copy files with reduced path names.
    done 9< <( find "${shellbase}"/files/ -type f -exec printf '%s\0' {} + )
#    v "Pathreducer mode is enabled but does not support the files " \
#      "directory. Files from '${shellbase}/files/' will be copied to " \
#      "'${odir}/files/' without reducing directory or filenames. " \
#      "You have to make sure yourself that all directory and filename " \
#      "in this directory are short enough and contain only allowed characters."	# Inform that these files names will not be reduced despite enabled pathreducer mode.
  else										# If pathreducer mode is not enabled.
    mkdir --parents -- "${odir}/files" || e "files directory not available."
    cp --recursive --update --no-preserve=all -- \
      "${shellbase}"/files "${odir}/" \
      || e "Could not copy the files directory to the output directory."	# Copy the files/ directory normally.
  fi
  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="$(pathreducer "$(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 id="'
  o printf '%s' "${outfile#$odir/}"						# Using the path/filename as a unique ID for the page for styling
  o printf '">\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/"
      o pathreducer "${re}.html"
      o printf "\"><em>'%s'</em></a>.</div></p>" "${retitle}"
    else
      w "The target entry with the name '${re}' as called for in '${entry#*:}" \
        "${entryname}' 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/"
      o pathreducer "${re}.html"
      o printf "\"><em>'%s'</em></a>.</div></p>" "${retitle}"
    else
      w "The target entry with the name '${re}' as called for in '${entry#*:}" \
        "${entryname}' 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/"
      o pathreducer "${re}.html"
      o printf "\"><em>'%s'</em></a>.</div></p>" "${retitle}"
    else
      w "The target entry with the name '${re}' as called for in '${entry#*:}" \
        "' 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 HEADER CHUNK AND ADD IT TO THE CURRENT $outfile
# This generates the part of an HTML page that is the title-wrapper of a single entry (as it appears on entry pages and tagpages).
# gen_entries/gen_tagpage is called first and then calls add_entry_chunk for every entry that needs to be generated.
add_entry_chunk() {								# $1: The entry (path relative to entries dir) whose chunk 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. You may use --force."
    return 2									# Don't generate anything if the file does not seem to be a text file of any sort.
  fi

  vv "-- Generating entry chunk '${entry}'."

  local cachefile="${cachedir}/entries/$(pathreducer "${1#$shellbase/}")"	# Cache entry chunks in that file.
  if [[ -r ${cachefile} ]]; then						# If there already is a cache file for this entry chunk and the file can be read ...
    o cat "${cachefile}" || w "Could not use cache file '${cachefile}'."	#   ... just use the contents of the existing cache file and warn if not possible.
#vv "-- Using cache."
  else										# If there is no readable cache file for this entry cache existing, generate it.
#vv "-- Making cache."
    mkdir --parents -- "${cachefile%/*}"					# Make sure that the directory exists so that the file can actually be created.
    local tags
    tags="$(grab_tags "${shellbase}/entries/${entry}")"

    c printf "<div class=\"entry-title-wrapper\">\n"				# This is just to enable styling.
    call_function_if_declared hook_entry_title_start
#    c printf "<h2 id=\"entry-title\">%s</h2>" "${title}"
    local created
    created="$(grab_tag "${tags}" created)"
    local edited
    edited="$(grab_tag "${tags}" edited)"
    local entrybasename
    entrybasename="$(basename "${entry}")"
    c printf "<a href=\"/entries/"
    c pathreducer "${entrybasename}.html"
    c printf "\"><h3 class=\"entry-title\">%s</h3></a>\n" "${title}"
    c printf "<div class=\"page-info\">\n"					# This is just to enable styling.
    # Add last modified date of the entry, link to entry so that entries without titles can be clicked
    c printf "<a class=\"date\" href=\"/entries/"
    c pathreducer "${entrybasename}.html"
    c printf "\">%s" "${created}"
    if [[ -n ${edited} ]]; then o printf " (edited %s)" "${edited}"; fi
    c printf "</a>\n"								# closing created/edited line
#    c 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
    while IFS= read -r tag; 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.
          c printf "<span class=\"tag icon %s\">" "${tag}"
          c printf "<a href=\"/tags/"
          c pathreducer "${tag}.html"
          c printf "\" title=\"Show all entries tagged with ${tag}\">"
          c printf "<img src=\"/tags/"
          c pathreducer "${tag}.png"
          c printf "\" /></a></span>\n"
        else
          c printf "<span class=\"tag %s\">" "${tag}"
          c printf "<a href=\"/tags/"
          c pathreducer "${tag}.html"
          c printf "\">"
          c printf "%s" "${tag}"
          c printf "</a>"
          c printf "</span>\n"
        fi
      call_function_if_declared hook_entry_title_tag_after
      ;;
      esac
#    done
    done <<< "${tags}"
    call_function_if_declared hook_entry_title_tags_after
    c printf "</div>\n"								# /page-info
    c printf "</div>\n"								# /title-wrapper
  fi
}








# CREATE A SINGLE ENTRY PAGE
# 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_page for every entry that is to be generated.
gen_entry_page() {								# $1: The entry (path relative to entries dir) whose 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. You may use --force."
    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/$(pathreducer "$(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/$(pathreducer "$(basename "${entry}").html")" \
      "entries/$(pathreducer "${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 "%s" "\" class=\"${tags}" | grep --text --extended-regexp -- \
    '^cat:|^top:|^lang:|^author:' | tr " " "-" | tr "\n" " "			# This is just to enable styling.
  o printf "\">\n"								# This is just to enable styling.

  add_entry_chunk "${entry}"

  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 "${shellbase}/entries/${entry}"
  call_function_if_declared hook_entry_after
  o printf "</div>\n"								# /content
  local gallerypath
  gallerypath="${shellbase}/gals/$(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)"
    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=\"/gals/"
      o pathreducer "$(basename "${entry}").html"
      o printf "\">View Gallery</a></p>"
      local image
      for image in ${images}; do
        local img
        img="$(basename "${image}")"
        o printf "<a class=\"gallery-image thumb\" href=\"/gals/"
        o pathreducer "$(basename "${entry}")/${img}"
        o printf "\"><img class=\"thumb\" src=\"/gals/"
        o pathreducer "$(basename "${entry}")/thumbs/${img}"
        o printf "\"/></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_page() will then be called for each entry that is to be generated.
gen_entries() {
  vv "Generating entries..."
  mkdir --parents -- "${odir}/entries" || e "entries directory not available."
  call_function_if_declared hook_entries_start

  [[ -n ${desired_entry_name} ]] \
    && gen_entry_page "${desired_entry_full#$shellbase/entries/}"		# Generate only the entry passed as an argument to option -e
										#   (the filename 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_page "${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 Q; then							# If parallel mode is enabled.
#        gen_entry_page "${entry#$shellbase/entries/}" &				# Generate the entrypages in parallel.
        gen_entry_page "${entry#$shellbase/entries/}"
      else
        gen_entry_page "${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}/gals/$(pathreducer "${gallery}")" \
    || e "Gallery directory '$(pathreducer "${gallery}")' not available."	# Create the gallery directory in the output directory before copying any files.
  local imagefile
  for imagefile in "${shellbase}/gals/${gallery}"/*; do
    if option_set F || [[ $(file --brief --mime "${imagefile}") == image* ]]	# If the file is indeed some image file (regardless of filename) ...
    then
      cp --update --no-preserve=all -- "${imagefile}" \
        "${odir}/gals/$(pathreducer "${gallery}/$(basename "${imagefile}")")" \
        || e "Could not copy '${imagefile}' to " \
        "'${odir}/gals/$(pathreducer "${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}/gals/$(pathreducer "${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 "<p><a title=\"There is a blog entry appertaining to this gal"
      o printf "lery. Click here to go to the blog entry.\" href=\"/entries/"
      o pathreducer "${gallery}.html"
      o printf '">View corresponding blog entry</a></p>\n'			# 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}/gals/$(pathreducer "${gallery}")/minis/"	# Make sure the subdirectory for this image size exists in this gallery directory.
    mkdir --parents -- "${odir}/gals/$(pathreducer "${gallery}")/thumbs/"
    mkdir --parents -- "${odir}/gals/$(pathreducer "${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 ...
      then
        vv "Skipping '${image}' because it doesn't appear to be an image file."
        continue								#   ... skip it.
      fi
      local img
      img="$(basename "${image}")"
      local imgdir
      for imgdir in minis thumbs previews
      do
        if [[ ! -f ${odir}/gals/$(pathreducer "${gallery}/${imgdir}/${img}") ]]
        then									# If this size this image already exists, skip generating it.
          local sizevar=${imgdir}ize						# For dynamic variable name (thumbsize, minisize, previewsize).
          mogrify -resize "${!sizevar}"x"${!sizevar}" -write \
            "${odir}/gals/$(pathreducer "${gallery}/${imgdir}/${img}")" -- \
            "${image}" || w "There was a problem converting '${image}' to " \
            "'${odir}/gals/$(pathreducer "${gallery}/${imgdir}/${img}")'."	# Resize the original image.
          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
      done


#        mogrify -resize "${minisize}"x"${minisize}" \
#          -write "${odir}/gals/$(pathreducer "${gallery}/minis/${img}")" -- \
#          "${image}" \
#          || w "There was a problem converting '${image}' to " \
#          "'${odir}/gals/$(pathreducer "${gallery}/minis/${img}")'."
#        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}/gals/$(pathreducer "${gallery}/thumbs/${img}") ]]
#      then									# If the thumbnail of this image already exists, skip generating it.
#        mogrify -resize "${thumbsize}"x"${thumbsize}" \
#          -write "${odir}/gals/$(pathreducer "${gallery}/thumbs/${img}")" -- \
#          "${image}" \
#          || w "There was a problem converting '${image}' to " \
#          "'${odir}/gals/$(pathreducer "${gallery}/thumbs/${img}")'."
#        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}/gals/$(pathreducer "${gallery}/previews/${img}") ]]
#      then 									# If the preview of this image already exists, skip generating it
#        mogrify -resize \
#          "${previewsize}"x"${previewsize}" -write \
#          "${odir}/gals/$(pathreducer "${gallery}/previews/${img}")" -- \
#          "${image}" \
#          || w "There was a problem converting '${image}' to " \
#          "'${odir}/gals/$(pathreducer "${gallery}/previews/${img}")'."
#        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 '<a class="gallery-image mini" href="#%s">' "${img}"
      o printf "<img src=\"/gals/"
      o pathreducer "${gallery}/minis/${img}"
      o printf "\"/></a>\n"
    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 "<a name=\"%s\" class=\"gallery-image preview\" href=\"/gals/" \
        "${img}"
      o pathreducer "${gallery}/${img}"
      o printf '"><img src="/gals/'
      o pathreducer "${gallery}/previews/${img}"
      o printf '"/></a>'
      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>"
      o printf "<br />\n"
      call_function_if_declared hook_gallery_image_after
    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() {
  if [[ -z ${desired_gallery} ]] && [[ -z "$(ls -A "${shellbase}"/gals/*)" ]]	# If there is nothing in the gals/ directory and no desired_gallery ...
  then
    return 1									# Skip this function, don't attempt to create any galleries.
  fi
  mkdir --parents -- "${odir}/gals" || e "gals directory not available."
  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}/gals/${desired_gallery} ]]; then
      local gallerypath="${shellbase}/gals/${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}"/gals/*/; do
      local gallery
      gallery="$(basename "${gallerypath}")"
      if option_set Q; then							# If parallel mode is enabled.
#        gen_gallery "${gallery}" &						# Generate the gallery pages in parallel.
        gen_gallery "${gallery}"
      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() {									# $1: Name of the page that should be generated
  local page="${1}"
d "-p- $page"
  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. You may use --force."
    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}/$(pathreducer "$(basename "${page}").html")"			# The HTML file that this page will be generated into. Using basename because
										#   subdirectories of the source file structure are not used in the resulting URLs.
  outfile="${odir}/$(pathreducer "${page}.html")"		        	# The HTML file that this page will be generated into. Using basename because
  mkdir --parents "${odir}/${page%/*}/" \
    || e "Directory for page '${page}' not available."                          # Create subdirectory if the page is in one and it doesn't already exit.
  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}" \
#    tr -d '\0' | tail --lines=+"$(grab_tag "${tags}" taglines)")" \
#    || e "The page '${page}' is missing or not accessible."

#  [[ -f ${shellbase}/pages/${page} ]] \
#    && o sed --quiet p "${shellbase}/pages/${page}" \
#    | tail --lines +"$(grab_tag "${tags}" taglines)" \
#    || e "The page '${page}' is missing or not accessible."			# Print the content of the page file (without the tag header) to the outfile.

#  [[ -f ${shellbase}/pages/${page} ]] \
#    && o sed 1,"$(grab_tag "${tags}" taglines)"d \
#    "${shellbase}/pages/${page}" \
#    || e "The page '${page}' is missing or not accessible."			# Print the content of the page file (without the tag header) to the outfile.

#d "-----g- $(grab_tag "${tags}" taglines)"
#  [[ -f ${shellbase}/pages/${page} ]] \
#    && o awk "NR > $(grab_tag "${tags}" taglines) { print }" \
#    < "${shellbase}/pages/${page}" \
#    || e "The page '${page}' is missing or not accessible."			# Print the content of the page file (without the tag header) to the outfile.

  add_content "${shellbase}/pages/${page}"

  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 filename 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_file#$shellbase/pages/}"			# 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 Q; 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.
#        gen_page "${page#$shellbase/pages/}" &					# Generate the pages in parallel.
        gen_page "${page#$shellbase/pages/}"
      else
#        ### This will have to be changed in order to allow multiple levels.
#        gen_page "$(basename "${page}")"					# Generate the pages.
        gen_page "${page#$shellbase/pages/}"					# 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..."
  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 ...
      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 it. You may use --force."	#   ... let stdout know that there is a wrong file in the entries directory.
    fi
  done

  declare -a tagslist_unsorted=()						# Start with empty array
  call_function_if_declared hook_navbar_start
  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.
#d "----- PTE: ${entry}"
    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.
#d "----- PTE T: ${tag}"
      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 Q; then							# If parallel mode is enabled.
#     process_this_entry &							# Process the entries in parallel.
     process_this_entry
    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
  if [[ -n ${desired_entry_file} ]] \
    || ls -A "${shellbase}"/entries/* &> /dev/null				# If there is something in the entries/ directory or an entry from somewhere else
  then										#   was requested.
    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=\"/$(pathreducer 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 "<li class=\"cat subcat %s\">" "${oddity}"
            o printf "<a title=\"Show blog entries in the category "
            o printf "'%s'.\" href=\"/tags/" "${item}"
            o pathreducer "${item}.html"
            o printf "\">"
            o printf "%s" "${tag}" | sed 's/^[^:]*://g'				# Remove the parent tag name.
            o printf "</a></li>\n"
          else
            o printf "<li class=\"cat %s\">" "${oddity}"
            o printf "<a title=\"Show blog entries in the category "
            o printf "'%s'.\" href=\"/tags/" "${item}"
            o pathreducer "${item}.html"
            o printf "\">%s</a></li>\n" "${tag}"
          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 "<div class=\"navheading\" id=\"top\"><h3><a href=\""
            o printf "/tags/%s\" title=\"List all ent" "$(pathreducer top.html)"
            o printf "ries that a topic is assigned to.\">Topics</a></h3><ul>\n"
          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 "top %s\"><a title=\"List blog entries " "${oddity}"
          o printf "regarding the topic '%s'.\" href=\"/tags/" "${item}"
          o pathreducer "${item}.html"
          o printf "\">%s</a></li>\n" "${tag}"
          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 "<li class=\"lang %s\"><a title=\"Show blog en" "${oddity}"
          o printf "tries with the language tag %s.\" href=\"/tags/" "${item}"
          o pathreducer "${item}.html"
          o printf "\">%s</a></li>\n" "${tag}"
          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 "<li class=\"author %s\"><a title=\"" "${oddity}"
            o printf "Show blog entries supposedly written by %s.\" " "${item}"
            o printf "href=\"/tags/"
            o pathreducer "${item}.html"
            o printf "\">%s</a></li>\n" "${tag}"
            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
  fi

  if [[ -n ${desired_gallery} ]] \
    || ls -Ad "${shellbase}"/gals/* &> /dev/null				# If there is something in the gals/ directory or there is a desired_gallery ...
  then
    o printf "<div class=\"navheading\" id=\"galleries\"><h3>Galleries</h3><ul>\n"
    gallerylist=()
    for gallery in "${shellbase}"/gals/*/; 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	# Accept any file types that start with image or any file if force mode is enabled.
        then
          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 "<li><a title=\"Show what's in the gallery "
      o printf "'%s'.\" href=\"/gals/" "${gallerytitle}"
      o pathreducer "${gallery}.html"
      o printf "\">"
      o printf "%s</a></li>\n" "${gallerytitle}"				# 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
  fi

  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}/$(pathreducer 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}/$(pathreducer "${tag}.html")"
  else
    local outfile="${odir}/tags/$(pathreducer "${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 --text -- ^author: | grep --text --invert-match -- + )"		# Get all existing authors (no combined tags)
    local sec									# sec meaning the secondary tag that is going to be combined with the current tag.
    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 --text --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/$(pathreducer "${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-filter\"><a href=\"/tags/" \
    "$(pathreducer "${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 [[ -n ${target} ]]; then							# If there was a target defined
      local targetpath
      if targetpath="$(find "${shellbase}/entries/" \
        -name "${target}" 2> /dev/null | grep .)"				# See whether a file of the name of the redirect target is in or somewhere below
      then									#   the entries directory. If there is one (the target entry exists)
#        if [[ -f ${shellbase}/entries/${target} ]]; then			# and an entry by that name exists
        o printf "%s %s\n" "<!-- The following is an entry redirection caused" \
          "by the entry '${entryname}'. -->"
        entryname="${targetpath#$shellbase/entries/}"				# Switch this entry generating iteration to the redirect target entry, keeping...
#        tags="$(grab_tags "${shellbase}/entries/${entryname}")"			# ... the already set output filename but getting the tags of the target entry.
        tags="$(grab_tags "${targetpath}")"					# ... the already set output filename but getting the tags of the target entry.
        title="[Redirection to:] "
      else
        w "The entry '${entryname}' should redirect to '${target}' but this " \
          "target does not exist. Including its original content instead."
      fi
    fi
    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 displayed (and thus handled)
    then									#   differently: The content of the entries is not printed to the tagpage of a topic.
      local created
      created="$(grab_tag "${tags}" created)"
      local edited
      edited="$(grab_tag "${tags}" edited)"
      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 %s " "${oddity}"			# This is just to enable styling.
      o printf "%s" "${tags}" | grep --text --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=\"top-entry\"><a href=\"/entries/"
      o pathreducer "${entrybasename}.html"
      o printf "\">%s</a></div>\n" "${title:-(Entry Without Title)}"		# Add linked entry title
      o printf "<div class=\"entry-info\"><a href=\"/entries/"
      o pathreducer "${entrybasename}.html"
      o printf "\"> %s" "${created:+created on }${created:-Permalink }"		# Add created date or "Permalink" if no created date.
      o printf "%s" "${edited:+ (last edited on $edited)}"			# 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 --text --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.

        add_entry_chunk "${entryname}"



#        # 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 "<a href=\"/entries/"
#        o pathreducer "${entrybasename}"
#        o printf ".html\"><h3 class=\"entry-title\">%s</h3></a>\n" "${title}"
#        # Add last modified date of the entry, link to entry so that entries without titles can be clicked
#        o printf "<a class=\"date\" href=\"/entries/"
#        o pathreducer "${entrybasename}"
#        o printf ".html\">%s" "${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.
#        while IFS= read -r tag2; do
#          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 "<span class=\"tag icon %s\">" "${tag2}"
#              o printf "<a title=\"Show all entries tagged with %s\" " "${tag2}"
#              o printf "href=\"/tags/"
#              o pathreducer "${tag2}"
#              o printf ".html\"><img src=\"/tags/"
#              o pathreducer "${tag2}"
#              o printf ".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 "<span class=\"tag %s\">" "${tag2}"
#              o printf "<a title=\"Show all entries tagged with %s\" " "${tag2}"
#              o printf "href=\"/tags/"
#              o pathreducer "${tag2}"
#              o printf ".html\">%s</a></span>" "${tag2}"
#            fi
#          ;;
#          esac
##        done
#        done <<< "${tags}"
#        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} ]] \
#        && o printf "%s" "$(< "${shellbase}/entries/${entryname}" \
#        tr -d '\0' | tail --lines=+"$(grab_tag "${tags}" taglines)")" \
#        || e "Entry '${entryname}' missing."
#      [[ -f ${shellbase}/entries/${entryname} ]] \
#        && o sed --quiet p "${shellbase}/entries/${entryname}" \
#        | tail --lines +"$(grab_tag "${tags}" taglines)" \
#        || e "Entry '${entryname}' can't be used. Is the file accessible?"	# Print the content of the entry file (without the tag header) to the outfile.
#      [[ -f ${shellbase}/entries/${entryname} ]] \
#        && o awk "NR > $(grab_tag "${tags}" taglines) { print }" \
#        < "${shellbase}/entries/${entryname}" \
#        || e "Entry '${entryname}' can't be used. Is the file accessible?"	# Print the content of the entry file (without the tag header) to the outfile.

      add_content "${shellbase}/entries/${entryname}"

      call_function_if_declared hook_tagpage_entry_content_after
      o printf "</div>\n"								# /entry-content-wrapper
      o printf "</div>\n"

      local gallerypath="${shellbase}/gals/${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 "<p><a title=\"This blog entry has pictures attached. "
          o printf "Click here to view those pictures in the gallery"
          o printf "view.\" href=\"/gals/"
          o pathreducer "${entrybasename}.html"
          o printf "\">View Gallery</a></p>\n"
          local image
#          for image in ${images}; do
          while IFS= read -r image; do
            local img
            img="$(basename "${image}")"
            o printf "<a title=\"Click to view the original image file. If "
            o printf "you wish to obtain a larger resolution image contact "
            o printf "the admin of this site. They may be saving space on "
            o printf "their webserver by not uploading large image files.\" "
            o printf "class=\"gallery-image mini\" href=\"/gals/"
            o pathreducer "${entrybasename}/${img}"
            o printf "\"><img class=\"mini\" src=\"/gals/"
            o pathreducer "${entrybasename}/minis/${img}"
            o printf "\"/></a>\n"
          done <<< "${images}"
          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 tagpages 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 " - <a href=\"/"
              o pathreducer "${tag}-$(( pagecount - 1 )).html"
              o printf "\">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 " - <a href=\"/tags/"
              o pathreducer "${tag}-$(( pagecount - 1)).html"
              o printf "\">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 " - <a href=\"/"
              o pathreducer "${tag}-$(( pagecount + 1)).html"
              o printf "\">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 " - <a href=\"/tags/"
              o pathreducer "${tag}-$(( pagecount + 1)).html"
              o printf "\">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}/$(pathreducer "${tag}-${pagecount}.html")"		# Update the output filename so that every following entry will go to a new page.
          else
            outfile="${odir}/tags/$(pathreducer "${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 "$(pathreducer all-0.html)" "$(pathreducer all.html)"
    fi										# The "previous" link on page 1 links to ...-0.html instead of ....html.
  else
    if (( lastpage > 0 )); then
      html_redirect "tags/$(pathreducer "${tag}-0.html")" \
        "tags/$(pathreducer "${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 filename (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%s" "${created:-1970-01-01}" "${edited}" \
        | 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
d "-e- ${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 --text --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/" || e "tags directory not available."	# Create tags directory if it doesn't already exit.
  if option_set R; then								# If pathreducer mode in enabled.
    for file in "${shellbase}/tagicons/"*; do
      cp --update --no-preserve=all -- "${file}" \
        "${odir}/tags/$(basename "$(pathreducer "${file}")")"			# Copy each tagicon individually, shortening the filename if necessary.
    done
  else										# If pathreducer mode is not enabled.
    cp --update --no-preserve=all -- "${shellbase}/tagicons/"* "${odir}/tags/"	# Copy all tagicons - if there are any - to the html directory.
  fi
  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//:/ - } - //")"	# Sort the entrylist for that tag. If the entrylists array is outsourced into files
										#   in the future, "sed -i -f -" could be used to allow for extremely long tags.
      else
        entrylists[${tag}]="$(printf "%s" "${entrylists[${tag}]}" \
          | sort --reverse)"							# Sort topic tagpages differently from other tagpages.
      fi
      if option_set Q; 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() {
  [[ ${desired_feed} ]] \
    && w "Generation of (RSS) feeds for individual tags is not supported, " \
    "yet. I can only generate 'all.rss', which includes all entries."

  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_rss_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)")"
#    content="$(awk "NR > $(grab_tag "${tags}" taglines) { print }" < "${entry}")"
    o printf "    <item>\n"
    call_function_if_declared hook_rss_entry
    o printf "      <title>%s</title>" "${title:-(ᵔᴥᵔ) Untitled Post}"
    o printf "      <link>%sentries/%s</link>" "${url}" "$(pathreducer "${entryname}".html)"
    o printf "      <author>%s</author>\n" "${author:-$sitename}"
    o printf "      %s%s\n" "<description><![CDATA[" \
    add_content "${entry}"
    o printf "]]></description>"
    o printf "      <pubDate>%s</pubDate>\n" "${pubdate}"
    o printf "      <guid>%sentries/%s</guid>" "${url}" "$(pathreducer "${entryname}".html)"
    o printf "      <category>%s</category>\n" "${tags}" | tr "\n" " "
    o printf "    </item>\n"
  }

  for entry in "${entrylist[@]}"; do
#  while IFS= read -r entry; do
    if option_set Q; then							# If parallel mode is enabled.
#      gen_rss_this_entry &							# Generate the entries in parallel.
      gen_rss_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_rss_this_entry							# Generate the entries sequentially.
    fi
  done
#  done <<< "${entrylist[@]}"
  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} ACTION(S) [ OPTIONS ]

	${scriptname} [ -c ] [ -b ] [ -g [ GALLERY ] ] [ -G GALLERYDIR ]
          [ -e [ ENTRY ] ] [ -E ENTRYFILE ] [ -p [ PAGE ] ] [ -P PAGEFILE ]
	  [ -t [ TAGPAGE ] ] [ -r ] [ -n ] [ -f ] [ -s [ STYLE ] ]
	  [ -i INPUT_DIR ] [ -o OUTPUT_DIR ] [ -n ENTRIES_PER_PAGE ] [ -R ]
	  [ -F ] [ -v [ -v ] ] [ -d ]

	${scriptname} -h [ HELP_PAGE ]

	${scriptname} -V\n"
}


# Just print the help/usage and exit.
print_help() {
  case ${1} in
    options|actions)								# In case help option argument is "options".
      more << END_OF_HELP
At least one option (action) 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 pages for many of them.

Actions:

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

	-G GALLERYDIR
		(Re-)generate the gallery that is located in the directory
		GALLERYDIR. GALLERYDIR can be located outsite of the web site's
		input directory.

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

	-E ENTRYFILE
		(Re-)generate the entry that is stored in the ENTRYFILE.
		ENTRYFILE can be located outsite of the web site's input
		directory.

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

	-P PAGEFILE
		(Re-)generate the page that is stored in the PAGEFILE.
		PAGEFILE can be located outsite of the web site's input
		directory.

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

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

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

	-n|--navbar
		Generate the navigation bar and the navbar.html file. This
		option is automatically included if an action is performed that
		generates any HTML page or feed.

Verbosity options:

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

	-d|--debug
		Included messages intended for debugging purposes on stdout.
		This option also sets the options -v (--verbose) and -vv
		(--very-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
		filename is omitted, $logfile (default: sbwg.log in the

Other options:

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

	-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.
		current directory) is used.

	-R|--reduce-paths [NUM]
		Shorten links/anchors, filenames and directory names to NUM
		bytes and remove any special characters except dashes,
		underscored and dots from them. If NUM contains two numbers
		separated by a dot (e.g. '8.3') the first value is used for
		the maximum basename length, the second one for maximum suffix
		length. If NUM is not given, the value of $maxfnl is used.
		Defaults to 255 bytes if no value is defined. Minimum value is
		'6' (6 bytes) or '6.0' (6 bytes, filename suffix removed).
		Note that the paths generated with this option will differ
		from those of a web site that was generated without this option
		and may therefore result in incompatible/dead links. This
		option slows down the generation process significantly.

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

END_OF_HELP
    ;;

    input)									# In case help option argument is "input".
      more << END_OF_HELP
The input directory is the root directory of the file structure that resembles
the source files of the website that should be generated into a browsable HTML
site. The input directory has to contain a 'settings' file in order to get
recognised a a SBWG input directory. All other files and directories and the
contents of the settings file are optional. All components of the site - if they
are included in the web site at hand - have to be placed in their respective
directory. For detailed information about the possible contents of a SBWG input
directory please refer to the README file or just peek in the example/ directory
to get an idea what goes where. The input directory may contain additional files
and directories that are not used by the script, are used by custom hooks or by
additional scripts.

Option: '--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
  input 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. (See '--help settings')
END_OF_HELP
    ;;

    output)									# In case help option argument is "output".
      more << END_OF_HELP
The output directory is the directory where the final browsable HTML site will
be placed by the script. It has to be writable by the script. Existing files
will be overwritten if the script regenerates them. Additional files that should
be accessible publicly may be placed anywhere inside the output directory and
will not be touched by SBWG if SBWG doesn't generate files of the same name.
Please refer to the README file for more information on the output directory.

Option: '--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 output
  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, this option can be omitted when generating or
  updating 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. 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 that 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
A SBWG page resembles a single static HTML page. SBWG will generate an HTML file
with header, navigation bar and footer from the source file. The source file of
a SBWG page consists of an optional 'title' tag at the top of the file
(see '--help tags') and the content of the page below after that. The content is
a block of HTML code that will be included unaltered in the resulting HTML page
by SBWG. See the README file for more information on SBWG pages.

Option: '--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 only
  (a) page(s) have been changed. If nothing else has been changed, this is the
  quickest way to update the web site.

Option: '-P PAGEFILE'
  While option '--page'/'-p' can be given the name of a single page that should
  be generated or updated, option '-P' takes the filename of a SBWG page that
  should be generated. This can be a file in the pages/ directory of the web
  site or any other file. This means that even files that are not part of the
  source file structure of the web site can be integrated using this option.
  As of right now, only one path/filename can be passed to this option.
END_OF_HELP
    ;;

    blog|blogs)									# In case help option argument is "blog".
      more << END_OF_HELP
A SBWG web site can have one weblog with as many entries as the generating
computer can handle. The page all.html contains all entries ordered by
'sort' tag > 'edited' date > 'created' date (split into several pages if there
are many entires). Additionally a tagpage for each category, topic, language and
author tag that is used in at least one entry. Furtherly a combined tagpage for
each tag that each author has used at least once is created, on which only
entries with that tag from that author are included. That means SBWG can be used
to create multiple blogs from different authors as long as all authors trust each
other.

The blog consists of the entries, tagpages and the RSS feed. Those three elements
can be created individually (see '--help options'). They are also created in a
complete web site generation (options '--complete' or '-c'). There is also an
option for generating/updating the parts of the web site that make up the blog
in one option.

Option: '--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 '--complete'/'-c'.
  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
An entry in SBWG is a piece of content that can contain zero, one or more tags
(like a title, categories, topics, author name and other meta data) and a text
or HTML part. All entries together make up the webblog of the web site. You can
think of an entry as a single blog post or blog entry. Entries are located at
the directory 'entries/' in the web site's source directory (input directory).
Each file in that directory resembles one entry.

Option: '--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.

Option: '-E ENTRYFILE'
  While option '--entries'/'-e' can be given the name of a single entry that
  should be generated or updated, option '-E' takes the filename of a SBWG entry
  that should be generated. This can be a file in the entries/ directory of the
  web site or any other file. This means that even files that are not part of
  the source file structure of the web site can be integrated using this option.
  As of right now, only one path/filename can be passed to this option.
END_OF_HELP
    ;;

    tagpages|tagpage)								# In case help option argument is "tagpages".
      more << END_OF_HELP
For every category, topic, language and author tag that exists in any entry in
the weblog, an HTML page is generated that includes or lists all entries that
are tagged with that tag. For topic tagpages, only the titles, authors and
dates are listed and entries are grouped by topic and sub-topic. For all other
tags the tagpages include the entire entries (blog view) and entries are sorted
by the creation date ('created:' tag), edited date ('edited:' tag) and sort
value ('sort:' tag). A tagpage is also created for every author+tag combination.
With these combined tagpages it is possible to view entries tagged with a
certain category, topic or language and filter them to show only entries by one
author.

Option: '--tagpage [TAGNAME]' 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
Currently, only one RSS feed is created for the entire website. No other formats
and no individual feeds for single tags. Both may change for future version.

Option: '--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


Option: '--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
#Option: '-G GALLERYDIR'
#  With this option, you can generate a gallery by giving this option the name
#  of the directory that holds the gallery. The differencd to option
#  '--gallery'/'-g' is that this can also be a path that is outside of the web
#  site's source directory.
    ;;

    files)									# In case help option argument is "files".
      more << END_OF_HELP
Option: '--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 in the output directory but not in the input
  directory's files/ directory will not be touched.
  If pathreducer mode is enabled (option '--reduce-paths'/'-R', see '--help
  pathreducer') the paths of files in the files/ directory including their
  filenames will be modiefied and shortened if necessary to meet the rules
  of the pathreducer. Note that this means that links to files, whether from
  inside the web site or from external web sites, will have do be adjusted
  accordingly. It is best to make sure that directory names and filenames
  inside the files/ directory do not contain any characters that could be
  removed or modified by the pathreducer in order to prevent surprises or
  dead links. If you want to use pathreducer mode but not for copying the
  files/ directory, you can run the script once with option '--reduce-paths'/
  '-R' and without option '--files'/'-f' and a second time with option
  '--files'/'-f' but without the option '--reduce-paths'/'-R'.
END_OF_HELP
    ;;

    pathreducer|reduce-paths)							# In case help option argument is "pathreducer".
      more << END_OF_HELP
Option: '--reduce-paths [MAXFNL[.MAXSL]]' or '-R [MAXFNL[.MAXSL]]'
  If this option is used, all file names generated by the script (that
  included entry files, page files, tagpages, arbitrary files from the files/
  directory as well as links in generated HTML files) will be reduced to
  contain only lowercase letters, numbers, underscores, dashes and dots and
  will be no longer than MAXFNL bytes.
  MAXFNL (the maximum filename length) can be set as an argument to this
  option, as the variable $maxfnl in the settings file or it can be omitted
  to use the default of 255 bytes.
  MAXFNL can be an integer to mean the maximum length of directory names and
  filenames or it can be two integers separated by a '.' (dot) to define both
  the maximum file namelength without filename extension/suffix and the
  maximum length of the suffix. An example of this two-value usage is '8.3'.
  This will only create directory names and filenames that abide by the
  Short File Name restrictions of old FAT filesystems of MS DOS.

  Using pathreducer mode slows down the generation process significantly. It
  should only be used if directory names or file names that can not be written
  to the filesystem of the output diectory are expected, e.g. because of very
  long tags or because the site will be hosted on an old DOS machine.

  Using this option can lead to directory names and filenames that differ from
  the original names (of course, that's the point of this option). That means
  that links to files (HTML files, image files, stylesheets, downloads, ...)
  can change when this option is used, not used or used with a different value
  for $maxfnl from previously. It should therefore be a conscious decision to
  either use or not use this option every for a public web site.
END_OF_HELP
    ;;

    styles|style)								# In case help option argument is "styles".
      more << END_OF_HELP
Option: '--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
Option: '--parallel [NUM]' or '-Q [NUM]'
  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
Option: '--force' or '-F'
  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 filename. 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
    ;;

    tag|tags|tagtype|tagstypes)							# In case help option argument is "tag" or "tags" or "tagtype" or "tagtypes".
      more << END_OF_HELP
Tags are meta-data that is assigned to SBWG entries and pages in their source
file headers. The use of tags is always optional. They should be used when
creating a weblog, though. Without any tags there is no structure to the blog
entries. One tag can be defined per line. Each tagline has the format:

    tagtype:tagvalue

There are different types of tags that are used differently by SBWG. Some
tagtypes allow for subtags. For those tagtypes multiple colons (:) can occur
per line:

    tagtype:tagvalue:subtagvalue:subsubtagvalue(:...)

For SBWG pages, only the title tag or no tag is allowed. For entries the
following types of tags exist:

title		Title of the entry or page
created		Creation date or other string used for sorting
edited		Last edited date or other string used for sorting. Overrides
		'created'.
sort		Bogus date or other string to overwrite 'edited' and 'created'.
author		Name of the author of the entry
cat		Category (and possibly sub-category)
top		Topic (and possibly sub-topic)
lang		Language of the entry
re		Marks the entry as connected/being an answer to another entry.
ref		Marks this entry as referring to another entry.
reply		Marks this entry as a reply to another entry.
redirect	Replaces the content with the content of another entry/redirects
		to another entry
note		Tag is ignored by the script. Can be used as a comment in the
		header.

For much more information on tags and how to use them, please see the Tags
section in the README file.
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
    ;;
    *)										# In case no or an unexpected 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, pathreducer, tagtypes\n'
    ;;
  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\|n"				# 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 by name is set. I'll only use the last one that has an argument."; fi
  if option_set_multi E; then v "More than one option to generate entries by path 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 by name 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 by path 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(s) to the options string so we can easily check later
  										#   whether it has been set.
  # shellcheck disable=SC2015
#  case "${1}" in
  case "${1: -1}" in								# Look at the last of the option letters because that one may have an argument.
    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.
    R) [[ ! "${2}" =~ (^-|^$) ]] && maxfnl="${2}" ;;	        		# If argument for option -R/--reduce-paths is provided, set the max.
                                                                                #   filename length accordingly, overriding the default and the settings file.
    l) [[ ! "${2}" =~ (^-) ]] && {
#      if [[ -n ${logfile} ]]; then
#        logfile="${2:-$logfile}"
#      else
#        logfile="${2:-SBWG.log}"
#      fi
      logfile="${2:-SBWG.log}"
      [[ -w ${logfile} ]] || :|install -D /dev/stdin "${logfile}" \
        || { w "Log file '${logfile}' can not be created."; logfile=SBWG.log; }	# Make sure the log file and all directories above it exist.
      [[ -d ${logfile} ]] && { w "'${logfile}' can't be used as a log file. " \
        "It's a directory."; logfile=SBWG.log; }
#      exec &> >(tee --append "${logfile}")					# Redirect stdout and stderr to both the logfile and the terminal
      exec 2> >(tee --append "${logfile}")					# Redirect stderr to both the logfile and the terminal so that errors from
										#   subprocesses won't be missed.
    };;
    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}" ;;
    _|')') ;;									# Dummy option "letters". Enables using set_option just to judge the following
										#   argument without executing anything option specific.
  esac
  d "Arg for set_option '${1}': '${2}'"
  [[ ! "${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 four options (-i, -h, -V and -l)
  n=$(( n+1 ))									#   done 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 ))
                   [[ ! "${!h}" =~ (^-|^$) ]] && h="${!h}" || h=		# Assign h argument only if it wouldn't start with a '-' (i.e. isn't another option).
                   print_help "${h}"						# While we're at it, also check for the help option, which should work and print the
                   exit								#   help screen independently and regardless of other or 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.
    -l|--log|--logging|--logfile) l=$(( n+1 )); set_option l "${!l}" ;;		# Option l has to be set early on so that no output is missing in the log file.
#    -l|--log|--logging) l=$(( n+1 )); [[ "${!l}" =~ (^-|^$) ]] && l=$n		# Assign l argument only if it wouldn't start with a '-' (i.e. isn't another option).
#    -l|--log|--logging) l=$(( n+1 ))
#                        [[ ! "${!l}" =~ (^-|^$) ]] && l=${!l} || l=		# Assign l argument only if it wouldn't start with a '-' (i.e. isn't another option).
#                        set_option l "${l}" && shift				# Option l has to be set early on so that no output is missing in the log file.
#    ;;
  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.
readonly logfile

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 "I can't see a 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.

call_function_if_declared hook_start
#while [ ${#} -gt 0 ]
while (( ${#} > 0 ))
do
  case "${1}" in
    -c|--complete) set_option petrsfgn ;;					# A complete re-build consists of generating galleries, blog and pages and copying files and styles.
    -b|--blog|--blogs) set_option etrn ;;
    -g|--gallery|--galleries) set_option ng "${2}" && shift ;;			# Set option g, skip next argument if it's an argument belonging to -g
    -E) set_option nG "${2}" && shift ;;					# Set option G, skip next argument if it's an argument belonging to -G
    -e|--entry|--entries) set_option ne "${2}" && shift ;;			# Set option e, skip next argument if it's an argument belonging to -e
    -E) set_option nE "${2}" && shift ;;					# Set option E, skip next argument if it's an argument belonging to -E
    -p|--page|--pages) set_option np "${2}" && shift ;;				# Set option p, skip next argument if it's an argument belonging to -p
    -P) set_option nP "${2}" && shift ;;					# Set option P, skip next argument if it's an argument belonging to -P
    -t|--tagpage|--tagpages) set_option nt "${2}" && shift ;;			# Set option t, skip next argument if it's an argument belonging to -t
    -r|--rss) set_option nr "${2}" && shift ;;					# Set option r, skip next argument if it's an argument belonging to -r
    -f|--files) set_option f ;;							# Set option f for copying the files directory.
    -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
    -n|--navbar) set_option n ;;						# Set option n for generating the navbar.html file.
    -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 ;;						# Set option v for verbose mode.
    -vv|--very-verbose|--veryverbose) set_option vv ;;				# Set option v for very verbose mode.
#    -l|--log|--logging|--logfile) set_option l "${2}" && shift ;;		# Set option l, skip next argument if it's an argument belonging to -l
    -l|--log|--logging|--logfile) set_option "(l)" "${2}" && shift ;;		# Option has been dealt with previously. But the conditional shift is still needed.
#    -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.
    -R|--reduce-paths) set_option R "${2}" && shift ;;				# Set option R, skip next argument if filename length is provided.
    -S|--setting) set_option S "${2}" && shift ;;				# Reserved for future feature.
    -Q|--parallel) set_option Q; v "Experimental parallel mode is enabled." ;;	# Enable parallel mode (experiemntal).
    -F|--force) set_option F; v "Force mode is enabled. Will ignore " \
      "errors 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 generate " \
      "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 or -P
option_set e || option_set E && gen_entries					# Option -e or --entry or --entries or -E
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.
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 with the things it can do.
# * e	is used to print an error message and exit the script/abort unless force mode (option --force or -f) is enabled.
# * Custom hooks should note that they have been called and what they are doing at least with a debug message (function d()).
#
################################################################################################################################################################


################################################################################################################################################################
#
# File Descriptors (Handles)
#
# 0	stdin		Not really used, actually.
# 1	stdout		Output meant for the terminal. Verbosity depending on options.
# 2	stderr		Error output from external processes and from error messages that warrant the script exiting/aborting.
# 3	(reserved)	For output that is meant for the log file. Highest verbosity (includes all messages including debug messages)
# 4	(reserved)	c?
# 5	(reserved)	v?
# 6	(reserved)	vv?
# 7	(reserved)	d?
# 8	(reserved)	?
# 9			Used for processing stuff here and there (e.g. in loops).
#
################################################################################################################################################################


################################################################################################################################################################
#
# 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)
# c()				Similar to o() but additionally saves the output in a cache file at a location in the $cachedir resembling the 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.
# pathreducer			Basically shortens the elements of a path that are longer than the defined maximum filename length ($maxfnl).
# 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_content()			Appends the content part of a source 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.
# pathreducer()			If wanted and necessary, converts each element of a path to be no more than $maxfnl bytes long.
#
# 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_page()		(Formerly gen_entry()) Generates the HTML file for one entry.
# add_entry_chunk()		Generates and adds the title-wrapper part of an entry. (It's the same an entry pages and tagpages).
# 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)
# $minisize		(Formerly $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 (but changed for 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
# $odir			Path of the output directory - The directory where the HTML web site is written to.		set_option(), settings file, script fallback
# $cachedir		Path of the caching directory used in the current run of the script.				set_option(), settings file, script fallback
# $logfile		Path/filename of the log file, in case logging is enabled.					script file
# $maxfnl		The maximum allowed/desired filename length without filename extension in bytes.		set_option(), settings file, script fallback
#
# Commonly used local variables:
# $outfile		Path and name of the file that's currently being processed. Used by many functions, mainly through o().
# $cachefile		Path and name of the file that's being used to store the cache of the currently prcessed item.
#
################################################################################################################################################################



################################################################################################################################################################
# todo:
#
# v0.9.15 milestones:
# - topic tagpages ending in ':' and their subtop tagpages are not printed (empty) in the navbar
# - in grab_tags() and prepare(): replace slashes and other always forbidden characters from tags with something else (dashes?)
# - change printf of warningsign, errorsign, etc. to use $s or $q or whatever the right one is for thise excaped characters (the signs might be changed to contain something completely different, after all.
# - parent tags of subtops are missing in sitebar if no entry has that tag? really, that has been handled well for a long time, hasn't it?
# - last tag gets sometimes printed to content if there is no newline between tagheader and content but sometines not
# - percent-encode special characters in hrefs and srcs - new function: encode_url()
#     - which characters other span space (%20) and double quotes (%22) need to be encoded in URLs? list: https://www.w3schools.com/tags/ref_urlencode.ASP
#         - unsafe and reserved characters from https://www.ietf.org/rfc/rfc1738.txt
#     - remove trailing and leading spaces from URLs not only in hrefs and src but also in file names (because hrefs can have them and they are not interpreted as part of the uri)
#
# v0.9.16 milestones:
# - fill the help pages with more information apart from the options definitions.
# - update or remove README content from example site
# - copy updated hooks from weblog/settings to example/settings
#
# v1.0.0 milestones:
# - change SBWG's name to something that doesn't contain a person's name, e.g. Supergood Bash Website Generator or Sweet something or something entirely different
#     - S(i)BW(i)SG - Sweet Bash Web Site Generator
#     - S(i)BW(i)G - Sweet Bash Website Generator
#     - S(i)BW(i)G - Sweet Blue Website Generator
#     - 
# - extensive testing of all sorts of things with all sorts of content and styles and files and options and arguments
#     - test all options with example site, draft0 and trap site
#         - script that runs them all in a row
#     - generate into non-existant odir (parent also doesn't exist)
#     - input dir disappears during generation
#     - odir disappears during generation
#     - 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
#     - many different and weird filename suffixes
#     - web site with empty settings file
#     - ...
# - remove obsolete lines (commented out or debug messages)
# - settings file durch shellcheck hindurchjagen
# - shellcheck script again
#
# v1.1.0 milestones:
# - start to use GNU parallel?
#     - find solution for message output
#         - a simple way should be to use mkfifo
#             - msgpipe="${tmpdir}/messages/$(pathreducer "FUNCTIONNAME/${1}")"
#             - mkfifo --mode=644 -- "${msgpipe}"
#             - then write all output to that fifo file pipe thingy
#             - at the end of the function read all the messages.............neeee so wird das nix
#         - use a lock file in $tmpdir - while it exists, don't write messages, wait - when it doesn't exist, create it, if creation was successful, output the message
#         - collect all messages from a function in a variable and only print the variable at the end
# - "img:" tag feature
# - contact link below posts
#
# (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 long version options
#         - for the equal sign: add "starting with '--optionname=' to the case check
#         - for the multiple short options combined: check for starting with '-i' instead of is '-i'
#             - if something remains after that, turn that into an option with a dash so it can be identified by the next loop?
#             - or: if argument starts with dash, loop through all its letters
#             - either way: recognise the last letter and interpret the following argument if there is one and it doesn't start with a dash as an argument to that last option letter
# - (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) generate atom feeds
# - (0) create galleries.html or galleries/index.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
# - setting in settings file for combined tagpages: which combinations should be generated? default: combos=author
# - new option for switching to interactive mode, where other option don't matter and the user is asked questions enter paths and names interactively/is lead to making all decisions step by step
# - create tag cloud for categories (and for topics?)
# - support galleries in subdirectories
#     - group galleries in navbar according to directory
#         - have them collapsed by default
#     - create html page per directory with list of galleries in that directory
# - 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 settings file before doing anything
# - new option that does nothing but call a hook (hook_custom or something similar)
#     - it would take an argument and pass that to the hook
#     - document this hook in README, help pages, usage information, howto
# - error_exit if sourcing the settings file produces errors/an error
#
# particularities:
# - change gen_page, gen_entry, etc. to take the full file/dir path as argument - options -P, -E and -G should be able to be used with page/entry/gallery not inside the input dir
# - (4) test substitudes for when nice and convert are not available
# - (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
# - generate (rss) feeds for individual tagpages
# - hardcode </body></html> into the script, remove them from footer file. Or replace footer file with hook so it's the same as it already is with the header.
# - allow --rss/-r to take argument for desried feed
# - previews in galleries and minis/thumbnails in entry pages should both link to the same files: either the originals - this todo will be obsolete when both are generated by the same function, which should be done to reduce code redundancy, anyways
# - test tags and stuff in prepare() or in gene_entries() or where applicable:
#     - check for '"' and other forbidden characters in everything that will be used for href or src attributes
#     - check for unfamiliar tags in entries and pages
#     - check all tag values for forbidden characters, non-printable characters, ending in ':', ...
#     - anything else?
# - search this file for: todo and fix those things
# - add debug output to all helper functions if too many arguments are passed (more arguments than expected)
# - include backlinks below entry content in entries that are a target of a re, ref or reply tag
# - if the target of a redirect tag has no title, use its name
# - maybe outsource those big arrays to into files after all
#     - maybe use "sed -i -f -" to allow extremely long tags to be processed
# - maybe make option to create ramdisk for cachedir
# - add link "(arrow up) show all entries on the topic .........." at the top of subtop tagpages
# - link header on top tagpages to their subtop tagpages
# - should mkdir -m be used to set directory permissions in the odir?
# - use declare -i for integer variables?
# - rename helper scripts "sbwg-"...
# - combined tagpages for tags that only one author has used are generated - they shouldn't be generated - the filter is not included on these tags anyway, as this would not make any sense
# - use checkboxes for several things to enable css tricks
# - 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 styles, e.g. for background images
# - update FAQ about file and directory permissions
# - use arrays for top and subtop tags? IFS='=' read -ra split <<< "$agline"
# - at least notify if no files are present for the chosen style set
# - error (if not forced) if the chosen style set does not exist (has no files) - or maybe better just warn?
# - replace echo to printf in helper scripts
# - use rsync instead of cp for files files and gallery images and style sheets and tagicons?
# - 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 filename 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? absolute paths and paths reöative to input dir should be allowed, sbwgstyles dir will be used if the style does not exist as a relative path
# - use --mime-type instead of --mime for file checks?
# - subdirs in pages do not work right now - subdirs in pages are supposed to work in future versions
# - how to handle multiple pages and entries with the same name
# - add hooks: before generating starts, in add_navbar, in other helper functions, in help printing function (enable adding own help pages?)
# - what happens if more than one files for the desired entry, page, etc. are found?
# - 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
# - add option -G for generating gallery by path
#
#
# nishnash:
# - check first line of settings file instead of whether the file exists (format: '#SBWG 1.0.0' (first word '#SBWG', second word minimum required version) and error if not there or version is too high
# - include filter(s) on pages other than 0
# - include page number in title
# - 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. tags or entry titles that starts with " will lead to title attributes that are closed too early
# - --log should be able to be used without any output to stdout, so, idenendently of -v, -vv and -d
# - when multiple edited, created, or sort tags are present, the first one should be used, not the last one - sbwg-editentry expexts that - the change would need to be documented in README
# - ignore taglines but consider them as tags if they start with a #?
# - find a replacement for the perl script to filter out broken unicode characters and control characters in tag values
# - maybe change the script to not output anything to any file or stdout or stderr or other file descriptor before all option have been parsed
# - for option --debug/-d: trap DEBUG, use $BASH_COMMAND?, read https://jichu4n.com/posts/debug-trap-and-prompt_command-in-bash/
# - note tags should be acceptable in page headers, not just entries
# - let option --force/-F take an argument: number of errors to ignore - that way if one error is encountered, user can decide to run it again ignoring just that error
# - for future weird tests:
#     - 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
# - problem: if --debug is not the first option, debug output is missing (also, log file change by option still creates two logfiles)
# - use file descriptors for: warning, errors, verbose, very-verbose, debug, log - use predate function for logfile, e.g. exec 5> >(predate|tee --append "${logfile}") or something like that
# - trap ERR
# - check and improve rss validity?
# - make pathreducer better:
#     - user iconv to convert to similar characters - ex.: iconv -f utf8 -t ascii//TRANSLIT < /tmp/utf8_input.txt > /tmp/ascii_output.txt
#     - support maxfnl=ext/ext2/ext3/ext4/fat/fat16, etc
#     - adapt pathreducing to what the filesystem supports (uppercase, special characters, filename length, suffixes)
#     - support maxfnl=auto
#         - detect filesystem with mount or something
#             - check for each directory/part?
#         - that way pathreducer could be enabled by default and turned off with '--reduce-paths=off'
# - 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: 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
# - allow more filename 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/gals/ 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?
# - 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 filenames and tag values
# - convert images in parallel if parallel is available. or what about xargs?
# - 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 or --fix-entry or something for re-generating one entry and all tagpages and feeds it's on?
# - the 'galleries' directory in odir has a name that is longer than 8 bytes. that should not be forced in the odir. using pathreducer here wouldn't be nice, so just rename it
# - remove sub-cat support or extend to infinite depth as with subtops
# - don't display parent cat of sub-cat in list in navbar
#
################################################################################################################################################################
