The ltcmdhooks module∗Frank Mittelbach

Phelype Oleinik

June 11, 2024

1Introduction

This file implements generic hooks for (arbitrary) commands. In theory every command\⟨name ⟩ offers now two associated hooks to which code can be added using \AddToHook,1\AddToHookNext, \AddToHookWithArguments, and \AddToHookNextWithArguments.2
These are:cmd/⟨name ⟩/before This hook is executed at the very start of the command, rightafter its arguments (if any) are parsed. The hook ⟨code ⟩ runs in the command inside a call to \UseHookWithArguments. Any code added to this hookusing \AddToHookWithArguments or \AddToHookNextWithArguments can accessthe command’s arguments using #1, #2, etc., up to the number of arguments of thecommand. If \AddToHook or \AddToHookNext are used, the arguments cannot beaccessed (see the lthooks documentation3 on hooks with arguments).
cmd/⟨name ⟩/after This hook is similar to cmd/⟨name ⟩/before, but it is executed at thevery end of the command body. This hook is implemented as a reversed hook.
The hooks are not physically present before \begin{document}4 (i.e., using a command in the preamble will never execute the hook) and if nobody has declared any codefor them, then they are not added to the command code ever. For example, if we havethe following definition\newcommand\foo[2]{Code #1 for #2!}
then executing \foo{A}{B} will simply run Code␣A␣for␣B! as it was always the case.
However, if somebody, somewhere (e.g., in a package) adds
\AddToHook{cmd/foo/before}{<before code>}then, after \begin{document} the definition of \foo will be:∗ This file has version v1.0j dated 2024/04/17, © L
AT X Project.

E

1 In this documentation, when something is being said about \AddToHook, the same will be valid for\AddToHookWithArguments, unless that particular paragraph is highlighting the differences between both.
The same is true for the other hook-related functions and their ...WithArguments counterparts.
2 In practice this is not supported for all types of commands, see section 2.2 for the restrictions that
apply and what happens if one tries to use this with commands for which this is not supported.
3 texdoc lthooks-doc
4 More specifically, they are inserted in the commands after the begindocument hook, so they are also
not present while LATEX is reading the .aux file.

1

\renewcommand\foo[2]{%\UseHookWithArguments{cmd/foo/before}{2}{#1}{#2}%
Code #1 for #2!}and similarly \AddToHook{cmd/foo/after}{<after code>} alters the definition to\renewcommand\foo[2]{%Code #1 for #2!%
\UseHookWithArguments{cmd/foo/after}{2}{#1}{#2}}
In other words, the mechanism is similar to what etoolbox offers with \pretocmd
and \apptocmd with the important differences• that code can be prepended or appended (i.e., added to the hooks) even if thecommand itself is not (yet) defined, because the defining package has not been
loaded at this point;• and that by using the hook management interface it is now possible to define how
the code chunks added in these places are ordered, if different packages want toadd code at these points.

2Restrictions and Operational details

Adding arbitrary material to commands is tricky because most of the time we do notknow what the macro expects as arguments when expanding and TEX doesn’t have areliable way to see that, so some guesswork has to be employed.

2.1

Patching

The code here tries to find out if a command was defined with \newcommand or\DeclareRobustCommand or \NewDocumentCommand, and if so it assumes that the argument specification of the command is as expected (which is not fail-proof, if someone
redefines the internals of these commands in devious ways, but is a reasonable assumption).
If the command is one of the defined types, the code here does a sandboxed expansionof the command such that it can be redefined again exactly as before, but with the hookcode added.
If however the command is not a known type (it was defined with \def, for example), then the code uses an approach similar to etoolbox’s \patchcmd to retokenize thecommand with the hook code in place. This procedure, however, is more likely to fail if
the catcode settings are not the same as the ones at the time of command’s definition,
so not always adding a hook to a command will work.
2.1.1

TimingWhen \AddToHook (or its expl3 equivalent) is called with a generic cmd hook, say,cmd/foo/before, for the first time (that is, no code was added to that same hook before), in the preamble of a document, it will store a patch instruction for that command
until \begin{document}, and only then all the commands which had hooks added will
be patched in one go. That means that no command in the preamble will have hooks
patched into them.
2

At \begin{document} all the delayed patches will be executed, and if the command
doesn’t exist the code is still added to the hook, but it will not be executed. After\begin{document}, when \AddToHook is called with a generic cmd hook the first time,
the command will be immediately patched to include the hook, and if it doesn’t exist or
if it can’t be patched for any reason, an error is thrown; if \AddToHook was already usedin the preamble no new patching is attempted.
This has the consequence that a command defined or redefined after \begin{document}
only uses generic cmd hook code if \AddToHook is called for the first time after the definition is made, or if the command explicitly uses the generic hook in its definition bydeclaring it with \NewHookPair adding \UseHook as part of the code.5

2.2

Commands that look ahead

Some commands are defined in different “steps” and they look ahead in the input stream
to find more arguments. If you try to add some code to the cmd/⟨name ⟩/after hook ofsuch command, it will not work, and it is not possible to detect that programmatically,so the user has to know (or find out) which commands can or cannot have hooks attachedto them.
One good example is the \section command. You can add something to the
cmd/section/before hook, but if you try to add something to the cmd/section/afterhook, \section will no longer work. That happens because the \section macro takes
no argument, but instead calls a few internal LATEX macros to look for the optional andmandatory arguments. By adding code to the cmd/section/after hook, you get in theway of that scanning.
In such a case, where it is known that a specific generic command hook does not work
if code is added to it, the package author can add a \DisableGenericHook6 declaration
to prevent this from happening in user documents and thereby avoiding obscure errors.

3Package Author Interface

The cmd hooks are, by default, available for all commands that can be patched to add
the hooks. For some commands, however, the very beginning or the very end of thecode is not the best place to put the hooks, for example, if the command looks ahead forarguments (see section 2.2).
If you are a package author and you want to add the hooks to your own commands in the proper position you can define the command and manually add the\UseHookWithArguments calls inside the command in the proper positions, and manually
define the hooks with \NewHookWithArguments or \NewReversedHookWithArguments.
When the hooks are explicitly defined, patching is not attempted so you can make sure
your command works properly. For example, an (admittedly not really useful) commandthat typesets its contents in a framed box with width optionally given in parentheses:\newcommand\fancybox{\@ifnextchar({\@fancybox}{\@fancybox(5cm)}}\def\@fancybox(#1)#2{\fbox{\parbox{#1}{#2}}}
If you try that definition, then add some code after it with
5 We might change this behavior in the main document slightly after gaining some usage experience.

6 Please use \DisableGenericHook if at all, only on hooks that you “own”, i.e., for commands yourpackage or class defines and not second guess whether or not hooks of other packages should get disabled!

3

\AddToHook{cmd/fancybox/after}{<code>}and then use the \fancybox command you will see that it will be completely broken,
because the hook will get executed in the middle of parsing for optional (...) argument.
If, on the other hand, you want to add hooks to your command you can do something
like:\newcommand\fancybox{\@ifnextchar({\@fancybox}{\@fancybox(5cm)}}\def\@fancybox(#1)#2{\fbox{%\UseHookWithArguments{cmd/fancybox/before}{2}{#1}{#2}%\parbox{#1}{#2}%\UseHookWithArguments{cmd/fancybox/after}{2}{#1}{#2}}}\NewHookWithArguments{cmd/fancybox/before}{2}\NewReversedHookWithArguments{cmd/fancybox/after}{2}then the hooks will be executed where they should and no patching will be attempted. It is important that the hooks are declared with \NewHookWithArguments or\NewReversedHookWithArguments, otherwise the command hook code will try to patch
the command. Note also that the call to \UseHookWithArguments{cmd/fancybox/before}
does not need to be in the definition of \fancybox, but anywhere it makes sense to insert
it (in this case in the internal \@fancybox).
Alternatively, if for whatever reason your command does not support the generichooks provided here, you can disable a hook with \DisableGenericHook7 , so that whensomeone tries to add code to it they will get an error. Or if you don’t want the error,
you can simply declare the hook with \NewHook and never use it.
The above approach is useful for really complex commands where for one or theother reason the hooks can’t be placed at the very beginning and end of the command
body and some hand-crafting is needed. However, in the example above the real (andin fact only) issue is the cascading argument parsing in the style developed long ago in
LATEX 2.09. Thus, a much simpler solution for this case is to replace it with the modern\NewDocumentCommand syntax and define the command as follows:\DeclareDocumentCommand\fancybox{D(){5cm}m}{\fbox{\parbox{#1}{#2}}}
If you do that then both hooks automatically work and are patched into the right places.

3.1

Arguments and redefining commands

The code in ltcmdhooks does its best to find out how many arguments a given commandhas, and to insert the appropriate call to \UseHookWithArguments, so that the arguments
seen by the hook are exactly those grabbed by the command (the hook, after all, is amacro call, so the arguments have to be placed in the right order, or they won’t match).
When using the package writer interface, as discussed in section 3, to change theposition of the hooks in your commands, you are also free to change how the hook codein your command sees its arguments. When a cmd hook is declared with \NewHook (or\NewHookWithArguments or other variations of that), it loses its “generic” nature andworks as a regular hook. This means that you may choose to declare it without arguments
regardless if the command takes arguments or not, or declare it with arguments, even if
the command takes none.
7 Please use \DisableGenericHook if at all, only on hooks that you “own”, i.e., for commands yourpackage or class defines and not second guess whether or not hooks of other packages should get disabled!

4However, this flexibility should not be abused. When using a nonstandard configuration for the hook arguments, think reasonably: a user will expect that the argument#1 in the hook corresponds to the argument’s first argument, and so on. Any otherconfiguration is likely to cause confusion and, if used, will have to be well documented.
This flexibility, however, allows you to “correct” the arguments for the hooks. For
example, LATEX’s \refstepcounter has a single argument, the name of the counter. The
cleveref package adds an optional argument to \refstepcounter, making the name of
the counter argument #2. If the author of cleveref wanted, for whatever reason, to addhooks to \refstepcounter, to preserve compatibility he could write something along thelines of:\NewHookWithArguments{cmd/refstepcounter/before}{1}\renewcommand\refstepcounter[2][<default>]{%\UseHookWithArguments{cmd/refstepcounter/before}{1}{#2}%<code for \refstepcounter>}so that the mandatory argument, which is arg #2 in the definition, would still be seen as#1 in the hook code.
Another possibility would be to place the optional argument as the second argument
for the hook, so that people looking for it would be able to use it. In either case, it would
have to be well documented to cause as little confusion as possible.

Index
The italic numbers denote the pages where the corresponding entry is described, numbers
underlined point to the definition, all others indicate the places where it is used.

\AddToHook

A...................... 2\AddToHookNext . . . . . . . . . . . . . . . .
\AddToHookNextWithArguments . . . . .
\AddToHookWithArguments . . . . . . . . .
\apptocmd . . . . . . . . . . . . . . . . . . . . .
\DeclareRobustCommand . . . . . . . . . .
\def . . . . . . . . . . . . . . . . . . . . . . . . .
\newcommand . . . . . . . . . . . . . . . . . . .
\NewDocumentCommand . . . . . . . . . . . .
\patchcmd . . . . . . . . . . . . . . . . . . . . .
\pretocmd . . . . . . . . . . . . . . . . . . . . .
\section . . . . . . . . . . . . . . . . . . . . .

D\DisableGenericHook . . . . . . . . . . . . . . . 4
N\NewDocumentCommand . . . . . . . . . . . . . . .
\NewHook . . . . . . . . . . . . . . . . . . . . . . . .
\NewHookPair . . . . . . . . . . . . . . . . . . . . .
\NewHookWithArguments . . . . . . . . . . . . .
\NewReversedHookWithArguments . . . . . .

44333R\refstepcounter . . . . . . . . . . . . . . . . . . 511122222223U\UseHook . . . . . . . . . . . . . . . . . . . . . . . . 3\UseHookWithArguments . . . . . . . . . . . . . 4TE
E ε commands:\AddToHook . . . . . . . . . . . . . . . . . . . . 1T X and LAT X 25