#!/bin/bash

################################################################################################################################################################
#
# SBWG - steeph's bash website generator
# Last changed: 2021-05-15
# Version: See variable below
# https://log.steeph.de/entries/SBWG.html
#
# You can place this file in a directory in your $PATH or execute it from anywhere. It is independent from the website source directory.
#
################################################################################################################################################################

#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								# Needed for various things and stuff, can not be in a function.

version=0.8.9
sitename="placeholder title"
url="https://www.example.com/"
style=style									# Set default style that is used if no style is set through settings file or option.
perpage=10 # number of entries per page - If a list of entries (tagpage or all.html) contains more than this amount of entries it's split in several pages.

# base directory of the webserver - no trailing slash - "/" for web root
webbase="/" # Webbases other than / are not actually supported (yet?)

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

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

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

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


error_exit() {					# Function to exit with error code after a cleanup attempt. Used to abort with a message in case of an error.
  local str="${@}"
  printf '\xE2\x9D\x8C '"${str}"'\n'
  call_function_if_declared hook_error "${@}"
  clean_up 1
}

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

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

# Function to output a string to stdout if verbose mode is enabled.
v() {
  if option_set v; then								# Only if in verbose or very verbose or debug mode ...
    local str="${@}"
    printf '\xE2\x84\xB9  '"${str}\n"						#   ... then display this message with a info symbol in front of it.
    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_multi v; then							# Only if in very verbose or debug mode ...
    local str="${@}"
    printf '\xE2\x84\xB9  '"${str}"'\n'						#   ... then display this message with an info symbol in front of it.
    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.
d() {
  if option_set d; then								# Only if in debug mode ...
    local str="${@}"
     >&2 printf '\xF0\x9F\x90\x9B '"${str}"'\n'					#   ... then display this message with a bug symbol in front of it.
  fi
}

# Function to output a string to the current outfile (The outfile usually is an HTML file that is currently being generated.)
# Detail level is currently untested. The detail feature is not implemented, yet.
o() {										# $1: detail level, $2 ...: string to write to outfile
#  if [[ $1 =~ ^[0-9]+$ ]]
#  then
#    local detail="${1}"
#    shift									# Don't execute the detail value. That was just for checking 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} ]]						# If the detail level is set large enough for this output.
#  then
    ${@} >> "${outfile}"							# Output the rest of the arguments to the previously set $outfile.
#  else
#    return 1									# Return 1 if the set detail level is too low.
#  fi
}

# Used for hooks. If the hook is declared anywhere (commonly in the web site's settings file) then it's called with all arguments starting with the second one.
call_function_if_declared() {							# $1: Name of the function to be called.
  local f="${1}"
  if declare -F "${f}" > /dev/null						# If $f is a function.
  then
    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
}

# This function is used to extract a single tag's value from a list of tag lines.
grab_tag() {									# $1: list of tags, $2: tag name/type, $3: default value (optional)
  if [ ${#} -lt 2 ]; then return 1; fi						# If not at least 2 arguments are given, fail.
  local ret
  ret=$(echo "${1}" | grep -m 1 "^${2}:" | cut -c $((${#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.
  echo "${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
    +([[:alnum:]]):[^:space:]*) num=1 ;;&					# Contain a colon not followed by a whitespace
    author:*|cat:*|top:*|lang:*|created:*|edited:*|sort:*|title:*|note:*|redirect:*|re:*|reply:*|ref:*) num=0;;	# Start with of of these tag types
    "<!--"*) num=1 ;;								# Start with an HTML comment opening (for comapibility with earlier versions)
    "-->") num=1 ;;								# Are only an HTML comment closing (for comapibility with earlier versions)
    *) num=2 ;;
  esac
  return ${num}
}

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



# 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 "${@}"
  echo '<meta http-equiv="refresh" content="0; url='"${webbase}${2}"'" />' > "${odir}/${1}"
  return 0
}


# Preparations that will be performed regardless of the given command line options.
prepare() {
  #tmpdir="${TEMP_DIR}/${scriptname}.$$.${RANDOM}"				# Generate a path were we will store temporary files during this run of the script.
  tmpdir="$(mktemp -d --suffix=.SBWG)"						# Generate a path were we will store temporary files during this run of the script.
  d "tmpdir: ${tmpdir}"
  d webbbase: ${webbase}
  d shellbase: ${shellbase}
  d odir: ${odir}
#  if [ -d "${shellbase}"/tags/ ]; then rm -f "${shellbase}"/tags/*; fi		# Remove all tag lists created in the previous run (if any).
  IFS=$'\n'									# Needed for something. (I forgot where.)
  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 -p "${odir}" || error_exit "Output directory not available"
  if option_set g				# If the gelleries are to be generated in this run then the following commands will be used. If they are not ...
  then						#   ... available, substitute them with aliases that basically do nothing just to prevent the commands from failing.
    command -v convert > /dev/null 2>&1 || alias convert='v "Image not converted. Please install ImageMagick or something compatible and run the the script again or convert the image files manually."'
    command -v nice > /dev/null 2>&1 || nice() { while [[ $1 != -- ]]; do shift; done; shift; $@; } # This only works if there is a -- after the options of nice.
  fi
  [[ $desired_author ]] && v "Ignoring entries that are not tagged with author:${desired_author}." # Inform if only content from only one author will be generated.
  if option_set b || option_set p || option_set e || option_set t || option_set g; then gen_navbar; fi # So that gen_navbar is only called once.
  call_function_if_declared hook_prepare
  v "Finished preparations. ✅"
}


# Called if option -s or -c is set. The assumption that every complete website contains at least one CSS file is almost true after all.
# The global variable $style has to be set. If it's not set through an option or setting in the settings file, the default that is set at the top of this script ...
# ... is used.
copy_styles() {									#  $1: Style prefix
  vv "Copying styles..."
  mkdir -p "${odir}/css"
  d "style: ${style}"
  # Copy all files that end in .css and either start with the provided style name or are named just the style name to the styles directory in the output directory.
  cp -u "${shellbase}/styles/${style}"{.css,-*.css} "${odir}/css/" &>/dev/null
  v "Using style '${style}': "$(ls -p "${shellbase}/styles/${style}"{.css,-*.css} 2>/dev/null | tr "\n" " ")
  v "Finished copying stylesheets. ✅"
}


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


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


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


# CREATE A SINGLE ENTRY
# This generates the HTML page for an entry (as in /entries/entryname.html) and does not care about tagpage generation or what tagpages exist.
# gen_entries is called first and then calls gen_entry for every entry that is to be generated.
gen_entry() {									# $1: The entry for which its page should be generated.
  local entry="${1}"
  if [[ $(file -bi "${shellbase}/entries/${entry}") != text* ]]			# Check again for individually generated entries.
  then
    v "${entry} is not a text file. Not generating it."
    return 2									# Don't generate anything if the file does not seem to be a text file of any sort.
  fi
#  local entryfile="${odir}/entries/${entry}.html"
  local outfile="${odir}/entries/${entry}.html"
  if [ -f "${outfile}" ]							# If the file already exists ...
  then										#   ... check if it is writable ...
    [ -w "${outfile}" ] || error_exit "Can not write to ${outfile}. Please get your permission straight or report this if you think it's a bug. Thank you very much!"
  else										#   ... otherwise check if its directory is writable.
    [ -w "${outfile%/*}" ] || error_exit "Can not write to ${outfile}. Please get your permission straight or report this if you think it's a bug. Thank you very much!"
  fi
  local tags
  tags=$(grab_tags "${shellbase}/entries/${entry}")
  local author
  author=$(grab_tag "${tags}" author)						# Get the author tag (if there is one).
  if [[ ${desired_author} ]] && [[ ${author} != ${desired_author} ]]		# If option -a was used and this is not an entry by that author.
  then
    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 so, redirect ...
  [[ ! -z "${target}" ]] && html_redirect "entries/${entry}.html" "entries/${target}.html" && return 2 # ... the page and don't generate anything into this file.

  local title
  title=$(grab_tag "${tags}" title)
  # print header
  pagetitle="${title} | ${sitename}"
  gen_head
  # print side bar
  o echo "<section>"								# This is just to enable styling.
  [ -f "${tmpdir}/navbar" ] \
    && o cat "${tmpdir}/navbar" \
    || error_exit "Navbar file is gone."					# Include the actual side bar from the temp dir, if the file exists.
  o printf "<main id=\"entry"							# This is just to enable styling.
  o printf "\" class=\"${tags}" | grep -E -- '^cat:|^top:|^lang:|^author:' | tr " " "-" | tr "\n" " " # This is just to enable styling.
  o echo "\">"									# This is just to enable styling.
  o echo "<div class=\"entry-title-wrapper\">"					# This is just to enable styling.
  call_function_if_declared hook_entry_title_start
  # print title
  o echo "<h2 id=\"entry-title\">"
  o echo "${title}"
  o echo "</h2>"
  # print last modified date
  local created
  created=$(grab_tag "${tags}" created)
  local edited
  edited=$(grab_tag "${tags}" edited)
  o echo "<div class=\"page-info\">"						# This is just to enable styling.
  o printf '%s' "${created}"
  if [[ -n ${edited} ]]; then o printf " (edited ${edited})"; fi
  call_function_if_declared hook_entry_title_tags_before
  local tag
  for tag in ${tags}; do
    call_function_if_declared hook_entry_title_tag_before
    case ${tag} in (author:*|cat:*|top:*|lang:*)				# If the tag starts with either of those (the tag types that visitors should be able to filter by
      if [ -f "${shellbase}/tagicons/${tag}.png" ]				# Print a list of tags (or icons if existing) for this entry, ...
      then 									# ... linked to the corresponding tagpage.
        o echo "<span class=\"tag icon ${tag}\">"
        o echo "<a href=\"${webbase}tags/${tag}.html\" title=\"Show all entries tagged with ${tag}\">"
        o echo "<img src=\"${webbase}tags/${tag}.png\" />"
        o echo "</a>"
        o echo "</span>"
      else
        o echo "<span class=\"tag ${tag}\">"
        o echo "<a href=\"${webbase}tags/${tag}.html\">"
        o echo "${tag}"
        o echo "</a>"
        o echo "</span>"
      fi
    call_function_if_declared hook_entry_title_tag_after
    ;;
    esac
  done
  call_function_if_declared hook_entry_title_tags_after
  o echo "</div>"								# /page-info
  o echo "</div>"								# /title-wrapper
  # print entry content
  o echo "<div id=\"content\">"							# This is just to enable styling.
  above_entry_content								# Add reply, ref and re tags
  call_function_if_declared hook_entry_before
  [ -f "${shellbase}/entries/${entry}" ] \
    && cat "${shellbase}/entries/${entry}" | tail -n +$(grab_tag "${tags}" taglines) >> "${outfile}" \
    || error_exit "Entry '${entry}' not there."
  call_function_if_declared hook_entry_after
  o echo "</div>"								# /content
  # attach gallery (if there is one for this entry)
  o echo "<div id=\"gallery\">"
  local gallerypath="${shellbase}/galleries/${entry}"
  shopt -s nocaseglob
  shopt -u nullglob							# Just to be sure. (It would assume there is a gallery for every post with nullglob enabled.)
  ls "${gallerypath}" &> /dev/null						# See if there if a gallery folder for this entry.
  if [[ $? -eq 0 ]]; then							# If there is a gallery directory
    local images=""
    images=$(ls -td "${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 echo "<p><a href=\"${webbase}galleries/${entry}.html\">View Gallery</a></p>"
      local image
      for image in ${images}; do
        local img
        img=$(basename "${image}")
        o echo "<a class=\"gallery-image thumb\" href=\"${webbase}galleries/${entry}/${img}\"><img class=\"thumb\" src=\"${webbase}galleries/${entry}/thumbnails/${img}\"/></a>"
      done
    fi
  fi
  o echo "</div>"								# /gallery
  o echo "</main>"
  o echo "</section>"								# This is just to enable styling.
  # print footer
  [ -f "${shellbase}/footer" ] \
    && o cat "${shellbase}/footer" \
    || vv "I miss the footer file."						# The content of the footer file is included in every page, entry and tagpage.

  call_function_if_declared hook_entry_end
}


# CREATE ENTRIES
# Function that is called once if entries are to be generated in this run. gen_entry() will then be called for each entry that is to be generated.
gen_entries() {
  vv "Generating entries..."
  mkdir -p "${odir}/entries"
  call_function_if_declared hook_entries_start
  if [[ -n ${desired_entry} ]]							# If an entry name was given with option -e to generate a single entry.
  then
    if [[ -f ${shellbase}/entries/${desired_entry} ]]
    then
      gen_entry "${desired_entry}"						# Generate only the entry passed as an argument to option -e
    else
      error_exit "The desired entry '${desired_entry}' does not exist in the entries directory."
    fi
  else										# If no argument (single entry name) was given to option -e
    local entry
    for entry in "${entrylist[@]}"; do						# Look into each entry again.
      gen_entry $(basename "${entry}")						# Generate the page for that entry.
    done
  fi
  call_function_if_declared hook_entries_end
  v "Finished generating entries. ✅"
}



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

#  find /path/to/files -iname '*.jpg.........' -exec mogrify ........... {} +	# Better alternative to the above line.
  local images=""
  images=$(ls -tdr "${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)
    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)
      pagetitle="${title} | ${sitename}"
    else
      pagetitle="${gallery} | ${sitename}"
    fi
    outfile="${odir}/galleries/${gallery}.html"
    if [ -f "${outfile}" ]							# If the file already exists ...
    then									#   ... check if it is writable ...
      [ -w "${outfile}" ] || error_exit "Can not write to ${outfile}. Please check your permission or report this if you think it's a bug. Thank you!"
    else									#   ... otherwise check if its directory is writable.
      [ -w "${outfile%/*}" ] || error_exit "Can not write to ${outfile}. Please check your permission or report this if you think it's a bug. Thank you!"
    fi
    call_function_if_declared hook_gallery_start
    gen_head
    o echo "<section>"
    [ -f "${tmpdir}/navbar" ] \
      && o cat "${tmpdir}/navbar" \
      || error_exit "navbar file is gone."
    o echo "<main id=\"gallery\">"						# This is to enable styling for gallery pages.
    if [ -s "${shellbase}/entries/${gallery}" ]					# If a corresponding blog entry exists
    then
      o echo "<a name=\"top\"><h2 id=\"page-title\">${title}</h2></a>"		# Print title of the blog entry because it's neater than the gallery directory name.
      o echo "<p><a title=\"There is a blog entry appertaining to this gallery. Click here to go to the blog entry.\" href=\"${webbase}entries/${gallery}.html\">View corresponding blog entry</a></p>"	# Also link to that blog entry.
    else									# If no correstponding blog entry exists
      o echo "<a name=\"top\"><h2 id=\"page-title\">Image Gallery: ${gallery}</h2></a>" # Print a title based on the gallery directory name.
    fi
    o echo "<div id=\"gallery-minis\">"

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

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

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

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


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



# CREATE SINGLE PAGE
# This generated the HTML file for a single page source file (from the source directory/pages/).
# gen_pages is called first and then calls gen_pages for every page that is to be generated.
gen_page() {
  local page="${1}"
  vv "- Generating page '${page}'."
  if [[ $(file -bi "${shellbase}/pages/${page}") != text* ]]			# Check again for individually generated pages.
  then
    v "${page} is not a text file. Not generating it."
    return 2									# Don't generate anything if the file does not seem to be a text file of any sort.
  fi
  local outfile
  outfile="${odir}/${page}.html"
  if [ -f "${outfile}" ]							# If the file already exists ...
  then										#   ... check if it is writable ...
    [ -w "${outfile}" ] || error_exit "Can not write to ${outfile}. Are you sure the directory permission are set correctly?"
  else										#   ... otherwise check if its directory is writable.
    [ -w "${outfile%/*}" ] || error_exit "Can not write to ${outfile}. Are you sure the directory permission are set correctly?"
  fi
  local tags
  tags=$(grab_tags "${shellbase}/pages/${page}")
  local title
  title=$(grab_tag "${tags}" title)						# The title is used as <title> tag and printed as <h2> in the body
  # print header
  call_function_if_declared hook_page_start
  if [[ -n ${title} ]]   	                                                # If the page has a title tag that is not empty in its source file header ...
  then
    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
  # print side bar
  o echo "<section>"								# This is just to enable styling.
  [ -f "${tmpdir}/navbar" ] \
    && o cat "${tmpdir}/navbar" \
    || error_exit "navbar file is gone."
  o echo "<main id=\"page\">"							# This is just to enable styling.
  # print title
  if [[ -n ${title} ]]   	                                                # If the page has a title tag that is not empty in its source file header ...
  then
    call_function_if_declared hook_page_title_before
    o echo "<h2 id=\"page-title\">${title}</h2>"				#   ... 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 echo "<div id=\"content\">"							# This is just to enable styling.
  call_function_if_declared hook_page_content_after
  [ -f "${shellbase}/pages/${page}" ] \
    && o echo $(cat "${shellbase}/pages/${page}" | tail -n +$(grab_tag "${tags}" taglines)) \
    || error_exit "The page '${page}' is missing."
  o echo "</div>"								# /content
  o echo "</main>"
  o echo "</section>"								# This is just to enable styling.
  # print footer
  [ -f "${shellbase}/footer" ] \
    && o cat "${shellbase}/footer" \
    || vv "The footer file is missing."						# The content of the footer file is included in every page, entry and tagpage.

  call_function_if_declared hook_page_end
}


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

  call_function_if_declared hook_pages_start
  if [[ -n ${desired_page} ]]							# If a page name was given with option -p to generate a single page.
  then
    if [[ -f ${shellbase}/pages/${desired_page} ]]
    then
      gen_page "${desired_page}"
    else
      error_exit "The desired page '${desired_page}' does not exist in the pages directory."
    fi
  else
    local page
    for page in $(ls -tp "${shellbase}/pages/" | grep -v /); do			# Look into each file (no dirs) in the pages/ directory & create HTML file from it.
      test -f "${shellbase}/pages/${page}" || error_exit "${shellbase}/pages/${page} is not an existing file."
      gen_page "${page}"
    done
  fi
  call_function_if_declared hook_pages_end
  v "Finished generating pages. ✅"
}





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

  declare -a tagslist_unsorted=()						# Start with empty array
  local num=0									# Start at item 0 in the array.
  for entry in "${entrylist[@]}"; do						# Look into each entry
    local tags
    tags=$(grab_tags "${entry}")
    local author
    author=$(grab_tag "${tags}" author)
    if [[ ${desired_author} ]] && [[ ${author} != ${desired_author} ]]		# If option -a was used and this is not an entry by that author.
    then
      continue									# Don't include this entry.
    fi
    local tag
    for tag in ${tags}; do							# For each tag that's found in this entry
      local tagslist_unsorted[${num}]=${tag}					#   put the tag as a new item into the array.
      if [[ ${tag} = top:* ]]
      then
        while [[ ${tag} = *:*:* ]]						# If the tag line contains two colons, it's a sub-tag.
        do									# To make sure all parents of existing sub-tags are also listed in the navbar ...
          tag="${tag%:*}"							#   ... cut off the child to make the currect tag the parent tag and ...
          let "num+=1"
          local tagslist_unsorted[${num}]=${tag}				#   ... put the tag as a new item into the array.
        done
      fi
      let "num+=1"								# Next time next item.
    done
  done
  readarray -t tagslist < <(printf '%s\n' "${tagslist_unsorted[@]}"|sort -t : -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 echo "<nav>"
  call_function_if_declared hook_navbar_before
  o echo "<h3><a title=\"A page listing all blog entries regardless of language, author, category and topic in full.\" href=\"${webbase}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}							# Add heading before first occurance
        then
          firstcat=true
          o echo "<div class=\"navheading\" id=\"cat\"><h3>Blog Categories</h3><ul>"
        fi
        local tag
        tag=$(echo "${item}" | cut -c 5-)
        if [[ ${tag} == *:* ]]; then						# If the tag still contains a colon (meaning it is a subcatagory)
          o printf "<li class=\"cat subcat ${oddity}\"><a title=\"Show blog entries in the category ${item}.\" href=\"${webbase}tags/${item}.html\">"
          o printf "${tag}" | sed 's/^[^:]*://g'					# Remove the parent tag name.
          o echo "</a></li>"
        else
          o echo "<li class=\"cat ${oddity}\"><a title=\"Show blog entries in the category ${item}.\" href=\"${webbase}tags/${item}.html\">${tag}</a></li>"
        fi
        if [[ ${oddity} == odd ]]; then oddity=even; else oddity=odd; fi	# Alternate between odd and even for CSS class.
        ;;
      top:*)									# If the item starts with "top"
        if ! ${firsttop}							# Add heading before first occurance
        then
          firsttop=true
          o echo "<div class=\"navheading\" id=\"top\"><h3>Topics</h3><ul>"
        fi
        local tag
        tag=$(echo "${item}" | cut -c 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 ${oddity}\"><a title=\"List blog entries regarding the topic ${item}.\" href=\"${webbase}tags/${item}.html\">"
#        o printf "${tag/*:/}"
        o printf "${tag}"
        o echo "</a></li>"
        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}							# Add heading before first occurance
        then
          firstlang=true
          o echo "<div class=\"navheading\" id=\"lang\"><h3>By Language</h3><ul>"
        fi
        local tag
        tag=$(echo "${item}" | cut -c 6-)
        o echo "<li class=\"lang ${oddity}\"><a title=\"Show blog entries with the language tag ${item}.\" href=\"${webbase}tags/${item}.html\">${tag}</a></li>"
        if [[ ${oddity} == odd ]]; then oddity=even; else oddity=odd; fi	# Alternate between odd and even for CSS class.
        ;;
      author:*)									# If the item starts with "author"
        if [[ ! ${desired_author} ]]						# Add heading before first occurance
        then
          if ! ${firstauthor}
          then firstauthor=true
          o echo "<div class=\"navheading\" id=\"author\"><h3>By Author</h3><ul>"
        fi
          local tag
          tag=$(echo "${item}" | cut -c 8-)
          o echo "<li class=\"author ${oddity}\"><a title=\"Show blog entries supposedly written by ${item}.\" href=\"${webbase}tags/${item}.html\">${tag}</a></li>"
          if [[ ${oddity} == odd ]]; then oddity=even; else oddity=odd; fi	# Alternate between odd and even for CSS class.
        fi
        ;;
      *)									# Every other item
        ;;
    esac
    call_function_if_declared hook_navbar_item_after
    if [[ ${item%%:*} != ${tagslist[${i}+1]%%:*} ]]				# If the next item in the tagslist is of a different tag type than the current one.
    then
      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 echo "</ul></div>"							# /.navheading
        ;;
      esac
    fi
  done

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

  call_function_if_declared hook_navbar_after

  o echo "</nav>"

  call_function_if_declared hook_navbar_end

  v "Finished generating navbar file. ✅"

  return 0
}




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

  call_function_if_declared hook_tagpage_start
  [[ ${tag} == all ]] \
    && local tagname="All Entries" \
    || local tagname="Entries tagged '${tag}'"					# all.html gets a title, all other tagpages get a title with the tag in it.
  pagetitle="${tagname} | ${sitename}"
  gen_head
  o echo "<section>"								# This is just to enable styling.
  [ -f "${tmpdir}/navbar" ] \
    && o cat "${tmpdir}/navbar" \
    || error_exit "navbar file is gone."
  o echo "<main id=\"tagpage\">"						# This is just to enable styling.
  o echo "<h2 id=\"page-title\">${tagname}</h2>"
  o echo "<div id=\"filters\">"							# It is needed for styling.
  if [[ ! $desired_author ]] && [[ ${tag} != all ]] && [[ ${tag} != author:* ]]	# If this is not all.html and not an author tagpage (or combined author tagpage) and there was no author set with option -a.
  then										# Generate filter links where entries from more than one author are available.
    local secs
    secs=$(printf '%s\n' "${!entrylists[@]}"|grep ^author:|grep -v + )		# Get all existing authors (no combined tags)
    local sec
    local filters
    local num
    for sec in ${secs}								# Cycle through all authors to look for entries with the current tag.
    do
      num=$(echo "${entrylists[${sec}+${tag}]}"|grep -v ^$|wc -l)		# Number of non-empty lines of entries for the combined tagpage of this author + this tag.
      [[ $num -gt 0 ]] && filters="${filters}<span class=\"filter\"><a title=\"${num} entries from ${sec} tagged with ${tag}\" href=\"${webbase}tags/${sec}+${tag}.html\">${sec}</a></span>"
    done
										# 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.
    [[ ${filters} == *href*href* ]] && o echo "<details><summary>Filter further</summary> by ${filters}</details>"
  fi
									# If this is a combines author tagpage, include link tagpage without the author filter.
  [[ ${tag} == author:*+*:* ]] && o echo "<span class=\"remove-filter\"><a href=\"${webbase}tags/${tag#*+}.html\">Remove Filter</a></span>"
  o echo "</div>"								# / #filters
  o echo "<div id=\"content\">"							# 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							# Reading each entry name from the current tag's array item.
  do										# 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}")

    local author
    author=$(grab_tag "${tags}" author)
    local target
    target=$(grab_tag "${tags}" redirect)					# Check if there is an entry to which this entry should redirect.
    local title=""
    if [ -f "${shellbase}/entries/${target}" ]					# If there is one and an entry by that name exists
    then
      o echo "<!-- The following is an entry redirection caused by the entry '${entryname}'. -->"
      entryname="${target}"							# Switch this entry generating iteration to the redirect target entry, keeping...
      tags=$(grab_tags "${shellbase}/entries/${entryname}")			# ... the already set output file name but getting the tags of the target entry.
      title="[Redirection to:] "
#      title=$(grab_tag "${tags}" title)" ➔ "
    fi
    local created
    created=$(grab_tag "${tags}" created)
    local edited
    edited=$(grab_tag "${tags}" edited)
    title="${title}"$(grab_tag "${tags}" title)		# By adding the title at the end of the possibly empty string, the prefix that marks a redirection is kept.
    if [[ ${tag} == top:* ]] || [[ ${tag} == *+top:* ]] || [[ ${tag} == top ]]	# Topic tagpages (combined author tagpage or not)  are handled differently though:
    then									#   The content of the entries is not printed to the tagpage of a topic.
      local sortby="${entry%:*}"						# Get the sort criterium for this entry.
      if [[ ${sortby// - /:} != ${tag} ]] && [[ ${previous_sortby} != ${sortby} ]] # If the current entry's sort criterium (being its complete tag name with ...
      then					#   ... parents) is not exactly this tagpage's tag and is different than the one of the previously processed entry.
        o echo "<h3 class=\"sub-top-heading\">${sortby}</h2>"			# 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.
      o printf "<div class=\"top-title-wrapper ${oddity} "			# This is just to enable styling.
      call_function_if_declared hook_tagpage_entry_top_before
      o printf "${tags}" | grep -E -- '^cat:|^top:|^lang:|^author:' | tr " " "-" | tr "\n" " " # This is just to enable styling.
      o echo "\">"								# This is just to enable styling.
      o echo "<div class=\"top-entry\"><a href=\"${webbase}entries/${entryname}.html\">${title:-(Entry Without Title)}</a></div>" # Add linked entry title
      o printf "<div class=\"entry-info\"><a href=\"${webbase}entries/${entryname}.html\">created on ${created}" # Add created date
      if [[ -n ${edited} ]]; then o printf " (last edited on ${edited})"; fi	# Add edited date if one was set in the entry's header.
      o echo "</a></div>"							# /entry-info
      call_function_if_declared hook_tagpage_entry_top_after
      o echo "</div>"								# /top-title-wrapper
    else									# If this is a tagpage of a tag type other than topic.
      o echo "<!-- next entry -->"
      o printf "<div class=\"entry-wrapper "					# This is just to enable styling.
      o printf "${tags}" | grep -E -- '^cat:|^top:|^lang:|^author:' | tr " " "-" | tr "\n" " " # This is just to enable styling.
      o echo "\">"								# This is just to enable styling.
      local sortby
      sortby=$(grab_tag "${tags}" sort)
      if [[ ${sortby} != about* ]] && [[ ${sortby} != stick* ]]			# If this is a stickied entry, don't print its title-wrapper.
      then
        # print entry content and stuff to tagpage
        o echo "<div class=\"entry-title-wrapper\">"				# This is just to enable styling.
        call_function_if_declared hook_tagpage_entry_title_before
        o echo "<a href=\"${webbase}entries/${entryname}.html\"><h3 class=\"entry-title\">${title}</h3></a>"
        # Add last modified date of the entry, link to entry so that entries without titles can be clicked
        o printf "<a class=\"date\" href=\"${webbase}entries/${entryname}.html\">${created}"
        if [[ -n ${edited} ]]; then o printf " (edited ${edited})"; fi
        o echo "</a>"								# closing created/edited line
        local tag2
        for tag2 in ${tags}; do							# For each tag of the currently processed entry print a linked tag name or icon.
          case ${tag2} in author:*|cat:*|top:*|lang:*)				# If the tag starts with either of those (the tag types that visitors should be able to filter by
            if [ -f "${shellbase}/tagicons/${tag2}.png" ]			# If a PNG file for this tag exists, use this instead of it's name.
            then								# Tag icons must already be of the correct size.
              o echo "<span class=\"tag icon ${tag2}\"><a title=\"Show all entries tagged with ${tag2}\" href=\"${webbase}tags/${tag2}.html\"><img src=\"${webbase}tags/${tag2}.png\" /></a></span>" # Print a list of tags for this entry, linked to the corresponding tagpage
            else								# Print a list of tags for this entry, linked to the corresponding tagpage.
              o echo "<span class=\"tag ${tag2}\"><a title=\"Show all entries tagged with ${tag2}\" href=\"${webbase}tags/${tag2}.html\">${tag2}</a></span>"
            fi
          ;;
          esac
        done
        call_function_if_declared hook_tagpage_entry_title_after
        o echo "</div>"								# /title-wrapper
      else									# If this is a stickied entry.
        call_function_if_declared hook_tagpage_entry_stickied
      fi
      o echo
      o echo "<div class=\"entry-content-wrapper\">"				# This is just to enable styling.
      above_entry_content							# Add reply, ref and re tags
      call_function_if_declared hook_tagpage_entry_content_before
      [ -f "${shellbase}/entries/${entryname}" ] \
        && cat "${shellbase}/entries/${entryname}" | tail -n +$(grab_tag "${tags}" taglines) >> "${outfile}" \
        || error_exit "Entry '${entryname}' missing."
      call_function_if_declared hook_tagpage_entry_content_after
      o echo "</div>"								# /entry-content-wrapper
      o echo "</div>"

      local gallerypath="${shellbase}/galleries/${entryname}"
      ls "${gallerypath}" &> /dev/null						# See if there if a gallery folder for this entry.
      shopt -s nocaseglob
      if [[ $? -eq 0 ]]; then							# If there is a gallery directory
        local images=""
        images=$(ls -td "${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 echo "<div class=\"gallery\">"					# This is just to enable styling.
          o echo "<p><a title=\"This blog entry has pictures attached. Click here to view those pictures in the gallery view.\" href=\"${webbase}galleries/${entryname}.html\">View Gallery</a></p>"
          local image
          for image in ${images}; do
            local img
            img=$(basename "${image}")
            o echo "<a title=\"Click to view the original image file. If you wish to obtain a larger resolution image contact the admin pf this site. They may be saving space on their webserver by not uploading large image files.\" class=\"gallery-image mini\" href=\"${webbase}galleries/${entryname}/${img}\"><img class=\"mini\" src=\"${webbase}galleries/${entryname}/minis/${img}\"/></a>"
          done
          o echo "</div>"							# /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:* ]]							# Tags of the type top: don't get page breaks.
    then
      if [[ ${lastpage} -gt 0 ]]						# If there is more than 1 page to generate.
      then
        if [[ $((( ${entrycount} + 1) % ${perpage} )) -eq 0 ]] || [[ ${lastentry} == $((entrycount + 1)) ]] # If the next entry would be supposed to be on the ...
        then		# ... next page or this is the last entry of this tag (last entries of single-page tags are excluded through below conditions anyway)
          o echo "<div class=\"pager\">"					# This is just to enable styling.
          o echo "Page ${pagecount} of ${lastpage}"				# Print current page number and last page number.
          if [[ ${pagecount} -gt 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 echo " - <a href=\"${webbase}${tag}-$((${pagecount} - 1)).html\">Go to previous page</a>" # Print link to previous page
            else								# If we are generating a tagpage for an individual tag as opposed to all.html
              o echo " - <a href=\"${webbase}tags/${tag}-$((${pagecount} - 1)).html\">Go to previous page</a>" # Print link to previous page
            fi
          fi
          if [[ ${pagecount} -lt ${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 echo " - <a href=\"${webbase}${tag}-$((${pagecount} + 1)).html\">Go to next page</a>" # Print link to next page
            else								# If we are generating a tagpage for an individual tag as opposed to all.html
              o echo " - <a href=\"${webbase}tags/${tag}-$((${pagecount} + 1)).html\">Go to next page</a>" # Print link to next page
            fi
          fi
          o echo "</div>"								# /pager
          o echo "</div>"								# /content
          o echo "</main>"
          o echo "</section>"							# This is just to enable styling.
          [ -f "${shellbase}/footer" ] \
            && o cat "${shellbase}/footer" \
            || vv "There is no footer file."					# Print the footer file before starting the next page.
          pagecount=$((pagecount + 1))
          if [[ ${tag} == all* ]]; then
            outfile="${odir}/${tag}-${pagecount}.html"				# Update the output filename so that every following entry will go to a new page.
          else
            outfile="${odir}/tags/${tag}-${pagecount}.html"			# Update the output filename so that every following entry will go to a new page.
          fi
          if [ -f "${outfile}" ]							# If the file already exists ...
          then										#   ... check if it is writable ...
            [ -w "${outfile}" ] || error_exit "Can not write to ${outfile}. Permissions? Please check. Bug? Please report. Thank you very much!"
          else										#   ... otherwise check if its directory is writable.
            [ -w "${outfile%/*}" ] || error_exit "Can not write to ${outfile}. Permissions? Please check. Bug? Please report. Thank you very much!"
          fi

          pagetitle="${tagname} | ${sitename}"
          gen_head
          o echo "<section>"							# This is just to enable styling.
          [ -f "${tmpdir}/navbar" ] \
            && o cat "${tmpdir}/navbar" \
            || error_exit "navbar file is gone."
          o echo "<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 echo "<h2 id=\"page-title\">${tagname}</h2>"
            if [[ ${tag} == top* ]]						# Tagpages of 'topic' tags are different from those of other tag types.
              then
                o echo "<em class=\"pageinfo\">These entries are available on the topic ${tagname}.</em>"
              else								# If this tagpage is not for a 'topic' tag
                o echo "<em class=\"pageinfo\">This page shows all entries tagged with ${tagname}, newest/last edited entries first.</em>"
            fi
          fi
          o echo "<div id=\"content\">"						# 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 echo "</div>"								# /content
  o echo "</main>"
  o echo "</section>"								# This is just to enable styling.
  [ -f "${shellbase}/footer" ] \
    && o cat "${shellbase}/footer" \
    || vv "The footer file is not there."					# Print the footer file before starting the next page.

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

  call_function_if_declared hook_tagpage_end
}



# CREATE TAGPAGES
# Generates first an array ($entrylists) with one array item per tag, then adds lines to these items: one line per entry, consisting of the entry's sort criteria
# and its name, then sorts these lists and then either generates
#  - a single tagpage if one was desired with option -t or
#  - all tagpages
gen_tagpages() {
  vv "Preparing tagpage generation..."
  declare -A entrylists=()							# Array used to store entry names (one per line) for each tag.
  local entry
  for entry in $(ls -tp "${shellbase}/entries/" | grep -v /); do		# First look at every entry ...
    test -f "${shellbase}/entries/${entry}" || error_exit "${shellbase}/entries/${entry}: no file there"
    local tags
    tags=$(grab_tags "${shellbase}/entries/${entry}")
    local created
    created=$(grab_tag "${tags}" created)
    local author
    author=$(grab_tag "${tags}" author)
    if [[ ${desired_author} ]] && [[ ${author} != ${desired_author} ]]		# If option -a was used and this is not an entry by that author.
    then
      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 "${created:-1970-01-01}\n${edited}"|sort -r|head -n 1) # Pick the larger/later one of the two values, regardless of their formats.
    fi
    call_function_if_declared hook_tagpages_entry
    local tag
    for tag in ${tags}; do							# ... and put its name into all taglists it belongs in.
      case ${tag}
      in
        (top:*)									# If this is a topic tag.
          local parenttag="${tag}"
										# For topic tag pages, their tags instead of $sortby gets used for sorting.
          entrylists[${tag}]="${entrylists[${tag}]}"$'\n'"${tag//:/ - }:${entry}" # Add a new line with that entry to the list.
          while [[ ${parenttag} = *:* ]]					# If the tag still contains a colon, it's a sub-tag.
          do
            parenttag="${parenttag%:*}"						# Cut off the youngest child and ...
            entrylists[${parenttag}]="${entrylists[${parenttag}]}"$'\n'"${tag//:/ - }:${entry}" #   ... add a new line wit this entry to the parent tag list as well.
          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} ]]						# Only include combined author tagpages if option -a (--author) was not used.
          then
            local second_tags
            second_tags=$(printf "${tags}" | grep -E -- '^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					# For each of them add to combined author tagpage to the list in the array so that...
            do									#   ... tagpages filtered by author and one other tag at the same time are generated.
              [[ "${tag}" != "${second_tag}" ]] && entrylists[${tag}+${second_tag}]="${entrylists[${tag}+${second_tag}]}"$'\n'"${sortby}:${entry}"
            done <<< "${second_tags}"
          fi
        ;;
      esac
    done
    if [[ "${sortby}" =~ ^[0-9] ]] || [[ "${sort}" =~ ^[0-9] ]]			# If the date starts with a number (i.e. is a date in the first place)
    then
      entrylists[all]="${entrylists[all]}"$'\n'"${sortby}:${entry}"		# Add new line with entry name to "all", too.
    fi
  done
  mkdir -p "${odir}/tags/"							# Create tags directory if it doesn't already exit.
  cp "${shellbase}/tagicons/"*.png "${odir}/tags/"				# Copy all tag icons - if there are any - to the html directory.
  v "Finished preparing tagpage generation. ✅"
  call_function_if_declared hook_tagpages_start
  if [[ -n "${desired_tagpage}" ]]						# If a tagpage name was given with option -t to generate a single tagpage.
  then
      [[ ${desired_tagpage} == top:* ]] \
        && entrylists[${desired_tagpage}]=$(echo "${entrylists[${desired_tagpage}]}"|sort --key=1,1 -t:|sed -e "s/^${desired_tagpage//:/ - } - //") \
        || entrylists[${desired_tagpage}]=$(echo "${entrylists[${desired_tagpage}]}"|sort -r) # Sort topic tagpages differently from other tagpages.
    gen_tagpage "${desired_tagpage}"
  else
    local tag
    for tag in "${!entrylists[@]}"						# Cycle through all tags.
    do
      [[ ${tag} == top:* ]] \
        && entrylists[${tag}]=$(echo "${entrylists[${tag}]}"|sort --key=1,1 -t:|sed -e "s/^${tag//:/ - } - //") \
        || entrylists[${tag}]=$(echo "${entrylists[${tag}]}"|sort -r)		# Sort topic tagpages differently from other tagpages.
      gen_tagpage "${tag}"							# Generate the tagpage for that tag.
    done
  fi
  call_function_if_declared hook_tagpages_end
  v "Finished generating tagpages. ✅"
  return 0
}




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

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



# Just print the help/usage and exit.
print_help() {
  cat << END_OF_HELP
Usage:
	${scriptname} [-c] [-b] [-g [GALLERY]] [-e [ENTRY]] [-p [PAGE]]
	  [-t [TAGPAGE]] [-r] [-f] [-s [STYLE]] [-i INPUT_DIR]
	  [-o OUTPUT_DIR] [-w WEBPATH] [-n ENTRIES_PER_PAGE]
	  [-z WIDTHxHEIGHT] [-v[v]] [-h] [-d]

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

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

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

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

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

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

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

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

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

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

	-w|--webpath WEBPATH
		Prepent WEBPATH to all HTML links that are generated by the
		script.

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

	-z|--thumbnail WIDTHxHEIGHT
		Resize gallery images down to be no wider than WIDTH pixels and no
		higher than HEIGHT pixels when displayed as thumbnails at the
		bottom of an entry page.

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

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

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

# Check if a certain option is set.
option_set() {									# $1: letter(s) of the short option
  echo "${options}" | grep -q "${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
  echo "${options}" | grep -q "\(${1}\).*\1"					# Returns 1 if the provided character appears more than once in $options.
}

# Check for conflicting options and stuff.
check_options() {
  d "options: ${options}"
  if option_set_multi e; then v "More than one option to generate entries is set. I'll only use the last one."; fi
  if option_set_multi p; then v "More than one option to generate pages is set. I'll only use the last one."; fi
  if option_set_multi t; then v "More than one option to generate tagpages is set. I'll only use the last one."; fi
  if option_set_multi s; then v "More than one style option is set. I'll only use the last one."; 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."; 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."; fi
  if option_set_multi w; then v "More than one option for the webpath is set. I'll only use the last one."; 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."; fi
  if option_set_multi z; then v "More than one option to set the thumbnail size is set. I'll only use the last one."; fi
  if option_set_multi a; then v "More than one author has been defined. I'll only use the last one."; fi
  if option_set_multi h; then v "I can only display one help topic or the entire help. Use '${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 "${options}${1}")						# Add option letter to the string so we can easily check later whether it was set.
  d "Arg for set_option '${1}': '${2}'"
  case "${1}" in
    g) [[ ! "${2}" =~ (^-|^$) ]] && desired_gallery="${2}" ;;			# This is not actually tested, yet.
    e) [[ ! "${2}" =~ (^-|^$) ]] && desired_entry="${2}" ;;			# This is not actually tested, yet.
    p) [[ ! "${2}" =~ (^-|^$) ]] && desired_page="${2}" ;;			# This is not actually tested, yet.
    t) [[ ! "${2}" =~ (^-|^$) ]] && desired_tagpage="${2}" ;;			# This is not actually tested, yet.
    s) [[ ! "${2}" =~ (^-|^$) ]] && style="${2:-style}" ;;			# Do nothing if no argument for option -s is provided. This ensures that the option
    i) [[ ! "${2}" =~ (^-|^$) ]] && shellbase="${2}" || error_exit "Option -i or --input needs an argument." ;;
    o) [[ ! "${2}" =~ (^-|^$) ]] && odir="${2}" || error_exit "Option -o or --output needs an argument." ;;
    w) [[ ! "${2}" =~ (^-|^$) ]] && webbase="${2}" || error_exit "Option -w or --webpath needs an argument." ;;
    n) [[ ! "${2}" =~ (^-|^$) ]] && perpage="${2}" || error_exit "Option -n or --perpage needs an argument." ;;
    z) [[ ! "${2}" =~ (^-|^$) ]] && thumbsize="${2}" || error_exit "Option -z or --thumbnail needs an argument." ;;
    a) [[ ! "${2}" =~ (^-|^$) ]] && desired_author="${2}" || error_exit "Option -a or --author needs an argument." ;;
    h) print_help ;;
  esac
  [[ ! "${2}" =~ (^-|^$) ]]							# Return 0 if the next argument belongs to the first one.
										#   This enables the possibility to use this function to check whether there is an
										#   argument provided for an option. (Whether the next argument exists but does
										#   not start with "-".)
}

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

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

n=0;for arg;do n=$((n+1));case ${arg} in -i|--input) i=$((n+1));;esac;done	# The -i/--input command line option has to be found first, if it is present.
shellbase="${!i:-$(pwd)}"							# If not, set the default: 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.
d "Input directory from command line option: ${!i}"

if test -f "${shellbase}/settings"; then					# If there is a settings file in directory where the website source resides ...
  source "${shellbase}/settings"						#   ... overwrite the variables that are being set at the top of this script file.
else
  error_exit "There is no settings file in \"${shellbase}\". Are you sure this is where the source files of the website you want to generate are? If the path is correct and you don't want to set any setting, touch \"${shellbase}/settings\" to create an empty settings file."
fi

while [ ${#} -gt 0 ]
do
  case "${1}" in
    -c|--complete) set_option pbsfg ;;				# A complete re-build consists of generating galleries, blog and pages and copying files and styles.
    -b|--blog|--blogs) set_option b ;;
    -g|--gallery|--galleries) set_option g "${2}" && shift ;;			# Set option g, skip next argument if it's an argument belonging to -g
    -e|--entry|--entries) set_option e "${2}" && shift ;;			# Set option e, skip next argument if it's an argument belonging to -e
    -p|--page|--pages) set_option p "${2}" && shift ;;				# Set option p, skip next argument if it's an argument belonging to -p
    -t|--tagpage|--tagpages) set_option t "${2}" && shift ;;			# Set option t, skip next argument if it's an argument belonging to -t
    -f|--files) set_option f ;;
    -s|--style|--styles) set_option s "${2}" && shift ;;			# Set option s, skip next argument if it's an argument belonging to -s
    -i|--input) set_option i "${2}" && shift ;;					# Set option i, skip next argument if it's an argument belonging to -i
    -o|--output) set_option o "${2}" && shift ;;				# Set option o, skip next argument if it's an argument belonging to -o
    -w|--webpath) set_option w "${2}" && shift ;;				# Set option w, skip next argument if it's an argument belonging to -w
    -n|--perpage) set_option n "${2}" && shift ;;				# Set option n, skip next argument if it's an argument belonging to -n
    -z|--thumbnail) set_option z "${2}" && shift ;;				# Set option z, skip next argument if it's an argument belonging to -z
    -a|--authir) 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. 🌈" ;;
    -F|--force) set_option F; vv "Forcing generation if necessary." ;;
    -v|--verbose) set_option v ;;
    -vv|--very-verbose|--veryverbose) set_option vv ;;
    -h|-\?|--help) set_option h "${2}" ;;					# Include second argument because I may want to have different help pages in future.
#    -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.
    -*) error_exit "Unfamiliar option '${1}' - see '${scriptname} --help'" ;;
    *) error_exit "Undesired argument '${1}' - see '${scriptname} --help'" ;;
  esac
  shift
done

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

check_options									# Check for colliding and redundant options.

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

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

prepare
option_set s && copy_styles							# Option -s or --style or --styles
option_set f && copy_files							# Option -f or --files
option_set b && {								# Option -b or --blog
  gen_tagpages
  gen_entries
  gen_rss
}
option_set p && gen_pages							# Option -p or --page or --pages
option_set e && gen_entries							# Option -e or --entry or --entries
option_set t && gen_tagpages							# Option -t or --tagpage or --tagpages
option_set g && gen_galleries							# Option -g or --gallery or --galleries
echo "${date}" > "${shellbase}/last"						# Log last complete generation date and time.
call_function_if_declared hook_end
v "I'm done now. ✅"
clean_up
error_exit "Script ended unexpededly-ish."

################################################################################################################################################################
# todo:
#
# v0.9.0 milestones:
# - include re ref and reply lines on tag pages as well as entry pages
#
# v1.0.0 milestones:
# - write HOWTO/FAQ
# - extensive testing of all sorts of things with all sorts of content and styles and files and options and arguments
# - code quality improvements (what do you mean, exactly? read the bash pitfalls again.)
# - come up with errors that could occur and try to catch them
# - finish README (list and explain tag types, options and so on, include rules for special characters and stuff like that)
# - finish HOWTO
# - finish INSTALL
#
# (new) features:
# - (1) scan subdirectories in the pages directory of the input
# - (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)
# - (0) new option: --list (-l) for listing entries with a certain tag? could just use grep though
# - (1) option improvements: check for multiple letters after a dash, use equal sign for lomg version options
# - (0) just an unnecessary idea: add text-only support with imaged converted by viu (or a better tool for html) (also: web 0.5 https://tilde.pt/~fimdomeio/)
# - (-0) just an unnecessary idea: add option to generate gopher site
# - (1) webbase other than /
# - (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) re-introduce option -r (--rss)
# - (0) stickied entries for topic tagpages
# - (0) introduce "reply:" tag type: marks an entry that is a reply to another entry; the linked title of the entry it's referring to is displayes above the content but below the title-wrapper
# - (0) new tag type: "ref:" or something similar for linking an entry to another one (like when it's a continuation but not a reply ot another entry)
# - (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?
# - (0) also generate tagpages that exclude entries that have a specific tag? (like cat:incomplete) no, too much.
# - (2) generate an rss feed for each tagpage
# - (0) generate atom feeds
# - (1) create install script
# - (0) create galleries.html that lists all galleries
#         - just an idea: icons for each gallery for: number of images, corrosponding entry exists
#         - link to corrosponding entries as well
# - (0) video support in galleries
# - (2) audio player at the end of an entry with a relevant audio file
# - (0) HTML/content parsing:
#         - escape in HTML content only where not used for markup: &nbsp; →   (U+00A0), &amp; → & (U+0026), &lt; → < (U+003C), &gt; → > (U+003E), &quot; → " (U+0022), &apos; → ' (U+0027)
#         - [] tags for image and video embedding and other stuff?
#         - add webbase to src="/ and href="/ and the like?
#             - identify relative links/URLs and add $webbase to them
#             - test with webbase other than "/"
# - (-0) i will probably never do this. but: templates instead of outputting the actual html directly from the script
# - (2) cut off long entries on tagpages and all.html?
# - if no file in the input directory was touched since the last generation, don't do anything
#     - should be possible to be overwritten with option --force
#         - --force could also be used to ignore excluded tags
# - maybe: only generate new/edited entries, pages, ... by default, use -a to force to generate all entries/galleries/pages
#     - maybe also use option --force to overwrite that
# - Add a FAQ file
# - option --settings (--setting, -S) to pass variable setting from the command line to overwrite settings set in the settings file
#
# particularities:
# - don't generate desired tagpage if there will be no entries on it
# - don't generate anything if no posts by desired author exist
# - use more printf
# - use format specifiers when using printf
# - make top.html good
#     - remove pager
#     - link in navbar
#     - edit line to smth like These are all entries that are tagged with topics.
# - use of css should be optional
# - replace header and footer files by hooks? maybe a hook in the example site that reads those files.
# - support BMP and GIF files (and other types?) in galleries?
# - make sure no web log related things are generated if there are no entires. a web site without a web log should be possible.
# - link from sub-top tagpage to parent
# - sub-topic h3 headings should be linked to their corrosponding sub-topic tagpages
# - make sure script does not return 0 when nothing has been done (e.g. option -v was set but nothing else)
# - reduce redundancy in entry title wrapper generation by outsourcing to a new function
# - reduce redundancy in sidebar and footer output
#     - new functions for those
#     - new hooks in those functions
# - test substitides for when nice and convert are not available
# - make certain variables readonly?
# - outsource the arrays to a something separated values file in tmpdir to get ready for huge content bases
#
# nishnash:
# - responsive css
# - note tags should be acceptable in page headers, not just entries
# - 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
# - change variables of output files to the same everywhere ($outfile) (hooks will be able to trust that that's the variable that's used everywhere
#    - and also make sure that these variables are always only local
# - ignore taglines but consider them as tags if they start with a #?
# - check and improve rss validity?
# - 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
# - improve speed a bit by removeing redundancies (outsorce repetetive code into functions)
# - CSS enabling:
#    - inlude parent tags and tag types in classes of tag spans?
#    - option to choose how detailed the html structure should be? e.g. value 0-9?
#        0: no html
#        1: only rough layout (regions)
#        2: divs on the topmost 
#        3: all usual divs/elements
#        4: extra classes (all tags, etc.)
#        5: almost too much detail
# - maybe make obvious by name that a variable is global
# - make sure all functions return a (meaningful) exit code
# - allow more file name extensions for gallery image files (upper/lower, ..., more types, SVG?, JPEG2000?)
# - RSS:
#    - exclude stickied posts from rss 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
# - improve image gallery generator
#    - gallery page html and styling
#    - create index.html in html/galleries/ with list of galleries?
# - use SVGs für tagicons? At least allow it. and also allow GIF and others. best support the same as with entrypics and galleries. would "all types starting with "image" work?
# - introduce an array for entries that contains a newline separated list of tags for that entry (all tags including author, sort and title and everything)
# - convert images in parallel if parallel is available. or what about xargs?
# - use find instead of ls to be safer and include files in subdirectories but not the directories themselves
# - line 1017 (commented "Only print title and pageinfo if it's tagpage of individual tag and not all.html."): Use case instead of if, include a line for all.html like "This page shows..."
# - remove sub-cat support or extend to infinite depth as with subtops
# - don't display parent cat of sub-cat in list in navbar
#
# testing:
# - test arguments given to hooks
# - test and finish redirect implemention
# - test hooks for functions in settings file
# - check HTML code integrity (broken tags) again
################################################################################################################################################################
