║│ • grep │║
║│ • git (optional) │║
║│ • ln │║
+║│ • ls │║
║│ • mkdir (requires support for `-p´) │║
║│ • mv │║
║│ • od (requires support for `-t x1´) │║
GIT := git
GREP := grep
LN := ln
+LS := ls
MKDIR := mkdir
MV := mv
OD := od
# 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),)
+FINDRULES := ! '(' '(' -name '[.-]*' -o -name '*[][*?:|$$%\#\\; ]*' -o -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.
#
# • ‹ 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 print all commands as they run.
# (callable) Extract the value of the text nodes in the provided X·M·L document and print them to `stdout´.
override extracttext = $(PRINTF) '%s' '<transform xmlns="http://www.w3.org/1999/XSL/Transform" version="1.0"><output method="text" encoding="UTF-8"/></transform>' | $(XSLTPROC) --nonet --novalid - $(call quote,$1)
+# (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) 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) 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 $(RM) -f $(call quote,$2); $(call extracttext,$1) > $(call quote,$2); elif $(call xpath,/*[local-name()="base64-binary" and namespace-uri()="urn:fdc:ladys.computer:20231231:Shu1She4"],$1); then $(RM) -f $(call quote,$2); $(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); elif $(call xpath,/*[local-name()="archive" and namespace-uri()="urn:fdc:ladys.computer:20231231:Shu1She4"],$1); then $(RM) -f $(call quote,$2); $(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'; else $(RM) -f $(call quote,$2); $(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); $(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
# ━ § BEGIN DEFAULT MAKE·FILE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
@$(PRINTF) '%s\n' $(call quote,Installing </$*>…)
$(silent)$(call ensuredirectory,$(dir $@))
$(silent)$(CP) $(call quote,$<) $(call quote,$@)
+ $(silent)for associated in $$($(call associatedfiles,$(dir $<),$(notdir $<))); do $(PRINTF) '%s\n' "$$associated" | $(SED) 's/^\(.*\)"{\(.*\)}"\(.*\)$$/\1\2\3/' | $(XARGS) -0 $(PRINTF) '%s%s\n' $(call quote,$(dir $@)) | $(XARGS) -0 $(CP) $(call quote,$(dir $<))"$$associated"; done
# ━ § BEGIN ARCHIVE MAKE·FILE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
$(silent)$(call ensuredirectory,$(dir $@))
$(silent)cd $(call quote,$(BUILDDIR)/files); $(PRINTF) '%s\n' $(foreach file,$(archivefiles),$(call quote,$(file))) | $(PAX) -w > $(call quote,$(abspath $@))
+# ━ § BEGIN PAGED MAKE·FILE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+else ifeq ($(MODE),urn:fdc:ladys.computer:20231231:Shu1She4:mode:paged)
+
+# ─ ¶ Non‐Recipe Variable Definitions ─────────────────────────────────
+
+# Get the list of pages.
+ifneq ($(wildcard $(BUILDDIR)/index),)
+override pagefiles := $(patsubst ./pages/%,%,$(shell $(CAT) $(call quote,$(BUILDDIR)/index)))
+endif
+
+# ─ ¶ Recipe Variable Definitions ─────────────────────────────────────
+
+# ─ ¶ Phony Targets ───────────────────────────────────────────────────
+
+# ─ ¶ Special Targets ─────────────────────────────────────────────────
+
+# Reload this make·file if the pages have changed.
+$(THISDIR)/GNUmakefile : $(BUILDDIR)/index
+ $(silent)$(TOUCH) $(THISDIR)/GNUmakefile
+ @$(PRINTF) '%b\n' $(call quote,\0033[1mPages for </$(NAME)> updated. Restarting…\0033[22m)
+
+# ─ ¶ Build Targets ───────────────────────────────────────────────────
+
+# Build the extractor transform for extracting the pages of the paged source file.
+$(BUILDDIR)/extractor.xslt : $(SRC) $(THISDIR)/lib/paged2extractor.xslt
+ $(silent)$(call ensuredirectory,$(dir $@))
+ $(silent)$(XSLTPROC) --nonet --novalid -o $(call quote,$@) --stringparam NAME $(call quote,$(call notdir,$<)) $(call quote,$(THISDIR)/lib/paged2extractor.xslt) $(call quote,$<)
+
+# Use the extractor transform to extract the paged source file out into its pages.
+#
+# This target sleeps for 1 second to ensure the resulting index will always be newer than this make·file.
+$(BUILDDIR)/index : $(BUILDDIR)/extractor.xslt $(SRC)
+ @$(PRINTF) '%s\n' $(call quote,Extracting pages for </$(NAME)>…)
+ $(silent)$(SLEEP) 1
+ $(silent)$(XSLTPROC) --nonet --novalid -o $(call quote,$@) --writesubtree $(call quote,$(dir $@)) $(call quote,$<) $(call quote,$(SRC))
+
+# All other pages are extracted alongside the base.
+$(foreach file,$(pagefiles),$(BUILDDIR)/pages/$(file)) : $(BUILDDIR)/index ;
+
+# Process each page into its final form.
+$(foreach file,$(pagefiles),$(BUILDDIR)/public/$(file)) : $(BUILDDIR)/public/% : $(BUILDDIR)/pages/%
+ @$(PRINTF) '%s\n' $(call quote,Building page `$(subst }",,$(subst "{,,$*))´ of </$(NAME)>…)
+ $(silent)$(call ensuredirectory,$(dir $@))
+ $(silent)$(call processresultto,$<,$@,$*)
+
+# Archive all components in the file to the destination.
+#
+# This is a match‐anything target, with the assumption that this make·file is being called recursively from the default mode.
+$(DESTDIR)/% : $(foreach file,$(pagefiles),$(BUILDDIR)/public/$(file))
+ @$(PRINTF) '%s\n' $(call quote,Collecting pages for </$(NAME)>…)
+ $(silent)$(call ensuredirectory,$(dir $@))
+ $(silent)$(call removeassociatedfiles,$@)
+ $(silent)cp $(foreach file,$(pagefiles),$(call quote,$(BUILDDIR)/public/$(file))) $(DESTDIR)
+
# ━ § END DEFINED MAKE·FILE MODES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
else
- `git` (optional; set `GIT=` to disable)
- `grep`
- `ln`
+- `ls`
- `mkdir`
- `mv`
- `od`
contain Ascii white·space, colons (`:`), semis (`;`), pipes (`|`),
bucks (`$`), percents (`%`), hashes (`#`), asterisks (`*`), brackets
(`[` or `]`), erotemes (`?`), backslashes (`\`), or control
- characters, must not begin with a hyphen‐minus (`-`), and must not
- end with a cloparen (`)`).**
+ characters, must not begin with a hyphen‐minus (`-`), must not end
+ with a cloparen (`)`), and must not contain quoted braces (`"{` or
+ `}"`).**
The former characters have the potential to conflict with make syntax,
- a leading hyphen‐minus is confusable for a command‐line argument, and
- a trailing cloparen [activates a bug in G·N·U Make
- 3.81](https://stackoverflow.com/questions/17148468/capturing-filenames-including-parentheses-with-gnu-makes-wildcard-function#comment24825307_17148894).
+ a leading hyphen‐minus is confusable for a command‐line argument, a
+ trailing cloparen [activates a bug in G·N·U Make
+ 3.81](https://stackoverflow.com/questions/17148468/capturing-filenames-including-parentheses-with-gnu-makes-wildcard-function#comment24825307_17148894),
+ and quoted braces are used internally by the program.
## Parsers
A plaintext (U·T·F‐8) file will be produced from the text nodes in
the transformation result.
+## Pagination
+
+It is possible to have a single source file produce multiple output
+ files via `<书社:page>` elements, whose `@name` gives the name of the
+ page.
+If a parsed document has a `@书社:destination` which contains `%s`,
+ the `%s` will be replaced with the `@name` for each `<书社:page>` (and
+ removed for the main output).
+Otherwise, the `@name` is inserted before the first period of the
+ filename (or at the end of the filename for those with no period).
+If `<书社:page>`s do not have a `@name`, they are numbered
+ sequentially.
+The destination of pages must be in the same directory as their parent.
+
+Pagination essentially forms a limited convenience for the more
+ sophisticated technique of creating an archive with ⛩️📰 书社 and
+ then unarchiving it.
+Pages are, from Make’s point of view, untracked side·effects of
+ installing the main output, meaning they cannot be targeted directly
+ and will not appear in `make list` or `make listout`.
+They are intended solely for the like of indices and feeds, for which
+ convenience and necessity outweigh their flaws.
+
## License
This repository conforms to [REUSE][].
-->
<transform
xmlns="http://www.w3.org/1999/XSL/Transform"
+ xmlns:exsl="http://exslt.org/common"
xmlns:xslt="http://www.w3.org/1999/XSL/TransformAlias"
xmlns:书社="urn:fdc:ladys.computer:20231231:Shu1She4"
version="1.0"
>
<namespace-alias stylesheet-prefix="xslt" result-prefix="#default"/>
<template match="/">
- <xslt:transform
- xmlns:exsl="http://exslt.org/common"
- extension-element-prefixes="exsl"
- version="1.0"
- >
+ <xslt:transform extension-element-prefixes="exsl" version="1.0">
<xslt:template match="/">
<for-each select="书社:archive/*[@书社:archived-as and not(starts-with(@书社:archived-as, '../') or starts-with(@书社:archived-as, '/') or contains(@书社:archived-as, '/../'))]">
<variable name="href">
<transform
xmlns="http://www.w3.org/1999/XSL/Transform"
xmlns:catalog="urn:oasis:names:tc:entity:xmlns:xml:catalog"
+ xmlns:exslstr="http://exslt.org/strings"
xmlns:书社="urn:fdc:ladys.computer:20231231:Shu1She4"
+ extension-element-prefixes="exslstr"
version="1.0"
>
<template match="/">
<text>|</text>
<choose>
<when test="$destination">
- <value-of select="$destination"/>
+ <variable name="dir">
+ <for-each select="exslstr:tokenize($destination, '/')[not(position()=last())]">
+ <value-of select="."/>
+ <text>/</text>
+ </for-each>
+ </variable>
+ <variable name="name" select="string(exslstr:tokenize($destination, '/')[last()])"/>
+ <value-of select="$dir"/>
+ <choose>
+ <when test="contains($name, '%s')">
+ <value-of select="substring-before($name, '%s')"/>
+ <value-of select="substring-after($name, '%s')"/>
+ </when>
+ <otherwise>
+ <value-of select="$name"/>
+ </otherwise>
+ </choose>
</when>
<otherwise>
<value-of select="substring-after(@name, 'about:shushe?source=')"/>
<xslt:variable name="书社:result">
<xslt:apply-templates select="exsl:node-set($书社:expansion)/node()"/>
</xslt:variable>
+ <xslt:variable name="书社:destination">
+ <xslt:choose>
+ <xslt:when test="string($书社:source/@书社:destination)!=''">
+ <xslt:value-of select="$书社:source/@书社:destination"/>
+ </xslt:when>
+ <xslt:when test="starts-with($PATH, '/')">
+ <xslt:value-of select="substring-after($PATH, '/')"/>
+ </xslt:when>
+ <xslt:otherwise>
+ <xslt:value-of select="$PATH"/>
+ </xslt:otherwise>
+ </xslt:choose>
+ </xslt:variable>
+ <xslt:variable name="书社:pagedestination">
+ <xslt:variable name="dir">
+ <xslt:for-each select="exslstr:tokenize($书社:destination, '/')[not(position()=last())]">
+ <xslt:value-of select="."/>
+ <xslt:text>/</xslt:text>
+ </xslt:for-each>
+ </xslt:variable>
+ <xslt:variable name="name" select="string(exslstr:tokenize($书社:destination, '/')[last()])"/>
+ <xslt:value-of select="$dir"/>
+ <xslt:choose>
+ <xslt:when test="contains($name, '%s')">
+ <xslt:value-of select="$name"/>
+ </xslt:when>
+ <xslt:when test="contains($name, '.')">
+ <xslt:value-of select="substring-before($name, '.')"/>
+ <xslt:text>%s.</xslt:text>
+ <xslt:value-of select="substring-after($name, '.')"/>
+ </xslt:when>
+ <xslt:otherwise>
+ <xslt:value-of select="$name"/>
+ <xslt:text>%s</xslt:text>
+ </xslt:otherwise>
+ </xslt:choose>
+ </xslt:variable>
<for-each select="//catalog:uri">
<xslt:include href="{@uri}">
<if test="contains(@name, ':')">
<xslt:apply-templates select="@*|node()" mode="书社:application"/>
</xslt:copy>
</xslt:template>
- <xslt:template match="@书社:destination|@书社:disable-output-wrapping|@书社:archived-as[../ancestor::*[not(self::书社:apply-attributes-to-root or self::书社:apply-attributes)]]" mode="书社:application" priority="1"/>
- <xslt:template match="书社:archive" mode="书社:application" priority="1">
+ <xslt:template match="@书社:destination[not(../self::书社:page)]|@书社:disable-output-wrapping|@书社:archived-as[../ancestor::*[not(self::书社:apply-attributes-to-root or self::书社:apply-attributes)]]" mode="书社:application" priority="1"/>
+ <xslt:template match="书社:archive|书社:page" mode="书社:application" priority="1">
<xslt:copy>
- <xslt:for-each select="@*|node()">
+ <xslt:for-each select="@*">
+ <xslt:choose>
+ <xslt:when test="../self::书社:archive and local-name()='archived-as' and namespace-uri()='&书社;' and ../ancestor::*[not(self::书社:apply-attributes-to-root or self::书社:apply-attributes)]"/>
+ <xslt:otherwise>
+ <xslt:apply-templates select="." mode="书社:application"/>
+ </xslt:otherwise>
+ </xslt:choose>
+ </xslt:for-each>
+ <xslt:if test="self::书社:page and not(@书社:destination)">
+ <xslt:attribute name="书社:destination">
+ <xslt:value-of select="substring-before($书社:pagedestination, '%s')"/>
+ <xslt:text>%22%7B</xslt:text>
+ <xslt:choose>
+ <xslt:when test="@name">
+ <xslt:value-of select="@name"/>
+ </xslt:when>
+ <xslt:otherwise>
+ <xslt:value-of select="count(ancestor::书社:page|preceding::书社:page)+1"/>
+ </xslt:otherwise>
+ </xslt:choose>
+ <xslt:text>%7D%22</xslt:text>
+ <xslt:value-of select="substring-after($书社:pagedestination, '%s')"/>
+ </xslt:attribute>
+ </xslt:if>
+ <xslt:for-each select="node()">
<xslt:choose>
<xslt:when test="self::*">
<xslt:variable name="component">
</xslt:variable>
<xslt:apply-templates select="exsl:node-set($component)/node()" mode="书社:application"/>
</xslt:when>
- <xslt:when test="../ancestor::*[not(self::书社:apply-attributes-to-root or self::书社:apply-attributes)] and local-name()='archived-as' and namespace-uri()='&书社;'"/>
<xslt:otherwise>
<xslt:apply-templates select="." mode="书社:application"/>
</xslt:otherwise>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+SPDX-FileCopyrightText: 2024 Lady <https://www.ladys.computer/about/#lady>
+SPDX-License-Identifier: MPL-2.0
+-->
+<!--
+⁌ ⛩️📰 书社 ∷ lib/paged2extractor.xslt
+
+© 2024 Lady [@ Lady’s 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 not distributed with this file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
+-->
+<transform
+ xmlns="http://www.w3.org/1999/XSL/Transform"
+ xmlns:exsl="http://exslt.org/common"
+ xmlns:exslstr="http://exslt.org/strings"
+ xmlns:xslt="http://www.w3.org/1999/XSL/TransformAlias"
+ xmlns:书社="urn:fdc:ladys.computer:20231231:Shu1She4"
+ version="1.0"
+>
+ <namespace-alias stylesheet-prefix="xslt" result-prefix="#default"/>
+ <param name="NAME"/>
+ <template match="/">
+ <xslt:transform extension-element-prefixes="exsl exslstr" version="1.0">
+ <xslt:template match="@*|node()">
+ <xslt:param name="destination" select="''"/>
+ <xslt:choose>
+ <xslt:when test="self::书社:page">
+ <xslt:apply-templates>
+ <xslt:with-param name="destination" select="$destination"/>
+ </xslt:apply-templates>
+ </xslt:when>
+ <xslt:when test="$destination='' and not(ancestor::书社:page) or string(ancestor::书社:page[1]/@书社:destination)=$destination">
+ <xslt:copy>
+ <xslt:apply-templates>
+ <xslt:with-param name="destination" select="$destination"/>
+ </xslt:apply-templates>
+ </xslt:copy>
+ </xslt:when>
+ <xslt:otherwise>
+ <xslt:apply-templates>
+ <xslt:with-param name="destination" select="$destination"/>
+ </xslt:apply-templates>
+ </xslt:otherwise>
+ </xslt:choose>
+ </xslt:template>
+ <xslt:template match="/" priority="1">
+ <exsl:document href="./pages/{$NAME}" method="xml" encoding="UTF-8">
+ <xslt:apply-templates/>
+ </exsl:document>
+ <xslt:text>
+ <text>./pages/</text>
+ <value-of select="$NAME"/>
+ <text>
</text>
+ </xslt:text>
+ <for-each select="//书社:page[@书社:destination]">
+ <if test="not(ancestor::书社:page[@书社:destination=current()/@书社:destination] or preceding::书社:page[@书社:destination=current()/@书社:destination])">
+ <variable name="href">
+ <text>./pages/</text>
+ <value-of select="exslstr:tokenize(@书社:destination, '/')[last()]"/>
+ </variable>
+ <exsl:document href="{$href}" method="xml" encoding="UTF-8">
+ <xslt:apply-templates>
+ <xslt:with-param name="destination">
+ <value-of select="@书社:destination"/>
+ </xslt:with-param>
+ </xslt:apply-templates>
+ </exsl:document>
+ <xslt:text>
+ <choose>
+ <when test="contains($href, '%22%7B')">
+ <value-of select="substring-before($href, '%22%7B')"/>
+ <text>"{</text>
+ <value-of select="substring-before(substring-after($href, '%22%7B'), '%7D%22')"/>
+ <text>}"</text>
+ <value-of select="substring-after(substring-after($href, '%22%7B'), '%7D%22')"/>
+ </when>
+ <otherwise>
+ <value-of select="$href"/>
+ </otherwise>
+ </choose>
+ <text>
</text>
+ </xslt:text>
+ </if>
+ </for-each>
+ </xslt:template>
+ <xslt:output method="text" encoding="UTF-8"/>
+ </xslt:transform>
+ </template>
+ <output method="xml" encoding="UTF-8"/>
+</transform>