This is a follow-up to this question:
where I explained that I want to write some definitions into an aux file and use hooks to achieve this. Refer to this question for context why I want to do the following.
I want to write a definition of a macro to an aux file that involves parameters as #1
, as in
\documentclass{article}
\begin{document}
\ExplSyntaxOn
\iow_now:cn { @auxout }
{
\def\auxfoo#1{auxfoo: #1.}
}
\ExplSyntaxOff
\end{document}
This results in an error when TeX re-reads the aux-file at the end of the document again, as the characters written to the aux file are actually \def \auxfoo ##1{auxfoo:##1.}
which is of course not valid TeX code.
How can I write a bare #1
token to the auxfile?
Or, more generally, if i have some token list that might involve #1
, #2
, how can I write it to the auxfile exactly as it is to restore it at some later time?
In my use case, this is slightly more complicated, as I have a macro that defines a DocumentCommand
nestedly, where its body has been captured as an argument (given by the end user). I want to define this nested DocumentCommand
and write the call of the outer macro (that the user typed somewhere) into the aux file.
But probably an answer to the above mwe question can be easily adapted.
Some more detail
Edit: As Ulrich Diez requested some further details on the exact usage, here it is, with the risk that what comes is quite lengthy. That's what I thought why this would not give a good question on its own and kept myself briefly about the exact intended usage.
I am writing on a (personal) package which I call groupthm
which I use to write up some of my lecture notes at university.
The source code can be found on my GitLab repository.
Shortly, the goal is to quickly define families of theorems that behave very similar, e.g. a Theorem
that has a visual star or some other symbol after its name, or a Theorem
that is numbered one layer lower than usual.
This concept is implemented in forms of groups of theorems, specifying the behaviour of a group (like having this and that suffix), and declaring theorems members of some group. This way, I can easily change the behaviour of a whole bunch of environments by simply altering the definition of some group.
This involves, at some point, giving the user the ability to declare an environment theorem
with a custom body and using xparse
for a convenient interface.
In this body, the user can parse the arguments of the environment with the usual syntax of xparse
and which will then select some groups on how this environment is handled.
For example, when declaring \begin{theorem}*
, this could be a member of the star
group, and \begin{theorem}
behaves like normal.
At the end of the term I usually want to get rid of the visual stars, so I just change the definiton of one group and all environments adjust properly.
As a backend, I just use thmtools
to declare these environments, and theorem
itself is a wrapper environment which then calls the corresponding variants depending on the arguments given.
For more information you can check out the documentation of the package that I am writing. (But beware that the quality might not be too good). This is just the processed .dtx
file.
This has, however, the disadvantage, that I have to quite a lot of processing until I end up with the underlying environments that thmtools
declares (I won't go into detail here too much).
On documents where I have a large number of such environments, this accounts for more than 2 seconds.
Since I am lazy and I have some wrapper package that just declares all of my usual environments by default (if you want, check it out at fancythm), this is a major slowdown even on smaller documents.
And yes, I get that I could avoid this problem by not defining all theorems by default, but again - I am lazy - and since I think this can be automated away, I definitely like trying to do so and have an easy-to use yet reasonably fast package.
Thus, I had the ideal to save the definitions of the underlying environments of thmtools
to the aux file on the first run of LaTeX, and then load them in future runs to that I do not have to compute these again. When a definition is changed, I can delete the aux file to get a slower run of TeX that updates these.
But since this saving also involves the declaration of an xparse
environment, I essentially have to save the definition of an xparse
environment into the auxfile - which yielded the above discussed problems with the expansion of the #
character.
Back to the question / definitions in aux files
Now considering the exact usage of the question, I wrote some proof of concept package mkessler-cache that demonstrates the desired behaviour by providing a \DeclareCachedDocumentCommand
macro that behaves like mentioned above.
With this, consider the following example file (taken from the test at my GitLab repository and added the package as filecontents so that this question remains complete):
\begin{filecontents}{mkessler-cache.sty}
\ProvidesExplPackage{mkessler-cache}{2022/02/02}{2.8.1-14-g01254fc}{Cache definitions of previous LaTeX runs to avoid computations}
%%%% This is just a proof of concept package,
%%%% there is possibly more room for clean-up
%%%% and / or speed-up etc.
%%% Also, the provided
%%% \DeclareCachedDocumentCommand
%%% is of course not really
%%% useful and just for demonstration
%%% purposes
\bool_new:N \g__cache_cache_bool
\int_new:N \g__cache_cache_version_document_int
\int_new:N \g__cache_cache_version_aux_int
\int_gset:Nn \g__cache_cache_version_aux_int { -1 }
\tl_new:N \g__cache_dump_auxfile_tl
\tl_new:N \g__cache_lazy_document_tl
\tl_new:N \g__cache_lazy_auxfile_tl
\keys_define:nn { cache }
{
cache .bool_set:N = \g__cache_cache_bool,
cache .default:n = { true },
__cache version__ .int_set:N = \g__cache_cache_version_document_int,
cache version .meta:nn = { cache } { cache = true, __cache version__ = #1 },
cache version .default:n = { 0 },
}
\RequirePackage{l3keys2e}
\ProcessKeysOptions{cache}
% Setting up lazy execution and
% selecting of executing after reading of aux file
% if cache option has been used
\bool_if:NTF \g__cache_cache_bool
{
%% Writing things (at one go) to aux file at end of document:
\cs_new:Npn \__cache_put_aux:n #1
{
\tl_gput_right:Nn \g__cache_dump_auxfile_tl { #1 }
}
\cs_generate_variant:Nn \__cache_put_aux:n { x, V }
\cs_generate_variant:Nn \iow_now:Nn { c V }
%%% Handles dumping data to aux file at end of document
\hook_gput_code:nnn { enddocument } { cache }
{
\iow_now:cV { @auxout } \g__cache_dump_auxfile_tl
}
%%% Writes the current cache version into aux file
\__cache_put_aux:x
{
\ExplSyntaxOn
\int_gset:Nn \exp_not:N \g__cache_cache_version_aux_int
{
\int_use:N \g__cache_cache_version_document_int
}
\ExplSyntaxOff
}
%%% Executing something lazily at beginning of document
%%% Lazy code only gets executed if auxfile version is older than document
\cs_new:Npn \__cache_lazy:n
{
\tl_gput_right:Nn \g__cache_lazy_document_tl
}
%%% Caching things. Handles writing and reading to aux file
%%% and makes code available in the next run of LaTeX
\cs_new:Npn \__cache_cache:n #1
{
\tl_set:Nn \l_tmpa_tl
{
\csname tl_gput_right:cn \endcsname { g__cache_lazy_auxfile_tl } { #1 }
}
\regex_replace_all:nnN { \cP\# } { \cO\# } \l_tmpa_tl
\__cache_put_aux:V \l_tmpa_tl
}
%%% This handles loading either the cached definitions
%%% from last run or executing the lazy definitions from the current run
%%% after loading the aux file
\hook_gput_code:nnn { begindocument } { cache }
{
\int_compare:nNnTF
\g__cache_cache_version_aux_int < \g__cache_cache_version_document_int
{
\tl_use:N \g__cache_lazy_document_tl
}
{
\tl_use:N \g__cache_lazy_auxfile_tl
}
}
}
{
\cs_set_eq:NN \__cache_lazy:n \use:n
\cs_set_eq:NN \__cache_cache:n \use_none:n
}
\cs_new:Npn \__cached_new_document_command:nnn #1 #2 #3
{
\NewDocumentCommand{#1}{#2}{#3}
\__cache_cache:n
{
\csname __cached_new_document_command:nnn \endcsname { #1 } { #2 } { #3 }
}
}
\NewDocumentCommand{\DeclareCachedDocumentCommand}{mmm}
{
\__cache_lazy:n
{
\__cached_new_document_command:nnn{#1}{#2}{#3}
}
}
\end{filecontents}
\documentclass{article}
%%% Setting a higher cache version will cause a re-evaluation of the
% \DeclareCachedDocumentCommand macros present in the preamble
% Otherwise, the definitions can be altered or even removed without breaking
% the document, as long as the aux file is present.
%% \DeclareCachedDocumentCommand is now just a plain wrapper around \DeclareDocumentCommand
%% that implements this caching, so it is not useful on itself
%%
%% If \DeclareCachedDocumentCommand is, however, some macro that does lengthy computations
%% and then issues one ore more of underlying \DeclareDocumentCommands
%% we can save this computation on the second run, since we do not evaluate
%% \DeclareCachedDocumentCommand further (we just copied its definition)
%% and load the plain definitions that the invocation produced in the last run
%% and that we saved to the aux file for restoration.
\usepackage[cache version = 1]{mkessler-cache}
\DeclareCachedDocumentCommand\foo{m}{foo: Called with argument '#1'.}
\DeclareCachedDocumentCommand\baz{m}{baz: Called with argument '#1'.}
\DeclareCachedDocumentCommand\parser{!s !t+}
{
\IfBooleanT {#1} {*}
\IfBooleanT {#2} {$\dagger$}
}
\begin{document}
\foo { one }
\baz{ arg }
\parser*+
Some text.
\end{document}
Changing the lines where the macros are defined has no effect (provided you do not delete the aux file), and incrementing the cache version
option causes a re 'computation' (remember in the intended case there is actually computation involved).
I hope that this clarifies my use case enough.
aux
file is the right place where to define commands with arguments.\string#
). But as the aux is read in a group one need \gdef not \def.