html.mk

static html creation framework using make(1) and cpp(1)
Log | Files | Refs | README | LICENSE

commit 8694c58409ea3a0b33f5a5647456a699c483e53f
Author: Kyle Milz <milz@0x30.net>
Date:   Wed, 17 Mar 2021 00:04:28 +0000

html.mk: website creation framework

Diffstat:
AMakefile | 29+++++++++++++++++++++++++++++
Amk/html.mk | 171+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amk/obj.mk | 35+++++++++++++++++++++++++++++++++++
Amk/site.mk | 24++++++++++++++++++++++++
Amk/subdir.mk | 48++++++++++++++++++++++++++++++++++++++++++++++++
At/cp_happy.t | 45+++++++++++++++++++++++++++++++++++++++++++++
At/cp_nodest.t | 30++++++++++++++++++++++++++++++
At/cp_unhappy.t | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
At/dep_simple.t | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
At/srcs_missing.t | 21+++++++++++++++++++++
At/srcs_suffix.t | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
At/xliterate.t | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
12 files changed, 654 insertions(+), 0 deletions(-)

diff --git a/Makefile b/Makefile @@ -0,0 +1,29 @@ +# +# Sample Makefile, edit for your needs. +# As is it will create `index.html' from source file `index.in'. +# + +# For out of tree builds, which files need copying into the object +# directory. +# +#CP_OBJ += file_a.pdf +#CP_OBJ += file_b.log + +# Files to transform into HTML documents. If omitted the default `index.in' +# will be used. +# These MUST HAVE `.in' suffix! +# +#SRCS += doc_a.in +#SRCS += doc_b.in + +# What directories to enter after building the current one completes. +# +#SUBDIR += dir_a +#SUBDIR += dir_b +#SUBDIR += dir_c + +# Relative path to the top level directory. Must always be set. +# +TOP = . + +.include "${TOP}/mk/html.mk" diff --git a/mk/html.mk b/mk/html.mk @@ -0,0 +1,171 @@ +# +# html.mk +# This file must be included by all Makefiles. +# + +# +# These variables shoud not be modified here but set in Makefiles instead. +# - TOP: Directory containing the `mk' directory +# - SRCS: List of source files to transform into HTML documents +# +TOP ?= . +SRCS ?= index.in +INCLUDE = ${.CURDIR}/${TOP}/include + + +# Clear existing suffixes. +.SUFFIXES: + + +# Check SRCS array for nonexisting files. +.for src in ${SRCS} +. if !exists(${.CURDIR}/${src}) +all: + @>&2 echo "html.mk: file '${src}' in SRCS array does not exist." +.else +all: ${src:.in=.html} +. endif +.endfor + + +all: _SUBDIRUSE + +# +# Use the C preprocessor to turn input source files into HTML. +# +# -I: global files and targets generated in object directory can be included +# -P: the files being preprocessed are not C +# -o: the final HTML files are written in the object directory +# +.SUFFIXES: .in .html +.in.html: + cpp -I${INCLUDE} -I${.OBJDIR} -P -o ${.OBJDIR}/$@ $< + + +# +# Use the C preprocessor to generate dependency files from input source +# files. +# +# -I: XXX +# -M: generate Makefile dependency target instead of preprocessing +# -MG: assume missing files are generated files +# -MF: write dependency list to .depend file where make will look for it +# -MT: override target name +# +.SUFFIXES: .depend +.in.depend: + cpp -I${INCLUDE} -M -MG -MF $@ -MT ${@:.depend=.html} $< + +# Create the final `.depend' file from all of the individual dependency files. +.if !empty(SRCS) +depend: ${SRCS:.in=.depend} _SUBDIRUSE + cat ${.ALLSRC} > .depend +.endif + + +# +# Generate `*_html' HTML fragments that use <img> tag to link to images. +# +.SUFFIXES: .jpg .jpg_html +.SUFFIXES: .png .png_html +.jpg.jpg_html .png.png_html: + @# Copy original image to object dir with `_cp' appended to file name. + @# Make gets confused when two files have the same names + @# in different directories. + cp $? ${.OBJDIR}/${<F:R}_cp.${<F:E} + + @# Generate thumbnail of image with `_thumb' appended to file name. + convert -resize 710x $? ${<F:R}_thumb.${<F:E} + + @# Create HTML fragment that uses the thumbnail as a base image + @# that is a link to the full size (copied) image, like this: + @# + @# <a href="pic_cp.jpg"><img src="pic_thumb.jpg" + @# + @# NOTE: The <img> tag is not closed! You must close it, hopefully with: + @# alt="description of image for blind people" /></a> + echo "\t<a href='${<F:R}_cp.${<F:E}'><img src='${<F:R}_thumb.${<F:E}' " > $@ + +# +# Generate `*_html' html fragments that link to videos using <video> tag. +# +.SUFFIXES: .mp4 .mp4_html +.SUFFIXES: .mov .mov_html +.mp4.mp4_html .mov.mov_html: + @# Copy video from source to object directory. + cp $? ${.OBJDIR}/ + + @# Generate html code snippet using HTML5 video tag to show video. + echo "\t<video controls>" > $@ + echo "\t\t<source src='${<F}' type='video/mp4'>" >> $@ + echo "\t\tYour browser does not support the video tag." >> $@ + echo "\t</video>" >> $@ + + +# +# Transliterate the contents of various file types when they coincidentally +# (not purposefully) contain HTML markup or C preprocessor directives. +# +# - turn '<' and '>' into HTML named character references so they are +# displayed and not parsed +# - turn '\' line continuation into HTML named character reference so they +# do not get removed by the HTML parser +# - turn '#' into HTML named character reference so the preprocessor doesn't +# try and operate on it. +# +# Note dependency file names must be unique for this inference because all +# target files will have .xliterate suffix. +# +.SUFFIXES: .c .html .in .txt +.SUFFIXES: .xliterate +.c.xliterate .html.xliterate .in.xliterate .txt.xliterate: + sed \ + -e "s/</\&lt;/g" \ + -e "s/>/\&gt;/g" \ + -e "s/\\\/\&bsol;/" \ + -e "s/#/\&num;/g" \ + $< > $@ + + +# +# Make an HTML time attribute for direct inclusion in HTML. Populate the +# "datetime" property with an appropriatly formatted date. +# Use full names for day of week and month instead of default abbreviated ones. +# +FMT="+%A %B %e %H:%M:%S %Z %Y" +_timestamp.gen: index.in + echo "<time datetime='`date +%Y-%m-%d`'>`date ${FMT}`</time>" > $@ + +# +# Generate a banner of the current directory for inclusion in HTML. +# +_dirbanner_html: + @echo " <pre class='small'>" > ${.OBJDIR}/$@ + here=`cd ${.CURDIR}; basename $$PWD`; \ + banner $$here >> ${.OBJDIR}/$@; + @echo "</pre>" >> ${.OBJDIR}/$@ + +# +# Plain file copy. +# +.for file in ${CP_OBJ} +.if !exists(${.CURDIR}/${file}) +all: + @>&2 echo "html.mk: file '${file}' in CP_OBJ array does not exist." +.else + +# +# We can get away with using the same file name here and make still being +# able to distinguish the files because we use absolute paths. +# +${.OBJDIR}/${file}: ${.CURDIR}/${file} + cp ${.CURDIR}/${file} $@ + +all: ${.OBJDIR}/${file} + +.endif +.endfor + +.include "obj.mk" +.include "site.mk" +.include "subdir.mk" diff --git a/mk/obj.mk b/mk/obj.mk @@ -0,0 +1,35 @@ +# +# Originally taken from OpenBSD /usr/share/mk/bsd.obj.mk. +# + +_SUBDIRUSE: + +obj! _SUBDIRUSE + @cd ${.CURDIR}; \ + here=`/bin/pwd`/; bsdsrcdir=`cd ${TOP}; /bin/pwd`; \ + subdir=$${here#$${bsdsrcdir}/}; \ + if [[ $$here != $$subdir ]]; then \ + dest=${DESTDIR}/$$subdir ; \ + echo "$$here/obj -> $$dest"; \ + if [[ ! -L obj || `readlink obj` != $$dest ]]; \ + then \ + [[ -e obj ]] && rm -rf obj; \ + ln -sf $$dest obj; \ + fi; \ + if [[ -d ${DESTDIR} ]]; then \ + [[ -d $$dest ]] || mkdir -p $$dest; \ + else \ + if [[ -e ${DESTDIR} ]]; then \ + echo "${DESTDIR} is not a directory"; \ + else \ + echo "${DESTDIR} does not exist"; \ + fi; \ + fi; \ + else \ + dest=$$here/obj ; \ + if [[ ! -d obj ]]; then \ + echo "making $$dest" ; \ + mkdir -p $$dest; \ + $$SETOWNER $$dest; \ + fi ; \ + fi; diff --git a/mk/site.mk b/mk/site.mk @@ -0,0 +1,24 @@ +# +# Site specific configuration. +# +# The directory to place out of tree builds. +DESTDIR ?= /tmp/html.mk + +# +# Helper targets for misc tasks. +# + +# +# Check files for trailing white space using `egrep`. +# -H: show matched filename +# -n: shows matched line in file +# '[[:space:]]+$': matches at least one space or tab until end of line +# +trailingspace: _SUBDIRUSE + ! egrep -Hn '[[:space:]]+$$' ${.CURDIR}/${SRCS} + +# +# Check spelling of all pages using `spell_ok' file for exceptions. +# +spell: _SUBDIRUSE + spell +${.CURDIR}/${TOP}/spell_ok ${.CURDIR}/${SRCS} diff --git a/mk/subdir.mk b/mk/subdir.mk @@ -0,0 +1,48 @@ +# +# Originally taken from OpenBSD /usr/share/mk/bsd.subdir.mk. +# + +# Make sure this is defined +SKIPDIR?= + +_SUBDIRUSE: .USE +.if defined(SUBDIR) + @for entry in ${SUBDIR}; do \ + _newdir_="$${entry}"; \ + if test X"${_THISDIR_}" = X""; then \ + _nextdir_="$${_newdir_}"; \ + else \ + _nextdir_="$${_THISDIR_}/$${_newdir_}"; \ + fi; \ + _makefile_spec_=""; \ + subskipdir=''; \ + for skipdir in ${SKIPDIR}; do \ + subentry=$${skipdir#$${entry}}; \ + if [ X$${subentry} != X$${skipdir} ]; then \ + if [ X$${subentry} = X ]; then \ + echo "($${_nextdir_} skipped)"; \ + break; \ + fi; \ + subskipdir="$${subskipdir} $${subentry#/}"; \ + fi; \ + done; \ + if [ X$${skipdir} = X -o X$${subentry} != X ]; then \ + echo "===> $${_nextdir_}"; \ + ${MAKE} -C ${.CURDIR}/$${_newdir_} \ + SKIPDIR="$${subskipdir}" \ + $${_makefile_spec_} _THISDIR_="$${_nextdir_}" \ + ${MAKE_FLAGS} \ + ${.TARGET:S/^real//}; \ + fi; \ + done +.endif + + +.for t in all depend obj spellcheck trailingspace +. if !target($t) +$t: _SUBDIRUSE +. endif +.endfor +.if !target(clean) && empty(.TARGETS:Mcleandir) +clean: _SUBDIRUSE +.endif diff --git a/t/cp_happy.t b/t/cp_happy.t @@ -0,0 +1,45 @@ +# +# Happy path for CP, obj/ and file exists. +# +use Modern::Perl; +use Test::Cmd; +use Test::File; +use Test::More tests => 9; + + +my $cmd = Test::Cmd->new( prog => '/usr/bin/make', workdir => '' ); +my $html_mk = $cmd->here . '/mk/html.mk'; + +$cmd->write( 'copy.txt', 'some stuff here' ); +$cmd->subdir( 'destdir' ); +$cmd->write( 'Makefile', <<EOF ); +CP_OBJ = copy.txt +DESTDIR = destdir +SRCS = + +.include "$html_mk" +EOF + +# +# Create obj/ directory. +# +my $o = ".*//obj -> destdir/"; + +$cmd->run( args => 'obj', chdir => $cmd->curdir ); +like( $cmd->stdout, qr{$o}, 'make obj stdout' ); +is( $cmd->stderr, '', 'make obj stderr' ); +is( $? >> 8, 0, 'make obj exit status' ); + +# +# Default make target should copy the file. +# +$o = "cp .*/copy.txt .*/obj/copy.txt"; + +$cmd->run( chdir => $cmd->curdir ); +like( $cmd->stdout, qr{$o}, 'make stdout' ); +is( $cmd->stderr, '', 'make stderr' ); +is( $? >> 8, 0, 'make exit status' ); + +file_exists_ok( $cmd->workdir . "/copy.txt" ); +file_exists_ok( $cmd->workdir . "/obj/copy.txt" ); +file_contains_like( $cmd->workdir . '/obj/copy.txt', qr/some stuff here/ ); diff --git a/t/cp_nodest.t b/t/cp_nodest.t @@ -0,0 +1,30 @@ +# +# CP file when not building into obj/. +# +use Modern::Perl; +use Test::Cmd; +use Test::File; +use Test::More tests => 5; + + +my $cmd = Test::Cmd->new( prog => '/usr/bin/make', workdir => '' ); +my $html_mk = $cmd->here . "/mk/html.mk"; + +$cmd->write( 'cp_file.txt', 'some file contents' ); +$cmd->write( 'Makefile', <<EOF ); +CP_OBJ = cp_file.txt +SRCS = + +.include "$html_mk" +EOF + +# +# Silence is OK here I think. +# +$cmd->run( chdir => $cmd->curdir ); +is( $cmd->stdout, '', 'make stdout' ); +is( $cmd->stderr, '', 'make stderr' ); +is( $? >> 8, 0, 'make exit status' ); + +file_exists_ok( $cmd->workdir . '/cp_file.txt' ); +file_contains_like( $cmd->workdir . '/cp_file.txt', qr/some file contents/ ); diff --git a/t/cp_unhappy.t b/t/cp_unhappy.t @@ -0,0 +1,54 @@ +# +# CP_OBJ nonexistent file and file that is a directory. +# +use Modern::Perl; +use Test::Cmd; +use Test::More tests => 9; + + +my $cmd = Test::Cmd->new( prog => '/usr/bin/make', workdir => '' ); +my $html_mk = $cmd->here . "/mk/html.mk"; + +# +# 1) CP_OBJ nonexistent file. +# + +my $e = "html.mk: file 'does_not_exist' in CP_OBJ array does not exist."; + +$cmd->write( 'Makefile', <<EOF ); +CP_OBJ = does_not_exist +SRCS = + +.include "$html_mk" +EOF + +$cmd->run( chdir => $cmd->curdir ); + +is( $cmd->stdout, '', 'make stdout' ); +like( $cmd->stderr, qr{$e}, 'make stderr' ); +is( $? >> 8, 0, 'make exit status' ); + +# +# 2) CP_OBJ directory, obj exists +# +my $o = "cp .*/cp_dir .*/obj/cp_dir"; +$e = 'cp: .*/cp_dir is a directory \(not copied\)'; + +$cmd->subdir( 'destdir', 'cp_dir' ); +$cmd->write( 'Makefile', <<EOF ); +CP_OBJ = cp_dir +DESTDIR = destdir +SRCS = + +.include "$html_mk" +EOF + +$cmd->run( args => 'obj', chdir => $cmd->curdir ); +like( $cmd->stdout, qr{obj -> destdir}, 'make obj stdout' ); +is( $cmd->stderr, '', 'make obj stderr' ); +is( $? >> 8, 0, 'make obj exit status' ); + +$cmd->run( chdir => $cmd->curdir ); +like( $cmd->stdout, qr{$o}, 'make stdout' ); +like( $cmd->stderr, qr{$e}, 'make stderr' ); +is( $? >> 8, 2, 'make exit status' ); diff --git a/t/dep_simple.t b/t/dep_simple.t @@ -0,0 +1,70 @@ +# +# make depend works on single file. +# +use Modern::Perl; +use Test::File; +use Test::Cmd; +use Test::More tests => 19; + + +my $cmd = Test::Cmd->new( prog => '/usr/bin/make', workdir => '' ); +my $html_mk = $cmd->here . "/mk/html.mk"; + +$cmd->write( 'index.in', '' ); +$cmd->write( 'Makefile', <<EOF ); +.include "$html_mk" +EOF + +# +# 1) `make depend' +# +$cmd->run( args => 'depend', chdir => $cmd->curdir ); + +my $o = "cpp .* -MF index.depend -MT index.html index.in"; +like( $cmd->stdout, qr{$o}, 'make depend stdout' ); +is( $cmd->stderr, '', 'make depend stderr' ); +is( $? >> 8, 0, 'make depend exit status' ); + +# XXX: index.depend and .depend should have identical contents. +file_exists_ok( $cmd->workpath . '/index.depend' ); +file_exists_ok( $cmd->workpath . '/.depend' ); + +# +# 2) `make' +# +$cmd->run( chdir => $cmd->curdir ); + +$o = "cpp .* -P -o .*index.html .*index.in"; +like( $cmd->stdout, qr{$o}, 'make stdout' ); +is( $cmd->stderr, '', 'make stderr' ); +is( $? >> 8, 0, 'make exit status' ); +file_exists_ok( $cmd->workpath . '/index.html' ); + +# +# 3) Another `make' does nothing. +# +$cmd->run( chdir => $cmd->curdir ); +is( $cmd->stdout, '', 'make stdout' ); +is( $cmd->stderr, '', 'make stderr' ); +is( $? >> 8, 0, 'make exit status' ); + +# +# 4) Touch the source file and re-run `make'. +# +$cmd->write( 'index.in', '' ); +$cmd->run( chdir => $cmd->curdir ); + +like( $cmd->stdout, qr{$o}, 'make stdout' ); +is( $cmd->stderr, '', 'make stderr' ); +is( $? >> 8, 0, 'make exit status' ); + +# +# 5) Remove index.html and re-run `make'. +# +my $f = $cmd->workdir . "/index.html"; +is (unlink( $f ), 1, 'unlink success' ); + +$cmd->run( chdir => $cmd->curdir ); +like( $cmd->stdout, qr{$o}, 'make stdout' ); +is( $cmd->stderr, '', 'make stderr' ); +is( $? >> 8, 0, 'make exit status' ); diff --git a/t/srcs_missing.t b/t/srcs_missing.t @@ -0,0 +1,21 @@ +# +# The default SRCS file is missing. +# +use Modern::Perl; +use Test::Cmd; +use Test::More tests => 3; + + +my $cmd = Test::Cmd->new( prog => '/usr/bin/make', workdir => '' ); +my $html_mk = $cmd->here . "/mk/html.mk"; + +$cmd->write( 'Makefile', <<EOF ); +.include "$html_mk" +EOF + +my $e = "html.mk: file 'index.in' in SRCS array does not exist."; + +$cmd->run( chdir => $cmd->curdir ); +is( $cmd->stdout, '', 'make stdout' ); +like( $cmd->stderr, qr{$e}, 'make stderr' ); +is( $? >> 8, 0, 'make exit status' ); diff --git a/t/srcs_suffix.t b/t/srcs_suffix.t @@ -0,0 +1,65 @@ +# +# No and strange file suffixes in SRCS array. +# These are different cases because make behaviour is dependant on suffixes. +# +use Modern::Perl; +use Test::Cmd; +use Test::More tests => 12; + +my $cmd = Test::Cmd->new( prog => '/usr/bin/make', workdir => '' ); +my $html_mk = $cmd->here . "/mk/html.mk"; + +# +# 1) File with no suffix in SRCS array and does not exist +# +$cmd->write( 'Makefile', <<EOF ); +SRCS = no_file_suffix + +.include "$html_mk" +EOF + +# This is an ok error message. +my $e = "html.mk: file 'no_file_suffix' in SRCS array does not exist."; + +$cmd->run( chdir => $cmd->curdir ); +is( $cmd->stdout, '', 'make stdout' ); +like( $cmd->stderr, qr{$e}, 'make stderr' ); +is( $? >> 8, 0, 'make exit status' ); + +# +# 1a) File with no suffix in SRCS array but file exists. +# XXX Strange behaviour... this should be an error with a message. +# +$cmd->write( 'no_file_suffix', '' ); + +$cmd->run( chdir => $cmd->curdir ); +is( $cmd->stdout, '', 'make stdout' ); +is( $cmd->stderr, '', 'make stderr' ); +is( $? >> 8, 0, 'make exit status' ); + +# +# 2) File with bad suffix in SRCS array that does not exist. +# +$cmd->write( 'Makefile', <<EOF ); +SRCS = bad.sfx + +.include "$html_mk" +EOF + +$e = "html.mk: file 'bad.sfx' in SRCS array does not exist."; + +$cmd->run( chdir => $cmd->curdir ); +is( $cmd->stdout, '', 'make stdout' ); +like( $cmd->stderr, qr{$e}, 'make stderr' ); +is( $? >> 8, 0, 'make exit status' ); + +# +# 2b) File with bad suffix in SRCS array but file exists. +# XXX Strange behaviour... this should be an error with a message. +# +$cmd->write( 'bad.sfx', '' ); + +$cmd->run( chdir => $cmd->curdir ); +is( $cmd->stdout, '', 'make stdout' ); +is( $cmd->stderr, '', 'make stderr' ); +is( $? >> 8, 0, 'make exit status' ); diff --git a/t/xliterate.t b/t/xliterate.t @@ -0,0 +1,62 @@ +# +# #include "foo.xliterate" works. +# +use Modern::Perl; +use Test::Cmd; +use Test::File::Contents; +use Test::More tests => 7; + + +my $cmd = Test::Cmd->new( prog => '/usr/bin/make', workdir => '' ); +my $html_mk = $cmd->here . "/mk/html.mk"; + +$cmd->write( 'c_frag.c', '#include <stdio.h>' ); +$cmd->write( 'html_frag.html', '<head><body> <img /> </body>' ); +$cmd->write( 'in_frag.xliterate', 'line one \n +line two' ); +$cmd->write( 'txt_frag.xliterate', 'whatever' ); + +$cmd->write( 'index.in', <<EOF ); +#include "c_frag.xliterate" +-- +#include "html_frag.xliterate" +-- +#include "in_frag.xliterate" +-- +#include "txt_frag.xliterate" +EOF + +$cmd->write( 'Makefile', <<EOF ); +.include "$html_mk" +EOF + +# make depend +$cmd->run( args => 'depend', chdir => $cmd->curdir ); +like( $cmd->stdout, qr/.*/, 'make stdout' ); +is( $cmd->stderr, "", 'make stderr' ); +is( $? >> 8, 0, 'make exit status' ); + +# make +$cmd->run( chdir => $cmd->curdir ); +like( $cmd->stdout, qr/sed .*/, 'make stdout' ); +is( $cmd->stderr, "", 'make stderr' ); +is( $? >> 8, 0, 'make exit status' ); + +# +# Compare index.html with known good output. +# +my $good = <<'EOF'; + + +&num;include &lt;stdio.h&gt; +-- +&lt;head&gt;&lt;body&gt; &lt;img /&gt; &lt;/body&gt; +-- +line one \n +line two +-- +whatever +EOF + +file_contents_eq_or_diff( $cmd->workdir . "/index.html", $good, + 'index.html contents transliterated' );