From: Lady Date: Tue, 30 Apr 2024 07:53:58 +0000 (-0400) Subject: Support pagination X-Git-Tag: 0.8.0^0 X-Git-Url: https://git.ladys.computer/Shushe/commitdiff_plain/6773582e45aab385c96638fe9e90d811132a1005?ds=inline;hp=89863e48f2c2e01ef64fa9228cf99de20b8da518 Support pagination It’s not clear to me that this is actually a good idea, and this functionality may be reverted later. It adds a lot of complexity despite still having significant drawbacks, and the alternative pattern of just generating archives and later expanding them is a much safer and more versatile solution. However, the pattern of e·g needing a paginated feed of all the posts in one’s blog suggests that something along these lines (backed by this method or archiving~expanding) *should* be supported by default in ⛩️📰 书社. One alternative might be to add (e·g) a `@书社:expand` attribute to <书社:archive> elements, tho how exactly this would be tracked on the resulting archives is unclear, and it would be restricted to producing folders (with exclusively archive contents) that could not easily be mixed with other files. --- diff --git a/GNUmakefile b/GNUmakefile index 90b769f..2cb311f 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -28,6 +28,7 @@ override define makefileinfo ║│ • grep │║ ║│ • git (optional) │║ ║│ • ln │║ +║│ • ls │║ ║│ • mkdir (requires support for `-p´) │║ ║│ • mv │║ ║│ • od (requires support for `-t x1´) │║ @@ -109,6 +110,7 @@ FIND := find GIT := git GREP := grep LN := ln +LS := ls MKDIR := mkdir MV := mv OD := od @@ -173,7 +175,7 @@ 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),) +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. @@ -219,6 +221,9 @@ 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 print all commands as they run. @@ -270,8 +275,14 @@ override xpath = $(XMLLINT) --xpath $(call quote,$1) $(call quote,$2) > /dev/nul # (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) +# (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 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ @@ -634,6 +645,7 @@ $(call installed,$(recursivefiles) $(installablefiles)) : $(DESTDIR)/% : $(BUILD @$(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 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ @@ -689,6 +701,61 @@ $(DESTDIR)/% : $(foreach file,$(archivefiles),$(BUILDDIR)/files/$(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 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 …) + $(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 …) + $(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 …) + $(silent)$(call ensuredirectory,$(dir $@)) + $(silent)$(call removeassociatedfiles,$@) + $(silent)cp $(foreach file,$(pagefiles),$(call quote,$(BUILDDIR)/public/$(file))) $(DESTDIR) + # ━ § END DEFINED MAKE·FILE MODES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ else diff --git a/README.markdown b/README.markdown index f6874ea..9523a8a 100644 --- a/README.markdown +++ b/README.markdown @@ -135,6 +135,7 @@ In every case, you may supply your own implementation by overriding the - `git` (optional; set `GIT=` to disable) - `grep` - `ln` +- `ls` - `mkdir` - `mv` - `od` @@ -281,12 +282,14 @@ Source files whose media type does not have an associated X·S·L·T 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 @@ -622,6 +625,29 @@ They are :⁠— 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][]. diff --git a/lib/archive2extractor.xslt b/lib/archive2extractor.xslt index d43be68..8ff59e7 100644 --- a/lib/archive2extractor.xslt +++ b/lib/archive2extractor.xslt @@ -13,17 +13,14 @@ If a copy of the M·P·L was not distributed with this file, You can obtain one -->