Files for the official Dragora website
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

559 lines
17 KiB

#!/bin/bash
#
# build
#
# Build script for the Dragora GNU/Linux-Libre website
# (https://www.dragora.org)
#
#
# Copyright (C) 2020, 2021 Michael Siegel
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#### INCLUDES ####
. include/constants || exit 1
. include/subroutines || exit 1
#### CONSTANTS ####
#### GLOBAL VARIABLES ####
backpath=
build_testbed=0
css_path=
err_msg= # Try to do without that. It's currently ununsed.
favicon_path=
lang=
logo_path=
mod_date=
page_title=
pg=
pg_basename_in=
pg_basename_out=
pg_path_in=
pg_path_out=
sublevel=
title=
#### FUNCTIONS ####
_get_page_title() {
## Retrieve page title from input file
grep '^<!--PAGETITLE:.*-->$' "$1" | cut -d ':' -f 2 | sed 's/-->$//'
}
_get_regular_pages() {
## Find all HTML pages in "$PAGES_DIR"/"$lang", except those that belong to
## the Dragora Handbook
find -- "$PAGES_DIR"/"$lang" -path "$PAGES_DIR"/"$lang"/doc/handbook -prune \
-o -type f -name '*.html.in' -print
}
_has_subpages() {
## Tell whether a page has sub-pages
local pg="$1"
local item=
for item in "${navtree[@]}"
do
[[ "$item" =~ ^"${pg}".+ ]] && return 0
# Quoting the variable substitution is not actually necessary because
# substitutions occur before the comparison. Still, quoting serves as a
# nice reminder that the substitution is not part of the actual regex.
# (Quotes will be stripped on substitution.)
done
unset item
return 1
}
_is_subpage() {
## Tell whether a page is a sub-page
[[ "$1" =~ /.+/ ]] && return 0
return 1
}
_mk_clean() {
## Remove page directories from $OUTPUT_DIR
local dir=
for dir in $(_get_lang_dirs)
do
rm -rf -- "${OUTPUT_DIR:?}/$dir" || \
{ _perr "cannot remove directory -- '$OUTPUT_DIR/$dir'"; return 1; }
done
unset dir
}
_mk_header() {
## Compile HTML page header
printf '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"\n "http://www.w3.org/TR/html4/strict.dtd">\n'
printf '<html lang="%s">\n<head>\n' "$lang"
printf \
'%s<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">\n' \
"$IND1"
printf '%s<link rel="stylesheet" type="text/css" href="%s">\n' "$IND1" \
"$css_path"
printf '%s<link rel="icon" type="image/gif" href="%s">\n' "$IND1" \
"$favicon_path"
printf '%s<title>%s</title>\n' "$IND1" "$title"
printf '%s<meta name="description" content="%s">\n' "$IND1" \
"$lang_description"
printf '</head>\n<body>\n'
printf '%s<div class="header" role="banner">\n' "$IND1"
printf '%s<img src="%s" height="80" alt="Dragora logo">\n' "$IND2" \
"$logo_path"
printf '%s<div class="header_text">\n' "$IND2"
printf '%s<h1>Dragora</h1>\n' "$IND3"
printf '%s<p class="tagline">%s</p>\n' "$IND3" "$lang_tagline"
printf '%s</div> <!-- close header_text -->\n' "$IND2"
printf '%s</div> <!-- close header -->\n' "$IND1"
printf '%s<div class="torso">\n' "$IND1"
}
_mk_indent() {
## Create an indentation string for a given level
local ind=
local level="$1"
local i=
for ((i=0; i<level; ++i))
do
ind="${ind}${IND1}"
done
unset i
printf '%s' "$ind"
}
_mk_nav_subpages() {
## Create entries for immediate sub-pages of a given page in the site
## navigation
local ind="$2"
local parent="$1"
local sub_item=
local sub_item_path=
local sub_item_label=
printf '\n%s<ul>\n' "$ind"
# Print entries for immediate sub-pages in $navtree order.
for sub_item in "${navtree[@]}"
do
# If $sub_item with the trailing slash removed starts like $parent and then
# contains additional characters but no addititonal slash, i.e., is an
# immediate sub-page to $parent…
if [[ "${sub_item%/}" =~ ^"${parent}"[^/][^/]*$ ]]
then
sub_item_path="${backpath}${sub_item}"
sub_item_label="$(_get_page_title "${PAGES_DIR}/${lang}/${sub_item}/index.html.in")"
printf \
'%s<li><div class="site_nav_item"><a href="%s">%s</a></div></li>\n' \
"${ind}${IND1}" "$sub_item_path" "$sub_item_label"
fi
done
unset sub_item
printf '%s</ul>\n' "$ind"
}
_mk_nav_matchlevel() {
## Count up to which field two paths match
local path1="$1"
local path2="$2"
local fx=
local fy=
local field=1
local match_level=0
while true
do
fx="$(printf '%s' "${path1%/}" | cut -d '/' -f "$field")"
fy="$(printf '%s' "${path2%/}" | cut -d '/' -f "$field")"
if [[ -z "$fx" || -z "$fy" ]]
then
break
fi
if [[ "$fx" = "$fy" ]]
then
((++match_level))
else
break
fi
((++field))
done
printf '%d' "$match_level"
}
_mk_nav() {
## Compile HTML navigation menu
local current_pg="${pg_path_out#*/}"
local ind_base="$IND4"
local ind_sublevel=
local item=
local item_label=
local item_path=
local nav_item=
local nav_label=
local nav_path=
local sublevels_preclosed=0
local sublevel_prev=0
local sublevel_curr=
local sublevel_self=
printf '%s<div class="site_nav" role="navigation">\n' "$IND2"
printf '%s<ul>\n' "$IND3"
# Looping on predefined targets in a predefined order because only certain
# pages are to appear in the navigation bar, and in a custom order.
for nav_item in "${navtree[@]}"
do
[[ "$build_testbed" -eq 0 && "$nav_item" = testbed/* ]] && continue
ind_sublevel="${ind_base}"
nav_path="${backpath}${nav_item}"
nav_label="$(_get_page_title \
"${PAGES_DIR}/${lang}/${nav_item}/index.html.in")"
if [[ "${nav_item%/}" = "$current_pg" ]]
then
if _is_subpage "$nav_item"
then
sublevel_self="$(printf '%s' "${nav_item%/}" | tr -dc '/' | wc -c)"
# Make entries for parent pages in $nav_item's path – excluding the
# top-level page – as well as an entry for $nav_item itself, including
# its immediate sub-pages.
for item in "${navtree[@]}"
do
sublevel_curr="$(printf '%s' "${item%/}" | tr -dc '/' | wc -c)"
# Ignore the top-level page in $nav_item's path
[[ "$sublevel_curr" -eq 0 ]] && continue
# Ignore anything that is a sub-page to $nav_item or its siblings
[[ "$sublevel_curr" -gt "$sublevel_self" ]] && continue
# Ignore "non-linear" sub-pages in $nav_item's path:
# Determine a path match level between $nav_item and $item.
# E.g., for testbed/tea* and testbed/icecream*, it would be 1.
# Pages for which the sublevel is bigger than the match level shall
# be ignored.
match_level="$(_mk_nav_matchlevel "$nav_item" "$item")"
[[ "$sublevel_curr" -gt "$match_level" ]] && continue
ind_sublevel="$(_mk_indent \
"$((sublevel_curr + (sublevel_curr - 1)))")"
# Sub-levels of pages and required indentation levels relate as
# follows:
# Sub-level: 1 2 3 4…
# Indentation level: 1 3 5 7…
# When entering a deeper nesting level, open a sub-list.
if [[ "$sublevel_curr" -gt "$sublevel_prev" ]]
then
printf '%s<ul>' "${ind_base}${ind_sublevel}"
# printf '<!-- OPEN sublevel %s -->' "$sublevel_curr" # DEBUG
printf '\n'
fi
# When entering a nesting level less deep, close a sub-list.
# This happens/needs to be done when any parent page has siblings that
# come after the current page in $navtree.
# This affects closing sub-lists at the end. Thus, pre-closed
# sub-levels are later substracted from the final sub-level used for
# bulk-closing open sub-lists.
if [[ "$sublevel_curr" -lt "$sublevel_prev" ]]
then
local sp="$sublevel_prev"
while [[ "$sp" -gt "$sublevel_curr" ]]
do
((++sublevels_preclosed))
ind_sublevel="$(_mk_indent "$((sp + (sp - 1)))")"
printf '%s</ul>' "${ind_base}${ind_sublevel}"
# printf '<!-- PRE-CLOSE sublevel %s -->' "$sp" # DEBUG
printf '\n'
printf '%s</li>' "${ind_base}${ind_sublevel%"$IND1"}"
# printf '<!-- close item -->' # DEBUG
printf '\n'
((--sp))
done
unset sp
# Reset indentation level to current sub-level
ind_sublevel="$(_mk_indent \
"$((sublevel_curr + (sublevel_curr - 1)))")"
# This comes out at the same indentation level as the closing </li>
# above because <li> always gets an additional $IND1 infront of it.
# The next <li> at $sublevel_curr is printed within the same
# iteration as the closing </li> above.
fi
if [[ "$item" = "$nav_item" ]]
then
printf '%s<li><div class="site_nav_item selected"><a href="%s">%s</a></div>' \
"${ind_base}${ind_sublevel}$IND1" "$nav_path" "$nav_label"
# printf '<!-- BEGIN sub-pages -->' # DEBUG
_has_subpages "$item" && \
_mk_nav_subpages "$item" "${ind_base}${ind_sublevel}$IND2" && \
printf "${ind_base}${ind_sublevel}$IND1"
# I.e., only indent the closing </li> if there are sub-pages and
# it will thus be one a line of its own.
printf '</li>'
# printf '<!-- END sub-pages -->' # DEBUG
printf '\n'
# Other pages in $nav_item's path:
elif [[ "$item" =~ ^"${nav_item%%/*}" ]]
then
item_path="${backpath}${item}"
item_label="$(_get_page_title "${PAGES_DIR}/${lang}/${item}/index.html.in")"
if [[ "$sublevel_curr" -eq "$sublevel_self" ]] # Handle siblings
then
printf '%s<li><div class="site_nav_item"><a href="%s">%s</a></div></li>\n' \
"${ind_base}${ind_sublevel}$IND1" "$item_path" "$item_label"
elif [[ "$sublevel_curr" -lt "$sublevel_self" ]] # Handle parents
then
printf '%s<li><div class="site_nav_item"><a href="%s">%s</a></div>' \
"${ind_base}${ind_sublevel}$IND1" "$item_path" "$item_label"
# Close list item immediately for parents for which no sub-pages
# shall be printed (i.e., siblings at a lower sub-level, where
# sub-pages would be "non-linear" pages.).
[[ "$sublevel_curr" -eq "$match_level" ]] && printf '</li>'
printf '\n'
fi
fi
sublevel_prev="$sublevel_curr"
done
# Close all open sub-lists
#
# The problems here are:
#
# A) sublevel_curr might well be one of an ignored $item that has deeper
# nesting than the last $item that wasn't ignored! We need to
# prevent those deeper nesting levels from leaking through.
#
# B) Sub-levels that have been pre-closed (see above) need to be counted
# out before bulk-closing open sub-lists.
#
# In the current context, there the deepest meaningful sublevel is
# $sublevel_self. If $sublevel_curr is greater than $sublevel_self,
# we're getting $sublevel_curr from an ignored page at a deeper nesting
# level. In this case $sublevel_curr will be reset to $sublevel_self.
#
# In order to determine the curroce amount of list closings needed, the
# number of occasions on which sub-lists were pre-closed (which has been
# recorded) will be substracted from $sublevel_curr.
[[ "$sublevel_curr" -gt "$sublevel_self" ]] \
&& sublevel_curr="$sublevel_self"
sublevel_curr="$((sublevel_curr - sublevels_preclosed))"
local i=
for ((i=sublevel_curr; i>0; i--))
do
ind_sublevel="$(_mk_indent "$((i + (i - 1)))")"
printf '%s</ul>' "${ind_base}${ind_sublevel}"
# printf '<!-- CLOSE sublevel %s -->' "$i" # DEBUG
printf '\n'
printf '%s</li>' "${ind_base}${ind_sublevel%"$IND1"}"
# printf '<!-- close item -->' # DEBUG
printf '\n'
# ${ind_sublevel%"$IND1"} reduces indentation to the previous
# sub-level, as is appropriate for the closing tag of the <li></li>
# container.
done
unset i
else # $nav_item matches the current page but is not a sub-page.
printf \
'%s<li><div class="site_nav_item selected"><a href="%s">%s</a></div>' \
"${ind_base}" "$nav_path" "$nav_label"
## printf '<!-- BEGIN sub-pages -->' # DEBUG
# Create entries for immediate sub-pages as needed
_has_subpages "$nav_item" && \
_mk_nav_subpages "$nav_item" "${ind_base}$IND1" && \
printf "${ind_base}"
# I.e., only indent the closing </li> if there are sub-pages and
# it will thus be on a line of its own.
printf '</li>'
# printf '<!-- END sub-pages -->' # DEBUG
printf '\n'
fi
else # $nav_item is not the current page.
# Only show items for top-level pages, i.e., if $nav_item represents a
# sub-page (of any kind), ignore it.
_is_subpage "$nav_item" && continue
printf '%s<li><div class="site_nav_item"><a href="%s">%s</a></div>' \
"${ind_base}" "$nav_path" "$nav_label"
# If $current_pg and $nav_item have a match_level of 1, i.e., this is
# the top-level page in $nav_item's path, don't close the list item
# here.
match_level="$(_mk_nav_matchlevel "$nav_item" "$current_pg")"
[[ "$match_level" -eq 1 ]] || printf '</li>'
printf '\n'
fi
done
printf '%s</ul>\n' "$IND3"
printf '%s</div> <!-- close site_nav -->\n' "$IND2"
printf '%s<div class="main" role="main">\n' "$IND2"
}
_mk_footer() {
## Compile HTML page footer
printf '\n<p class="back_top"><a href="#">%s</a></p>\n\n' "$lang_back_to_top"
printf '%s</div> <!-- close main -->\n' "$IND2"
printf '%s</div> <!-- close torso -->\n' "$IND1"
printf '%s<hr class="footer_hr">\n' "$IND1"
printf '%s<div class="footer" role="contentinfo">\n' "$IND1"
printf '%s<p>%s: %s</p>\n' "$IND2" "$lang_mod_label" "$mod_date"
printf '%s<p>Copyright © %s %s<br>\n' "$IND2" "$COPYRIGHT_YEARS" \
"$COPYRIGHT_HOLDERS"
printf '%s<a href="%slicense/">%s</a>\n' "$IND3" "${backpath}" \
"$(_get_page_title "${PAGES_DIR}/${lang}/license/index.html.in")"
printf '%s</p>\n' "$IND2"
printf '%s</div> <!-- close footer -->\n' "$IND1"
printf '</body>\n</html>\n'
}
_mk_pretty() {
## Remove meta comments from page source
sed '/^<!--PAGETITLE:.*-->$/ d' "$1"
}
_set_backpath() {
backpath= # If you don't, the string will grow with every invocation.
while [[ "$sublevel" -gt 0 ]]
do
backpath="${backpath}$DIR_UP"
((--sublevel))
done
}
#### MAIN ####
# TODO: implement traps
## Environment checks
_env_checks || _abort
## Parse command-line arguments
[[ "$1" = '-t' ]] && build_testbed=1
## Remove page directories from $OUTPUT_DIR, then rsync static files
## Order matters!
_mk_clean || _abort
rsync -av --delete "$COMMON_DIR"/ "$OUTPUT_DIR" || _abort
# Compile pages
_get_navtree
for lang in $(_get_lang_dirs)
do
# Get values for language-specific parameters in header and footer
. "$PAGES_DIR/$lang/$HEADER_PARAMS" || _abort
. "$PAGES_DIR/$lang/$FOOTER_PARAMS" || _abort
# Compile pages
for pg in $(_get_regular_pages)
do
# Set path and file names
pg_path_in="${pg%/*}"
pg_path_out="$(printf '%s' "$pg_path_in" | sed "s,$PAGES_DIR/,,")"
# echo "$pg_path_out" # DEBUG
pg_basename_in="${pg##*/}"
pg_basename_out="${pg_basename_in%.in}" # .html.in → .html
if [[ "$build_testbed" -eq 0 ]]
then
# Skip testbed pages
[[ "$(printf '%s' "$pg_path_out" | cut -d '/' -f 2)" = testbed ]] && \
continue
fi
sublevel="$(printf '%s' "$pg_path_out" | tr -dc '/' | wc -c)"
_set_backpath # makes use of $sublevel
# these should, maybe, be 'local' to _mk_header
css_path="${DIR_UP}${backpath}${STYLESHEET_PATH}"
favicon_path="${DIR_UP}${backpath}${FAVICON_PATH}"
logo_path="${DIR_UP}${backpath}${LOGO_PATH}"
# Determine page title
page_title="$(_get_page_title "$pg")"
title="$SITE_TITLE $SEPARATOR $page_title"
# Determine modification date (requires GNU version of `stat')
mod_date="$(TZ="$TIMEZONE" stat -c '%y' -- "$pg" | cut -d '.' -f 1)"
mod_date="$mod_date $TIMEZONE"
# Action
mkdir -p -- "$OUTPUT_DIR"/"$pg_path_out" || _abort
{ _mk_header && _mk_nav && _mk_pretty "$pg" && _mk_footer; } \
> "$OUTPUT_DIR"/"$pg_path_out"/"$pg_basename_out" || _abort
done
ln -s -- "$INDEX_PAGE_DIR"/index.html "$OUTPUT_DIR"/"$lang"/index.html
done
ln -s -- "${PAGES_DIR_MASTER##*/}"/"$INDEX_PAGE_DIR"/index.html \
"$OUTPUT_DIR"/index.html
_cleanup