X-Git-Url: https://git.ladys.computer/Shushe/blobdiff_plain/12fbd559e25c8ac456254c4f4a69d53905045ef8..c4c2cfb24adfa62c6b4cbd819b7efe80438d8d08:/GNUmakefile
diff --git a/GNUmakefile b/GNUmakefile
index 5b341d6..99e94cf 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -6,7 +6,7 @@ SHELL = /bin/sh
# ━ § BASIC INFORMATION ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
override define makefileinfo
╭────────────────────────────╮
-╔╡ ⁌ ⛩️📰 书社 ∷ GNUmakefile ╞═════════════════════════════════╗
+╔╡ ⁌ ⛩📰 书社 ∷ GNUmakefile ╞═════════════════════════════════╗
║╰────────────────────────────╯ ║
╟┬ ¶ Prerequisites ───────────────────────────────────────────┬╢
║│ │║
@@ -19,16 +19,16 @@ override define makefileinfo
║│ │║
║│ • awk │║
║│ • cat │║
+║│ • cd │║
║│ • cksum │║
║│ • cp │║
║│ • date │║
-║│ • echo │║
+║│ • diff │║
║│ • file │║
║│ • find │║
║│ • grep │║
║│ • git (optional) │║
║│ • ln │║
-║│ • ls │║
║│ • mkdir │║
║│ • mv │║
║│ • od │║
@@ -37,13 +37,12 @@ override define makefileinfo
║│ • rm │║
║│ • sed │║
║│ • sleep │║
-║│ • stat │║
║│ • test │║
║│ • touch │║
║│ • tr │║
║│ • uuencode │║
║│ • uudecode │║
-║│ • xargs (requires support for `-0´) │║
+║│ • xargs │║
║│ • xmlcatalog (provided by libxml2) │║
║│ • xmllint (provided by libxml2) │║
║│ • xsltproc (provided by libxslt) │║
@@ -84,7 +83,7 @@ override define makefileinfo
║╰────────────────────────────────────────────────────────────╯║
╟┬ ¶ Copyright & License ─────────────────────────────────────┬╢
║│ │║
-║│ Copyright © 2023–2024 Lady [@ Lady’s Computer]. │║
+║│ Copyright © 2023–2024 Lady [@ Ladys Computer]. │║
║│ │║
║│ This Source Code Form is subject to the terms of the │║
║│ Mozilla Public License, v 2.0. If a copy of the M·P·L was │║
@@ -101,16 +100,16 @@ endef
# If these are not installed on your computer, or you need to use a different implementation, you can override the appropriate variable.
AWK := awk
CAT := cat
+CD := cd
CKSUM := cksum
CP := cp
DATE := date
-ECHO := echo
+DIFF := diff
FILE := file
FIND := find
GIT := git
GREP := grep
LN := ln
-LS := ls
MKDIR := mkdir
MV := mv
OD := od
@@ -119,7 +118,6 @@ PRINTF := printf
RM := rm
SED := sed
SLEEP := sleep
-STAT := stat
TEST := test
TOUCH := touch
TR := tr
@@ -151,7 +149,7 @@ SRCDIR := sources
# Multiple directories can be given so long as files with the same name do not exist in each.
#
# These can be inside of `SRCDIR´ directories if desired.
-INCLUDEDIR := sources/includes
+INCLUDEDIR := $(foreach dir,$(SRCDIR),$(dir)/includes)
# The directory in which to generate temporary buildfiles.
#
@@ -163,7 +161,7 @@ BUILDDIR := build
# This variable is also used by the archiving `MODE´.
DESTDIR := public
-# The location of this Makefile (and related ⛩️📰 书社 files), relative to the current working directory.
+# The location of this Makefile (and related ⛩📰 书社 files), relative to the current working directory.
#
# By default, this is inferred from the variable `MAKEFILE_LIST´.
#
@@ -175,8 +173,8 @@ THISDIR := $(patsubst %/,%,$(dir $(lastword $(MAKEFILE_LIST))))
# By default, `find´ will ignore files which begin with a period and those which are likely to cause problems for `make´.
EXTRAFINDRULES :=
EXTRAFINDINCLUDERULES :=
-FINDRULES := ! '(' '(' -name '[.-]*' -o -name '*[][*?:|$$%\#\\; ]*' -o -name '*"{*' -o -name '*}"*' -o -name '*[)]' ')' -a -prune ')'$(if $(EXTRAFINDRULES), -a $(EXTRAFINDRULES),)
-FINDINCLUDERULES := $(FINDRULES)$(if $(EXTRAFINDINCLUDERULES), -a $(EXTRAFINDINCLUDERULES),)
+FINDRULES := ! '(' '(' -name '[.-]*' -a ! -name '.' -o -name '*[][*?:|$$%\#\\; ]*' -o -name '*[)]' ')' -a -prune ')'$(if $(EXTRAFINDRULES), -a '(' $(EXTRAFINDRULES) ')',)
+FINDINCLUDERULES := $(FINDRULES)$(if $(EXTRAFINDINCLUDERULES), -a '(' $(EXTRAFINDINCLUDERULES) ')',)
# The list of magic files to use when determining media types.
#
@@ -197,17 +195,23 @@ EXTRATRANSFORMS :=
TRANSFORMS := $(sort $(patsubst ./%,%,$(wildcard $(THISDIR)/transforms/*.xslt)) $(EXTRATRANSFORMS))
# List of types which should be treated as X·M·L.
-XMLTYPES := application/xml text/xml
+#
+# Types which start with a `+´ are suffixes and match any type which
+# ends accordingly.
+XMLTYPES := application/xml text/xml +xml
+
+# A program to run on basic X·M·L files after they are transformed.
+FINALIZE := $(XMLLINT) --nonet --nsclean
ifdef GIT
ifneq ($(wildcard $(THISDIR)/.git),)
-# A description of the current git revision of ⛩️📰 书社.
-THISREV := $(shell cd $(THISDIR); $(GIT) describe 2> /dev/null || $(GIT) rev-parse HEAD 2> /dev/null || true)
+# A description of the current git revision of ⛩📰 书社.
+THISREV := $(shell $(CD) $(THISDIR); $(GIT) describe 2>>/dev/null || $(GIT) rev-parse HEAD 2>>/dev/null || :)
endif
ifneq ($(wildcard .git),)
# A description of the current git revision of the working directory.
-SRCREV := $(shell $(GIT) describe 2> /dev/null || $(GIT) rev-parse HEAD 2> /dev/null || true)
+SRCREV := $(shell $(GIT) describe 2>>/dev/null || $(GIT) rev-parse HEAD 2>>/dev/null || :)
endif
endif
@@ -221,17 +225,31 @@ endif
#
# • ‹ urn:fdc:ladys.computer:20231231:Shu1She4:mode:archive ›:
# Generates archive files from parse results.
-#
-# • ‹ urn:fdc:ladys.computer:20231231:Shu1She4:mode:paged ›:
-# Generates paginated files from parse results.
MODE := urn:fdc:ladys.computer:20231231:Shu1She4:mode:default
+# Set to a non·empty value to silence informative messages.
+QUIET :=
+
# Set to a non·empty value to print all commands as they run.
VERBOSE :=
+# Posix locale information.
+LC_ALL := C
+export LC_ALL
+
+# Posix timezone information.
+TZ := UTC0
+export UTC0
+
# The default target for this makefile.
.DEFAULT_GOAL := all
+# Flags for use when calling the shell.
+#
+# `-e` enables early exit from invocations of multiple commands after
+# the first failure.
+.SHELLFLAGS := -c -e
+
# ━ § BEGIN SHARED MAKE·FILE CONSTRUCTS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# ─ ¶ Non‐Recipe Variable Definitions ─────────────────────────────────
@@ -254,35 +272,33 @@ override comma := ,
# (callable) Quote the given string for use within shell calls.
override quote = '$(subst ','"'"',$1)'
-# Outputs an `@´ to silence rules, unless `VERBOSE´ is nonempty.
-override silent := $(if $(VERBOSE),,@)
+# ─ ¶ Recipe Variable Definitions ─────────────────────────────────────
-# (callable) Get the modified time of the provided file.
-#
-# This is messy; there is no portable way of using `stat´.
-#
-# ☡ This variable creates a subshell every time it is computed.
-override modtime = $(shell if $(STAT) --version 2> /dev/null | $(GREP) -q GNU; then $(DATE) -u -d "@$$(TZ= $(STAT) -c '%Y' $(call quote,$1))" '+%Y-%m-%dT%H:%M:%SZ'; else TZ= $(STAT) -f '%Sm' -t '%Y-%m-%dT%H:%M:%SZ' $(call quote,$1); fi)
+# Outputs an `@´ to silence rules, unless `VERBOSE´ is non·empty.
+override silent := $(if $(VERBOSE),,@)
-# ─ ¶ Recipe Variable Definitions ─────────────────────────────────────
+# (callable) Only perform the rule if `QUIET` is not set.
+override inform = $(if $(QUIET),,@$1)
# (callable) Check to see if the given directory exists and create it if not.
override ensuredirectory = if $(TEST) ! -d $(call quote,$1); then $(MKDIR) -p $(call quote,$1); fi
-# (callable) Test if the provided xpath expression matches the provided document.
-override xpath = $(XMLLINT) --xpath $(call quote,$1) $(call quote,$2) > /dev/null 2> /dev/null
+# Quote standard input in such a way that piping it to xargs will result in it being processed as a single argument.
+#
+# If standard input ends in a newline, it is stripped; all other newlines are preserved.
+override xargsquote = $(SED) $(call quote,s/'/'"'"'/g;s/^/'/;s/$$/'/;$$!s/$$/\\/)
-# (callable) Extract the value of the text nodes in the provided X·M·L document and print them to `stdout´.
-override extracttext = $(PRINTF) '%s' '' | $(XSLTPROC) --nonet --novalid - $(call quote,$1)
+# Quote standard input in such a way that piping it to xargs will result in each line being processed as a single argument.
+override xargsmultiquote = $(SED) $(call quote,s/'/'"'"'/g;s/^/'/;s/$$/'/)
-# (callable) List the files in the provided directory which are associated with the provided filename, one per line.
-override associatedfiles = $(LS) -1 $(call quote,$1) | $(SED) '/.*"{.*}".*/!d;s/^\(.*\)"{.*}"\(.*\)$$/\1\2|&/' | $(GREP) -F -e $(call quote,$2|) | $(SED) 's/^.*|//'
+# (callable) Test if the provided xpath expression matches the provided document.
+override xpath = $(XMLLINT) --noent --nonet --xpath $(call quote,$1) $(call quote,$2) >>/dev/null 2>>/dev/null
-# (callable) Remove files associated with the provided file.
-override removeassociatedfiles = ASSOCIATED="$$($(call associatedfiles,$(dir $1),$(notdir $1)) | $(TR) '\n' ' ')"; if $(TEST) -n "$$ASSOCIATED"; then cd $(call quote,$(dir $1)) && $(PRINTF) '%s\n' "$$ASSOCIATED" | $(TR) ' ' '\n' | $(SED) $(call quote,/^$$/d;s/'/'"'"'/g;s/^/'/;s/$$/'/) | $(TR) '\n' ' ' | $(XARGS) $(RM); fi
+# (callable) Extract the value of the text nodes in the provided X·M·L document and print them to `stdout´.
+override extracttext = $(PRINTF) '%s' '' | $(XSLTPROC) --nonet --novalid --nomkdir --nowrite - $(call quote,$1)
# (callable) Process the provided transformation result and output the result to the provided location, given the provided relative path.
-override processresultto = if $(call xpath,/*[local-name()="raw-text" and namespace-uri()="urn:fdc:ladys.computer:20231231:Shu1She4"],$1); then $(call extracttext,$1) > $(call quote,$2); $(call removeassociatedfiles,$2); elif $(call xpath,/*[local-name()="base64-binary" and namespace-uri()="urn:fdc:ladys.computer:20231231:Shu1She4"],$1); then $(call extracttext,$1) | $(TR) -d '\t\n\f\r ' | $(XARGS) -0 $(PRINTF) 'begin-base64 644 -\n%s\n====\n' | $(UUDECODE) -o /dev/stdout > $(call quote,$2); $(call removeassociatedfiles,$2); elif $(call xpath,/*[local-name()="archive" and namespace-uri()="urn:fdc:ladys.computer:20231231:Shu1She4"],$1); then $(MAKE) -f $(call quote,$(abspath $(THISDIR)/GNUmakefile)) $(call quote,$2) NAME=$(call quote,$3) SRC=$(call quote,$1) BUILDDIR=$(call quote,$(BUILDDIR)/archive/$3) DESTDIR=$(call quote,$(patsubst %/,%,$(dir $2))) MODE='urn:fdc:ladys.computer:20231231:Shu1She4:mode:archive'; $(call removeassociatedfiles,$2); elif $(call xpath,//*[local-name()="page" and namespace-uri()="urn:fdc:ladys.computer:20231231:Shu1She4"],$1); then $(MAKE) -f $(call quote,$(abspath $(THISDIR)/GNUmakefile)) $(call quote,$2) NAME=$(call quote,$3) SRC=$(call quote,$1) BUILDDIR=$(call quote,$(BUILDDIR)/paged/$3) DESTDIR=$(call quote,$(patsubst %/,%,$(dir $2))) MODE='urn:fdc:ladys.computer:20231231:Shu1She4:mode:paged'; else $(XMLLINT) --nsclean $(call quote,$1) > $(call quote,$2); $(call removeassociatedfiles,$2); fi
+override processresultto = if $(call xpath,/*[local-name()="raw-text" and namespace-uri()="urn:fdc:ladys.computer:20231231:Shu1She4"],$1); then $(call extracttext,$1) >|$(call quote,$2); elif $(call xpath,/*[local-name()="base64-binary" and namespace-uri()="urn:fdc:ladys.computer:20231231:Shu1She4"],$1); then { $(PRINTF) '%s\n' 'begin-base64 644 -'; $(call extracttext,$1) | $(TR) -d '\t\n\f\r '; $(PRINTF) '\n%s\n' '===='; } | $(UUDECODE) -o /dev/stdout >|$(call quote,$2); elif $(call xpath,/*[local-name()="archive" and namespace-uri()="urn:fdc:ladys.computer:20231231:Shu1She4"],$1); then $(MAKE) -f $(call quote,$(abspath $(THISDIR)/GNUmakefile)) NAME=$(call quote,$3) SRC=$(call quote,$1) BUILDDIR=$(call quote,$(BUILDDIR)/archive/$3) DESTDIR=$(call quote,$(patsubst %/,%,$(dir $2))) MODE='urn:fdc:ladys.computer:20231231:Shu1She4:mode:archive' $(call quote,$2); else $(FINALIZE) $(call quote,$1) >|$(call quote,$2); fi
# ━ § BEGIN DEFAULT MAKE·FILE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
@@ -297,13 +313,19 @@ ifeq ($(MODE),urn:fdc:ladys.computer:20231231:Shu1She4:mode:default)
# Calling this variable is useful when a given target should be updated whenever its list of prerequisites changes in addition to whenever there is a change to one of its prerequisites.
#
# ☡ This variable creates at least one subshell every time it is computed.
-override diffprereqs = $(if $(subst $(shell $(CAT) $(call quote,$(BUILDDIR)/lastprereqs/$1) 2> /dev/null || true),,$2),$2 FORCE$(and $(shell $(call ensuredirectory,$(BUILDDIR)/lastprereqs) && $(PRINTF) '%s\n' $(call quote,$2) > $(BUILDDIR)/lastprereqs/$1),),$2)
+override diffprereqs = $(if $(subst $(shell $(CAT) $(call quote,$(BUILDDIR)/lastprereqs/$1) 2>>/dev/null || :),,$2),$2 FORCE$(and $(shell $(call ensuredirectory,$(BUILDDIR)/lastprereqs) && $(PRINTF) '%s\n' $(call quote,$2) >|$(BUILDDIR)/lastprereqs/$1),),$2)
+
+# (callable) Escape special characters for use in X·M·L.
+override xmlesc = $(subst >,>,$(subst <,<,$(subst &,&,$1)))
+
+# (callable) Escape special characters for use in X·M·L attributes.
+override attresc = $(subst ",",$(call xmlesc,$1))
# (callable) Escape special characters for use in sed regular expressions.
override sedesc = $(subst /,[/],$(subst $$,\$$,$(subst *,\*,$(subst .,\.,$(subst [,\[,$(subst ^,\^,$(subst \,\\,$1)))))))
# The command to use for percent‐decoding.
-override perdeccmd := $(SED) 's/|/%7C/g;s/[\]/%5C/g;s/%[0-9A-Fa-f]\{2\}/|&|/g' | $(TR) '|' '\n' | $(SED) '/^%[0-9A-Fa-f]\{2\}$$/!s/%/|%25|/' | $(TR) '|' '\n' | $(AWK) '$$0!~/%/{printf "%s",$$0}/%/{d="0123456789ABCDEF";v=substr(toupper($$0),2,2);printf "\\%04o",(index(d,substr(v,1,1))-1)*16+index(d,substr(v,2,1))-1}' | $(XARGS) -0 $(PRINTF) '%b'
+override perdeccmd := $(SED) 's/|/%7C/g;s/[\]/%5C/g;s/%[0123456789ABCDEFabcdef]\{2\}/|&|/g' | $(TR) '|' '\n' | $(SED) '/^%[0123456789ABCDEFabcdef]\{2\}$$/!s/%/|%25|/' | $(TR) '|' '\n' | $(AWK) '$$0!~/%/{printf "%s",$$0}/%/{d="0123456789ABCDEF";v=substr(toupper($$0),2,2);printf "\\%04o",(index(d,substr(v,1,1))-1)*16+index(d,substr(v,2,1))-1}' | $(xargsquote) | $(XARGS) -E '' $(PRINTF) '%b'
# (callable) Percent‐decode the given strings.
#
@@ -318,18 +340,23 @@ override perdec = $(shell $(PRINTF) '%s\0450A' $(foreach encoded,$1,$(call quote
# It is assumed that the given strings do not contain newlines.
#
# ☡ This variable creates a subshell every time it is computed.
-override perenc = $(shell $(PRINTF) '%s\n' $(foreach unencoded,$1,$(call quote,$(unencoded))) | $(OD) -t x1 | $(SED) 's/^[0-9]*//;s/0[Aa]/%&/g;s/2[146-9A-Ea-e]/%&/g;s/3[0-9AaBbDd]/%&/g;s/40/%&/g;s/[46][1-9A-Fa-f]/%&/g;s/[57][0-9Aa]/%&/g;s/5[Ff]/%&/g;s/7[Ee]/%&/g;s/[ ][0-9A-Fa-f]\{2\}/%25&/g' | $(TR) -d ' \n' | $(TR) 'abcdef' 'ABCDEF' | $(perdeccmd))
+override perenc = $(shell $(PRINTF) '%s\n' $(foreach unencoded,$1,$(call quote,$(unencoded))) | $(OD) -t x1 | $(SED) 's/^[0123456789]*//;s/0[Aa]/%&/g;s/2[146789ABCDEabcde]/%&/g;s/3[0123456789AaBbDd]/%&/g;s/40/%&/g;s/[46][123456789ABCDEFabcdef]/%&/g;s/[57][0123456789Aa]/%&/g;s/5[Ff]/%&/g;s/7[Ee]/%&/g;s/[ ][0123456789ABCDEFabcdef]\{2\}/%25&/g' | $(TR) -d ' \n' | $(TR) 'abcdef' 'ABCDEF' | $(perdeccmd))
# (callable) Percent‐encode each component in the given paths.
#
# ☡ This variable creates a subshell every time it is computed.
override pathenc = $(subst %2F,/,$(call perenc,$1))
+# (callable) Create a unique name for the given file, based on its absolute path.
+#
+# ☡ This variable creates a subshell every time it is computed.
+namehash = $(shell $(PRINTF) '%s' $(call quote,$(abspath $1)) | $(CKSUM) | $(SED) 's/ .*//' | $(XARGS) -E '' $(PRINTF) '%X')-$(notdir $1)
+
# (overridable) Collect all of the applicable includes from the includes directory.
-sourceincludes := $(shell $(FIND) $(foreach dir,$(INCLUDEDIR),$(call quote,$(dir))) '(' $(FINDRULES) ')' -a -type f)
+sourceincludes := $(if $(and $(INCLUDEDIR),$(wildcard $(INCLUDEDIR))),$(patsubst ./%,%,$(shell $(FIND) $(foreach dir,$(INCLUDEDIR),$(call quote,$(dir))) '(' $(FINDINCLUDERULES) ')' -a -type f -a -print)),)
# (overridable) Collect all of the applicable source files from the source directory, removing any which are also includes.
-sourcefiles := $(filter-out $(sourceincludes),$(shell $(FIND) $(foreach dir,$(SRCDIR),$(call quote,$(dir))) '(' $(FINDRULES) ')' -a -type f))
+sourcefiles := $(if $(and $(SRCDIR),$(wildcard $(SRCDIR))),$(filter-out $(sourceincludes),$(patsubst ./%,%,$(shell $(FIND) $(foreach dir,$(SRCDIR),$(call quote,$(dir))) '(' $(FINDRULES) ')' -a -type f -a -print))))
# Figure out the file type of each source file and source include.
ifneq ($(wildcard $(BUILDDIR)/magic.mgc),)
@@ -338,11 +365,13 @@ endif
# Get the list of supported plaintext file types from the parser.
ifneq ($(wildcard $(BUILDDIR)/parser.xslt),)
-override plaintexttypes := $(filter-out $(XMLTYPES),$(shell $(XMLLINT) --xpath '/*/*[@name="书社:parsers"]//*[namespace-uri()="http://www.w3.org/1999/xhtml" and local-name()="dd"]/text()' $(call quote,$(BUILDDIR)/parser.xslt)))
+override plaintexttypes := $(filter-out $(XMLTYPES),$(shell $(XMLLINT) --noent --nonet --xpath '/*/*[@name="书社:parsers"]//*[namespace-uri()="http://www.w3.org/1999/xhtml" and local-name()="dd"]/text()' $(call quote,$(BUILDDIR)/parser.xslt)))
endif
-# (callable) Get all of the files (source and includes) which have the given types.
-override filesoftype = $(foreach type,$1,$(patsubst %|$(type),%,$(filter %|$(type),$(types))))
+# (callable) Get all of the files (source and includes) which have the provided types.
+#
+# If a provided type begins with a `+´, it is analysed as a suffix, and all matching types will be used.
+override filesoftype = $(foreach typeorsuffix,$1,$(foreach type,$(if $(filter +%,$(typeorsuffix)),$(sort $(foreach suffixedtype,$(filter %$(typeorsuffix),$(types)),$(lastword $(subst |, ,$(suffixedtype))))),$(typeorsuffix)),$(patsubst %|$(type),%,$(filter %|$(type),$(types)))))
# Build up collections of various file types.
override xmlfiles := $(call filesoftype,$(XMLTYPES))
@@ -350,10 +379,10 @@ override plaintextfiles := $(filter-out $(xmlfiles),$(call filesoftype,$(plainte
override assetfiles := $(filter-out $(xmlfiles) $(plaintextfiles),$(sourcefiles) $(sourceincludes))
# (callable) Get the types of the given files.
-override typeoffile = $(patsubst $(foreach file,$1,$(file)|%),%,$(filter $(foreach file,$1,$(file)|%),$(types)))
+override typeoffile = $(foreach file,$1,$(or $(patsubst $(file)|%,%,$(filter $(file)|%,$(types))),$(error Unable to get type of file `$(file)´)))
# Pair each source magic file with its location in the build directory.
-override magicpair := $(foreach magicfile,$(MAGIC),$(magicfile)|$(BUILDDIR)/magic/$(notdir $(magicfile)))
+override magicpair := $(foreach magicfile,$(MAGIC),$(magicfile)|$(BUILDDIR)/magic/$(call namehash,$(magicfile)))
# (callable) Get the source file for the given magic files.
override magicsource = $(foreach magicpath,$1,$(patsubst %|$(magicpath),%,$(firstword $(filter %|$(magicpath),$(magicpair)))))
@@ -362,15 +391,15 @@ override magicsource = $(foreach magicpath,$1,$(patsubst %|$(magicpath),%,$(firs
override magicfile = $(foreach file,$1,$(patsubst $(file)|%,%,$(filter $(file)|%,$(magicpair))))
# (callable) Get the local path for the given source file.
-override sourcepath = $(firstword $(foreach directory,$(SRCDIR),$(if $(filter $(directory)/%,$1),$(patsubst $(directory)/%,%,$1),)))
+override sourcepath = $(or $(firstword $(foreach directory,$(SRCDIR),$(if $(filter .,$(directory)),$(wildcard $1),$(if $(filter $(directory)/%,$1),$(patsubst $(directory)/%,%,$1),)))),$(error Unable to get local path for source file `$1´))
# (callable) Get the local path for the given include.
-override includepath = $(firstword $(foreach directory,$(INCLUDEDIR),$(if $(filter $(directory)/%,$1),$(patsubst $(directory)/%,%,$1),)))
+override includepath = $(or $(firstword $(foreach directory,$(INCLUDEDIR),$(if $(filter .,$(directory)),$(wildcard $1),$(if $(filter $(directory)/%,$1),$(patsubst $(directory)/%,%,$1),)))),$(error Unable to get local path for include file `$1´))
# (callable) Get base64 data u·r·i’s for the given files.
#
# ☡ This variable creates a subshell every time it is computed.
-override datauri = $(foreach file,$1,data:$(call typeoffile,$(file));base64,$(shell $(UUENCODE) -m $(call quote,$(file)) _ | $(SED) '2,$$!d;$d' | $(TR) -d ' \n'))
+override datauri = $(foreach file,$1,data:$(call typeoffile,$(file));base64,$(shell $(UUENCODE) -m $(call quote,$(file)) _ | $(SED) '2,$$!d;$$d' | $(TR) -d ' \n'))
# Pair each source file and include with its local u·r·i.
override sourcelocalpair := $(foreach file,$(sourcefiles) $(sourceincludes),$(file)|about:shushe?$(if $(filter $(file),$(sourceincludes)),include=$(call pathenc,$(call includepath,$(file))),source=$(call pathenc,$(call sourcepath,$(file)))))
@@ -381,11 +410,20 @@ override localuri = $(foreach file,$1,$(patsubst $(file)|%,%,$(filter $(file)|%,
# (callable) Get the source files for the given local uris.
override sourcefile = $(foreach local,$1,$(patsubst %|$(local),%,$(filter %|$(local),$(sourcelocalpair))))
-# Adds a requirement on `$(BUILDDIR)/.update-types´ if the file is present.
+# Resolves to the `FORCE´ target if `$(BUILDDIR)/.update-types´ is present.
#
-# This file is created after a reload due to type changes, and is removed after.
-# Requiring it ensures that file classifications are up·to·date immediately after the reload.
-override typeupdates := $(wildcard $(BUILDDIR)/.update-types)
+# This file is created before a reload due to type changes, and is removed after.
+# This ensures that file classifications are up·to·date immediately after the reload.
+override typeupdates := $(if $(wildcard $(BUILDDIR)/.update-types),FORCE,)
+
+# Pair each source file and include with its metadata location.
+override sourcemetadatapair := $(foreach file,$(sourcefiles) $(sourceincludes),$(file)|$(BUILDDIR)/$(if $(filter $(file),$(sourceincludes)),includes.metadata/$(call includepath,$(file)),sources.metadata/$(call sourcepath,$(file))))
+
+# (callable) Get the location of the transformed X·M·L files for the given source files.
+override metadata = $(foreach file,$1,$(patsubst $(file)|%,%,$(filter $(file)|%,$(sourcemetadatapair))))
+
+# (callable) Get the source files for the given parsed file.
+override datadata = $(foreach file,$1,$(patsubst %|$(file),%,$(filter %|$(file),$(sourcemetadatapair))))
# Pair each source file and include with its parsed location.
override sourceparsedpair := $(foreach file,$(sourcefiles) $(sourceincludes),$(file)|$(BUILDDIR)/$(if $(filter $(file),$(sourceincludes)),includes/$(call includepath,$(file)),sources/$(call sourcepath,$(file))))
@@ -396,15 +434,15 @@ override parsed = $(foreach file,$1,$(patsubst $(file)|%,%,$(filter $(file)|%,$(
# (callable) Get the source files for the given parsed file.
override unparsed = $(foreach file,$1,$(patsubst %|$(file),%,$(filter %|$(file),$(sourceparsedpair))))
-# Pair each parser, transform, or parsed file with its file u·r·i.
-override fileuripairs := $(join $(patsubst %,%|,$(PARSERS) $(TRANSFORMS) $(call parsed,$(sourcefiles) $(sourceincludes))),$(call pathenc,$(foreach uriable,$(PARSERS) $(TRANSFORMS) $(call parsed,$(sourcefiles) $(sourceincludes)),file://$(abspath $(uriable)))))
+# Pair each build directory, parser, transform, or source file with its file u·r·i.
+override fileuripairs := $(join $(patsubst %,%|,$(BUILDDIR) $(PARSERS) $(TRANSFORMS) $(sourcefiles) $(sourceincludes)),$(call pathenc,$(foreach uriable,$(BUILDDIR) $(PARSERS) $(TRANSFORMS) $(sourcefiles) $(sourceincludes),file://$(abspath $(uriable)))))
-# (callable) Get the file u·r·is for the given parsers, transforms, or parsed files.
-override fileuri = $(foreach file,$1,$(patsubst $(file)|%,%,$(filter $(file)|%,$(fileuripairs))))
+# (callable) Get the file u·r·is for the given parsers, transforms, parsed files, or metadata compilations.
+override fileuri = $(foreach file,$1,$(or $(patsubst $(file)|%,%,$(filter $(file)|%,$(fileuripairs))),$(error Unable to get file u·r·i for `$(file)´)))
ifneq ($(wildcard $(BUILDDIR)/dependencies),)
# Pair each file with a list of dependencies for it.
-override dependenciesforfile := $(foreach file,$(filter-out $(assetfiles),$(sourcefiles) $(sourceincludes)),$(file)|$(subst $(space),|,$(shell $(CAT) $(call quote,$(BUILDDIR)/dependencies) | $(SED) $(call quote,/^$(call sedesc,$(call localuri,$(file)))$$/$(comma)/^[^ ]/!d;/^ /!d;s/^ //))))
+override dependenciesforfile := $(foreach file,$(filter-out $(assetfiles),$(sourcefiles) $(sourceincludes)),$(file)|$(subst $(space),|,$(shell $(SED) $(call quote,/^$(call sedesc,$(call localuri,$(file)))$$/$(comma)/^[^ ]/!d;/^ /!d;s/^ //) <$(call quote,$(BUILDDIR)/dependencies))))
# (callable) Get the list of dependency leiris for the given source files.
#
@@ -412,9 +450,13 @@ override dependenciesforfile := $(foreach file,$(filter-out $(assetfiles),$(sour
override dependencyuris = $(foreach file,$1,$(subst |, ,$(patsubst $(file)|%,%,$(filter $(file)|%,$(dependenciesforfile)))))
# (callable) Get the list of recursive dependencies for the given source files.
+#
+# If the file cannot have dependencies (e·g is an asset file), the resulting value will be the empty string.
override recursives = $(foreach uri,$(filter -%,$(call dependencyuris,$1)),$(call sourcefile,$(patsubst -%,%,$(uri))))
# (callable) Get the list of (nonrecursive) dependencies for the given source files.
+#
+# If the file cannot have dependencies (e·g is an asset file), the resulting value will be the empty string.
override dependencies = $(foreach uri,$(filter-out -%,$(call dependencyuris,$1)),$(call sourcefile,$(uri)))
endif
@@ -438,8 +480,10 @@ override destinations := $(shell $(CAT) $(BUILDDIR)/destinations)
# Pair source files and their destinations.
override sourcedestinationpair := $(foreach destination,$(destinations),$(call sourcefile,$(firstword $(subst |, ,$(destination))))|$(call perdec,$(subst $(space),|,$(wordlist 2,$(words $(subst |, ,$(destination))),$(subst |, ,$(destination))))))
-# (callable) Get the destination for the given source files.
-override destination = $(foreach file,$1,$(patsubst $(file)|%,%,$(filter $(file)|%,$(sourcedestinationpair))))
+# (callable) Get the destination for the given source files, or return `.NOTDEF/$1´.
+#
+# The fallback here is because destinations are used to generate targets and thus must always be non·empty and should be unique, even when they haven’t been generated yet.
+override destination = $(foreach file,$1,$(or $(patsubst $(file)|%,%,$(filter $(file)|%,$(sourcedestinationpair))),.NOTDEF/$1))
# Pair each source file with its compiled location.
override sourcecompiledpair := $(foreach file,$(sourcefiles),$(file)|$(BUILDDIR)/results/$(call destination,$(file)))
@@ -463,17 +507,17 @@ endif
# ─ ¶ Recipe Variable Definitions ─────────────────────────────────────
# (callable) Get the identifier for the given parser or transform.
-override id = $(XMLLINT) --xpath '/*/*[local-name()="id" and namespace-uri()="urn:fdc:ladys.computer:20231231:Shu1She4"]/text()[1]' $(call quote,$1) 2> /dev/null || $(PRINTF) '%s\n' $(call quote,about:shushe?$(or $2,unknown)=$(call pathenc,$(basename $(notdir $1))))
+override id = $(XMLLINT) --noent --nonet --xpath '/*/*[local-name()="id" and namespace-uri()="urn:fdc:ladys.computer:20231231:Shu1She4"]/text()[1]' $(call quote,$1) 2>>/dev/null || $(PRINTF) '%s\n' $(call quote,about:shushe?$(or $2,unknown)=$(call pathenc,$(basename $(notdir $1))))
# (callable) Sanitize and wrap the provided plaintext file in X·M·L, printing to `stdout´.
-override wrapplaintext = $(TR) '\000\013\014' '\032\011\012' < $(call quote,$1) | $(SED) "$$($(PRINTF) '%b' 's/]]>/]]]]>/g\ns/\0357\0277\0276/�/g\ns/\0357\0277\0277/�/g\n$$!s/\\r$$//g\ns/\\r/\\n/g\n$$!s/\0302\0205$$//g\ns/\0302\0205/\\n/g;s/\0342\0200\0250/\\n/g;s/[\0001-\0010]/�/g;s/[\0016-\0037]/�/g')" | $(XARGS) -0 $(PRINTF) '%b%s]]>\n' '\n'; }
# (callable) Check if the provided X·M·L file is X·M·L 1.1, and if so, coerce to X·M·L 1.0 as best as possible, printing the result (or the original file contents) to `stdout´.
#
# The X·M·L declaration will be dropped and character escapes for C0 control codes will be replaced with a literal `U+0091 PRIVATE USE ONE´, which is invalid in X·M·L 1.1, but valid X·M·L 1.0 (making the replacement obvious).
#
# This isn’t a perfect substitution (it makes some assumptions about the format of the underlying X·M·L), but it should be workable for most sensible, welformed files.
-override serializexml = $(SED) "$$($(PRINTF) '%b' '/]*?>//\n s/&\0043x0*[1-8BCEFbcef];/\0302\0221/g\n s/&\0043x0*1[0-9A-Fa-f];/\0302\0221/g\n s/&\00430*[1-8];/\0302\0221/g\n s/&\00430*1[124-9];/\0302\0221/g\n s/&\00430*2[0-9];/\0302\0221/g\n s/&\00430*3[01];/\0302\0221/g\n}')" < $(call quote,$1) | $(SED) "$$($(PRINTF) '%b' ':a\n/^\\n*$$/{ $$d\n N\n ba\n}')"
+override serializexml = $(SED) "$$($(PRINTF) '%b' '/]*?>//\n s/&\0043x0*[12345678BCEFbcef];/\0302\0221/g\n s/&\0043x0*1[0123456789ABCDEFabcdef];/\0302\0221/g\n s/&\00430*[12345678];/\0302\0221/g\n s/&\00430*1[12456789];/\0302\0221/g\n s/&\00430*2[0123456789];/\0302\0221/g\n s/&\00430*3[01];/\0302\0221/g\n}')" <$(call quote,$1) | $(SED) "$$($(PRINTF) '%b' ':a\n/^\\n*$$/{ $$d\n N\n ba\n}')"
# ─ ¶ Phony Targets ───────────────────────────────────────────────────
@@ -482,7 +526,7 @@ all : $(call built,$(recursivefiles) $(installablefiles)) ;
# Destroy buildfiles.
clean :
- $(if $(BUILDDIR),$(silent)$(RM) -rf $(call quote,$(BUILDDIR)/),)
+ $(if $(BUILDDIR),$(silent)$(RM) -f -R $(call quote,$(BUILDDIR)/),)
# Destroy build directory and installed files.
gone : clean uninstall ;
@@ -500,7 +544,7 @@ list :
# Lists out the destinations of all resulting files (relative to `DESTDIR´).
listout :
- @$(PRINTF) '⛩️📰 书社 Output: %s\n' $(call quote,$(foreach file,$(sort $(sourcefiles)),$(call destination,$(file))))
+ @$(PRINTF) '%s\n' $(call quote,$(foreach file,$(sort $(sourcefiles)),$(call destination,$(file))))
# Destroy installed files.
uninstall :
@@ -508,7 +552,7 @@ uninstall :
# Raise an error when attempting to build any files with recursive dependencies.
$(call built,$(recursivefiles)) :
- @$(PRINTF) '%b\n' $(call quote,\0033[93;41mError:\0033[39;49m `$(call unbuilt,$@)´ has recursive dependencies:\n$(subst |, ,$(subst $(space),$(newline),$(foreach recursive,$(call recursives,$(call unbuilt,$@)),•|$(recursive))))) && false
+ @$(PRINTF) '%b\n' $(call quote,\0033[93;41mError:\0033[39;49m `$(call unbuilt,$@)´ has recursive dependencies:\n$(subst |, ,$(subst $(space),$(newline),$(foreach recursive,$(call recursives,$(call unbuilt,$@)),•|$(recursive))))) >&2 && exit 1
# Add as a prerequisite to treat the target as tho it were phony.
FORCE : ;
@@ -521,6 +565,9 @@ FORCE : ;
# Don’t use any implicit rules.
.SUFFIXES : ;
+# Don’t delete these files even if Make is stopped in the process of rebuilding them.
+.PRECIOUS : GNUmakefile ;
+
# Phony rules; always consider these out·of·date.
.PHONY : FORCE all default clean gone info install list listout uninstall $(call built,$(recursivefiles)) ;
@@ -535,10 +582,10 @@ ifneq ($(typeupdates)$(wildcard $(BUILDDIR)/dependencies)$(wildcard $(BUILDDIR)/
$(THISDIR)/GNUmakefile :: $(BUILDDIR)/dependencies $(BUILDDIR)/destinations
$(silent)$(TOUCH) $(THISDIR)/GNUmakefile
ifeq ($(typeupdates),)
- @if $(TEST) ! -f $(call quote,$(BUILDDIR)/.update-types); then $(PRINTF) '%b\n' '\0033[1mDependency graph and output destinations updated. Restarting…\0033[22m'; fi
+ $(call inform,if $(TEST) ! -f $(call quote,$(BUILDDIR)/.update-types); then $(PRINTF) '%b\n' '\0033[1mDependency graph and output destinations updated. Restarting…\0033[22m' >&2; fi)
else
- $(silent)$(RM) $(BUILDDIR)/.update-types
- @$(PRINTF) '%b\n' '\0033[1mDependency graph and output destinations updated. Restarting…\0033[22m'
+ $(silent)$(RM) $(call quote,$(BUILDDIR)/.update-types)
+ $(call inform,$(PRINTF) '%b\n' '\0033[1mDependency graph and output destinations updated. Restarting…\0033[22m' >&2)
endif
endif
@@ -551,7 +598,7 @@ ifeq ($(typeupdates),)
$(THISDIR)/GNUmakefile :: $(BUILDDIR)/magic.mgc $(BUILDDIR)/parser.xslt
$(silent)$(TOUCH) $(THISDIR)/GNUmakefile
$(silent)$(RM) -f $(call quote,$(BUILDDIR)/dependencies) $(call quote,$(BUILDDIR)/destinations)
- @$(PRINTF) '%b\n' '\0033[1mMagic file or parsers have updated. Restarting…\0033[22m'
+ $(call inform,$(PRINTF) '%b\n' '\0033[1mMagic file or parsers have updated. Restarting…\0033[22m' >&2)
$(silent)$(SLEEP) 1
$(silent)$(TOUCH) $(call quote,$(BUILDDIR)/.update-types)
endif
@@ -568,84 +615,89 @@ $(call magicfile,$(MAGIC)) : $(BUILDDIR)/magic/% : $$(call magicsource,$$@)
# It must be updated if any of the files in the magic directory change.
$(BUILDDIR)/magic.mgc : $(call diffprereqs,magic,$(sort $(call magicfile,$(MAGIC))))
$(foreach outdated,$(filter-out $^,$(wildcard $(BUILDDIR)/magic/*)),$(silent)$(RM) $(call quote,$(outdated))$(newline))
- @$(ECHO) "Compiling new magic…"
+ $(call inform,$(PRINTF) '%s\n' 'Compiling new magic…' >&2)
$(silent)$(call ensuredirectory,$(dir $@))
- $(silent)cd $(call quote,$(BUILDDIR)) && $(FILE) -C -m $(call quote,$(realpath $(BUILDDIR)/magic))
+ $(silent)$(CD) $(call quote,$(BUILDDIR)) && $(FILE) -C -m $(call quote,$(realpath $(BUILDDIR)/magic))
$(silent)$(TOUCH) $(call quote,$(BUILDDIR)/.update-types)
# Generate the main parser.
$(BUILDDIR)/parser.catalog : $(call diffprereqs,parsers,$(sort $(PARSERS)))
- @$(ECHO) "Generating catalog of parsers…"
+ $(call inform,$(PRINTF) '%s\n' 'Generating catalog of parsers…' >&2)
$(silent)$(XMLCATALOG) --create --noout $(call quote,$@)
- $(foreach parser,$(PARSERS),$(silent)( $(call id,$(parser)) ) | $(XARGS) -I %% $(XMLCATALOG) --add uri %% $(call quote,$(call fileuri,$(parser))) --noout $(call quote,$@)$(newline))
+ $(foreach parser,$(PARSERS),$(silent){ $(call id,$(parser)); $(PRINTF) '%s\n' $(call quote,$(call fileuri,$(parser))) '--noout' $(call quote,$@); } | $(xargsmultiquote) | $(XARGS) -E '' $(XMLCATALOG) --add uri$(newline))
$(BUILDDIR)/parser.xslt : $(BUILDDIR)/parser.catalog $(THISDIR)/lib/catalog2parser.xslt
- @$(ECHO) "Generating main parser…"
- $(silent)$(XSLTPROC) --nonet --novalid -o $(call quote,$@) $(call quote,$(THISDIR)/lib/catalog2parser.xslt) $(call quote,$<)
+ $(call inform,$(PRINTF) '%s\n' 'Generating main parser…' >&2)
+ $(silent)$(XSLTPROC) --nonet --novalid --nomkdir --nowrite $(call quote,$(THISDIR)/lib/catalog2parser.xslt) $(call quote,$<) >|$(call quote,$@)
$(silent)$(TOUCH) $(call quote,$(BUILDDIR)/.update-types)
+# Generate R·D·F metadata for files.
+$(call metadata,$(sourcefiles) $(sourceincludes)) : % : $$(call datadata,$$@) $(typeupdates)
+ $(call inform,$(PRINTF) '%s\n' $(call quote,Generating metadata for `$<´…) >&2)
+ $(silent)$(call ensuredirectory,$(dir $@))
+ $(silent){ if $(TEST) ! -f $(call quote,$(BUILDDIR)/.mtime); then $(PRINTF) '%b' '\n' >|$(call quote,$(BUILDDIR)/.mtime); fi; $(TOUCH) -r $(call quote,$<) $(call quote,$(BUILDDIR)/.mtime); $(DIFF) -u $(call quote,$(BUILDDIR)/.mtime) /dev/null | $(SED) '1!d;s/.* \([^ ]*\) \([^ ]*\).*$$/\1T\2Z/'; $(CKSUM) $(call quote,$<) | $(SED) 's/[ ].*//'; } | $(xargsmultiquote) | $(XARGS) -E '' $(PRINTF) '$(if $(filter $<,$(assetfiles)),,)%s' $(call quote,$(call attresc,$(call localuri,$<))) $(call quote,$(call attresc,$(if $(filter $<,$(sourceincludes)),$(call includepath,$<),$(call sourcepath,$<)))) $(call quote,$(call attresc,$(call fileuri,$<))) $(call quote,$(call attresc,$(call typeoffile,$<))) >|$(call quote,$@)
+
# Parse the files.
#
# Even plain X·M·L files are parsed, because they may contain X·H·T·M·L `