X-Git-Url: https://git.ladys.computer/Shushe/blobdiff_plain/3339490e52d47775af7ec4a5d2e7b683ec9541b1..dcd6da9bf7c21c6e5cf799df62598087a7c1fffa:/GNUmakefile?ds=sidebyside diff --git a/GNUmakefile b/GNUmakefile index d54df18..a16e8bf 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -6,7 +6,7 @@ SHELL = /bin/sh # ━ § BASIC INFORMATION ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ override define makefileinfo ╭────────────────────────────╮ -╔╡ ⁌ ⛩️📰 书社 ∷ GNUmakefile ╞═════════════════════════════════╗ +╔╡ ⁌ ⛩📰 书社 ∷ GNUmakefile ╞═════════════════════════════════╗ ║╰────────────────────────────╯ ║ ╟┬ ¶ Prerequisites ───────────────────────────────────────────┬╢ ║│ │║ @@ -83,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 │║ @@ -149,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. # @@ -161,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´. # @@ -173,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 '*[)]' ')' -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. # @@ -200,9 +200,12 @@ TRANSFORMS := $(sort $(patsubst ./%,%,$(wildcard $(THISDIR)/transforms/*.xslt)) # 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 ⛩️📰 书社. +# 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 @@ -230,6 +233,14 @@ 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 @@ -258,19 +269,12 @@ override space := $(empty) $(empty) # A variable which contains a single comma. override comma := , +# (callable) Make empty strings non·empty, and non·empty strings empty. +override not = $(if $1,,1) + # (callable) Quote the given string for use within shell calls. override quote = '$(subst ','"'"',$1)' -# (callable) Get the modified time of the provided file. -# -# This touches a file containing only a newline and then diffs it with `/dev/null´ and extracts the timestamp from the diff. -# Interestingly, on Macintosh, the format of `diff -u´ is only Posixy (including a fractional component and timezone) when `COMMAND_MODE=legacy´; however, the timezone will always be zero and the fractional component is ignorable, so it’s not necessary to worry about that here. -# -# The diff will always have an exit status of 1, but this is ignored by piping into Sed. -# -# ☡ This variable creates a subshell every time it is computed. -override modtime = $(shell if $(TEST) ! -f $(call quote,$(BUILDDIR)/.mtime); then $(PRINTF) '%b' '\n' > $(call quote,$(BUILDDIR)/.mtime); fi; $(TOUCH) -r $(call quote,$1) $(call quote,$(BUILDDIR)/.mtime); TZ=UTC0 $(DIFF) -u $(call quote,$(BUILDDIR)/.mtime) /dev/null | $(SED) '1!d;s/.* \([^ ]*\) \([^ ]*\).*$$/\1T\2Z/') - # ─ ¶ Recipe Variable Definitions ───────────────────────────────────── # Outputs an `@´ to silence rules, unless `VERBOSE´ is non·empty. @@ -291,13 +295,13 @@ override xargsquote = $(SED) $(call quote,s/'/'"'"'/g;s/^/'/;s/$$/'/;$$!s/$$/\\/ override xargsmultiquote = $(SED) $(call quote,s/'/'"'"'/g;s/^/'/;s/$$/'/) # (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 +override xpath = $(XMLLINT) --noent --nonet --xpath $(call quote,$1) $(call quote,$2) >>/dev/null 2>>/dev/null # (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) +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); 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 $(XMLLINT) --nsclean $(call quote,$1) >|$(call quote,$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 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ @@ -305,14 +309,25 @@ ifeq ($(MODE),urn:fdc:ladys.computer:20231231:Shu1She4:mode:default) # ─ ¶ Non‐Recipe Variable Definitions ───────────────────────────────── +# Non·empty if `help´ or `clean´, and no targets other than `help´ or `clean´, were specified as goals on the commandline. +override notbuilding := $(and $(filter help clean,$(MAKECMDGOALS)),$(call not,$(filter-out help clean,$(MAKECMDGOALS)))) + # (callable) Test to see if the prerequisites provided by the second argument matches the value in the file corresponding to the first argument in `$(BUILDDIR)/lastprereqs´. # If not, save the new value and then add FORCE. # Return them regardless. # # 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. # +# If `$(notbuilding)´ is non·empty, this variable produces no result to avoid unnecessary work. +# # ☡ 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 || :),,$2),$2 FORCE$(and $(shell $(call ensuredirectory,$(BUILDDIR)/lastprereqs) && $(PRINTF) '%s\n' $(call quote,$2) >|$(BUILDDIR)/lastprereqs/$1),),$2) +override diffprereqs = $(if $(notbuilding),,$(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))))))) @@ -340,11 +355,16 @@ override perenc = $(shell $(PRINTF) '%s\n' $(foreach unencoded,$1,$(call quote,$ # ☡ 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),) @@ -353,7 +373,7 @@ 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 provided types. @@ -367,10 +387,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 = $(foreach file,$1,$(patsubst $(file)|%,%,$(filter $(file)|%,$(types)))) +override typeoffile = $(foreach file,$1,$(or $(patsubst $(file)|%,%,$(filter $(file)|%,$(types))),application/octet-stream)) # 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))))) @@ -379,10 +399,10 @@ 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. # @@ -402,7 +422,16 @@ override sourcefile = $(foreach local,$1,$(patsubst %|$(local),%,$(filter %|$(lo # # 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,) +override typeupdates := $(and $(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)))) @@ -413,15 +442,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, source file, or parsed file with its file u·r·i. +override fileuripairs := $(join $(patsubst %,%|,$(BUILDDIR) $(PARSERS) $(TRANSFORMS) $(sourcefiles) $(sourceincludes) $(call parsed,$(sourcefiles) $(sourceincludes))),$(call pathenc,$(foreach uriable,$(BUILDDIR) $(PARSERS) $(TRANSFORMS) $(sourcefiles) $(sourceincludes) $(call parsed,$(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, source file or parsed files. +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. # @@ -429,9 +458,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 @@ -453,10 +486,13 @@ ifneq ($(wildcard $(BUILDDIR)/destinations),) 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)))))) +override sourcedestinationpair := $(foreach destination,$(destinations),$(call sourcefile,$(firstword $(subst |, ,$(destination))))|$(subst $(space),|,$(wordlist 2,$(words $(subst |, ,$(destination))),$(subst |, ,$(destination))))) +endif -# (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))) @@ -475,12 +511,11 @@ override unbuilt = $(foreach file,$1,$(call uncompiled,$(patsubst $(BUILDDIR)/pu # (callable) Get the installed locations for the given source files. override installed = $(foreach file,$1,$(DESTDIR)/$(call destination,$(file))) -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 = { $(PRINTF) '%s\n%s' '' ''; } @@ -492,6 +527,9 @@ override wrapplaintext = { $(PRINTF) '%s\n%s' '' '