summaryrefslogtreecommitdiff
path: root/macros/generic
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2024-01-03 03:03:02 +0000
committerNorbert Preining <norbert@preining.info>2024-01-03 03:03:02 +0000
commit92effab22b73c432933e7eb8e12c1cefda221df9 (patch)
treecdf3576ee645074d1df994e376d9ca2483c81ec5 /macros/generic
parent6b10e9901a24325e837f0fdc2cbce8f43f18ccff (diff)
CTAN sync 202401030303
Diffstat (limited to 'macros/generic')
-rw-r--r--macros/generic/advice/CHANGELOG.md9
-rw-r--r--macros/generic/advice/advice.edtx129
-rw-r--r--macros/generic/collargs/CHANGELOG.md25
-rw-r--r--macros/generic/collargs/README.md11
-rw-r--r--macros/generic/collargs/collargs.edtx107
-rw-r--r--macros/generic/collargs/collargs.ins10
-rw-r--r--macros/generic/memoize/CHANGELOG.md59
-rw-r--r--macros/generic/memoize/Makefile56
-rw-r--r--macros/generic/memoize/doc/memoize-clean.1.md4
-rw-r--r--macros/generic/memoize/doc/memoize-code.pdfbin621070 -> 626766 bytes
-rw-r--r--macros/generic/memoize/doc/memoize-code.sty27
-rw-r--r--macros/generic/memoize/doc/memoize-code.tex34
-rw-r--r--macros/generic/memoize/doc/memoize-doc.mst (renamed from macros/generic/memoize/doc/memoize.mst)0
-rw-r--r--macros/generic/memoize/doc/memoize-doc.pdf (renamed from macros/generic/memoize/doc/memoize.pdf)bin2164795 -> 2180931 bytes
-rw-r--r--macros/generic/memoize/doc/memoize-doc.sty6
-rw-r--r--macros/generic/memoize/doc/memoize-doc.tex (renamed from macros/generic/memoize/doc/memoize.tex)1248
-rw-r--r--macros/generic/memoize/doc/memoize-extract.1.md82
-rwxr-xr-xmacros/generic/memoize/memoize-clean.pl25
-rwxr-xr-xmacros/generic/memoize/memoize-clean.py16
-rwxr-xr-xmacros/generic/memoize/memoize-extract.pl859
-rwxr-xr-xmacros/generic/memoize/memoize-extract.py674
-rw-r--r--macros/generic/memoize/memoize.edtx760
-rw-r--r--macros/generic/memoize/memoize.ins1
23 files changed, 2854 insertions, 1288 deletions
diff --git a/macros/generic/advice/CHANGELOG.md b/macros/generic/advice/CHANGELOG.md
index 63de5163a9..f112324840 100644
--- a/macros/generic/advice/CHANGELOG.md
+++ b/macros/generic/advice/CHANGELOG.md
@@ -14,6 +14,15 @@ those cases, please see [Memoize's GitHub
repository](https://github.com/sasozivanovic/memoize) for the recent
development history.
+## 2024/01/02 v1.1.0
+* Implement `\AdviceCsnameGetOriginal`.
+* Change `\AdviceGetOriginal` to expand to the internal control sequence
+ holding the original definition of the advised command under full expansion.
+* Add the missing info messages for environment (de)activation.
+* Add the original command meaning to trace.
+
+Manual: Memoize 2024/01/02 v1.1.0
+
## 2023/10/25 v1.0.1
* Require package `xparse`, as `\GetDocumentCommandArgSpec` is being dropped
from the LaTeX kernel.
diff --git a/macros/generic/advice/advice.edtx b/macros/generic/advice/advice.edtx
index dcbdfb297e..415d9da79b 100644
--- a/macros/generic/advice/advice.edtx
+++ b/macros/generic/advice/advice.edtx
@@ -29,14 +29,14 @@
% \relax
%
%<*main>
-%<latex>\ProvidesPackage{advice}[2023/10/25 v1.0.1 Extend commands and environments]
+%<latex>\ProvidesPackage{advice}[2024/01/02 v1.1.0 Extend commands and environments]
%<context>%D \module[
%<context>%D file=t-advice.tex,
-%<context>%D version=1.0.1,
+%<context>%D version=1.1.0,
%<context>%D title=Advice,
%<context>%D subtitle=Extend commands and environments,
%<context>%D author=Saso Zivanovic,
-%<context>%D date=2023-10-25,
+%<context>%D date=2024-01-02,
%<context>%D copyright=Saso Zivanovic,
%<context>%D license=LPPL,
%<context>%D ]
@@ -96,7 +96,9 @@
% namespace is hard-coded into these keys via |#1|; their arguments are
% the command/environment (cs)name, and setup keys belonging to path
% \meta{installation keypath}|/\meta{setup key name}|.
+ % \indentmacrocode
#2/.code 2 args={%
+ % \noindentmacrocode
% Call the internal setup macro, wrapping the received keylist into a
% |pgfkeys| invocation.
\AdviceSetup{#1}{#2}{##1}{\pgfqkeys{#1/#2}{##2}}%
@@ -151,7 +153,7 @@
% defined right away, as they don't change with the type of activation
% (immediate vs.\ deferred).
activate csname/.style={activate/.expand once={\csname##1\endcsname}},
- deactivate csname/.style={activate/.expand once={\csname##1\endcsname}},
+ deactivate csname/.style={deactivate/.expand once={\csname##1\endcsname}},
% \end{key}
%
% \begin{key}{activate key, deactivate key}
@@ -178,7 +180,8 @@
% These keys are used to setup the handling of the command or
% environment. The storage macros (|\AdviceRunConditions| etc.) have
% public names as they also play a crucial role in the handler
- % definitions, see section~\ref{sec:code:advice:handle}. \indentmacrocode
+ % definitions, see section~\ref{sec:code:advice:handle}.
+ % \indentmacrocode[-0.5em]
run conditions/.store in=\AdviceRunConditions,
bailout handler/.store in=\AdviceBailoutHandler,
outer handler/.store in=\AdviceOuterHandler,
@@ -215,7 +218,7 @@
reset/.code={\csname\advice@setup@init@\AdviceType\endcsname},
% \end{mmzautokey}
%
- % \begin{mmzautokey}
+ % \begin{mmzautokey}{after setup}
% The code given here will be executed once we exit the setup group.
% |integrated driver| of Memoize uses it to declare a conditional.
after setup/.code={\appto\AdviceAfterSetup{##1}},
@@ -257,6 +260,7 @@
% Open a group, so that we allow for embedded |auto| invocations.
\begingroup
\def\AdviceName{#3}%
+ \advice@def@AdviceCsname
% Command, complain, or environment?
\collargs@cs@cases{#3}{%
\def\AdviceType{command}%
@@ -399,7 +403,8 @@
\noexpand\csdef{advice@I#1//\string#2}{%
\def\noexpand\AdviceOuterHandler{\expandonce\AdviceOuterHandler}%
\def\noexpand\AdviceCollector{\expandonce\AdviceCollector}%
- \def\noexpand\AdviceRawCollectorOptions{\expandonce\AdviceRawCollectorOptions}%
+ \def\noexpand\AdviceRawCollectorOptions{%
+ \expandonce\AdviceRawCollectorOptions}%
\def\noexpand\AdviceCollectorOptions{\expandonce\AdviceCollectorOptions}%
\def\noexpand\AdviceArgs{\expandonce\AdviceArgs}%
\def\noexpand\AdviceInnerHandler{\expandonce\AdviceInnerHandler}%
@@ -412,9 +417,9 @@
% \label{sec:code:advice:activation}
%
% \begin{advicekey}{activation/immediate, activation/deferred}
-% The two subkeys of |/advice/activation| install the immediate and the
+% These two subkeys of |/advice/activation| install the immediate and the
% deferred activation code into the installation keypath. They are invoked
-% by \meta{installation keypath}|/activation=|\meta{type}.
+% by key \meta{installation keypath}|/activation=|\meta{type}.
%
% Under the deferred activation regime, the commands are not (de)activated
% right away. Rather, the (de)activation calls are collected in style
@@ -490,9 +495,27 @@
\def\advice@original@cs#1#2{\csname advice@o#1//\string#2\endcsname}
\def\AdviceGetOriginal#1#2{%
\ifcsname advice@o#1//\string#2\endcsname
- \csname advice@o#1//\string#2\expandafter\endcsname
+ \expandonce{\csname advice@o#1//\string#2\expandafter\endcsname\expandafter}%
\else
- \expandafter#2%
+ \expandafter\noexpand\expandafter#2%
+ \fi
+}
+% \end{macro}
+%
+% \begin{macro}{\AdviceCsnameGetOriginal}
+% A version of |\AdviceGetOriginal| which accepts a control sequence name as
+% the second argument.
+\begingroup
+\catcode`\/=0
+\catcode`\\=12
+/gdef/advice@backslash@other{\}%
+/endgroup
+\def\AdviceCsnameGetOriginal#1#2{%
+ \ifcsname advice@o#1//\advice@backslash@other#2\endcsname
+ \expandonce{\csname advice@o#1//\advice@backslash@other#2\expandafter\endcsname
+ \expandafter}%
+ \else
+ \expandonce{\csname#2\expandafter\endcsname\expandafter}%
\fi
}
% \end{macro}
@@ -696,6 +719,7 @@
\def\advice@handle@outer#1#2{%
\def\AdviceNamespace{#1}%
\def\AdviceName{#2}%
+ \advice@def@AdviceCsname
\let\AdviceReplaced\AdviceName
\def\AdviceOriginal{\AdviceGetOriginal{#1}{#2}}%
% We then load the stage-two settings. This defines the following macros:
@@ -724,6 +748,14 @@
% All prepared, we execute the outer handler.
\AdviceOuterHandler
}
+\def\advice@def@AdviceCsname{%
+ \begingroup
+ \escapechar=-1
+ \expandafter\expandafter\expandafter\endgroup
+ \expandafter\expandafter\expandafter\def
+ \expandafter\expandafter\expandafter\AdviceCsname
+ \expandafter\expandafter\expandafter{\expandafter\string\AdviceName}%
+}
% \end{macro}
%
% \begin{macro}{\ifAdviceRun}
@@ -745,18 +777,22 @@
% CollArgs' command |\CollectArgumentsRaw|. It will provide that command
% with:
% \begin{itemize}
-% \item the collector options, given in the raw format: the caller
-% (|\collargsCaller|), the raw options (|\AdviceRawCollectorOptions|) and the
-% user options (|\AdviceRawCollectorOptions|, wrapped in |\collargsSet|;
+% \item the collector options, given in the raw format:
+% \begin{itemize}
+% \item the caller (|\collargsCaller|),
+% \item the raw options (|\AdviceRawCollectorOptions|), and
+% \item the user options (|\AdviceRawCollectorOptions|, wrapped in
+% |\collargsSet|;
+% \end{itemize}
% \item the argument specification |\AdviceArgs| of the handled command; and
% \item the inner handler |\AdviceInnerHandler| to execute after collecting the
% arguments; the inner handler receives the collected arguments as a single
% braced argument.
% \end{itemize}
-% If the argument specification is not defined (either the user did not set
-% it, or has reset it by writing |args| without a value), it is assumed that
-% the handled command was defined by |xparse| and |\AdviceArgs| will be
-% retrieved by |\GetDocumentCommandArgSpec|.
+% \hangindent=0pt If the argument specification is not defined (either the user
+% did not set it, or has reset it by writing |args| without a value), it is
+% assumed that the handled command was defined by |xparse| and |\AdviceArgs|
+% will be retrieved by |\GetDocumentCommandArgSpec|.
% \begin{listingregion}{_advice-CollectArgumentsRaw.tex}
\def\advice@CollectArgumentsRaw{%
\AdviceIfArgs{}{%
@@ -845,6 +881,7 @@
\advice@activate@error@activated{#1}{#2}{Environment}{already}%
}{%
\csdef{\advice@original@csname{#1}{#2}}{}%
+ \PackageInfo{advice (#1)}{Activated environment "#2"}%
}%
}
\def\advice@deactivate@env#1#2{%
@@ -852,6 +889,7 @@
\csundef{\advice@original@csname{#1}{#2}}{}%
}{%
\advice@activate@error@activated{#1}{#2}{Environment}{not yet}%
+ \PackageInfo{advice (#1)}{Dectivated environment "#2"}%
}%
}
% \end{macro}
@@ -911,6 +949,7 @@
\def\advice@begin@env@outer#1#2#3{%
\def\AdviceNamespace{#1}%
\def\AdviceName{#3}%
+ \let\AdviceCsname\advice@undefined
\def\AdviceReplaced{#2{#3}}%
\def\AdviceOriginal{\AdviceGetOriginal{#1}{#2}{#3}}%
\advice@init@I{#1}{#3}%
@@ -1000,6 +1039,8 @@
% Install the tracing code.
\def\advice@trace@init@i#1#2{%
\advice@trace{Advising \detokenize\expandafter{\string#2} (\detokenize{#1})}%
+ \advice@trace{\space\space Original command meaning:
+ \expandafter\expandafter\expandafter\meaning\advice@original@cs{#1}{#2}}%
\advice@setup@init@i{#1}{#2}%
\edef\AdviceRunConditions{%
% We first execute the original run conditions, so that we can show the
@@ -1032,13 +1073,13 @@
Executing collector:
\detokenize\expandafter{\AdviceCollector}}%
\noexpand\advice@trace{\space\space\space\space
- argument specification:
+ Argument specification:
\detokenize\expandafter{\AdviceArgs}}%
\noexpand\advice@trace{\space\space\space\space
- options:
+ Options:
\detokenize\expandafter{\AdviceCollectorOptions}}%
\noexpand\advice@trace{\space\space\space\space
- raw options:
+ Raw options:
\detokenize\expandafter{\AdviceRawCollectorOptions}}%
\expandonce\AdviceCollector
}%
@@ -1052,7 +1093,7 @@
Received arguments:
\noexpand\detokenize{##1}}%
\noexpand\advice@trace{\space\space\space\space
- options:
+ Options:
\detokenize\expandafter{\AdviceOptions}}%
\expandonce{\AdviceInnerHandler}{##1}%
}%
@@ -1079,38 +1120,38 @@
% The \TikZ; code resides in a special file. It is meant to be |\input| at any
% time, so we need to temporarily assign |@| category code 11.
%<*tikz>
-\edef\mmzresetatcatcode{\catcode`\noexpand\@\the\catcode`\@\relax}%
+\edef\adviceresetatcatcode{\catcode`\noexpand\@\the\catcode`\@\relax}%
\catcode`\@=11
\def\AdviceCollectTikZArguments{%
-% We initialize the token register which will hold the collected arguments, and
-% start the collection. Nothing of note happens until \dots
- \mmz@temptoks={}%
- \mmz@tikz@anim
+ % We initialize the token register which will hold the collected arguments,
+ % and start the collection. Nothing of note happens until \dots
+ \toks0={}%
+ \advice@tikz@anim
}
-\def\mmz@tikz@anim{%
- \pgfutil@ifnextchar[{\mmz@tikz@opt}{%
- \pgfutil@ifnextchar:{\mmz@tikz@anim@a}{%
- \mmz@tikz@code}}%]
+\def\advice@tikz@anim{%
+ \pgfutil@ifnextchar[{\advice@tikz@opt}{%
+ \pgfutil@ifnextchar:{\advice@tikz@anim@a}{%
+ \advice@tikz@code}}%]
}
-\def\mmz@tikz@anim@a#1=#2{%
- \toksapp\mmz@temptoks{#1={#2}}%
- \mmz@tikz@anim
+\def\advice@tikz@anim@a#1=#2{%
+ \toksapp0{#1={#2}}%
+ \advice@tikz@anim
}
-\def\mmz@tikz@opt[#1]{%
- \toksapp\mmz@temptoks{[#1]}%
- \mmz@tikz@code
+\def\advice@tikz@opt[#1]{%
+ \toksapp0{[#1]}%
+ \advice@tikz@code
}
-\def\mmz@tikz@code{%
- \pgfutil@ifnextchar\bgroup\mmz@tikz@braced\mmz@tikz@single
+\def\advice@tikz@code{%
+ \pgfutil@ifnextchar\bgroup\advice@tikz@braced\advice@tikz@single
}
-\long\def\mmz@tikz@braced#1{\toksapp\mmz@temptoks{{#1}}\mmz@tikz@done}
-\def\mmz@tikz@single#1;{\toksapp\mmz@temptoks{#1;}\mmz@tikz@done}
+\long\def\advice@tikz@braced#1{\toksapp0{{#1}}\advice@tikz@done}
+\def\advice@tikz@single#1;{\toksapp0{#1;}\advice@tikz@done}
% \dots\ we finish collecting the arguments, when we execute the inner handler,
% with the (braced) collected arguments is its sole argument.
-\def\mmz@tikz@done{%
- \expandafter\AdviceInnerHandler\expandafter{\the\mmz@temptoks}%
+\def\advice@tikz@done{%
+ \expandafter\AdviceInnerHandler\expandafter{\the\toks0}%
}
-\mmzresetatcatcode
+\adviceresetatcatcode
%</tikz>
% \end{macrocode}
%
diff --git a/macros/generic/collargs/CHANGELOG.md b/macros/generic/collargs/CHANGELOG.md
new file mode 100644
index 0000000000..e3e1204b2d
--- /dev/null
+++ b/macros/generic/collargs/CHANGELOG.md
@@ -0,0 +1,25 @@
+# CollArgs changelog
+
+This package was developed as an auxiliary package of
+[Memoize](https://ctan.org/pkg/memoize), and is documented alongside that
+package. Note, however, that the Memoize manual, `memoize-doc.pdf`, is not
+reissued for patch-level releases of CollArgs; the version of the Memoize manual
+fully applicable to the particular version of CollArgs is indicated at each
+version.
+
+Whenever the date of the Memoize manual is older than the release date of a
+particular version of CollArgs, the documented source of CollArgs shown in that
+version of `memoize-code.pdf` of course does not reflect the latest changes; in
+those cases, please see [Memoize's GitHub
+repository](https://github.com/sasozivanovic/memoize) for the recent
+development history.
+
+## 2024/01/02 v1.1.0
+* Implement `brace collected`.
+
+Manual: Memoize 2024/01/02 v1.1.0
+
+## 2023/10/10 v1.0.0
+* The initial release.
+
+Manual: Memoize 2023/10/10 v1.0.0
diff --git a/macros/generic/collargs/README.md b/macros/generic/collargs/README.md
index f36abea78a..235225f08a 100644
--- a/macros/generic/collargs/README.md
+++ b/macros/generic/collargs/README.md
@@ -6,3 +6,14 @@ This package was developed as an auxiliary package of
[Memoize](https://ctan.org/pkg/memoize). This is why it is, somewhat
unconventionally, documented alongside that package. This applies to both the
manual and the documented code listing.
+
+# LICENCE
+
+This work may be distributed and/or modified under the conditions of the LaTeX
+Project Public License, either version 1.3c of this license or (at your option)
+any later version. The latest version of this license is in
+https://www.latex-project.org/lppl.txt and version 1.3c or later is part of all
+distributions of LaTeX version 2008 or later.
+
+This work has the LPPL maintenance status `maintained'. The Current Maintainer
+of this work is Sašo Živanović (saso.zivanovic@guest.arnes.si).
diff --git a/macros/generic/collargs/collargs.edtx b/macros/generic/collargs/collargs.edtx
index 40a102c1a8..6cd39ad4ca 100644
--- a/macros/generic/collargs/collargs.edtx
+++ b/macros/generic/collargs/collargs.edtx
@@ -10,11 +10,11 @@
%% (Sa\v{s}o \v{Z}ivanovi\'{c})
%%
%% This work may be distributed and/or modified under the conditions of the
-%% LaTeX Project Public License, either version 1.3 of this license or (at your
-%% option) any later version. The latest version of this license is in
-%% http://www.latex-project.org/lppl.txt and version 1.3 or later is part of
-%% all distributions of LaTeX version 2005/12/01 or later.
-%%
+%% LaTeX Project Public License, either version 1.3c of this license or (at
+%% your option) any later version. The latest version of this license is in
+%% https://www.latex-project.org/lppl.txt and version 1.3c or later is part of
+%% all distributions of LaTeX version 2008 or later.
+%%
%% This work has the LPPL maintenance status `maintained'.
%% The Current Maintainer of this work is Saso Zivanovic.
%%
@@ -46,14 +46,14 @@
%
%
% \paragraph{Identification}
-%<latex>\ProvidesPackage{collargs}[2023/10/10 v1.0.0 Collect arguments of any command]
+%<latex>\ProvidesPackage{collargs}[2024/01/02 v1.1.0 Collect arguments of any command]
%<context>%D \module[
%<context>%D file=t-collargs.tex,
-%<context>%D version=1.0.0,
+%<context>%D version=1.1.0,
%<context>%D title=CollArgs,
%<context>%D subtitle=Collect arguments of any command,
%<context>%D author=Saso Zivanovic,
-%<context>%D date=2023-10-10,
+%<context>%D date=2024-01-02,
%<context>%D copyright=Saso Zivanovic,
%<context>%D license=LPPL,
%<context>%D ]
@@ -70,24 +70,38 @@
%<plain>\catcode`\@11\relax
%
% \begin{macro}{\toksapp,\gtoksapp,\etoksapp,\xtoksapp}
-% Our macros for appending to a token register only accept a control sequence
-% defined by |\toksdef| (like |\mytoks|) but not an explicit register
-% designation like |\toks0|, so we only define them if noone else did; the
-% names of the macros match the \hologo{LuaTeX} primitives, so they surely
-% won't be defined there.
-\ifdefined\toksapp\else
- \long\def\toksapp#1#2{#1\expandafter{\the#1#2}}%
-\fi
-\ifdefined\etoksapp\else
- \long\def\etoksapp#1#2{#1\expandafter{\expanded{\the#1#2}}}%
-\fi
-\ifdefined\gtoksapp\else
- \long\def\gtoksapp#1#2{\global#1\expandafter{\the#1#2}}%
-\fi
-\ifdefined\xtoksapp\else
- \long\def\xtoksapp#1#2{\global#1\expandafter{\expanded{\the#1#2}}}%
+% Macros for appending to a token register. We don't have to define them in
+% \hologo{LuaTeX}, where they exist as primitives. Same as these primitives,
+% out macros accept either a register number or a |\toksdef|fed control
+% sequence as the (unbraced) |#1|; |#2| is the text to append.
+\ifdefined\luatexversion
+\else
+ \def\toksapp{\toks@cs@or@num\@toksapp}
+ \def\gtoksapp{\toks@cs@or@num\@gtoksapp}
+ \def\etoksapp{\toks@cs@or@num\@etoksapp}
+ \def\xtoksapp{\toks@cs@or@num\@xtoksapp}
+ \def\toks@cs@or@num#1#2#{%
+ % Test whether |#2| (the original |#1|) is a number or a control sequence.
+ \ifnum-2>-1#2
+ % It is a number. |\toks@cs@or@num@num| will gobble |\toks@cs@or@num@cs|
+ % below.
+ \expandafter\toks@cs@or@num@num
+ % The register control sequence in |#2| is skipped over in the false
+ % branch.
+ \fi
+ \toks@cs@or@num@cs{#1}{#2}%
+ }
+ % |#1| is one of |\@toksapp| and friends. The second macro prefixes the
+ % register number by |\toks|.
+ \def\toks@cs@or@num@cs#1#2{#1{#2}}
+ \def\toks@cs@or@num@num\toks@cs@or@num@cs#1#2{#1{\toks#2 }}
+ % Having either |\tokscs| or |\toks<number>| in |#1|, we can finally do the
+ % real job.
+ \long\def\@toksapp#1#2{#1\expandafter{\the#1#2}}%
+ \long\def\@etoksapp#1#2{#1\expandafter{\expanded{\the#1#2}}}%
+ \long\def\@gtoksapp#1#2{\global#1\expandafter{\the#1#2}}%
+ \long\def\@xtoksapp#1#2{\global#1\expandafter{\expanded{\the#1#2}}}%
\fi
-\ifdefined\toks@\else\toksdef\toks@=0 \fi
% \end{macro}
%
% \begin{macro}{\CollectArguments,\CollectArgumentsRaw}
@@ -521,7 +535,19 @@
\newif\ifcollargsNoDelimiters
% \end{macro}
% \end{collargskey}
-%
+
+% \begin{collargskey}{brace collected}
+% \begin{macro}{\ifcollargsBraceCollected}
+% When this conditional is set to false, the collected arguments are not
+% enclosed in braces when passed on to \meta{next-code}.
+\collargsSet{%
+ brace collected/.is if=collargsBraceCollected,
+}
+\newif\ifcollargsBraceCollected
+\collargsBraceCollectedtrue
+% \end{macro}
+% \end{collargskey}
+%
% \subsubsection{The central loop}
% \label{sec:code:collargs:central-loop}
%
@@ -678,7 +704,12 @@
\expanded{%
\endgroup
\noexpand\collargs@fix{%
- \expandonce\collargs@next{\the\collargs@toks}%
+ \expandonce\collargs@next
+ \ifcollargsBraceCollected
+ {\the\collargs@toks}%
+ \else
+ \the\collargs@toks
+ \fi
\collargs@spaces
}%
}%
@@ -1680,7 +1711,7 @@
\collargs@CPT@def\collargs@gobbleOneB\collargs@begintag{%
% Assign the collected tokens into a register. The first token in |##1|
% will be |\collargs@empty|, so we expand to get rid of it.
- \expandafter\toks@\expandafter{##1}%
+ \toks0\expandafter{##1}%
% |cprotect| simply grabs the token following the |\collargs@begintag| with
% a parameter. We can't do this, because we need the code to work in the
% non-verbatim mode, as well, and we might stumble upon a brace there. So
@@ -1692,13 +1723,13 @@
\ifcollargs@long\long\fi
\collargs@CPT@def\collargs@gobbleUntilE\collargs@endtag{%
% Expand |\collargs@empty| at the start of |##1|.
- \expandafter\toksapp\expandafter\toks@\expandafter{##1}%
+ \expandafter\toksapp\expandafter0\expandafter{##1}%
\collargs@gobbleUntilE@i
}%
% Initialize.
\collargs@begins=0\relax
\collargsArg{}%
- \toks@{}%
+ \toks0{}%
% We will call |\collargs@gobbleUntilE| via the caller control sequence.
\collargs@letusecollector\collargs@gobbleUntilE
% We insert |\collargs@empty| to avoid the potential debracing problem.
@@ -1726,7 +1757,7 @@
\expandafter\collargs@gobbleOneB@v
\else
% Append the real begin-tag to the temporary tokens.
- \etoksapp\toks@{\expandonce\collargs@begintag}%
+ \etoksapp0{\expandonce\collargs@begintag}%
\expandafter\collargs@gobbleOneB@ii
\fi
}%
@@ -1754,14 +1785,14 @@
\fi
% Whatever the result was, we have to append the gobbled group to the
% temporary toks.
- \etoksapp\toks@{\collargs@grabbed@spaces\unexpanded{{#1}}}%
+ \etoksapp0{\collargs@grabbed@spaces\unexpanded{{#1}}}%
\collargs@init@grabspaces
\collargs@gobbleOneB@vi
}
\def\collargs@gobbleOneB@v#1{\collargs@gobbleOneB@vi}
\def\collargs@gobbleOneB@vi{%
% Store.
- \etoksapp\collargsArg{\the\toks@}%
+ \etoksapp\collargsArg{\the\toks0}%
% Advance the begin-tag counter.
\advance\collargs@begins\collargs@begins@increment\relax
% Find more begin-tags, unless this was the final one.
@@ -1785,7 +1816,7 @@
}%
}
\def\collargs@gobbleUntilE@iii#1{%
- \etoksapp\toks@{\collargs@grabbed@spaces}%
+ \etoksapp0{\collargs@grabbed@spaces}%
\collargs@init@grabspaces
\def\collargs@tempa{#1}%
\ifx\collargs@tempa\collargs@tagarg
@@ -1794,7 +1825,7 @@
\else
% Nope, this |\end| belongs to someone else. Insert the end tag plus the
% tag argument, and collect until the next |\end|.
- \expandafter\toksapp\expandafter\toks@\expandafter{\collargs@endtag{#1}}%
+ \expandafter\toksapp\expandafter0\expandafter{\collargs@endtag{#1}}%
\expandafter\collargs@letusecollector\expandafter\collargs@gobbleUntilE
\fi
}
@@ -1802,7 +1833,7 @@
% Invoke |\collargs@gobbleOneB| with the collected material, plus a fake
% begin-tag and a quark guard.
\ifcollargsIgnoreNesting
- \expandafter\collargsArg\expandafter{\the\toks@}%
+ \expandafter\collargsArg\expandafter{\the\toks0}%
\expandafter\collargs@commandatend
\else
\expandafter\collargs@gobbleUntilE@v
@@ -1812,7 +1843,7 @@
\expanded{%
\noexpand\collargs@letusecollector\noexpand\collargs@gobbleOneB
\noexpand\collargs@empty
- \the\toks@
+ \the\toks0
% Add a fake begin-tag and a quark guard.
\expandonce\collargs@begintag
\noexpand\collargs@qend
@@ -1825,7 +1856,7 @@
\expandafter\ifx\expandafter\relax\collargs@tagarg\relax\else{%
\expandonce\collargs@tagarg}\fi
}%
- \toks@={}%
+ \toks0={}%
\expandafter\collargs@letusecollector\expandafter\collargs@gobbleUntilE
\expandafter\collargs@empty
\fi
diff --git a/macros/generic/collargs/collargs.ins b/macros/generic/collargs/collargs.ins
index a8673b6edc..5cde9e2b72 100644
--- a/macros/generic/collargs/collargs.ins
+++ b/macros/generic/collargs/collargs.ins
@@ -9,11 +9,11 @@
%% (Sa\v{s}o \v{Z}ivanovi\'{c})
%%
%% This work may be distributed and/or modified under the conditions of the
-%% LaTeX Project Public License, either version 1.3 of this license or (at your
-%% option) any later version. The latest version of this license is in
-%% http://www.latex-project.org/lppl.txt and version 1.3 or later is part of
-%% all distributions of LaTeX version 2005/12/01 or later.
-%%
+%% LaTeX Project Public License, either version 1.3c of this license or (at
+%% your option) any later version. The latest version of this license is in
+%% https://www.latex-project.org/lppl.txt and version 1.3c or later is part of
+%% all distributions of LaTeX version 2008 or later.
+%%
%% This work has the LPPL maintenance status `maintained'.
%% The Current Maintainer of this work is Saso Zivanovic.
%%
diff --git a/macros/generic/memoize/CHANGELOG.md b/macros/generic/memoize/CHANGELOG.md
new file mode 100644
index 0000000000..f0d283ee76
--- /dev/null
+++ b/macros/generic/memoize/CHANGELOG.md
@@ -0,0 +1,59 @@
+# Memoize changelog
+
+For the development history, see [Memoize's GitHub
+repository](https://github.com/sasozivanovic/memoize).
+
+## 2024/1/02 v1.1.0
+
+* Improve the extraction scripts:
+ * respect `TEXMF_OUTPUT_DIRECTORY`;
+ * respect `openin_any` and `openout_any`;
+ * implement `--format`;
+ * improve error reporting;
+ * drop the `Path::Class` dependency for the Perl script;
+ * allow for `PDF::Builder` in the Perl script;
+ * implement `--library` in the Perl script;
+ * set an appropriate exit code on exit;
+ * and several further minor changes.
+
+* Remove key `path` in favour of `prefix`.
+
+* `mkdir` is now initially `true`, but the directory is only created if `mkdir
+ command` is non-empty (and it is empty initially). The definition of `(no)
+ memo dir` is accordingly simpler.
+
+* The directory name is now appended to the value `mkdir command` when
+ constructing the system call.
+
+* A workaround for compatibility with package `morewrites`.
+
+* Process package options using the new LaTeX mechanism to avoids the issue of
+ spaces in package options. The remaining issue of `/` is addressed by
+ implementing option `options`.
+
+* Add the missing commands to `nomemoize` and `memoizable`, and implement a
+ generic variant of the latter (`memoizable.code.tex`).
+
+* Implement auto-key `to context`.
+
+* Write a c-memo even upon abortion.
+
+* Demote warning messages "memoization aborted" & "marked as unmemoizable" to
+ info messages.
+
+* Implement biblatex support.
+
+* Support `\DiscardShipoutBox`.
+
+* Advance the counter underlying `\pgfpictureid` when utilizing a tikzpicture
+ (`memoize tikz`).
+
+* Remove the `\pgfsys@getposition` hack for `tikzpicture`s.
+
+## 2023/10/10 v1.0.0
+
+* A complete, fully documented reimplementation.
+
+## 2020/07/17 v0.1
+
+* The proof of concept, available only at GitHub.
diff --git a/macros/generic/memoize/Makefile b/macros/generic/memoize/Makefile
index 7edbd7ba34..2f79a3b8ce 100644
--- a/macros/generic/memoize/Makefile
+++ b/macros/generic/memoize/Makefile
@@ -9,15 +9,16 @@ all: ctan/memoize.zip
# Prepare the CTAN submission.
PACKAGE = memoize
-VERSION = 1.0.0
-YEAR = 2023
-MONTH = 10
-DAY = 10
+VERSION = 1.1.0
+YEAR = 2024
+MONTH = 01
+DAY = 02
FORMAT = generic
COMMON = memoize nomemoize memoizable
PLAIN = memoize-extract-one.tex
+GENERIC = memoizable.code.tex
SOURCE = memoize.edtx memoize.ins # $(makefiles)
SCRIPTS := memoize-extract memoize-clean
@@ -31,11 +32,9 @@ SCRIPTS := $(SCRIPTS:%=%.pl) $(SCRIPTS:%=%.py)
%.py.1: %.1
echo .so man1/$*.1 > $@ # link to .1 man page
-.PHONY: runtime
-runtime: $(RUNTIME)
-
README = doc/README.memoize.md
INSTALL = INSTALL.md
+CHANGELOG = CHANGELOG.md
MAKEFILE = Makefile
LICENCE = LICENCE
@@ -44,11 +43,11 @@ makefiles = Makefile.package Makefile.runtimes Makefile.advice Makefile.collargs
codedoc-source = memoize-code.tex \
memoize-code.sty memoize-doc-common.sty
-manual-source = memoize.tex \
+manual-source = memoize-doc.tex \
memoize-doc.sty memoize-doc-common.sty yadoc.sty \
- memoize.mst
+ memoize-doc.mst
-PDF = memoize.pdf memoize-code.pdf
+PDF = memoize-doc.pdf memoize-code.pdf
codedoc-source := $(codedoc-source:%=doc/%)
manual-source := $(manual-source:%=doc/%)
@@ -80,11 +79,19 @@ ctan/$(PACKAGE).zip:
ln -sr $(TDS-DOC-DIR)/examples.zip $(CTAN-DIR)/doc
$(CTAN-END)
+%.py.dtx: %.py
+ edtx2dtx -s -c '#' -B '^__version__' -E '^# Local Variables:' $< \
+ | sed -e '/^% Local Variables:/Q' > $@
+%.pl.dtx: %.pl
+ edtx2dtx -s -c '#' -B '^my \$$PROG' -E '^# Local Variables:' $< \
+ | sed -e '/^% Local Variables:/Q' > $@
-doc/memoize-code.pdf: $(SOURCE) $(codedoc-source)
+doc/memoize-code.pdf: $(SOURCE) $(codedoc-source) \
+ advice.edtx advice.ins collargs.edtx collargs.ins \
+ $(SCRIPTS:%=%.dtx)
-doc/memoize.pdf: $(manual-source) $(examples-src)
+doc/memoize.pdf: $(manual-source) $(examples-src) memoize.edtx advice.edtx collargs.edtx
%.pdf: %.tex
latexmk -cd -lualatex -bibtex- $< && touch $@
@@ -119,11 +126,9 @@ version:
$(call EDIT-VERSION-PYTHON,memoize-clean.py)
$(call EDIT-VERSION-MAN,doc/memoize-extract.1.md)
$(call EDIT-VERSION-MAN,doc/memoize-clean.1.md)
-# Change the date of the latest release (identified by the version).
- sed -Ei 's!^\\item\[\\githubrelease\{[0-9]{4}/[0-9]{2}/[0-9]{2}\}\{v$(VERSION)\}\] *$$!\\item\[\\githubrelease\{$(YEAR)/$(MONTH)/$(DAY)\}\{v$(VERSION)\}\]!' doc/memoize.tex
-
+ $(call EDIT-DATE-CHANGELOG,CHANGELOG.md)
define COLOR_VERSION
-grep -E --color '[0-9]{4}[/-][0-9]{2}[/-][0-9]{2}|v?[0-9]\.[0-9]\.[0-9]|(January|February|March|April|May|June|July|August|September|October|November|December) [0-9]+, [0-9]{4}'
+grep -E --color '[0-9]{4}[/-][0-9]{2}[/-][0-9]{2}|v?[0-9]\.[0-9]\.[0-9]([-a-z]*)|(January|February|March|April|May|June|July|August|September|October|November|December) [0-9]+, [0-9]{4}'
endef
versions-show:
@@ -131,9 +136,26 @@ versions-show:
@grep __version__ *.py | ${COLOR_VERSION}
@grep VERSION *.pl | ${COLOR_VERSION}
@grep -E '^(footer|date):' doc/memoize-*.md | ${COLOR_VERSION}
- @grep -E 'githubrelease' doc/memoize.tex | ${COLOR_VERSION}
+ @${COLOR_VERSION} CHANGELOG.md doc/CHANGELOG.advice.md doc/CHANGELOG.collargs.md
include Makefile.package
include Makefile.runtimes
VERSION-MAN = of Memoize v$(VERSION)
+
+.PHONY: all-runtimes link-all-runtimes unlink-all-runtimes test
+
+all-runtimes: runtimes
+ $(MAKE) -f Makefile.advice runtimes
+ $(MAKE) -f Makefile.collargs runtimes
+
+link-all-runtimes: link-runtimes
+ $(MAKE) -f Makefile.advice link-runtimes
+ $(MAKE) -f Makefile.collargs link-runtimes
+
+unlink-all-runtimes: unlink-runtimes
+ $(MAKE) -f Makefile.advice unlink-runtimes
+ $(MAKE) -f Makefile.collargs unlink-runtimes
+
+test:
+ cd testing && ./MakeTests.py
diff --git a/macros/generic/memoize/doc/memoize-clean.1.md b/macros/generic/memoize/doc/memoize-clean.1.md
index 138e0ee871..b296697d6c 100644
--- a/macros/generic/memoize/doc/memoize-clean.1.md
+++ b/macros/generic/memoize/doc/memoize-clean.1.md
@@ -2,8 +2,8 @@
title: memoize-clean
section: 1
header: User Manual
-footer: memoize-clean of Memoize v1.0.0
-date: October 10, 2023
+footer: memoize-clean of Memoize v1.1.0
+date: January 02, 2024
hyphenate: false
---
diff --git a/macros/generic/memoize/doc/memoize-code.pdf b/macros/generic/memoize/doc/memoize-code.pdf
index 7eeed4b3bf..415b401f42 100644
--- a/macros/generic/memoize/doc/memoize-code.pdf
+++ b/macros/generic/memoize/doc/memoize-code.pdf
Binary files differ
diff --git a/macros/generic/memoize/doc/memoize-code.sty b/macros/generic/memoize/doc/memoize-code.sty
index 30d48f364c..c7bddde08b 100644
--- a/macros/generic/memoize/doc/memoize-code.sty
+++ b/macros/generic/memoize/doc/memoize-code.sty
@@ -23,7 +23,8 @@
\RequirePackage{memoize-doc-common}
% Typesetting the code & indentation
-\MacroIndent=0pt
+%\MacroIndent=0pt
+\AddToHook{begindocument}{\MacroIndent=0pt }
\MacroTopsep = 0pt
\MacrocodeTopsep = 7pt plus 2pt minus 2pt
\AddToHook{env/macrocode/before}{\macrocodepar}%
@@ -137,22 +138,33 @@
\newcommand\hang[2]{%
\setbox0=\hbox{\MacroFont \string#2\relax}% "\relax" is short enough to never trigger a hanging indent
\ifdim\wd0>\dimexpr\marginparwidth\relax
- \hangindent\dimexpr\wd0-\marginparwidth\relax
+ \hangindent\dimexpr\wd0-\marginparwidth+\hang@next@extra\relax
\hangafter-#1\relax
+ \gdef\hang@next@extra{0pt}%
\AddToHookNext{env/macrocode/before}{\par\nohang}%
\AddToHookNext{para/after}{\aftergroup\nohang}%
\fi
\ignorespaces
}
-\newcommand\nohang{\hangindent 0pt\relax}
-\newcommand\indentmacrocode{%
- \settowidth\MacroIndent{\theCodelineNo\ }%
- \addtolength\MacroIndent{\hangindent}%
+\def\AdjustNextHang#1{\gdef\hang@next@extra{#1}\ignorespaces}
+\gdef\hang@next@extra{0pt}
+\newcommand\nohang{%
+ \hangindent 0pt\relax
+ \gdef\hang@next@extra{0pt}%
+}
+\newcommand\indentmacrocode[1][0pt]{%
+ \settowidth\@tempdima{\reset@font\scriptsize\arabic{CodelineNo}\ }%
+ \setlength\ExtraMacroIndent{#1}%
+ \addtolength\MacroIndent{\dimexpr\hangindent+\@tempdima+\ExtraMacroIndent}%
}
+\newdimen\ExtraMacroIndent
\newcommand\noindentmacrocode{%
\setlength\MacroIndent{0em}%
\AddToHookNext{env/macrocode/begin}{\MacrocodeTopsep 0pt\relax}%
}
+\def\theCodelineNo{%
+ \reset@font\scriptsize\textcolor{gray}{\arabic{CodelineNo}}%
+ \hspace*{\dimexpr\leftskip-\MacroIndent+\ExtraMacroIndent}}%
% and now ... autohang!
% (the macros should be listed without spaces after commas)
@@ -165,6 +177,7 @@
advice=\macro{hang},
advice=\autokey{hang},
advice=\mmzautokey{hang},
+ advice=\advicekey{hang},
advice=\collargskey{hang},
advice=\handlerskey{hang},
advice=\adviceinstallkey{hang},
@@ -204,7 +217,7 @@
\def\doline{%
\expandafter\futurelet\expandafter\firsttoken\expandafter\dolinei\line\fi}
\def\dolinei{\ifx\firsttoken\newlabel}%
-\openin0=memoize.aux
+\openin0=memoize-doc.aux
\loop
\unless\ifeof0
\read0 to \line
diff --git a/macros/generic/memoize/doc/memoize-code.tex b/macros/generic/memoize/doc/memoize-code.tex
index 6daa583383..28aa812334 100644
--- a/macros/generic/memoize/doc/memoize-code.tex
+++ b/macros/generic/memoize/doc/memoize-code.tex
@@ -61,47 +61,23 @@
\DocInput{\docdir/collargs.dtx}
-\clearpage
-\restoregeometry
-
\section{The scripts}
\label{sec:code:scripts}
-\def\continuation{\llap{\textcolor{purple}{$\rightarrow$}\,}}
-
\subsection{The Perl extraction script \texttt{memoize-extract.pl}}
-
-\lstinputlisting[firstline=20, % manual (exclude the license information)
- language=Perl,
- breaklines, breakindent=4em, postbreak=\continuation,
- commentstyle=,
-]{../memoize-extract.pl}
+\DocInput{\docdir/memoize-extract.pl.dtx}
\subsection{The Python extraction script \texttt{memoize-extract.py}}
-
-\lstinputlisting[firstline=20, % manual (exclude the license information)
- language=Python,
- breaklines, breakindent=4em, postbreak=\continuation,
- commentstyle=,
-]{../memoize-extract.py}
+\DocInput{\docdir/memoize-extract.py.dtx}
\subsection{The Perl clean-up script \texttt{memoize-clean.pl}}
-
-\lstinputlisting[firstline=20, % manual (exclude the license information)
- language=Perl,
- breaklines, breakindent=4em, postbreak=\continuation,
- commentstyle=,
-]{../memoize-clean.pl}
+\DocInput{\docdir/memoize-clean.pl.dtx}
\subsection{The Python clean-up script \texttt{memoize-clean.py}}
-
-\lstinputlisting[firstline=20, % manual (exclude the license information)
- language=Python,
- breaklines, breakindent=4em, postbreak=\continuation,
- commentstyle=,
-]{../memoize-clean.py}
+\DocInput{\docdir/memoize-clean.py.dtx}
\clearpage
+\restoregeometry
\IndexPrologue{\section*{Index}Numbers written in red refer to the code line
where the corresponding entry is defined; numbers in blue refer to the code
diff --git a/macros/generic/memoize/doc/memoize.mst b/macros/generic/memoize/doc/memoize-doc.mst
index 68a11283a5..68a11283a5 100644
--- a/macros/generic/memoize/doc/memoize.mst
+++ b/macros/generic/memoize/doc/memoize-doc.mst
diff --git a/macros/generic/memoize/doc/memoize.pdf b/macros/generic/memoize/doc/memoize-doc.pdf
index 5642f641b6..02d3caf6f0 100644
--- a/macros/generic/memoize/doc/memoize.pdf
+++ b/macros/generic/memoize/doc/memoize-doc.pdf
Binary files differ
diff --git a/macros/generic/memoize/doc/memoize-doc.sty b/macros/generic/memoize/doc/memoize-doc.sty
index 2a1d7482c4..380ab94f28 100644
--- a/macros/generic/memoize/doc/memoize-doc.sty
+++ b/macros/generic/memoize/doc/memoize-doc.sty
@@ -406,7 +406,7 @@
\let\mmzPrefix\gobble
\let\mmzNewCMemo\gobble
\let\mmzNewCCMemo\gobble
- \def\mmzNewExtern##1##2##3##4##5{}%
+ \def\mmzNewExtern##1##2##3##4{}%
\let\mmzUsedCMemo\gobble
\let\mmzUsedCCMemo\gobble
\let\mmzUsedExtern\gobble
@@ -683,7 +683,7 @@
\ifregion{%
\AddToHook{begindocument/before}{%
\makeatletter
- \InputIfFileExists{memoize.aux}{}{}%
+ \InputIfFileExists{memoize-doc.aux}{}{}%
\makeatother
}%
\AddToHook{enddocument}{%
@@ -706,6 +706,6 @@
% \captionsetup{labelformat=empty}
% Local Variables:
-% TeX-master: "memoize.tex"
+% TeX-master: "memoize-doc.tex"
% TeX-engine: luatex
% End:
diff --git a/macros/generic/memoize/doc/memoize.tex b/macros/generic/memoize/doc/memoize-doc.tex
index 8bc647f0ba..efde098ef0 100644
--- a/macros/generic/memoize/doc/memoize.tex
+++ b/macros/generic/memoize/doc/memoize-doc.tex
@@ -30,7 +30,7 @@
}
\usepackage{makeidx}
-\pdfsystem{makeindex -s memoize.mst \jobname.idx}
+\pdfsystem{makeindex -s memoize-doc.mst \jobname.idx}
\makeindex
\usepackage[hangindent=1em,justific=raggedright,font=small]{idxlayout}
\appto\indexfont{\def\pkgcolor{gray}}
@@ -128,8 +128,15 @@
index annotation=option of \hologo{TeX} binaries},
generic={name=enable-write18, name prefix=--, label prefix:=--,
index annotation=option of \hologo{TeX} binaries},
- generic={name=TEXMFOUTPUT, index annotation={variable in
+ generic={name=TEXMFOUTPUT, index annotation={environment variable,
+ or variable in \reffile[into index=false]{texmf.cnf}}},
+ generic={name=TEXMF\_OUTPUT\_DIRECTORY, label=TEXMF_OUTPUT_DIRECTORY,
+ index annotation={environment variable, or variable in
\reffile[into index=false]{texmf.cnf}}},
+ generic={name=openout\_any, label=openout_any,
+ index annotation={variable in \reffile[into index=false]{texmf.cnf}}},
+ generic={name=openin\_any, label=openin_any,
+ index annotation={variable in \reffile[into index=false]{texmf.cnf}}},
}%
\docindex{%
cmd={name=pdfprimitive, see={\refcmd[into index=false,short]{primitive}}},
@@ -166,7 +173,7 @@
\end{enumerate*}
\end{abstract}
-\bigskip
+\vfill
\ExampleName{titlepage}
\makeexample{\examplename.pdf N=2} % N=2, because we show the .mmz file later
@@ -203,8 +210,6 @@
\end{tcboxedraster}
\end{tcboxedraster}
-\medskip
-
\begin{tcolorbox}[title=Using the externalized graphics]
\begin{tcolorbox}[example title=doc.pdf,
halign=center, remember as=pdf2]
@@ -220,17 +225,7 @@
\end{tcolorbox}
\end{tcolorbox}
-\begin{tikzpicture}[remember picture, overlay]
- \draw[->, snake arrow, thick, red] (tex.west) to[out=-90-60, in=90+60] (pdf1.west);
- \draw[->, snake arrow, thick, red] (tex.east) to[out=-90+19, in=90-19] (pdf2.east);
- \draw[->, thick, red] (extern1.south) to[out=south, in=north] (externfile1);
- \draw[->, thick, red] (extern2.south) to[out=south, out looseness=1.1, in=north, in looseness=0.9] (externfile2);
- \draw[->, thick, red] (extern3.south) to[out=south, out looseness=0.7, in=north, in looseness=0.9] (externfile3);
- \draw[->, thick,blue] (externfile1) to[out=south, in=north] ([yshift=0.8mm]used extern 1.north);
- \draw[->, thick,blue] (externfile2) to[out=south, in=north] (used extern 2.north);
- \draw[->, thick,blue] (externfile3) to[out=south, in=north] ([yshift=0.3mm]used extern 3.north);
-\end{tikzpicture}
-\endgroup
+\vfill
\begin{tcolorbox}[colback=emphcolor, fontupper=\footnotesize]
This manual also documents packages Advice
@@ -240,7 +235,18 @@
independent packages as they might be useful outside the context of Memoize,
as well. See sections~\ref{sec:advice} and \ref{sec:ref:advice} for Advice, and
sections~\ref{sec:collargs} and \ref{sec:ref:collargs} for CollArgs.
+ \begin{tikzpicture}[remember picture, overlay]
+ \draw[->, snake arrow, thick, red] (tex.west) to[out=-90-60, in=90+60] (pdf1.west);
+ \draw[->, snake arrow, thick, red] (tex.east) to[out=-90+19, in=90-19] (pdf2.east);
+ \draw[->, thick, red] (extern1.south) to[out=south, in=north] (externfile1);
+ \draw[->, thick, red] (extern2.south) to[out=south, out looseness=1.1, in=north, in looseness=0.9] (externfile2);
+ \draw[->, thick, red] (extern3.south) to[out=south, out looseness=0.7, in=north, in looseness=0.9] (externfile3);
+ \draw[->, thick,blue] (externfile1) to[out=south, in=north] ([yshift=0.8mm]used extern 1.north);
+ \draw[->, thick,blue] (externfile2) to[out=south, in=north] (used extern 2.north);
+ \draw[->, thick,blue] (externfile3) to[out=south, in=north] ([yshift=0.3mm]used extern 3.north);
+ \end{tikzpicture}
\end{tcolorbox}
+\endgroup
@@ -421,10 +427,9 @@ there's a silver lining: once Memoize is set up, it is set up for good.
\begin{tcolorbox}[title=What do I have to do?]
In principle, all you have to do for Memoize to work \emph{under the default
- configuration} is install Perl libraries
- \hreftt{https://metacpan.org/pod/PDF::API2}{PDF::API2} and
- \hreftt{https://metacpan.org/pod/Path::Class}{Path::Class}; see the Perl
- section below for the installation guideline.
+ configuration} is install Perl library
+ \hreftt{https://metacpan.org/pod/PDF::API2}{PDF::API2}; see the Perl section
+ below for the installation guideline.
\end{tcolorbox}
@@ -443,11 +448,18 @@ have to either
\docref{--enable-write18} (on MiK\hologo{TeX}), or
\item set up the restricted shell escape mode to allow for the execution of
\refscript{memoize-extract.pl}.\footnote{\label{fn:shell-escape-commands}On
- \hologo{TeX}Live, execute \code{tlmgr conf texmf shell\_escape\_commands}
- to get the list of currently allowed commands \meta{current}, then add the
- script by executing \code{tlmgr conf texmf shell\_escape\_commands
- \meta{current},memoize-extract.pl}. On MiK\hologo{TeX}, you get the
- \meta{current} list by \code{initexmf
+ \hologo{TeX}Live, |tlmgr conf texmf shell\_escape| will tell you which shell
+ escape mode is currently in effect (|p| = partial = restricted, |f| = disabled
+ and |t| = enabled); if necessary, use |tlmgr conf texmf shell\_escape p| to
+ set it to restricted. Then, execute \code{tlmgr conf texmf
+ shell\_escape\_commands} to get the list of currently allowed commands
+ \meta{current}, then add the script by executing \code{tlmgr conf texmf
+ shell\_escape\_commands \meta{current},memoize-extract.pl}.
+
+ On MiK\hologo{TeX}, the shell escape mode can be reported by |initexmf
+ --show-config-value=[Core]ShellCommandMode|, and restricted mode set by
+ |initexmf --set-config-value=[Core]ShellCommandMode=Restricted|, and you
+ get the \meta{current} list by \code{initexmf
--show-config-value=[Core]AllowedShellCommands[]}, and add to it by
\code{initexmf
--set-config-value=[Core]AllowedShellCommands[]=\meta{current};memoize-extract.pl}.}
@@ -460,17 +472,19 @@ section~\ref{sec:tut:test} to test if the setup was successful.
\begingroup
\setlength\intextsep{0pt}
+\vspace{-1ex}% manual
+
\paragraph{Perl} If you're running GNU/Linux or macOS, Perl is most likely
already installed on your system. On Windows, you might have to install it. I
tested Memoize with Strawberry Perl, available
at \url(https://){strawberryperl.com}; see \url(https://){www.perl.org} for
other options.
-\begin{wrapfigure}[3]{r}{0.25\linewidth}
+\begin{wrapfigure}[2]{r}{0.25\linewidth}
\raisebox{0.5mm}[\dimexpr\height-0.5mm][\depth]{%
\mmznext{no verbatim}
\begin{tcolorbox}[
- % example title=Installing the required libraries in Perl,
+ % example title=Installing the required library in Perl,
% attach shifted boxed title to top right,
% top=2.5mm,
fontupper=\small,
@@ -478,21 +492,18 @@ other options.
force nobeforeafter,
]
|cpan PDF::API2|
-
- |cpan Path::Class|
\end{tcolorbox}%
}%
\end{wrapfigure}
-Once Perl is installed on your system, you will need to install two Perl
-libraries as well: the PDF processing library
-\hreftt{https://metacpan.org/pod/PDF::API2}{PDF::API2}, and the cross-platform
-path specification manipulation library
-\hreftt{https://metacpan.org/pod/Path::Class}{Path::Class}. On some GNU/Linux
-distributions, these libraries are included as packages --- just use your
-package manager to install them. Otherwise, install them from
+Once Perl is installed on your system, you will also need to install the PDF
+processing library \hreftt{https://metacpan.org/pod/PDF::API2}{PDF::API2} (or
+its fork, \hreftt{https://metacpan.org/pod/PDF::Builder}{PDF::Builder}). On
+some GNU/Linux distributions, this library is included as a package --- just
+use your package manager to install it. Otherwise, install it from
\hreftt{https://www.cpan.org}{CPAN} using
\hreftt{https://metacpan.org/pod/CPAN}{cpan} tool, as shown in the box.
+\vspace{-1ex}% manual
\paragraph{Python} Installing (Python and) the required Python library is only
necessary if you decide to use the Python-based extraction script; see
@@ -519,10 +530,10 @@ the ``Install launcher for all users'' when running the installer.
}%
\end{wrapfigure}
-Once Python is installed on your system, you will also need to install the
-|pdfrw2| library (or its predecessor, |pdfrw|, which will work just as well).
-Some GNU/Linux distributions offer this library as a package; if this is not
-the case, and on other operating systems, install it
+Once Python is installed on your system, you will also need to install the PDF
+processing library |pdfrw2| library (or its predecessor, |pdfrw|, which will
+work just as well). Some GNU/Linux distributions offer this library as a
+package; if this is not the case, and on other operating systems, install it
from \href{https://pypi.org}{The Python Package Index}
using \href{https://pip.pypa.io}{pip} tool (if you
run \href{https://pip.pypa.io}{pip} as a superuser, it will install the library
@@ -627,12 +638,10 @@ once, only the final invocation takes effect.
\end{tcblisting}
\end{tcbraster}
-I like to follow |\usepackage{memoize}| by \refcmd{mmzset}, but if you prefer, you
-can also provide the document-wide configuration as \Emph{package options.}
-Note, however, that \hologo{LaTeX} removes spaces from package
-options, so keys such as \refmmz{memo dir} won't work. That said, the following are
-equivalent --- both will force re-externalization of all the externs in the
-document.
+I like to follow |\usepackage{memoize}| by \refcmd{mmzset}, but if you prefer,
+you can also provide the document-wide configuration as \Emph{package options.}
+For example, the following are equivalent --- both will force
+re-externalization of all the externs in the document.
\begin{tcbraster}[raster columns=2, raster equal height=rows]
\begin{tcblisting}{listing only}
@@ -720,8 +729,9 @@ directories; see section~\ref{sec:tut:memodir} for details.
The \refmmz{extract} line concludes the story on ``permanently'' selecting the
extraction method, started in chapter~\ref{sec:setup}. As the final note,
-observe that key \refmmz{extract} only makes sense as a package option or as a
-\refcmd{mmzset} key in \reffile{memoize.cfg}. It will have no effect as a
+observe that \refmmz{extract} and other extraction-related keys like
+\refmmz{perl extraction options} only make sense as a package option or as a
+\refcmd{mmzset} key in \reffile{memoize.cfg}. They will have no effect as a
\refcmd{mmzset} key in the document, because the extraction happens while the
package is loaded.
@@ -752,10 +762,10 @@ Take example file \texttt{\examplename.tex},\footnote{Where can you find
full, compilable documents, including the document preamble etc.} or some
simple document containing a \TikZ picture or a Forest tree, add
\EmphVerbatim{\usepackage{memoize}} to the preamble,\footnote{Memoize likes to
- be loaded early. If you get the error \texttt{Extern extraction from
- document \meta{jobname}.pdf was unsuccessful}, move
- \cs{usepackage}\bracestt{memoize} up the preamble; see
- section~\ref{sec:loading-order} for details.} and compile it twice.
+ be loaded early. If you get the warning \texttt{Cannot read file
+ \meta{jobname}.pdf}, move \cs{usepackage}\bracestt{memoize} up the
+ preamble; see section~\ref{sec:loading-order} for details.} and compile it
+twice.
\begin{itemize}
\item The first compilation of the example should produce a three-page PDF.
@@ -1195,7 +1205,7 @@ long hexadecimal numbers.
{
\relaxmmzcommands
\edef\marshal{%
- \noexpand\def\noexpand\mynewextern.//\examplename
+ \noexpand\def\noexpand\mynewextern\examplename
}\marshal.#1-#2.pdf\relax{%
\forestset{
append={[\examplename.#1.memo\vphantom{p},pointcolor]},
@@ -1214,7 +1224,7 @@ long hexadecimal numbers.
font=\scriptsize\tt, grow'=0, folder, s sep=0}},
[\meta{the document folder}, inner xsep=0,
TeX={%
- \def\mmzNewExtern##1##2##3##4##5{\mynewextern##1\relax}%
+ \def\mmzNewExtern##1##2##3##4{\mynewextern##1\relax}%
\input{\examplepath.mmz}%
},
[\examplename.tex]
@@ -1246,19 +1256,19 @@ I like to keep a clean house by instructing Memoize to put memos and friends
into their own directory. This can be achieved by writing
\refcmd{mmzset}\braces{\Emph{\refmmz{memo dir}}} into the preamble (anytime
after loading the package).\footnote{The \refmmz{memo dir} key is in fact
- merely an abbreviation for a sequence of \refmmzpath{dir} and
- \refmmzpath{prefix} within \refmmz{path}; use these keys if you need more
- control over the name and location of the auxiliary files. Furthermore, there
- is also the \refmmz{no memo dir} key, which reverts the configuration back to
- the dirty default.} This will put the memo and the extern files into folder
-\meta{document name}|.memo.dir| (and it will also omit the \meta{document name}
-prefix in their filenames, because it makes no sense to repeat it there).
+ merely an abbreviation for \code{\refmmz{prefix}=\string\jobname.memo.dir};
+ use this key if you need more control over the name and location of the
+ auxiliary files. Furthermore, there is also the \refmmz{no memo dir} key,
+ which reverts the configuration back to the dirty default.} This will put
+the memo and the extern files into folder \meta{document name}|.memo.dir| (and
+it will also omit the \meta{document name} prefix in their filenames, because
+it makes no sense to repeat it there).
\makeexample{\examplename.pdf}
{%
\relaxmmzcommands
\edef\marshal{%
- \noexpand\def\noexpand\mynewextern./\examplename
+ \noexpand\def\noexpand\mynewextern\examplename
}\marshal.memo.dir/#1-#2.pdf\relax{%
\forestset{
append={[#1.memo\vphantom{p},pointcolor]},
@@ -1284,7 +1294,7 @@ prefix in their filenames, because it makes no sense to repeat it there).
% tikz={\draw[dotted](.east)--+(9em,0ex) node [anchor=south west, yshift=-1ex, font=\footnotesize, emphshade=black!5!white, text width=15em, align=justify]{You need to create this directory by hand, or allow Memoize to create it by adding |mkdir| to the list of restricted shell escape commands.};},
tikz={\draw[dotted](.east)--+(9em,0ex) node [anchor=west, font=\footnotesize, emphshade=black!5!white]{This directory must be created, somehow.};},
TeX={%
- \def\mmzNewExtern##1##2##3##4##5{\mynewextern##1\relax}%
+ \def\mmzNewExtern##1##2##3##4{\mynewextern##1\relax}%
\input{\examplepath.mmz}%
},
]
@@ -1421,10 +1431,10 @@ above) and set up \refmmz{memo dir} as shown below. Well, this won't work, or
at least it won't work with the vanilla \hologo{TeX} Live, because \hologo{TeX}
will refuse to \emph{write} into (memo) files outside the directory where it
was executed,\cprotect\footnote{In \hologo{TeX} Live, the \reffile{texmf.cnf}
- option controlling this behaviour is called |openout_any|. By default, it is
+ option controlling this behaviour is called \docref{openout_any}. By default, it is
set to |p| (paranoid), which ``disallow[s] opening dot files [and]
\emph{going to parent directories}, and restrict[s] absolute paths to be
- under \texttt{\$TEXMFOUTPUT}'' (emphasis mine).} and this is precisely what
+ under \docref{TEXMFOUTPUT}'' (emphasis mine).} and this is precisely what
the chapter compilation is asked to do below.
\begin{tcboxedraster}[raster columns=2, raster valign=top]{blankest, bad}
@@ -1489,8 +1499,7 @@ set), you will have a reasonably up-to-date set of externs.
\meta{original document name}, which is then fed to \refmmz{memo dir}. Now,
the trick works automatically for any document.\footnote{The assumption here
is that \refmmz{memo dir} is in effect for the original document. If not,
- the trick can be adapted to use (\refmmzpath{dir} and) \refmmzpath{prefix}
- within \refmmz{path}.}
+ the trick can be adapted to use \refmmz{prefix}.}
\tcbinputexample{
title=\tt memoize.cfg,
@@ -2013,9 +2022,9 @@ extern extraction. It ships with three \emph{extraction methods}:
\item[\refmmz{extract=perl}] A Perl script, \refscript{memoize-extract.pl}.
This method is the default because it is fast and because Perl is usually
already installed on a system running \hologo{TeX}. However, you will most
- likely still need to install two pieces of software that the script depends
- on: Perl libraries \hreftt{https://metacpan.org/pod/PDF::API2}{PDF::API2} and
- the \hreftt{https://metacpan.org/pod/Path::Class}{Path::Class}, the
+ likely still need to install the PDF processing library
+ \hreftt{https://metacpan.org/pod/PDF::API2}{PDF::API2}, or its fork
+ \hreftt{https://metacpan.org/pod/PDF::Builder}{PDF::Builder}; the
installation guidelines can be found in section~\ref{sec:install}.
\item[\refmmz{extract=python}] A Python script, \refscript{memoize-extract.py}.
This method is even faster than the Perl script, though not by much. Try it
@@ -3005,11 +3014,13 @@ cc-memo. The number of externs mostly depends on the memoization driver (see
section~\ref{sec:memoization-drivers}); the default driver always produces
exactly one extern.
-A cc-memo is located in the directory given via subkey \refmmzpath{dir} (and
-\refmmzpath{relative}) of key \refmmz{path}. You can recognize it by its filename,
-which has the following form (\meta{prefix} is set via subkey \refmmzpath{prefix}):
+A cc-memo is located in the directory determined by key \refmmz{prefix}
+(everything up to the final |/| in the value of this key, or the current
+directory if this value contains no |/|). You can recognize it by its
+filename, which has the following form (\meta{prefix name} is everything
+following the final |/| in the value of \refmmz{prefix}):
\begin{center}
- \code{\meta{prefix}\meta{code md5sum}-\meta{context md5sum}.memo}
+ \code{\meta{prefix name}\meta{code md5sum}-\meta{context md5sum}.memo}
\end{center}
In fact, this is how Memoize recognizes --- or rather, searches for --- a
@@ -3040,7 +3051,7 @@ cc-memo (it belongs to the titlepage penguin):
\input{\exampledir titlepage.mmz.c1}
\sed{%
s/\cmd{quitvmode} \cmd{mmzIncludeExtern}/\cmd{quitvmode}\n\cmd{mmzIncludeExtern}/;
- s/\(\marg\marg\marg\)\(\marg\marg\marg\marg\marg\)/\1\space\2/;
+ s/\(\marg\)\cmd{global}/\1\n\cmd{global}/;
}{\exampledir\myccmemo}
\tcbinputlisting{
listing only,
@@ -3163,13 +3174,12 @@ first learn about the context adjustments. Here's where c-memos enter the
picture: \emph{the primary job of a c-memo is to store the context adjustments
made by the memoized code}. Let's see how this works in detail.
-Same as cc-memos, c-memos are located in the directory given via subkey
-\refmmzpath{dir} (and \refmmzpath{relative}) of key \refmmz{path}. However, a c-memo
-belonging to some memoized code is identified by the MD5 sum of that code
-alone; its filename has the following form, where \meta{prefix} is again set
-via subkey \refmmzpath{prefix}:
+Same as cc-memos, c-memos are located in the directory determined by key
+\refmmz{prefix}, and their filenames start by \meta{prefix name} determined by
+the same key. However, a c-memo belonging to some memoized code is identified
+by the MD5 sum of that code alone:
\begin{center}
- \code{\meta{prefix}\meta{code md5sum}.memo}
+ \code{\meta{prefix name}\meta{code md5sum}.memo}
\end{center}
The c-memo is created at the end of the memoization process. At that time, the
@@ -3589,7 +3599,7 @@ when preceded by no \cs{pause} command (|pauses=1|).
s/\(\cmd{quitvmode}\) \(\cmd{only}\)/\1\n\2/;
s/\rbrace\(\cmd{mmzIncludeExtern}\)/\rbrace\percentchar\n\1/;
s/\rbrace\(\cmd{setcounter}\)/\rbrace\percentchar\n\1/;
- s/\(\marg\marg\marg\)\(\marg\marg\marg\marg\marg\)/\1\space\2/;
+ s/\(\marg\)\cmd{global}/\1\n\cmd{global}/;
}{\exampledir\myccmemo}
\tcbinputlisting{
@@ -3678,12 +3688,11 @@ reflect this with \refcmd{mmzUsedCMemo}, \refcmd{mmzUsedCCMemo} and
Finally, both versions illustrate that a \dmmz file always begins with command
\refcmd{mmzPrefix} and ends with the |\endinput| marker. The argument of
-\refcmd{mmzPrefix} is the path prefix to the memo and extern files, as
-determined by the invocation of key \refmmz{path} (and its subkeys
-\refmmzpath{relative}, \refmmzpath{dir} and \refmmzpath{prefix}). The initial
+\refcmd{mmzPrefix} is the prefix to the memo and extern files, as
+determined by the invocation of key \refmmz{prefix}. The initial
\refcmd{mmzPrefix} line is written to the \dmmz file at the beginning of the
document, but an additional \refcmd{mmzPrefix} line will occur for every
-invocation of \refmmz{path} in the document body. Finally, the |\endinput|
+invocation of \refmmz{prefix} in the document body. Finally, the |\endinput|
marker signals that the \dmmz file is complete.
As mentioned above, the full contingent of \dmmz file commands is only used by
@@ -3742,9 +3751,9 @@ not needed for the record file type may be left undefined):
\item Key \refmmz{record/record type/end} will be executed at the end of the
document; it will receive no argument. Use it to close the record file.
\item Key \refmmz{record/record type/prefix} will be executed at the end of the
- document and at every invocation of key \refmmz{path} in the document body;
- it will receive a single argument, the path prefix determined by key
- \refmmz{path}.
+ document and at every invocation of key \refmmz{prefix} in the document body;
+ it will receive a single argument, the prefix determined by key
+ \refmmz{prefix}.
\item Keys \refmmz{record/record type/new cmemo}, \refmmz{record/record
type/used cmemo}, \refmmz{record/record type/new ccmemo} and
\refmmz{record/record type/used ccmemo} will be executed after creating or
@@ -4046,7 +4055,7 @@ be externalized into separate externs, as shown below.
\restoreexpandmode
\relaxmmzcommands
}%
- \def\mmzNewExtern##1##2##3##4##5{\mmzUsedExtern{##1}}
+ \def\mmzNewExtern##1##2##3##4{\mmzUsedExtern{##1}}
\def\myexternname##1{extern \dots\texttt{\mytemp##1.pdf}}%
\input{\examplepath.mmz}%
\begin{tabular}{ccc}
@@ -4676,13 +4685,15 @@ key \refmmzauto{collector} in addition to executing \refmmzauto{memoize}. For
example, Memoize submits \refcmd{tikz} to automemoization by loading
\reffile{advice-tikz.code.tex}, which contains Advice's definition of the
\cs{tikz} collector \refcmd{AdviceCollectTikZArguments}, and issuing the
-following declaration.
+following declaration.\footnote{This is a simplification, see
+ \refmmzauto{memoize tikz} for the full story.}
-\makeexcerpt{_auto-tikz-collector}
-\tcbinputexample[.tex][.excerpt]{%
- listing only, one file, no attachment,
- title={Declaring automemoization of command \cs{tikz}},
-}
+\begin{tcblisting}{
+ example title={Declaring automemoization of command \cs{tikz}},
+ listing only,
+ }
+ auto=\tikz{memoize, collector=\AdviceCollectTikZArguments},
+\end{tcblisting}
\paragraph{\refmmzauto[show keypath]{ref}}
@@ -5036,20 +5047,23 @@ with \refcmd{CollectArgumentsRaw} plus the following three arguments:
In this section, the extra information about keys, offered at the right of a
key in parenthesis, may contain the initial value of a key, and also a default
value of a key. In this context, the terms ``initial'' and ``default'' have
-the meaning employed by the \pkg{pgfkeys} utility. Initial value refers to the
-value that is set by the package. More often, we would call this the default
-or the package-default value, but in the \pkg{pgfkeys} parlance, the default value
-refers to the value that is passed to the key in the absence of the argument.
-Honestly, I only kept to this convention in the reference section; elsewhere,
-I often say ``default'' or ``package-default'' and mean the initial value.
-
-Another convention I kept to in this section is the color-coding of the keys
+the meaning employed by the \pkg{pgfkeys} utility (\PGFmanual{87}). The term
+``initial value'' applies to the setting underlying a key (when there is no
+such setting, the key is marked as a ``style''), and refers to the value of
+this setting that is set by the package. In other contexts, we would call this
+the default or the package-default value, but in the \pkg{pgfkeys} parlance,
+the term ``default value'' applies to a key taking an argument, and refers to
+the value that is passed to the key in the absence of that argument.
+(Honestly, I only keep to this convention in the reference section; elsewhere,
+I often say ``default'' or ``package-default'' and mean the initial value.)
+
+Another convention I keep to in this section is the color-coding of the keys
and commands. Green background indicates a basic key or command, which any
user might want to know about. Red background indicates other, more or less
advanced keys and commands.
-\subsection{Loading}
+\subsection{Loading and initialization}
\label{sec:ref:loading}
\paragraph{\hologo{LaTeX}}
@@ -5058,21 +5072,24 @@ Load Memoize by \cs{usepackage}\braces{\docaux{package}{memoize}} or
almost identically to the former followed by
\refcmd{mmzset}\bracestt{\meta{options}}, with two exceptions.
-First, the \meta{options} as package options may not contain spaces. This is a
-constraint imposed by \hologo{LaTeX} itself. To avoid it for a particular key,
-define a spaceless variant of the key in \reffile{memoize.cfg}. For example,
-to use \refmmz{memo dir} as a package option, put
-\refcmd{mmzset}|{memodir/.style={|\refmmz{memo dir}|={#1}}}| into that file.
-
-Second, key \refmmz{extract} only make senses as a package option, or within
-\reffile{memoize.cfg}. Internal extern extraction can only be performed before
-the output PDF is opened, and to maximize chances of that, Memoize is designed
-to extract as soon as it can: while it is being loaded. Consequently, changing
-the extraction method in the document preamble has no effect.
-
-The necessity of performing extraction before the output PDF is opened is also
-why Memoize prefers to be loaded early with respect to other packages. In
-particular, it must be loaded before \PGF library |fadings| (see
+First, when used as package options, \meta{options} may not contain the slash
+character (|/|), which is necessary to invoke \pkg{pgfkeys} handlers, because
+is it misinterpreted by \hologo{LaTeX}.\footnote{The historic \hologo{LaTeX}
+ constraint prohibiting spaces in package options does not apply anymore.} In
+the rare situations requiring key handling in the package options, use option
+\refmmz{options}.
+
+Second, key \refmmz{extract} and other extraction-related keys such as
+\refmmz{perl extraction options} should normally be used as a package option,
+or within \reffile{memoize.cfg}. Because Memoize extracts the externs while it
+is being loaded, executing these keys after the package is loaded will have a
+different effect; see the documentation of \refmmz{extract} for details.
+
+Memoize extracts the externs while it is being loaded (and not, say, at the
+beginning of the document) because extern extraction can only be performed
+before the output PDF is opened, and some packages cause it to be opened when
+they are loaded. This also implies that Memoize must be loaded before any such
+package. In particular, it must be loaded before \PGF library |fadings| (see
section~\ref{sec:fadings-problem}) and before the \pkg{beamer} class (see
section~\ref{sec:tut:beamer}).
@@ -5123,8 +5140,9 @@ this means that Memoize must be loaded before \pkg{atbegshi}.
Load Memoize by \cs{usemodule}\bracketstt{memoize} or
\cs{usemodule}\bracketstt{memoize}\oarg{options}. Unlike in \hologo{LaTeX},
-spaces are allowed in \meta{options}; the remarks on key \refmmz{extract} and
-loading order are the same as for \hologo{LaTeX}.
+there are no restrictions on characters allowed within \meta{options}; the
+remarks on key \refmmz{extract} and loading order are the same as for
+\hologo{LaTeX}.
In \hologo{LaTeX}, Memoize automatically executes its initialization and
finalization code when at the beginning and the end of the document body. Due
@@ -5141,12 +5159,14 @@ on how to handle this properly.
\begin{doc}{package={name=nomemoize}}
Loading this package instead of Memoize completely disables memoization, but
does not require removing any Memoize commands from the document (they all
- become no-ops).
+ become no-ops). This package accepts any package options (and ignores them).
\end{doc}
\begin{doc}{package={name=memoizable}}
This package is a programmer's stub: if Memoize is loaded, it does nothing;
- otherwise, it provides the no-op variants Memoize commands. See
+ otherwise, it provides the no-op variants Memoize commands. This package
+ accepts no package options. Generic packages, e.g.\ \TikZ libraries, can
+ also load this package via |\input memoizable.code.tex|. See
section~\ref{sec:loading-memoize} for details.
\end{doc}
@@ -5238,6 +5258,26 @@ on how to handle this properly.
by package \refpkg{memoizable}, though).
\end{doc}
+\begin{doc}{key={name=options, par=\marg{options}}}
+ Execute \meta{options} as if they were given an an argument to \refcmd{mmzset}.
+
+ This option primarily exists to allow any key--value pair accepted by
+ \refcmd{mmzset} to be used as a package option. In particular, this applies
+ key handlers (see \PGFmanual{87}), because their invocation includes a slash
+ (|/|). For example, one cannot directly use
+ \begin{center}
+ \code{\cs{usepackage}[\refmmz{perl extraction
+ options}/.prefix=\braces{\braces{\refscript{memoize-extract.pl--quiet}}} ]\braces{memoize}}
+ \end{center}
+ to add (prepend) option \refscript{memoize-extract.pl--quiet} to the
+ invocation of the perl extraction script \refscript{memoize-extract.pl}, but
+ this will work:
+ \begin{center}
+ \code{\cs{usepackage}[\refmmz{options}=\braces{\refmmz{perl extraction
+ options}/.prefix=\braces{\braces{\refscript{memoize-extract.pl--quiet}}} ]\braces{memoize}}}
+ \end{center}
+\end{doc}
+
\begin{doc}{cmd={name=nommzkeys, par=\marg{key}}}
In package \refpkg{memoize}, this command is a no-op; in packages
\refpkg{nomemoize} and \refpkg{memoizable}, it defines key
@@ -5335,7 +5375,7 @@ on how to handle this properly.
\end{doc}
\begin{doc}{easy,
- key={name=normal, desc=the default mode},
+ key={name=normal, desc=the initial mode},
key={name=readonly},
key={name=recompile},
}
@@ -5388,7 +5428,7 @@ on how to handle this properly.
desc={style},
key={name=verbatim},
key={name=verb},
- key={name=no verbatim},
+ key={name=no verbatim, desc={style, the initial mode}},
}
When \refmmz{verbatim} or \refmmz{verb} is in effect, the code submitted to
memoization is read verbatim; \refmmz{no verbatim} reverts to the normal,
@@ -5511,7 +5551,7 @@ on how to handle this properly.
\end{doc}
\begin{doc}{
- keypath={/handlers},
+ keypath={/handlers}, desc=handler,
key={easy, name=.meaning to context},
key={name=.value to context},
}
@@ -5546,7 +5586,7 @@ on how to handle this properly.
\end{doc}
\begin{doc}{easy, key={name=per overlay,desc=style}}
- This key is only defined in the Beamer class. When in effect, the memoized
+ This style is only defined in the Beamer class. When applied, the memoized
code will produce a cc-memo (and extern) for each overlay of the frame. For
implementation, see section~\ref{sec:per-overlay}.
\end{doc}
@@ -5840,8 +5880,8 @@ on how to handle this properly.
\begin{doc}{
cmd={name=ifmmzkeepexterns, desc={initially \cs{iffalse}}},
- cmd={name=mmzkeepexternstrue},
- cmd={name=mmzkeepexternsfalse},
+ cmd={head prefix=\texttt{\string\global}, name=mmzkeepexternstrue},
+ cmd={head prefix=\texttt{\string\global}, name=mmzkeepexternsfalse},
}
Setting this conditional to true makes Memoize keep the extern boxes in the
global temporary storage even after shipping them out as extern pages. (The
@@ -5861,11 +5901,12 @@ on how to handle this properly.
from the temporary storage rather than from (at that point still
non-existing) extern files. However, as this mechanism requires Memoize to
keep the externs around even after memoization, it is not enabled by default:
- it must be enabled by setting conditional \refcmd{ifmmzkeepexterns} to true.
+ it must be enabled by (globally) setting conditional
+ \refcmd{ifmmzkeepexterns} to true.
\end{doc}
\begin{doc}{
- key={keypath=/mmz/auto, name=integrated driver, par=\marg{name}}
+ key={keypath=/mmz/auto, name=integrated driver, par=\marg{name}, desc=style}
}
Use this key to easily setup a memoization driver which is integrated into
the command itself.
@@ -5877,9 +5918,9 @@ on how to handle this properly.
An integrated driver must have a way of telling whether it is memoizing or
regularly compiling the code. This key declares a driver-specific
conditional which may be inspected, using \refcmd{IfMemoizing}, to determine
- this. The conditional is set to true by the formal driver of the command
- (set up by the invocation of this key), executed at the start of memoization;
- it should never be set elsewhere. See
+ this. The conditional is set to true by the formal \refmmz{driver} of the
+ command (set up by the invocation of this key), executed at the start of
+ memoization; it should never be set elsewhere. See
section~\ref{sec:memoization-multiple-externs} for details and an example.
\end{doc}
@@ -6092,160 +6133,139 @@ require.
\begin{doc}{easy,
key={name=memo dir,par=\meta{name}, desc={style, default \texttt{\string\jobname}}},
}
- A convenient way to store memos and externs in a dedicated directory (and
- create this directory in case it does not exist). Without an argument, this
- key places these files in subdirectory \meta{document name}|.memo.dir| of the
- current directory. See section~\ref{sec:tut:memodir} for the tutorial.
-
- This key sets \refmmzpath{relative} to |false|, \refmmzpath{dir} to
- \meta{name}|.memo.dir|, \refmmzpath{prefix} to empty and \refmmz{mkdir} to
- |true|. In effect, memos and externs are placed in the subdirectory
- \meta{name}|.memo.dir| of the directory containing the document; their
- filenames contain no \meta{name} prefix, as \meta{name} already occurs in the
- directory name. The latter feature also makes it easy for projects (or parts
- of a project) to share memos and externs; see section~\ref{sec:tut:multifile}
- for a typical usage case.
+ A convenient way to store memos and externs in a dedicated directory (see
+ sections~\ref{sec:tut:memodir} and~\ref{sec:tut:multifile} for the tutorial).
+
+ Without an argument, this key places these files in subdirectory
+ \meta{document name}|.memo.dir| of the current directory. With an argument,
+ these files are stored in subdirectory \meta{name}|.memo.dir|. In both
+ cases, memo and extern filenames contain no prefix, as \meta{name} already
+ occurs in the directory name.
+
+ The definition of this style is very simple: |memo
+ dir/.style={|\refmmz{prefix}|={#1.memo.dir/}}| (note the slash). Feel free
+ to redefine it, or to define another style to suit your needs.
\end{doc}
\begin{doc}{easy,key={name=no memo dir,desc=style}}
A convenient way to undo the effect of \refmmz{memo dir} and revert to the
- initial settings where memos and externs are located in the current
- directory. See section~\ref{sec:tut:memodir} for the tutorial.
-
- This key resets the subkeys of \refmmz{path} to their initial values, and
- sets \refmmz{mkdir} to |false|.
+ initial \refmmz{prefix} setting which locates memos and externs in the
+ current directory, with filenames prefixed by \meta{document name}|.| (mind
+ the dot).
\end{doc}
\begin{doc}{
- key={name=path, par=\meta{keylist}, desc=style},
+ key={name=prefix, par=\meta{prefix},
+ desc={no default, initially \texttt{\string\jobname.}}},
}
-
- Set the \emph{path prefix} to memo and extern files by executing the given
- \meta{keylist} in keypath \docaux{key path}{mmz/path}.
-
- The path prefix contains both the location of the memo\slash extern files
- (set by \refmmzpath{relative} and \refmmzpath{dir}) and the initial,
- fixed part of their filenames (set by \refmmzpath{prefix}):
- \begin{center}
- \meta{path prefix}=[|./|]\meta{directory}|/|\meta{prefix}
- \end{center}
- where ``|./|'' is only present when \refmmzpath{relative} is in effect.
- Given the \meta{path prefix}, this is how the paths to memos and externs are
- constructed:
+ This key determines the location of memo and extern files, and the initial
+ part of their filenames.
+
+ If \meta{prefix} contains no |/|, memos and externs are stored in the current
+ directory, alongside the output PDF, and their filenames begin with
+ \meta{prefix} (and continue with the identifier, see below). For example,
+ this is what happens with the initial value of this key, where memo and
+ extern filenames are prefixed by \meta{jobname} (i.e.\ the name of the
+ document) plus |.| (a dot). Such a situation is shown in the |dirty-house|
+ example in section~\ref{sec:tut:memodir}.
+
+ If \meta{prefix} contains slashes, everything up to the final slash
+ determines the directory which memos and externs will be stored into, and the
+ part after the final slash determines the starting part of their filenames; a
+ slash (|/|) as a directory separator should be used even on Windows, where
+ the system directory separator is a backslash (|\|). For example,
+ \refmmz{memo dir} sets \refmmz{prefix} to |\jobname.memo.dir/|. As shown in
+ the |clean-house| example in section~\ref{sec:tut:memodir}, this results in
+ memos and externs stored in directory \meta{jobname}|.memo.dir|, with their
+ filenames consisting solely of the identifier.
+
+ In detail, the paths to memos and externs are constructed as shown below,
+ where \meta{code md5 sum} and \meta{context md5 sum} identify the memoized
+ code and the \refmmz{context} in effect at its memoization, and $N$ is the
+ sequential number of the extern with respect to that code and context ($N$ is
+ usually $0$, as memoization normally produces a single
+ extern).\footnote{Normally, these paths are relative to the current directory
+ (i.e.\ the directory where \hologo{TeX} is executed from; usually, this
+ will be the directory where the compiled |.tex| file resides). However,
+ when \hologo{TeX} is invoked with option |-output-directory|, these paths
+ are relative to the specified output directory. Furthermore, when a memo
+ or extern cannot be written into the current\slash output directory, it
+ will be stored into the temporary directory \docref{TEXMFOUTPUT}, if
+ specified.}
\begin{center}
\begin{tabular}{r@{ }l}
- c-memo:&\meta{path prefix}\meta{code md5 sum}|.memo|\\
- cc-memo:&\meta{path prefix}\meta{code md5 sum}|-|\meta{context md5 sum}|.memo|\\
- extern:&\meta{path prefix}\meta{code md5 sum}|-|\meta{context md5 sum}-$N$|.pdf|\\
+ c-memo:&\meta{prefix}\meta{code md5 sum}|.memo|\\
+ cc-memo:&\meta{prefix}\meta{code md5 sum}|-|\meta{context md5 sum}|.memo|\\
+ extern:&\meta{prefix}\meta{code md5 sum}|-|\meta{context md5 sum}-$N$|.pdf|\\
&(where ``-$N$'' is only present when $N\neq 0$, i.e.\ for non-first externs)\\
\end{tabular}
\end{center}
-
- For example, the default \refmmz{no memo dir} \meta{path prefix} equals
- \meta{jobname}|.| (note the dot), and after issuing \refmmz{memo dir}, the
- \meta{path prefix} is \meta{jobname}|.memo.dir|. Executing
- \refmmz{path}|=|\bracestt{\refmmzpath{dir}=project-memo-dir,
- \refmmzpath{prefix}=\string\jobname.} would set the memo prefix to
- |./project-memo-dir/|\meta{jobname}|.|, allowing the author to keep the memo
- files of all documents compiled from the project directory in the same place.
- The following keys may occur in the \meta{keylist}:
- \yadocset{keypath=/mmz/path}
-
- \begin{doc}{
- key={name=relative, conditional=true},
- }
-
- This key determines whether the location is relative to the current
- directory, i.e.\ the directory where \hologo{TeX} is executed from;
- usually, this will be the directory where the compiled |.tex| file resides.
- When set to |true| (the default), the directory set by \refmmzpath{dir}
- is prefixed by ``|./|''.
-
- You will probably need to set |openout_any=a| in \reffile{texmf.cnf}
- to allow writing to an arbitrary directory --- but remember, this might be
- dangerous!
- \end{doc}
-
- \begin{doc}{
- key={name=dir, par=\meta{name}, desc={no default, initially empty}},
- }
-
- Set the \meta{directory} where Memoize will search for memos and externs
- and\slash or create them in.
-
- Unless \refmmzpath{relative} is set to |false|, this location is
- relative to the current directory. Given the empty default, memos and
- externs are therefore created in the directory holding the source file by
- default.
-
- In principle, the given memo directory must already exist, but see
- \refmmz{mkdir}.
- \end{doc}
+ \begin{tcolorbox}[warning,title=The final slash matters]
+ To reiterate, the presence vs.\ absence of a slash (|/|) determines whether
+ memos and externs are stored in a dedicated directory or not. For example,
+ if you want to store memos and externs in directory |memos|, you should set
+ \code{\refmmz{prefix}=memos/}, with the final slash. Without the final
+ slash, these files would end up in the current directory.
+ \end{tcolorbox}
- \begin{doc}{
- key={name=prefix, par=\meta{name},
- desc={no default, initially \texttt{\string\jobname.}}},
- }
+ In principle, the directory specified by this key must already exist.
+ However, Memoize does it best to create it for you, and should succeed at
+ least when extraction method is set to \refmmz{extract=perl} or
+ \refmmz{extract=python}. See also \refmmz{mkdir} and \refmmz{mkdir command}.
- Set the \meta{prefix}, i.e.\ the initial part of the memo\slash extern
- filename. Initially, it is set to the document name.
- \end{doc}
+ When invoked from the document body, each invocation of this key records the
+ new prefix by invoking \refmmz{record/record type/prefix} (this typically
+ results in a \refcmd{mmzPrefix} entry in the \dmmz file) and attempts to
+ create memo directory if \refmmz{mkdir} is in effect. When \refmmz{prefix}
+ is executed in the document preamble, these actions are only carried out at
+ the beginning of the document, for the final value of the key.
- After processing the \meta{keylist}, \refmmz{path} records the new path
- prefix by invoking \refmmz{record/record type/prefix}, which typically results
- in a \refcmd{mmzPrefix} entry in the \dmmz file, and attempts to create the
- \meta{directory} is \refmmz{mkdir} is in effect --- except when \refmmz{path}
- is executed in the document preamble: then, these actions are only carried
- out at the beginning of the document, for the final value of the keys.
+
\end{doc}
-\begin{doc}{key={name=mkdir, conditional=false}}
- When this key is set to true, Memoize will attempt to create the directory
- set by \refmmz{path} if it does not yet exist. The directory creation takes
- place at the beginning of the document and at every subsequent invocation of
- key \refmmz{path}, using the system command specified by \refmmz{mkdir
- command}.
+\begin{doc}{key={name=mkdir, conditional=true}}
+ When this key is set to true and the value of \refmmz{mkdir command} is
+ non-empty, Memoize will attempt to create the memo directory set by
+ \refmmz{prefix} if it does not yet exist.
+
+ The directory is created using the system command specified by \refmmz{mkdir
+ command}. The directory creation takes place at the beginning of the
+ document and at every subsequent invocation of key \refmmz{prefix}.
\end{doc}
\begin{doc}{
- key={name=mkdir command, par=\meta{system command invocation},
- desc={no default, initially |mkdir "\#1"|}},
+ key={name=mkdir command, par=\meta{system command},
+ desc={no default, initially empty}},
}
- Sets the command used to create \refmmzpath{dir} when \refmmz{mkdir} is in effect.
-
- This is a system command, so an appropriate shell escape mode must be in
- effect to execute it successfully.
-
- The default should work on Linux, MacOS and Windows. On Linux systems, it
- makes sense to change the default (using \reffile{memoize.cfg}) to |mkdir
- -p "#1"|, which can create parent folders as necessary.
-
- Note that extraction methods \refmmz{extract=perl} and
+ This key sets the command used to create the memo directory specified
+ by \refmmz{prefix} when \refmmz{mkdir} is in effect.
+
+ Memoize attempts to create the directory by executing \meta{system command}
+ followed by (a space and) \meta{directory}, where \meta{directory} is the
+ directory part of the \meta{prefix} set by \refmmz{prefix}. The directory
+ creation takes place at the beginning of the document and at every subsequent
+ invocation of key \refmmz{prefix}.
+
+ This key could be set to |mkdir| (on Linux, |mkdir -p| would be an even
+ better choice), however, this is not advisable as |mkdir| does not respect
+ \hologo{TeX}'s output restrictions, set by \docref{openout_any} in
+ \hologo{TeX}Live and |[Core]AllowUnsafeInputFiles| in MiK\hologo{TeX}.
+ Further note that as the value of this key is a system command, an
+ appropriate shell escape mode must be in effect to execute it successfully;
+ again, not something to be taken lightly.
+
+ The extractions scripts shipped with Memoize accept option
+ \refscript{memoize-extract.pl--mkdir}, which makes them behave as a safe
+ variant of |mkdir|, i.e.\ a |mkdir| which respects \hologo{TeX}'s output
+ restrictions. Therefore, extraction methods \refmmz{extract=perl} and
\refmmz{extract=python} set \refmmz{mkdir command} to
\refscript{memoize-extract.pl} \refscript{memoize-extract.pl--mkdir} and
\refscript{memoize-extract.py} \refscript{memoize-extract.pl--mkdir},
- respectively. Unlike system |mkdir|, these commands are safe in the sense
- that they conform to the paranoid openout regime (|openout_any=p|).
-\end{doc}
-
-\begin{doc}{
- key={name=output-directory, par=\meta{directory},
- desc={no default, initially undefined, as package option only}},
- cmd={name=mmzOutputDirectory, par=\meta{directory}, desc={pre-defineable}}
- }
- If the \hologo{TeX} binary was invoked with option |-output-directory|, one
- should use this key or define this macro to inform Memoize this, as there is
- no way to learn about it automatically.
-
- The given \meta{directory} must end with a slash (|/|).
-
- The key is only allowed as a package option. But I imagine that defining the
- macro from the command line, prior to inputting the document source, will be
- more common:
- \begin{center}
- |pdftex -output-directory somedir '\def\mmzOutputDirectory{somedir/}\input doc'|
- \end{center}
+ respectively. In effect, as the initial extraction method is
+ \refmmz{extract=perl}, the memo directory (if set via \refmmz{prefix} or
+ \refmmz{memo dir}) should be created under the initial settings without any
+ user intervention.
\end{doc}
@@ -6262,11 +6282,14 @@ require.
Out of the box, Memoize recognizes the following \meta{extraction method}
keywords: \docAux{easy, of=key:/mmz/extract, value={name=perl}, comma,
- value={name=python}, comma, value={name=tex}, and, value={name=no}}. The final
- keyword (not available in \hologo{plainTeX}) instructs Memoize to \emph{not}
- perform to extraction; it should be used when extraction is performed
- externally (for details, see section~\ref{sec:setup}). Additional methods
- may be installed by defining key \docaux{key
+ value={name=python}, comma, value={name=tex}, and, value={name=no}}. The
+ first three keywords trigger extern extraction using the methods documented
+ in sections~\ref{sec:ref:extraction-perl-python}
+ and~\ref{sec:ref:extraction-tex}. The final keyword (not available in
+ \hologo{plainTeX}) instructs Memoize to \emph{not} perform the extraction at
+ the end of loading the package; it should be used when extraction is
+ performed externally (for details, see section~\ref{sec:setup}). Additional
+ methods may be installed by defining key \docaux{key
path}{mmz/extract}|/|\meta{extraction method}.
When invoked from \reffile{memoize.cfg} or used as a package option, this key
@@ -6276,26 +6299,31 @@ require.
\reffile{memoize.cfg}, which in turn overrides the package-initial value
\refmmz{extract=perl}.
- In \hologo{LaTeX} and \hologo{ConTeXt}, the selected method is executed at
- the end of loading the package. Afterwards, the key is disabled. If you
- really need to invoke extraction in the preamble, or again, write
- \refmmz{extract}|/|\meta{method}.
-
- When invoked from a \refcmd{mmzset} in the document preamble, this key
- immediately \emph{executes} the given extraction method. In the preamble
- incarnation, the key may be invoked without a value to execute the previously
- selected method.
-
- As we want to allow a \hologo{plainTeX} author to override the extraction
- method specified by the package (\refmmz{extract=perl}) or in
- \reffile{memoize.cfg}, Memoize does \emph{not} perform extern extraction
- while loading the package in this format. In \hologo{plainTeX}, internal
- extraction can only be triggered by an explicit invocation of
- \refmmz{extract} in the ``document preamble'' --- i.e.\ between |\input
- memoize| and \refcmd{mmzset}\braces{\refmmz{begin document}}. In this case,
- the key does not require an argument; invoking it without the argument will
- execute either the package-default, \refmmz{extract=perl}, or whatever the
- user had selected in \reffile{memoize.cfg}.
+ In \hologo{LaTeX} and \hologo{ConTeXt}, the selected method is automatically
+ executed at the end of loading the package. This does not happen in
+ \hologo{plainTeX}, because we want to allow the author to override the
+ initial extraction method (\refmmz{extract=perl}) or the extraction method
+ specified in \reffile{memoize.cfg}, even if \hologo{plainTeX} has no package
+ options. In \hologo{plainTeX}, internal extraction must be triggered by an
+ explicit invocation of \refmmz{extract} in the ``document preamble'' ---
+ i.e.\ between |\input memoize| and \refcmd{mmzset}\braces{\refmmz{begin
+ document}}. If given an \meta{extraction method} argument, the key will
+ execute the given extraction method; otherwise, it will execute either the
+ initial method, \refmmz{extract=perl}, or the overwide specified in
+ \reffile{memoize.cfg}.
+
+ In general, Memoize guards against triggering the extraction more than once.
+ In formats other than \hologo{plainTeX}, invoking key \refmmz{extract} from
+ the document preamble is thus only allowed when
+ \refmmz{extract}|=|\refmmz{extract=no} was previously selected. In this
+ case, \refmmz{extract} in the document preamble behaves as in
+ \hologo{plainTeX}, i.e.\ it triggers the given extraction method; if no
+ extraction method is given, Memoize executes either the initial extraction
+ method \refmmz{extract=perl}, or the extraction method specified in
+ \reffile{memoize.cfg}. The possibility of invoking key \refmmz{extract} from
+ the document preamble makes it possible to bake Memoize into a user format
+ (with \refmmz{extract}|=|\refmmz{extract=no}), and trigger the internal
+ extraction manually.
Executing extraction method \refmmz{extract=perl} or \refmmz{extract=python}
has an additional effect of setting the \refmmz{mkdir command} to the
@@ -6307,51 +6335,49 @@ require.
\subsubsection{Perl- and Python-based extraction}
\label{sec:ref:extraction-perl-python}
+Perl- and Python-based extraction is triggered by
+\refmmz{extract}|=|\refmmz{extract=perl} and
+\refmmz{extract}|=|\refmmz{extract=python}, respectively.
+
\begin{doc}{
key={name=perl extraction command, par=\meta{system command},
desc={no default, initially \refscript{memoize-extract.pl}}},
key={name=perl extraction options, par=\meta{options},
- desc={no default, initially: see below}},
- key={name=python extraction command, par=\meta{system command},
- desc={no default, initially \refscript{memoize-extract.py}}},
- key={name=python extraction options, par=\meta{options},
- desc={no default, initially: see below}},
+ desc={no default, initially: \code{\refscript[short]{memoize-extract.pl--format}
+ \meta{format} \string\jobname}}},
+ key={name=python extraction command, par=\meta{system command}, desc={no
+ default, initially \refscript{memoize-extract.py}}}, key={name=python
+ extraction options, par=\meta{options}, desc={no default, initially:
+ \code{\refscript[short]{memoize-extract.pl--format} \meta{format}
+ \string\jobname}}}
}
+
These keys determine the system calls used for invoking the extraction
scripts \refscript{memoize-extract.pl} and \refscript{memoize-extract.py}.
All the details below apply both to the Perl and the Python version.
Use |perl|\slash|python extraction command| to set the name of the extraction
- script. If necessary, include the full path to the scrip, or |perl|\slash
+ script. If necessary, include the full path to the script, or |perl|\slash
|python| plus the path to the script. Whatever you set here must be allowed
by the shell escape mode.
Use |perl|\slash|python extraction options| to set the options that the
script will receive; consult the documentation of
- \refscript{memoize-extract.pl} for their meaning.
-
- The initial value of \refmmz{perl extraction options}, shown below, (i) sets
- embedded mode, which prefixes each output line with the script name, (ii)
- requests a log file named \meta{jobname}|.mmz.log| (|-l|; |\input|ting the
- log after extraction informs Memoize, and the author, whether extraction was
- successful), and (iii) sets the warning template suitable for the format
- (|-w|; this way, any warning messages issued by the script can be reissued by
- the compilation of the document).
-
- These keys were initialized using \pkg{pgfkeys} handler |.initial|, so their
- values may be modified by handlers |.prefix|, |.append|, etc. During the
- execution of the system call, their values are fully expanded --- thus the
- |\string|s in the initial options value below. The guards
- (|%<latex>| etc.) make sure that
- each format asks for a warning message it understands (the code shown below
- is an excerpt from the |.dtx| file; ultimately, each format receives a single
- of the three guarded lines).
-
- \makeexcerpt{perl-extraction-options}
- \tcbinputexample[.tex][.excerpt]{listing only, one file, no attachment,
- title={The initial value of \refmmz{perl extraction
- options}}, full width, left=-2mm, right=0mm, listing options app={
- morecomment={[s]{\%<}>} }, }
+ \refscript{memoize-extract.pl} for their meaning. The initial value asks the
+ script to process \meta{document name}\dmmz, and informs it that it is
+ executed from within a \hologo{TeX} compilation of a document in the given
+ \meta{format} (|latex| for \hologo{LaTeX}, |plain| for \hologo{plainTeX},
+ |context| for \hologo{ConTeXt}); the latter makes the script yield any
+ warnings or errors in a form expected by the format.
+
+ During the execution of a system call, the values of these settings are fully
+ expanded. Furthermore, these keys were initialized using \pkg{pgfkeys}
+ handler |.initial|, so their values may be modified by handlers |.prefix|,
+ |.append|, etc. The initial value of the extraction options contains a space
+ on both sides, so that these handlers are easy to use. For example, write
+ \code{\refmmz{perl extraction
+ options}/.append=\refscript{memoize-extract.pl--quiet}} to ask
+ for less output.
\end{doc}
\begin{doc}{
@@ -6363,13 +6389,20 @@ require.
\refmmz{extract}|=|\refmmz{extract=perl} (the default) or
\refmmz{extract}|=|\refmmz{extract=python}.
+ Argument \meta{name}\dmmz may be given either in full (e.g.\ |doc.mmz|), or
+ merely as the stem (|doc|). In the latter case, |.mmz| is appended to the
+ given argument even if it already contains a suffix (e.g.\ |my.doc| will
+ result in |my.doc.mmz|); the exception is suffix |.tex|, which is replaced by
+ |.mmz|.
+
The script inspects the given record file, \meta{name}\dmmz, for lines of
form \refcmd{mmzNewExtern}\marg{extern filename}\marg{extern page
number}\braces{\meta{expected width}\texttt{pt}}\braces{\meta{expected
- height}\texttt{pt}}. For each such line, page number
- \meta{extern page number} is extracted from \meta{name}|.pdf| into
- \meta{extern filename}. Other lines are ignored (and so are commented
- invocations of \refcmd{mmzNewExtern}).
+ height}\texttt{pt}}. For each such line, page number \meta{extern page
+ number} is extracted from \meta{name}|.pdf| into \meta{extern filename}.
+ (The script also creates directories specified by \refcmd{mmzPrefix} lines.
+ Other lines are ignored, and so are commented invocations of
+ \refcmd{mmzNewExtern} and \refcmd{mmzPrefix}.)
The \meta{extern filename} may contain a (relative or absolute) path to the
new extern file. The relative paths are relative to the location of the
@@ -6386,22 +6419,23 @@ require.
The extraction script's paranoia extends further. It will refuse to extract
the page, yielding a warning, if a (c)c-memo associated to the extern does
- not exist. And it will refuse (yielding a runtime error) to write to any
- file whose absolute path does not occur under the current directory or the
- directory set by \docref{TEXMFOUTPUT} (in \reffile{texmf.cnf} or as an
- environment variable); \docref{TEXMFOUTPUT} is also not allowed to point to
- the root directory, except on Windows, where it can only point to a drive
- root.
-
- \yadocset{
- of=script:memoize-extract.pl,
- override/.style={
- index annotation={option of {%
+ not exist. And it will respect the |openin| and |openout| settings of
+ |texmf.cnf|; under the default configuration, it will therefore refuse
+ (yielding an error) to write to any file whose absolute path does not occur
+ under the current directory, the temporary directory set by
+ \docref{TEXMFOUTPUT} (in \reffile{texmf.cnf} or as an environment variable),
+ or the output directory (\docref{TEXMF_OUTPUT_DIRECTORY}). Furthermore, it
+ will refuse to write into the root directory (except on Windows, where
+ writing into a drive root might potentially make sense).
+
+ Exit codes: 0 = success, 1 = Perl/Python error, 2 = usage error, 10 =
+ extraction warning, 11 = extraction error.
+
+ \yadocset{ of=script:memoize-extract.pl, override/.style={ index
+ annotation={option of {%
\hypercolor{link}{gray}%
\hyperref[script:memoize-extract.pl]{\texttt{memoize-extract}}%
- }},
- },
- }
+ }}, }, }
\begin{doc}{
option={name=pdf, short name=P, par=\meta{pdf}}
}
@@ -6423,34 +6457,31 @@ require.
prevents this behaviour.
\end{doc}
\begin{doc}{
- option={name=log, short name=l, parameters=\meta{filename}}
+ option={name=format, short name=F,
+ par=\code{latex}\Alt\code{plain}\Alt\code{context}}
}
- When given this option, the script creates a log file with the given name.
- The log will receive any warnings yielded by the script, see also
- \refscript{memoize-extract.pl--warning-template}. A well-formed log ends
- with |\endinput|, indicating that the \dmmz file was completely processed.
+ When this option is given, the script assumes that it was called from
+ within a \hologo{TeX} compilation of a document in the given format
+ (|latex| for \hologo{LaTeX}, |plain| for \hologo{plainTeX}, |context| for
+ \hologo{ConTeXt}).
- In absence of this option, the warnings are printed to the standard error.
- \end{doc}
- \begin{doc}{
- option={name=warning-template, short name=w, parameters=\meta{template}},
- }
- When this option is given, a warning message is not logged (or printed to
- standard error) as-is; it is wrapped by the template given as the argument
- of this option. More precisely, the script will log the given
- \meta{template}, with all occurrences of \docaux{cmd}{warningtext}
- substituted by the actual warning message.
-
- The option can be used to propagate warnings to the upstream compilation,
- simply by setting this option appropriately (see the initial value of
- \refmmz{python extraction options}) and |\input|ting the log after
- extraction.
+ For one, the script will prefix all its output by the script name, to be
+ easily identifiable in the terminal output, but more importantly, it will
+ create a log file (\code{\string\jobname.mmz.log}) which will receive any
+ warnings and errors yielded by the script (in absence of this option, the
+ warnings and errors are printed to the standard error). The log contains
+ messages in a form recognized by the format (e.g.\ \cs{PackageError} for
+ \hologo{LaTeX}), so Memoize can then \cs{input}s this log to reproduce the
+ warnings and errors in the compilation.
+
+ There should be no need to use this option when executing the script form
+ the command line.
\end{doc}
\begin{doc}{
option={name=force, short name=f}
}
- Force extern extraction even if the size-check fails. The failure will
- still be logged.
+ Force extern extraction even if the size check or (c)c-memo existence check
+ fails. The failure will still yield a warning.
\end{doc}
\begin{doc}{
option={name=quiet, short name=q}
@@ -6460,12 +6491,11 @@ require.
it is extracting. This option disables this behaviour.
\end{doc}
\begin{doc}{
- option={name=embedded, short name=e}
- }
- When given this option, everything printed to the standard output is
- prefixed by the script name. The idea is to use this option when calling
- the script from the \hologo{TeX} compilation, as it makes it easy to
- identify the output of the script.
+ option={name=library, short name=L,
+ par=\texttt{PDF::API2\Alt PDF::Builder}, desc=Perl-only}
+ }
+ Use the specified library for PDF processing. If this option is not given,
+ the script uses the first available library (in the order given above).
\end{doc}
\begin{doc}{
option={name=mkdir, short name=m}
@@ -6488,14 +6518,14 @@ require.
\refmmz{extract=python}.
\end{doc}
\begin{doc}{
- option={name=help, short name=h}
+ option={name=version, short name=V}
}
- Show help.
+ Show Memoize version.
\end{doc}
\begin{doc}{
- option={name=version, short name=V}
+ option={name=help, short name=h}
}
- Show Memoize version.
+ Show help.
\end{doc}
Functionally, the Perl (|.pl|) and the Python (|.py|) version of the script
@@ -6525,6 +6555,9 @@ require.
\subsubsection{\texorpdfstring{\hologo{TeX}}{TeX}-based extraction}
\label{sec:ref:extraction-tex}
+\hologo{TeX}-based extraction is triggered by
+\refmmz{extract}|=|\refmmz{extract=tex}.
+
\begin{doc}{
key={name=tex extraction command, par=\meta{system command},
desc={no default, initially |pdftex|}},
@@ -6714,25 +6747,25 @@ require.
script={name=memoize-clean.py, par=[\meta{options}] [\meta{name}\texttt{.mmz} \dots]},
}
This script removes memo and extern files whose filenames start with
- \meta{path prefix}es mentioned in the given \dmmz files or by the
+ \meta{prefix}es mentioned in the given \dmmz files or by the
\refscript{memoize-clean.pl--prefix} option. Unless option
\refscript{memoize-clean.pl--all} is given, the script only deletes the
\emph{stale} files, i.e.\ the files not mentioned in any of the given \dmmz
files.
- A \meta{path prefix} of a memo or an extern is what was set by key
- \refmmz{path}, or more commonly, one of the shortcut keys \refmmz{memo dir}
+ A \meta{prefix} of a memo or an extern is what was set by key
+ \refmmz{prefix}, or more commonly, one of the shortcut keys \refmmz{memo dir}
and \refmmz{no memo dir}; see section~\ref{sec:ref:dirs} for details on the
form of a memo/extern filename.
In detail, the script scans the given \dmmz files for occurrences of
- \refcmd{mmzPrefix}, and adds their \meta{path prefix} arguments to the list
- of prefixes given on the command line by option
- \refscript{memoize-clean.pl--prefix}; a \meta{path prefix} occurring in some
- \dmmz file is interpreted relatively to the location of the \dmmz file. The
- script removes all files whose full pathname (relative to the current
- directory) matches pattern \meta{path
- prefix}\meta{md5sum}(|-|\meta{md5sum})(|.memo|\alt(|-|$N$)|.pdf|\alt|.log|),%
+ \refcmd{mmzPrefix}, and adds their \meta{prefix} arguments to the list of
+ prefixes given on the command line by option
+ \refscript{memoize-clean.pl--prefix}; a \meta{prefix} occurring in some \dmmz
+ file is interpreted relatively to the location of the \dmmz file. The script
+ removes all files whose full pathname (relative to the current directory)
+ matches pattern
+ \meta{prefix}\meta{md5sum}(|-|\meta{md5sum})(|.memo|\alt(|-|$N$)|.pdf|\alt|.log|),%
\footnote{The |.log| files are produced by the \hologo{TeX}-based extraction
script.} except those which occur as the \meta{filename} argument to one
of \refcmd{mmzUsedCMemo}, \refcmd{mmzUsedCCMemo}, \refcmd{mmzUsedExtern},
@@ -6758,9 +6791,9 @@ require.
},
}
\begin{doc}{
- option={name=prefix, short name=p, par=\meta{path prefix}}
+ option={name=prefix, short name=p, par=\meta{prefix}}
}
- Add \meta{path prefix} to the list of prefixes; the given prefix is
+ Add \meta{prefix} to the list of prefixes; the given prefix is
relative to the current directory. This option may be given multiple
times.
\end{doc}
@@ -6817,7 +6850,7 @@ require.
\yadocset{of=key:/mmz/record}
\begin{doc}{value={name=mmz}}
This record type produces a \dmmz file recording new/used
- externs/c-memos/cc-memos and changes in the \refmmz{path} prefix to these
+ externs/c-memos/cc-memos and changes in the \refmmz{prefix} to these
files; see section~\ref{sec:.mmz} for details.
The produced file is named \docAux{file={name=mmz,
@@ -6830,8 +6863,8 @@ require.
the \hologo{TeX} syntax, to be easily parsable by the external scripts such
as \refscript{memoize-extract.pl}. Each line of the file consists of a
(possibly commented) invocation of one of the commands listed below; the
- final line is |\endinput|. The \meta{path prefix} below consists of the
- path to memos/externs and the immutable \refmmzpath{prefix} of their filename.
+ final line is |\endinput|. The \meta{prefix} below consists of the
+ path to memos/externs and the immutable \refmmz{prefix} of their filename.
\begin{doc}{
cmd={name=mmzUsedCMemo, par=\marg{filename}},
cmd={name=mmzUsedCCMemo, par=\marg{filename}},
@@ -6856,8 +6889,8 @@ require.
footnote~\ref{fn:tolerance}), where the height is the total height
comprising both \hologo{TeX} height and depth.
\end{doc}
- \begin{doc}{cmd={name=mmzPrefix, par=\marg{path prefix}}}
- Record that the \refmmz{path} prefix of memo and extern files was
+ \begin{doc}{cmd={name=mmzPrefix, par=\marg{prefix}}}
+ Record that the \refmmz{prefix} of memo and extern files was
changed.
\end{doc}
\end{doc}
@@ -6866,7 +6899,7 @@ require.
utility, triggers \hologo{TeX}-based extraction of the new externs.
\begin{doc}{
key={name=makefile, par=\marg{filename}, desc={no default,
- initially \texttt{memoize-extract.\jobname.makefile}}}
+ initially \texttt{memoize-extract.\string\jobname.makefile}}}
}
Use this key to change the filename of the produced makefile.
\end{doc}
@@ -6880,9 +6913,9 @@ require.
\begin{doc}{
key={name=sh, par=\marg{filename}, desc={no default,
- initially \texttt{memoize-extract.\jobname.sh}}},
+ initially \texttt{memoize-extract.\string\jobname.sh}}},
key={name=bat, par=\marg{filename}, desc={no default,
- initially \texttt{memoize-extract.\jobname.bat}}}
+ initially \texttt{memoize-extract.\string\jobname.bat}}}
}
Use these keys to change the filename of the produced shell script.
\end{doc}
@@ -6896,7 +6929,7 @@ require.
name prefix={\meta{record type}/}, ref prefix=,
index annotation={key in \texttt{/mmz/\meta{record type}}},
key={name=begin},
- key={name=prefix, par=\marg{path prefix}},
+ key={name=prefix, par=\marg{prefix}},
key={name=new extern, par=\marg{filename}},
key={name=new cmemo, par=\marg{filename}},
key={name=new ccmemo, par=\marg{filename}},
@@ -6959,7 +6992,7 @@ automatically memoize the results of compilation of selected commands and
environments.
\begin{doc}{
- keypath={/handlers},
+ keypath={/handlers}, desc=handler,
key={name=.install advice,
sort index=install advice,
par=\marg{configuration keylist},
@@ -7089,7 +7122,7 @@ headers.
\begin{doc}{
key={name=activation, par=\docAux{of=key:/mmz/activation, value={name=immediate},
- text=\Alt, value={name=deferred}}, desc={style, no default}},
+ text=\Alt, value={name=deferred}}, desc={no default, initially set by \refkey{/handlers/.install advice}}},
key={name=activate deferred, desc={style}},
}
Key \refmmz{activation} selects the activation regime. Under the
@@ -7221,6 +7254,15 @@ headers.
details, see sections~\ref{sec:tut:automemoization},
\ref{sec:tut:working-on-a-picture} and~\ref{sec:tut:verbatim}.
+ Note that in \hologo{LaTeX}, advising an environment (say, |bar|) is
+ different than advising the internal command (|\bar|) containing the
+ \meta{begin-code} of the environment, created by \cs{newenvironment}.
+ Advising environment |bar| effectively replaces the entire
+ |\begin{bar}...\end{bar}| construction, so that \hologo{LaTeX}'s |\begin|
+ code is never executed. The advice of command |\bar|, on the other hand,
+ is executed after |\begin| initializes the environment; in particular, it
+ is executed within the group introduced by the environment.
+
The advice is configured by the given \meta{keylist}, which is executed with
the default keypath set to \docaux{key path}{mmz/auto}. Any unknown keys in
\meta{keylist} are passed on to key \refmmzauto{options}; for example, a
@@ -7280,7 +7322,8 @@ headers.
In general, the name of this key equals whatever was submitted to
\refkey{/advice/install/setup key} during the installation of the advising
- framework via \refkey{/handlers/.install advice}.
+ framework via \refkey{/handlers/.install advice}; the initial value is
+ |advice| (and Memoize sets the value to |auto|).
\end{doc}
\begin{doc}{
@@ -7456,9 +7499,10 @@ headers.
commonly used.
\item the macros holding information about the namespace and the advised
command or environment: \refcmd{AdviceNamespace}, \refcmd{AdviceName},
- \refcmd{AdviceReplaced} and \refcmd{AdviceOriginal}. (Command
- \refcmd{AdviceGetOriginal} might also be useful, although using
- \refcmd{AdviceOriginal} will likely be more practical.)
+ \refcmd{AdviceCsname}, \refcmd{AdviceReplaced} and \refcmd{AdviceOriginal}.
+ (Commands \refcmd{AdviceGetOriginal} and \refcmd{AdviceCsnameGetOriginal}
+ might also be useful, although using \refcmd{AdviceOriginal} will likely be
+ more practical.)
\end{itemize}
This key is initially set to an internal control sequence which merely
@@ -7711,8 +7755,8 @@ the begin tag, e.g.\ |\begin{foo}|) and \refcmd{AdviceOriginal} (which executes
The inner handler has access to all the macros available to the outer
handler, but given that most of them have already fulfilled their function,
only the following will likely be useful in the inner handler:
- \refcmd{AdviceNamespace}, \refcmd{AdviceName}, \refcmd{AdviceReplaced},
- \refcmd{AdviceOriginal}, and \refcmd{AdviceOptions}.
+ \refcmd{AdviceNamespace}, \refcmd{AdviceName}, \refcmd{AdviceCsname},
+ \refcmd{AdviceReplaced}, \refcmd{AdviceOriginal}, and \refcmd{AdviceOptions}.
Because there is clearly no reasonable default for the inner handler, this key
is initially set to an internal control sequence producing an ``undefined
@@ -7779,14 +7823,15 @@ the begin tag, e.g.\ |\begin{foo}|) and \refcmd{AdviceOriginal} (which executes
\paragraph{Commands available during the execution of advice}
-With the exception of \refcmd{AdviceGetOriginal}, the commands listed below
-only become available in the outer handler, and if that handler does nothing
-funky, they should be available in the collector and the inner handler, as
-well. However, once the advice yields control to foreign code, these macros
-are not guaranteed to hold the expected values anymore, because the foreign
-code might trigger another piece of advice. Consequently, these macros
-should be expanded, once, before integrating them into arbitrary (non-advice)
-code; in particular, this applies to \refcmd{AdviceOriginal}.
+With the exception of \refcmd{AdviceGetOriginal} and
+\refcmd{AdviceCsnameGetOriginal}, the commands listed below only become
+available in the outer handler, and if that handler does nothing funky, they
+should be available in the collector and the inner handler, as well. However,
+once the advice yields control to foreign code, these macros are not guaranteed
+to hold the expected values anymore, because the foreign code might trigger
+another piece of advice. Consequently, these macros should be expanded, once,
+before integrating them into arbitrary (non-advice) code; in particular, this
+applies to \refcmd{AdviceOriginal}.
\begin{doc}{cmd={name=AdviceNamespace}}
This macro holds the \meta{namespace}, i.e.\ the keypath which this instance
@@ -7800,6 +7845,12 @@ code; in particular, this applies to \refcmd{AdviceOriginal}.
\hologo{TeX} format), their name, e.g.\ |foo|.
\end{doc}
+\begin{doc}{cmd={name=AdviceCsname}}
+ This macro holds the control sequence name of the advised command; it is
+ undefined for environments. For example, when \refcmd{AdviceName} contains
+ |\foo@bar|, this macro will hold |foo@bar|.
+\end{doc}
+
\begin{doc}{cmd={name=AdviceReplaced}}
This macro holds the code which was replaced by the outer handler. For
commands, this will be the command itself, e.g.\ |\foo|, so
@@ -7821,23 +7872,27 @@ code; in particular, this applies to \refcmd{AdviceOriginal}.
\begin{doc}{cmd={name=AdviceGetOriginal, par=\marg{namespace}\marg{control sequence}}}
This command invokes the original definition of the \meta{control sequence}
- advised by the \meta{namespace} instantiation of Advice. It may be safely
- used outside the advice, even if the advised command is not activated.
+ advised by the \meta{namespace} instantiation of Advice; more precisely, the
+ full expansion of this macro produces the (internal) control sequence holding
+ the definition of \meta{control sequence} in effect when this control
+ sequence was activated in \meta{namespace}. This macro may be safely used
+ outside the advice, even if the advised command is not activated.
For example, upon executing key |/ns/|\refmmz{auto}|=\foo{...}|,
|\AdviceGetOriginal{/ns}{\foo}| will recall the original definition of |\foo|
if |\foo| is activated, and simply execute |\foo| otherwise.
The second argument of this command should \emph{not} be an environment name.
- To execute the original environment |foo| in \hologo{TeX} or
+ To execute the original environment |foo| in \hologo{plainTeX} or
\hologo{ConTeXt}, use \refcmd{AdviceGetOriginal} with the appropriate macro:
|\AdviceGetOriginal{/ns}{\foo}| or |\AdviceGetOriginal{/ns}{\startfoo}|. In
\hologo{LaTeX}, one should use |\AdviceGetOriginal{/ns}{\begin}{foo}|, which
- executes the original \refcmd{begin} and provides it with the environment name.
+ executes the original \refcmd{begin} and provides it with the environment
+ name.
Within the advice, you will probably never have to use this command directly,
but will rather rely on the (plain or pre-expanded) \refcmd{AdviceOriginal}.
- However, outside the advice, this command provides the only means to
+ However, outside the advice, this command provides the only official means to
access the original definition of an advised command. (Unlike the
commands described above, this command is available throughout the
document.)
@@ -7857,7 +7912,17 @@ code; in particular, this applies to \refcmd{AdviceOriginal}.
(|/|), but has no slash at the end.
\end{tcolorbox}
\end{doc}
-
+
+\begin{doc}{cmd={name=AdviceCsnameGetOriginal,
+ par=\marg{namespace}\marg{control sequence name}}}
+ This is a version of \refcmd{AdviceGetOriginal} which accepts a control
+ sequence name as the second argument.
+
+ This macro is used by auto-key \refmmzauto{to context} to include the meaning
+ of any command (even internal commands containing |@|, or \pkg{expl3}
+ commands) into the context.
+\end{doc}
+
\paragraph{Support for specific packages}
At the moment, Advice only implements specific support for \TikZ, by defining a
\refmmzauto{collector} for command \refcmd{tikz}.
@@ -7928,21 +7993,23 @@ be generalized to \meta{namespace} and \meta{setup key} of
\midrule
\refcmd{begin}&custom&Only in \hologo{LaTeX}; declared by Advice.\\
\refcmd{errmessage}&\refmmzauto{abort}&Not available in \hologo{LuaTeX},
- where better error-detection is implemented.\\
+ where better error-detection is implemented.\\
\refenv{forest}&\refmmzauto{memoize}\\
\refcmd{Forest}&\refmmzauto{memoize}\\
\refcmd{index}&\refmmzauto{replicate}&The argument is expanded prior to replication.\\
- \refcmd{label}&custom&Globally appends \refcmd{mmzLabel}\marg{label key}\marg{current label}
- to register \refcmd{mmzCCMemo}; \refmmzauto{run if memoizing}.\\
+ \refcmd{label}&custom&\refmmzauto{run if memoizing}; globally appends
+ \refcmd{mmzLabel}\marg{label key}\marg{current label}
+ to register \refcmd{mmzCCMemo}.\\
\refcmd{pageref}&\refmmzauto{ref}\\
\refcmd[into index=false]{pdfsavepos}&\refmmzauto{abort}&Not available in \hologo{LuaTeX}.\\
\refcmd{pgfsys@getposition}&\refmmzauto{abort}&Available only in \TikZ is
- loaded, it aborts memoization of a picture which gets accidentally marked as
- ``remembered''.\\
+ loaded, it aborts memoization of a picture which gets accidentally marked
+ as
+ ``remembered''.\\
\refcmd{ref}&\refmmzauto{ref}\\
\refcmd[short]{savepos}&\refmmzauto{abort}&Available only in \hologo{LuaTeX}.\\
- \refcmd{tikz}&\refmmzauto{memoize}\\
- \refenv{tikzpicture}&\refmmzauto{memoize}\\
+ \refcmd{tikz}&\refmmzauto{memoize tikz}\\
+ \refenv{tikzpicture}&\refmmzauto{memoize tikz}\\
\bottomrule
\end{tabularx}
\caption{Commands advised by Memoize}
@@ -8087,6 +8154,15 @@ be generalized to \meta{namespace} and \meta{setup key} of
preferred.
\end{doc}
+\begin{doc}{key={name=memoize tikz,desc=style}}
+ This key is used to declare memoization of \TikZ pictures; it may also be
+ used for PGF pictures. Besides executing \refmmzauto{memoize}, it uses
+ \refmmz{at begin memoization} and \refmmz{at end memoization} to add the code
+ which measures the increase of the PGF picture ID during memoization of a
+ picture, and increases this ID for the measured amount upon the utilization
+ of the extern, thereby making sure that a utilized \TikZ\slash\PGF extern
+ advances the PGF picture ID as if the picture was compiled.
+\end{doc}
\begin{doc}{easy,
key={name=ref,desc=style},
@@ -8150,6 +8226,19 @@ be generalized to \meta{namespace} and \meta{setup key} of
\end{doc}
\begin{doc}{
+ key={name=to context, desc=style}
+ }
+ This key sets up advice which appends the original meaning of the advised
+ command to the context. It invokes \refmmzauto{run if memoizing}, so that
+ the command is only advised during memoization.
+
+ It is safe to apply this key to internal commands such as commands whose name
+ contains |@|, or \pkg{expl3} commands. It is not necessary to provide the
+ argument structure of the advised command (using \refmmzauto{args}).
+
+\end{doc}
+
+\begin{doc}{
key={name=replicate, desc=style}
}
This key sets up advice which replicates the invocation of the command in
@@ -8458,10 +8547,10 @@ grab the environment body in the verbatim mode.)
\docaux{cmd}{collargsAppendPostprocessor},
\docaux{cmd}{collargsPrependPostprocessor}},
]{
- key={name=append preprocessor, par=\meta{code}, desc={no default}},
- key={name=prepend preprocessor, par=\meta{code}, desc={no default}},
- key={name=append postprocessor, par=\meta{code}, desc={no default}},
- key={name=prepend postprocessor, par=\meta{code}, desc={no default}},
+ key={name=append preprocessor, par=\meta{code}, desc={style, no default}},
+ key={name=prepend preprocessor, par=\meta{code}, desc={style, no default}},
+ key={name=append postprocessor, par=\meta{code}, desc={style, no default}},
+ key={name=prepend postprocessor, par=\meta{code}, desc={style, no default}},
}
These keys declare processors which will transform the collected argument
before appending it to the argument list.
@@ -8506,8 +8595,8 @@ grab the environment body in the verbatim mode.)
pi={\docaux{cmd}{collargsClearPreprocessors},
\docaux{cmd}{collargsClearPostprocessors}},
]{
- key={name=clear preprocessors},
- key={name=clear postprocessors},
+ key={name=clear preprocessors}, desc=style,
+ key={name=clear postprocessors}, desc=style,
}
Clear the list of pre- or post-processors.
\end{doc}
@@ -8519,10 +8608,10 @@ grab the environment body in the verbatim mode.)
\docaux{cmd}{collargsPrependExpandablePostprocessor}
},
]{
- key={name=append expandable preprocessor, par=\meta{code}, desc={no default}},
- key={name=prepend expandable preprocessor, par=\meta{code}, desc={no default}},
- key={name=append expandable postprocessor, par=\meta{code}, desc={no default}},
- key={name=prepend expandable postprocessor, par=\meta{code}, desc={no default}},
+ key={name=append expandable preprocessor, par=\meta{code}, desc={style, no default}},
+ key={name=prepend expandable preprocessor, par=\meta{code}, desc={style, no default}},
+ key={name=append expandable postprocessor, par=\meta{code}, desc={style, no default}},
+ key={name=prepend expandable postprocessor, par=\meta{code}, desc={style, no default}},
}
These keys may be used to add fully expandable processors. A processor added
with one of these keys will end up among the processors declared by
@@ -8544,10 +8633,10 @@ grab the environment body in the verbatim mode.)
\docaux{cmd}{collargsAppendPostwrap},
\docaux{cmd}{collargsPrependPostwrap}},
]{
- key={name=append prewrap, par=\meta{macro definition}, desc={no default}},
- key={name=prepend prewrap, par=\meta{macro definition}, desc={no default}},
- key={name=append postwrap, par=\meta{macro definition}, desc={no default}},
- key={name=prepend postwrap, par=\meta{macro definition}, desc={no default}},
+ key={name=append prewrap, par=\meta{macro definition}, desc={style, no default}},
+ key={name=prepend prewrap, par=\meta{macro definition}, desc={style, no default}},
+ key={name=append postwrap, par=\meta{macro definition}, desc={style, no default}},
+ key={name=prepend postwrap, par=\meta{macro definition}, desc={style, no default}},
}
These keys add processors which transform the collected argument in a single
expansion.
@@ -8581,6 +8670,16 @@ grab the environment body in the verbatim mode.)
\tcbinputexample{comment=\input{\examplepath.tex.c1}}
\end{doc}
+\begin{doc}[
+ pi=\docaux{cmd}{ifcollargsBraceCollected}
+ ]{
+ key={name=brace collected, conditional=true},
+ }
+ When this conditional is set to false, the collected arguments are not
+ enclosed in braces when passed on to \meta{next-code}. This probably only
+ makes sense when wrapping the individual arguments, e.g.\ by
+ \refcollargs{append postwrap}|={{#1}}|.
+\end{doc}
\begin{doc}[
pi={\docaux{cmd}{collargsVerbatim},
@@ -8591,7 +8690,7 @@ grab the environment body in the verbatim mode.)
]{
key={name=verbatim, desc=style},
key={name=verb, desc=style},
- key={name=no verbatim, desc=style},
+ key={name=no verbatim, desc={style, the initial mode}},
}
Select the full verbatim, the partial verbatim, or the non-verbatim mode of
argument collection.
@@ -8665,7 +8764,7 @@ deficiency, this character pair is customizable via key
pi=\docaux{cmd}{collargsBraces}
]{
key={name=braces, par={\meta{begin-group char}\meta{end-group char}},
- desc={no default, for details see below}},
+ desc={no default, for the initial value see below}},
}% here
This key sets the verbatim begin-group and end-group characters. The setting
affects collection of argument types \docref{xparse:m}, \docref{xparse:g},
@@ -8849,7 +8948,7 @@ as follows: \docref{xparse:o}, \docref{xparse:d}, \docref{xparse:s},
package.
-\paragraph{Disappearing errors}
+\paragraph{An error occurs, but disappears in the next compilation}
If a non-fatal internal \hologo{TeX} error occurs during memoization, the memos
and externs may be nevertheless produced and utilized in subsequent
compilations. In such a case, the erroneous code won't be compiled again, and
@@ -8892,56 +8991,181 @@ release.
\label{sec:fadings-problem}
\label{sec:loading-order}
-If the internal extern extraction does not work, you should have gotten one of
-the following warnings:
+Upon an unsuccessful extern extraction, you should get one of the errors or
+warnings listed below. They should appear both in the terminal output and in
+the log file. All but the final error can also appear when extern extraction
+is performed externally.
+
+
+Errors:
+
\begin{enumerate}
-\item \code{Package memoize Warning: Extraction of externs from document
- "\meta{jobname}.pdf" using method "perl" was unsuccessful. Have you set the
- shell escape mode as suggested in chapter 1 of the manual?}
- This warning is thrown when the extraction log \meta{jobname}|.mmz.log|,
- which should be produced by the extraction script, is incomplete, i.e.\ it
- does not finish by |\endinput|.\footnote{The extraction log will never be
- absent, because Memoize clears it before executing the extraction script
- --- and clearing it creates it if it doesn't exist.} The most likely
- reason is that the script was not executed at all, so the question asked in
- the warning message is pertinent. However, if all is well with the
- shell-mode, you have a mystery at your hands. To gather information, I
- suggest inspecting:
+\item \code{Error: Python module 'pdfrw' was not found'},\\
+ \code{Error: Perl module 'PDF::API' was not found"}, or\\
+ \code{Error: Perl module 'PDF::Builder' was not found"}
+
+ You have not installed the PDF processing library, at least not
+ successfully. The installation instructions can be found in
+ section~\ref{sec:install}. Perhaps you have multiple instances of Perl\slash
+ Python on your system and you have installed the library into the wrong
+ instance?
+
+\item\label{item:warning:extract:cannot-read} \code{Error: File
+ "\meta{jobname}.pdf" seems corrupted. Perhaps you have to load Memoize
+ earlier in the preamble?}
+
+ While the PDF could be corrupted for various reasons, the most probable
+ reason is the package loading order, as indicated in the error message
+ itself. (Note that the absence of \meta{jobname}|.pdf| does not yield this
+ error. This is so that a fatal error unrelated to Memoize doesn't make
+ Memoize throw another error in the next compilation.) Embedded extern
+ extraction requires an intact document PDF from the previous compilation, so
+ Memoize must be loaded before the document PDF is opened for writing the
+ results of the ongoing compilation. In particular, the PDF is opened by \PGF
+ library |fadings|, included by \TikZ's libraries |fadings| and |shadows|, so
+ Memoize must be loaded before any of these libraries. With \pkg{beamer}, the
+ problem is particularly acute because the PDF is opened while loading the
+ class. In this case, simply moving |\usepackage{memoize}| up the preamble,
+ as suggested, won't help: you have to write |\RequirePackage{memoize}|
+ \emph{before} |\documentclass{beamer}|!
+
+ It is also possible that the PDF processing library deployed by the
+ extraction script chokes on your particular document. Inspecting the error
+ message original error message produced by Perl\slash Python, which can be
+ found at the end of the long error message in the log, should help you see
+ whether this is the case. For example, a couple of times, I got
+ \code{%\myfontexpansionon
+ Invalid dictionary key at /.../perl5/site\_perl/PDF/API2/Basic/PDF/File.pm
+ line $N$\myfontexpansionoff} when extracting with the Perl-based script.
+ The issue might be related to PDF version --- by default, \hologo{TeX}
+ produces PDFs of version 1.5, while the PDF library
+ \hreftt{https://metacpan.org/pod/PDF::API2}{PDF::API2} officially only
+ supports versions up to 1.4 --- but I'm afraid I haven't identified the exact
+ circumstances yet (possibly, the externalizing a picture containing an
+ embedded PDF file might be the culprit). In general, the workaround is to
+ use another extraction method or PDF processing library, see \refmmz{extract}
+ and \refscript{memoize-extract.pl--library}.
+
+\item\label{item:error:openin_any}
+ \code{Error: I'm not allowed to write to '{\meta{filename}}' (openout\_any =
+ {\meta{mode}})"}
+
+ Your \hologo{TeX} distribution does not allow this file to be written to.
+ The relevant setting is called \docref{openin_any} in \hologo{TeX}Live and
+ |[Core]AllowUnsafeOutputFiles| in MiK\hologo{TeX}.
+
+\item \code{Error: I'm not allowed to read from '{\meta{filename}}' (openin\_any
+ = {\meta{mode}})"}
+
+ Your \hologo{TeX} distribution does not allow this file to be read from. The
+ relevant setting is called \docref{openout_any} in \hologo{TeX}Live and
+ |[Core]AllowUnsafeInputFiles| in MiK\hologo{TeX}.
+
+\item \code{Error: "Semi-absolute" paths are disallowed: '\meta{filename}'}
+
+ This error can only occur on Windows. The extraction scripts are even more
+ paranoid than \hologo{TeX} and don't allow paths such as |C:foo\bar| or
+ |\foo\bar|, neither as path to a \dmmz, memo or extern file nor as an output
+ or temporary directory.
+
+\item \code{Error: I cannot extract page {\meta{n}} '\meta{filename}.pdf', as
+ it contains only \meta{N} pages}
+
+ The document PDF is too short. This could happen if you are accidentally
+ triggering the extern extraction twice, and the first extraction was called
+ with \refscript{memoize-extract.pl--prune}.
+
+\item\label{item:error:runtime} \code{Error: Python error: \meta{error
+ message}} or \code{Error: Perl error: \meta{error message}}
+
+ A runtime error occurred during the execution of the extraction script. The
+ error message should be followed by the traceback information which includes
+ the line number (in the extraction script) where the error occurred.
+
+ One possible source of this error are insufficient filesystem permissions.
+ For example, it will occur if the current directory is marked as read-only
+ and the temporary output directory (\texttt{\$TEXMFOUTPUT}) is not set.
+
+\item \code{Error: Extraction of externs from document "\meta{jobname}.pdf"
+ using method "\meta{extraction method}" was unsuccessful.}
+
+ This is a generic error produced when the extraction script was either not
+ executed at all, or the execution didn't finish properly.\footnote{How does
+ Memoize know whether this happened? When invoked given option
+ \refscript{memoize-extract.pl--format}, the extraction script is supposed
+ to write a log (actually, status) file called
+ % todo: \mbox is a workaround preventing "font expansion: using fonts with
+ % different limit of expansion in one paragraph is not allowed" error,
+ % which arises in combination with "pi=..." in the CollArgs section.
+ \mbox{\meta{jobname}}|.mmz.log|. Upon a clean completion, the final line
+ of this log reads \cs{endinput}. If this marker is missing, Memoize
+ produces the error under discussion. Feel free to see what's up with this
+ file if all else fails.}
+
+ A couple of reasons for the failure to execute the script:
\begin{itemize}
- \item \meta{jobname}|.log| --- search for \code{runsystem(memoize-extract.pl
- ...)}, it will tell you whether the script was executed;
- \item the terminal output --- if the script was executed, it should've
- announced itself by \code{Extracting externs from \meta{jobname}.pdf}; are
- there any further messages between this header and the warning message?
- \item \meta{the path to the extern}|.log|, if you are using the
- \hologo{TeX}-based extraction (\code{\refmmz{extract}=\refmmz{extract=tex}}).
+ \item Is the shell escape configured properly? It should be, as both
+ Memoize's extraction scripts are listed among restricted shell escape
+ commands in both \hologo{TeX}Live and MiK\hologo{TeX}, but it never hurts
+ to check.
+
+ The easiest way to see if shell escape is the culprit is to compile the
+ document with command option \docref{-shell-escape} (on \hologo{TeX}Live)
+ or \docref{--enable-write18} (on MiK\hologo{TeX}). If this resolves the
+ problem, check your shell escape mode; see
+ footnote~\ref{fn:shell-escape-commands} on
+ page~\pageref{fn:shell-escape-commands} for how to do this.
+ \item Is environment variable PATH set correctly, so that the system can find
+ the extraction script?
+ \item Is Perl\slash Python installed on your system, and accessible to
+ \hologo{TeX}?
\end{itemize}
- \hologo{TeX}-based extraction will yield this error even in the case of a
- missing or corrupted PDF, i.e.\ in
- case~\ref{item:warning:extract:cannot-read} below.
-
-\item\label{item:warning:extract:cannot-read}
- \code{Package memoize (perl-based extraction) Warning: Cannot read file
- "\meta{jobname}.pdf". Perhaps you have to load Memoize earlier in the
- preamble?}
-
- Embedded extern extraction requires an intact document PDF from the previous
- compilation, so Memoize must be loaded before the document PDF is opened for
- writing the results of the ongoing compilation. In particular, the PDF is
- opened by \PGF library |fadings|, included by \TikZ's libraries |fadings|
- and |shadows|, so Memoize must be loaded before any of these libraries.
-
- With \pkg{beamer}, the problem is particularly acute because the PDF is
- opened while loading the class. In this case, simply moving
- |\usepackage{memoize}| up the preamble, as suggested, won't help: you have to
- write |\RequirePackage{memoize}| \emph{before} |\documentclass{beamer}|!
-
-\item\label{item:warning:extract:unexpected-size} \code{Package memoize
- (perl-based extraction) Warning: I refuse to extract page $n$ from
- "\meta{jobname}.pdf", because its size ($\meta{width}\times\meta{height}$)
- is not what I expected\\ ($\meta{expected width}\times\meta{expected
- height}$)}
+
+ Normally, you should get error~\ref{item:error:runtime} if a runtime error
+ occurs during the execution of the extraction script. You can only get the
+ generic error if the runtime error occurred before the script could set up
+ the log file, perhaps due to insufficient filesystem permissions in
+ combination with the paranoid \docref{openout_any} setting (which is the
+ default).
+
+ If the source of the error remains a mistery, I suggest inspecting the
+ following sources of information, to help you with your investigation:
+ \begin{itemize}
+ \item Can you can run the extraction script by hand? Open the terminal, go
+ into the directory containing your document, write |memoize-extract.pl
+ |\meta{document name} (or |memoize-extract.py |\meta{document name}), and
+ see what happens.
+ \item Inspect \meta{jobname}|.log| --- search for
+ \code{runsystem(memoize-extract.pl ...)}, it will tell you whether the
+ script was executed.
+ \item Inspect the \hologo{TeX} terminal output --- if the script was
+ executed, it should've announced itself by \code{Extracting externs from
+ \meta{jobname}.pdf}; are there any further messages between this header
+ and the error message?
+ \item Inspect \meta{the path to the extern}|.log|, if you are using
+ \hologo{TeX}-based extraction
+ (\code{\refmmz{extract}=\refmmz{extract=tex}}).
+ \end{itemize}
+
+\end{enumerate}
+
+Not every failure to extract externs results in an error. When a warning is
+produced, the compilation will succeed, it's just that as Memoize cannot
+extract the externs, they will be produced, and dumped into your document, at
+each and every compilation. For warnings other than the missing document PDF,
+Memoize extracts as many externs as possible.
+
+\begin{enumerate}
+
+\item \code{Warning: Cannot open '\meta{filename}.pdf'}
+
+ This is not an error because what if you deleted the document PDF on purpose?
+
+\item\label{item:warning:extract:unexpected-size} \code{Warning: I refuse to
+ extract page $n$ from "\meta{jobname}.pdf", because its size
+ ($\meta{width}\times\meta{height}$) is not what I expected\\
+ ($\meta{expected width}\times\meta{expected height}$)}
If the compilation which produced the offending extern pages yielded any
errors, you should probably disregard this warning, fix the errors, and
@@ -8958,26 +9182,23 @@ the following warnings:
option \refscript{memoize-extract.pl--force} to the script invocation, which
can be set by \refmmz{perl extraction options}. However, as such a situation
probably indicates a bug in Memoize, please let me know about it.
-\item\code{\myfontexpansionon Invalid dictionary key at
- /.../perl5/site\_perl/PDF/API2/Basic/PDF/File.pm line $N$\myfontexpansionoff}
- This can sometimes happen when extracting with the Perl-based script. The
- issue might be related to PDF version --- by default, \hologo{TeX} produces
- PDFs of version 1.5, while the PDF library
- \hreftt{https://metacpan.org/pod/PDF::API2}{PDF::API2} officially only
- supports versions up to 1.4 --- but I'm afraid I haven't identified the exact
- circumstances yet. Possibly, the externalizing a picture containing an
- embedded PDF file might be the culprit. The workaround is to use Python- or
- \hologo{TeX}-based extraction, i.e.\ load Memoize with package option
- \refmmz{extract}|=|\refmmz{extract=python} or
- \refmmz{extract}|=|\refmmz{extract=tex}.
+\item \code{Warning: I refuse to extract page $n$ into extern \meta{extern
+ filename}, because the associated (c)c-memo does not exist.}
+
+ Assuming that you haven't deleted the memos (|.memo| files), either manually
+ or via \refscript{memoize-clean.pl}, could it be that they were never created
+ in the first place? Check where they should be written to (the configuration
+ commands are listed in section~\ref{sec:ref:dirs}). Is that directory
+ writeable, both in the sense of the system and for \hologo{TeX} (the
+ |openout| setting in |texmf.cnf|)?
+
+
\end{enumerate}
+
-The failure to extract externs intentionally doesn't result in an error,
-because what if you deleted the document PDF on purpose? At any rate, the
-compilation will succeed, it's just that as Memoize cannot extract the externs,
-they will be produced, and dumped into your document, at each and every
-compilation.
+% warning('I failed to execute "kpsewhich"; assuming openin_any = openout_any =
+% "p" '
\paragraph{An extern won't be included}
@@ -8991,10 +9212,17 @@ Did you receive a warning or error message?
above, only that it occurs once the extern is extracted. The same
investigative methods apply.
\item \code{!pdfTeX error: pdflatex (file \meta{extern path}.pdf): reading
- image file failed}
+ image file failed}, or something similar for engines other than
+ \hologo{pdfTeX}
- This is a fatal error. The extern file got corrupted, somehow ---
- inexistent, or even empty, extern files merely trigger recompilation.
+ This is a fatal error. The extern file got corrupted, somehow --- inexistent
+ and even empty extern files merely trigger recompilation.
+\item \code{pdfTeX warning: pdflatex (file \meta{extern path}.pdf): PDF
+ inclusion: found PDF version <$m$>, but at most version <$n$> allowed}
+
+ When you produced the externs, a higher \docref{reg:pdfmajorversion} and/or
+ \docref{reg:pdfminorversion} was in effect than now. I guess you shouldn't worry
+ about this warning if the output looks fine.
\end{enumerate}
If there was no warning or error --- are you certain that Memoize is
@@ -9052,18 +9280,6 @@ at \url{https://ctan.org/pkg/memoize} and on GitHub
at \url{https://github.com/sasozivanovic/memoize}.
-\subsection{Changelog}
-
-\newcommand\githubrelease[2]{%
- \href{https://github.com/sasozivanovic/memoize/releases/tag/#2}{#1 #2}%
-}
-\begin{description}
-\item[\githubrelease{2020/07/17}{v0.1}]
- The proof-of-concept, available only at GitHub.
-\item[\githubrelease{2023/10/10}{v1.0.0}]
- A complete, fully documented reimplementation.
-\end{description}
-
\subsection{Acknowledgments}
First and foremost, my gratitude goes to Stefan Müller of Language Science
@@ -9078,7 +9294,11 @@ me misappropriating \pkg{etoolbox} for other formats. Petra Rübe-Pugliese
package; I would never get to call Advice Advice without their advice. Karl
also performed a security review of the extraction scripts, providing some very
useful comments along the way, and agreed to include the scripts among the
-shell escape commands allowed in \hologo{TeX}Live.
+shell escape commands allowed in \hologo{TeX}Live. Christian Schenk worked
+hard to support Memoize in MiK\hologo{TeX}. After the package was first
+published, amonakov, mbert, Qrrbrbirlbel, and Ulrike Fischer provided valuable
+feedback, suggestions and\slash or bug reports; cfr did all that, and also
+advertised the package extensively.
Thank you all!
diff --git a/macros/generic/memoize/doc/memoize-extract.1.md b/macros/generic/memoize/doc/memoize-extract.1.md
index 434f2fea2f..f3f3d52ad0 100644
--- a/macros/generic/memoize/doc/memoize-extract.1.md
+++ b/macros/generic/memoize/doc/memoize-extract.1.md
@@ -2,8 +2,8 @@
title: memoize-extract
section: 1
header: User Manual
-footer: memoize-extract of Memoize v1.0.0
-date: October 10, 2023
+footer: memoize-extract of Memoize v1.1.0
+date: January 02, 2024
hyphenate: false
---
@@ -34,17 +34,17 @@ Memoize communicates with **memoize-extract** through file *document.mmz*. When
*document.mmz*, which records which pages in the produced document are extern
pages and to which extern files they should be extracted. Therefore, after
compiling *document.tex*, the externs should be extracted by
-**memoize-extract** *document.mmz*.
+**memoize-extract** *document.mmz*.
+
+*document.mmz* may also be given as document* or *document.tex*. When
+environment variable *TEXMF_OUTPUT_DIRECTORY* is set, this filename is relative
+to the output directory specified by this variable.
*document.mmz* also records the expected width and height of each extern. In
case of a mismatch, **memoize-extract** refuses to extract the page and removes
the extern file if it already exist, and prints a warning message to the
-standard error. The script furthermore refuses to extract the page if a
-(c)c-memo associated to the extern does not exist, and to write to any file
-whose absolute path does not occur under the current directory or the directory
-set by TEXMFOUTPUT (in *texmf.cnf* or as an environment variable); TEXMFOUTPUT
-is also not allowed to point to the root directory, except on Windows, where it
-can only point to a drive root.
+standard error. The script also refuses to extract the page if a (c)c-memo
+associated to the extern does not exist. See also section SECURITY.
The Perl (.pl) and the Python (.py) version of the script are functionally
equivalent. The Perl script requires library
@@ -64,30 +64,68 @@ library [pdfrw2](https://pypi.org/project/pdfrw2).
: Do not modify the *document.mmz* to mark the externs as extracted. By
default, they are commented out to prevent double extraction.
+**-F, \--format** *latex*|*plain*|*context*
+: When this option is given, the script assumes that it was called from within
+ a TeX compilation of a document in the given format: it prefixes all output
+ by the script name, and creates a log file *document.mmz.log*, which receives
+ any extraction-related warnings and errors.
+
**-f, \--force**
: Extract the extern even if the size-check fails.
-**-l, \--log** *filename*
-: Log size mismatches to *filename* rather than to the standard error.
-
-**-w, \--warning-template** *string*
-: Wrap the size mismatch warning text into the given template: \\warningtext in
- the template will be replaced by the warning text.
-
**-q, \--quiet**
: Don't describe what's happening.
-**-e, \--embedded**
-: Prefix all messages to the standard output with the script name.
-
**-m, \--mkdir**
-: A paranoid *mkdir -p*. (No extraction occurs, *document.mmz* is interpreted as a directory name.)
+: A paranoid *mkdir -p*. (No extraction occurs, *document.mmz* is interpreted as a directory name, which may end in any suffix; no suffix mangling is performed.)
+
+**-V, \--version**
+: Show the Memoize version number and exit.
**-h, \--help**
: Show help and exit.
-**-V, \--version**
-: Show the Memoize version number and exit.
+# SECURITY
+
+This script respects the restrictions on file input and output imposed by the
+TeX configuration, more precisely, the variables *openin_any* and *openout_any*
+of the **kpathsea** library (https://tug.org/kpathsea). You can inspect the
+values of these variables by executing '**kpsewhich** -var-value=openin_any' and
+'**kpsewhich** -var-value=openout_any'. The interpretation is as follows:
+
+**a** (or **y** or **1**) any
+: Allows any file to be opened.
+
+**r** (or **n** or **0**) restricted
+: Means disallowing special file names.
+
+**p** (or any other value) paranoid
+: Means being really paranoid: disallowing special file names and restricting
+ input/output files to be in or below the working directory or the directory
+ specified by *TEXMFOUTPUT* or *TEXMF_OUTPUT_DIRECTORY*. *TEXMFOUTPUT* may be
+ set either in *texmf.cnf* (e.g. by **tlmgr**) or as an environment
+ variable. *TEXMF_OUTPUT_DIRECTORY* may only be set as an environment
+ variables; it is automatically set by TeX when called by *-output-directory*
+ option (starting in TeXLive 2024).
+
+# EXIT STATUS
+
+**0**
+: The externs were successfully extracted. This exit code is returned even if
+ no externs need to be extracted, or if *document.mmz* does not exist.
+
+**10**
+: A warning also reported back to the compilation when given option
+ **\--format**. Currently, either: (i) size-mismatch; (ii) a non-existing
+ associated (c)c-memo file; or (iii) unavailable *kpsewhich*.
+
+**11**
+: An error also reported back to the compilation when given option
+ **\--format**. Currently, either: (i) a currupted document PDF, or (ii)
+ a kpathsea permission error.
+
+Other exit codes are as produced by the underlying scripting language (Perl of
+Python).
# SEE ALSO
diff --git a/macros/generic/memoize/memoize-clean.pl b/macros/generic/memoize/memoize-clean.pl
index 4e1334c695..7d7e4f5b43 100755
--- a/macros/generic/memoize/memoize-clean.pl
+++ b/macros/generic/memoize/memoize-clean.pl
@@ -18,18 +18,19 @@
# The files belonging to this work and covered by LPPL are listed in
# <texmf>/doc/generic/memoize/FILES.
+my $PROG = 'memoize-clean.pl';
+my $VERSION = '2024/01/02 v1.1.0';
+
use strict;
use Getopt::Long;
-# I intend to rewrite this script using Path::Class.
use Cwd 'realpath';
use File::Spec;
use File::Basename;
-my $VERSION = '2023/10/10 v1.0.0';
-
-my $usage = "usage: memoize-clean.py [-h] [--yes] [--all] [--quiet] [--prefix PREFIX] [mmz ...]\n";
+my $usage = "usage: $PROG [-h] [--yes] [--all] [--quiet] [--prefix PREFIX] " .
+ "[mmz ...]\n";
my $Help = <<END;
-Remove (stale) memo and extern files.
+Remove (stale) memo and extern files produced by package Memoize.
positional arguments:
mmz .mmz record files
@@ -41,7 +42,8 @@ options:
--all, -a Remove *all* memos and externs.
--quiet, -q
--prefix PREFIX, -p PREFIX
- A path prefix to clean; this option can be specified multiple times.
+ A path prefix to clean;
+ this option can be specified multiple times.
For details, see the man page or the Memoize documentation.
END
@@ -111,8 +113,11 @@ sub populate_tbdeleted {
opendir(MD, $dir) or die "Cannot open directory '$dir'";
while( (my $fn = readdir(MD)) ) {
my $path = File::Spec->catfile(($dir),$fn);
- if ($fn =~ /\Q$basename_prefix\E[0-9A-F]{32}(?:-[0-9A-F]{32})?(?:-[0-9]+)?(\.memo|(?:-[0-9]+)?\.pdf|\.log)/ and ($all || !exists($keep{$path}))) {
- push @tbdeleted, $path;
+ if ($fn =~
+ /\Q$basename_prefix\E[0-9A-F]{32}(?:-[0-9A-F]{32})?(?:-[0-9]+)?#
+ (\.memo|(?:-[0-9]+)?\.pdf|\.log)/x
+ and ($all || !exists($keep{$path}))) {
+ push @tbdeleted, $path;
}
}
closedir(MD);
@@ -161,3 +166,7 @@ if (scalar(@tbdeleted) != 0) {
} elsif (!$quiet) {
print "Nothing to do, the directory seems clean.\n";
}
+
+# Local Variables:
+# after-save-hook: pl2dtx
+# End:
diff --git a/macros/generic/memoize/memoize-clean.py b/macros/generic/memoize/memoize-clean.py
index dd543327cb..db758bbd35 100755
--- a/macros/generic/memoize/memoize-clean.py
+++ b/macros/generic/memoize/memoize-clean.py
@@ -18,13 +18,14 @@
# The files belonging to this work and covered by LPPL are listed in
# <texmf>/doc/generic/memoize/FILES.
-__version__ = '2023/10/10 v1.0.0'
+__version__ = '2024/01/02 v1.1.0'
import argparse, re, sys, pathlib, os
parser = argparse.ArgumentParser(
description="Remove (stale) memo and extern files.",
- epilog = "For details, see the man page or the Memoize documentation."
+ epilog = "For details, see the man page or the Memoize documentation "
+ "(https://ctan.org/pkg/memoize)."
)
parser.add_argument('--yes', '-y', action = 'store_true',
help = 'Do not ask for confirmation.')
@@ -63,7 +64,8 @@ for mmz_fn in args.mmz:
elif endinput:
raise RuntimeError(
- r'Bailing out, \endinput is not the last line of file $mmz_fn.')
+ rf'Bailing out, '
+ rf'\endinput is not the last line of file {mmz_fn}.')
elif m := re_prefix.match(line):
prefix = m[1]
@@ -109,7 +111,8 @@ tbdeleted = []
def populate_tbdeleted(folder, basename_prefix):
re_aux = re.compile(
re.escape(basename_prefix) +
- '[0-9A-F]{32}(?:-[0-9A-F]{32})?(?:-[0-9]+)?(?:\.memo|(?:-[0-9]+)?\.pdf|\.log)$')
+ '[0-9A-F]{32}(?:-[0-9A-F]{32})?'
+ '(?:-[0-9]+)?(?:\.memo|(?:-[0-9]+)?\.pdf|\.log)$')
try:
for f in folder.iterdir():
if re_aux.match(f.name) and (args.all or f not in keep):
@@ -157,3 +160,8 @@ if tbdeleted:
print("Bailing out.")
elif not args.quiet:
print('Nothing to do, the directory seems clean.')
+
+# Local Variables:
+# fill-column: 79
+# after-save-hook: py2dtx
+# End:
diff --git a/macros/generic/memoize/memoize-extract.pl b/macros/generic/memoize/memoize-extract.pl
index 8fba3d356f..96eec29b97 100755
--- a/macros/generic/memoize/memoize-extract.pl
+++ b/macros/generic/memoize/memoize-extract.pl
@@ -18,188 +18,757 @@
# The files belonging to this work and covered by LPPL are listed in
# <texmf>/doc/generic/memoize/FILES.
+my $PROG = 'memoize-extract.pl';
+my $VERSION = '2024/01/02 v1.1.0';
+
use strict;
+use File::Basename qw/basename/;
use Getopt::Long;
-use Path::Class;
-use File::Spec;
-use File::Basename;
-use PDF::API2;
+use File::Spec::Functions
+ qw/splitpath catpath splitdir rootdir file_name_is_absolute/;
+use File::Path qw(make_path);
+# We will only try to import the PDF processing library once we set up the error
+# log.
+
+# Declare variables for command-line arguments and for |kpathsea|
+# variables. They are defined here so that they are global in the subs which use
+# them.
+our ($pdf_file, $prune, $keep, $format, $force, $quiet,
+ $pdf_library, $print_version, $mkdir, $help,
+ $openin_any, $openout_any, $texmfoutput, $texmf_output_directory);
+
+# \paragraph{Messages}
+
+# The messages are written both to the extraction log and the terminal (we
+# output to stdout rather than stderr so that messages on the TeX terminal and
+# document |.log| appear in chronological order). Messages are automatically
+# adapted to the TeX |--format|.
+
+# The format of the messages. It depends on the given |--format|; the last
+# entry is for t the terminal output.
+
+my %ERROR = (
+ latex => '\PackageError{memoize (perl-based extraction)}{$short}{$long}',
+ plain => '\errhelp{$long}\errmessage{memoize (perl-based extraction): $short}',
+ context => '\errhelp{$long}\errmessage{memoize (perl-based extraction): $short}',
+ '' => '$header$short. $long');
+
+my %WARNING = (
+ latex => '\PackageWarning{memoize (perl-based extraction)}{$texindent$text}',
+ plain => '\message{memoize (perl-based extraction) Warning: $texindent$text}',
+ context => '\message{memoize (perl-based extraction) Warning: $texindent$text}',
+ '' => '$header$indent$text.');
+
+my %INFO = (
+ latex => '\PackageInfo{memoize (perl-based extraction)}{$texindent$text}',
+ plain => '\message{memoize (perl-based extraction): $texindent$text}',
+ context => '\message{memoize (perl-based extraction): $texindent$text}',
+ '' => '$header$indent$text.');
-my $VERSION = '2023/10/10 v1.0.0';
+# Some variables used in the message routines; note that |header| will be
+# redefined once we parse the arguments.
+
+my $exit_code = 0;
+my $log;
+my $header = '';
+my $indent = '';
+my $texindent = '';
+
+# The message routines.
+
+sub error {
+ my ($short, $long) = @_;
+ if (! $quiet) {
+ $_ = $ERROR{''};
+ s/\$header/$header/;
+ s/\$short/$short/;
+ s/\$long/$long/;
+ print(STDOUT "$_\n");
+ }
+ if ($log) {
+ $long =~ s/\\/\\string\\/g;
+ $_ = $ERROR{$format};
+ s/\$short/$short/;
+ s/\$long/$long/;
+ print(LOG "$_\n");
+ }
+ $exit_code = 11;
+ endinput();
+}
+
+sub warning {
+ my $text = shift;
+ if ($log) {
+ $_ = $WARNING{$format};
+ s/\$texindent/$texindent/;
+ s/\$text/$text/;
+ print(LOG "$_\n");
+ }
+ if (! $quiet) {
+ $_ = $WARNING{''};
+ s/\$header/$header/;
+ s/\$indent/$indent/;
+ s/\$text/$text/;
+ print(STDOUT "$_\n");
+ }
+ $exit_code = 10;
+}
+
+sub info {
+ my $text = shift;
+ if ($text && ! $quiet) {
+ $_ = $INFO{''};
+ s/\$header/$header/;
+ s/\$indent/$indent/;
+ s/\$text/$text/;
+ print(STDOUT "$_\n");
+ if ($log) {
+ $_ = $INFO{$format};
+ s/\$texindent/$texindent/;
+ s/\$text/$text/;
+ print(LOG "$_\n");
+ }
+ }
+}
+
+# Mark the log as complete and exit.
+sub endinput {
+ if ($log) {
+ print(LOG "\\endinput\n");
+ close(LOG);
+ }
+ exit $exit_code;
+}
+
+sub die_handler {
+ stderr_to_warning();
+ my $text = shift;
+ chomp($text);
+ error("Perl error: $text", '');
+}
+
+sub warn_handler {
+ my $text = shift;
+ chomp($text);
+ warning("Perl warning: $text");
+}
+
+# This is used to print warning messages from PDF::Builder, which are output to
+# STDERR.
+my $stderr;
+sub stderr_to_warning {
+ if ($stderr) {
+ my $w = ' Perl info: ';
+ my $nl = '';
+ for (split(/\n/, $stderr)) {
+ /(^\s*)(.*?)(\s*)$/;
+ $w .= ($1 ? ' ' : $nl) . $2;
+ $nl = "\n";
+ }
+ warning("$w");
+ $stderr = '';
+ }
+}
+
+# \paragraph{Permission-related functions}
+
+# We will need these variables below. Note that we only support Unix and
+# Windows.
+my $on_windows = $^O eq 'MSWin32';
+my $dirsep = $on_windows ? '\\' : '/';
+
+# |paranoia_in|/|out| should work exactly as
+# |kpsewhich -safe-in-name|/|-safe-out-name|.
+sub paranoia_in {
+ my ($f, $remark) = @_;
+ error("I'm not allowed to read from '$f' (openin_any = $openin_any)",
+ $remark) unless _paranoia($f, $openin_any);
+}
+
+sub paranoia_out {
+ my ($f, $remark) = @_;
+ error("I'm not allowed to write to '$f' (openin_any = $openout_any)",
+ $remark) unless _paranoia($f, $openout_any);
+}
+
+sub _paranoia {
+ # |f| is the path to the file (it should not be empty), and |mode| is the
+ # value of |openin_any| or |openout_any|.
+ my ($f, $mode) = @_;
+ return if (! $f);
+ # We split the filename into the directory and the basename part, and the
+ # directory into components.
+ my ($volume, $dir, $basename) = splitpath($f);
+ my @dir = splitdir($dir);
+ return (
+ # In mode `any' (|a|, |y| or |1|), we may access any file.
+ $mode =~ /^[ay1]$/
+ || (
+ # Otherwise, we are at least in the restricted mode, so we should
+ # not open dot files on Unix-like systems (except file called
+ # |.tex|).
+ ! (!$on_windows && $basename =~ /^\./ && !($basename =~ /^\.tex$/))
+ && (
+ # If we are precisely in the restricted mode (|r|, |n|, |0|),
+ # then there are no further restrictions.
+ $mode =~ /^[rn0]$/
+ # Otherwise, we are in the paranoid mode (officially |p|, but
+ # any other value is interpreted as |p| as well). There are
+ # two further restrictions in the paranoid mode.
+ || (
+ # We're not allowed to go to a parent directory.
+ ! grep(/^\.\.$/, @dir) && $basename ne '..'
+ &&
+ # If the given path is absolute, is should be a descendant
+ # of either |TEXMF_OUTPUT_DIRECTORY| or |TEXMFOUTPUT|.
+ (!file_name_is_absolute($f)
+ ||
+ is_ancestor($texmf_output_directory, $f)
+ ||
+ is_ancestor($texmfoutput, $f)
+ )))));
+}
+
+# Only removes final "/"s. This is unlike |File::Spec|'s |canonpath|, which also
+# removes |.| components, collapses multiple |/| --- and unfortunately also goes
+# up for |..| on Windows.
+sub normalize_path {
+ my $path = shift;
+ my ($v, $d, $n) = splitpath($path);
+ if ($n eq '' && $d =~ /[^\Q$dirsep\E]\Q$dirsep\E+$/) {
+ $path =~ s/\Q$dirsep\E+$//;
+ }
+ return $path;
+}
+
+# On Windows, we disallow ``semi-absolute'' paths, i.e.\ paths starting with the
+# |\| but lacking the drive. |File::Spec|'s function |file_name_is_absolute|
+# returns 2 if the path is absolute with a volume, 1 if it's absolute with no
+# volume, and 0 otherwise. After a path was sanitized using this function,
+# |file_name_is_absolute| will work as we want it to.
+sub sanitize_path {
+ my $f = normalize_path(shift);
+ my ($v, $d, $n) = splitpath($f);
+ if ($on_windows) {
+ my $a = file_name_is_absolute($f);
+ if ($a == 1 || ($a == 0 && $v) ) {
+ error("\"Semi-absolute\" paths are disallowed: " . $f,
+ "The path must either both contain the drive letter and " .
+ "start with '\\', or none of these; paths like 'C:foo\\bar' " .
+ "and '\\foo\\bar' are disallowed");
+ }
+ }
+}
+
+sub access_in {
+ return -r shift;
+}
+
+sub access_out {
+ my $f = shift;
+ my $exists;
+ eval { $exists = -e $f };
+ # Presumably, we get this error when the parent directory is not executable.
+ return if ($@);
+ if ($exists) {
+ # An existing file should be writable, and if it's a directory, it
+ # should also be executable.
+ my $rw = -w $f; my $rd = -d $f; my $rx = -x $f;
+ return -w $f && (! -d $f || -x $f);
+ } else {
+ # For a non-existing file, the parent directory should be writable.
+ # (This is the only place where function |parent| is used, so it's ok
+ # that it returns the logical parent.)
+ my $p = parent($f);
+ return -w $p;
+ }
+}
+
+# This function finds the location for an input file, respecting
+# |TEXMF_OUTPUT_DIRECTORY| and |TEXMFOUTPUT|, and the permissions in the
+# filesystem. It returns an absolute file as-is. For a relative file, it tries
+# |TEXMF_OUTPUT_DIRECTORY| (if defined), the current directory (always), and
+# |TEXMFOUTPUT| directory (if defined), in this order. The first readable file
+# found is returned; if no readable file is found, the file in the current
+# directory is returned.
+sub find_in {
+ my $f = shift;
+ sanitize_path($f);
+ return $f if file_name_is_absolute($f);
+ for my $df (
+ $texmf_output_directory ? join_paths($texmf_output_directory, $f) : undef,
+ $f,
+ $texmfoutput ? join_paths($texmfoutput, $f) : undef) {
+ return $df if $df && -r $df;
+ }
+ return $f;
+}
+
+# This function finds the location for an output file, respecting
+# |TEXMF_OUTPUT_DIRECTORY| and |TEXMFOUTPUT|, and the permissions in the
+# filesystem. It returns an absolute file as-is. For a relative file, it tries
+# |TEXMF_OUTPUT_DIRECTORY| (if defined), the current directory (unless
+# |TEXMF_OUTPUT_DIRECTORY| is defined), and |TEXMFOUTPUT| directory (if
+# defined), in this order. The first writable file found is returned; if no
+# writable file is found, the file in either the current or the output directory
+# is returned.
+sub find_out {
+ my $f = shift;
+ sanitize_path($f);
+ return $f if file_name_is_absolute($f);
+ for my $df (
+ $texmf_output_directory ? join_paths($texmf_output_directory, $f) : undef,
+ $texmf_output_directory ? undef : $f,
+ $texmfoutput ? join_paths($texmfoutput, $f) : undef) {
+ return $df if $df && access_out($df);
+ }
+ return $texmf_output_directory ? join_paths($texmf_output_directory, $f) : $f;
+}
+
+# We next define some filename-related utilities matching what Python offers out
+# of the box. We avoid using |File::Spec|'s |canonpath|, because on Windows,
+# which has no concept of symlinks, this function resolves |..| to the parent.
+
+sub name {
+ my $path = shift;
+ my ($volume, $dir, $filename) = splitpath($path);
+ return $filename;
+}
+
+sub suffix {
+ my $path = shift;
+ my ($volume, $dir, $filename) = splitpath($path);
+ $filename =~ /\.[^.]*$/;
+ return $&;
+}
-my $usage = "usage: memoize-extract.pl [-h] [--pdf PDF] [--prune] [--keep] [--force] [--log LOG] [--warning-template WARNING_TEMPLATE] [--quiet] [--mkdir] mmz\n";
+sub with_suffix {
+ my ($path, $suffix) = @_;
+ my ($volume, $dir, $filename) = splitpath($path);
+ if ($filename =~ s/\.[^.]*$/$suffix/) {
+ return catpath($volume, $dir, $filename);
+ } else {
+ return catpath($volume, $dir, $filename . $suffix);
+ }
+}
+
+sub with_name {
+ my ($path, $name) = @_;
+ my ($volume, $dir, $filename) = splitpath($path);
+ my ($v,$d,$f) = splitpath($name);
+ die "Runtime error in with_name: " .
+ "'$name' should not contain the directory component"
+ unless $v eq '' && $d eq '' && $f eq $name;
+ return catpath($volume, $dir, $name);
+}
+
+sub join_paths {
+ my $path1 = normalize_path(shift);
+ my $path2 = normalize_path(shift);
+ return $path2 if !$path1 || file_name_is_absolute($path2);
+ my ($volume1, $dir1, $filename1) = splitpath($path1, 'no_file');
+ my ($volume2, $dir2, $filename2) = splitpath($path2);
+ die if $volume2;
+ return catpath($volume1,
+ join($dirsep, ($dir1 eq $dirsep ? '' : $dir1, $dir2)),
+ $filename2);
+}
+
+# The logical parent. The same as |pathlib.parent| in Python.
+sub parent {
+ my $f = normalize_path(shift);
+ my ($v, $dn, $_dummy) = splitpath($f, 1);
+ my $p_dn = $dn =~ s/[^\Q$dirsep\E]+$//r;
+ if ($p_dn eq '') {
+ $p_dn = $dn =~ /^\Q$dirsep\E/ ? $dirsep : '.';
+ }
+ my $p = catpath($v, $p_dn, '');
+ $p = normalize_path($p);
+ return $p;
+}
+
+# This function assumes that both paths are absolute; ancestor may be '',
+# signaling a non-path.
+sub is_ancestor {
+ my $ancestor = normalize_path(shift);
+ my $descendant = normalize_path(shift);
+ return if ! $ancestor;
+ $ancestor .= $dirsep unless $ancestor =~ /\Q$dirsep\E$/;
+ return $descendant =~ /^\Q$ancestor/;
+}
+
+# A paranoid |Path.mkdir|. The given folder is preprocessed by |find_out|.
+sub make_directory {
+ my $folder = find_out(shift);
+ if (! -d $folder) {
+ paranoia_out($folder);
+ # Using |make_path| is fine because we know that
+ # |TEXMF_OUTPUT_DIRECTORY|/|TEXMFOUTPUT|, if given, exists, and that
+ # ``folder'' contains no |..|.
+ make_path($folder);
+ # This does not get logged when the function is invoked via |--mkdir|,
+ # as it is not clear what the log name should be.
+ info("Created directory $folder");
+ }
+}
+
+sub unquote {
+ shift =~ s/"(.*?)"/\1/rg;
+}
+
+# \paragraph{Kpathsea}
+
+# Get the values of |openin_any|, |openout_any|, |TEXMFOUTPUT| and
+# |TEXMF_OUTPUT_DIRECTORY|.
+
+my $maybe_backslash = $on_windows ? '' : '\\';
+my $query = 'kpsewhich -expand-var=' .
+ "openin_any=$maybe_backslash\$openin_any," .
+ "openout_any=$maybe_backslash\$openout_any," .
+ "TEXMFOUTPUT=$maybe_backslash\$TEXMFOUTPUT";
+my $kpsewhich_output = `$query`;
+if (! $kpsewhich_output) {
+ # No TeX? (Note that |kpsewhich| should exist in MiKTeX as well.) In
+ # absence of |kpathsea| information, we get very paranoid.
+ ($openin_any, $openout_any) = ('p', 'p');
+ ($texmfoutput, $texmf_output_directory) = ('', '');
+ # Unfortunately, this warning can't make it into the log. But then again,
+ # the chances of a missing |kpsewhich| are very slim, and its absence would
+ # show all over the place anyway.
+ warning('I failed to execute "kpsewhich", is there no TeX system installed? ' .
+ 'Assuming openin_any = openout_any = "p" ' .
+ '(i.e. restricting all file operations to non-hidden files ' .
+ 'in the current directory of its subdirectories).');
+} else {
+ $kpsewhich_output =~ /^openin_any=(.*),openout_any=(.*),TEXMFOUTPUT=(.*)/;
+ ($openin_any, $openout_any, $texmfoutput) = @{^CAPTURE};
+ $texmf_output_directory = $ENV{'TEXMF_OUTPUT_DIRECTORY'};
+ if ($openin_any =~ '^\$openin_any') {
+ # When the |open*_any| variables are not expanded, we assume we're
+ # running MiKTeX. The two config settings below correspond to TeXLive's
+ # |openin_any| and |openout_any|; afaik, there is no analogue to
+ # |TEXMFOUTPUT|.
+ $query = 'initexmf --show-config-value=[Core]AllowUnsafeInputFiles ' .
+ '--show-config-value=[Core]AllowUnsafeOutputFiles';
+ my $initexmf_output = `$query`;
+ $initexmf_output =~ /^(.*)\n(.*)\n/m;
+ $openin_any = $1 eq 'true' ? 'a' : 'p';
+ $openout_any = $2 eq 'true' ? 'a' : 'p';
+ $texmfoutput = '';
+ $texmf_output_directory = '';
+ }
+}
+
+# An output directory should exist, and may not point to the root on Linux. On
+# Windows, it may point to the root, because being absolute also implies
+# containing the drive; see |sanitize_filename|.
+sub sanitize_output_dir {
+ return unless my $d = shift;
+ sanitize_path($d);
+ # On Windows, |rootdir| returns |\|, so it cannot possibly match |$d|.
+ return $d if -d $d && $d ne rootdir();
+}
+
+$texmfoutput = sanitize_output_dir($texmfoutput);
+$texmf_output_directory = sanitize_output_dir($texmf_output_directory);
+
+# We don't delve into the real script when loaded from the testing code.
+return 1 if caller;
+
+# \paragraph{Arguments}
+
+my $usage = "usage: $PROG [-h] [-P PDF] [-p] [-k] [-F {latex,plain,context}] [-f] " .
+ "[-L {PDF::API2,PDF::Builder}] [-q] [-m] [-V] mmz\n";
my $Help = <<END;
-Extract extern pages out of the document PDF.
+Extract extern pages produced by package Memoize out of the document PDF.
positional arguments:
- mmz the record file produced by Memoize: doc.mmz when compiling doc.tex
+ mmz the record file produced by Memoize:
+ doc.mmz when compiling doc.tex
+ (doc and doc.tex are accepted as well)
options:
- --help, -h show this help message and exit
- --version, -V show version and exit
- --pdf PDF, -P PDF extract from file PDF
- --prune, -p remove the extern pages after extraction
- --keep, -k do not mark externs as extracted
- --force, -f extract even if the size-check fails
- --log LOG, -l LOG the log file
- --warning-template WARNING_TEMPLATE, -w WARNING_TEMPLATE
- \warningtext in the template will be replaced by the warning message
- --quiet, -q don't describe what's happening
- --embedded, -e prefix all messages to the standard output with the script name
- --mkdir, -m create a directory (and exit)
+ -h, --help show this help message and exit
+ -P PDF, --pdf PDF extract from file PDF
+ -p, --prune remove the extern pages after extraction
+ -k, --keep do not mark externs as extracted
+ -F, --format {latex,plain,context}
+ the format of the TeX document invoking extraction
+ -f, --force extract even if the size-check fails
+ -q, --quiet describe what's happening
+ -L, --library {PDF::API2, PDF::Builder}
+ which PDF library to use for extraction (default: PDF::API2)
+ -m, --mkdir create a directory (and exit);
+ mmz argument is interpreted as directory name
+ -V, --version show program's version number and exit
For details, see the man page or the Memoize documentation.
END
-my ($pdf_arg, $prune, $keep, $log, $quiet, $embedded, $force, $mkdir, $help, $print_version);
-my $warning_template = '\warningtext';
+my @valid_libraries = ('PDF::API2', 'PDF::Builder');
Getopt::Long::Configure ("bundling");
GetOptions(
- "pdf|P=s" => \$pdf_arg,
- "keep|k" => \$keep,
+ "pdf|P=s" => \$pdf_file,
"prune|p" => \$prune,
- "log|l=s" => \$log,
- "quiet|q" => \$quiet,
- "embedded|e" => \$embedded,
+ "keep|k" => \$keep,
+ "format|F=s" => \$format,
"force|f" => \$force,
- "warning-template|w=s" => \$warning_template,
+ "quiet|q" => \$quiet,
+ "library|L=s" => \$pdf_library,
"mkdir|m" => \$mkdir,
- "help|h|?" => \$help,
"version|V" => \$print_version,
+ "help|h|?" => \$help,
) or die $usage;
+
if ($help) {print("$usage\n$Help"); exit 0}
-if ($print_version) { print("memoize-extract.pl of Memoize $VERSION\n"); exit 0 }
-die $usage unless @ARGV == 1;
-my $message_prefix = $embedded ? basename($0) . ': ' : '';
-print("\n") if ($embedded);
+if ($print_version) { print("$PROG of Memoize $VERSION\n"); exit 0 }
-my @output_paths = (dir()->absolute->resolve);
-my $texmfoutput = `kpsewhich --var-value=TEXMFOUTPUT`;
-$texmfoutput =~ s/^\s+|\s+$//g;
-if ($texmfoutput) {
- my $texmfoutput_dir = dir($texmfoutput)->absolute->resolve;
- push(@output_paths, $texmfoutput_dir) unless $texmfoutput_dir->dir_list == 1 && ! $texmfoutput_dir->volume;
-}
+die "${usage}$PROG: error: the following arguments are required: mmz\n"
+ unless @ARGV == 1;
-sub paranoia {
- my $file = $_[0];
- die "${message_prefix}Cannot create a hidden file or dir: $file"
- if $file->basename =~ /^\./;
- my $parent = $file->parent->absolute->resolve;
- die "${message_prefix}Cannot write outside the current working or output directory tree: $file"
- unless grep($_->contains($parent), @output_paths);
-}
+die "${usage}$PROG: error: argument -F/--format: invalid choice: '$format' " .
+ "(choose from 'latex', 'plain', 'context')\n"
+ unless grep $_ eq $format, ('', 'latex', 'plain', 'context');
+
+die "${usage}$PROG: error: argument -L/--library: invalid choice: '$pdf_library' " .
+ "(choose from " . join(", ", @valid_libraries) . ")\n"
+ if $pdf_library && ! grep $_ eq $pdf_library, @valid_libraries;
-my $mmz_arg = $ARGV[0];
-my $mmz_file = file($mmz_arg);
-my $mmz_dir = $mmz_file->parent;
+$header = $format ? basename($0) . ': ' : '';
+# start a new line in the TeX terminal output
+print("\n") if $format;
+
+# \paragraph{Initialization}
+
+# With |--mkdir|, argument |mmz| is interpreted as the directory to create.
if ($mkdir) {
- my $dir = dir($mmz_arg)->cleanup;
- my $current = dir(File::Spec->catpath($dir->volume,
- $dir->is_absolute ? File::Spec->rootdir : File::Spec->curdir,
- ''))->absolute;
- for my $c ($dir->components) {
- $current = $current->subdir($c);
- if (-d $current) {
- $current = $current->resolve;
- } else {
- paranoia($current);
- mkdir($current);
- print("${message_prefix}Created directory $current\n") unless $quiet;
- }
- }
- exit;
-} else {
- die "${message_prefix}The 'mmz' argument should be a file with suffix '.mmz', not '$mmz_file'\n" unless $mmz_file->basename =~ /\.mmz$/;
+ make_directory($ARGV[0]);
+ exit 0;
}
-# Enable in-place editing (of the .mmz file).
-paranoia($mmz_file) unless $keep;
-$^I = "" unless $keep;
-
-my $pdf_file = $pdf_arg ? file($pdf_arg) : $mmz_dir->file($mmz_file->basename =~ s/\.mmz$/\.pdf/r)->cleanup;
-paranoia($pdf_file) if $prune;
+# Normalize the |mmz| argument into a |.mmz| filename.
+my $mmz_file = $ARGV[0];
+$mmz_file = with_suffix($mmz_file, '.mmz')
+ if suffix($mmz_file) eq '.tex';
+$mmz_file = with_name($mmz_file, name($mmz_file) . '.mmz')
+ if suffix($mmz_file) ne '.mmz';
-if ($log) {
- paranoia(file($log));
+# Once we have the |.mmz| filename, we can open the log.
+if ($format) {
+ my $_log = find_out(with_suffix($mmz_file, '.mmz.log'));
+ paranoia_out($_log);
+ info("Logging to '$_log'");
+ $log = $_log;
open LOG, ">$log";
+}
+
+# Now that we have opened the log file, we can try loading the PDF processing
+# library.
+if ($pdf_library) {
+ eval "use $pdf_library";
+ error("Perl module '$pdf_library' was not found",
+ 'Have you followed the instructions is section 1.1 of the manual?')
+ if ($@);
} else {
- *LOG = *STDERR;
+ for (@valid_libraries) {
+ eval "use $_";
+ if (!$@) {
+ $pdf_library = $_;
+ last;
+ }
+ }
+ if (!$pdf_library) {
+ error("No suitable Perl module for PDF processing was found, options are " .
+ join(", ", @valid_libraries),
+ 'Have you followed the instructions is section 1.1 of the manual?');
+ }
+}
+
+# Catch any errors in the script and output them to the log.
+$SIG{__DIE__} = \&die_handler;
+$SIG{__WARN__} = \&warn_handler;
+close(STDERR);
+open(STDERR, ">", \$stderr);
+
+# Find the |.mmz| file we will read, but retain the original filename in
+# |$given_mmz_file|, as we will still need it.
+my $given_mmz_file = $mmz_file;
+$mmz_file = find_in($mmz_file, 1);
+if (! -e $mmz_file) {
+ info("File '$given_mmz_file' does not exist, assuming there's nothing to do");
+ endinput();
}
+paranoia_in($mmz_file);
+paranoia_out($mmz_file,
+ 'I would have to rewrite this file unless option --keep is given.')
+ unless $keep;
-my ($pdf, %extern_pages);
-print "${message_prefix}Extracting externs from $pdf_file:\n" unless $quiet;
+# Determine the PDF filename: it is either given via |--pdf|, or constructed
+# from the |.mmz| filename.
+$pdf_file = with_suffix($given_mmz_file, '.pdf') if !$pdf_file;
+$pdf_file = find_in($pdf_file);
+paranoia_in($pdf_file);
+paranoia_out($pdf_file,
+ 'I would have to rewrite this file because option --prune was given.')
+ if $prune;
+# Various initializations.
+my $pdf;
+my %extern_pages;
+my $new_mmz;
my $tolerance = 0.01;
-my $done_message = "${message_prefix}Done (there was nothing to extract).\n";
-
-while (<>) {
- if (/^\\mmzNewExtern *{(?P<extern_path>(?P<prefix>.*?)(?P<code_md5sum>[0-9A-F]{32})-(?P<context_md5sum>[0-9A-F]{32})(?:-[0-9]+)?.pdf)}{(?P<page_n>[0-9]+)}{(?P<expected_width>[0-9.]*)pt}{(?P<expected_height>[0-9.]*)pt}/) {
- my $extern_file = file($+{extern_path});
- if (! $extern_file->is_absolute) {
- $extern_file = $mmz_dir->file($+{extern_path});
- }
- paranoia($extern_file);
- my $page = $+{page_n};
- my $expected_width_pt = $+{expected_width};
- my $expected_height_pt = $+{expected_height};
- my $c_memo_file = $mmz_dir->file($+{prefix} . $+{code_md5sum} . '.memo');
- my $cc_memo_file = $mmz_dir->file($+{prefix} . $+{code_md5sum} . '-' . $+{context_md5sum} . '.memo');
- if (!(-e $c_memo_file && -e $cc_memo_file)) {
- print LOG ($warning_template =~ s/\\warningtext/Not extracting page $page into extern $extern_file, because the associated (c)c-memo does not exist/gr), "\n\\endinput\n";
- last;
- }
- eval { $pdf = PDF::API2->open($pdf_file->stringify) unless $pdf; };
- if ($@) {
- print LOG ($warning_template =~ s/\\warningtext/Cannot read file "$pdf_file". Perhaps you have to load Memoize earlier in the preamble?/gr), "\n\\endinput\n";
- close LOG;
- die "${message_prefix}File '$pdf_file' cannot be read, bailing out.\n";
- }
- my $extern = PDF::API2->new();
- $extern->version($pdf->version);
- $extern->import_page($pdf, $page);
- my $extern_page = $extern->open_page(1);
- my ($x0, $y0, $x1, $y1) = $extern_page->get_mediabox();
- my $width_pt = ($x1 - $x0) / 72 * 72.27;
- my $height_pt = ($y1 - $y0) / 72 * 72.27;
- my $warning = '';
- if (abs($width_pt - $expected_width_pt) > $tolerance
- || abs($height_pt - $expected_height_pt) > $tolerance)
- {
- $warning = "I refuse to extract page $page from $pdf_file, because its size (${width_pt}pt x ${height_pt}pt) is not what I expected (${expected_width_pt}pt x ${expected_height_pt}pt)";
- print LOG ($warning_template =~ s/\\warningtext/$warning/gr), "\n";
- }
- if ($warning && !$force) {
- unlink $extern_file;
- } else {
- $extern->saveas($extern_file->stringify);
- $done_message = "${message_prefix}Done.\n";
- print STDOUT "${message_prefix} Page $page --> $extern_file\n" unless $quiet;
- $extern_pages{$page} = 1 if $prune;
- print("%") unless $keep;
- }
- }
- print unless $keep;
-}
-
-print $done_message unless $quiet;
-
-if ($pdf and $prune) {
- paranoia($pdf_file);
- my $pruned_pdf = PDF::API2->new();
- for (my $n = 1; $n <= $pdf->page_count(); $n++) {
- if (! $extern_pages{$n}) {
- $pruned_pdf->import_page($pdf, $n);
- }
+info("Extracting new externs listed in '$mmz_file' " .
+ "from '$pdf_file' using Perl module $pdf_library");
+my $done_message = "Done (there was nothing to extract)";
+$indent = ' ';
+$texindent = '\space\space ';
+my $dir_to_make;
+
+# \paragraph{Process \texttt{.mmz}}
+#
+# We cannot process the .mmz file using in-place editing. It would fail when
+# the file is writable but its parent directory is not.
+
+open (MMZ, $mmz_file);
+while (<MMZ>) {
+ my $mmz_line = $_;
+ if (/^\\mmzPrefix *{(?P<prefix>)}/) {
+ # Found |\mmzPrefix|: create the extern directory, but only later, if an
+ # extern file is actually produced. We parse the prefix in two steps
+ # because we have to unquote the entire prefix.
+ my $prefix = unquote($+{prefix});
+ warning("Cannot parse line '$mmz_line'") unless
+ $prefix =~ /(?P<dir_prefix>.*\/)?(?P<name_prefix>.*?)/;
+ $dir_to_make = $+{dir_prefix};
+ } elsif (/^\\mmzNewExtern\ *{(?P<extern_path>.*?)}{(?P<page_n>[0-9]+)}#
+ {(?P<expected_width>[0-9.]*)pt}{(?P<expected_height>[0-9.]*)pt}/x) {
+ # Found |\mmzNewExtern|: extract the extern page into an extern file.
+ $done_message = "Done";
+ my $ok = 1;
+ my %m_ne = %+;
+ # The extern filename, as specified in |.mmz|:
+ my $extern_file = unquote($m_ne{extern_path});
+ # We parse the extern filename in a separate step because we have to
+ # unquote the entire path.
+ warning("Cannot parse line '$mmz_line'") unless
+ $extern_file =~ /(?P<dir_prefix>.*\/)?(?P<name_prefix>.*?)#
+ (?P<code_md5sum>[0-9A-F]{32})-#
+ (?P<context_md5sum>[0-9A-F]{32})(?:-[0-9]+)?.pdf/x;
+ # The actual extern filename:
+ my $extern_file_out = find_out($extern_file);
+ paranoia_out($extern_file_out);
+ my $page = $m_ne{page_n};
+ # Check whether c-memo and cc-memo exist (in any input directory).
+ my $c_memo = with_name($extern_file,
+ $+{name_prefix} . $+{code_md5sum} . '.memo');
+ my $cc_memo = with_name($extern_file,
+ $+{name_prefix} . $+{code_md5sum} .
+ '-' . $+{context_md5sum} . '.memo');
+ my $c_memo_in = find_in($c_memo);
+ my $cc_memo_in = find_in($cc_memo);
+ if ((! access_in($c_memo_in) || ! access_in($cc_memo_in)) && !$force) {
+ warning("I refuse to extract page $page into extern '$extern_file', " .
+ "because the associated c-memo '$c_memo' and/or " .
+ "cc-memo '$cc_memo' does not exist");
+ $ok = '';
+ }
+ # Load the PDF. We only do this now so that we don't load it if there
+ # is nothing to extract.
+ if ($ok && ! $pdf) {
+ if (!access_in($pdf_file)) {
+ warning("Cannot open '$pdf_file'", '');
+ endinput();
+ }
+ # Temporarily disable error handling, so that we can catch the error
+ # ourselves.
+ $SIG{__DIE__} = undef; $SIG{__WARN__} = undef;
+ # All safe, |paranoia_in| was already called above.
+ eval { $pdf = $pdf_library->open($pdf_file, msgver => 0) };
+ $SIG{__DIE__} = \&die_handler; $SIG{__WARN__} = \&warn_handler;
+ error("File '$pdf_file' seems corrupted. " .
+ "Perhaps you have to load Memoize earlier in the preamble",
+ "In particular, Memoize must be loaded before TikZ library " .
+ "'fadings' and any package deploying it, and in Beamer, " .
+ "load Memoize by writing \\RequirePackage{memoize} before " .
+ "\\documentclass{beamer}. " .
+ "This was the error thrown by Perl:" . "\n$@") if $@;
+ }
+ # Does the page exist?
+ if ($ok && $page > (my $n_pages = $pdf->page_count())) {
+ error("I cannot extract page $page from '$pdf_file', " .
+ "as it contains only $n_pages page" .
+ ($n_pages > 1 ? 's' : ''), '');
+ }
+ if ($ok) {
+ # Import the page into the extern PDF (no disk access yet).
+ my $extern = $pdf_library->new(outver => $pdf->version);
+ $extern->import_page($pdf, $page);
+ my $extern_page = $extern->open_page(1);
+ # Check whether the page size matches the |.mmz| expectations.
+ my ($x0, $y0, $x1, $y1) = $extern_page->get_mediabox();
+ my $width_pt = ($x1 - $x0) / 72 * 72.27;
+ my $height_pt = ($y1 - $y0) / 72 * 72.27;
+ my $expected_width_pt = $m_ne{expected_width};
+ my $expected_height_pt = $m_ne{expected_height};
+ if ((abs($width_pt - $expected_width_pt) > $tolerance
+ || abs($height_pt - $expected_height_pt) > $tolerance) && !$force) {
+ warning("I refuse to extract page $page from $pdf_file, " .
+ "because its size (${width_pt}pt x ${height_pt}pt) " .
+ "is not what I expected " .
+ "(${expected_width_pt}pt x ${expected_height_pt}pt)");
+ } else {
+ # All tests were successful, let's create the extern file.
+ # First, the containing directory, if necessary.
+ if ($dir_to_make) {
+ make_directory($dir_to_make);
+ $dir_to_make = undef;
+ }
+ # Now the extern file. Note that |paranoia_out| was already
+ # called above.
+ info("Page $page --> $extern_file_out");
+ $extern->saveas($extern_file_out);
+ # This page will get pruned.
+ $extern_pages{$page} = 1 if $prune;
+ # Comment out this |\mmzNewExtern|.
+ $new_mmz .= '%' unless $keep;
+ }
+ }
}
- $pruned_pdf->save($pdf_file->stringify);
- print("${message_prefix}The following extern pages were pruned out of the PDF: ",
- join(",", sort(keys(%extern_pages))) . "\n") unless $quiet;
+ $new_mmz .= $mmz_line unless $keep;
+ stderr_to_warning();
}
+close(MMZ);
+$indent = '';
+$texindent = '';
+info($done_message);
-if ($log) {
- print LOG "\\endinput\n";
- close LOG;
+# Write out the |.mmz| file with |\mmzNewExtern| lines commented out. (All safe,
+# |paranoia_out| was already called above.)
+if (!$keep) {
+ open(MMZ, ">", $mmz_file);
+ print MMZ $new_mmz;
+ close(MMZ);
}
+
+# Remove the extracted pages from the original PDF. (All safe, |paranoia_out|
+# was already called above.)
+if ($prune and keys(%extern_pages) != 0) {
+ my $pruned_pdf = $pdf_library->new();
+ for (my $n = 1; $n <= $pdf->page_count(); $n++) {
+ if (! $extern_pages{$n}) {
+ $pruned_pdf->import_page($pdf, $n);
+ }
+ }
+ $pruned_pdf->save($pdf_file);
+ info("The following extern pages were pruned out of the PDF: " .
+ join(",", sort(keys(%extern_pages))));
+}
+
+endinput();
+
+# Local Variables:
+# fill-column: 79
+# after-save-hook: pl2dtx
+# End:
diff --git a/macros/generic/memoize/memoize-extract.py b/macros/generic/memoize/memoize-extract.py
index 54d8870e7b..29ea58192c 100755
--- a/macros/generic/memoize/memoize-extract.py
+++ b/macros/generic/memoize/memoize-extract.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-
+#
# This file is a part of Memoize, a TeX package for externalization of
# graphics and memoization of compilation results in general, available at
# https://ctan.org/pkg/memoize and https://github.com/sasozivanovic/memoize.
@@ -18,166 +18,552 @@
# The files belonging to this work and covered by LPPL are listed in
# <texmf>/doc/generic/memoize/FILES.
-__version__ = '2023/10/10 v1.0.0'
-
-import argparse, re, sys, os, pdfrw, subprocess, itertools
-from pathlib import Path
-
-parser = argparse.ArgumentParser(
- description = "Extract extern pages out of the document PDF.",
- epilog = "For details, see the man page or the Memoize documentation.",
- prog = 'memoize-extract.py',
-)
-parser.add_argument('--pdf', '-P', help = 'extract from file PDF')
-parser.add_argument('--prune', '-p', action = 'store_true',
- help = 'remove the extern pages after extraction')
-parser.add_argument('--keep', '-k', action = 'store_true',
- help = 'do not mark externs as extracted')
-parser.add_argument('--force', '-f', action = 'store_true',
- help = 'extract even if the size-check fails')
-parser.add_argument('--log', '-l', default = os.devnull, help = 'the log file')
-parser.add_argument('--warning-template', '-w', default = '\warningtext',
- help = '\warningtext in the template will be replaced by the warning message')
-parser.add_argument('--quiet', '-q', action = 'store_true',
- help = "describe what's happening")
-parser.add_argument('--embedded', '-e', action = 'store_true',
- help = "prefix all messages to the standard output with the script name")
-parser.add_argument('--mkdir', '-m', action = 'store_true',
- help = 'create a directory (and exit)')
-parser.add_argument('mmz',
- help = 'the record file produced by Memoize: doc.mmz when compiling doc.tex')
-parser.add_argument('--version', '-V', action = 'version',
- version = f"%(prog)s of Memoize " + __version__)
-
-args = parser.parse_args()
-
-message_prefix = parser.prog + ': ' if args.embedded else ''
-if args.embedded:
- print()
-
-# Even a bit more paranoid than required:
-# (a) disallowing TEXMFOUTPUT=/ (while allowing C:\ on Windows)
-# (b) waiting for access to '-output-directory'.
-output_paths = [Path.cwd()]
-if texmfoutput := subprocess.run(
- ['kpsewhich', '--var-value=TEXMFOUTPUT'],
- capture_output = True).stdout.decode().strip():
- texmfoutput_dir = Path(texmfoutput).resolve()
- if len(texmfoutput_dir.parts) != 1 or texmfoutput_dir.drive:
- output_paths.append(texmfoutput_dir)
-
-def paranoia(f):
- p = Path(f).resolve()
- if p.stem.startswith('.'):
- raise RuntimeError(f"{message_prefix}Cannot create a hidden file or dir: {f}")
- if not any(p.is_relative_to(op) for op in output_paths):
- raise RuntimeError(f"{message_prefix}Cannot write outside the current working or output directory tree: {f}")
-
-mmz_file = Path(args.mmz)
+__version__ = '2024/01/02 v1.1.0'
+
+import argparse, re, sys, os, subprocess, itertools, traceback, platform
+from pathlib import Path, PurePath
+# We will only try to import the PDF processing library once we set up the
+# error log.
+
+# \paragraph{Messages}
+
+# The messages are written both to the extraction log and the terminal (we
+# output to stdout rather than stderr so that messages on the TeX terminal and
+# document |.log| appear in chronological order). Messages are automatically
+# adapted to the TeX |--format|.
+
+# The format of the messages. It depends on the given |--format|; the last
+# entry is for t the terminal output.
+
+ERROR = {
+ 'latex': r'\PackageError{{{package_name}}}{{{short}}}{{{long}}}',
+ 'plain': r'\errhelp{{{long}}}\errmessage{{{package_name}: {short}}}',
+ 'context': r'\errhelp{{{long}}}\errmessage{{{package_name}: {short}}}',
+ None: '{header}{short}.\n{long}',
+}
+
+WARNING = {
+ 'latex': r'\PackageWarning{{{package_name}}}{{{texindent}{text}}}',
+ 'plain': r'\message{{{package_name}: {texindent}{text}}}',
+ 'context': r'\message{{{package_name}: {texindent}{text}}}',
+ None: r'{header}{indent}{text}.',
+}
+
+INFO = {
+ 'latex': r'\PackageInfo{{{package_name}}}{{{texindent}{text}}}',
+ 'plain': r'\message{{{package_name}: {texindent}{text}}}',
+ 'context': r'\message{{{package_name}: {texindent}{text}}}',
+ None: r'{header}{indent}{text}.',
+}
+
+# Some variables used in the message routines; note that |header| will be
+# redefined once we parse the arguments.
+
+package_name = 'memoize (python-based extraction)'
+exit_code = 0
+log = None
+header = ''
+indent = ''
+texindent = ''
+
+# The message routines.
+
+def error(short, long):
+ if not args.quiet:
+ print(ERROR[None].format(short = short, long = long, header = header))
+ if log:
+ long = long.replace('\\', '\\string\\')
+ print(
+ ERROR[args.format].format(
+ short = short, long = long, package_name = package_name),
+ file = log)
+ global exit_code
+ exit_code = 11
+ endinput()
+
+def warning(text):
+ if log:
+ print(
+ WARNING[args.format].format(
+ text = text, texindent = texindent, package_name = package_name),
+ file = log)
+ if not args.quiet:
+ print(WARNING[None].format(text = text, header = header, indent = indent))
+ global exit_code
+ exit_code = 10
-if args.mkdir: # Here, argument "mmz" is interpreted as the directory to create.
- # We cannot simply say
- # paranoia(mmz_file)
- # mmz_file.mkdir(parents = True, exist_ok = True)
- # because have be paranoid about the intermediate directories as well:
- # memoize-extract.py -m a/../../BAD/../<cwd-name>/b
- # Note that paranoia might kick in only after a part of the given path was
- # already created. This is in accord to how "mkdir" behaves wrt existing
- # files.
- for folder in itertools.chain(reversed(mmz_file.parents), (mmz_file,)):
- if not folder.is_dir():
- paranoia(folder)
- folder.mkdir(exist_ok = True)
- if not args.quiet:
- print(f"{message_prefix}Created directory {folder}")
- sys.exit()
-elif mmz_file.suffix != '.mmz':
- raise RuntimeError(f"{message_prefix}The 'mmz' argument should be a file with suffix '.mmz', not '{mmz_file}'")
-
-mmz_dir = mmz_file.parent
-pdf_file = Path(args.pdf) if args.pdf else mmz_file.with_suffix('.pdf')
-paranoia(pdf_file)
-pdf = None
-extern_pages = []
-new_mmz = []
-args.log is os.devnull or paranoia(Path(args.log))
-re_newextern = re.compile(r'\\mmzNewExtern *{(?P<extern_fn>(?P<prefix>.*?)(?P<code_md5sum>[0-9A-F]{32})-(?P<context_md5sum>[0-9A-F]{32})(?:-[0-9]+)?.pdf)}{(?P<page_n>[0-9]+)}{(?P<expected_width>[0-9.]*)pt}{(?P<expected_height>[0-9.]*)pt}')
-tolerance = 0.01
-done_message = f"{message_prefix}Done (there was nothing to extract)."
-
-# Complication: I want to use 'with', but don't have to open stderr.
-with open(args.log, 'w') as log:
- log = sys.stderr if args.log is os.devnull else log
+def info(text):
+ if text and not args.quiet:
+ print(INFO[None].format(text = text, header = header, indent = indent))
+ if log:
+ print(
+ INFO[args.format].format(
+ text = text, texindent = texindent, package_name = package_name),
+ file = log)
+
+# Mark the log as complete and exit.
+def endinput():
+ if log:
+ print(r'\endinput', file = log)
+ log.close()
+ sys.exit(exit_code)
+
+# \paragraph{Permission-related functions}
+
+# |paranoia_in|/|out| should work exactly as
+# |kpsewhich -safe-in-name|/|-safe-out-name|.
+def paranoia_in(f, remark = ''):
+ if f and not _paranoia(f, openin_any):
+ error(f"I'm not allowed to read from '{f}' (openin_any = {openin_any})",
+ remark)
+
+def paranoia_out(f, remark = ''):
+ if f and not _paranoia(f, openout_any):
+ error(f"I'm not allowed to write to '{f}' (openout_any = {openout_any})",
+ remark)
+
+def _paranoia(f, mode):
+ # |mode| is the value of |openin_any| or |openout_any|. |f| is a
+ # |pathlib.Path| object.
+ return (
+ # In mode `any' (|a|, |y| or |1|), we may access any file.
+ mode in 'ay1'
+ or (
+ # Otherwise, we are at least in the restricted mode, so we should not
+ # open dot files on Unix-like systems (except file called |.tex|).
+ not (os.name == 'posix' and f.stem.startswith('.') and f.stem != '.tex')
+ and (
+ # If we are precisely in the restricted mode (|r|, |n|, |0|),
+ # then there are no further restrictions.
+ mode in 'rn0'
+ # Otherwise, we are in the paranoid mode (officially |p|, but
+ # any other value is interpreted as |p| as well). There are
+ # two further restrictions in the paranoid mode.
+ or (
+ # We're not allowed to go to a parent directory.
+ '..' not in f.parts
+ and
+ # If the given path is absolute, is should be a descendant
+ # of either |TEXMF_OUTPUT_DIRECTORY| or |TEXMFOUTPUT|.
+ (not f.is_absolute()
+ or
+ is_ancestor(texmf_output_directory, f)
+ or
+ is_ancestor(texmfoutput, f)
+ )))))
+
+# On Windows, we disallow ``semi-absolute'' paths, i.e.\ paths starting with
+# the |\| but lacking the drive. On Windows, |pathlib|'s |is_absolute| returns
+# |True| only for paths starting with |\| and containing the drive.
+def sanitize_filename(f):
+ if f and platform.system() == 'Windows' and not (f.is_absolute() or not f.drive):
+ error(f"\"Semi-absolute\" paths are disallowed: '{f}'", r"The path must "
+ r"either contain both the drive letter and start with '\', "
+ r"or none of these; paths like 'C:foo' and '\foo' are disallowed")
+
+def access_in(f):
+ return os.access(f, os.R_OK)
+
+# This function can fail on Windows, reporting a non-writable file or dir as
+# writable, because |os.access| does not work with Windows' |icacls|
+# permissions. Consequence: we might try to write to a read-only current or
+# output directory instead of switching to the temporary directory. Paranoia
+# is unaffected, as it doesn't use |access_*| functions.
+def access_out(f):
try:
- mmz = mmz_file.open()
- except FileNotFoundError:
- print(f'''{message_prefix}File "{mmz_file}" does not exist, assuming there's nothing to do.''',
- file = sys.stderr)
+ exists = f.exists()
+ # Presumably, we get this error when the parent directory is not
+ # executable.
+ except PermissionError:
+ return
+ if exists:
+ # An existing file should be writable, and if it's a directory, it
+ # should also be executable.
+ return os.access(f, os.W_OK) and (not f.is_dir() or os.access(f, os.X_OK))
else:
- if not args.quiet:
- print(f"{message_prefix}Extracting externs from {pdf_file}")
+ # For a non-existing file, the parent directory should be writable.
+ # (This is the only place where function |pathlib.parent| is used, so
+ # it's ok that it returns the logical parent.)
+ return os.access(f.parent, os.W_OK)
+
+# This function finds the location for an input file, respecting
+# |TEXMF_OUTPUT_DIRECTORY| and |TEXMFOUTPUT|, and the permissions in the
+# filesystem. It returns an absolute file as-is. For a relative file, it
+# tries |TEXMF_OUTPUT_DIRECTORY| (if defined), the current directory (always),
+# and |TEXMFOUTPUT| directory (if defined), in this order. The first readable
+# file found is returned; if no readable file is found, the file in the current
+# directory is returned.
+def find_in(f):
+ sanitize_filename(f)
+ if f.is_absolute():
+ return f
+ for df in (texmf_output_directory / f if texmf_output_directory else None,
+ f,
+ texmfoutput / f if texmfoutput else None):
+ if df and access_in(df):
+ return df
+ return f
+
+# This function finds the location for an output file, respecting
+# |TEXMF_OUTPUT_DIRECTORY| and |TEXMFOUTPUT|, and the permissions in the
+# filesystem. It returns an absolute file as-is. For a relative file, it
+# tries |TEXMF_OUTPUT_DIRECTORY| (if defined), the current directory (unless
+# |TEXMF_OUTPUT_DIRECTORY| is defined), and |TEXMFOUTPUT| directory (if
+# defined), in this order. The first writable file found is returned; if no
+# writable file is found, the file in either the current or the output
+# directory is returned.
+def find_out(f):
+ sanitize_filename(f)
+ if f.is_absolute():
+ return f
+ for df in (texmf_output_directory / f if texmf_output_directory else None,
+ f if not texmf_output_directory else None,
+ texmfoutput / f if texmfoutput else None):
+ if df and access_out(df):
+ return df
+ return texmf_output_directory / f if texmf_output_directory else f
+
+# This function assumes that both paths are absolute; ancestor may be |None|,
+# signaling a non-path.
+def is_ancestor(ancestor, descendant):
+ if not ancestor:
+ return
+ a = ancestor.parts
+ d = descendant.parts
+ return len(a) < len(d) and a == d[0:len(a)]
+
+# A paranoid |Path.mkdir|. The given folder is preprocessed by |find_out|.
+def mkdir(folder):
+ folder = find_out(Path(folder))
+ if not folder.exists():
+ paranoia_out(folder)
+ # Using |folder.mkdir| is fine because we know that
+ # |TEXMF_OUTPUT_DIRECTORY|/|TEXMFOUTPUT|, if given, exists, and that
+ # ``folder'' contains no |..|.
+ folder.mkdir(parents = True, exist_ok = True)
+ # This does not get logged when the function is invoked via |--mkdir|,
+ # as it is not clear what the log name should be.
+ info(f"Created directory {folder}")
+
+_re_unquote = re.compile(r'"(.*?)"')
+def unquote(fn):
+ return _re_unquote.sub(r'\1', fn)
+
+# \paragraph{Kpathsea}
+
+# Get the values of |openin_any|, |openout_any|, |TEXMFOUTPUT| and
+# |TEXMF_OUTPUT_DIRECTORY|.
+
+kpsewhich_output = subprocess.run(['kpsewhich',
+ f'-expand-var='
+ f'openin_any=$openin_any,'
+ f'openout_any=$openout_any,'
+ f'TEXMFOUTPUT=$TEXMFOUTPUT'],
+ capture_output = True
+ ).stdout.decode().strip()
+if not kpsewhich_output:
+ # No TeX? (Note that |kpsewhich| should exist in MiKTeX as well.) In
+ # absence of |kpathsea| information, we get very paranoid, but still try to
+ # get |TEXMFOUTPUT| from an environment variable.
+ openin_any, openout_any = 'p', 'p'
+ texmfoutput, texmf_output_directory = None, None
+ # Unfortunately, this warning can't make it into the log. But then again,
+ # the chances of a missing |kpsewhich| are very slim, and its absence would
+ # show all over the place anyway.
+ warning('I failed to execute "kpsewhich"; , is there no TeX system installed? '
+ 'Assuming openin_any = openout_any = "p" '
+ '(i.e. restricting all file operations to non-hidden files '
+ 'in the current directory of its subdirectories).')
+else:
+ m = re.fullmatch(r'openin_any=(.*),openout_any=(.*),TEXMFOUTPUT=(.*)',
+ kpsewhich_output)
+ openin_any, openout_any, texmfoutput = m.groups()
+ texmf_output_directory = os.environ.get('TEXMF_OUTPUT_DIRECTORY', None)
+ if openin_any == '$openin_any':
+ # When the |open*_any| variables are not expanded, we assume we're
+ # running MiKTeX. The two config settings below correspond to TeXLive's
+ # |openin_any| and |openout_any|; afaik, there is no analogue to
+ # |TEXMFOUTPUT|.
+ initexmf_output = subprocess.run(
+ ['initexmf', '--show-config-value=[Core]AllowUnsafeInputFiles',
+ '--show-config-value=[Core]AllowUnsafeOutputFiles'],
+ capture_output = True).stdout.decode().strip()
+ openin_any, openout_any = initexmf_output.split()
+ openin_any = 'a' if openin_any == 'true' else 'p'
+ openout_any = 'a' if openout_any == 'true' else 'p'
+ texmfoutput = None
+ texmf_output_directory = None
+
+# An output directory should exist, and may not point to the root on Linux. On
+# Windows, it may point to the root, because we only allow absolute filenames
+# containing the drive, e.g.\ |F:\|; see |is_absolute|.
+def sanitize_output_dir(d_str):
+ d = Path(d_str) if d_str else None
+ sanitize_filename(d)
+ return d if d and d.is_dir() and \
+ (not d.is_absolute() or len(d.parts) != 1 or d.drive) else None
+
+texmfoutput = sanitize_output_dir(texmfoutput)
+texmf_output_directory = sanitize_output_dir(texmf_output_directory)
+
+class NotExtracted(UserWarning):
+ pass
+
+# We don't delve into the real script when loaded from the testing code.
+if __name__ == '__main__':
+
+ # \paragraph{Arguments}
+
+ parser = argparse.ArgumentParser(
+ description = "Extract extern pages produced by package Memoize "
+ "out of the document PDF.",
+ epilog = "For details, see the man page or the Memoize documentation.",
+ prog = 'memoize-extract.py',
+ )
+ parser.add_argument('-P', '--pdf', help = 'extract from file PDF')
+ parser.add_argument('-p', '--prune', action = 'store_true',
+ help = 'remove the extern pages after extraction')
+ parser.add_argument('-k', '--keep', action = 'store_true',
+ help = 'do not mark externs as extracted')
+ parser.add_argument('-F', '--format', choices = ['latex', 'plain', 'context'],
+ help = 'the format of the TeX document invoking extraction')
+ parser.add_argument('-f', '--force', action = 'store_true',
+ help = 'extract even if the size-check fails')
+ parser.add_argument('-q', '--quiet', action = 'store_true',
+ help = "describe what's happening")
+ parser.add_argument('-m', '--mkdir', action = 'store_true',
+ help = 'create a directory (and exit); '
+ 'mmz argument is interpreted as directory name')
+ parser.add_argument('-V', '--version', action = 'version',
+ version = f"%(prog)s of Memoize " + __version__)
+ parser.add_argument('mmz', help = 'the record file produced by Memoize: '
+ 'doc.mmz when compiling doc.tex '
+ '(doc and doc.tex are accepted as well)')
+
+ args = parser.parse_args()
+
+ header = parser.prog + ': ' if args.format else ''
+
+ # Start a new line in the TeX terminal output.
+ if args.format:
+ print()
+
+ # \paragraph{Initialization}
+
+ # With |--mkdir|, argument |mmz| is interpreted as the directory to create.
+ if args.mkdir:
+ mkdir(args.mmz)
+ sys.exit()
+
+ # Normalize the |mmz| argument into a |.mmz| filename.
+ mmz_file = Path(args.mmz)
+ if mmz_file.suffix == '.tex':
+ mmz_file = mmz_file.with_suffix('.mmz')
+ elif mmz_file.suffix != '.mmz':
+ mmz_file = mmz_file.with_name(mmz_file.name + '.mmz')
+
+ # Once we have the |.mmz| filename, we can open the log.
+ if args.format:
+ log_file = find_out(mmz_file.with_suffix('.mmz.log'))
+ paranoia_out(log_file)
+ info(f"Logging to '{log_file}'");
+ log = open(log_file, 'w')
+
+ # Now that we have opened the log file, we can try loading the PDF
+ # processing library.
+ try:
+ import pdfrw
+ except ModuleNotFoundError:
+ error("Python module 'pdfrw' was not found",
+ 'Have you followed the instructions is section 1.1 of the manual?')
+
+ # Catch any errors in the script and output them to the log.
+ try:
+
+ # Find the |.mmz| file we will read, but retain the original filename
+ # in |given_mmz_file|, as we will still need it.
+ given_mmz_file = mmz_file
+ mmz_file = find_in(mmz_file)
+ paranoia_in(mmz_file)
+ if not args.keep:
+ paranoia_out(mmz_file,
+ remark = 'This file is rewritten unless option --keep is given.')
+ try:
+ mmz = open(mmz_file)
+ except FileNotFoundError:
+ info(f"File '{given_mmz_file}' does not exist, "
+ f"assuming there's nothing to do")
+ endinput()
+
+ # Determine the PDF filename: it is either given via |--pdf|, or
+ # constructed from the |.mmz| filename.
+ pdf_file = find_in(Path(args.pdf)
+ if args.pdf else given_mmz_file.with_suffix('.pdf'))
+ paranoia_in(pdf_file)
+ if args.prune:
+ paranoia_out(pdf_file,
+ remark = 'I would have to rewrite this file '
+ 'because option --prune was given.')
+
+ # Various initializations.
+
+ re_prefix = re.compile(r'\\mmzPrefix *{(?P<prefix>.*?)}')
+ re_split_prefix = re.compile(r'(?P<dir_prefix>.*/)?(?P<name_prefix>.*?)')
+ re_newextern = re.compile(
+ r'\\mmzNewExtern *{(?P<extern_path>.*?)}{(?P<page_n>[0-9]+)}'
+ r'{(?P<expected_width>[0-9.]*)pt}{(?P<expected_height>[0-9.]*)pt}')
+ re_extern_path = re.compile(
+ r'(?P<dir_prefix>.*/)?(?P<name_prefix>.*?)'
+ r'(?P<code_md5sum>[0-9A-F]{32})-'
+ r'(?P<context_md5sum>[0-9A-F]{32})(?:-[0-9]+)?.pdf')
+ pdf = None
+ extern_pages = []
+ new_mmz = []
+ tolerance = 0.01
+ dir_to_make = None
+ info(f"Extracting new externs listed in '{mmz_file}' from '{pdf_file}'")
+ done_message = "Done (there was nothing to extract)"
+ indent = ' '
+ texindent = '\space\space '
+
+ # \paragraph{Process \texttt{.mmz}}
+
for line in mmz:
- if m := re_newextern.match(line):
- extern_file = mmz_dir / m['extern_fn']
- paranoia(extern_file)
- page_n = int(m['page_n'])-1
- c_memo = mmz_dir / (m['prefix'] + m['code_md5sum'] + '.memo')
- cc_memo = mmz_dir / (m['prefix'] + m['code_md5sum'] + '-' + m['context_md5sum'] + '.memo')
- if not (c_memo.exists() and cc_memo.exists()):
- print(args.warning_template.replace('\warningtext', f'Not extracting page {page_n} into extern {extern_file}, because the associated (c)c-memo does not exist'), file = log)
- continue
- if not pdf:
- try:
- pdf = pdfrw.PdfReader(pdf_file)
- except pdfrw.errors.PdfParseError:
- print(f'{message_prefix}File "{pdf_file}" cannot be read, bailing out.', file = sys.stderr)
- print(args.warning_template.replace('\warningtext', f'Cannot read file "{pdf_file}". Perhaps you have to load Memoize earlier in the preamble?'), file = log)
- args.keep = True
- break
- extern = pdfrw.PdfWriter(extern_file)
- page = pdf.pages[page_n]
- expected_width_pt, expected_height_pt = float(m['expected_width']), float(m['expected_height'])
- mb = page['/MediaBox']
- width_bp, height_bp = float(mb[2]) - float(mb[0]), float(mb[3]) - float(mb[1])
- width_pt = width_bp / 72 * 72.27
- height_pt = height_bp / 72 * 72.27
- warning = None
- if abs(width_pt - expected_width_pt) > tolerance \
- or abs(height_pt - expected_height_pt) > tolerance:
- warning = (
- f'I refuse to extract page {page_n+1} from "{pdf_file}", '
- f'because its size ({width_pt}pt x {height_pt}pt) is not '
- f'what I expected ({expected_width_pt}pt x {expected_height_pt}pt)')
- print(args.warning_template.replace('\warningtext', warning), file = log)
- if warning and not args.force:
- extern_file.unlink(missing_ok = True)
- else:
+ try:
+ if m_p := re_prefix.match(line):
+ # Found |\mmzPrefix|: create the extern directory, but only
+ # later, if an extern file is actually produced. We parse
+ # the prefix in two steps because we have to unquote the
+ # entire prefix.
+ prefix = unquote(m_p['prefix'])
+ if not (m_sp := re_split_prefix.match(prefix)):
+ warning(f"Cannot parse line {line.strip()}")
+ dir_to_make = m_sp['dir_prefix']
+ elif m_ne := re_newextern.match(line):
+ # Found |\mmzNewExtern|: extract the extern page into an
+ # extern file.
+ done_message = "Done"
+ # The extern filename, as specified in |.mmz|:
+ unquoted_extern_path = unquote(m_ne['extern_path'])
+ extern_file = Path(unquoted_extern_path)
+ # We parse the extern filename in a separate step because
+ # we have to unquote the entire path.
+ if not (m_ep := re_extern_path.match(unquoted_extern_path)):
+ warning(f"Cannot parse line {line.strip()}")
+ # The actual extern filename:
+ extern_file_out = find_out(extern_file)
+ paranoia_out(extern_file_out)
+ page_n = int(m_ne['page_n'])-1
+ # Check whether c-memo and cc-memo exist (in any input
+ # directory).
+ c_memo = extern_file.with_name(
+ m_ep['name_prefix'] + m_ep['code_md5sum'] + '.memo')
+ cc_memo = extern_file.with_name(
+ m_ep['name_prefix'] + m_ep['code_md5sum']
+ + '-' + m_ep['context_md5sum'] + '.memo')
+ c_memo_in = find_in(c_memo)
+ cc_memo_in = find_in(cc_memo)
+ if not (access_in(c_memo_in) and access_in(cc_memo_in)) \
+ and not args.force:
+ warning(f"I refuse to extract page {page_n+1} into extern "
+ f"'{extern_file}', because the associated c-memo "
+ f"'{c_memo}' and/or cc-memo '{cc_memo}' "
+ f"does not exist")
+ raise NotExtracted()
+ # Load the PDF. We only do this now so that we don't load
+ # it if there is nothing to extract.
+ if not pdf:
+ if not access_in(pdf_file):
+ warning(f"Cannot open '{pdf_file}'")
+ endinput()
+ try:
+ # All safe, |paranoia_in| was already called above.
+ pdf = pdfrw.PdfReader(pdf_file)
+ except pdfrw.errors.PdfParseError as err:
+ error(rf"File '{pdf_file}' seems corrupted. Perhaps you "
+ rf"have to load Memoize earlier in the preamble",
+ f"In particular, Memoize must be loaded before "
+ f"TikZ library 'fadings' and any package "
+ f"deploying it, and in Beamer, load Memoize "
+ f"by writing \RequirePackage{{memoize}} before "
+ f"\documentclass{{beamer}}. "
+ f"This was the error thrown by Python: \n{err}")
+ # Does the page exist?
+ if page_n >= len(pdf.pages):
+ error(rf"I cannot extract page {page_n} from '{pdf_file}', "
+ rf"as it contains only {len(pdf.pages)} page" +
+ ('s' if len(pdf.pages) > 1 else ''), '')
+ # Check whether the page size matches the |.mmz|
+ # expectations.
+ page = pdf.pages[page_n]
+ expected_width_pt = float(m_ne['expected_width'])
+ expected_height_pt = float(m_ne['expected_height'])
+ mb = page['/MediaBox']
+ width_bp = float(mb[2]) - float(mb[0])
+ height_bp = float(mb[3]) - float(mb[1])
+ width_pt = width_bp / 72 * 72.27
+ height_pt = height_bp / 72 * 72.27
+ if (abs(width_pt - expected_width_pt) > tolerance
+ or abs(height_pt - expected_height_pt) > tolerance) \
+ and not args.force:
+ warning(
+ f"I refuse to extract page {page_n+1} from '{pdf_file}' "
+ f"because its size ({width_pt}pt x {height_pt}pt) "
+ f"is not what I expected "
+ f"({expected_width_pt}pt x {expected_height_pt}pt)")
+ raise NotExtracted()
+ # All tests were successful, let's create the extern file.
+ # First, the containing directory, if necessary.
+ if dir_to_make:
+ mkdir(dir_to_make)
+ dir_to_make = None
+ # Now the extern file. Note that |paranoia_out| was
+ # already called above.
+ info(f"Page {page_n+1} --> {extern_file_out}")
+ extern = pdfrw.PdfWriter(extern_file_out)
extern.addpage(page)
- if not args.quiet:
- print(f"{message_prefix} Page {page_n+1} --> {extern_file}", file = sys.__stdout__)
extern.write()
- done_message = f"{message_prefix}Done."
+ # This page will get pruned.
if args.prune:
extern_pages.append(page_n)
+ # Comment out this |\mmzNewExtern|.
if not args.keep:
line = '%' + line
- if not args.keep:
- new_mmz.append(line)
+ except NotExtracted:
+ pass
+ finally:
+ if not args.keep:
+ new_mmz.append(line)
mmz.close()
- if not args.quiet:
- print(done_message)
+ indent = ''
+ texindent = ''
+ info(done_message)
+
+ # Write out the |.mmz| file with |\mmzNewExtern| lines commented
+ # out. (All safe, |paranoia_out| was already called above.)
if not args.keep:
- paranoia(mmz_file)
with open(mmz_file, 'w') as mmz:
for line in new_mmz:
print(line, file = mmz, end = '')
+
+ # Remove the extracted pages from the original PDF. (All safe,
+ # |paranoia_out| was already called above.)
if args.prune and extern_pages:
pruned_pdf = pdfrw.PdfWriter(pdf_file)
pruned_pdf.addpages(
page for n, page in enumerate(pdf.pages) if n not in extern_pages)
pruned_pdf.write()
- if not args.quiet:
- print(f"{message_prefix}The following extern pages were pruned out of the PDF:",
- ",".join(str(page+1) for page in extern_pages))
- if args.log is not os.devnull:
- print(r'\endinput', file = log)
+ info(f"The following extern pages were pruned out of the PDF: " +
+ ",".join(str(page+1) for page in extern_pages))
+
+ # Report that extraction was successful.
+ endinput()
+
+ # Catch any errors in the script and output them to the log.
+ except Exception as err:
+ error(f'Python error: {err}', traceback.format_exc())
+
+# Local Variables:
+# fill-column: 79
+# after-save-hook: py2dtx
+# End:
diff --git a/macros/generic/memoize/memoize.edtx b/macros/generic/memoize/memoize.edtx
index 10794c7a3b..86ed2b6197 100644
--- a/macros/generic/memoize/memoize.edtx
+++ b/macros/generic/memoize/memoize.edtx
@@ -1,5 +1,3 @@
-% \iffalse
-%
% memoize.edtx (this is not a .dtx file; to produce a .dtx, process it with edtx2dtx)
%
%% This file is a part of Memoize, a TeX package for externalization of
@@ -21,8 +19,6 @@
%% The files belonging to this work and covered by LPPL are listed in
%% (<texmf>/doc/generic/memoize/)FILES.
%
-% \fi
-%
% \begin{macrocode}
%
% \relax
@@ -68,7 +64,7 @@
% listing options app={basicstyle=\ttfamily\scriptsize}}}
%
% \begin{tcbraster}[raster columns=100]
-% \tcbinputlisting{raster multicolumn=55, ins listing, top=1mm, bottom=1mm, title=memoize.ins,listing file=../memoize.ins, linerange={27-38}, leftupper=1mm}
+% \tcbinputlisting{raster multicolumn=55, ins listing, top=1mm, bottom=1mm, title=memoize.ins,listing file=../memoize.ins, linerange={27-39}, leftupper=1mm}
% \begin{tcboxedraster}[raster columns=1]{blankest, raster multicolumn=45}
% \tcbinputlisting{ins listing, title=advice.ins, listing file=../advice.ins, linerange=28-31}
% \tcbinputlisting{ins listing, title=collar\raisebox{0pt}[\height][0pt]{g}s.ins, listing file=../collargs.ins, linerange=29-31}
@@ -77,8 +73,7 @@
%
% Memoize also contains two scripts, |memoize-extract| and |memoize-clean|.
% Both come in two functionally equivalent implementations: Perl (|.pl|) and a
-% Python (|.py|). The code is listed in \S\ref{sec:code:scripts}, I believe it
-% is self-explanatory enough to lack more than a occasional comment.
+% Python (|.py|). Their code is listed in \S\ref{sec:code:scripts}.
%
%
% \thispagestyle{empty}
@@ -95,55 +90,55 @@
%
% \paragraph{Identification} of |memoize|, |memoizable| and |nomemoize|.
%<*mmz>
-%<latex>\ProvidesPackage{memoize}[2023/10/10 v1.0.0 Fast and flexible externalization]
+%<latex>\ProvidesPackage{memoize}[2024/1/02 v1.1.0 Fast and flexible externalization]
%<context>%D \module[
%<context>%D file=t-memoize.tex,
-%<context>%D version=1.0.0,
+%<context>%D version=1.1.0,
%<context>%D title=Memoize,
%<context>%D subtitle=Fast and flexible externalization,
%<context>%D author=Saso Zivanovic,
-%<context>%D date=2023-10-10,
+%<context>%D date=2024-01-02,
%<context>%D copyright=Saso Zivanovic,
%<context>%D license=LPPL,
%<context>%D ]
%<context>\writestatus{loading}{ConTeXt User Module / memoize}
%<context>\unprotect
%<context>\startmodule[memoize]
-%<plain>% Package memoize 2023/10/10 v1.0.0
+%<plain>% Package memoize 2024/01/02 v1.1.0
%</mmz>
%<*mmzable>
-%<latex>\ProvidesPackage{memoizable}[2023/10/10 v1.0.0 A programmer's stub for Memoize]
+%<latex>\ProvidesPackage{memoizable}[2024/1/02 v1.1.0 A programmer's stub for Memoize]
%<context>%D \module[
%<context>%D file=t-memoizable.tex,
-%<context>%D version=1.0.0,
+%<context>%D version=1.1.0,
%<context>%D title=Memoizable,
%<context>%D subtitle=A programmer's stub for Memoize,
%<context>%D author=Saso Zivanovic,
-%<context>%D date=2023-10-10,
+%<context>%D date=2024-01-02,
%<context>%D copyright=Saso Zivanovic,
%<context>%D license=LPPL,
%<context>%D ]
%<context>\writestatus{loading}{ConTeXt User Module / memoizable}
%<context>\unprotect
%<context>\startmodule[memoizable]
-%<plain>% Package memoizable 2023/10/10 v1.0.0
+%<plain>% Package memoizable 2024/01/02 v1.1.0
%</mmzable>
%<*nommz>
-%<latex>\ProvidesPackage{nomemoize}[2023/10/10 v1.0.0 A no-op stub for Memoize]
+%<latex>\ProvidesPackage{nomemoize}[2024/1/02 v1.1.0 A no-op stub for Memoize]
%<context>%D \module[
%<context>%D file=t-nomemoize.tex,
-%<context>%D version=1.0.0,
+%<context>%D version=1.1.0,
%<context>%D title=Memoize,
%<context>%D subtitle=A no-op stub for Memoize,
%<context>%D author=Saso Zivanovic,
-%<context>%D date=2023-10-10,
+%<context>%D date=2024-01-02,
%<context>%D copyright=Saso Zivanovic,
%<context>%D license=LPPL,
%<context>%D ]
%<context>\writestatus{loading}{ConTeXt User Module / nomemoize}
%<context>\unprotect
%<context>\startmodule[nomemoize]
-%<mmz>% Package nomemoize 2023/10/10 v1.0.0
+%<mmz>% Package nomemoize 2024/01/02 v1.1.0
%</nommz>
%
% \paragraph{Required packages} and \hologo{LaTeX}ization of \hologo{plainTeX}
@@ -163,13 +158,6 @@
\def\newtoks{\alloc@5\toks\toksdef\@cclvi}
\def\newwrite{\alloc@7\write\chardef\sixt@@n}
%</mmz&plain>
-% |Nomemoize| has to load |pgfopts| as well, and process package options (right
-% away, why not), otherwise \hologo{LaTeX} will complain.
-%<*latex>
-%<mmz,nommz>\RequirePackage{pgfopts} % pgfkeys-based package options
-%<nommz>\pgfkeys{/memoize/package options/.unknown/.code={}}
-%<nommz>\ProcessPgfPackageOptions{/memoize/package options}
-%</latex>
% I can't really write any code without |etoolbox| \dots
%<*mmz>
%<latex>\RequirePackage{etoolbox}
@@ -238,6 +226,7 @@
}%
}%
}
+%</mmz>
%
% In \hologo{ConTeXt}, |\unexpanded| means |\protected|, and the usual
% |\unexpanded| is available as |\normalunexpanded|. Option one: use dtx
@@ -249,6 +238,18 @@
% |\unexpanded| in the |.dtx|, and |sed| through the generated \hologo{ConTeXt}
% files to replace all its occurrences by |\normalunexpanded|. Oh yeah!
%
+% Load |pgfkeys| in |nomemoize| and |memoizable|. Not necessary in |memoize|,
+% as it is already loaded by CollArgs.
+%<*nommz,mmzable>
+%<latex>\RequirePackage{pgfkeys}
+%<plain>\input pgfkeys
+%<context>\input t-pgfkey
+%</nommz,mmzable>
+%
+% Different formats of |memoizable| merely load |memoizable.code.tex|, which
+% exists so that |memoizable| can be easily loaded by generic code, like a
+% |tikz| library.
+%<mmzable&!generic>\input memoizable.code.tex
%
% \paragraph.{Shipout}
% We will next load our own auxiliary package, CollArgs, but before we do
@@ -260,6 +261,7 @@
% section~\ref{sec:code:extern}. Below, we first check that the current
% meaning of |\shipout| is primitive, and then redefine it.
%
+%<*mmz>
%<*plain>
\def\mmz@regular@shipout{%
\global\advance\mmzRegularPages1\relax
@@ -284,7 +286,7 @@
% \paragraph.{Loading order} |memoize| and |nomemoize| are mutually exclusive,
% and |memoizable| must be loaded before either of them. |\mmz@loadstatus|: 1
% = memoize, 2 = memoizable, 3 = nomemoize.
-%<*(plain,context)&(mmz,nommz,mmzable)>
+%<*mmz,nommz>
\def\ifmmz@loadstatus#1{%
\ifnum#1=0\csname mmz@loadstatus\endcsname\relax
\expandafter\@firstoftwo
@@ -292,11 +294,9 @@
\expandafter\@secondoftwo
\fi
}
-%</(plain,context)&(mmz,nommz,mmzable)>
+%</mmz,nommz>
%<*mmz>
-%<latex>\@ifpackageloaded{nomemoize}%
-%<plain,context>\ifmmz@loadstatus{3}%
-{%
+\ifmmz@loadstatus{3}{%
\PackageError{memoize}{Cannot load the package, as "nomemoize" is already
loaded. Memoization will NOT be in effect}{Packages "memoize" and
"nomemoize" are mutually exclusive, please load either one or the other.}%
@@ -304,9 +304,7 @@
%<latex>\ProcessPgfPackageOptions{/memoize/package options}
\endinput
}{}%
-%<latex>\@ifpackageloaded{memoizable}%
-%<plain,context>\ifmmz@loadstatus{2}%
-{%
+\ifmmz@loadstatus{2}{%
\PackageError{memoize}{Cannot load the package, as "memoizable" is already
loaded}{Package "memoizable" is loaded by packages which support
memoization. Memoize must be loaded before all such packages. The
@@ -324,30 +322,20 @@
%<latex>\ProcessPgfPackageOptions{/memoize/package options}
\endinput
}{}%
-%<plain,context>\ifmmz@loadstatus{1}{\endinput}{}%
-%<plain,context>\def\mmz@loadstatus{1}%
+\ifmmz@loadstatus{1}{\endinput}{}%
+\def\mmz@loadstatus{1}%
%</mmz>
-%<*mmzable>
-%<latex>\@ifpackageloaded{memoize}%
-%<plain,context>\ifmmz@loadstatus{1}%
-{\endinput}{}
-%<latex>\@ifpackageloaded{nomemoize}%
-%<plain,context>\ifmmz@loadstatus{3}%
-{\endinput}{}%
-%<plain,context>\ifmmz@loadstatus{2}{\endinput}{}%
-%<plain,context>\def\mmz@loadstatus{2}%
-%</mmzable>
+%<*mmzable&generic>
+\ifcsname mmz@loadstatus\endcsname\endinput\fi
+\def\mmz@loadstatus{2}%
+%</mmzable&generic>
%<*nommz>
-%<latex>\@ifpackageloaded{memoize}
-%<plain,context>\ifmmz@loadstatus{1}%
-{%
+\ifmmz@loadstatus{1}{%
\PackageError{nomemoize}{Cannot load the package, as "memoize" is already
loaded; memoization will remain in effect}{Packages "memoize" and
"nomemoize" are mutually exclusive, please load either one or the other.}%
\endinput }{}%
-%<latex>\@ifpackageloaded{memoizable}%
-%<plain,context>\ifmmz@loadstatus{2}%
-{%
+\ifmmz@loadstatus{2}{%
\PackageError{nomemoize}{Cannot load the package, as "memoizable" is already
loaded}{Package "memoizable" is loaded by packages which support
memoization. (No)Memoize must be loaded before all such packages. The
@@ -363,8 +351,8 @@
of that package.}%
\endinput
}{}%
-%<plain,context>\ifmmz@loadstatus{3}{\endinput}{}%
-%<plain,context>\def\mmz@loadstatus{3}%
+\ifmmz@loadstatus{3}{\endinput}{}%
+\def\mmz@loadstatus{3}%
%</nommz>
%
%<*mmz>
@@ -373,7 +361,7 @@
% Read \hologo{TeX} file |#2| into token register |#1| (under the current
% category code regime); |\toksapp| is defined in CollArgs.
\def\filetotoks#1#2{%
- \immediate\openin0{#2}%
+ \immediate\mmz@openin0{#2}%
#1={}%
\loop
\unless\ifeof0
@@ -385,6 +373,12 @@
}
% \end{macro}
%
+% \begin{macro}{\mmz@openin,\mmz@openout}
+% A workaround for |morewrites|.
+\let\mmz@openin\openin
+\let\mmz@openout\openout
+% \end{macro}
+%
% \paragraph{Other} little things.
\newif\ifmmz@temp
\newtoks\mmz@temptoks
@@ -395,22 +389,24 @@
% \label{sec:code:configuration}
%
% \begin{macro}{\mmzset}
-% The user primarily interacts with |Memoize| through the |pgfkeys|-based
+% The user primarily interacts with Memoize through the |pgfkeys|-based
% configuration macro |\mmzset|, which executes keys in path |/mmz|. In
% |nomemoize| and |memoizable|, is exists as a no-op.
\def\mmzset#1{\pgfqkeys{/mmz}{#1}\ignorespaces}
%</mmz>
-%<nommz,mmzable>\def\mmzset#1{\ignorespaces}
+%<*nommz,mmzable&generic>
+\def\mmzset#1{\ignorespaces}
+%</nommz,mmzable&generic>
% \end{macro}
-%
-% \begin{macro}{\mmzset}
+%
+% \begin{macro}{\nommzkeys}
% Any |/mmz| keys used outside of |\mmzset| must be declared by this macro
% for |nomemoize| package to work.
%<mmz>\def\nommzkeys#1{}
-%<*nommz,mmzable>
+%<*nommz,mmzable&generic>
\def\nommzkeys{\pgfqkeys{/mmz}}
\pgfqkeys{/mmz}{.unknown/.code={\pgfkeysdef{\pgfkeyscurrentkey}{}}}
-%</nommz,mmzable>
+%</nommz,mmzable&generic>
% \end{macro}
%
% \begin{key}{enable, disable}
@@ -427,7 +423,9 @@
%
% |Nomemoize| does not need the keys themselves, but it does need the
% underlying conditional --- which will be always false.
-%<mmz,nommz,mmzable>\newif\ifmemoize
+%<*mmz,nommz,mmzable&generic>
+\newif\ifmemoize
+%</mmz,nommz,mmzable&generic>
%<*mmz>
\mmzset{%
enable/.style={begindocument/.append code=\memoizetrue},
@@ -439,10 +437,15 @@
% Memoize is enabled at the beginning of the document, unless explicitly
% disabled by the user in the preamble.
enable,
-}
% \end{macro}
% \end{key}
%
+% \begin{key}{options}
+% Execute the given value as a keylist of Memoize settings.
+ options/.style={#1},
+}
+% \end{key}
+%
% \begin{key}{normal,readonly,recompile}
% When Memoize is enabled, it can be in one of three modes
% (\MS\ref{sec:tut:working-on-a-picture}): normal, readonly, and recompile.
@@ -461,7 +464,7 @@
}
% \end{key}
%
-% \begin{key}{path, path/relative, path/dir, path/prefix}
+% \begin{key}{prefix}
% Key |path| executes the given keylist in path |/mmz/path|, to determine the
% full \emph{path prefix} to memo and extern files
% (\MS\ref{sec:tut:memodir},\ref{sec:memos}): |relative|, true by default,
@@ -471,56 +474,77 @@
% under user control, and neither is the suffix. These subkeys will be
% initialized a bit later, via |no memo dir|.
\mmzset{%
- path/.code={\pgfqkeys{/mmz/path}{#1}},
- path/.cd,
- relative/.is if=mmz@relativepath,
- dir/.store in=\mmz@dir,
- dir/.value required,
- prefix/.store in=\mmz@prefix,
- prefix/.value required,
-}
-\newif\ifmmz@relativepath
-% Key |path| concludes by performing two post-path-setting actions: creating
-% the given directory if |mkdir| is in effect, and noting the new path prefix
-% in record files (by eventually executing |record/prefix|, which typically
-% puts a |\mmzPrefix| line in the |.mmz| file). These actions are the reason
-% for having the path setting keys grouped under |path| --- we don't want them
-% to be triggered by changes of the individual components of the path.
-% Similarly, we don't want them triggered by multiple invocations of |path| in
-% the preamble; only the final setting matters, so |path| is only equipped with
-% the action-triggering code at the beginning of the document.
+ prefix/.code={\mmz@parse@prefix{#1}},
+}
+% \begin{macro}{\mmz@split@prefix}
+% This macro stores the detokenized expansion of |#1| into |\mmz@prefix|,
+% which it then splits into |\mmz@prefix@dir| and |\mmz@prefix@name| at the
+% final |/|. The slash goes into |\mmz@prefix@dir|. If there is no slash,
+% |\mmz@prefix@dir| is empty.
+\begingroup
+\catcode`\/=12
+\gdef\mmz@parse@prefix#1{%
+ \edef\mmz@prefix{\detokenize\expandafter{\expanded{#1}}}%
+ \def\mmz@prefix@dir{}%
+ \def\mmz@prefix@name{}%
+ \expandafter\mmz@parse@prefix@i\mmz@prefix/\mmz@eov
+}
+\gdef\mmz@parse@prefix@i#1/#2{%
+ \ifx\mmzeov#2%
+ \def\mmz@prefix@name{#1}%
+ \else
+ \appto\mmz@prefix@dir{#1/}%
+ \expandafter\mmz@parse@prefix@i\expandafter#2%
+ \fi
+}
+\endgroup
+% \end{macro}
+%
+% Key |prefix| concludes by performing two actions: it creates the given
+% directory if |mkdir| is in effect, and notes the new prefix in record files
+% (by eventually executing |record/prefix|, which typically puts a |\mmzPrefix|
+% line in the |.mmz| file). In the preamble, only the final setting of
+% |prefix| matters, so this key is only equipped with the action-triggering
+% code at the beginning of the document.
\mmzset{%
begindocument/.append style={
- path/.append code=\mmz@maybe@mkmemodir\mmz@record@prefix,
+ prefix/.append code=\mmz@maybe@mkmemodir\mmz@record@prefix,
},
- % Consequently, the post-path-setting actions must be triggered manually at
+ % Consequently, the post-prefix-setting actions must be triggered manually at
% the beginning of the document. Below, we trigger directory creation;
% |record/prefix| will be called from |record/begin|, which is executed at
% the beginning of the document, so it shouldn't be mentioned here.
begindocument/.append code=\mmz@maybe@mkmemodir,
}
-% Define the paths to the memo directory and the prefix.
-\def\mmz@dir@path{\ifmmz@relativepath.\fi/\mmz@dir}
-\def\mmz@prefix@path{\mmz@dir@path/\mmz@prefix}
% \end{key}
%
% \begin{key}{mkdir, mkdir command}
% Should we create the memo/extern directory if it doesn't exist? And which
-% command should we use to create it? Of course, shell escape must be
-% properly configured for this to work (\MS\ref{sec:shell-escape}).
-% ^^A todo: What about quoting the paths containing spaces on Windows? Will this work?
+% command should we use to create it? There is no initial value for the
+% latter, because |mkdir| cannot be executed out of the box, but note that
+% |extract=perl| and |extract=python| will set the extraction script with
+% option |--mkdir| as the value of |mkdir command|.
\mmzset{
mkdir/.is if=mmz@mkdir,
- mkdir command/.code={\def\mmz@mkdir@command##1{#1}},
- mkdir command={mkdir "#1"},
+ mkdir command/.store in=\mmz@mkdir@command,
+ mkdir command={},
}
% The underlying conditional \cs{ifmmz@mkdir} is only ever used in
% |\mmz@maybe@mkmemodir| below, which is itself only executed at the end of
-% |path| and in |begindocument|.
+% |prefix| and in |begindocument|.
\newif\ifmmz@mkdir
+\mmz@mkdirtrue
+% We only attempt to create the memo directory if |\ifmmz@mkdir| is in effect
+% and if both |\mmz@mkdir@command| and |\mmz@prefix@dir| are specified (i.e.\
+% non-empty).
\def\mmz@maybe@mkmemodir{%
\ifmmz@mkdir
- \pdf@system{\mmz@mkdir@command{\mmzOutputDirectory\mmz@dir@path}}%
+ \ifdefempty\mmz@mkdir@command{}{%
+ \ifdefempty\mmz@prefix@dir{}{%
+ \mmz@remove@quotes{\mmz@prefix@dir}\mmz@temp
+ \pdf@system{\mmz@mkdir@command\space"\mmz@temp"}%
+ }%
+ }%
\fi
}
% \end{key}
@@ -533,51 +557,49 @@
% filenames themselves have no prefix. Furthermore, |memo dir| triggers the
% creation of the directory.
\mmzset{%
- memo dir/.style={
- mkdir,
- path={
- relative,
- dir={#1.memo.dir},
- prefix={},
- },
- },
- memo dir/.default=\mmzUnquote\jobname,
- no memo dir/.style={
- mkdir=false,
- path={
- relative,
- dir={},
- prefix={#1.},
- },
- },
- no memo dir/.default=\mmzUnquote\jobname,
+ memo dir/.style={prefix={#1.memo.dir/}},
+ memo dir/.default=\jobname,
+ no memo dir/.style={prefix={#1.}},
+ no memo dir/.default=\jobname,
no memo dir,
}
% \end{key}
%
-% \begin{macro}{\mmzUnquote}
-% If the expanded argument is surrounded by double quotes, remove them. This
-% relies on |#1| containing no quotes other than the potential surrounding
-% quotes, which should be the case when applying the macro to |\jobname|. An
-% empty |#1| is dealt with correctly, even if |\jobname| can hardly ever be
-% empty (needs |openout_any=a|).
+% \begin{macro}{\mmz@remove@quotes}
+% This macro removes fully expands |#1|, detokenizes the expansion and then
+% removes all double quotes the string. The result is stored in the control
+% sequence given in |#2|.
%
% We use this macro when we are passing a filename constructed from
% |\jobname| to external programs.
-\def\mmzUnquote#1{\expanded{\noexpand\mmz@unquote#1}\mmz@unquote@end}
-\def\mmz@unquote#1{%
- \ifx\mmz@unquote@end#1%
+\def\mmz@remove@quotes#1#2{%
+ \def\mmz@remove@quotes@end{\let#2\mmz@temp}%
+ \def\mmz@temp{}%
+ \expanded{%
+ \noexpand\mmz@remove@quotes@i
+ \detokenize\expandafter{\expanded{#1}}%
+ "\noexpand\mmz@eov
+ }%
+}
+\def\mmz@remove@quotes@i{%
+ \CollectArgumentsRaw
+ {\collargsBraceCollectedfalse
+ \collargsNoDelimiterstrue
+ \collargsAppendPostwrap{{##1}}%
+ }%
+ {u"u\mmz@eov}%
+ \mmz@remove@quotes@ii
+}
+\def\mmz@remove@quotes@ii#1#2{%
+ \appto\mmz@temp{#1}%
+ \ifx&#2&%
+ \mmz@remove@quotes@end
+ \expandafter\@gobble
\else
- \ifx"#1%
- \expandafter\expandafter\expandafter\mmz@unquote@quotes
- \else
- \expandafter\expandafter\expandafter\mmz@unquote@noquotes
- \expandafter\expandafter\expandafter#1%
- \fi
+ \expandafter\@firstofone
\fi
+ {\mmz@remove@quotes@i#2\mmz@eov}%
}
-\def\mmz@unquote@quotes#1"\mmz@unquote@end{#1}
-\def\mmz@unquote@noquotes#1\mmz@unquote@end{#1}
% \end{macro}
%
% \begin{key}{ignore spaces}
@@ -832,6 +854,7 @@
%<context>\let\startmemoize\startnomemoize
%<context>\let\stopmemoize\stopnomemoize
%</nommz>
+%</mmz,nommz>
% \end{environment}
%
% \subsection{The memoization process}
@@ -843,6 +866,7 @@
% anywhere else. It is checked by |\Memoize| to prevent nested
% memoizations, deployed in advice run conditions set by |run only if
% memoizing|, etc.
+%<*mmz,nommz,mmzable&generic>
\newif\ifmemoizing
% \end{macro}
%
@@ -859,7 +883,7 @@
% An auxiliary macro which rescans the given
% code using |\scantokens| if the verbatim mode is active. We also need it
% in NoMemoize, to properly grab verbatim manually memoized code.
-%</mmz,nommz>
+%</mmz,nommz,mmzable&generic>
%<*mmz>
\def\mmz@maybe@scantokens{%
\ifmmz@verbatim
@@ -1091,15 +1115,16 @@
\gtoksapp\mmzCMemo{\global\mmzUnmemoizabletrue}%
\mmz@write@cmemo
\mmz@trace@endmemoize@unmemoizable
- \PackageWarning{memoize}{Marking this code as unmemoizable}%
+ \PackageInfo{memoize}{Marking this code as unmemoizable}%
\else
\ifmmz@abort
% If memoization was aborted, we create an empty c-memo, to make sure that
% no leftover c-memo tricks Memoize into thinking that the code was
% successfully memoized.
\mmz@trace@endmemoize@aborted
- \PackageWarning{memoize}{Memoization was aborted}%
- \mmz@write@empty@cmemo
+ \PackageInfo{memoize}{Memoization was aborted}%
+ \mmz@compute@context@mdfivesum
+ \mmz@write@cmemo
\else
% If memoization was not aborted, we compute the \meta{context md5sum},
% open and write out the memos, and shipout the externs (as pages into the
@@ -1320,7 +1345,7 @@
%
% \paragraph.{Tracing} We populate the hooks which send the tracing info to the
% terminal.
-\def\mmz@trace#1{\immediate\write16{[tracing memoize] #1}}
+\def\mmz@trace#1{\advice@typeout{[tracing memoize] #1}}
\def\mmz@trace@context{\mmz@trace{\space\space
Context: "\expandonce{\mmz@context@key}" --> \mmz@context@mdfivesum}}
\def\mmz@trace@Memoize@on{%
@@ -1424,9 +1449,6 @@
% \end{macro}
% \end{key}
%
-%</mmz>
-%<nommz,mmzable>\newcommand\IfMemoizing[2][]{\@secondoftwo}
-%<*mmz>
%
% \subsection{Context}
% \label{sec:code:context}
@@ -1465,24 +1487,27 @@
% context, key value to context, /handlers/.meaning to context,
% /handlers/.value to context}
% Utilities to put the meaning of various stuff into |context|.
+ % \indentmacrocode
meaning to context/.code={\forcsvlist\mmz@mtoc{#1}},
- csname meaning to context/.code={\mmz@mtoc@cs{#1}},
- key meaning to context/.code={\forcsvlist\mmz@mtoc\mmz@mtoc@keycmd{#1}},
+ csname meaning to context/.code={\mmz@mtoc@csname{#1}},
+ key meaning to context/.code={%
+ \forcsvlist\mmz@mtoc\mmz@mtoc@keycmd{#1}},
key value to context/.code={\forcsvlist\mmz@mtoc@key{#1}},
- /handlers/.meaning to context/.code={%
- \expanded{\noexpand\mmz@mtoc@cs{pgfk@\pgfkeyscurrentpath/.@cmd}}},
+ /handlers/.meaning to context/.code={\expanded{%
+ \noexpand\mmz@mtoc@csname{pgfk@\pgfkeyscurrentpath/.@cmd}}},
/handlers/.value to context/.code={%
- \expanded{\noexpand\mmz@mtoc@cs{pgfk@\pgfkeyscurrentpath}}},
+ \expanded{\noexpand\mmz@mtoc@csname{pgfk@\pgfkeyscurrentpath}}},
}
+% \noindentmacrocode
\def\mmz@mtoc#1{%
\collargs@cs@cases{#1}%
{\mmz@mtoc@cmd{#1}}%
{\mmz@mtoc@error@notcsorenv{#1}}%
{%
- \mmz@mtoc@cs{%
+ \mmz@mtoc@csname{%
%<context>start%
#1}%
- \mmz@mtoc@cs{%
+ \mmz@mtoc@csname{%
%<latex,plain>end%
%<context>stop%
#1}%
@@ -1492,17 +1517,18 @@
\begingroup
\escapechar=-1
\expandafter\endgroup
- \expandafter\mmz@mtoc@cs\expandafter{\string#1}%
+ \expandafter\mmz@mtoc@csname\expandafter{\string#1}%
}
-\def\mmz@mtoc@cs#1{%
+\def\mmz@mtoc@csname#1{%
\pgfkeysvalueof{/mmz/context/.@cmd}%
- \expandafter\string\csname#1\endcsname={\expandafter\meaning\csname#1\endcsname}%
- \pgfeov
+ \detokenize{#1}={\expandafter\meaning\csname#1\endcsname}%
+ \pgfeov
}
-\def\mmz@mtoc@key#1{\mmz@mtoc@cs{pgfk@#1}}
-\def\mmz@mtoc@key#1{\mmz@mtoc@cs{pgfk@#1/.@cmd}}
+\def\mmz@mtoc@key#1{\mmz@mtoc@csname{pgfk@#1}}
+\def\mmz@mtoc@keycmd#1{\mmz@mtoc@csname{pgfk@#1/.@cmd}}
\def\mmz@mtoc@error@notcsorenv#1{%
- \PackageError{memoize}{'\detokenize{#1}' passed to key 'meaning to context' is neither a command nor an environment}{}%
+ \PackageError{memoize}{'\detokenize{#1}' passed to key 'meaning to context'
+ is neither a command nor an environment}{}%
}
% \end{key}
%
@@ -1512,7 +1538,7 @@
%
% \paragraph{The path} to a c-memo consists of the path prefix, the MD5 sum of
% the memoized code, and suffix |.memo|.
-\def\mmz@cmemo@path{\mmz@prefix@path\mmz@code@mdfivesum.memo}
+\def\mmz@cmemo@path{\mmz@prefix\mmz@code@mdfivesum.memo}
% \begin{macro}{\mmzCMemo}
% The additional, free-form content of the c-memo is collected in this token
% register.
@@ -1536,7 +1562,7 @@
% |\mmzCMemo|.
\def\mmz@write@cmemo{%
% Open the file for writing.
- \immediate\openout\mmz@out{\mmz@cmemo@path}%
+ \immediate\mmz@openout\mmz@out{\mmz@cmemo@path}%
% The memo starts with the |\mmzMemo| marker (a signal that the memo is valid).
\immediate\write\mmz@out{\noexpand\mmzMemo}%
% We store the content of |\mmzContextExtra| by writing out a command that
@@ -1559,17 +1585,6 @@
}
% \end{macro}
%
-% \begin{macro}{\mmz@write@empty@cmemo}
-% This macro is used to create an empty c-memo on aborted memoization, to
-% make sure that no leftover c-memo tricks Memoize into thinking that the
-% code was successfully memoized.
-\def\mmz@write@empty@cmemo{%
- \immediate\openout\mmz@out{\mmz@cmemo@path}%
- \immediate\closeout\mmz@out
-}
-% \end{macro}
-%
-%
% \begin{macro}{\mmzSource}
% The c-memo memoized code marker. This macro is synonymous with |\endinput|,
% so the source following it is ignored when inputting the c-memo.
@@ -1644,7 +1659,7 @@
% hyphen-separated MD5 sums of the memoized code and the (evaluated) context,
% and suffix |.memo|.
\def\mmz@ccmemo@path{%
- \mmz@prefix@path\mmz@code@mdfivesum-\mmz@context@mdfivesum.memo}
+ \mmz@prefix\mmz@code@mdfivesum-\mmz@context@mdfivesum.memo}
%
% \paragraph{The structure} of a cc-memo:
% \begin{itemize}
@@ -1699,7 +1714,7 @@
% memoized code can update the context. This is one of the two reasons why
% we couldn't write the cc-memo directly into the file, but had to collect
% its contents into token register |\mmzCCMemo|.
- \immediate\openout\mmz@out{\mmz@ccmemo@path}%
+ \immediate\mmz@openout\mmz@out{\mmz@ccmemo@path}%
% Token register |\mmz@ccmemo@resources| consists of calls to
% |\mmz@ccmemo@append@resource|, so the following code writes down the list
% of created externs into the cc-memo. Wanting to have this list at the top
@@ -1751,7 +1766,7 @@
% might be produced by a failed \hologo{TeX}-based extraction, should count
% as no file. The |0| behind |\ifnum| is there because |\pdffilesize|
% returns an empty string when the file does not exist.
- \ifnum0\pdf@filesize{\mmz@dir@path/#1}=0
+ \ifnum0\pdf@filesize{\mmz@prefix@dir/#1}=0
\ifmmz@direct@ccmemo@input
\let\mmzMemo\endinput
\else
@@ -1768,8 +1783,10 @@
%
% \begin{macro}{\mmz@process@ccmemo,\mmzThisContext,\mmzEndMemo}
% This macro processes the cc-memo.
+% \indentmacrocode
\def\mmz@process@ccmemo{%
\mmz@trace@process@ccmemo
+ % \noindentmacrocode
% The following conditional signals whether cc-memo was successfully
% utilized. If the cc-memo file does not exist, |\ifmmz@abort| will remain
% true. If it exists, it is headed by the list of resources. If a
@@ -1865,11 +1882,11 @@
% full path.
\newcount\mmz@seq
\def\mmz@extern@basename{%
- \mmz@prefix\mmz@code@mdfivesum-\mmz@context@mdfivesum
+ \mmz@prefix@name\mmz@code@mdfivesum-\mmz@context@mdfivesum
\ifnum\mmz@seq>0 -\the\mmz@seq\fi
}
\def\mmz@extern@name{\mmz@extern@basename.pdf}
-\def\mmz@extern@basepath{\mmz@dir@path/\mmz@extern@basename}
+\def\mmz@extern@basepath{\mmz@prefix@dir\mmz@extern@basename}
\def\mmz@extern@path{\mmz@extern@basepath.pdf}
%
% \begin{key}{padding left, padding right, padding top, padding bottom}
@@ -2124,12 +2141,14 @@
% |\mmzExternPages| holds the number of shipped-out extern pages; and
% |\mmzExtraPages| holds, or at least should hold, the number of pages
% shipped out using any other means.
- \edef\pagenumber{\the\numexpr\mmzRegularPages
+ \edef\pagenumber{%
+ \the\numexpr\mmzRegularPages
% In \hologo{LaTeX}, the |\mmzRegularPages| holds to number of pages
% already shipped out. In \hologo{ConTeXt}, the counter is already
% increased while processing the page, so we need to subtract $1$.
%<context> -1%
- +\mmzExternPages+\mmzExtraPages}%
+ +\mmzExternPages+\mmzExtraPages
+ }%
% Record the creation of the new extern. We do this after shipping out the
% extern page, so that the recording mechanism can serve as an after-shipout
% hook, for the unlikely situation that some package really needs to do
@@ -2212,7 +2231,7 @@
enddocument/afterlastpage/.append code={%
\ifnum\mmzExternPages>0
\PackageWarning{memoize}{The compilation produced \the\mmzExternPages\space
- new extern\ifnum\mmzExternPages>1 s\fi.}%
+ new extern\ifnum\mmzExternPages>1 s\fi}%
\fi
},
}
@@ -2304,13 +2323,13 @@
%
% \begin{macro}{\mmz@include@extern@from@tbe@box}
% Include the extern number |#1| residing in |\mmz@tbe@box| into the
-% document. This helper macro makes it possible for a complex memoization
-% driver to process the cc-memo right after memoization --- by using the
-% |\mmzkeepexternstrue\xtoksapp\mmzAfterMemoizationExtra{\the\mmzCCMemo}|
-% trick --- to ensure that the result of the memoizing compilation matches
-% the result of inputting the cc-memo. The rest of the arguments are
-% gobbled, as we don't have to do any size adjustment or checking here, and
-% the box is of the correct type.
+% document. It may be called as |\mmzIncludeExtern| from |after memoization|
+% hook if |\ifmmzkeepexterns| was set to true during memoization. The macro
+% takes the same arguments as |\mmzIncludeExtern| but disregards all but
+% the first one, the extern sequential number. Using this macro, a complex
+% memoization driver can process the cc-memo right after memoization, by
+% issuing
+% |\global\mmzkeepexternstrue\xtoksapp\mmzAfterMemoizationExtra{\the\mmzCCMemo}|.
\def\mmz@include@extern@from@tbe@box#1#2#3#4#5#6#7#8#9{%
\setbox0\vbox{%
\@tempcnta#1\relax
@@ -2344,7 +2363,7 @@
\mmzset{
extract/.estore in=\mmz@extraction@method,
extract/.value required,
- begindocument/.append style={extract/.code=\mmz@preamble@only@warning},
+ begindocument/.append style={extract/.code=\mmz@preamble@only@error},
% \end{key}
%
% \begin{key}{extract/perl, extract/python}
@@ -2352,10 +2371,10 @@
% Memoize ships with two extraction scripts, a Perl script and a Python
% script, which are selected by |extract=perl| (the default) and
% |extract=python|, respectively. We run the scripts in verbose mode
- % (without |-q|), and keep the |.mmz| file as is, i.e.\ we're not commenting
- % out the |\mmzNewExtern| lines, because we're about to overwrite it
- % anyway. We also request the log file, which will contain
- % |\mmzExtractionSuccessful| if extraction was successful.
+ % (without |-q|), and keep the |.mmz| file as is (without |-k|), i.e.\
+ % we're not commenting out the |\mmzNewExtern| lines, because we're about
+ % to overwrite it anyway. We inform the script about the format of the
+ % document (|-F|).
extract/perl/.code={%
\mmz@clear@extraction@log
\pdf@system{%
@@ -2363,17 +2382,14 @@
\mmzvalueof{perl extraction options}%
}%
\mmz@check@extraction@log{perl}%
- \def\mmz@mkdir@command##1{\mmzvalueof{perl extraction command}\space --mkdir "##1"}%
+ \def\mmz@mkdir@command{\mmzvalueof{perl extraction command} --mkdir}%
},
perl extraction command/.initial=memoize-extract.pl,
- perl extraction options/.initial={%
-% \begin{listingregion}{perl-extraction-options.tex}
- -e -l "\mmzOutputDirectory\mmzUnquote\jobname.mmz.log" -w
- %<latex>"\string\PackageWarning{memoize (perl-based extraction)}{\string\warningtext}"
- %<plain>"\string\warning{memoize (perl-based extraction): \string\warningtext}"
- %<context>"\string\warning{memoize (perl-based extraction): \string\warningtext}"
- "\mmzOutputDirectory\mmzUnquote\jobname.mmz"
-% \end{listingregion}
+ perl extraction options/.initial={\space
+ %<latex>-F latex
+ %<plain>-F plain
+ %<context>-F context
+ \jobname\space
},
extract=perl,
extract/python/.code={%
@@ -2383,21 +2399,20 @@
\mmzvalueof{python extraction options}%
}%
\mmz@check@extraction@log{python}%
- \def\mmz@mkdir@command##1{\mmzvalueof{python extraction command}\space --mkdir "##1"}%
+ \def\mmz@mkdir@command{\mmzvalueof{python extraction command} --mkdir}%
},
python extraction command/.initial=memoize-extract.py,
- python extraction options/.initial={%
- -e -l "\mmzOutputDirectory\mmzUnquote\jobname.mmz.log" -w
- %<latex>"\string\PackageWarning{memoize (python-based extraction)}{\string\warningtext}"
- %<plain>"\string\warning{memoize (python-based extraction): \string\warningtext}"
- %<context>"\string\warning{memoize (python-based extraction): \string\warningtext}"
- "\mmzOutputDirectory\mmzUnquote\jobname.mmz"
+ python extraction options/.initial={\space
+ %<latex>-F latex
+ %<plain>-F plain
+ %<context>-F context
+ \jobname\space
},
}
-\def\mmz@preamble@only@warning{%
- \PackageWarning{memoize}{%
+\def\mmz@preamble@only@error{%
+ \PackageError{memoize}{%
Ignoring the invocation of "\pgfkeyscurrentkey".
- This key may only be executed in the preamble}%
+ This key may only be executed in the preamble}{}%
}
% \end{key}
%
@@ -2406,15 +2421,14 @@
% communicate with the system command via the ``extraction log file,'' produced
% by both \hologo{TeX}-based extraction and the Perl and Python extraction
% script. This file signals whether the embedded extraction was successful ---
-% if it is, the file contains |\mmzExtractionSuccessful| --- and also contains
-% any size-mismatch warnings (these are currently only thrown by the
-% \hologo{TeX}-based extraction). As the log is really a \hologo{TeX} file,
-% the idea is to simply input it after extracting each extern (for
+% if it is, the file ends if |\endinput| --- and also contains any warnings and
+% errors thrown by the script. As the log is really a \hologo{TeX} file, the
+% idea is to simply input it after extracting each extern (for
% \hologo{TeX}-based extraction) or after the extraction of all externs (for
% the external scripts).
\def\mmz@clear@extraction@log{%
\begingroup
- \immediate\openout0{\mmzUnquote\jobname.mmz.log"}%
+ \immediate\mmz@openout0{\jobname.mmz.log}%
\immediate\closeout0
\endgroup
}
@@ -2426,9 +2440,11 @@
\@input{\jobname.mmz.log}%
\ifmmz@temp \else \mmz@extraction@error \fi \endgroup }
\def\mmz@extraction@error{%
- \PackageWarning{memoize}{Extraction of externs from document "\mmzUnquote\jobname.pdf"
- using method "\extractionmethod" was unsuccessful. Have you set the
- shell escape mode as suggested in chapter 1 of the manual?}{}}
+ \PackageError{memoize}{Extraction of externs from document
+ "\jobname.pdf" using method "\extractionmethod" was
+ unsuccessful}{The extraction script "\mmzvalueof{\extractionmethod\space
+ extraction command}" wasn't executed or didn't finish execution
+ properly.}}
%
% \subsection{The record files}
% \label{sec:code:record}
@@ -2451,7 +2467,7 @@
% surely occurs at the top of the |.mmz| file. Listing each prefix type
% separately in this hook ensures that |prefix| of a certain type is
% executed after that type's |begin|.
- /mmz/record/#1/prefix/.try/.expanded=\mmz@prefix@path,
+ /mmz/record/#1/prefix/.try/.expanded=\mmz@prefix,
},
record/prefix/.append style={/mmz/record/#1/prefix/.try={##1}},
record/new extern/.append style={/mmz/record/#1/new extern/.try={##1}},
@@ -2495,7 +2511,7 @@
% We define this macro because |\aftergroup|, used in |record/prefix|, only
% accepts a token.
\def\mmz@record@prefix{%
- \mmzset{/mmz/record/prefix/.expanded=\mmz@prefix@path}%
+ \mmzset{/mmz/record/prefix/.expanded=\mmz@prefix}%
}
% \paragraph{Initialize} the hook keys, preactivate |mmz| record type, and
% execute hooks |begin| and |end| at the edges of the document.
@@ -2516,12 +2532,11 @@
% contains are intentionally as simple as possible (just a macro plus braced
% arguments), to facilitate parsing by the external scripts.
%
-% These hooks simply put the calls of the corresponding macros into the file.
-% All but hooks but |begin| and |end| receive the full path to the relevant
-% file as the only argument (ok, |prefix| receives the full path prefix, as set
-% by key |path|).
-%
% \begin{key}{record/mmz/...}
+% These hooks simply put the calls of the corresponding macros into the file.
+% All but hooks but |begin| and |end| receive the full path to the relevant
+% file as the only argument (ok, |prefix| receives the full path prefix, as
+% set by key |path|).
\mmzset{
record/mmz/begin/.code={%
\newwrite\mmz@mmzout
@@ -2529,7 +2544,7 @@
% location (the current directory, i.e.\ the directory where \hologo{TeX}
% is executed from; usually, this will be the directory containing the
% \hologo{TeX} source).
- \immediate\openout\mmz@mmzout{\jobname.mmz}%
+ \immediate\mmz@openout\mmz@mmzout{\jobname.mmz}%
},
% The |\mmzPrefix| is used by the clean-up script, which will remove all
% files with the given path prefix but (unless called with |--all|) those
@@ -2592,7 +2607,7 @@
% \begin{key}{record/sh/...} Define the Linux shell script record type.
record/sh/begin/.code={%
\newwrite\mmz@shout
- \immediate\openout\mmz@shout{\mmz@shname}%
+ \immediate\mmz@openout\mmz@shout{\mmz@shname}%
},
record/sh/new extern/.code={%
\begingroup
@@ -2611,7 +2626,7 @@
% Rinse and repeat for Windows.
record/bat/begin/.code={%
\newwrite\mmz@batout
- \immediate\openout\mmz@batout{\mmz@batname}%
+ \immediate\mmz@openout\mmz@batout{\mmz@batname}%
},
record/bat/new extern/.code={%
\begingroup
@@ -2649,7 +2664,7 @@
% variables |.DEFAULT_GOAL| and |.PHONY|.
\newwrite\mmz@makefileout
\newtoks\mmz@makefile@externs
- \immediate\openout\mmz@makefileout{\mmz@makefilename}%
+ \immediate\mmz@openout\mmz@makefileout{\mmz@makefilename}%
\immediate\write\mmz@makefileout{.DEFAULT_GOAL = externs}%
\immediate\write\mmz@makefileout{.PHONY: externs}%
},
@@ -2693,11 +2708,10 @@
}
% \end{key}
%
-% \begingroup
-% \setlength\MacroIndent{2em}
% \begin{macro}{\mmzUsedCMemo,\mmzUsedCCMemo,\mmzUsedExtern,\mmzNewCMemo,\mmzNewCCMemo,\mmzPrefix}
% We can ignore everything but |\mmzNewExtern|s. All these macros receive a
% single argument.
+% \indentmacrocode
\def\mmzUsedCMemo#1{}
\def\mmzUsedCCMemo#1{}
\def\mmzUsedExtern#1{}
@@ -2705,7 +2719,6 @@
\def\mmzNewCCMemo#1{}
\def\mmzPrefix#1{}
% \end{macro}
-% \endgroup
%
% \begin{macro}{\mmzNewExtern}
% Command |\mmzNewExtern| takes four arguments. It instructs us to extract
@@ -2744,11 +2757,11 @@
% \end{macro}
\def\mmz@pageextraction@error{%
\PackageError{memoize}{Extraction of extern page \pagenumber\space from
- document "\mmzUnquote\jobname.pdf" using method "\extractionmethod" was unsuccessful.
- Have you set the shell escape mode as suggested in chapter 1 of the
- manual?}{If "\mmzvalueof{tex extraction command}" was executed,
- shell escape mode is not the problem, and inspecting "\externbasepath.log"
- might give you a clue what's wrong}}
+ document "jobname.pdf" using method "\extractionmethod" was
+ unsuccessful.}{Check the log file to see if the extraction script was
+ executed at all, and if it finished successfully. You might also want to
+ inspect "\externbasepath.log", the log file of the embedded TeX compilation
+ which ran the extraction script}}
%
% \begin{key}{tex extraction command, tex extraction options, tex extraction script}
% Using these keys, we set the system call
@@ -2786,12 +2799,11 @@
-halt-on-error
-interaction=batchmode
-jobname "\externbasepath"
- \ifdefempty\mmzOutputDirectory{}{-output-directory "\mmzOutputDirectory"}
% \end{listingregion}
},
tex extraction script/.initial={%
% \begin{listingregion}{tex-extraction-script.tex} ^^A todo: context
- \def\noexpand\fromdocument{"\mmzOutputDirectory"\jobname.pdf}%
+ \def\noexpand\fromdocument{\jobname.pdf}%
\def\noexpand\pagenumber{\pagenumber}%
\def\noexpand\expectedwidth{\expectedwidth}%
\def\noexpand\expectedheight{\expectedheight}%
@@ -3008,6 +3020,7 @@
%<plain,context>noop/.style={inner handler=\mmz@auto@noop},
nomemoize/.style={noop, options=disable},
replicate/.style={run if memoizing, inner handler=\mmz@auto@replicate},
+ to context/.style={run if memoizing, outer handler=\mmz@auto@tocontext},
}
%
% \paragraph{Abortion}
@@ -3221,6 +3234,27 @@
}
% \end{macro}
% \end{mmzautokey}
+%
+% \begin{mmzautokey}{to context}
+% \begin{macro}{\mmz@auto@tocontext}
+% This outer handler appends the original definition of the handled command
+% to the context. The |\expandafter| are there to expand |\AdviceName|
+% once before fully expanding |\AdviceGetOriginalCsname|.
+\def\mmz@auto@tocontext{%
+ \expanded{%
+ \noexpand\pgfkeysvalueof{/mmz/context/.@cmd}%
+ original "\AdviceNamespace" csname "\AdviceCsname"={%
+ \noexpand\expanded{%
+ \noexpand\noexpand\noexpand\meaning
+ \noexpand\AdviceCsnameGetOriginal{\AdviceNamespace}{\AdviceCsname}%
+ }%
+ }%
+ }%
+ \pgfeov
+ \AdviceOriginal
+}
+% \end{macro}
+% \end{mmzautokey}
%
% \subsection{\hologo{LaTeX}-specific handlers}
%
@@ -3277,12 +3311,7 @@
% appends the reference key to the context. |\mmzNoRef| only does that if
% the reference is defined, otherwise it aborts the memoization.
\def\mmzForceNoRef#1{%
- \ifmemoizing
- \expandafter\gtoksapp\expandafter\mmzContextExtra
- \else
- \expandafter\toksapp\expandafter\mmzContext
- \fi
- {r@#1={\csname r@#1\endcsname}}%
+ \mmz@mtoc@csname{r@#1}%
\ignorespaces
}
\def\mmzNoRef#1{%
@@ -3360,11 +3389,17 @@
\endgroup
}
%</latex>
-%</mmz>
%
% \end{macro}
%
% \section{Support for various classes and packages}
+%
+%<*latex>
+\AddToHook{shipout/before}[memoize]{\global\advance\mmzExtraPages-1\relax}
+\AddToHook{shipout/after}[memoize]{\global\advance\mmzExtraPages1\relax}
+\mmzset{auto=\DiscardShipoutBox{
+ outer handler=\global\advance\mmzExtraPages1\relax\AdviceOriginal}}
+%</latex>
%
% \subsection{\TikZ;}
% \label{sec:code:mmz:tikz}
@@ -3373,7 +3408,6 @@
% Advice). All the action happens at the end of the preamble, so that we can
% detect whether \TikZ; was loaded (regardless of whether Memoize was loaded
% before \TikZ;, or vice versa), but still input the definitions.
-%<*mmz>
\mmzset{
begindocument/before/.append code={%
%<latex>\@ifpackageloaded{tikz}{%
@@ -3384,32 +3418,23 @@
% We define and activate the automemoization handlers for the \TikZ; command
% and environment.
\mmzset{%
- % \begin{listingregion}{_auto-tikz-collector.tex}
- auto=\tikz{memoize, collector=\AdviceCollectTikZArguments},
- % \end{listingregion}
- %/utils/exec={\tracingall},
- auto={tikzpicture}{memoize},
- % A hack to prevent memoizing pictures which are accidentally marked as
- % remembered --- accidentally in the sense that because the document
- % changed, the |.aux| file contains a |\pgfsyspdfmark| command which
- % erroneously refers to the picture being memoized. We know that
- % memoizing a remembered picture can't be right, as we always abort on
- % |\pdfsavepos|. This is implemented by hacking into PGF's
- % |\pgfsys@getposition|, and aborting memoization if the mark does not
- % equal |\relax|. (We have to duplicate |#| because of |.append code|.)
- auto=\pgfsys@getposition{
- run if memoizing, outer handler=\mmz@pgfsys@getposition},
- }%
- \def\mmz@pgfsys@getposition##1{%
- \expandafter\ifx\csname pgf@sys@pdf@mark@pos@##1\endcsname\relax
- \else
- \mmzAbort
- \fi
- \AdviceOriginal{##1}%
+ auto/memoize tikz/.style={
+ memoize,
+ at begin memoization=\edef\mmz@pgfpictureid{%
+ \the\pgf@picture@serial@count
+ },
+ at end memoization=\xtoksapp\mmzCCMemo{%
+ \unexpanded{%
+ \global\expandafter\advance\csname pgf@picture@serial@count\endcsname
+ }%
+ \the\numexpr\pgf@picture@serial@count-\mmz@pgfpictureid\relax\relax
+ },
+ },
+ auto=\tikz{memoize tikz, collector=\AdviceCollectTikZArguments},
+ auto={tikzpicture}{memoize tikz},
}%
},
}
-%</mmz>
%
%
% \subsection{Forest}
@@ -3417,7 +3442,6 @@
%
% Forest will soon feature extensive memoization support, but for now, let's
% just enable the basic, single extern externalization.
-%<*mmz>
%<*latex>
\mmzset{
begindocument/before/.append code={%
@@ -3478,6 +3502,98 @@
}{}}
%</latex>
%
+% \subsection{Morewrites}
+% \label{sec:code:morewrites}
+%
+% Use the old grammar for |\openin| and |\openout| as a temporary workaround.
+% |prefix|es containing spaces must be quoted manually.
+%<*latex>
+\AddToHook{begindocument/before}{%
+ \@ifpackageloaded{morewrites}{%
+ \def\mmz@openin#1#2{\openin#1=#2\relax}%
+ \def\mmz@openout#1#2{\openout#1=#2\relax}%
+ }{}%
+}
+%</latex>
+%
+% \subsection{Biblatex}
+% \label{sec:biblatex}
+%
+%<*latex>
+\mmzset{
+ begindocument/before/.append style={%
+ auto=\blx@bbl@entry{outer handler=\mmz@biblatex@entry},
+ auto/cite/.style={run if memoizing, outer handler=\mmz@biblatex@cite},
+ auto/cites/.style={run if memoizing, outer handler=\mmz@biblatex@cites},
+ auto=\cite{cite},
+ auto=\cites{cites},
+ }%
+}
+% \begin{macro}{\mmz@biblatex@entry}
+% This macro stores the MD5 sum of the |\entry| when reading the |.bbl| file.
+\def\mmz@biblatex@entry#1#2\endentry{%
+ \csxdef{mmz@bbl@#1}{\pdf@mdfivesum{#2}}%
+ \AdviceOriginal{#1}#2\endentry
+}
+% \end{macro}
+% \begin{macro}{\mmz@biblatex@cite}
+% This macro puts the cites reference keys into the context, and adds the
+% handled |\cite| command to the cc-memo.
+\def\mmz@biblatex@cite#1#{\mmz@biblatex@cite@i{#1}}
+\def\mmz@biblatex@cite@i#1#2{%
+ \forcsvlist\mmz@biblatex@cite@do@key{#2}%
+ \xtoksapp\mmzCCMemo{%
+ \noexpand\setbox0\noexpand\hbox{%
+ \expandonce\AdviceOriginal\unexpanded{#1}{#2}%
+ }}%
+ \AdviceOriginal#1{#2}%
+}
+\def\mmz@biblatex@cite@do@key#1{%
+ \mmz@mtoc@csname{mmz@bbl@#1}%
+ \ifcsdef{mmz@bbl@#1}{}{\mmzAbort}%
+}
+% \end{macro}
+% \begin{macro}{\mmz@biblatex@cites}
+% This macro puts the cites reference keys into the context, and adds the
+% handled |\cites| command to the cc-memo.
+\def\mmz@biblatex@cites{%
+ \mmz@temptoks{}%
+ \mmz@biblatex@cites@i
+}
+\def\mmz@biblatex@cites@i{%
+ \futurelet\mmz@temp\mmz@biblatex@cites@ii
+}
+\def\mmz@biblatex@cites@ii{%
+ \mmz@tempfalse
+ \ifx\mmz@temp\bgroup
+ \mmz@temptrue
+ \else
+ \ifx\mmz@temp[%]
+ \mmz@temptrue
+ \fi
+ \fi
+ \ifmmz@temp
+ \expandafter\mmz@biblatex@cites@iii
+ \else
+ \expandafter\mmz@biblatex@cites@z
+ \fi
+}
+\def\mmz@biblatex@cites@iii#1#{\mmz@biblatex@cites@iv{#1}}
+\def\mmz@biblatex@cites@iv#1#2{%
+ \forcsvlist\mmz@biblatex@cite@do@key{#2}%
+ \toksapp\mmz@temptoks{#1{#2}}%
+ \mmz@biblatex@cites@i
+}
+\def\mmz@biblatex@cites@z{%
+ \xtoksapp\mmzCCMemo{%
+ \noexpand\setbox0\noexpand\hbox{%
+ \expandonce\AdviceOriginal\the\mmz@temptoks
+ }}%
+ \expandafter\AdviceOriginal\the\mmz@temptoks
+}
+%</latex>
+% \end{macro}
+%
%
% \section{Initialization}
% \label{sec:code:initialization}
@@ -3512,6 +3628,10 @@
%</plain,context>
% \end{key}
%
+% Formats other than \hologo{plainTeX} need a way to prevent extraction during
+% package-loading.
+%<!plain>\mmzset{extract/no/.code={}}
+%
% \paragraph{\texttt{memoize.cfg}} Load the configuration file. Note that
% |nomemoize| must input this file as well, because any special
% memoization-related macros defined by the user should be available; for
@@ -3521,63 +3641,91 @@
%<mmz,nommz>\InputIfFileExists{memoize.cfg}{}{}
%<*mmz>
%
-% Formats other than \hologo{plainTeX} need a way to prevent extraction during
-% package-loading.
-\mmzset{
- %<!plain>extract/no/.code={},
-% \begin{key}{output directory}
-% \begin{macro}{mmzOutputDirectory}
-% Set the |-output-directory| --- manually, as there is no other way.
-% \indentmacrocode
- output-directory/.store in=\mmzOutputDirectory,
-}
-% \end{macro}
-% \end{key}
+% For formats other than \hologo{plainTeX}, we also save the current (initial
+% or |memoize.cfg|-set) value of |extract|, so that we can restore it when
+% package options include |extract=no|. Then, |extract| can be called without
+% an argument in the preamble, triggering extraction using this method; this is
+% useful e.g.\ if Memoize is compiled into a format.
+%<!plain>\let\mmz@initial@extraction@method\mmz@extraction@method
%
% \paragraph{Process} the package options (except in \hologo{plainTeX}).
-%<latex>\ProcessPgfPackageOptions{/mmz}
+%<*latex>
+\DeclareUnknownKeyHandler[mmz]{%
+ \expanded{\noexpand\pgfqkeys{/mmz}{#1\IfBlankF{#2}{={#2}}}}}
+\ProcessKeyOptions[mmz]
+%</latex>
%<context>\expandafter\mmzset\expandafter{\currentmoduleparameters}
-%
-% Define |\mmzOutputDirectory| if |output-directory| was not given.
-\ifdefined\mmzOutputDirectory
-\else
- \def\mmzOutputDirectory{}%
-\fi
-\mmzset{output directory/.code={\PackageError{memoize}{Key "output-directory"
- may only be used as a package option}{}}}
+%
+% In \hologo{LaTeX}, |nomemoize| has to process package options as well,
+% otherwise \hologo{LaTeX} will complain.
+%</mmz>
+%<*latex&nommz>
+\DeclareUnknownKeyHandler[mmz]{}
+\ProcessKeyOptions[mmz]
+%</latex&nommz>
+% \paragraph{Extern extraction}
+% We redefine |extract| to immediately trigger extraction. This is crucial in
+% \hologo{plainTeX}, where extraction must be invoked after loading the
+% package, but also potentially useful in other formats when package options
+% include |extract=no|.
+%<*mmz>
\mmzset{
- % \paragraph{Extract} the externs using the method given by |memoize.cfg| or
- % the package options --- unless we're running \hologo{plainTeX}.
- %<*!plain>
- extract/\mmz@extraction@method,
- % In non-\hologo{plainTeX} formats, also disable |extract| in the preamble.
- extract/.code={\PackageError{memoize}{Key "extract" is only allowed as a
- package option.}{If you really want to extract in the preamble, execute
- "extract/<method>".}},
- %</!plain>
- %<*plain>
- % In \hologo{plainTeX}, where extraction must be invoked after
- % loading the package, we now have to redefine |extract|, so that it will
- % immediately trigger extraction.
extract/.is choice,
extract/.default=\mmz@extraction@method,
% But only once:
extract/.append style={
- extract/.code={\PackageError{memoize}{Key "extract" is only allowed to
- be used once.}{If you really want to extract again, execute
- "extract/<method>".}},
+ extract/.code={\PackageError{memoize}{Key "extract" was invoked twice.}{In
+ principle, externs should be extracted only once. If you really want
+ to extract again, execute "extract/<method>".}},
},
- %</plain>
-}
+ % In formats other than \hologo{plainTeX}, we remember the current |extract|
+ % code and then trigger the extraction.
+ %<!plain>/utils/exec={\pgfkeysgetvalue{/mmz/extract/.@cmd}\mmz@temp@extract},
+ %<!plain>extract=\mmz@extraction@method,
+}
+% Option |extract=no| (which only exists in formats other than
+% \hologo{plainTeX}) should allow for an explicit invocation of |extract| in
+% the preamble.
+%<*!plain>
+\def\mmz@temp{no}
+\ifx\mmz@extraction@method\mmz@temp
+ \pgfkeyslet{/mmz/extract/.@cmd}\mmz@temp@extract
+ \let\mmz@extraction@method\mmz@initial@extraction@method
+\fi
+\let\mmz@temp@extract\relax
+%</!plain>
%
% Memoize was not really born for the draft mode, as it cannot produce new
-% externs there. But we don't want to disable the package, as utilization is
-% still perfectly valid in this mode, so let's just warn the user.
+% externs there. But we don't want to disable the package, as utilization and
+% pure memoization are still perfectly valid in this mode, so let's just warn
+% the user.
\ifnum\pdf@draftmode=1
- \PackageWarning{memoize}{No memoization will be performed in the draft mode}%
+ \PackageWarning{memoize}{No externalization will be performed in the draft mode}%
\fi
%</mmz>
%
+% Several further things which need to be defined as dummies in
+% |nomemoize|\slash|memoizable|.
+%<*nommz,mmzable&generic>
+\pgfkeys{%
+ /handlers/.meaning to context/.code={},
+ /handlers/.value to context/.code={},
+}
+\let\mmzAbort\relax
+\let\mmzUnmemoizable\relax
+\newcommand\IfMemoizing[2][]{\@secondoftwo}
+\let\mmzNoRef\@gobble
+\let\mmzForceNoRef\@gobble
+\newtoks\mmzContext
+\newtoks\mmzContextExtra
+\newtoks\mmzCMemo
+\newtoks\mmzCCMemo
+\newcount\mmzExternPages
+\newcount\mmzExtraPages
+\let\mmzTracingOn\relax
+\let\mmzTracingOff\relax
+%</nommz,mmzable&generic>
+%
% \paragraph{The end} of |memoize|, |nomemoize| and |memoizable|.
%<*mmz,nommz,mmzable>
%<plain>\resetatcatcode
diff --git a/macros/generic/memoize/memoize.ins b/macros/generic/memoize/memoize.ins
index f288e8e555..2c24ac368b 100644
--- a/macros/generic/memoize/memoize.ins
+++ b/macros/generic/memoize/memoize.ins
@@ -34,6 +34,7 @@
\file{memoizable.sty}{\from{memoize.dtx}{mmzable,latex}}%
\file{memoizable.tex}{\from{memoize.dtx}{mmzable,plain}}%
\file{t-memoizable.tex}{\from{memoize.dtx}{mmzable,context}}%
+ \file{memoizable.code.tex}{\from{memoize.dtx}{mmzable,generic}}%
\file{memoize-extract-one.tex}{\from{memoize.dtx}{extract-one}}%
}
\endbatchfile