#! /bin/bash # # -- Program to locate all DocBook and LinuxDoc files in The Linux # Documentation Project (TLDP) Version Control System (VCS) and # produce HTML, PDF and text outputs (with companion Makefile) # # -- License: GPLv2 # # -- written for TLDP in January 2016; Martin A. Brown # set -e # set -x SELFNAME="$( readlink --canonicalize ${0})" ME="${SELFNAME##*/}" # -- basename DIR="${SELFNAME%/*}" # -- dirname MAKEFILE="${DIR}/Makefile" SOURCETREE="${1:-$HOME/vcs/LDP/LDP}" && shift SOURCETREE="$( readlink --canonicalize $SOURCETREE )" OUTPUTTREE="${1:-$PWD/output}" && shift OUTPUTTREE="$( readlink --canonicalize $OUTPUTTREE )" SOURCEDIRS="" SOURCEDIRS="${SOURCEDIRS} ${SOURCETREE}/faq/linuxdoc" SOURCEDIRS="${SOURCEDIRS} ${SOURCETREE}/guide/linuxdoc" SOURCEDIRS="${SOURCEDIRS} ${SOURCETREE}/howto/linuxdoc" SOURCEDIRS="${SOURCEDIRS} ${SOURCETREE}/faq/docbook" SOURCEDIRS="${SOURCEDIRS} ${SOURCETREE}/guide/docbook" SOURCEDIRS="${SOURCEDIRS} ${SOURCETREE}/howto/docbook" SOURCEDIRS="${SOURCEDIRS} ${SOURCETREE}/ref/docbook" # -- my favorite shell functions (and their offspring) # gripe () { printf >&2 "%s\n" "$@"; } debug () { : ; } # -- NOOP for now notice () { gripe "${ME}[$$]: $@"; } abort () { EXIT_CODE="${1}" && shift; gripe "E: $@"; exit $EXIT_CODE; } # -- defaults for logging output; override these in environment # if you care # : "${LOG_FACILITY:=daemon}" : "${LOG_PRIO:=err}" : "${LOGGER_OPTS:=-t ${ME}[$$] -p ${LOG_FACILITY}.${LOG_PRIO}}" # -- if we are connected to a terminal, then send logging output # to STDERR, as well # -- create logger as a shell function # logger () { tty -s && LOGGER_OPTS="-s $LOGGER_OPTS" command logger $LOGGER_OPTS -- "$@" ; } usage () { gripe "usage: ${ME} [ []]" \ " default ldp_checkout=${SOURCETREE}" \ " default destdir=${OUTPUTTREE}" \ "" abort 1 "$@"; } # - - - - - - - - - - - - function select_source_files () { # - - - - - - - - - - - - # # -- return a list of filenames in any directory which are (likely) # to be documents for TLDP # args 1: file extension sought # args +2: base directories in which to seek local ext=${1} && shift for d in $@; do test -e "$d" || abort 2 "ENOENT (2): No such file or directory: ${d}" test -d "$d" || abort 20 "ENOTDIR (20): Not a directory: ${d}"; # -- locate each solo text document, e.g. XWindow-User-HOWTO.xml # find "$d" -mindepth 1 -maxdepth 1 -type f -name \*."${ext}" -printf "%p\n" # -- locate each document owning a directory, e.g. 8021X-HOWTO/8021X-HOWTO.sgml # { find "$d" -mindepth 1 -maxdepth 1 -type d -printf "%p\n" | while read DIR; do # -- get just the stem of the document name from the directory, e.g. 8021X-HOWTO stem=${DIR##*/} # -- construct an abspath, e.g. /full/path/to/8021X-HOWTO/8021X-HOWTO.sgml doc="${DIR}/${stem}.${ext}" # -- see if file exists, and if so, print its name # File existence test logic is reversed, because this script runs # with "set -e". If the test for existence fails, then the script # stops (this should be familiar to those who have written # Makefiles). # test ! -e "${doc}" || printf "%s\n" "${doc}" done } done } # - - - - - - - - - - - - list_output_documents () { # - - - - - - - - - - - - # # -- by definition, each directory in the output tree # is named by the STEM, so this produces a list of the # stem names of the documents that have been produced # find "$@" -mindepth 1 -maxdepth 1 -printf "%P\n"; } # - - - - - - - - - - - - list_source_documents () { # - - - - - - - - - - - - # select_source_files xml "$@"; select_source_files sgml "$@"; } # - - - - - - - - - - - - get_stem () { # - - - - - - - - - - - - # # -- filter filenames and produce stem names # - no args # - STDIN: A filename (can be full filename) # - STDOUT: Just the stem name. # while read FULLNAME ; do fn="${FULLNAME##*/}" printf "%s\n" "${fn%.*}"; done } # - - - - - - - - - - - - sort_by_stem () { # - - - - - - - - - - - - # # -- typical decorate, sort, undecorate pattern, though, # admittedly, not common in shell # - no args # - STDIN: stream of strings with full path # - STDOUT: stem-sorted stream of strings with full path # { { while read OBJ; do stem=$( printf "%s\n" "$OBJ" | get_stem ) printf "%s %s\n" "${stem}" "$OBJ" done; } | sort; } | awk '{print $2}'; } # - - - - - - - - - - - - find_sort_by_mtime () { # - - - - - - - - - - - - # local SOURCE="${1}" && shift local OUTPUT="${1}" && shift { # -- directory age does not count, only file age! # test -e "${SOURCE}" \ || abort 2 "ENOENT (2): we thought this directory just existed: ${SOURCE}" find "$SOURCE" -type f -printf "%T@ SOURCE %p\n"; test -d "${OUTPUT}" \ && find "$OUTPUT" -type f -printf "%T@ OUTPUT %p\n"; } | sort -gr; } # - - - - - - - - - - - - function is_output_current () { # - - - - - - - - - - - - # # -- return a list of filenames in any directory which are (likely) # to be documents for TLDP # args 1: input file name # args 2: OUTPUTTREE # # -- there are two (probable) cases for the input, A) a single standalone # file or B) the main document source file in a dedicated directory; # below is a description of the logic.... # # Case A) single standalone file; we don't want to find all files from # the parent directory # # OBJ=/home/fred/vcs/LDP/LDP/howto/docbook/Software-Release-Practice-HOWTO.xml # OUTPUTTREE=/home/fred/wip/tldp/output # fn=Software-Release-Practice-HOWTO.xml # stem=Software-Release-Practice-HOWTO # parentdir=/home/fred/vcs/LDP/LDP/howto/docbook # parentstem=docbook # # Case B) main document source file in a dedicated directory; we do want # to look for all files starting from the parent directory # # OBJ=/home/fred/vcs/LDP/LDP/howto/docbook/Jabber-Server-Farming-HOWTO/Jabber-Server-Farming-HOWTO.xml # OUTPUTTREE=/home/fred/wip/tldp/output # fn=Jabber-Server-Farming-HOWTO.xml # stem=Jabber-Server-Farming-HOWTO # parentdir=/home/fred/vcs/LDP/LDP/howto/docbook/Jabber-Server-Farming-HOWTO # parentstem=Jabber-Server-Farming-HOWTO # local stem="${1}" && shift local OBJ="${1}" && shift local OUTPUTTREE="${1}" && shift local parentdir="${OBJ%/*}" local parentstem="${parentdir##*/}" startloc="$OBJ" test "${stem}" == "${parentstem}" \ && startloc="${parentdir}" local outdir="${OUTPUTTREE}/${stem}" result=$( find_sort_by_mtime "$startloc" "$outdir" ) \ || abort 1 "Could not retrieve files for $stem sorted by mtime." newest=$( head -n 1 <<<"$result" ) \ || abort 1 "Could not get first result for $stem files." DECISION=$( sed -e '/ OUTPUT /,$d' <<<"$newest" ) \ || abort 1 "Could not check if the SOURCE files are newest...." if test -z "$DECISION" ; then debug "${stem} No new source files" return 0 else # -- squawk about it, to the user # while read TS SOURCE FNAME ; do ts=$( date +%F-%T --date @"$TS" ) notice "${stem} updated file found ($ts): $FNAME" done <<< "$DECISION" return 1 fi } # - - - - - - - - - - - - generate_new_content () { # - - - - - - - - - - - - # local stem="${1}" && shift local OBJ="${1}" && shift local OUTPUTTREE="${1}" && shift #local makefile="${1}" && shift # -- make a stab at processing the document, but don't let the driver # stop just because a single document has an error # { { exec 2>&1; make -f "$MAKEFILE" OBJ="${OBJ}" DESTDIR="${OUTPUTTREE}"; test $? -eq 0 \ || gripe "FAILURE" "FAILURE producing all outputs from $OBJ" "FAILURE"; } | sed -ue "s/^/${stem} /"; } | command logger -s ${LOGGER_OPTS} -- } # - - - - - - - - - - - - list_orphaned_outputs () { # - - - - - - - - - - - - # local OUTPUTTREE="${1}" && shift local SOURCEDIRS="$@" { SOURCEDOCS=$( list_source_documents "${SOURCEDIRS}" | get_stem | sort ) OUTPUTDOCS=$( list_output_documents "${OUTPUTTREE}" | sort ) # -- list documents in source tree that do not have corresponding # output directory # diff -Nur -- <( printf "%s\n" "$SOURCEDOCS" ) <( printf "%s\n" "$OUTPUTDOCS" ) \ | tail -n +2 \ | grep -- '^-' \ | sed -e 's|^-|Unbuilt source document |'; # -- list documents in output tree that do not have corresponding # input directory # diff -Nur -- <( printf "%s\n" "$OUTPUTDOCS" ) <( printf "%s\n" "$SOURCEDOCS" ) \ | tail -n +2 \ | grep -- '^-' \ | sed -e 's|^-|Extra output document |'; } | command logger -s ${LOGGER_OPTS} --; } # - - - - - - - - - - - - produce_ldp_outputs () { # - - - - - - - - - - - - # local OUTPUTTREE="${1}" && shift local SOURCEDIRS="$@" list_source_documents "$SOURCEDIRS" | sort_by_stem | while read OBJ; do stem=$( printf "%s\n" "$OBJ" | get_stem ) notice "${stem} checking if rebuild required for $OBJ" # -- N.B. there's a dangerous looking ||: at the end of the line # calling the Makefile. This is intentional. The Makefile # may not be able to process the input successfully, but we # would like to continue, if possible, and process the next # available document. # is_output_current "${stem}" "$OBJ" "$OUTPUTTREE" \ || (generate_new_content "${stem}" "${OBJ}" "${OUTPUTTREE}" || :) done } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # main # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - test -d "${OUTPUTTREE}" \ || usage "Output directory must already exist, quitting." produce_ldp_outputs "$OUTPUTTREE" "$SOURCEDIRS" list_orphaned_outputs "$OUTPUTTREE" "$SOURCEDIRS" # -- end of file