From 75db33dc347ec9cfbaa2f3ec538a2697351f571b Mon Sep 17 00:00:00 2001
From: Norbert Preining <norbert@preining.info>
Date: Wed, 5 May 2021 03:02:17 +0000
Subject: CTAN sync 202105050302

---
 macros/latex-dev/base/ltcmdhooks.dtx | 1093 ++++++++++++++++++++++++++++++++++
 1 file changed, 1093 insertions(+)
 create mode 100644 macros/latex-dev/base/ltcmdhooks.dtx

(limited to 'macros/latex-dev/base/ltcmdhooks.dtx')

diff --git a/macros/latex-dev/base/ltcmdhooks.dtx b/macros/latex-dev/base/ltcmdhooks.dtx
new file mode 100644
index 0000000000..cc67f5eaa7
--- /dev/null
+++ b/macros/latex-dev/base/ltcmdhooks.dtx
@@ -0,0 +1,1093 @@
+% \iffalse meta-comment
+%
+%% File: ltcmdhooks.dtx (C) Copyright 2020-2021
+%%       Frank Mittelbach, Phelype Oleinik, LaTeX Team
+%
+% It may be distributed and/or modified under the conditions of the
+% LaTeX Project Public License (LPPL), either version 1.3c of this
+% license or (at your option) any later version.  The latest version
+% of this license is in the file
+%
+%    https://www.latex-project.org/lppl.txt
+%
+%
+%%% From File: ltcmdhooks.dtx
+%
+\def\ltcmdhooksversion{v1.0a}
+\def\ltcmdhooksdate{2021/04/30}
+%
+%
+%
+%<*driver>
+\documentclass{l3doc}
+%\usepackage{ltcmdhooks}
+\makeatletter
+\providecommand\footref[1]{%
+  \begingroup
+    \unrestored@protected@xdef\@thefnmark{\ref{#1}}%
+  \endgroup
+  \@footnotemark}
+\makeatother
+\EnableCrossrefs
+\CodelineIndex
+\begin{document}
+  \DocInput{ltcmdhooks.dtx}
+\end{document}
+%</driver>
+%
+% \fi
+%
+%
+% \providecommand\hook[1]{\texttt{#1}}
+% \providecommand\fmi[1]{\marginpar{\footnotesize FMi: #1}}
+% \providecommand\pho[1]{\marginpar{\footnotesize PhO: #1}}
+% \providecommand\phoinline[1]{\begin{quote}\itshape\footnotesize PhO: #1\end{quote}}
+%
+% \title{The \texttt{ltcmdhooks} module\thanks{This file has version
+%    \ltcmdhooksversion\ dated \ltcmdhooksdate, \copyright\ \LaTeX\
+%    Project.}}
+% \author{Frank Mittelbach \and Phelype Oleinik}
+%
+% \maketitle
+%
+%
+% \tableofcontents
+%
+%
+% \section{Introduction}
+%
+%    This file implements generic hooks for (arbitrary) commands.
+%    In theory every command \tn[no-index]{\meta{name}} offers now two
+%    associated hooks to which code can be added using \tn{AddToHook}
+%    or \tn{AddToHookNext}.\footnote{In practice this is not supported
+%    for all types of commands, see section~\ref{sec:look-ahead} for
+%    the restrictions that apply and what happens if one tries to use
+%    this with commands for which this is not supported.}  These are
+%    \begin{description}
+%    \item[\hook{cmd/\meta{name}/before}]
+%
+%       This hook is executed at the very start of the command
+%       execution after its arguments (if any) are parsed.  The hook
+%       \meta{code} is wrapped in the command inside a call to
+%       \cs{UseHook}|{cmd/|\meta{name}|/before}|, so the arguments
+%       passed to the command are \emph{not} available in the hook
+%       \meta{code}.
+%
+%    \item[\hook{cmd/\meta{name}/after}]
+%
+%       This hook is similar to \hook{cmd/\meta{name}/before}, but it is
+%       executed at the very end of the command body.  This hook is
+%       implemented as a reversed hook.
+%    \end{description}
+%
+%    The hooks are not physically present before
+%    \verb=\begin{document}= (i.e., using a command in the preamble
+%    will never execute them) and if nobody has declared any code for
+%    them, then they are not added to the command code ever.  For
+%    example, if we have the following definition
+%\begin{verbatim}
+%    \newcommand\foo[2]{Code #1 for #2!}
+%\end{verbatim}
+%    then executing \verb=\foo{A}{B}= will simply run
+%    \verb*=Code A for B!=
+%    as it was always the case. However, if somebody, somewhere (e.g.,
+%    in a package) adds
+%\begin{verbatim}
+%    \AddToHook{cmd/foo/before}{<before code>}
+%\end{verbatim}
+%    then, after |\begin{document}| the definition of \cs[no-index]{foo} will be:
+%\begin{verbatim}
+%    \renewcommand\foo[2]{\UseHook{cmd/foo/before}Code #1 for #2!}
+%\end{verbatim}
+%    and similarly \verb=\AddToHook{cmd/foo/after}{<after code>}=
+%    alters the definition to
+%\begin{verbatim}
+%    \renewcommand\foo[2]{Code #1 for #2!\UseHook{cmd/foo/after}}
+%\end{verbatim}
+%
+%    In other words, the mechanism is similar to what \pkg{etoolbox}
+%    offers with \tn{pretocmd} and \tn{apptocmd} with the important
+%    differences 
+%    \begin{itemize}
+%    \item
+%
+%      that code can be prepended or appended (i.e., added to the
+%      hooks) even if the command itself is not defined, because the
+%      defining package has not yet been loaded
+%
+%    \item
+%
+%      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 to add code at these
+%      points.
+%
+%    \end{itemize}
+%
+%
+%
+%
+% \section{Restrictions and Operational details}
+% \label{sec:restrictions}
+%
+% Adding arbitrary material to commands is tricky because most of the
+% time we do not know what the macro expects as arguments when expanding
+% and \TeX{} doesn't have a reliable way to see that, so some guesswork
+% has to be employed.
+%
+% \subsection{Patching}
+%
+% The code here tries to find out if a command was defined with
+% \tn{newcommand} or \tn{DeclareRobustCommand} or
+% \tn{NewDocumentCommand}, and if so it \emph{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 expansion of the command such that it can be redefined again
+% exactly as before, but with the hook code added.
+%
+% If however the command is not a known type (it was defined with
+% \tn{def}, for example), then the code uses an approach similar to
+% \pkg{etoolbox}'s \tn{patchcmd} to retokenize the command 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.
+%
+% \subsubsection{Timing}
+%
+% When \cs{AddToHook} (or its \pkg{expl3} equivalent) is called with
+% a generic |cmd| hook, say, \hook{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.
+%
+% 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
+% \cs{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 \cs{AddToHook} was already used in 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
+% \cs{AddToHook} is called for the first time after the definition is
+% made, or if the command explicitly uses the generic hook in its
+% definition by declaring it with \cs{NewHookPair} adding \cs{UseHook} as
+% part of the code.\footnote{We might change this behavior in the main
+% document slightly after gaining some usage experience.}
+%
+%
+% \subsection{Commands that look ahead}
+% \label{sec: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 \hook{cmd/\meta{name}/after} hook of such 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 attached to them.
+%
+% One good example is the \tn{section} command.  You can add something
+% to the \hook{cmd/section/before} hook, but if you try to add something
+% to the \hook{cmd/section/after} hook, \tn{section} will no longer
+% work.  That happens because the \tn{section} macro takes no argument,
+% but instead calls a few internal \LaTeX{} macros to look for the
+% optional and mandatory arguments.  By adding code to the
+% \hook{cmd/section/after} hook, you get in the way of that scanning.
+%
+%
+%
+% \section{Package Author Interface}
+%
+% The \hook{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 the code is not the best place
+% to put the hooks, for example, if the command looks ahead for
+% arguments (see section~\ref{sec:look-ahead}).
+%
+% 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 \cs{UseHook} calls inside the command in the proper
+% positions, and manually define the hooks with \cs{NewHook} or
+% \cs{NewReversedHook}.  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) command
+% that typesets its contents in a framed box with width optionally
+% given in parentheses:
+% \begin{verbatim}
+%    \newcommand\fancybox{\@ifnextchar({\@fancybox}{\@fancybox(5cm)}}
+%    \def\@fancybox(#1)#2{\fbox{\parbox{#1}{#2}}}
+% \end{verbatim}
+% If you try that definition, then add some code after it with
+% \begin{verbatim}
+%    \AddToHook{cmd/fancybox/after}{<code>}
+% \end{verbatim}
+% and then use the \cs[no-index]{fancybox} command you will see that it
+% will be completely broken, because the hook will get executed in the
+% middle of parsing for optional \texttt{(...)} argument.
+%
+% If, on the other hand, you want to add hooks to your command you can
+% do something like:
+% \begin{verbatim}
+%   \newcommand\fancybox{\@ifnextchar({\@fancybox}{\@fancybox(5cm)}}
+%   \def\@fancybox(#1)#2{\fbox{%
+%                        \UseHook{cmd/fancybox/before}%
+%                        \parbox{#1}{#2}%
+%                        \UseHook{cmd/fancybox/after}}}
+%   \NewHook{cmd/fancybox/before}
+%   \NewReversedHook{cmd/fancybox/after}
+% \end{verbatim}
+% then the hooks will be executed where they should and no patching
+% will be attempted.  It is important that the hooks are declared with
+% \cs{NewHook} or \cs{NewReversedHook}, otherwise the command hook
+% code will try to patch the command.  Note also that the call to
+% |\UseHook{cmd/fancybox/before}| does not need to be in the
+% definition of \cs[no-index]{fancybox}, but anywhere it makes sense
+% to insert it (in this case in the internal
+% \cs[no-index]{@fancybox}).
+%
+% Alternatively, if for whatever reason your command does not support
+% the generic hooks provided here, you can disable a hook with
+% \cs{DisableHook}\footnote{Please use \cs{DisableHook} if at all, only
+% on hooks that you \enquote{own}, i.e., for commands that your
+% package or class defines and not second guess
+% whether or not hooks of other packages should get disabled!}, so
+% that when someone 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
+% \cs{NewHook} and never use it.
+%
+%
+% The above approach is useful for really complex commands where for
+% one or the other 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 (and in 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 \cs{NewDocumentCommand} syntax and
+% define the command as follows:
+% \begin{verbatim}
+%  \DeclareDocumentCommand\fancybox{D(){5cm}m}{\fbox{\parbox{#1}{#2}}}
+% \end{verbatim}
+% If you do that then both hooks automatically work and are patched
+% into the right places.
+%
+% \StopEventually{\setlength\IndexMin{200pt}  \PrintIndex  }
+%
+%
+%
+% \section{The Implementation}
+%
+% \subsection{Execution plan}
+%
+% To add |before| and |after| hooks to a command we will need to peek
+% into the definition of a command, which is always a tricky thing to
+% do.  Some cases are easy because we know how the command was defined,
+% so we can assume how its \meta{parameter text} looks like (for example
+% a command defined with \tn{newcommand} may have an optional argument
+% followed by a run of mandatory arguments), so we can just expand that
+% command and make it grab |#1|, |#2|, etc.\@ as arguments and
+% define it all back with the hooks added.
+%
+% Life's usually not that easy, so with some commands we can't do that
+% (a |#1| might as well be |#|$_{12}$|1|$_{12}$ instead of the expected
+% |#|$_{6}$|1|$_{12}$, for example) so we need to resort to ``patching''
+% the command: read its \tn{meaning}, and tokenize it again with
+% \tn{scantokens} and hope for the best.
+%
+% So the overall plan is:
+% \begin{enumerate}
+%   \item
+%     Check if a command is of a known type (that is, defined with
+%     \tn{newcommand}\footnote{It's not always possible to reliably
+%     detect this case because a command defined with no optional
+%     argument is indistinguishable from a \tn{def}ed command.},
+%     \cs[no-index]{DeclareRobustCommand}, or
+%     \cs[no-index]{New(Expandable)DocumentCommand}), and if is, take
+%     appropriate action.
+%   \item
+%     If the command is not a known type, we'll check if the command can
+%     be patched.  Two things will prevent a command from being
+%     patched:  if it was defined in a nonstandard catcode setting, or
+%     if it is an internal expl3 command with |__|\meta{module} in its
+%     name, in which case we refuse to patch.
+%   \item
+%     If the command was defined in nonstandard catcode settings, we
+%     will try a few standard ones to try our best to carry out the
+%     pathing.  If this doesn't help either, the code will give up and
+%     throw an error.
+% \end{enumerate}
+%
+%    \begin{macrocode}
+%<@@=hook>
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+%<*2ekernel|latexrelease>
+\ExplSyntaxOn
+%<latexrelease>\NewModuleRelease{2021/06/01}{ltcmdhooks}
+%<latexrelease>                 {The~hook~management~system~for~commands}
+%    \end{macrocode}
+%
+% \subsection{Variables}
+%
+% \begin{macro}[int]{\g_hook_patch_action_list_tl}
+%    Pairs of |\if<cmd>..\patch<cmd>| to be used with
+%    \tn{robust@command@act} when looking for a known patching
+%    rule. This token list is exposed because we see some future
+%    applications (with very specialized packages, such as
+%    \pkg{etoolbox} that may want to extend the pairs processed. It is
+%    not meant for general use which is why it is not documented in
+%    the interface documentation above.
+%    \begin{macrocode}
+\tl_new:N \g_hook_patch_action_list_tl
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\l_@@_patch_num_args_int}
+%   The number of arguments in a macro being patched.
+%    \begin{macrocode}
+\int_new:N \l_@@_patch_num_args_int
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\l_@@_patch_prefixes_tl}
+% \begin{macro}{\l_@@_patch_param_text_tl}
+% \begin{macro}{\l_@@_patch_replacement_tl}
+%   The prefixes and parameters of the definition for the macro being
+%   patched.
+%    \begin{macrocode}
+\tl_new:N \l_@@_patch_prefixes_tl
+\tl_new:N \l_@@_patch_param_text_tl
+\tl_new:N \l_@@_patch_replacement_tl
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\g_@@_delayed_patches_prop}
+%   A list containing the patches delayed to |\begin{document}|, so that
+%   patching is not attempted twice.
+%    \begin{macrocode}
+\prop_new:N \g_@@_delayed_patches_prop
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_patch_debug:x}
+%   A helper for patching debug info.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_patch_debug:x #1
+  { \@@_debug:n { \iow_term:x { [lthooks]~#1 } } }
+%    \end{macrocode}
+% \end{macro}
+%
+% \subsection{Variants}
+%
+% \begin{macro}[int]{\tl_rescan:nV}
+%   \pkg{expl3} function variants used throughout the code.
+%    \begin{macrocode}
+\cs_generate_variant:Nn \tl_rescan:nn { nV }
+%    \end{macrocode}
+% \end{macro}
+%
+% \subsection{Patching or delaying}
+%
+% \begin{macro}{\@@_try_put_cmd_hook:n,\@@_try_put_cmd_hook:w}
+%   Before |\begin{document}| all patching is delayed.
+%   This function is called from within \cs{AddToHook}, when code is
+%   added to a generic |cmd| hook is newly declared.  It checks whether
+%   the patch position is valid, then proceeds to trying to patch or
+%   delaying to |\begin{document}| if in the preamble.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_try_put_cmd_hook:n #1
+  { \@@_try_put_cmd_hook:w #1 / / / \s_@@_mark {#1} }
+\cs_new_protected:Npn \@@_try_put_cmd_hook:w
+    #1 / #2 / #3 / #4 \s_@@_mark #5
+  {
+    \@@_debug:n { \iow_term:n { ->~Adding~cmd~hook~to~'#2'~(#3): } }
+    \str_case:nnTF {#3}
+        { { before } { } { after } { } }
+      { \exp_args:Nc \@@_patch_cmd_or_delay:Nnn {#2} {#2} {#3} }
+      { \__kernel_msg_error:nnn { hooks } { wrong-cmd-hook } {#2} {#3} }
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_patch_cmd_or_delay:Nnn}
+% \begin{macro}{\@@_cmd_begindocument_code:}
+%   In the preamble, \cs{@@_patch_cmd_or_delay:Nnn} just adds the patch
+%   instruction to a property list to be executed later.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_patch_cmd_or_delay:Nnn #1 #2 #3
+  {
+    \@@_debug:n { \iow_term:n { ->~Add~generic~cmd~hook~for~#2~(#3). } }
+    \@@_debug:n
+      { \iow_term:n { !~In~the~preamble:~delaying. } }
+    \prop_gput:Nnn \g_@@_delayed_patches_prop { #2 / #3 }
+      { \@@_cmd_try_patch:nn {#2} {#3} }
+  }
+%    \end{macrocode}
+%
+%   The delayed patches are added to a property list to prevent
+%   duplication, and the code stored in the property list for each
+%   key is executed.  The function \cs{@@_patch_cmd_or_delay:Nnn} is
+%   also redefined to be \cs{@@_patch_command:Nnn} so that no further
+%   delaying is attempted.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_cmd_begindocument_code:
+  {
+    \cs_gset_eq:NN \@@_patch_cmd_or_delay:Nnn \@@_patch_command:Nnn
+    \prop_map_function:NN \g_@@_delayed_patches_prop { \use_ii:nn }
+    \prop_gclear:N \g_@@_delayed_patches_prop
+    \cs_undefine:N \@@_cmd_begindocument_code:
+  }
+\g@addto@macro \@kernel@after@begindocument
+  { \@@_cmd_begindocument_code: }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_cmd_try_patch:nn}
+%   At |\begin{document}| tries patching the command if the hook
+%   was not manually created in the meantime.  If the document does not
+%   exist, no error is raised here as it may hook into a package that
+%   wasn't loaded.  Hooks added to commands in the document body still
+%   raise an error if the command is not defined.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_cmd_try_patch:nn #1 #2
+  {
+    \@@_debug:n
+      { \iow_term:x { ->~\string\begin{document}~try~cmd / #1 / #2. } }
+    \@@_if_declared:nTF { cmd / #1 / #2 }
+      {
+        \@@_debug:n
+          { \iow_term:n { .->~Giving~up:~hook~already~created. } }
+      }
+      {
+        \cs_if_exist:cT {#1}
+          { \exp_args:Nc \@@_patch_command:Nnn {#1} {#1} {#2} }
+      }
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+%
+%
+%
+%
+% \subsection{Patching commands}
+%
+% \begin{macro}{\@@_patch_command:Nnn}
+% \begin{macro}{\@@_patch_check:NNnn}
+% \begin{macro}[TF]{\@@_if_public_command:N}
+% \begin{macro}{\@@_if_public_command:w}
+%   \cs{@@_patch_command:Nnn} will do some sanity checks on the
+%   argument to detect if it is possible to add hooks to the command,
+%   and raises an error otherwise.  If the command can contain hooks,
+%   then it uses \tn{robust@command@act} to find out what type is the
+%   command, and patch it accordingly.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_patch_command:Nnn #1 #2 #3
+  {
+    \@@_patch_debug:x { analyzing~'\token_to_str:N #1' }
+    \@@_patch_debug:x { \token_to_str:N #1 = \token_to_meaning:N #1 }
+    \@@_patch_check:NNnn \cs_if_exist:NTF #1 { undef }
+      {
+        \@@_patch_debug:x { ++~control~sequence~is~defined }
+        \@@_patch_check:NNnn \token_if_macro:NTF #1 { macro }
+          {
+            \@@_patch_debug:x { ++~control~sequence~is~a~macro }
+            \@@_patch_check:NNnn \@@_if_public_command:NTF #1 { expl3 }
+              {
+                \@@_patch_debug:x { ++~macro~is~not~private }
+                \robust@command@act
+                  \g_hook_patch_action_list_tl #1
+                  \@@_retokenize_patch:Nnn { #1 {#2} {#3} }
+              }
+          }
+      }
+  }
+%    \end{macrocode}
+%
+%   And here's the auxiliary used above:
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_patch_check:NNnn #1 #2 #3 #4
+  {
+    #1 #2 {#4}
+      {
+        \__kernel_msg_error:nnxx { hooks } { cant-patch }
+          { \token_to_str:N #2 } {#3}
+      }
+  }
+%    \end{macrocode}
+%   and a conditional \cs{@@_if_public_command:N} to check if a command
+%   has |__| in its name (no other checking is performed).  Primitives
+%   with |:D| in their name could be included here, but they are already
+%   discarded in the \cs{token_if_macro:NTF} test above.
+%    \begin{macrocode}
+\use:x
+  {
+    \prg_new_protected_conditional:Npnn
+        \exp_not:N \@@_if_public_command:N ##1 { TF }
+      {
+        \exp_not:N \exp_last_unbraced:Nf
+          \exp_not:N \@@_if_public_command:w
+            { \exp_not:N \cs_to_str:N ##1 }
+          \tl_to_str:n { _ _ } \s_@@_mark
+      }
+  }
+\exp_last_unbraced:NNNNo
+\cs_new_protected:Npn \@@_if_public_command:w
+    #1 \tl_to_str:n { _ _ } #2 \s_@@_mark
+  {
+    \tl_if_empty:nTF {#2}
+      { \prg_return_true: }
+      { \prg_return_false: }
+  }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+%
+%
+%
+%
+%
+%
+% \subsubsection{Patching by expansion and redefinition}
+%
+% \begin{macro}[int]{\g_hook_patch_action_list_tl}
+%    This is the list of known command types and the function that
+%    patches the command hooks into them.  The conditionals are taken
+%    from \tn{ShowCommand}, \tn{NewCommandCopy} and
+%    \cs{__kernel_cmd_if_xparse:NTF} defined in \texttt{ltcmd}.
+%    \begin{macrocode}
+\tl_gset:Nn \g_hook_patch_action_list_tl
+  {
+    { \@if@DeclareRobustCommand \@@_patch_DeclareRobustCommand:Nnn }
+    { \@if@newcommand \@@_patch_newcommand:Nnn }
+    { \__kernel_cmd_if_xparse:NTF \@@_cmd_patch_xparse:Nnn }
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+%
+%
+%
+% \begin{macro}{\@@_patch_DeclareRobustCommand:Nnn}
+%   At this point we know that the commands can be patched by expanding
+%   then redefining.  These are the cases of commands defined with
+%   \tn{newcommand} with an optional argument or with
+%   \tn{DeclareRobustCommand}.
+%
+%   With \cs{@@_patch_DeclareRobustCommand:Nnn} we check if the command
+%   has an optional argument (with a test counter-intuitively called
+%   \tn{@if@newcommand}).  If so, we forward the action to
+%   \cs{@@_patch_newcommand:Nnn}, otherwise call the patching engine
+%   \cs{@@_patch_expand_redefine:NNnn} with a \cs{c_false_bool} to
+%   indicate that there is no optional argument.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_patch_DeclareRobustCommand:Nnn #1
+  {
+    \exp_args:Nc \@if@newcommand { \cs_to_str:N #1 ~ }
+      { \exp_args:Nc \@@_patch_newcommand:Nnn }
+      { \exp_args:NNc \@@_patch_expand_redefine:NNnn \c_false_bool }
+        { \cs_to_str:N #1 ~ }
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+%
+%
+% \begin{macro}{\@@_patch_newcommand:Nnn}
+%   If the command was defined with \tn{newcommand} and an optional
+%   argument, call the patching engine with a \cs{c_true_bool} to flag
+%   the presence of an optional argument, and with
+%   \cs[no-index]{\string\command} to patch the actual code for
+%   \cs[no-index]{command}.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_patch_newcommand:Nnn #1
+  {
+    \exp_args:NNc \@@_patch_expand_redefine:NNnn \c_true_bool
+      { \c_backslash_str \cs_to_str:N #1 }
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+%  \begin{macro}{\@@_cmd_patch_xparse:Nnn}
+%    And for commands defined by the \pkg{xparse} commands use this
+%    for patching:
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_cmd_patch_xparse:Nnn #1
+  {
+    \exp_args:NNc \@@_patch_expand_redefine:NNnn \c_false_bool
+      { \cs_to_str:N #1 ~ code }
+  }
+%    \end{macrocode}
+%  \end{macro}
+%
+%
+%
+%
+% \begin{macro}{\@@_patch_expand_redefine:NNnn}
+% \begin{macro}[EXP]{\@@_make_prefixes:w}
+%   Now the real action begins.  Here we have in |#1| a boolean
+%   indicating if the command has a |[|\ldots|]|-delimited argument, in
+%   |#2| the command control sequence, in |#3| the name of the command
+%   (note that |#1|${}\ne{}$|\csname#2\endcsname| at this point!), and
+%   in |#4| the hook position, either |before| or |after|.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_patch_expand_redefine:NNnn #1 #2 #3 #4
+  {
+    \@@_patch_debug:x { ++~command~can~be~patched~without~rescanning }
+%    \end{macrocode}
+%   We'll start by counting the number of arguments in the command by
+%   counting the number of characters in the \cs{cs_argument_spec:N} of
+%   the macro, divided by two, and subtracting one if the command has an
+%   optional argument (that is, an extra |[]| in its
+%   \meta{parameter text}).
+%    \begin{macrocode}
+    \int_set:Nn \l_@@_patch_num_args_int
+      {
+        \exp_args:Nf \str_count:n { \cs_argument_spec:N #2 } / 2
+        \bool_if:NT #1 { -1 }
+      }
+%    \end{macrocode}
+%   Now build two token lists:
+%   \begin{description}
+%     \item[\cs{l_@@_patch_param_text_tl}] will contain the
+%       \meta{parameter text} to be used when redefining the macro.  It
+%       should be identical to the \meta{parameter text} used when
+%       originally defining that macro.
+%     \item[\cs{l_@@_patch_replacement_tl}] will contain braced pairs of
+%       |#|$_{12}$\meta{num} to feed to the macro when expanded.  This
+%       token list as well as the previous will have the first item
+%       surrounded by |[|\ldots|]| in the case of an optional argument.
+%   \end{description}
+%    \begin{macrocode}
+    \int_compare:nNnTF { \l_@@_patch_num_args_int } > { \c_zero_int }
+      {
+        \tl_set:Nx \l_@@_patch_param_text_tl
+          { \bool_if:NTF #1 { [####1] } {  ####1  } }
+        \tl_set:Nx \l_@@_patch_replacement_tl
+          { \bool_if:NTF #1 { [####1] } { {####1} } }
+        \int_step_inline:nnn { 2 } { \l_@@_patch_num_args_int }
+          {
+            \tl_put_right:Nn \l_@@_patch_param_text_tl    { ## ####1 }
+            \tl_put_right:Nn \l_@@_patch_replacement_tl { { ## ####1 } }
+          }
+      }
+      {
+        \tl_clear:N \l_@@_patch_param_text_tl
+        \tl_clear:N \l_@@_patch_replacement_tl
+      }
+%    \end{macrocode}
+%   Finally, before redefining, we need to also get the prefixes used
+%   when defining the command.  Here we ensure that the \tn{escapechar}
+%   is printable, otherwise a macro defined with prefixes
+%   |\protected \long| will have it \tn{meaning} printed as
+%   |protectedlong|, making life unnecessarily complicated.  Here the
+%   \tn{escapechar} is changed to |/|, then we loop between pairs of
+%   |/|\ldots|/| extracting the prefixes.
+%    \begin{macrocode}
+    \group_begin:
+      \int_set:Nn \tex_escapechar:D { `\/ }
+      \use:x
+        {
+    \group_end:
+    \tl_set:Nx \exp_not:N \l_@@_patch_prefixes_tl
+      { \exp_not:N \@@_make_prefixes:w \cs_prefix_spec:N #2 / / }
+        }
+%    \end{macrocode}
+%   Now that all the needed tools are ready, without further ado we'll
+%   redefine the command |#2|.  The definition uses the prefixes
+%   gathered in \cs{l_@@_patch_prefixes_tl}, a primitive \cs{tex_def:D}
+%   to avoid adding extra prefixes, and the \meta{parameter text} from
+%   \cs{l_@@_patch_param_text_tl}.
+%
+%   Then finally, in the body of the definition, we insert
+%   \hook{cmd/\#3/before} if |#4| is |before|, the code of the
+%   command expanded once grabbing the parameters in
+%   \cs{l_@@_patch_replacement_tl}, and \hook{cmd/\#3/after} if |#4| is
+%   |after|.
+%    \begin{macrocode}
+    \use:x
+      {
+        \l_@@_patch_prefixes_tl \tex_def:D
+            \exp_not:N #2 \exp_not:V \l_@@_patch_param_text_tl
+          {
+            \str_if_eq:nnT {#4} { before }
+              { \exp_not:N \UseHook { cmd / #3 / #4 } }
+            \exp_args:No \exp_not:o
+              { \exp_after:wN #2 \l_@@_patch_replacement_tl }
+            \str_if_eq:nnT {#4} { after }
+              { \exp_not:N \UseHook { cmd / #3 / #4 } }
+          }
+      }
+  }
+%    \end{macrocode}
+%
+%   Here's the auxiliary that makes the prefix control sequences for the
+%   redefinition.  Each item has to be \cs{tl_trim_spaces:n}'d because
+%   the last item (and not any other) has a trailing space.
+%    \begin{macrocode}
+\cs_new:Npn \@@_make_prefixes:w / #1 /
+  {
+    \tl_if_empty:nF {#1}
+      {
+        \exp_not:c { tex_ \tl_trim_spaces:n {#1} :D }
+        \@@_make_prefixes:w /
+      }
+  }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+%
+%
+%
+%
+%
+% \subsubsection{Patching by retokenization}
+%
+% At this point we've drained the possibilities of patching a command by
+% expansion-and-redefinition, so we have to resort to patching by
+% retokenizing the command.  Patching by retokenization is done by
+% getting the \tn{meaning} of the command, doing the necessary
+% manipulations on the generated string, and the retokenizing that again
+% by using \tn{scantokens}.
+%
+% Patching by retokenization is definitely a riskier business, because
+% it relies that the tokens printed by \tn{meaning} produce the exact
+% same tokens as the ones in the original definition.  That is, the
+% catcode régime must be exactly(ish) the same, and there is no way of
+% telling except by trial and error.
+%
+% \begin{macro}{\@@_retokenize_patch:Nnn}
+%   This is the macro that will control the whole process.  First we'll
+%   try out one final, rather trivial case, of a command with no
+%   arguments;  that is, a token list.  This case can be patched with
+%   the expand-and-redefine routine but it has to be the very last case
+%   tested for, because most (all?) robust commands start with a
+%   top-level macro with no arguments, so testing this first would
+%   short-circuit \tn{robust@command@act} and the top-level macros would
+%   be incorrectly patched.  In that case, we just check if the
+%   \cs{cs_argument_spec:N} is empty, and call
+%   \cs{@@_patch_expand_redefine:NNnn}.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_retokenize_patch:Nnn #1 #2 #3
+  {
+    \@@_patch_debug:x { ..~command~can~only~be~patched~by~rescanning }
+    \str_if_eq:eeTF { \cs_argument_spec:N #1 } { }
+      { \@@_patch_expand_redefine:NNnn \c_false_bool #1 {#2} {#3} }
+      {
+%    \end{macrocode}
+%
+%   Otherwise, we start the actual patching by retokenization job.  The
+%   code calls \cs{@@_try_patch_with_catcodes:Nnnnw} with a different
+%   catcode setting:
+%   \begin{itemize}
+%     \item The current catcode setting;
+%     \item Switching the catcode of |@|;
+%     \item Switching the \pkg{expl3} syntax on or off;
+%     \item Both of the above.
+%   \end{itemize}
+%
+%   If patching succeeds, \cs{@@_try_patch_with_catcodes:Nnnnw} has the
+%   side-effect of patching the macro |#1| (which may be an internal
+%   from the command whose name is~|#2|).
+%    \begin{macrocode}
+        \tl_set:Nx \l_@@_tmpa_tl
+          {
+            \int_compare:nNnTF { \char_value_catcode:n {`\@ } } = { 12 }
+              { \exp_not:N \makeatletter } { \exp_not:N \makeatother }
+          }
+        \tl_set:Nx \l_@@_tmpb_tl
+          {
+            \bool_if:NTF \l__kernel_expl_bool
+              { \ExplSyntaxOff } { \ExplSyntaxOn }
+          }
+        \use:x
+          {
+            \exp_not:N \@@_try_patch_with_catcodes:Nnnnw
+                \exp_not:n { #1 {#2} {#3} }
+              { \prg_do_nothing: }
+              { \exp_not:V \l_@@_tmpa_tl } % @
+              { \exp_not:V \l_@@_tmpb_tl } % _:
+              {
+                \exp_not:V \l_@@_tmpa_tl   % @
+                \exp_not:V \l_@@_tmpb_tl   % _:
+              }
+          }
+              \q_recursion_tail \q_recursion_stop
+%    \end{macrocode}
+%
+%   If no catcode setting succeeds, give up and raise an error.  The
+%   command isn't changed in any way in that case.
+%    \begin{macrocode}
+          {
+            \__kernel_msg_error:nnxx { hooks } { cant-patch }
+              { \c_backslash_str #2 } { retok }
+          }
+      }
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+%
+%
+% \begin{macro}{\@@_try_patch_with_catcodes:Nnnnw}
+%   This function is a simple wrapper around
+%   \cs{@@_cmd_if_scanable:NnTF} and \cs{@@_patch_retokenize:Nnnn} if
+%   the former returns \meta{true}, plus some debug messages.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_try_patch_with_catcodes:Nnnnw #1 #2 #3 #4
+  {
+    \quark_if_recursion_tail_stop_do:nn {#4} { \use:n }
+    \@@_patch_debug:x { ++~trying~to~patch~by~retokenization }
+    \@@_cmd_if_scanable:NnTF {#1} {#4}
+      {
+        \@@_patch_debug:x { ++~macro~can~be~retokenized~cleanly }
+        \@@_patch_debug:x { ==~retokenizing~macro~now }
+        \@@_patch_retokenize:Nnnn #1 {#2} {#3} {#4}
+        \use_i_delimit_by_q_recursion_stop:nw \use_none:n
+      }
+      {
+        \@@_patch_debug:x { --~macro~cannot~be~retokenized~cleanly }
+        \@@_try_patch_with_catcodes:Nnnnw #1 {#2} {#3}
+      }
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+%
+%
+%
+% \begin{macro}[int]{\kerneltmpDoNotUse}
+%   This is an oddity required to be safe (as safe as reasonably
+%   possible) when patching the command.  The entirety of
+%   \begin{quote}
+%     \meta{prefixes} \tn{def} \meta{cs} \meta{parameter text}
+%       |{|\meta{replacement text}|}|
+%   \end{quote}
+%   will go through \tn{scantokens}.  The \meta{parameter text} and
+%   \meta{replacement text} are what we are trying to retokenize, so not
+%   much worry there.  The other items, however, should ``just work'',
+%   so some care is needed to not use too fancy catcode settings.
+%   Therefore we can't use an \pkg{expl3}-named macro for \meta{cs}, nor
+%   the \pkg{expl3} versions of \tn{def} or the \meta{prefixes}.
+%   That is why the definitions that will eventually go into
+%   \tn{scantokens} will use the oddly (but hopefully clearly)-named
+%   \cs{kerneltmpDoNotUse}:
+%    \begin{macrocode}
+\cs_new_eq:NN \kerneltmpDoNotUse !
+%    \end{macrocode}
+%   \phoinline{Maybe this can be avoided by running the \meta{parameter text}
+%     and the \meta{replacement text} separately through \tn{scantokens}
+%     and then putting everything together at the end.}
+% \end{macro}
+%
+%
+%
+% \begin{macro}{\@@_patch_required_catcodes:}
+%   Here are the catcode settings that are \emph{mandatory} when
+%   retokenizing commands.  These are the minimum necessary settings to
+%   perform the definitions:  they identify control sequences, which
+%   must be escaped with |\|$_0$, delimit the definition with |{|$_1$
+%   and |}|$_2$, and mark parameters with |#|$_6$.  Everything else may
+%   be changed, but not these.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_patch_required_catcodes:
+  {
+    \char_set_catcode_escape:N \\
+    \char_set_catcode_group_begin:N \{
+    \char_set_catcode_group_end:N \}
+    \char_set_catcode_parameter:N \#
+    % \int_set:Nn \tex_endlinechar:D { -1 }
+    % \int_set:Nn \tex_newlinechar:D { -1 }
+  }
+%    \end{macrocode}
+%   \phoinline{\pkg{etoolbox} sets the \tn{endlinechar} and \tn{newlinechar}
+%     when patching, but as far as I tested these didn't make much of
+%     a difference, so I left them out for now.  Maybe
+%     \tn{newlinechar}|=-1| avoids a space token being added after the
+%     definition.}
+%   \phoinline{If the patching is split by \meta{parameter text} and
+%     \meta{replacement text}, then only \# will have to stay in that
+%     list.}
+%   \phoinline{Actually now that we patch
+%     \texttt{\cs{UseHook}\{cmd/foo/before\}}, all the tokens there need
+%     to have the right catcodes, so this list now includes all
+%     lowercase letters, U and H, the slash, and whatever characters in
+%     the command name\ldots sigh\ldots}
+% \end{macro}
+%
+%
+%
+%
+% \begin{macro}[TF]{\@@_cmd_if_scanable:Nn}
+%   Here we'll do a quick test if the command being patched can in fact
+%   be retokenized with the specific catcode setting without changing
+%   in meaning.  The test is straightforward:
+%   \begin{enumerate}
+%     \item apply \tn{meaning} to the command;
+%     \item split the \meta{prefixes}, \meta{parameter text} and
+%       \meta{replacement text} and arrange them as
+%       \begin{quote}
+%         \meta{prefixes}\tn{def}\cs{kerneltmpDoNotUse}%^^A
+%           \meta{parameter text}|{|\meta{replacement text}|}|
+%       \end{quote}
+%     \item rescan that with the given catcode settings, and do
+%       the definition; then finally
+%     \item compare \cs{kerneltmpDoNotUse} with the original command.
+%   \end{enumerate}
+%   If both are \tn{ifx}-equal, the command can be safely patched.
+%    \begin{macrocode}
+\prg_new_protected_conditional:Npnn \@@_cmd_if_scanable:Nn #1 #2 { TF }
+  {
+    \cs_set_eq:NN \kerneltmpDoNotUse \scan_stop:
+    \cs_set_eq:NN \@@_tmp:w \scan_stop:
+    \use:x
+      {
+        \cs_set:Npn \@@_tmp:w
+            ####1 \tl_to_str:n { macro: } ####2 -> ####3 \s_@@_mark
+          { ####1 \def \kerneltmpDoNotUse ####2   {####3} }
+        \tl_set:Nx \exp_not:N \l_@@_tmpa_tl
+          { \exp_not:N \@@_tmp:w \token_to_meaning:N #1 \s_@@_mark }
+      }
+    \tl_rescan:nV { #2 \@@_patch_required_catcodes: } \l_@@_tmpa_tl
+    \token_if_eq_meaning:NNTF #1 \kerneltmpDoNotUse
+      { \prg_return_true: }
+      { \prg_return_false: }
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+%
+%
+% \begin{macro}{\@@_patch_retokenize:Nnnn}
+%   Then, if \cs{@@_cmd_if_scanable:NnTF} returned true, we can go on
+%   and patch the command.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_patch_retokenize:Nnnn #1 #2 #3 #4
+  {
+%    \end{macrocode}
+%   Start off by making some things \tn{relax} to avoid lots of
+%   \tn{noexpand} below.
+%    \begin{macrocode}
+    \cs_set_eq:NN \kerneltmpDoNotUse \scan_stop:
+    \cs_set_eq:NN \@@_tmp:w \scan_stop:
+    \use:x
+      {
+%    \end{macrocode}
+%   Now we'll define \cs{@@_tmp:w} such that it splits the \tn{meaning}
+%   of the macro (|#1|) into its three parts:
+%   \begin{enumerate}
+%     \def\makelabel#1{\texttt{\#\#\#\##1}}
+%     \item \meta{prefixes}
+%     \item \meta{parameter text}
+%     \item \meta{replacement text}
+%   \end{enumerate}
+%   and arrange that a complete definition, then place the |before|
+%   or |after| hooks around the \meta{replacement text}:
+%   accordingly.
+%    \begin{macrocode}
+        \cs_set:Npn \@@_tmp:w
+            ####1 \tl_to_str:n { macro: } ####2 -> ####3 \s_@@_mark
+          {
+            ####1 \def \kerneltmpDoNotUse ####2
+              {
+                \str_if_eq:nnT {#3} { before }
+                  { \token_to_str:N \UseHook { cmd / #2 / #3 } }
+                ####3
+                \str_if_eq:nnT {#3} { after }
+                  { \token_to_str:N \UseHook { cmd / #2 / #3 } }
+              }
+          }
+%    \end{macrocode}
+%   Now we just have to get the \tn{meaning} of the command being
+%   patched and pass it through the meat grinder above.
+%    \begin{macrocode}
+        \tl_set:Nx \exp_not:N \l_@@_tmpa_tl
+          { \exp_not:N \@@_tmp:w \token_to_meaning:N #1 \s_@@_mark }
+      }
+%    \end{macrocode}
+%   Now rescan with the given catcode settings (overridden by the
+%   \cs{@@_patch_required_catcodes:}), and implicitly (by using the
+%   rescanned token list) carry out the definition from above.
+%    \begin{macrocode}
+    \tl_rescan:nV { #4 \@@_patch_required_catcodes: } \l_@@_tmpa_tl
+%    \end{macrocode}
+%   And to close, copy the newly-defined command into the old name and
+%   the patching is finally completed:
+%    \begin{macrocode}
+    \cs_set_eq:NN #1 \kerneltmpDoNotUse
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% \subsection{Messages}
+%
+%    \begin{macrocode}
+\__kernel_msg_new:nnnn { hooks } { wrong-cmd-hook }
+  {
+    Command~hook~`cmd/#1/#2'~invalid.\\
+    The~hook~should~be~`cmd/#1/before'~or~`cmd/#1/after'.
+  }
+  {
+    You~tried~to~add~a~hook~to~command~\iow_char:N \\#1,~but~`#2'~
+    is~an~invalid~position.~Only~`before'~or~`after'~are~allowed.
+  }
+\__kernel_msg_new:nnnn { hooks } { cant-patch }
+  {
+    Command~`#1'~cannot~have~hooks~because~it~
+    \@@_unpatchable_cases:n {#2} .
+  }
+  {
+    You~tried~to~add~a~hook~to~`#1',~but~LaTeX~was~not~able~to~
+    add~the~hook~to~that~command~because~`#1'~
+    \@@_unpatchable_cases:n {#2} .
+  }
+\cs_new:Npn \@@_unpatchable_cases:n #1
+  {
+    \str_case:nn {#1}
+      {
+        { undef } { doesn't~exist }
+        { macro } { is~not~a~macro }
+        { expl3 } { is~a~private~expl3~macro }
+        { retok } { can't~be~retokenized~cleanly }
+      }
+  }
+%    \end{macrocode}
+%
+%
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{0000/00/00}{ltcmdhooks}%
+%<latexrelease>                 {The~hook~management~system~for~commands}
+%<latexrelease>
+%    \end{macrocode}
+%    The command \cs{@@_cmd_begindocument_code:} is used in an
+%    internal hook, so we need to make sure it has a harmless
+%    definition after rollback as that will not remove it from the
+%    kernel hook.
+%    \begin{macrocode}
+%<latexrelease>\cs_set_eq:NN \@@_cmd_begindocument_code: \prg_do_nothing:
+%<latexrelease>
+%<latexrelease>\EndModuleRelease
+\ExplSyntaxOff
+%</2ekernel|latexrelease>
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+%<@@=>
+%    \end{macrocode}
+%
+% \Finale
+%
-- 
cgit v1.2.3

