% \iffalse meta-comment % % Copyright (C) 2020-2024 % Frank Mittelbach, Phelype Oleinik, The LaTeX Project % % This file is part of the LaTeX base system. % ------------------------------------------- % % It 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 file has the LPPL maintenance status "maintained". % % The list of all files belonging to the LaTeX base distribution is % given in the file `manifest.txt'. See also `legal.txt' for additional % information. % % The list of derived (unpacked) files belonging to the distribution % and covered by LPPL is defined by the unpacking scripts (with % extension .ins) which are part of the distribution. % % \fi % % \iffalse %%% From File: lthooks.dtx % %<*driver> % \fi \ProvidesFile{lthooks.dtx} [2024/03/09 v1.1h LaTeX Kernel (hooks)] % \iffalse % \documentclass{l3doc} \GetFileInfo{lthooks.dtx} \providecommand\InternalDetectionOff{} \providecommand\InternalDetectionOn{} \EnableCrossrefs \CodelineIndex \begin{document} \DocInput{lthooks.dtx} \end{document} % % % \fi % % % \providecommand\hook[1]{\texttt{#1}} % % \providecommand\fmi[1]{\marginpar{\footnotesize FMi: #1}} % \providecommand\fmiinline[1]{\begin{quote}\itshape\footnotesize FMi: #1\end{quote}} % \providecommand\pho[1]{\marginpar{\footnotesize PhO: #1}} % \providecommand\phoinline[1]{\begin{quote}\itshape\footnotesize PhO: #1\end{quote}} % % % % \title{\LaTeX{}'s hook management\thanks{This module has version % \fileversion\ dated \filedate, \copyright\ \LaTeX\ % Project.}} % % \author{Frank Mittelbach\thanks{Code improvements for speed and other goodies by Phelype Oleinik}} % % \maketitle % % % \tableofcontents % % \section{Introduction} % % Hooks are points in the code of commands or environments where it % is possible to add processing code into existing commands. This % can be done by different packages that do not know about each % other and to allow for hopefully safe processing it is necessary % to sort different chunks of code added by different packages into % a suitable processing order. % % This is done by the packages adding chunks of code (via % \cs{AddToHook}) and labeling their code with some label by % default using the package name as a label. % % At \verb=\begin{document}= all code for a hook is then sorted % according to some rules (given by \cs{DeclareHookRule}) for fast % execution without processing overhead. If the hook code is % modified afterwards (or the rules are changed), % a new version for fast processing is generated. % % Some hooks are used already in the preamble of the document. If % that happens then the hook is prepared for execution (and sorted) % already at that point. % % % \section{Package writer interface} % % The hook management system is offered as a set of CamelCase % commands for traditional \LaTeXe{} packages (and for use in the % document preamble if needed) as well as \texttt{expl3} commands % for modern packages, that use the L3 programming layer of % \LaTeX{}. Behind the scenes, a single set of data structures is % accessed so that packages from both worlds can coexist and access % hooks in other packages. % % % % \subsection{\LaTeXe\ interfaces} % % \subsubsection{Declaring hooks} % % With a few exceptions, hooks have to be declared before they can % be used. The exceptions are the generic hooks for commands and % environments (executed at \cs{begin} and \cs{end}), and the % hooks run when loading files (see section~\ref{sec:generic}). % % \begin{function}{\NewHook} % \begin{syntax} % \cs{NewHook} \Arg{hook} % \end{syntax} % Creates a new \meta{hook}. % If this hook is declared within a package it is suggested % that its name is always structured as follows: % \meta{package-name}\texttt{/}\meta{hook-name}. If necessary you % can further subdivide the name by adding more \texttt{/} parts. % If a hook name is already taken, an error is raised and the hook % is not created. % % The \meta{hook} can be specified using the dot-syntax to denote % the current package name. See section~\ref{sec:default-label}. % \end{function} % % \begin{function}{\NewReversedHook} % \begin{syntax} % \cs{NewReversedHook} \Arg{hook} % \end{syntax} % Like \cs{NewHook} declares a new \meta{hook}. % the difference is that the code chunks for this hook are in % reverse order by default (those added last are executed first). % Any rules for the hook are applied after the default ordering. % See sections~\ref{sec:order} and \ref{sec:reversed-order} % for further details. % % The \meta{hook} can be specified using the dot-syntax to denote % the current package name. See section~\ref{sec:default-label}. % \end{function} % % \begin{function}{\NewMirroredHookPair} % \begin{syntax} % \cs{NewMirroredHookPair} \Arg{hook-1} \Arg{hook-2} % \end{syntax} % A shorthand for % \cs{NewHook}\Arg{hook-1}\cs{NewReversedHook}\Arg{hook-2}. % % The \meta{hook} can be specified using the dot-syntax to denote % the current package name. See section~\ref{sec:default-label}. % \end{function} % % % \begin{function}[added=2023-06-01]{\NewHookWithArguments} % \begin{syntax} % \cs{NewHookWithArguments} \Arg{hook} \Arg{number} % \end{syntax} % Creates a new \meta{hook} whose code takes \meta{number} arguments, % and otherwise works exactly like \cs{NewHook}. % Section~\ref{sec:hook-args} explains hooks with arguments. % % The \meta{hook} can be specified using the dot-syntax to denote % the current package name. See section~\ref{sec:default-label}. % \end{function} % % \begin{function}[added=2023-06-01]{\NewReversedHookWithArguments} % \begin{syntax} % \cs{NewReversedHookWithArguments} \Arg{hook} \Arg{number} % \end{syntax} % Like \cs{NewReversedHook}, but creates a hook whose code takes % \meta{number} arguments. % Section~\ref{sec:hook-args} explains hooks with arguments. % % The \meta{hook} can be specified using the dot-syntax to denote % the current package name. See section~\ref{sec:default-label}. % \end{function} % % \begin{function}[added=2023-06-01]{\NewMirroredHookPairWithArguments} % \begin{syntax} % \cs{NewMirroredHookPairWithArguments} \Arg{hook-1} \Arg{hook-2} \Arg{number} % \end{syntax} % A shorthand for % \cs{NewHookWithArguments}\Arg{hook-1}\Arg{number}\\ % \cs{NewReversedHookWithArguments}\Arg{hook-2}\Arg{number}. % Section~\ref{sec:hook-args} explains hooks with arguments. % % The \meta{hook} can be specified using the dot-syntax to denote % the current package name. See section~\ref{sec:default-label}. % \end{function} % % % \subsubsection{Special declarations for generic hooks} % % The declarations here should normally not be used. They are available % to provide support for special use cases mainly involving % generic command hooks. % % \changes{v1.0p}{2021/08/20}{Documentation updates for generic hook commands (gh/638)} % % \begin{function}{\DisableGenericHook} % \begin{syntax} % \cs{DisableGenericHook} \Arg{hook} % \end{syntax} % After this declaration\footnotemark{} the \meta{hook} is no longer % usable: Any further attempt to add code to it will result in an % error and any use, e.g., via \cs{UseHook}, will simply do nothing. % % This is intended to be used with generic command hooks (see % \texttt{ltcmdhooks-doc}) as depending on the definition of the % command such generic hooks may be unusable. If that is known, a % package developer can disable such hooks up front. % % The \meta{hook} can be specified using the dot-syntax to denote % the current package name. See section~\ref{sec:default-label}. % \end{function}\footnotetext{In the 2020/06 release this command was % called \cs{DisableHook}, but that name was misleading as it % shouldn't be used to disable non-generic hooks.} % % % \begin{function}{\ActivateGenericHook} % \begin{syntax} % \cs{ActivateGenericHook} \Arg{hook} % \end{syntax} % This declaration activates a generic hook provided by a package/class % (e.g., one used in code with \cs{UseHook} or % \cs{UseOneTimeHook}) without it being explicitly declared with % \cs{NewHook}). % If the hook is already activated, this command does nothing. % % Note that this command does not undo the effect of \cs{DisableGenericHook}. % See section~\ref{sec:generic-hooks} for a discussion of when this % declaration is appropriate. % \end{function} % % % % % % \subsubsection{Using hooks in code} % % % \begin{function}{\UseHook} % \begin{syntax} % \cs{UseHook} \Arg{hook} % \end{syntax} % Execute the code stored in the \meta{hook}. % % Before \verb=\begin{document}= the fast execution code for a hook % is not set up, so in order to use a hook there it is explicitly % initialized first. As that involves assignments using a hook at % those times is not 100\% the same as using it after % \verb=\begin{document}=. % % The \meta{hook} \emph{cannot} be specified using the dot-syntax. % A leading |.| is treated literally. % \end{function} % % \begin{function}[added=2023-06-01]{\UseHookWithArguments} % \begin{syntax} % \cs{UseHookWithArguments} \Arg{hook} \Arg{number} \Arg{arg_1} \ldots \Arg{arg_n} % \end{syntax} % Execute the code stored in the \meta{hook} and pass the arguments % \Arg{arg_1} through \Arg{arg_n} to the \meta{hook}. Otherwise, it % works exactly like \cs{UseHook}. % The \meta{number} should be the number of arguments declared for % the hook. If the hook is not declared, this command does nothing % and it will remove \meta{number} items from the input. % Section~\ref{sec:hook-args} explains hooks with arguments. % % The \meta{hook} \emph{cannot} be specified using the dot-syntax. % A leading |.| is treated literally. % \end{function} % % \begin{function}{\UseOneTimeHook} % \begin{syntax} % \cs{UseOneTimeHook} \Arg{hook} % \end{syntax} % Some hooks are only used (and can be only used) in one place, for % example, those in \verb=\begin{document}= or % \verb=\end{document}=. From that point onwards, adding to the hook % through a defined \cs[no-index]{\meta{addto-cmd}} command (e.g., % \cs{AddToHook} or \cs{AtBeginDocument}, etc.\@) would have no % effect (as would the use of such a command inside the hook code % itself). It is therefore customary to redefine % \cs{\meta{addto-cmd}} to simply process its argument, i.e., % essentially make it behave like \cs{@firstofone}. % % \cs{UseOneTimeHook} does that: it records that the hook has been % consumed and any further attempt to add to it will result in % executing the code to be added immediately. % % Using \cs{UseOneTimeHook} several times with the same % \Arg{hook} means that it only executes the first time it is used. % For example, if it is used in a command that can be called several times % then the hook executes during only the \emph{first} invocation of that % command; this allows its use as an \enquote{initialization hook}. % % Mixing \cs{UseHook} and \cs{UseOneTimeHook} for the same % \Arg{hook} should be avoided, but if this is done then neither will execute % after the first \cs{UseOneTimeHook}. % % The \meta{hook} \emph{cannot} be specified using the dot-syntax. % A leading |.| is treated literally. See % section~\ref{sec:default-label} for details. % % \end{function} % % \begin{function}[added=2023-06-01]{\UseOneTimeHookWithArguments} % \begin{syntax} % \cs{UseOneTimeHookWithArguments} \Arg{hook} \Arg{number} \Arg{arg_1} \ldots \Arg{arg_n} % \end{syntax} % Works exactly like \cs{UseOneTimeHook}, but passes arguments % \Arg{arg_1} through \Arg{arg_n} to the \meta{hook}. % The \meta{number} should be the number of arguments declared for % the hook. If the hook is not declared, this command does nothing % and it will remove \meta{number} items from the input. % % It should be noted that after a one-time hook is used, it is no % longer possible to use \cs{AddToHookWithArguments} or similar with % that hook. \cs{AddToHook} continues to work as normal. % Section~\ref{sec:hook-args} explains hooks with arguments. % % The \meta{hook} \emph{cannot} be specified using the dot-syntax. % A leading |.| is treated literally. See % section~\ref{sec:default-label} for details. % % \end{function} % % % \subsubsection{Updating code for hooks} % % \begin{function}{\AddToHook} % \begin{syntax} % \cs{AddToHook} \Arg{hook} \oarg{label} \Arg{code} % \end{syntax} % Adds \meta{code} to the \meta{hook} labeled by \meta{label}. % When the optional argument \meta{label} is not provided, the % \meta{default label} is used (see section~\ref{sec:default-label}). % If \cs{AddToHook} is used in a package/class, the % \meta{default label} is the package/class name, otherwise it is % \hook{top-level} (the \hook{top-level} label is treated % differently: see section~\ref{sec:top-level}). % % If there already exists code under the \meta{label} then the new % \meta{code} is appended to the existing one (even if this is a reversed hook). % If you want to replace existing code under the % \meta{label}, first apply \cs{RemoveFromHook}. % % The hook doesn't have to exist for code to be added to % it. However, if it is not declared, then obviously the % added \meta{code} will never be executed. This % allows for hooks to work regardless of package loading order and % enables packages to add to hooks from other packages without % worrying whether they are actually used in the current document. % See section~\ref{sec:querying}. % % The \meta{hook} and \meta{label} can be specified using the % dot-syntax to denote the current package name. % See section~\ref{sec:default-label}. % \end{function} % % \begin{function}[added=2023-06-01]{\AddToHookWithArguments} % \begin{syntax} % \cs{AddToHookWithArguments} \Arg{hook} \oarg{label} \Arg{code} % \end{syntax} % Works exactly like \cs{AddToHook}, except that the \meta{code} can % access the arguments passed to the hook using \verb|#1|, \verb|#2|, % \ldots, \verb|#n| (up to the number of arguments declared for the % hook). If the \meta{code} should contain \emph{parameter tokens} % (\verb|#|) that are not supposed to be understood as the arguments % of the hook, such tokens should be doubled. For example, with % \cs{AddToHook} one can write: %\begin{verbatim} % \AddToHook{myhook}{\def\foo#1{Hello, #1!}} %\end{verbatim} % but to achieve the same with \cs{AddToHookWithArguments}, one should % write: %\begin{verbatim} % \AddToHookWithArguments{myhook}{\def\foo##1{Hello, ##1!}} %\end{verbatim} % because in the latter case, \verb|#1| refers to the first argument % of the hook \hook{myhook}. % Section~\ref{sec:hook-args} explains hooks with arguments. % % The \meta{hook} and \meta{label} can be specified using the % dot-syntax to denote the current package name. % See section~\ref{sec:default-label}. % \end{function} % % \begin{function}{\RemoveFromHook} % \begin{syntax} % \cs{RemoveFromHook} \Arg{hook} \oarg{label} % \end{syntax} % Removes any code labeled by \meta{label} from the \meta{hook}. % When the optional argument \meta{label} is not provided, the % \meta{default label} is used (see section~\ref{sec:default-label}). % % If there is no code under the \meta{label} in the \meta{hook}, % or if the \meta{hook} does not exist, a warning is issued when % you attempt to \cs{RemoveFromHook}, and the command is ignored. % \cs{RemoveFromHook} should be used only when you know exactly what % labels are in a hook. Typically this will be when some code gets added to a hook % by a package, then later this code is removed by that same package. % If you want to prevent the execution of code from another % package, use the |voids| rule instead (see section~\ref{sec:rules}). % % If the optional \meta{label} argument is \texttt{*}, then all code chunks are % removed. This is rather dangerous as it may well drop code from other % packages (that one may not know about); it should therefore not be used % in packages but only in document preambles! % % The \meta{hook} and \meta{label} can be specified using the % dot-syntax to denote the current package name. % See section~\ref{sec:default-label}. % \end{function} % % \medskip % % In contrast to the \texttt{voids} relationship between two labels % in a \cs{DeclareHookRule} this is a destructive operation as the % labeled code is removed from the hook data structure, whereas the % relationship setting can be undone by providing a different % relationship later. % % A useful application for this declaration inside the document body % is when one wants to temporarily add code to hooks and later remove % it again, e.g., %\begin{verbatim} % \AddToHook{env/quote/before}{\small} % \begin{quote} % A quote set in a smaller typeface % \end{quote} % ... % \RemoveFromHook{env/quote/before} % ... now back to normal for further quotes %\end{verbatim} % Note that you can't cancel the setting with %\begin{verbatim} % \AddToHook{env/quote/before}{} %\end{verbatim} % because that only \enquote{adds} a further empty chunk of code to % the hook. Adding \cs{normalsize} would work but that means the hook % then contained \cs{small}\cs{normalsize} which means two font size % changes for no good reason. % % The above is only needed if one wants to typeset several quotes in a % smaller typeface. If the hook is only needed once then % \cs{AddToHookNext} is simpler, because it resets itself after one use. % % % \begin{function}{\AddToHookNext} % \begin{syntax} % \cs{AddToHookNext} \Arg{hook} \Arg{code} % \end{syntax} % Adds \meta{code} to the next invocation of the \meta{hook}. % The code is executed after the normal hook code has finished and % it is executed only once, i.e. it is deleted after it was used. % % Using this declaration is a global operation, i.e., the code is % not lost even if the declaration is used inside a group and the % next invocation of the hook happens after the end of that group. % If the declaration is % used several times before the hook is executed then all code is % executed in the order in which it was declared.\footnotemark % % If this declaration is used with a one-time hook then the code % is only ever used if the declaration comes before the hook’s % invocation. This is because, in contrast % to \cs{AddToHook}, the code in this declaration is not % executed immediately in the case when the invocation of the hook % has already happened---in other words, this code will truly execute % only on the next invocation of the hook (and in the case of a % one-time hook there is no such \enquote{next invocation}). % This gives you a choice: should my code execute % always, or should it execute only at the point where the % one-time hook is used (and not at all if this is impossible)? For % both of these possibilities there are use cases. % % It is possible to nest this declaration using the same hook (or % different hooks): e.g., % \begin{quote} % \cs{AddToHookNext}\Arg{hook}\verb={=\meta{code-1}^^A % \cs{AddToHookNext}\Arg{hook}\Arg{code-2}\verb=}= % \end{quote} % will execute \meta{code-1} next time the \meta{hook} is used and at % that point puts \meta{code-2} into the \meta{hook} so that it gets % executed on following time the hook is run. % % A hook doesn't have to exist for code to be added to it. This % allows for hooks to work regardless of package loading % order. % See section~\ref{sec:querying}. % % The \meta{hook} can be specified using the dot-syntax to denote % the current package name. See section~\ref{sec:default-label}. % \end{function}\footnotetext{There is % no mechanism to reorder such code chunks (or delete them).} % % \begin{function}[added=2023-06-01]{\AddToHookNextWithArguments} % \begin{syntax} % \cs{AddToHookNextWithArguments} \Arg{hook} \Arg{code} % \end{syntax} % Works exactly like \cs{AddToHookNext}, but the \meta{code} can % contain references to the arguments of the \meta{hook} as described % for \cs{AddToHookWithArguments} above. % Section~\ref{sec:hook-args} explains hooks with arguments. % % The \meta{hook} can be specified using the dot-syntax to denote % the current package name. See section~\ref{sec:default-label}. % \end{function} % % % % \begin{function}{\ClearHookNext} % \begin{syntax} % \cs{ClearHookNext} \Arg{hook} % \end{syntax} % Normally \cs{AddToHookNext} is only used when you know precisely % where it will apply and why you want some extra code at that % point. However, there are a few use cases in which such a % declaration needs to be canceled, for example, when % discarding a page with \cs{DiscardShipoutBox} (but even then not % always), and in such situations \cs{ClearHookNext} can be % used. % \end{function} % % % % % % \subsubsection{Hook names and default labels} % \label{sec:default-label} % % It is best practice to use \cs{AddToHook} in packages or classes % \emph{without specifying a \meta{label}} because then the package % or class name is automatically used, which is helpful if rules are % needed, and avoids mistyping the \meta{label}. % % Using an explicit \meta{label} is only necessary in very specific % situations, e.g., if you want to add several chunks of code into a % single hook and have them placed in different parts of the hook % (by providing some rules). % % The other case is when you develop a larger package with several % sub-packages. In that case you may want to use the same % \meta{label} throughout the sub-packages in order to avoid % that the labels change if you internally reorganize your code. % % Except for \cs{UseHook}, \cs{UseOneTimeHook} and \cs{IfHookEmptyTF} % (and their \pkg{expl3} interfaces \cs{hook_use:n}, % \cs{hook_use_once:n} and \cs{hook_if_empty:nTF}), all \meta{hook} % and \meta{label} arguments are processed in the same way: first, % spaces are trimmed around the argument, then it is fully expanded % until only character tokens remain. If the full expansion of the % \meta{hook} or \meta{label} contains a non-expandable non-character % token, a low-level \TeX{} error is raised (namely, the \meta{hook} is % expanded using \TeX's \cs{csname}\ldots\cs{endcsname}, as such, % Unicode characters are allowed in \meta{hook} and \meta{label} % arguments). The arguments of \cs{UseHook}, \cs{UseOneTimeHook}, and % \cs{IfHookEmptyTF} are % processed much in the same way except that spaces are not trimmed % around the argument, for better performance. % % It is not enforced, but highly recommended that the hooks defined by % a package, and the \meta{labels} used to add code to other hooks % contain the package name to easily identify the source of the code % chunk and to prevent clashes. This should be the standard practice, % so this hook management code provides a shortcut to refer to the % current package in the name of a \meta{hook} and in a \meta{label}. % If the \meta{hook} name or the \meta{label} consist just of a single dot % (|.|), or starts with a dot followed by a slash (|./|) then the dot % denotes the \meta{default label} (usually the current package or class % name---see~\cs{SetDefaultHookLabel}). % A \enquote{|.|} or \enquote{|./|} anywhere else in a \meta{hook} or in % \meta{label} is treated literally and is not replaced. % % For example, % inside the package \texttt{mypackage.sty}, the default label is % \texttt{mypackage}, so the instructions: % \begin{verbatim} % \NewHook {./hook} % \AddToHook {./hook}[.]{code} % Same as \AddToHook{./hook}{code} % \AddToHook {./hook}[./sub]{code} % \DeclareHookRule{begindocument}{.}{before}{babel} % \AddToHook {file/foo.tex/after}{code} % \end{verbatim} % are equivalent to: % \begin{verbatim} % \NewHook {mypackage/hook} % \AddToHook {mypackage/hook}[mypackage]{code} % \AddToHook {mypackage/hook}[mypackage/sub]{code} % \DeclareHookRule{begindocument}{mypackage}{before}{babel} % \AddToHook {file/foo.tex/after}{code} % unchanged % \end{verbatim} % % The \meta{default label} is automatically set equal to the name of the % current package or class at the time the package is loaded. If the % hook command is used outside of a package, or the current file wasn't % loaded with \cs{usepackage} or \cs{documentclass}, then the % \texttt{top-level} is used as the \meta{default label}. This may have % exceptions---see \cs{PushDefaultHookLabel}. % % This syntax is available in all \meta{label} arguments and most % \meta{hook} arguments, both in the \LaTeXe{} interface, and the \LaTeX3 % interface described in section~\ref{sec:l3hook-interface}. % % Note,\marginpar{\raggedleft\rightskip5pt\itshape \textbf{Important:}\break The dot-syntax % is \textbf{not} available with % \cs{UseHook} and some other commands that are typically used within code!} % however, that the replacement of |.| by the \meta{default label} % takes place when the hook command is executed, so actions that are % somehow executed after the package ends will have the wrong % \meta{default label} if the dot-syntax is used. For that reason, % this syntax is not available in \cs{UseHook} (and \cs{hook_use:n}) % because the hook is most of the time used outside of the package file % in which it was defined. This syntax is also not available in the hook % conditionals \cs{IfHookEmptyTF} (and \cs{hook_if_empty:nTF}), because these % conditionals are used in some performance-critical parts of the hook % management code, and because they are usually used to refer to other % package's hooks, so the dot-syntax doesn't make much sense. % % In some cases, for example in large packages, one may want to separate % the code in logical parts, but still use the main package name as the % \meta{label}, then the \meta{default label} can be set using % \cs{PushDefaultHookLabel}\verb={...}=\,\ldots\cs{PopDefaultHookLabel} % or \cs{SetDefaultHookLabel}\verb={...}=. % % \begin{function}{\PushDefaultHookLabel,\PopDefaultHookLabel} % \begin{syntax} % \cs{PushDefaultHookLabel} \Arg{default label} % \quad \meta{code} % \cs{PopDefaultHookLabel} % \end{syntax} % \cs{PushDefaultHookLabel} sets the current \meta{default label} to % be used in \meta{label} arguments, or when replacing a leading % ``|.|'' (see above). \cs{PopDefaultHookLabel} reverts the % \meta{default label} to its previous value. % % Inside a package or class, the \meta{default label} is equal to the % package or class name, unless explicitly changed. Everywhere else, % the \meta{default label} is |top-level| (see % section~\ref{sec:top-level}) unless explicitly changed. % % The effect of \cs{PushDefaultHookLabel} holds until the next % \cs{PopDefaultHookLabel}. \cs{usepackage} (and \cs{RequirePackage} % and \cs{documentclass}) internally use % \begin{quote} % \cs{PushDefaultHookLabel}\Arg{package name} \\ % \null \quad \meta{package code} \\ % \cs{PopDefaultHookLabel} % \end{quote} % to set the \meta{default label} for the package or class file. % Inside the \meta{package code} the \meta{default label} can also be % changed with \cs{SetDefaultHookLabel}. \cs{input} and other % file input-related commands from the \LaTeX{} kernel do not use % \cs{PushDefaultHookLabel}, so code within files loaded by these % commands does \emph{not} get a dedicated \meta{label}! (that is, the % \meta{default label} is the current active one when the file was % loaded.) % % Packages that provide their own package-like interfaces % (Ti\textit{k}Z's \cs{usetikzlibrary}, for example) can use % \cs{PushDefaultHookLabel} and \cs{PopDefaultHookLabel} to set % dedicated labels and to emulate \cs{usepackage}-like hook behavior % within those contexts. % % The |top-level| label is treated differently, and is reserved to the % user document, so it is not allowed to change the % \meta{default label} to |top-level|. % \end{function} % % \begin{function}{\SetDefaultHookLabel} % \begin{syntax} % \cs{SetDefaultHookLabel} \Arg{default label} % \end{syntax} % Similarly to \cs{PushDefaultHookLabel}, % sets the current \meta{default label} to % be used in \meta{label} arguments, or when replacing a leading % ``|.|''. The effect holds until the label is changed again or until % the next \cs{PopDefaultHookLabel}. The difference between % \cs{PushDefaultHookLabel} and \cs{SetDefaultHookLabel} is that the % latter does not save the current \meta{default label}. % % This command is useful when a large package is composed of several % smaller packages, but all should have the same \meta{label}, so % \cs{SetDefaultHookLabel} can be used at the beginning of each % package file to set the correct label. % % \cs{SetDefaultHookLabel} is not allowed in the main document, where % the \meta{default label} is |top-level| and there is no % \cs{PopDefaultHookLabel} to end its effect. % It is also not allowed to change the \meta{default label} to % |top-level|. % \end{function} % % \subsubsection{The \texttt{top-level} label} % \label{sec:top-level} % % The |top-level| label, assigned to code added from the main document, % is different from other labels. Code added to hooks (usually % \cs{AtBeginDocument}) in the preamble is almost always to change % something defined by a package, so it should go at the very end of the % hook. % % Therefore, code added in the |top-level| is always executed at the end % of the hook, regardless of where it was declared. If the hook is % reversed (see \cs{NewReversedHook}), the |top-level| chunk is executed % at the very beginning instead. % % Rules regarding |top-level| have no effect: if a user wants to have a % specific set of rules for a code chunk, they should use a different % label to said code chunk, and provide a rule for that label instead. % % The |top-level| label is exclusive for the user, so trying to add code % with that label from a package results in an error. % % \subsubsection{Defining relations between hook code} % \label{sec:rules} % % The default assumption is that code added to hooks by different % packages are independent and the order in which they are executed is % irrelevant. While this is true in many cases it is obviously false % in others. % % Before the hook management system was introduced % packages had to take elaborate precaution to determine of some other % package got loaded as well (before or after) and find some ways to % alter its behavior accordingly. In addition is was often the user's % responsibility to load packages in the right order so that code % added to hooks got added in the right order and some cases even % altering the loading order wouldn't resolve the conflicts. % % With the new hook management system it is now possible to define % rules (i.e., relationships) between code chunks added by different % packages and explicitly describe in which order they should be % processed. % % \begin{function}{\DeclareHookRule} % \begin{syntax} % \cs{DeclareHookRule} \Arg{hook} \Arg{label1} \Arg{relation} \Arg{label2} % \end{syntax} % Defines a relation between \meta{label1} and \meta{label2} for a % given \meta{hook}. If \meta{hook} is \texttt{??} this defines a default % relation for all hooks that use the two labels, i.e., that have % chunks of code labeled with \meta{label1} and \meta{label2}. % Rules specific to a given hook take precedence over default % rules that use \texttt{??} as the \meta{hook}. % % Currently, the supported relations are the following: % \begin{itemize} % % \item[\texttt{before} or \texttt{\string<}] % % Code for \meta{label1} comes before code for \meta{label2}. % % \item[\texttt{after} or \texttt{\string>}] % Code for \meta{label1} comes after code for \meta{label2}. % % \item[\texttt{incompatible-warning}] % % Only code for either \meta{label1} or \meta{label2} can appear % for that hook (a way to say that two packages---or parts of % them---are incompatible). A warning is raised if both labels % appear in the same hook. % % \item[\texttt{incompatible-error}] % % Like \texttt{incompatible-error} but instead of a warning a % \LaTeX{} error is raised, and the code for both labels are % dropped from that hook until the conflict is resolved. % % \item[\texttt{voids}] % % Code for \meta{label1} overwrites code for \meta{label2}. More % precisely, code for \meta{label2} is dropped for that % hook. This can be used, for example if one package is a % superset in functionality of another one and therefore wants to % undo code in some hook and replace it with its own version. % % \item[\texttt{unrelated}] % % The order of code for \meta{label1} and \meta{label2} is % irrelevant. This rule is there to undo an incorrect rule % specified earlier. % % \end{itemize} % There can only be a single relation between two labels for a % given hook, % i.e., a later \cs{DeclareHookRule} overwrites any previous % declaration. % % The \meta{hook} and \meta{label} can be specified using the % dot-syntax to denote the current package name. % See section~\ref{sec:default-label}. % % \end{function} % % % \begin{function}{\ClearHookRule} % \begin{syntax} % \cs{ClearHookRule} \Arg{hook} \Arg{label1} \Arg{label2} % \end{syntax} % Syntactic sugar for saying that \meta{label1} and \meta{label2} % are unrelated for the given \meta{hook}. % \end{function} % % % % \begin{function}{\DeclareDefaultHookRule} % \begin{syntax} % \cs{DeclareDefaultHookRule} \Arg{label1} \Arg{relation} \Arg{label2} % \end{syntax} % This sets up a relation between \meta{label1} and \meta{label2} % for all hooks unless overwritten by a specific rule for a hook. % Useful for cases where one package has a specific relation to % some other package, e.g., is \texttt{incompatible} or always % needs a special ordering \texttt{before} or \texttt{after}. % (Technically it is just a shorthand for using \cs{DeclareHookRule} % with \texttt{??} as the hook name.) % % Declaring default rules is only supported in the document % preamble.\footnotemark{} % % The \meta{label} can be specified using the dot-syntax to denote % the current package name. See section~\ref{sec:default-label}. % \end{function}\footnotetext{Trying to do so, e.g., via % \cs{DeclareHookRule} with \texttt{??} has bad side-effects and % is not supported (though not explicitly caught for performance % reasons).} % % % % \subsubsection{Querying hooks} % \label{sec:querying} % % Simpler data types, like token lists, have three possible states; they % can: % \begin{itemize} % \item exist and be empty; % \item exist and be non-empty; and % \item not exist (in which case emptiness doesn't apply); % \end{itemize} % Hooks are a bit more complicated: % a hook may exist or not, and independently it may or may not be empty. % This means that even a hook that doesn't exist may be non-empty and % it can also be disabled. % % This seemingly strange state may happen when, for example, package~$A$ % defines hook \hook{A/foo}, and package $B$ adds some code to that % hook. However, a document may load package $B$ before package $A$, or % may not load package $A$ at all. In both cases some code is added to % hook \hook{A/foo} without that hook being defined yet, thus that % hook is said to be non-empty, whereas it doesn't exist. Therefore, % querying the existence of a hook doesn't imply its emptiness, neither % does the other way around. % % Given that code or rules can be added to a hook even if it doesn't % physically exist yet, means that a querying its existence has no % real use case (in contrast to other variables that can only be % update if they have already been declared). For that reason only the % test for emptiness has a public interface. % % A hook is said to be empty when no code was added to it, either to % its permanent code pool, or to its ``next'' token list. The hook % doesn't need to be declared to have code added to its code pool. % A hook is said to exist when it was declared with \cs{NewHook} or % some variant thereof. Generic hooks such as \hook{file} and \hook{env} hooks are % automatically declared when code is added to them. % % \begin{function}[EXP]{\IfHookEmptyTF} % \begin{syntax} % \cs{IfHookEmptyTF} \Arg{hook} \Arg{true code} \Arg{false code} % \end{syntax} % Tests if the \meta{hook} is empty (\emph{i.e.}, no code was added to % it using either \cs{AddToHook} or \cs{AddToHookNext}) or such code % was removed again (via \cs{RemoveFromHook}), and % branches to either \meta{true code} or \meta{false code} depending % on the result. % % The \meta{hook} \emph{cannot} be specified using the dot-syntax. % A leading |.| is treated literally. % \end{function} % % % % \subsubsection{Displaying hook code} % % If one has to adjust the code execution in a hook using a hook % rule it is helpful to get some information about the code % associated with a hook, its current order and the existing rules. % % \begin{function}{\ShowHook,\LogHook} % \begin{syntax} % \cs{ShowHook} \Arg{hook} % \cs{LogHook} \Arg{hook} % \end{syntax} % Displays information about the \meta{hook} such as % \begin{itemize} % \item % the code chunks (and their labels) added to it, % \item % any rules set up to order them, % \item % the computed order in which the chunks are executed, % \item % any code executed on the next invocation only. % \end{itemize} % \end{function} % % \cs{LogHook} prints the information to the |.log| file, and % \cs{ShowHook} prints them to the terminal/command window and starts % \TeX's prompt (only in \cs{errorstopmode}) to wait for user action. % % The \meta{hook} can be specified using the dot-syntax to denote % the current package name. See section~\ref{sec:default-label}. % %^^A % Code for the listing below: %^^A \NewHook{example-hook} %^^A \AddToHook{example-hook}{[code from 'top-level']} %^^A \AddToHook{example-hook}[foo]{[code from package 'foo']} %^^A \AddToHook{example-hook}[bar]{[from package 'bar']} %^^A \AddToHook{example-hook}[baz]{[package 'baz' is here]} %^^A \AddToHookNext{example-hook}{[one-time code]} %^^A \DeclareHookRule{example-hook}{baz}{before}{foo} %^^A \DeclareDefaultHookRule{bar}{after}{baz} %^^A \ShowHook{example-hook} % % \def\theFancyVerbLine{\textcolor[gray]{0.5}{%^^A % \sffamily\tiny\arabic{FancyVerbLine}}} % % \bigskip % Suppose a hook \texttt{example-hook} whose output of % \cs{ShowHook}|{example-hook}| is: % \begin{verbatim}[numbers=left] % -> The hook 'example-hook': % > Code chunks: % > foo -> [code from package 'foo'] % > bar -> [from package 'bar'] % > baz -> [package 'baz' is here] % > Document-level (top-level) code (executed last): % > -> [code from 'top-level'] % > Extra code for next invocation: % > -> [one-time code] % > Rules: % > foo|baz with relation > % > baz|bar with default relation < % > Execution order (after applying rules): % > baz, foo, bar. % \end{verbatim} % % In the listing above, lines~3 to~5 show the three code chunks added % to the hook and their respective labels in the format % \begin{quote} % \quad \meta{label}\verb| -> |\meta{code} % \end{quote} % % Line~7 shows the code chunk added by the user in the main document % (labeled |top-level|) in the format % \begin{quote} % \quad\verb|Document-level (top-level) code (executed |%^^A % \meta{first\texttt{\string|}last}\verb|):|\\ % \quad\verb| -> |\meta{\texttt{top-level} code} % \end{quote} % This code will be either the first or last code executed by the hook % (|last| if the hook is normal, |first| if it is reversed). This % chunk is not affected by rules and does not take part in sorting. % % Line~9 shows the code chunk for the next execution of the hook in % the format % \begin{quote} % \quad \verb|-> |\meta{next-code} % \end{quote} % This code will be used and disappear at the next % \verb|\UseHook{example-hook}|, in contrast to the chunks mentioned % earlier, which can only be removed from that hook by doing % \verb|\RemoveFromHook{|\meta{label}|}[example-hook]|. % % Lines~11 and~12 show the rules declared that affect this hook in the % format % \begin{quote} % \quad \meta{label-1}\verb+|+\meta{label-2}| with |%^^A % \meta{\texttt{default}?}| relation |\meta{relation} % \end{quote} % which means that the \meta{relation} applies to \meta{label-1} and % \meta{label-2}, in that order, as detailed in \cs{DeclareHookRule}. % If the relation is \texttt{default} it means that this rule applies % to \meta{label-1} and \meta{label-2} in \emph{all} hooks, (unless % overridden by a non-default relation). % % Finally, line~14 lists the labels in the hook after sorting; % that is, in the order they will be executed when the hook is used. % % % \subsubsection{Debugging hook code} % % \begin{function}{\DebugHooksOn,\DebugHooksOff} % \begin{syntax} % \cs{DebugHooksOn} \ldots\ \cs{DebugHooksOff} % \end{syntax} % Turn the debugging of hook code on or off. This displays most changes % made to the hook data structures. The output is rather coarse and % not really intended for normal use. % \end{function} % % % \subsection{L3 programming layer (\texttt{expl3}) interfaces} % \label{sec:l3hook-interface} % % % This is a quick summary of the \LaTeX3 programming interfaces for % use with packages written in \texttt{expl3}. In contrast to the % \LaTeXe{} interfaces they always use mandatory arguments only, e.g., % you always have to specify the \meta{label} for a code chunk. We % therefore suggest to use the declarations discussed in the previous % section even in \texttt{expl3} packages, but the choice is yours. % % % \begin{function}{ % \hook_new:n, % \hook_new_reversed:n, % \hook_new_pair:nn % } % \begin{syntax} % \cs{hook_new:n} \Arg{hook} % \cs{hook_new_reversed:n} \Arg{hook} % \cs{hook_new_pair:nn} \Arg{hook-1} \Arg{hook-2} % \end{syntax} % Creates a new \meta{hook} with normal or reverse ordering of code % chunks. \cs{hook_new_pair:nn} creates a pair of such hooks with % \Arg{hook-2} being a reversed hook. % If a hook name is already taken, an error is raised and the hook % is not created. % % The \meta{hook} can be specified using the dot-syntax to denote % the current package name. See section~\ref{sec:default-label}. % \end{function} % % \begin{function}[added=2023-06-01]{ % \hook_new_with_args:nn, % \hook_new_reversed_with_args:nn, % \hook_new_pair_with_args:nnn % } % \begin{syntax} % \cs{hook_new_with_args:nn} \Arg{hook} \Arg{number} % \cs{hook_new_reversed_with_args:nn} \Arg{hook} \Arg{number} % \cs{hook_new_pair_with_args:nnn} \Arg{hook-1} \Arg{hook-2} \Arg{number} % \end{syntax} % Creates a new \meta{hook} with normal or reverse ordering of code % chunks, that takes \meta{number} arguments from the input stream % when it is used. \cs{hook_new_pair_with_args:nn} creates a pair of % such hooks with \Arg{hook-2} being a reversed hook. % If a hook name is already taken, an error is raised and the hook % is not created. % % The \meta{hook} can be specified using the dot-syntax to denote % the current package name. See section~\ref{sec:default-label}. % \end{function} % % % % \begin{function}{\hook_disable_generic:n} % \begin{syntax} % \cs{hook_disable_generic:n} \Arg{hook} % \end{syntax} % Marks \Arg{hook} as disabled. Any further attempt to add code to % it or declare it, will result in an error and any call to % \cs{hook_use:n} will simply do nothing. % % This declaration is intended for use with generic hooks that are % known not to work (see \texttt{ltcmdhooks-doc}) if they receive % code. % % The \meta{hook} can be specified using the dot-syntax to denote % the current package name. See section~\ref{sec:default-label}. % \end{function} % % \begin{function}{\hook_activate_generic:n} % \begin{syntax} % \cs{hook_activate_generic:n} \Arg{hook} % \end{syntax} % This is like \cs{hook_new:n} but it does nothing if the hook was previously % declared with \cs{hook_new:n}. This declaration should be used % only in special situations, e.g., when a command from another package % needs to be altered and it is not clear whether a % generic \hook{cmd} hook (for that command) has been previously % explicitly declared. % % Normally \cs{hook_new:n} should be used instead of this. % \end{function} % % % % % \begin{function}{\hook_use:n} % \begin{syntax} % ~~\cs{hook_use:n} \Arg{hook} % \end{syntax} % \end{function} % \vskip-1.2\baselineskip % \begin{function}[added=2023-06-01]{\hook_use:nnw} % \begin{syntax} % \cs{hook_use:nnw} \Arg{hook} \Arg{number} \Arg{arg_1} \ldots \Arg{arg_n} % \end{syntax} % Executes the \Arg{hook} code followed (if set up) by the code for next % invocation only, then empties that next invocation code. % \cs{hook_use:nnw} should be used for hooks declared with arguments, % and should be followed by as many brace groups as the declared % number of arguments. % The \meta{number} should be the number of arguments declared for % the hook. If the hook is not declared, this command does nothing % and it will remove \meta{number} items from the input. % % The \meta{hook} \emph{cannot} be specified using the dot-syntax. % A leading |.| is treated literally. % \end{function} % % \begin{function}{\hook_use_once:n} % \begin{syntax} % \cs{hook_use_once:n} \Arg{hook} % \end{syntax} % \end{function} % \vskip-1.2\baselineskip % \begin{function}[added=2023-06-01]{\hook_use_once:nnw} % \begin{syntax} % \cs{hook_use_once:nnw} \Arg{hook} \Arg{number} \Arg{arg_1} \ldots \Arg{arg_n} % \end{syntax} % Changes the \Arg{hook} status so that from now on any addition to % the hook code is executed immediately. Then execute any % \Arg{hook} code already set up. % \cs{hook_use_once:nnw} should be used for hooks declared with arguments, % and should be followed by as many brace groups as the declared % number of arguments. % The \meta{number} should be the number of arguments declared for % the hook. If the hook is not declared, this command does nothing % and it will remove \meta{number} items from the input. % % The \meta{hook} \emph{cannot} be specified using the dot-syntax. % A leading |.| is treated literally. % \end{function} % % \begin{function}{ % \hook_gput_code:nnn, % } % \begin{syntax} % ~~~~~~~~~\cs{hook_gput_code:nnn} \Arg{hook} \Arg{label} \Arg{code} % \end{syntax} % \end{function} % \vskip -1.2\baselineskip % \begin{function}[added=2023-06-01]{ % \hook_gput_code_with_args:nnn % } % \begin{syntax} % \cs{hook_gput_code_with_args:nnn} \Arg{hook} \Arg{label} \Arg{code} % \end{syntax} % Adds a chunk of \meta{code} to the \meta{hook} labeled % \meta{label}. If the label already exists the \meta{code} is % appended to the already existing code. % % If \cs{hook_gput_code_with_args:nnn} is used, the \meta{code} % can access the arguments passed to \cs{hook_use:nnw} % (or~\cs{hook_use_once:nnw}) with \verb|#1|, \verb|#2|, \ldots, % \verb|#n| (up to the number of arguments declared for the hook). % In that case, if an actual parameter token should be added to the % code, it should be doubled. % % If code is added to an external \meta{hook} (of the kernel or % another package) then the convention is to use the package name % as the \meta{label} not some internal module name or some other % arbitrary string. % % The \meta{hook} and \meta{label} can be specified using the % dot-syntax to denote the current package name. % See section~\ref{sec:default-label}. % \end{function} % % \begin{function}{ % \hook_gput_next_code:nn, % } % \begin{syntax} % ~~~~~~~~~~~~~\cs{hook_gput_next_code:nn} \Arg{hook} \Arg{code} % \end{syntax} % \end{function} % \vskip-1.2\baselineskip % \begin{function}[added=2023-06-01]{ % \hook_gput_next_code_with_args:nn, % } % \begin{syntax} % \cs{hook_gput_next_code_with_args:nn} \Arg{hook} \Arg{code} % \end{syntax} % Adds a chunk of \meta{code} for use only in the next invocation of the % \meta{hook}. Once used it is gone. % % If \cs{hook_gput_next_code_with_args:nn} is used, the \meta{code} % can access the arguments passed to \cs{hook_use:nnw} % (or~\cs{hook_use_once:nnw}) with \verb|#1|, \verb|#2|, \ldots, % \verb|#n| (up to the number of arguments declared for the hook). % In that case, if an actual parameter token should be added to the % code, it should be doubled. % % \end{function} % This is simpler than \cs{hook_gput_code:nnn}, the code is simply % appended to the hook in the order of declaration at the very end, % i.e., after all standard code for the hook got executed. % Thus if one needs to undo what the standard does one has to do % that as part of \meta{code}. % % The \meta{hook} can be specified using the dot-syntax to denote % the current package name. See section~\ref{sec:default-label}. % % % \begin{function}{\hook_gclear_next_code:n} % \begin{syntax} % \cs{hook_gclear_next_code:n} \Arg{hook} % \end{syntax} % Undo any earlier \cs{hook_gput_next_code:nn}. % \end{function} % % % % \begin{function}{\hook_gremove_code:nn} % \begin{syntax} % \cs{hook_gremove_code:nn} \Arg{hook} \Arg{label} % \end{syntax} % Removes any code for \meta{hook} labeled \meta{label}. % % If there is no code under the \meta{label} in the \meta{hook}, % or if the \meta{hook} does not exist, a warning is issued when % you attempt to use \cs{hook_gremove_code:nn}, and the command is ignored. % % If the second argument is \texttt{*}, then all code chunks are % removed. This is rather dangerous as it drops code from other % packages one may not know about, so think twice before using % that! % % The \meta{hook} and \meta{label} can be specified using the % dot-syntax to denote the current package name. % See section~\ref{sec:default-label}. % \end{function} % % % \begin{function}{\hook_gset_rule:nnnn} % \begin{syntax} % \cs{hook_gset_rule:nnnn} \Arg{hook} \Arg{label1} \Arg{relation} \Arg{label2} % \end{syntax} % Relate \meta{label1} with \meta{label2} when used in \meta{hook}. % See \cs{DeclareHookRule} for the allowed \meta{relation}s. % If \meta{hook} is \texttt{??} a default rule is specified. % % The \meta{hook} and \meta{label} can be specified using the % dot-syntax to denote the current package name. % See section~\ref{sec:default-label}. % The dot-syntax is parsed in both \meta{label} arguments, but it % usually makes sense to be used in only one of them. % \end{function} % % \begin{function}[pTF]{\hook_if_empty:n} % \begin{syntax} % \cs{hook_if_empty:nTF} \Arg{hook} \Arg{true code} \Arg{false code} % \end{syntax} % Tests if the \meta{hook} is empty (\emph{i.e.}, no code was added to % it using either \cs{AddToHook} or \cs{AddToHookNext}), and % branches to either \meta{true code} or \meta{false code} depending % on the result. % % The \meta{hook} \emph{cannot} be specified using the dot-syntax. % A leading |.| is treated literally. % \end{function} % % % \begin{function}{\hook_show:n,\hook_log:n} % \begin{syntax} % \cs{hook_show:n} \Arg{hook} % \cs{hook_log:n} \Arg{hook} % \end{syntax} % Displays information about the \meta{hook} such as % \begin{itemize} % \item % the code chunks (and their labels) added to it, % \item % any rules set up to order them, % \item % the computed order in which the chunks are executed, % \item % any code executed on the next invocation only. % \end{itemize} % % \cs{hook_log:n} prints the information to the |.log| file, and % \cs{hook_show:n} prints them to the terminal/command window and starts % \TeX's prompt (only if \cs{errorstopmode}) to wait for user action. % % The \meta{hook} can be specified using the dot-syntax to denote % the current package name. See section~\ref{sec:default-label}. % \end{function} % % \begin{function}{\hook_debug_on:,\hook_debug_off:} % \begin{syntax} % \cs{hook_debug_on:} % \end{syntax} % Turns the debugging of hook code on or off. This displays changes % to the hook data. % \end{function} % % % % \subsection{On the order of hook code execution} \label{sec:order} % % Chunks of code for a \meta{hook} under different labels are supposed % to be independent if there are no special rules set up that % define a relation between the chunks. This means that you can't % make assumptions about the order of execution! % % Suppose you have the following declarations: %\begin{verbatim} % \NewHook{myhook} % \AddToHook{myhook}[packageA]{\typeout{A}} % \AddToHook{myhook}[packageB]{\typeout{B}} % \AddToHook{myhook}[packageC]{\typeout{C}} %\end{verbatim} % then executing the hook with \cs{UseHook} will produce the % typeout \texttt{A} \texttt{B} \texttt{C} in that order. In other % words, the execution order is computed to be \texttt{packageA}, % \texttt{packageB}, \texttt{packageC} which you can verify with % \cs{ShowHook}\texttt{\{myhook\}}: %\begin{verbatim} % -> The hook 'myhook': % > Code chunks: % > packageA -> \typeout {A} % > packageB -> \typeout {B} % > packageC -> \typeout {C} % > Document-level (top-level) code (executed last): % > --- % > Extra code for next invocation: % > --- % > Rules: % > --- % > Execution order: % > packageA, packageB, packageC. %\end{verbatim} % The reason is that the code chunks are internally saved in a property list % and the initial order of such a property list is the order in % which key-value pairs got added. However, that is only true if % nothing other than adding happens! % % Suppose, for example, you want to replace the code chunk for % \texttt{packageA}, e.g., %\begin{verbatim} % \RemoveFromHook{myhook}[packageA] % \AddToHook{myhook}[packageA]{\typeout{A alt}} %\end{verbatim} % then your order becomes \texttt{packageB}, % \texttt{packageC}, \texttt{packageA} because the label got removed % from the property list and then re-added (at its end). % % While that may not be too surprising, the execution order is % also sometimes altered if you add a redundant rule, e.g. if you specify %\begin{verbatim} % \DeclareHookRule{myhook}{packageA}{before}{packageB} %\end{verbatim} % instead of the previous lines we get %\begin{verbatim} % -> The hook 'myhook': % > Code chunks: % > packageA -> \typeout {A} % > packageB -> \typeout {B} % > packageC -> \typeout {C} % > Document-level (top-level) code (executed last): % > --- % > Extra code for next invocation: % > --- % > Rules: % > packageB|packageA with relation > % > Execution order (after applying rules): % > packageA, packageC, packageB. %\end{verbatim} % As you can see the code chunks are still in the same order, but % in the execution order for the labels \texttt{packageB} and % \texttt{packageC} have % swapped places. % The reason is that, with the rule there are two orders that % satisfy it, and the algorithm for sorting happened to pick a % different one compared to the case without rules (where it % doesn't run at all as there is nothing to resolve). % Incidentally, if we had instead specified the redundant rule %\begin{verbatim} % \DeclareHookRule{myhook}{packageB}{before}{packageC} %\end{verbatim} % the execution order would not have changed. % % In summary: it is not possible to rely on the order of execution % unless there are rules that partially or fully define the order % (in which you can rely on them being fulfilled). % % % \subsection{The use of \enquote{reversed} hooks} \label{sec:reversed-order} % % You may have wondered why you can declare a \enquote{reversed} hook % with \cs{NewReversedHook} and what that does exactly. % % In short: the execution order of a reversed hook (without any % rules!) is exactly reversed to the order you would have gotten for % a hook declared with \cs{NewHook}. % % This is helpful if you have a pair of hooks where you expect to see % code added that involves grouping, e.g., starting an environment % in the first and closing that environment in the second hook. % To give a somewhat contrived example\footnote{there are simpler % ways to achieve the same effect.}, suppose there is a package % adding the following: %\begin{verbatim} % \AddToHook{env/quote/before}[package-1]{\begin{itshape}} % \AddToHook{env/quote/after} [package-1]{\end{itshape}} %\end{verbatim} % As a result, all quotes will be in italics. % Now suppose further that another |package-too| makes the quotes % also in blue and therefore adds: %\begin{verbatim} % \usepackage{color} % \AddToHook{env/quote/before}[package-too]{\begin{color}{blue}} % \AddToHook{env/quote/after} [package-too]{\end{color}} %\end{verbatim} % Now if the \hook{env/quote/after} hook would be a normal hook we % would get the same execution order in both hooks, namely: %\begin{verbatim} % package-1, package-too %\end{verbatim} % (or vice versa) and as a result, would get: %\begin{verbatim} % \begin{itshape}\begin{color}{blue} ... % \end{itshape}\end{color} %\end{verbatim} % and an error message saying that \verb=\begin{color}= was ended by % \verb=\end{itshape}=. % With \hook{env/quote/after} declared as a reversed hook the % execution order is reversed and so all environments are closed in % the correct sequence and \cs{ShowHook} would give us the % following output: %\begin{verbatim} % -> The hook 'env/quote/after': % > Code chunks: % > package-1 -> \end {itshape} % > package-too -> \end {color} % > Document-level (top-level) code (executed first): % > --- % > Extra code for next invocation: % > --- % > Rules: % > --- % > Execution order (after reversal): % > package-too, package-1. %\end{verbatim} % % The reversal of the execution order happens before applying any % rules, so if you alter the order you will probably have to alter % it in both hooks, not just in one, but that depends on the use case. % % % % % \subsection{Difference between \enquote{normal} and % \enquote{one-time} hooks} % \label{sec:onetime-hooks} % % When executing a hook a developer has the choice of using % either \cs{UseHook} or \cs{UseOneTimeHook} (or their \pkg{expl3} % equivalents \cs{hook_use:n} and \cs{hook_use_once:n}). % This choice affects how \cs{AddToHook} is handled after the hook % has been executed for the first time. % % With normal hooks adding code via \cs{AddToHook} means that the % code chunk is added to the hook data structure and then used each time % \cs{UseHook} is called. % % With one-time hooks it this is handled slightly differently: % After \cs{UseOneTimeHook} has been called, any further attempts to % add code to the hook via \cs{AddToHook} will simply execute the % \meta{code} immediately. % % This has some consequences one needs to be aware of: % \begin{itemize} % \item % % If \meta{code} is added to a normal hook after the hook was % executed and it is never executed again for one or the other % reason, then this new \meta{code} will never be executed. % % \item % % In contrast if that happens with a one-time hook the \meta{code} is % executed immediately. % % \end{itemize} % In particular this means that construct such as %\begin{quote} % \cs{AddToHook}\verb={myhook}=\\ % \phantom{\cs{AddToHook}}\verb={= \meta{code-1} % \cs{AddToHook}\verb={myhook}=\Arg{code-2} % \meta{code-3} \verb=}= %\end{quote} % works for one-time hooks\footnote{This is sometimes used with % \cs{AtBeginDocument} which is why it is supported.} (all three % code chunks are executed one after another), but it makes little % sense with a normal hook, because with a normal hook the first time % \verb=\UseHook{myhook}= is executed it would % \begin{itemize} % \item % execute \meta{code-1}, % \item % then execute \verb=\AddToHook{myhook}{code-2}= which adds the % code chunk \meta{code-2} to the hook for use on the next invocation, % \item % and finally execute \meta{code-3}. % \end{itemize} % The second time \cs{UseHook} is called it would execute the % above and in addition \meta{code-2} as that was added as a code % chunk to the hook in the meantime. So each time the hook is used % another copy of \meta{code-2} is added and so that code chunk % is executed $\meta{\# of invocations} -1$ times. % % % % \subsection{Generic hooks provided by packages} % \label{sec:generic-hooks} % % \changes{v1.0p}{2021/08/20}{Section on generic hooks added (gh/638)} % % The hook management system also implements a category of hooks % that are called \enquote{Generic Hooks}. Normally a hook has to % be explicitly declared before it can be used in code. This % ensures that different packages are not using the same hook name % for unrelated purposes---something that would result in absolute % chaos. However, there are a number of \enquote{standard} hooks % where it is unreasonable to declare them beforehand, e.g, each % and every command has (in theory) an associated \texttt{before} % and \texttt{after} hook. In such cases, i.e., for command, % environment or file hooks, they can be used simply by adding code % to them with \cs{AddToHook}. For more specialized generic hooks, % e.g., those provided by \pkg{babel}, you have to additionally % enable them with \cs{ActivateGenericHook} as explained below. % % The generic hooks provided by \LaTeX{} are those for % \hook{cmd}, % \hook{env}, % \hook{file}, % \hook{include} % \hook{package}, and % \hook{class}, % and all these are available out of the box: you only have to % use \cs{AddToHook} to % add code to them, but you don't have to add \cs{UseHook} or % \cs{UseOneTimeHook} to your code, because this is already done for % you (or, in the case of \hook{cmd} hooks, the command’s code is % patched at \verb=\begin{document}=, if necessary). % % However, if you want to provide further generic hooks in your own % code, the situation is slightly different. To do this you should % use \cs{UseHook} or \cs{UseOneTimeHook}, but % \emph{without declaring the hook} with \cs{NewHook}. As % mentioned earlier, a call to \cs{UseHook} with an undeclared hook % name does nothing. So as an additional setup step, you need to % explicitly activate your generic hook. Note that a generic hook % produced in this way is always a normal hook. % % For a truly generic hook, with a variable part in the hook name, % such upfront activation would be difficult or impossible, because % you typically do not know what kind of variable parts may come up % in real documents. % % For example, \pkg{babel} provides hooks such as % \hook{babel/\meta{language}/afterextras}. However, language % support in \pkg{babel} is often done through external language % packages. Thus doing the activation for all languages inside the % core \pkg{babel} code is not a viable approach. Instead it needs % to be done by each language package (or by the user who wants to % use a particular hook). % % Because the hooks are not declared with \cs{NewHook} their names % should be carefully chosen to ensure that they are (likely to be) % unique. Best practice is to include the package or % command name, as was done in the \pkg{babel} example above. % % Generic hooks defined in this way are always normal hooks (i.e., % you can't implement reversed hooks this way). This is a % deliberate limitation, because it speeds up the processing % considerably. % % % \subsection{Hooks with arguments} % \label{sec:hook-args} % % Sometimes it is necessary to pass contextual information to a hook, % and, for one reason or another, it is not feasible to store such % information in macros. To serve this purpose, hooks can be % declared with arguments, so that the programmer can pass along the % data necessary for the code in the hook to function properly. % % A hook with arguments works mostly like a regular hook, and most % commands that work for regular hooks, also work for hooks that take % arguments. The differences are when the hook is declared % (\cs{NewHookWithArguments} is used instead of \cs{NewHook}), then % code can be added with both \cs{AddToHook} and % \cs{AddToHookWithArguments}, and when the hook is used % (\cs{UseHookWithArguments} instead of \cs{UseHook}). % % \medskip % % A hook with arguments must be declared as such (before it is first % used, as all regular hooks) using % \cs{NewHookWithArguments}\Arg{hook}\Arg{number}. All code added to % that hook can then use \verb|#1| to access the first argument, % \verb|#2| to access the second, and so forth up to the number of % arguments declared. However, it is still possible to add code with % references to the arguments of a hook that was not yet declared % (we will discuss that later). At their core, hooks are macros, so % \TeX's limit of 9~arguments applies, and a low-level \TeX{} error % is raised if you try to reference an argument number that doesn't % exist. % % \medskip % % To use a hook with arguments, just write % \cs{UseHookWithArguments}\Arg{hook}\Arg{number} followed by a % braced list of the arguments. For example, if the hook \hook{test} % takes three arguments, write: %\begin{verbatim} % \UseHookWithArguments{test}{3}{arg-1}{arg-2}{arg-3} %\end{verbatim} % then, in the \meta{code} of the hook, all instances of \verb|#1| % will be replaced by \verb|arg-1|, \verb|#2| by \verb|arg-2| and so % on. If, at the point of usage, the programmer provides more % arguments than the hook is declared to take, the excess arguments % are simply ignored by the hook. Behaviour is % unpredictable\footnote % {The hook \emph{will} take the declared number of arguments, and % what will happen depends on what was grabbed, and what the hook % code does with its arguments.} % if too few arguments are provided. If the hook isn't declared, % \meta{number} arguments are removed from the input stream. % % \medskip % % Adding code to a hook with arguments can be done with % \cs{AddToHookWithArguments} as well as with the regular % \cs{AddToHook}, to achieve different outcomes. The main difference % when it comes to adding code to a hook, in this case, is firstly % the possibility of accessing a hook's arguments, of course, and % second, how parameter tokens (\verb|#|$_6$) are treated. % % Using \cs{AddToHook} in a hook that takes arguments will work as it % does for all other hooks. This allows a package developer to add % arguments to a hook that otherwise had none without having to worry % about compatibility. This means that, for example: %\begin{verbatim} % \AddToHook{test}{\def\foo#1{Hello, #1!}} %\end{verbatim} % will define the same macro \cs[no-index]{foo} regardless if the % hook \hook{test} takes arguments or not. % % Using \cs{AddToHookWithArguments} allows the \meta{code} added to % access the arguments of the hook with \verb|#1|, \verb|#2|, and so % forth, up to the number of the arguments declared in the hook. % This means that if one wants to add a \verb|#|$_6$ to the % \meta{code} that token must be doubled in the input. The same % definition from above, using \cs{AddToHookWithArguments}, needs to % be rewritten: %\begin{verbatim} % \AddToHookWithArguments{test}{\def\foo##1{Hello, ##1!}} %\end{verbatim} % % Extending the above example to use the hook arguments, we could % rewrite something like (now from declaration to usage, to get the % whole picture): %\begin{verbatim} % \NewHookWithArguments{test}{1} % \AddToHookWithArguments{test}{% % \typeout{Defining foo with "#1"} % \def\foo##1{Hello, ##1! Some text after: #1}% % } % \UseHook{test}{Howdy!} % \ShowCommand\foo %\end{verbatim} % Running the code above prints in the terminal: %\begin{verbatim} % Defining foo with "Howdy!" % > \foo=macro: % #1->Hello, #1! Some text after: Howdy!. %\end{verbatim} % Note how \verb|##1| in the call to \cs{AddToHookWithArguments} % became \verb|#1|, and the \verb|#1| was replaced by the argument % passed to the hook. Should the hook be used again, with a % different argument, the definition would naturally change. % % \bigskip % % It is possible to add code referencing a hook's arguments before % such hook is declared and the number of hooks is fixed. However, % if some code is added to the hook, that references more arguments % than will be declared for the hook, there will be a low-level % \TeX{} error about an \enquote{Illegal parameter number} at the % time the hook is declared, which will be hard to track down because % at that point \TeX{} can't know whence the offending code came % from. Thus it is important that package writers explicitly % document how many arguments (if any) each hook can take, so users % of those packages know how many arguments can be referenced, and % equally important, what each argument means. % % \subsection{Private \LaTeX{} kernel hooks} % % There are a few places where it is absolutely essential for % \LaTeX{} to function correctly that code is executed in a precisely % defined order. Even that could have been implemented with the % hook management (by adding various rules to ensure the % appropriate ordering with respect to other code added by % packages). However, this makes every document unnecessary % slow, because there has to be sorting even though the result is % predetermined. Furthermore it forces package writers to % unnecessarily add such rules if they add further code to the hook % (or break \LaTeX{}). % % For that reason such code is not using the hook management, but % instead private kernel commands directly before or after a public % hook with the following naming % convention: \cs{@kernel@before@\meta{hook}} or % \cs{@kernel@after@\meta{hook}}. For example, in % \cs{enddocument} you find %\begin{verbatim} % \UseHook{enddocument}% % \@kernel@after@enddocument %\end{verbatim} % which means first the user/package-accessible \hook{enddocument} % hook is executed and then the internal kernel hook. As their name % indicates these kernel commands should not be altered by third-party % packages, so please refrain from that in the interest of % stability and instead use the public hook next to it.\footnote{As % with everything in \TeX{} there is not enforcement of this rule, % and by looking at the code it is easy to find out how the kernel % adds to them. The main reason of this section is therefore to say % \enquote{please don't do that, this is unconfigurable code!}} % % % % \subsection{Legacy \LaTeXe{} interfaces} % % \newcommand\onetimetext{This is a one-time hook, so after it % is executed, all further % attempts to add code to it will execute such code immediately % (see section~\ref{sec:onetime-hooks}).} % % \LaTeXe{} offered a small number of hooks together with commands to % add to them. They are listed here and are retained for backwards % compatibility. % % With the new hook management, several additional hooks have been added % to \LaTeX\ and more will follow. See the next section for what % is already available. % % % \begin{function}{\AtBeginDocument} % \begin{syntax} % \cs{AtBeginDocument} \oarg{label} \Arg{code} % \end{syntax} % If used without the optional argument \meta{label}, it works essentially % like before, i.e., it is adding \meta{code} to the hook % \hook{begindocument} % (which is executed inside \verb=\begin{document}=). % However, all code added this way is labeled with the label % \hook{top-level} (see section~\ref{sec:top-level}) % if done outside of a package or class or with the % package/class name if called inside such a file % (see section~\ref{sec:default-label}). % % This way one can add code to the hook using % \cs{AddToHook} or \cs{AtBeginDocument} using a different label % and explicitly order the code chunks as necessary, e.g., run some % code before or after another package's code. When using the % optional argument the call is equivalent to running % \cs{AddToHook} \texttt{\{begindocument\}} \oarg{label} % \Arg{code}. % % \cs{AtBeginDocument} is a wrapper around the \hook{begindocument} % hook (see section~\ref{sec:begindocument-hooks}), which is a % one-time hook. As such, after the \hook{begindocument} hook is % executed at \verb=\begin{document}= any attempt to add \meta{code} % to this hook with \cs{AtBeginDocument} or with \cs{AddToHook} will % cause that \meta{code} to execute immediately instead. % See section~\ref{sec:onetime-hooks} for more on one-time hooks. % % For important packages with known order requirement we may over % time add rules to the kernel (or to those packages) so that they % work regardless of the loading-order in the document. % \end{function} % % \begin{function}{\AtEndDocument} % \begin{syntax} % \cs{AtEndDocument} \oarg{label} \Arg{code} % \end{syntax} % Like \cs{AtBeginDocument} but for the \hook{enddocument} hook. % \end{function} % % \bigskip % % The few hooks that existed previously in \LaTeXe{} used internally % commands such as \cs{@begindocumenthook} and packages sometimes % augmented them directly rather than working through % \cs{AtBeginDocument}. For that reason there is currently support % for this, that is, if the system detects that such an internal % legacy hook command contains code it adds it to the new hook % system under the label \texttt{legacy} so that it doesn't get % lost. % % However, over time the remaining cases of direct usage need % updating because in one of the future release of \LaTeX{} we will % turn this legacy support off, as it does unnecessary slow down % the processing. % % % \section{\LaTeXe{} commands and environments augmented by hooks} % % In this section we describe the standard hooks that are now % offered by \LaTeX{}, or give pointers to other documents in which % they are described. This section will grow over time (and % perhaps eventually move to usrguide3). % % \subsection{Generic hooks} % \label{sec:generic} % % As stated earlier, with the exception of generic hooks, all hooks must % be declared with \cs{NewHook} before they can be used. % All generic hooks have names of the form % \enquote{\meta{type}/\meta{name}/\meta{position}}, where \meta{type} % is from the predefined list shown below, and \meta{name} is the variable % part whose meaning will depend on the \meta{type}. The last component, % \meta{position}, has more complex possibilities: % it can always be |before| or |after|; for |env| hooks, it can also be |begin| % or |end|; and for |include| hooks it can also be |end|. Each specific % hook is documented below, or in \texttt{ltcmdhooks-doc.pdf} or % \texttt{ltfilehook-doc.pdf}. % % The generic hooks provided by \LaTeX{} belong to one of the six types: % \begin{description} % \item[env] Hooks executed before and after environments -- % \meta{name} is the name of the environment, and available values % for \meta{position} are |before|, |begin|, |end|, and |after|; % \item[cmd] Hooks added to and executed before and after commands -- % \meta{name} is the name of the command, and available values % for \meta{position} are |before| and |after|; % \item[file] Hooks executed before and after reading a file -- % \meta{name} is the name of the file (with extension), and % available values for \meta{position} are |before| and |after|; % \item[package] Hooks executed before and after loading packages -- % \meta{name} is the name of the package, and available values for % \meta{position} are |before| and |after|; % \item[class] Hooks executed before and after loading classes -- % \meta{name} is the name of the class, and available values for % \meta{position} are |before| and |after|; % \item[include] Hooks executed before and after \cs{include}d files -- % \meta{name} is the name of the included file (without the |.tex| % extension), and available values for \meta{position} are |before|, % |end|, and |after|. % \end{description} % % Each of the hooks above are detailed in the following sections and % in linked documentation. % ^^A^^A^^A \pho{Wouldn't it be better to document all hooks here?} % % \subsubsection{Generic hooks for all environments} % % Every environment \meta{env} has now four associated hooks coming % with it: % \begin{description} % \item[\hook{env/\meta{env}/before}] % % This hook is executed as part of \cs{begin} as the very first % action, in particular prior to starting the environment group. % Its scope is therefore not restricted by the environment. % % \item[\hook{env/\meta{env}/begin}] % % This hook is executed as part of \cs{begin} directly in front % of the code specific to the environment start (e.g., % the third argument of \cs{NewDocumentEnvironment} and % the second argument of \cs{newenvironment}). % Its scope is the environment body. % % \item[\hook{env/\meta{env}/end}] % % This hook is executed as part of \cs{end} directly in front of the % code specific to the end of the environment (e.g., % the forth argument of \cs{NewDocumentEnvironment} and % the third argument of \cs{newenvironment}). % % \item[\hook{env/\meta{env}/after}] % % This hook is executed as part of \cs{end} after the % code specific to the environment end and after the environment % group has ended. % Its scope is therefore not restricted by the environment. % % The hook is implemented as a reversed hook so if two packages % add code to \hook{env/\meta{env}/before} and to % \hook{env/\meta{env}/after} they can add surrounding % environments and the order of closing them happens in the % right sequence. % % \end{description} % Generic environment hooks are never one-time hooks even with % environments that are supposed to appear only once in a % document.\footnote{Thus if one adds code to such hooks after the % environment has been processed, it will only be executed if the % environment appears again and if that doesn't happen the code % will never get executed.} In contrast to other hooks there is % also no need to declare them using \cs{NewHook}. % % The hooks are only executed if \cs{begin}\Arg{env} and % \cs{end}\Arg{env} is used. If the environment code is executed % via low-level calls to \cs[no-index]{\meta{env}} and \cs[no-index]{end\meta{env}} % (e.g., to avoid the environment grouping) they are not % available. If you want them available in code using this method, % you would need to add them yourself, i.e., write something like %\begin{verbatim} % \UseHook{env/quote/before}\quote % ... % \endquote\UseHook{env/quote/after} %\end{verbatim} % to add the outer hooks, etc. % % % Largely for compatibility with existing packages, the following % four commands are also available to set the environment hooks; but for % new packages we recommend directly using the hook names and % \cs{AddToHook}. % % % \begin{function}{\BeforeBeginEnvironment} % \begin{syntax} % \cs{BeforeBeginEnvironment} \oarg{label} \Arg{env} \Arg{code} % \end{syntax} % This declaration adds to the \hook{env/\meta{env}/before} hook % using the \meta{label}. If \meta{label} is not given, the % \meta{default label} is used (see section~\ref{sec:default-label}). % \end{function} % % \begin{function}{\AtBeginEnvironment} % \begin{syntax} % \cs{AtBeginEnvironment} \oarg{label} \Arg{env} \Arg{code} % \end{syntax} % This is like \cs{BeforeBeginEnvironment} but it adds to the \hook{env/\meta{env}/begin} hook. % \end{function} % % \begin{function}{\AtEndEnvironment} % \begin{syntax} % \cs{AtEndEnvironment} \oarg{label} \Arg{env} \Arg{code} % \end{syntax} % This is like \cs{BeforeBeginEnvironment} but it adds to the \hook{env/\meta{env}/end} hook. % \end{function} % % \begin{function}{\AfterEndEnvironment} % \begin{syntax} % \cs{AfterEndEnvironment} \oarg{label} \Arg{env} \Arg{code} % \end{syntax} % This is like \cs{BeforeBeginEnvironment} but it adds to the \hook{env/\meta{env}/after} hook. % \end{function} % % % \subsubsection{Generic hooks for commands} % % Similar to environments there are now (at least in theory) two % generic hooks available for any \LaTeX{} command. These are % \begin{description} % \item[\hook{cmd/\meta{name}/before}] % % This hook is executed at the very start of the command % execution. % % \item[\hook{cmd/\meta{name}/after}] % This hook is executed at the very end of the command body. It is % implemented as a reversed hook. % \end{description} % In practice there are restrictions and especially the % \hook{after} hook works only with a subset of commands. Details % about these restrictions are documented in % \texttt{ltcmdhooks-doc.pdf} or with code in % \texttt{ltcmdhooks-code.pdf}. % % % % % \subsubsection{Generic hooks provided by file loading operations} % % There are several hooks added to \LaTeX{}'s process of loading % file via its high-level interfaces such as \cs{input}, % \cs{include}, \cs{usepackage}, \cs{RequirePackage}, etc. These % are documented in \texttt{ltfilehook-doc.pdf} or with code in % \texttt{ltfilehook-code.pdf}. % % % % \subsection{Hooks provided by \cs{begin}\texttt{\{document\}}} % \label{sec:begindocument-hooks} % % Until 2020 \cs{begin}\texttt{\{document\}} offered exactly one % hook that one could add to using % \cs{AtBeginDocument}. Experiences over the years have shown that % this single hook in one place was not enough and as part of % adding the general hook management system a number of additional % hooks have been added at this point. The places for these hooks have % been chosen to provide the same support as offered by external % packages, such as \pkg{etoolbox} and others that augmented % \cs{document} to gain better control. % % Supported are now the following hooks (all of them one-time hooks): % \begin{description} % % % \item[\hook{begindocument/before}] % % This hook is executed at the very start of \cs{document}, one can % think of it as a hook for code at the end of the preamble % section and this is how it is used by \pkg{etoolbox}'s % \cs{AtEndPreamble}. % % \onetimetext % % \item[\hook{begindocument}] % % This hook is added to by using \cs{AddToHook}\texttt{\{begindocument\}} % or by using \cs{AtBeginDocument} and it is % executed after the \texttt{.aux} file has been read and most % initialization are done, so they can be altered and inspected by % the hook code. It is followed by a small number of further % initializations that shouldn't be altered and are therefore % coming later. % % The hook should not be used to add material for typesetting as % we are still in \LaTeX's initialization phase and not in the % document body. If such material needs to be added to the document % body use the next hook instead. % % \onetimetext % % \item[\hook{begindocument/end}] % % This hook is executed at the end of the \cs{document} code in % other words at the beginning of the document body. The only % command that follows it is \cs{ignorespaces}. % % \onetimetext % % \end{description} % The generic hooks executed by \cs{begin} also exist, i.e., % \hook{env/document/before} and \hook{env/document/begin}, but % with this special environment it is better use the dedicated % one-time hooks above. % % % % % \subsection{Hooks provided by \cs{end}\texttt{\{document\}}} % % \LaTeXe{} has always provided \cs{AtEndDocument} to add code to the % \verb=\end{document}=, just in front of the code that % is normally executed there. While this was a big improvement over % the situation in \LaTeX\,2.09, it was not flexible enough for a % number of use cases and so packages, such as \pkg{etoolbox}, % \pkg{atveryend} and others patched \cs{enddocument} to add % additional points where code could be hooked into. % % Patching using packages is always problematical as leads to % conflicts (code availability, ordering of patches, incompatible % patches, etc.). For this reason a number of additional hooks % have been added to the \cs{enddocument} code to allow packages % to add code in various places in a controlled way without the % need for overwriting or patching the core code. % % Supported are now the following hooks (all of them one-time hooks): % \begin{description} % % \item[\hook{enddocument}] % % The hook associated with \cs{AtEndDocument}. It is immediately % called at the beginning of \cs{enddocument}. % % When this hook is executed there may be still unprocessed % material (e.g., floats on the deferlist) and the hook may add % further material to be typeset. After it, \cs{clearpage} is % called to ensure that all such material gets typeset. If there % is nothing waiting the \cs{clearpage} has no effect. % % \onetimetext % % \item[\hook{enddocument/afterlastpage}] % % As the name indicates this hook should not receive code that % generates material for further pages. It is the right place to % do some final housekeeping and possibly write out some % information to the \texttt{.aux} file (which is still open at % this point to receive data, but since there will be no more pages you % need to write to it using \cs{immediate}\cs{write}). It is also the % correct place to % set up any testing code to be run when the \texttt{.aux} file % is re-read in the next step. % % % After this hook has been executed the \texttt{.aux} file is % closed for writing and then read back in to do some tests % (e.g., looking for missing references or duplicated labels, etc.). % % \onetimetext % % \item[\hook{enddocument/afteraux}] % % At this point, the \texttt{.aux} file has been reprocessed and so % this is a possible place for final checks and display of % information to the user. However, for the latter you might % prefer the next hook, so that your information is displayed after the % (possibly longish) list of files if that got requested via \cs{listfiles}. % % \onetimetext % % \item[\hook{enddocument/info}] % % This hook is meant to receive code that write final information % messages to the terminal. It follows immediately after the % previous hook (so both could have been combined, but then % packages adding further code would always need to also supply % an explicit rule to specify where it should go. % % This hook already contains some code added by the kernel (under % the labels \texttt{kernel/filelist} and % \texttt{kernel/warnings}), namely the list of files when % \cs{listfiles} has been used and the warnings for duplicate % labels, missing references, font substitutions etc. % % \onetimetext % % \item[\hook{enddocument/end}] % % Finally, this hook is executed just in front of the final call % to \cs{@{}@end}. % % \onetimetext % is it even possible to add code after this one? % % \end{description} % % % There is also the hook \hook{shipout/lastpage}. This hook is % executed as part of the last \cs{shipout} in the document to % allow package to add final \cs{special}'s to that page. Where % this hook is executed in relation to those from the above list % can vary from document to document. Furthermore to determine correctly % which of the \cs{shipout}s is the last one, \LaTeX{} needs to be run % several times, so initially it might get executed on the wrong % page. See section~\ref{sec:shipout} for where to find the details. % % % It is in also possible to use the generic \hook{env/document/end} % hook which is executed by \cs{end}, i.e., just in front of the % first hook above. Note however that the other generic \cs{end} % environment hook, i.e., \hook{env/document/after} will never get % executed, because by that time \LaTeX{} has finished the document % processing. % % % % % \subsection{Hooks provided by \cs{shipout} operations} % \label{sec:shipout} % % There are several hooks and mechanisms added to \LaTeX{}'s % process of generating pages. These are documented in % \texttt{ltshipout-doc.pdf} or with code in % \texttt{ltshipout-code.pdf}. % % % \subsection{Hooks provided for paragraphs} % \label{sec:para} % % The paragraph processing has been augmented to include a number of % internal and public hooks. These are documented in % \texttt{ltpara-doc.pdf} or with code in % \texttt{ltpara-code.pdf}. % % % % \subsection{Hooks provided in NFSS commands} % % In languages that need to support for more than one script in % parallel (and thus several sets of fonts, e.g., supporting both Latin and % Japanese fonts), NFSS font commands such as \cs{sffamily} need % to switch both the Latin family to ``Sans Serif'' and in addition % alter a second set of fonts. % % To support this, several NFSS commands have hooks to which % such support can be added. % \begin{description} % % \item[\hook{rmfamily}] % % After \cs{rmfamily} has done its initial checks and prepared a % font series update, this hook is executed before \cs{selectfont}. % % \item[\hook{sffamily}] % % This is like the \hook{rmfamily} hook, but for the \cs{sffamily} command. % % \item[\hook{ttfamily}] % % This is like the \hook{rmfamily} hook, but for the \cs{ttfamily} command. % % \item[\hook{normalfont}] % % The \cs{normalfont} command resets the font encoding, family, series % and shape to their document defaults. It then executes this % hook and finally calls \cs{selectfont}. % % \item[\hook{expand@font@defaults}] % % The internal \cs{expand@font@defaults} command expands and % saves the current defaults for the meta families (rm/sf/tt) and % the meta series (bf/md). If the NFSS machinery has been % augmented, e.g., for Chinese or Japanese fonts, then further % defaults may need to be set at this point. This can be done in % this hook which is executed at the end of this macro. % % \item[\hook{bfseries/defaults}, \hook{bfseries}] % % If the \cs{bfdefault} was explicitly changed by the user, its % new value is used to set the bf series defaults for the meta % families (rm/sf/tt) when \cs{bfseries} is called. The % \hook{bfseries/defaults} hook allows further adjustments to be made % in this case. This hook is only executed if such a change is % detected. In contrast, the \hook{bfseries} hook is always % executed just before \cs{selectfont} is called to change to the % new series. % % % \item[\hook{mdseries/defaults}, \hook{mdseries}] % % These two hooks are like the previous ones but they are in the % \cs{mdseries} command. % % \item[\hook{selectfont}] % % This hook is executed inside \cs{selectfont}, after the current % values for \textit{encoding}, \textit{family}, \textit{series}, % \textit{shape}, and \textit{size} are evaluated and the new font % is selected (and if necessary loaded). After the hook has % executed, NFSS will still do any updates necessary for a new % \textit{size} (such as changing the size of \cs{strut}) and any % updates necessary to a change in \textit{encoding}. % % This hook is intended for use cases where, in parallel to a % change in the main font, some other fonts need to be altered % (e.g., in CJK processing where you may need to deal with several % different alphabets). % % \end{description} % % % % \subsection{Hook provided by the mark mechanism} % % See \texttt{ltmarks-doc.pdf} for details. % \begin{description} % % \item[\hook{insertmark}] % % This hook allows for a special setup while \cs{InsertMark} % inserts a mark. It is executed in group so local changes only % apply to the mark being inserted. % % \end{description} % % \MaybeStop{\setlength\IndexMin{200pt} \PrintIndex } % % % \section{The Implementation} % % % \begin{macrocode} %<@@=hook> % \end{macrocode} % % \changes{v1.0i}{2021/03/18}{Use \cs{NewModuleRelease}.} % \changes{v1.0n}{2021/05/24}{Use \cs{msg_...} instead of \cs{__kernel_msg...}} % % \begin{macrocode} %<*2ekernel|latexrelease> \ExplSyntaxOn %\NewModuleRelease{2020/10/01}{lthooks} % {The~hook~management~system} % \end{macrocode} % % \subsection{Debugging} % % \begin{macro}{\g_@@_debug_bool} % Holds the current debugging state. % \begin{macrocode} \bool_new:N \g_@@_debug_bool % \end{macrocode} % \end{macro} % % \begin{macro}{\hook_debug_on:,\hook_debug_off:} % \begin{macro}{\@@_debug:n} % \begin{macro}{\@@_debug_gset:} % Turns debugging on and off by redefining \cs{@@_debug:n}. % \begin{macrocode} \cs_new_eq:NN \@@_debug:n \use_none:n \cs_new_protected:Npn \hook_debug_on: { \bool_gset_true:N \g_@@_debug_bool \@@_debug_gset: } \cs_new_protected:Npn \hook_debug_off: { \bool_gset_false:N \g_@@_debug_bool \@@_debug_gset: } \cs_new_protected:Npn \@@_debug_gset: { \cs_gset_protected:Npx \@@_debug:n ##1 { \bool_if:NT \g_@@_debug_bool {##1} } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % % % \subsection{Borrowing from internals of other kernel modules} % % % \begin{macro}[EXP]{\@@_str_compare:nn} % Private copy of \cs{__str_if_eq:nn} % \InternalDetectionOff % \begin{macrocode} \cs_new_eq:NN \@@_str_compare:nn \__str_if_eq:nn % \end{macrocode} % \InternalDetectionOn % \end{macro} % % \subsection{Declarations} % % \begin{macro}{\l_@@_tmpa_bool} % Scratch boolean used throughout the package. % \begin{macrocode} \bool_new:N \l_@@_tmpa_bool % \end{macrocode} % \end{macro} % % \begin{macro}{\l_@@_return_tl,\l_@@_tmpa_tl,\l_@@_tmpb_tl} % Scratch variables used throughout the package. % \begin{macrocode} \tl_new:N \l_@@_return_tl \tl_new:N \l_@@_tmpa_tl \tl_new:N \l_@@_tmpb_tl % \end{macrocode} % \end{macro} % % \begin{macro}{\g_@@_all_seq} % In a few places we need a list of all hook names ever defined so % we keep track if them in this sequence. % \begin{macrocode} \seq_new:N \g_@@_all_seq % \end{macrocode} % \end{macro} % % \begin{macro}{\l_@@_cur_hook_tl} % Stores the name of the hook currently being sorted. % \begin{macrocode} \tl_new:N \l_@@_cur_hook_tl % \end{macrocode} % \end{macro} % % \begin{macro}{\l_@@_work_prop} % A property list holding a copy of the % \cs[no-index]{g_@@_\meta{hook}_code_prop} of the hook being sorted % to work on, so that changes don't act destructively on the hook data % structure. % \begin{macrocode} \prop_new:N \l_@@_work_prop % \end{macrocode} % \end{macro} % % \begin{macro}{\g_@@_used_prop} % All hooks that receive code (for use in debugging display). % \begin{macrocode} \prop_new:N \g_@@_used_prop % \end{macrocode} % \end{macro} % % \begin{macro}{\g_@@_hook_curr_name_tl,\g_@@_name_stack_seq} % Default label used for hook commands, and a stack to keep track of % packages within packages. % \begin{macrocode} \tl_new:N \g_@@_hook_curr_name_tl \seq_new:N \g_@@_name_stack_seq % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_tmp:w} % Temporary macro for generic usage. % \begin{macrocode} \cs_new_eq:NN \@@_tmp:w ? % \end{macrocode} % \end{macro} % % \begin{macro}{\c_@@_empty_tl} % \begin{macro}{\c_@@_nine_parameters_tl} % An empty token list, and one containing nine parameters. % \changes{v1.1a}{2023/04/06} % {Add auxiliary token lists (hook-args).} % \begin{macrocode} \tl_const:Nn \c_@@_empty_tl { } \tl_const:Nn \c_@@_nine_parameters_tl { #1#2#3#4#5#6#7#8#9 } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[int]{ % \tl_gremove_once:Nx, % \tl_show:x, % \tl_log:x, % \tl_set:Ne, % \cs_replacement_spec:c, % \prop_put:Nne, % \str_count:e % } % Some variants of \pkg{expl3} functions. % \fmiinline{should probably be moved to expl3} % \begin{macrocode} \cs_generate_variant:Nn \tl_gremove_once:Nn { Nx } \cs_generate_variant:Nn \tl_show:n { x } \cs_generate_variant:Nn \tl_log:n { x } \cs_generate_variant:Nn \tl_set:Nn { Ne } \cs_generate_variant:Nn \cs_replacement_spec:N { c } \cs_generate_variant:Nn \prop_put:Nnn { Nne } \cs_generate_variant:Nn \str_count:n { e } % \end{macrocode} % \end{macro} % % \begin{macro}{\s_@@_mark} % Scan mark used for delimited arguments. % \begin{macrocode} \scan_new:N \s_@@_mark % \end{macrocode} % \end{macro} % % \begin{macro}{ % \@@_use_none_delimit_by_s_mark:w, % \@@_use_i_delimit_by_s_mark:nw % } % Removes tokens until the next \cs{s_@@_mark}. % \changes{v1.1a}{2023/04/06} % {Use standard naming scheme (hook-args).} % \begin{macrocode} \cs_new:Npn \@@_use_none_delimit_by_s_mark:w #1 \s_@@_mark { } \cs_new:Npn \@@_use_i_delimit_by_s_mark:nw #1 #2 \s_@@_mark {#1} % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_tl_set:cn} % Private copies of a few \pkg{expl3} functions. \pkg{l3debug} will % only add debugging to the public names, not to these copies, so we % don't have to use \cs{debug_suspend:} and \cs{debug_resume:} % everywhere. % % Functions like \cs{@@_tl_set:Nn} have to be redefined, rather than % copied because in \pkg{expl3} they use % \cs[no-index]{__kernel_tl_(g)set:Nx}, which is also patched by % \pkg{l3debug}. % \changes{v1.0h}{2021/01/07}{Manually define some \pkg{l3tl} commands % to work around \pkg{expl3} changes} % \changes{v1.1a}{2023/04/06} % {Clean-up unused variants (hook-args).} % \begin{macrocode} \cs_new_protected:Npn \@@_tl_set:cn #1#2 { \cs_set_nopar:cpx {#1} { \__kernel_exp_not:w {#2} } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_tl_gset:Nn,\@@_tl_gset:Nx, % \@@_tl_gset:cn,\@@_tl_gset:co,\@@_tl_gset:cx} % Same as above. % \begin{macrocode} \cs_new_protected:Npn \@@_tl_gset:Nn #1#2 { \cs_gset_nopar:Npx #1 { \__kernel_exp_not:w {#2} } } \cs_new_protected:Npn \@@_tl_gset:Nx #1#2 { \cs_gset_nopar:Npx #1 {#2} } \cs_generate_variant:Nn \@@_tl_gset:Nn { c, co } \cs_generate_variant:Nn \@@_tl_gset:Nx { c } % \end{macrocode} % \end{macro} % % \begin{macro}{ % \@@_tl_gput_right:Nn, % \@@_tl_gput_right:Ne, % \@@_tl_gput_right:cn, % } % Same as above. % \begin{macrocode} \cs_new_protected:Npn \@@_tl_gput_right:Nn #1#2 { \@@_tl_gset:Nx #1 { \__kernel_exp_not:w \exp_after:wN { #1 #2 } } } \cs_generate_variant:Nn \@@_tl_gput_right:Nn { Ne, cn } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_tl_gput_left:Nn} % Same as above. % \begin{macrocode} \cs_new_protected:Npn \@@_tl_gput_left:Nn #1#2 { \@@_tl_gset:Nx #1 { \__kernel_exp_not:w {#2} \__kernel_exp_not:w \exp_after:wN {#1} } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_tl_gset_eq:NN} % Same as above. % \begin{macrocode} \cs_new_eq:NN \@@_tl_gset_eq:NN \tl_gset_eq:NN % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_tl_gclear:N,\@@_tl_gclear:c} % Same as above. % \begin{macrocode} \cs_new_protected:Npn \@@_tl_gclear:N #1 { \@@_tl_gset_eq:NN #1 \c_empty_tl } \cs_generate_variant:Nn \@@_tl_gclear:N { c } % \end{macrocode} % \end{macro} % % % \subsection{Providing new hooks} % % \subsubsection{The data structures of a hook} % % \DescribeMacro{\g_@@_\meta{hook}_code_prop} % \DescribeMacro{\@@\textvisiblespace\meta{hook}} % \DescribeMacro{\g_@@_\meta{hook}_reversed_tl} % \DescribeMacro{\g_@@_\meta{hook}_declared_tl} % \DescribeMacro{\g_@@_\meta{hook}_parameter_tl} % \DescribeMacro{\@@_next\textvisiblespace\meta{hook}} % \DescribeMacro{\@@_toplevel\textvisiblespace\meta{hook}} % Hooks have a name (called \meta{hook} in the description below) % and for each hook we have to % provide a number of data structures. These are % \begin{description} % \item[\cs{g_@@_\meta{hook}_code_prop}] A property list holding the code % for the hook in separate chunks. The keys are by default the % package names that add code to the hook, but it is possible % for packages to define other keys. % % \item[{\cs[no-index]{g_@@_\meta{hook}_rule_\meta{label1}\string|\meta{label2}_tl}}] % A token list holding the relation between \meta{label1} and % \meta{label2} in the \meta{hook}. The \meta{labels} are lexically % (reverse) sorted to ensure that two labels always point to the same % token list. For global rules, the \meta{hook} name is \texttt{??}. % % \item[\cs{@@\textvisiblespace\meta{hook}}] The code that is actually executed % when the hook is called in the document is stored in this token % list. It is constructed from the code chunks applying the % information. % This token list is named like that so that in case of an error % inside the hook, the reported token list in the error is shorter, % and to make it simpler to normalize hook names in % \cs{@@_make_name:n}. % % \item[\cs{g_@@_\meta{hook}_reversed_tl}] Some hooks are % \enquote{reversed}. This token list stores a |-| for such hook % so that it can be identified. The |-| character is used because % $\meta{reversed}1$ is $+1$ for normal hooks and $-1$ for reversed % ones. % % \item[{\cs[no-index]{g_@@_\meta{hook}_declared_tl}}] This token % list serves as a marker for the hook being officially declared. Its % existence is tested to raise an error in case another declaration % is attempted. % % \item[{\cs[no-index]{c_@@_\meta{hook}_parameter_tl}}] This token % list stores the parameter text for a declared hook (its existence % almost completely intersects the token list above), which is used % for managing hooks with arguments. % % \item[\cs{@@_toplevel\textvisiblespace\meta{hook}}] This token list stores the code % inserted in the hook from the user's document, in the |top-level| % label. This label is special, and doesn't participate in sorting. % Instead, all code is appended to it and executed after (or before, % if the hook is reversed) the normal % hook code, but before the |next| code chunk. % % \item[\cs{@@_next\textvisiblespace\meta{hook}}] Finally there is % extra code (normally empty) that is used on the next invocation % of the hook (and then deleted). This can be used to define some % special behavior for a single occasion from within the document. % This token list follows the same naming scheme than the main % \cs{@@\textvisiblespace\meta{hook}} token list. It is called % \cs{@@_next\textvisiblespace\meta{hook}} rather than % \cs[no-index]{@@\textvisiblespace next_\meta{hook}} because % otherwise a hook whose name is |next_|\meta{hook} would clash % with the next code-token list of the hook called \meta{hook}. % % \end{description} % % % \subsubsection{On the existence of hooks} % \label{sec:existence} % % A hook may be in different states of existence. Here we give an % overview of the internal commands to set up hooks and explain how the % different states are distinguished. The actual implementation % then follows in subsequent sections. % % One problem we have to solve is that we need to be able to add % code to hooks (e.g., with \cs{AddToHook}) even if that code has % not yet been declared. For example, one package needs to write % into a hook of another package, but that package may not get % loaded, or is loaded only later. Another problem is that most hooks, % but not the generic hooks, require a declaration. % % We therefore distinguish the following states for a hook, which % are managed by four different tests: structure existence % (\cs{@@_if_structure_exist:nTF}), creation % (\cs{@@_if_usable:nTF}), declaration (\cs{@@_if_declared:nTF}) % and disabled or not (\cs{@@_if_disabled:nTF}) % \begin{description} % \setlist[itemize]{leftmargin=5cm,format=\cs} % \item[not existing] % % Nothing is known about the hook so far. This state can be % detected with \cs{@@_if_structure_exist:nTF} % (which uses the false branch). % % In this state the hook can be declared, disabled, rules can be % defined or code could be added to it, but it is not possible % to use the hook (with \cs{UseHook}). % \item[basic data structure set up] % % A hook is this state when its basic data structure has been % set up (using \cs{@@_init_structure:n}). The data structure setup happens % automatically when commands such as \cs{AddToHook} are used % and the hook is at that point in state \enquote{not existing}. % % In this state the four tests give the following results: % \begin{itemize} % \item [@@_if_structure_exist:nTF] returns |true|. % \item [@@_if_usable:nTF] returns |false|. % \item [@@_if_declared:nTF] returns |false|. % \item [@@_if_disabled:nTF] returns |false|. % \end{itemize} % % The allowed actions are the same as in the \enquote{not % existing} state. % % \item[declared] % % A hook is in this state it is not disabled and was explicitly declared (e.g., % with \cs{NewHook}). In this case the four tests give the % following results: % \begin{itemize} % \item [@@_if_structure_exist:nTF] returns |true|. % \item [@@_if_usable:nTF] returns |true|. % \item [@@_if_declared:nTF] returns |true|. % \item [@@_if_disabled:nTF] returns |false|. % \end{itemize} % % \item[usable] % % A hook is in this state if it is not disabled, was not % explicitly declared but nevertheless is allowed to be used % (with \cs{UseHook} or \cs{hook_use:n}). This state is only % possible for generic hooks as they do not need to be % declared. Therefore such hooks move directly from state % \enquote{not existing} to \enquote{usable} the moment a % declaration such as \cs{AddToHook} wants to add to the hook % data structure. In this state the tests give the following % results: % \begin{itemize} % \item [@@_if_structure_exist:nTF] returns |true|. % \item [@@_if_usable:nTF] returns |true|. % \item [@@_if_declared:nTF] returns |false|. % \item [@@_if_disabled:nTF] returns |false|. % \end{itemize} % % % \item[disabled] % % A generic hook in any state is moved to this state when % \cs{DisableGenericHook} is used. This changes the tests to give the % following results: % \begin{itemize} % \item [@@_if_structure_exist:nTF] \emph{unchanged}. % \item [@@_if_usable:nTF] returns |false|. % \item [@@_if_declared:nTF] returns |true|. % \item [@@_if_disabled:nTF] returns |true|. % \end{itemize} % The structure test is unchanged (if the hook was unknown before it is % |false|, otherwise |true|). The usable test returns |false| so that % any \cs{UseHook} will bypass the hook from now on. The % declared test returns true so that any further \cs{NewHook} % generates an error and the disabled test returns true so that % \cs{AddToHook} can return an error. % \fmiinline{maybe it should do this only after begin document?} % % \end{description} % % % % % \subsubsection{Setting hooks up} % % % \begin{macro}{ % \hook_new:n, % \hook_new_with_args:nn % } % \begin{macro}{\@@_new:nn} % The \cs{hook_new:n} declaration declares a new hook and expects % the hook \meta{name} as its argument, e.g., % \hook{begindocument}. % \changes{v1.1a}{2023/04/06} % {Add \cs{hook_new_with_args:nn} (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\hook_new_with_args:nn} % {Hooks~with~args} \cs_new_protected:Npn \hook_new:n #1 { \@@_normalize_hook_args:Nn \@@_new:nn {#1} { 0 } } \cs_new_protected:Npn \hook_new_with_args:nn #1 #2 { \@@_normalize_hook_args:Nn \@@_new:nn {#1} {#2} } % \end{macrocode} % % \begin{macrocode} \cs_new_protected:Npn \@@_new:nn #1 #2 { % \end{macrocode} % We check if the hook was already \emph{explicitly} declared with % \cs{hook_new:n}, and if it already exists we complain, otherwise set % the \enquote{created} flag for the hook so that it errors next time % \cs{hook_new:n} is used. % \changes{v1.1d}{2023/05/21} % {Changes to allow support arguments in cmd hooks (cmd-args).} % \begin{macrocode} \@@_if_declared:nTF {#1} { \msg_error:nnn { hooks } { exists } {#1} } { \tl_new:c { g_@@_#1_declared_tl } \cs_undefine:c { @@~#1 } \cs_undefine:c { c_@@_#1_parameter_tl } \@@_make_usable:nn {#1} {#2} % \end{macrocode} % In case there is already code in a hook, but it's undeclared, run % \cs{@@_update_hook_code:n} to make it ready to be executed (see test % \texttt{lthooks-034}). % \changes{v1.1a}{2023/04/06} % {Update hook code after declaring.} % \begin{macrocode} \@@_update_hook_code:n {#1} } } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % \begin{macrocode} %\IncludeInRelease{2020/10/01}{\hook_new_with_args:nn} % {Hooks~with~args} %\cs_gset_protected:Npn \hook_new:n #1 % { \@@_normalize_hook_args:Nn \@@_new:n {#1} } %\cs_undefine:N \@@_new:nn %\cs_gset_protected:Npn \@@_new:n #1 % { % \@@_if_declared:nTF {#1} % { \msg_error:nnn { hooks } { exists } {#1} } % { % \tl_new:c { g_@@_#1_declared_tl } % \@@_make_usable:n {#1} % } % } %\cs_gset_protected:Npn \hook_new_with_args:nn #1 { } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % % % \begin{macro}{\@@_make_usable:nn} % % This initializes all hook data structures for the hook but if % used on its own doesn't mark the hook as declared (as % \cs{hook_new:n} does, so a later \cs{hook_new:n} on that hook will % not result in an error. This command is internally used by % \cs{hook_gput_code:nnn} when adding code to a generic hook. % \changes{v1.1a}{2023/04/06} % {Changes to add hook arguments (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\@@_make_usable:nn} % {Hooks~with~args} \cs_new_protected:Npn \@@_make_usable:nn #1 #2 { % \end{macrocode} % Now we check if the hook's data structure can be safely created % without \pkg{expl3} raising errors, then % we add the hook name to the list of all hooks and % allocate the necessary data structures for the new hook, % otherwise just do nothing. % \begin{macrocode} \@@_if_usable:nF {#1} { \seq_gput_right:Nn \g_@@_all_seq {#1} % \end{macrocode} % Here we'll define the \cs[no-index]{c_@@_\meta{hook}_parameter_tl} % to hold a run of parameters up to the number of arguments of the % hook (\verb|#2|). % \begin{macrocode} \__kernel_cs_parm_from_arg_count:nnF { \tl_const:cn { c_@@_#1_parameter_tl } } {#2} { \msg_error:nnnn { hooks } { too-many-args } {#1} {#2} \tl_const:cx { c_@@_#1_parameter_tl } { \exp_not:V \c_@@_nine_parameters_tl } } % \end{macrocode} % After that, use \cs{@@_normalise_cs_args:nn} to correct the number % of parameters of the macros % \cs[no-index]{@@_toplevel\textvisiblespace\meta{hook}} and % \cs[no-index]{@@_next\textvisiblespace\meta{hook}}. We need to be % able to add % code with arguments to a hook without prior knowledge of the number % of arguments of that hook, so \pkg{lthooks} assumes~9 until the % hook is properly declared and the number of arguments is known. % \cs{@@_normalise_cs_args:nn} does the normalisation by using the % \cs[no-index]{c_@@_\meta{hook}_parameter_tl} defined just above. % \begin{macrocode} \@@_normalise_cs_args:nn { _toplevel } {#1} \@@_normalise_cs_args:nn { _next } {#1} % \end{macrocode} % This is only used by the actual code of the current hook, so % declare it normally: % \begin{macrocode} \@@_code_gset:nn {#1} { } % \end{macrocode} % Now ensure that the base data structure for the hook exists: % \begin{macrocode} \@@_init_structure:n {#1} % \end{macrocode} % The call to \cs{@@_normalise_code_pool:n} will correct any improper % reference to arguments that don't exist in the hook, raising a % low-level \TeX{} error and doubling the offending parameter tokens. % It has to be done after \cs{@@_init_structure:n} because it % operates on \cs[no-index]{g_@@_\meta{hook}_code_prop}. % \begin{macrocode} \@@_normalise_code_pool:n {#1} % \end{macrocode} % The \cs{g_@@_\meta{hook}_labels_clist} holds the sorted list of % labels (once it got sorted). This is used only for debugging. % These are defined conditionally, in case \cs{@@_make_usable:nn} % is being used to redefine a hook. % \changes{v1.1d}{2023/05/21} % {Changes to allow support arguments in cmd hooks (cmd-args).} % \begin{macrocode} \clist_if_exist:cF { g_@@_#1_labels_clist } { \clist_new:c { g_@@_#1_labels_clist } % \end{macrocode} % Some hooks should reverse the default order of code chunks. To % signal this we have a token list which is empty for normal hooks % and contains a \verb=-= for reversed hooks. % \begin{macrocode} \tl_new:c { g_@@_#1_reversed_tl } } % \end{macrocode} % The above is all in L3 convention, but we also provide an % interface to legacy \LaTeXe{} hooks of the form \cs{@...hook}, % e.g., \cs{@begindocumenthook}. % there have been a few of them and they have been added to % using \cs{g@addto@macro}. If there exists such a macro matching % the name of the new hook, i.e., % \verb+\@+\meta{hook-name}\texttt{hook} and it is not empty then % we add its contents as a code chunk under the label \texttt{legacy}. % \begin{quote} % \textbf{Warning: this support will vanish in future releases!} % \end{quote} % % \begin{macrocode} \@@_include_legacy_code_chunk:n {#1} } } %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2020/10/01}{\@@_make_usable:nn} % {Hooks~with~args} %\cs_undefine:N \@@_make_usable:nn %\cs_gset_protected:Npn \@@_make_usable:n #1 % { % \tl_if_exist:cF { @@~#1 } % { % \seq_gput_right:Nn \g_@@_all_seq {#1} % \tl_new:c { @@~#1 } % \@@_init_structure:n {#1} % \clist_new:c { g_@@_#1_labels_clist } % \tl_new:c { g_@@_#1_reversed_tl } % \@@_include_legacy_code_chunk:n {#1} % } % } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_init_structure:n} % This function declares the basic data structures for a hook % without explicit declaring the hook itself. This is needed to % allow adding to undeclared hooks. Here it is unnecessary to % check whether all variables exist, since all three are declared % at the same time (either all of them exist, or none). % % It creates the hook code pool % (\cs[no-index]{g_@@_\meta{hook}_code_prop}) and the |top-level| % and |next| token lists. A hook is initialized with % \cs{@@_init_structure:n} the first time anything is added to it. % Initializing a hook just with \cs{@@_init_structure:n} will not % make it usable with \cs{hook_use:n}. % \changes{v1.1a}{2023/04/06} % {Changes to add hook arguments (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\@@_init_structure:n} % {Hooks~with~args} \cs_new_protected:Npn \@@_init_structure:n #1 { \@@_if_structure_exist:nF {#1} { \prop_new:c { g_@@_#1_code_prop } \@@_toplevel_gset:nn {#1} { } \@@_next_gset:nn {#1} { } } } %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2020/10/01}{\@@_init_structure:n} % {Hooks~with~args} %\cs_gset_protected:Npn \@@_init_structure:n #1 % { % \@@_if_structure_exist:nF {#1} % { % \prop_new:c { g_@@_#1_code_prop } % \tl_new:c { @@_toplevel~#1 } % \tl_new:c { @@_next~#1 } % } % } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % \begin{macro}{ % \hook_new_reversed:n, % \hook_new_reversed_with_args:nn % } % \begin{macro}{\@@_new_reversed:nn} % % Declare a new hook. The default ordering of code chunks is % reversed, signaled by setting the token list to a minus sign. % \changes{v1.1a}{2023/04/06} % {Add \cs{hook_new_reversed_with_args:nn} (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\hook_new_reversed_with_args:nn} % {Hooks~with~args} \cs_new_protected:Npn \hook_new_reversed:n #1 { \@@_normalize_hook_args:Nn \@@_new_reversed:nn {#1} { 0 } } \cs_new_protected:Npn \hook_new_reversed_with_args:nn #1 #2 { \@@_normalize_hook_args:Nn \@@_new_reversed:nn {#1} {#2} } \cs_new_protected:Npn \@@_new_reversed:nn #1 #2 { \@@_if_declared:nTF {#1} { \msg_error:nnn { hooks } { exists } {#1} } { \@@_new:nn {#1} {#2} \tl_gset:cn { g_@@_#1_reversed_tl } { - } } } %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2020/10/01}{\hook_new_reversed_with_args:nn} % {Hooks~with~args} %\cs_gset_protected:Npn \hook_new_reversed:n #1 % { \@@_normalize_hook_args:Nn \@@_new_reversed:n {#1} } %\cs_undefine:N \@@_new_reversed:nn %\cs_gset_protected:Npn \@@_new_reversed:n #1 % { % \@@_new:n {#1} % \tl_gset:cn { g_@@_#1_reversed_tl } { - } % } %\cs_undefine:N \@@_new_reversed:nn %\cs_gset_protected:Npn \hook_new_reversed_with_args:nn #1 #2 { } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\hook_new_pair:nn,\hook_new_pair_with_args:nnn} % A shorthand for declaring a normal and a (matching) reversed hook in one go. % \changes{v1.1a}{2023/04/06} % {Add \cs{hook_new_pair_with_args:nnn} (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\hook_new_pair_with_args:nnn} % {Hooks~with~args} \cs_new_protected:Npn \hook_new_pair:nn #1#2 { \@@_normalize_hook_args:Nnn \@@_new_pair:nnn {#1} {#2} { 0 } } \cs_new_protected:Npn \hook_new_pair_with_args:nnn #1#2#3 { \@@_normalize_hook_args:Nnn \@@_new_pair:nnn {#1} {#2} {#3} } \cs_new_protected:Npn \@@_new_pair:nnn #1 #2 #3 { \@@_if_declared:nTF {#1} { \msg_error:nnn { hooks } { exists } {#1} } { \@@_if_declared:nTF {#2} { \msg_error:nnn { hooks } { exists } {#2} } { \@@_new:nn {#1} {#3} \@@_new_reversed:nn {#2} {#3} } } } %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2020/10/01}{\hook_new_pair_with_args:nnn} % {Hooks~with~args} %\cs_gset_protected:Npn \hook_new_pair:nn #1#2 % { % \hook_new:n {#1} % \hook_new_reversed:n {#2} % } %\cs_gset_protected:Npn \hook_new_pair_with_args:nnn #1#2#3 % { } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_include_legacy_code_chunk:n} % The \LaTeX{} legacy concept for hooks uses with hooks the % following naming scheme in the code: \cs{@...hook}. % % If this macro is not empty we add it under the label % \texttt{legacy} to the current hook and then empty it globally. % This way packages or classes directly manipulating commands such % as \cs{@begindocumenthook} still get their hook data added. % \begin{quote} % \textbf{Warning: this support will vanish in future releases!} % \end{quote} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\@@_include_legacy_code_chunk:n} % {Hooks~with~args} \cs_new_protected:Npn \@@_include_legacy_code_chunk:n #1 { % \end{macrocode} % If the macro doesn't exist (which is the usual case) then nothing % needs to be done. % \begin{macrocode} \tl_if_exist:cT { @#1hook } { % \end{macrocode} % Of course if the legacy hook exists but is empty, there is no need % to add anything under \texttt{legacy} the legacy label. % \begin{macrocode} \tl_if_empty:cF { @#1hook } { % \end{macrocode} % Here we set \cs{@@_replacing_args_false:} because no legacy code % will reference hook arguments. % \changes{v1.1b}{2023/04/16} % {\cs{@@_replacing_args_false:} in % \cs{@@_include_legacy_code_chunk:n}.} % \begin{macrocode} \@@_replacing_args_false: \use:e { \@@_hook_gput_code_do:nnn {#1} { legacy } { \exp_not:v { @#1hook } } } \@@_replacing_args_reset: % \end{macrocode} % Once added to the hook, we need to clear it otherwise it might % get added again later if the hook data gets updated. % \begin{macrocode} \@@_tl_gclear:c { @#1hook } } } } %\EndIncludeInRelease %\IncludeInRelease{2020/10/01}{\@@_include_legacy_code_chunk:n} % {Hooks~with~args} %\cs_gset_protected:Npn \@@_include_legacy_code_chunk:n #1 % { % \tl_if_exist:cT { @#1hook } % { % \tl_if_empty:cF { @#1hook } % { % \exp_args:Nnnv \@@_hook_gput_code_do:nnn % {#1} { legacy } { @#1hook } % \@@_tl_gclear:c { @#1hook } % } % } % } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % % % \subsubsection{Disabling and providing hooks} % % \changes{v1.0p}{2021/08/20}{Renames of generic hook commands (gh/638)} % % \begin{macro}{\hook_disable_generic:n} % \begin{macro}{\@@_disable:n} % \begin{macro}[pTF]{\@@_if_disabled:n} % % Disables a hook by creating its % \cs[no-index]{g_@@_\meta{hook}_declared_tl} so that the hook % errors when used with \cs{hook_new:n}, then it undefines % \cs{@@\textvisiblespace\meta{hook}} so that it may not be executed. % % This does not clear any code that may be already stored in the % hook's structure, but doesn't allow adding more code. % \cs{@@_if_disabled:nTF} uses that specific combination to check % if the hook is disabled. % % \begin{macrocode} %\IncludeInRelease{2021/06/01}{\hook_disable_generic:n} % {Disable~hooks} % \end{macrocode} % % \begin{macrocode} \cs_new_protected:Npn \hook_disable_generic:n #1 { \@@_normalize_hook_args:Nn \@@_disable:n {#1} } \cs_new_protected:Npn \@@_disable:n #1 { \tl_gclear_new:c { g_@@_#1_declared_tl } \cs_undefine:c { @@~#1 } } \prg_new_conditional:Npnn \@@_if_disabled:n #1 { p, T, F, TF } { \bool_lazy_and:nnTF { \tl_if_exist_p:c { g_@@_#1_declared_tl } } { ! \cs_if_exist_p:c { @@~#1 } } { \prg_return_true: } { \prg_return_false: } } %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2020/10/01}{\hook_disable_generic:n} % {Disable~hooks} % %\cs_new_protected:Npn \hook_disable_generic:n #1 {} % %\EndIncludeInRelease % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\hook_activate_generic:n} % \begin{macro}{\@@_activate_generic:n} % The \cs{hook_activate_generic:n} declaration declares a new hook if it % wasn't declared already, in which case it only checks that the % already existing hook is not a reversed hook. % \changes{v1.1a}{2023/04/06} % {Changes to add hook arguments (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\hook_activate_generic:n} % {Providing~hooks} % \end{macrocode} % % \begin{macrocode} \cs_new_protected:Npn \hook_activate_generic:n #1 { \@@_normalize_hook_args:Nn \@@_activate_generic:nn {#1} { } } % \end{macrocode} % % \begin{macrocode} \cs_new_protected:Npn \@@_activate_generic:nn #1 #2 { % \end{macrocode} % If the hook to be activated was disabled we warn (for now --- this % may change). % \begin{macrocode} \@@_if_disabled:nTF {#1} { \msg_warning:nnn { hooks } { activate-disabled } {#1} } % \end{macrocode} % Otherwise we check if the hook is not declared, and if it isn't, % figure out if it's reversed or not, then declare it accordingly. % \begin{macrocode} { \@@_if_declared:nF {#1} { \tl_new:c { g_@@_#1_declared_tl } \@@_make_usable:nn {#1} { 0 } \tl_gset:cx { g_@@_#1_reversed_tl } { \@@_if_generic_reversed:nT {#1} { - } } % \end{macrocode} % Reflect that we have activated the generic hook and set its % execution code. % \changes{v1.0v}{2022/06/15}{Ensure that a newly activated generic hook % gets its execution code set} % \begin{macrocode} \@@_update_hook_code:n {#1} } } } % \end{macrocode} % % \begin{macrocode} %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2021/06/01}{\hook_activate_generic:n} % {Providing~hooks} %\cs_gset_protected:Npn \@@_activate_generic:nn #1 #2 % { % \@@_if_disabled:nTF {#1} % { \msg_warning:nnn { hooks } { activate-disabled } {#1} } % { % \@@_if_declared:nF {#1} % { % \tl_new:c { g_@@_#1_declared_tl } % \@@_make_usable:n {#1} % \tl_gset:cx { g_@@_#1_reversed_tl } % { \@@_if_generic_reversed:nT {#1} { - } } % \@@_update_hook_code:n {#1} % } % } % } %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2020/10/01}{\hook_activate_generic:n} % {Providing~hooks} %\cs_gset_protected:Npn \hook_activate_generic:n #1 { } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % \end{macro} % % % \subsection{Parsing a label} % % \begin{macro}[EXP]{\@@_parse_label_default:n} % This macro checks if a label was given (not \cs{c_novalue_tl}), and % if so, tries to parse the label looking for a leading \verb|.| to % replace by \cs{@@_currname_or_default:}. % \begin{macrocode} \cs_new:Npn \@@_parse_label_default:n #1 { \tl_if_novalue:nTF {#1} { \@@_currname_or_default: } { \tl_trim_spaces_apply:nN {#1} \@@_parse_dot_label:n } } % \end{macrocode} % \end{macro} % % \begin{macro}[EXP]{\@@_parse_dot_label:n} % \begin{macro}[EXP]{ % \@@_parse_dot_label:w, % \@@_parse_dot_label_cleanup:w, % \@@_parse_dot_label_aux:w % } % Start by checking if the label is empty, which raises an error, and % uses the fallback value. If not, % split the label at a \verb|./|, if any, and check if no tokens are % before the \verb|./|, or if the only character is a \verb|.|. % If these requirements are fulfilled, the leading % \verb|.| is replaced with \cs{@@_currname_or_default:}. Otherwise % the label is returned unchanged. % \begin{macrocode} \cs_new:Npn \@@_parse_dot_label:n #1 { \tl_if_empty:nTF {#1} { \msg_expandable_error:nn { hooks } { empty-label } \@@_currname_or_default: } { \str_if_eq:nnTF {#1} { . } { \@@_currname_or_default: } { \@@_parse_dot_label:w #1 ./ \s_@@_mark } } } \cs_new:Npn \@@_parse_dot_label:w #1 ./ #2 \s_@@_mark { \tl_if_empty:nTF {#1} { \@@_parse_dot_label_aux:w #2 \s_@@_mark } { \tl_if_empty:nTF {#2} { \@@_make_name:n {#1} } { \@@_parse_dot_label_cleanup:w #1 ./ #2 \s_@@_mark } } } \cs_new:Npn \@@_parse_dot_label_cleanup:w #1 ./ \s_@@_mark {#1} \cs_new:Npn \@@_parse_dot_label_aux:w #1 ./ \s_@@_mark { \@@_currname_or_default: / \@@_make_name:n {#1} } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[EXP]{\@@_currname_or_default:} % This uses \cs{g_@@_hook_curr_name_tl} if it is set, otherwise it tries % \cs{@currname}. If neither is set, it raises an error and uses the % fallback value \verb|label-missing|. % \begin{macrocode} \cs_new:Npn \@@_currname_or_default: { \tl_if_empty:NTF \g_@@_hook_curr_name_tl { \tl_if_empty:NTF \@currname { \msg_expandable_error:nnn { latex2e } { should-not-happen } { Empty~default~label. } \@@_make_name:n { label-missing } } { \@currname } } { \g_@@_hook_curr_name_tl } } % \end{macrocode} % \end{macro} % % \begin{macro}[EXP]{\@@_make_name:n,\@@_make_name:w} % This provides a standard sanitization of a hook's name. % It uses \cs{cs:w} to build a control sequence out of the hook name, % then uses \cs{cs_to_str:N} to get the string representation of that, % without the escape character. \cs{cs:w}-based expansion is used % instead of |e|-based because Unicode characters don't behave well % inside \cs{expanded}. The macro adds the \cs[no-index]{@@\textvisiblespace} prefix to the % hook name to reuse the hook's code token list to build the csname % and avoid leaving \enquote{public} control sequences defined % (as~\cs[no-index]{relax}) in TeX's memory. % \begin{macrocode} \cs_new:Npn \@@_make_name:n #1 { \exp_after:wN \exp_after:wN \exp_after:wN \@@_make_name:w \exp_after:wN \token_to_str:N \cs:w @@~ #1 \cs_end: } \exp_last_unbraced:NNNNo \cs_new:Npn \@@_make_name:w #1 \tl_to_str:n { @@~ } { } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_normalize_hook_args:Nn} % \begin{macro}{\@@_normalize_hook_args:Nnn} % \begin{macro}{\@@_normalize_hook_rule_args:Nnnnn} % \begin{macro}{\@@_normalize_hook_args_aux:Nn} % This is the standard route for normalizing hook and label arguments. The main % macro does the entire operation within a group so that csnames made % by \cs{@@_make_name:n} are wiped off before continuing. This means % that this function cannot be used for \cs{hook_use:n}! % \begin{macrocode} \cs_new_protected:Npn \@@_normalize_hook_args_aux:Nn #1 #2 { \group_begin: \use:e { \group_end: \exp_not:N #1 #2 } } \cs_new_protected:Npn \@@_normalize_hook_args:Nn #1 #2 { \@@_normalize_hook_args_aux:Nn #1 { { \@@_parse_label_default:n {#2} } } } \cs_new_protected:Npn \@@_normalize_hook_args:Nnn #1 #2 #3 { \@@_normalize_hook_args_aux:Nn #1 { { \@@_parse_label_default:n {#2} } { \@@_parse_label_default:n {#3} } } } \cs_new_protected:Npn \@@_normalize_hook_rule_args:Nnnnn #1 #2 #3 #4 #5 { \@@_normalize_hook_args_aux:Nn #1 { { \@@_parse_label_default:n {#2} } { \@@_parse_label_default:n {#3} } { \tl_trim_spaces:n {#4} } { \@@_parse_label_default:n {#5} } } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\@@_curr_name_push:n,\@@_curr_name_push_aux:n} % \begin{macro}{\@@_curr_name_pop:} % \begin{macro}{\@@_end_document_label_check:} % The token list \cs{g_@@_hook_curr_name_tl} stores the name of the % current package/file to be used as the default label in hooks. % Providing a consistent interface is tricky because packages can % be loaded within packages, and some packages may not use % \cs{SetDefaultHookLabel} to change the default label (in which % case \cs{@currname} is used). % % To pull that one off, we keep a stack that contains the default % label for each level of input. The bottom of the stack contains the % default label for the |top-level| (this stack should never go % empty). If we're building the format, set the default label to be % |top-level|: % \begin{macrocode} \tl_gset:Nn \g_@@_hook_curr_name_tl { top-level } % \end{macrocode} % % Then, in case we're in \pkg{latexrelease} we push something on % the stack to support roll forward. But in some rare cases, % \pkg{latexrelease} may be loaded inside another package (notably % \pkg{platexrelease}), so we'll first push the |top-level| entry: % \changes{v1.0i}{2021/03/18} % {Only add \texttt{top-level} if not already there.} % \begin{macrocode} %\seq_if_empty:NT \g_@@_name_stack_seq % { \seq_gput_right:Nn \g_@@_name_stack_seq { top-level } } % \end{macrocode} % then we dissect the \cs{@currnamestack}, adding \cs{@currname} to % the stack: % \changes{v1.0f}{2020/11/24}{Support for roll forward (gh/434)} % \begin{macrocode} %\cs_set_protected:Npn \@@_tmp:w #1 #2 #3 % { % \quark_if_recursion_tail_stop:n {#1} % \seq_gput_right:Nn \g_@@_name_stack_seq {#1} % \@@_tmp:w % } %\exp_after:wN \@@_tmp:w \@currnamestack % \q_recursion_tail \q_recursion_tail % \q_recursion_tail \q_recursion_stop % \end{macrocode} % and finally set the default label to be the \cs{@currname}: % \changes{v1.0i}{2021/03/18} % {Remove the (empty) \enquote{top-level} from \cs{@currnamestack}.} % \begin{macrocode} %\tl_gset:Nx \g_@@_hook_curr_name_tl { \@currname } %\seq_gpop_right:NN \g_@@_name_stack_seq \l_@@_tmpa_tl % \end{macrocode} % % Two commands keep track of the stack: when a file is input, % \cs{@@_curr_name_push:n} pushes the current default label onto the % stack and sets the new default label (all in one go): % \begin{macrocode} \cs_new_protected:Npn \@@_curr_name_push:n #1 { \exp_args:Nx \@@_curr_name_push_aux:n { \@@_make_name:n {#1} } } \cs_new_protected:Npn \@@_curr_name_push_aux:n #1 { \tl_if_blank:nTF {#1} { \msg_error:nn { hooks } { no-default-label } } { \str_if_eq:nnTF {#1} { top-level } { \msg_error:nnnnn { hooks } { set-top-level } { to } { PushDefaultHookLabel } {#1} } { \seq_gpush:NV \g_@@_name_stack_seq \g_@@_hook_curr_name_tl \tl_gset:Nn \g_@@_hook_curr_name_tl {#1} } } } % \end{macrocode} % and when an input is over, the topmost item of the stack is popped, % since that label will not be used again, and \cs{g_@@_hook_curr_name_tl} % is updated to equal the now topmost item of the stack: % \begin{macrocode} \cs_new_protected:Npn \@@_curr_name_pop: { \seq_gpop:NNTF \g_@@_name_stack_seq \l_@@_return_tl { \tl_gset_eq:NN \g_@@_hook_curr_name_tl \l_@@_return_tl } { \msg_error:nn { hooks } { extra-pop-label } } } % \end{macrocode} % % At the end of the document we want to check if there was no % \cs{@@_curr_name_push:n} without a matching \cs{@@_curr_name_pop:} % (not a critical error, but it might indicate that something else is % not quite right): % \begin{macrocode} \tl_gput_right:Nn \@kernel@after@enddocument@afterlastpage { \@@_end_document_label_check: } \cs_new_protected:Npn \@@_end_document_label_check: { \seq_gpop:NNT \g_@@_name_stack_seq \l_@@_return_tl { \msg_error:nnx { hooks } { missing-pop-label } { \g_@@_hook_curr_name_tl } \tl_gset_eq:NN \g_@@_hook_curr_name_tl \l_@@_return_tl \@@_end_document_label_check: } } % \end{macrocode} % % The token list \cs{g_@@_hook_curr_name_tl} is but a mirror of the % top of the stack. % % \begin{macro}{\@@_set_default_hook_label:n,\@@_set_default_label:n} % Now define a wrapper that replaces the top of the stack with the % argument, and updates \cs{g_@@_hook_curr_name_tl} accordingly. % \begin{macrocode} \cs_new_protected:Npn \@@_set_default_hook_label:n #1 { \seq_if_empty:NTF \g_@@_name_stack_seq { \msg_error:nnnnn { hooks } { set-top-level } { for } { SetDefaultHookLabel } {#1} } { \exp_args:Nx \@@_set_default_label:n { \@@_make_name:n {#1} } } } \cs_new_protected:Npn \@@_set_default_label:n #1 { \str_if_eq:nnTF {#1} { top-level } { \msg_error:nnnnn { hooks } { set-top-level } { to } { SetDefaultHookLabel } {#1} } { \tl_gset:Nn \g_@@_hook_curr_name_tl {#1} } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \subsection{Adding or removing hook code} % % \begin{macro}{\hook_gput_code:nnn,\hook_gput_code_with_args:nnn} % \begin{macro}{ % \@@_gput_code:nnn, % \@@_gput_code_store:nnn, % \@@_hook_gput_code_do:nnn, % \@@_prop_gput_labeled_cleanup:nnn, % \@@_prop_gput_labeled_do:Nnnn % } % % With \cs{hook_gput_code:nnn}\Arg{hook}\Arg{label}\Arg{code} a % chunk of \meta{code} is added to an existing \meta{hook} labeled % with \meta{label}. % \changes{v1.0o}{2021/07/22}{Do not queue removals (gh/625)} % \changes{v1.1a}{2023/04/06} % {Add \cs{hook_gput_code_with_args:nnn} (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\hook_gput_code:nnn} % {Hooks~with~args} \cs_new_protected:Npn \hook_gput_code:nnn #1 #2 #3 { \@@_replacing_args_false: \@@_normalize_hook_args:Nnn \@@_gput_code:nnn {#1} {#2} {#3} \@@_replacing_args_reset: } \cs_new_protected:Npn \hook_gput_code_with_args:nnn #1 #2 #3 { \@@_replacing_args_true: \@@_normalize_hook_args:Nnn \@@_gput_code:nnn {#1} {#2} {#3} \@@_replacing_args_reset: } % \end{macrocode} % % If \cs{AddToHookWithArguments} was used, do some sanity checking, % and if it's not possible to use arguments at this point, fall back % to regular \cs{AddToHook} by using \cs{@@_replacing_args_false:}. % \changes{v1.1a}{2023/04/06} % {Changes to add hook arguments (hook-args).} % \begin{macrocode} \cs_new_protected:Npn \@@_gput_code:nnn #1 #2 #3 { \@@_chk_args_allowed:nn {#1} { AddToHook } % \end{macrocode} % Then check if the code should be executed immediately, rather than % stored: % \changes{v1.0r}{2021/09/06}{Use dedicated conditional (gh/606)} % \begin{macrocode} \@@_if_execute_immediately:nTF {#1} { % \end{macrocode} % \cs{AddToHookWithArguments} can't be used on one-time hooks (that % were already used). % \begin{macrocode} \@@_if_replacing_args:TF { \msg_error:nnnn { hooks } { one-time-args } {#1} { AddToHook } } { } \use:n } { \@@_gput_code_store:nnn {#1} {#2} } {#3} } % \end{macrocode} % % \begin{macrocode} \cs_new_protected:Npn \@@_gput_code_store:nnn #1 #2 #3 { % \end{macrocode} % Then check if the hook is usable. % \begin{macrocode} \@@_if_usable:nTF {#1} % \end{macrocode} % If so we simply add (or append) the new code to the property list % holding different chunks for the hook. At \verb=\begin{document}= % this is then sorted into a token list for fast execution. % \begin{macrocode} { \@@_hook_gput_code_do:nnn {#1} {#2} {#3} % \end{macrocode} % However, if there is an update within the document we need to alter % this execution code which is done by % \cs{@@_update_hook_code:n}. In the preamble this does nothing. % \begin{macrocode} \@@_update_hook_code:n {#1} } % \end{macrocode} % % If the hook is not usable, before giving up, check if it's not % disabled and otherwise try to declare it as a generic hook, if its % name matches one of the valid patterns. % \begin{macrocode} { \@@_if_disabled:nTF {#1} { \msg_error:nnn { hooks } { hook-disabled } {#1} } { \@@_try_declaring_generic_hook:nnn {#1} {#2} {#3} } } } % \end{macrocode} % % This macro will unconditionally add a chunk of code to the given hook. % \changes{v1.1a}{2023/04/06} % {Changes to add hook arguments (hook-args).} % \begin{macrocode} \cs_new_protected:Npn \@@_hook_gput_code_do:nnn #1 #2 #3 { % \end{macrocode} % However, first some debugging info if debugging is enabled: % \begin{macrocode} \@@_debug:n{\iow_term:x{****~ Add~ to~ \@@_if_usable:nF {#1} { undeclared~ } hook~ #1~ (#2) \on@line\space <-~ \tl_to_str:n{#3}} } % \end{macrocode} % Then try to get the code chunk labeled \verb=#2= from the hook. % If there's code already there, then append \verb=#3= to that, % otherwise just put \verb=#3=. If the current label is |top-level|, % the code is added to a dedicated token list % \cs{@@_toplevel\textvisiblespace\meta{hook}} that goes at the end of the % hook (or at the beginning, for a reversed hook), just before % \cs[no-index]{@@_next\textvisiblespace\meta{hook}}. % \begin{macrocode} \str_if_eq:nnTF {#2} { top-level } { \str_if_eq:eeTF { top-level } { \@@_currname_or_default: } { % \end{macrocode} % If the hook's basic structure does not exist, we need to declare it % with \cs{@@_init_structure:n}. % \begin{macrocode} \@@_init_structure:n {#1} % \end{macrocode} % Then append to the \verb|_toplevel| container for the hook. % \begin{macrocode} \@@_cs_gput_right:nnn { _toplevel } {#1} {#3} } { \msg_error:nnn { hooks } { misused-top-level } {#1} } } { % \end{macrocode} % When adding to the code pool, we have to double hashes if % \cs{AddToHook} was used (\verb|replacing_args| is false), so that % later it is turned into a single parameter token, rather than a % parameter to the hook macro. % \begin{macrocode} \exp_args:Nx \@@_prop_gput_labeled_cleanup:nnn { \@@_if_replacing_args:TF { \exp_not:n } { \@@_double_hashes:n } {#3} } {#1} {#2} } } % \end{macrocode} % % Adds code to a hook's code pool. % \changes{v1.1a}{2023/04/06} % {Macro added (hook-args).} % \begin{macrocode} \cs_new_protected:Npn \@@_prop_gput_labeled_cleanup:nnn #1 #2 #3 { \tl_set:Nn \l_@@_return_tl {#1} \@@_if_replacing_args:TF { \@@_if_usable:nT {#2} { \@@_set_normalise_fn:nn {#2} { Invalid~code~added~\msg_line_context: } \@@_normalise_fn:nn {#3} {#1} \prop_get:NnN \l_@@_work_prop {#3} \l_@@_return_tl } } { } \exp_args:NcV \@@_prop_gput_labeled_do:Nnn { g_@@_#2_code_prop } \l_@@_return_tl {#3} } \cs_new_protected:Npn \@@_prop_gput_labeled_do:Nnn #1 #2 #3 { \prop_get:NnNTF #1 {#3} \l_@@_return_tl { \prop_gput:Nno #1 {#3} { \l_@@_return_tl #2 } } { \prop_gput:Nnn #1 {#3} {#2} } } % \end{macrocode} % % \begin{macrocode} %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2020/10/01}{\hook_gput_code:nnn} % {Providing~hooks} %\cs_gset_protected:Npn \hook_gput_code:nnn #1 #2 % { \@@_normalize_hook_args:Nnn % \@@_gput_code:nnn {#1} {#2} } %\cs_gset_protected:Npn \@@_gput_code:nnn #1 #2 #3 % { % \@@_if_execute_immediately:nTF {#1} % {#3} % { % \@@_if_usable:nTF {#1} % { % \@@_hook_gput_code_do:nnn {#1} {#2} {#3} % \@@_update_hook_code:n {#1} % } % { % \@@_if_disabled:nTF {#1} % { \msg_error:nnn { hooks } { hook-disabled } {#1} } % { \@@_try_declaring_generic_hook:nnn % {#1} {#2} {#3} } % } % } % } %\cs_gset_protected:Npn \@@_hook_gput_code_do:nnn #1 #2 #3 % { % \@@_debug:n{\iow_term:x{****~ Add~ to~ % \@@_if_usable:nF {#1} { undeclared~ } % hook~ #1~ (#2) % \on@line\space <-~ \tl_to_str:n{#3}} } % \str_if_eq:nnTF {#2} { top-level } % { % \str_if_eq:eeTF { top-level } % { \@@_currname_or_default: } % { % \@@_init_structure:n {#1} % \@@_tl_gput_right:cn { @@_toplevel~#1 } {#3} % } % { \msg_error:nnn { hooks } { misused-top-level } {#1} } % } % { % \prop_get:cnNTF % { g_@@_#1_code_prop } {#2} \l_@@_return_tl % { % \prop_gput:cno { g_@@_#1_code_prop } {#2} % { \l_@@_return_tl #3 } % } % { \prop_gput:cnn { g_@@_#1_code_prop } {#2} {#3} } % } % } %\cs_gset_protected:Npn \hook_gput_code_with_args:nnn #1#2#3 { } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\@@_chk_args_allowed:nn} % This macro checks if it is possible to add code with references to % a hook's arguments for hook \verb|#1|. It only does something if % the function being run is \verb|replacing_args|. This macro will % error if the hook is declared and takes no arguments, then it will % set \cs{@@_replacing_args_false:} so that the macro which called it % will add the code normally. % \changes{v1.1a}{2023/04/06} % {Macro added (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\@@_chk_args_allowed:nn} % {Hooks~with~args} \cs_new_protected:Npn \@@_chk_args_allowed:nn #1 #2 { \@@_if_replacing_args:TF { \@@_if_declared:nT {#1} { \tl_if_empty:cT { c_@@_#1_parameter_tl } { \use_ii:nn } } \use_none:n { \msg_error:nnnn { hooks } { without-args } {#1} {#2} \@@_replacing_args_false: } } { } } %\EndIncludeInRelease %\IncludeInRelease{2020/10/01}{\@@_chk_args_allowed:nn} % {Hooks~with~args} %\cs_undefine:N \@@_chk_args_allowed:nn %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_gput_undeclared_hook:nnn} % Often it may happen that a package $A$ defines a hook \verb=foo=, % but package $B$, that adds code to that hook, is loaded before $A$. % In such case we need to add code to the hook before its declared. % An implicitly declared hook doesn't have arguments (in principle), % so use \cs{c_false_bool} here. % \begin{macrocode} \cs_new_protected:Npn \@@_gput_undeclared_hook:nnn #1 #2 #3 { \@@_init_structure:n {#1} \@@_hook_gput_code_do:nnn {#1} {#2} {#3} } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_try_declaring_generic_hook:nnn, % \@@_try_declaring_generic_next_hook:nn} % % These entry-level macros just pass the arguments along to the % common \cs{@@_try_declaring_generic_hook:nNNnn} with the right % functions to execute when some action is to be taken. % % The wrapper \cs{@@_try_declaring_generic_hook:nnn} then defers % \cs{hook_gput_code:nnn} if the generic hook was declared, or to % \cs{@@_gput_undeclared_hook:nnn} otherwise (the hook was tested for % existence before, so at this point if it isn't generic, it doesn't % exist). % % The wrapper \cs{@@_try_declaring_generic_next_hook:nn} for % next-execution hooks does the same: it defers the code to % \cs{hook_gput_next_code:nn} if the generic hook was declared, or % to \cs{@@_gput_next_do:nn} otherwise. % \changes{v1.0p}{2021/08/25}{Standardise generic hook names (gh/648)} % \changes{v1.1d}{2023/05/21} % {Changes to allow support arguments in cmd hooks (cmd-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01} % {\@@_try_declaring_generic_hook:nnn} % {Hooks~with~args} \cs_new_protected:Npn \@@_try_declaring_generic_hook:nnn #1 { \@@_try_declaring_generic_hook:wnTF #1 / / / \scan_stop: {#1} \@@_gput_code:nnn \@@_gput_undeclared_hook:nnn {#1} } \cs_new_protected:Npn \@@_try_declaring_generic_next_hook:nn #1 { \@@_try_declaring_generic_hook:wnTF #1 / / / \scan_stop: {#1} \@@_gput_next_code:nn \@@_gput_next_do:nn {#1} } %\EndIncludeInRelease %\IncludeInRelease{2021/11/15} % {\@@_try_declaring_generic_hook:nnn} % {Standardise~generic~hook~names} %\cs_gset_protected:Npn \@@_try_declaring_generic_hook:nnn #1 % { % \@@_try_declaring_generic_hook:wnTF #1 / / / \scan_stop: % {#1} % \hook_gput_code:nnn % \@@_gput_undeclared_hook:nnn % {#1} % } %\cs_gset_protected:Npn % \@@_try_declaring_generic_next_hook:nn #1 % { % \@@_try_declaring_generic_hook:wnTF #1 / / / \scan_stop: % {#1} % \hook_gput_next_code:nn % \@@_gput_next_do:nn % {#1} % } %\EndIncludeInRelease %\IncludeInRelease{2020/10/01} % {\@@_try_declaring_generic_hook:nnn} % {Standardise~generic~hook~names} %\cs_new_protected:Npn % \@@_try_declaring_generic_hook:nnn #1 % { % \@@_try_declaring_generic_hook:nNNnn {#1} % \hook_gput_code:nnn \@@_gput_undeclared_hook:nnn % } %\cs_new_protected:Npn % \@@_try_declaring_generic_next_hook:nn #1 % { % \@@_try_declaring_generic_hook:nNNnn {#1} % \hook_gput_next_code:nn \@@_gput_next_do:nn % } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_try_declaring_generic_hook:nNNnn, % \@@_try_declaring_generic_hook_split:nNNnn} % % \cs{@@_try_declaring_generic_hook:nNNnn} % now splits the hook name % at the first \texttt{/} (if any) and first checks if it is a % file-specific hook (they require some normalization) using % \cs{@@_if_file_hook:wTF}. If not then check it is one of a % predefined set for generic names. We also split off the second % component to see if we have to make a reversed hook. In either case % the function returns \meta{true} for a generic hook and \meta{false} % in other cases. % % \changes{v1.0s}{2021/09/28} % {Correct usage of older \cs{@@_if_file_hook:wTF} (gh/675)} % \changes{v1.1h}{2024/01/24} % {Correct usage of older \cs{@@_if_file_hook:wTF} (gh/1243)} % \begin{macrocode} %\cs_new_protected:Npn \@@_try_declaring_generic_hook:nNNnn #1 % { % \@@_if_file_hook:wTF #1 / / \s_@@_mark % { % \exp_args:Ne % \@@_try_declaring_generic_hook_split:nNNnn % { \exp_args:Ne \@@_file_hook_normalize:n {#1} } % } % { \@@_try_declaring_generic_hook_split:nNNnn {#1} } % } % \end{macrocode} % % \begin{macrocode} %\cs_new_protected:Npn % \@@_try_declaring_generic_hook_split:nNNnn #1 #2 #3 % { % \@@_try_declaring_generic_hook:wnTF #1 / / / \scan_stop: % {#1} % { #2 } % { #3 } {#1} % } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % \begin{macro}[TF]{\@@_try_declaring_generic_hook:wn} % \changes{v1.1a}{2023/04/06} % {Changes to add hook arguments (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01} % {\@@_try_declaring_generic_hook:wn} % {Hooks~with~args} \prg_new_protected_conditional:Npnn \@@_try_declaring_generic_hook:wn #1 / #2 / #3 / #4 \scan_stop: #5 { TF } { \@@_if_generic:nTF {#5} { \@@_if_usable:nF {#5} { % \end{macrocode} % If the hook doesn't exist yet we check if it is a \texttt{cmd} % hook and if so we attempt patching the command in addition to % declaring the hook. % % For some commands this will not be possible, in which case % \cs{@@_patch_cmd_or_delay:Nnn} (defined in \texttt{ltcmdhooks}) % will generate an appropriate error message. % \changes{v1.1d}{2023/05/21} % {Changes to allow support arguments in cmd hooks (cmd-args).} % \begin{macrocode} \str_if_eq:nnT {#1} { cmd } { \@@_try_put_cmd_hook:n {#5} \@@_make_usable:nn {#5} { 9 } \use_none:nnn } % \end{macrocode} % % Declare the hook always even if it can't really be used (error % message generated elsewhere). % % Here we use \cs{@@_make_usable:nn}, so that a \cs{hook_new:n} is % still possible later. Generic hooks (except \hook{cmd} hooks) take % no arguments, so use zero as the second argument. % \begin{macrocode} \@@_make_usable:nn {#5} { 0 } } \@@_if_generic_reversed:nT {#5} { \tl_gset:cn { g_@@_#5_reversed_tl } { - } } \prg_return_true: } { % \end{macrocode} % % Generic hooks are all named \meta{type}/\meta{name}/\meta{place}, % where \meta{type} and \meta{place} are predefined % (\cs{c_@@_generic_\meta{type}/./\meta{place}_tl}), and \meta{name} % is the variable component. Older releases had some hooks with the % \meta{name} in the third part, so the code below supports that % syntax for a while, with a warning. % % The \cs{exp_after:wN} |...| \cs{exp:w} trick is there to remove the % conditional structure inserted by % \cs{@@_try_declaring_generic_hook:wnTF} and thus allow access to the % tokens that follow it, as is needed to keep things going. % % When the deprecation cycle ends, the lines below should all be % replaced by \cs{prg_return_false:}. % \begin{macrocode} \@@_if_deprecated_generic:nTF {#5} { \@@_deprecated_generic_warn:n {#5} \exp_after:wN \@@_declare_deprecated_generic:NNn \exp:w % \exp_end: } { \prg_return_false: } } } % \end{macrocode} % % \begin{macro}{ % \@@_deprecated_generic_warn:Nn, % \@@_deprecated_generic_warn:Nw, % } % \cs{@@_deprecated_generic_warn:n} will issue a deprecation warning % for a given hook, and mark that hook such that the warning will not % be issued again (multiple warnings can be issued, but only once per % hook). % \begin{macrocode} \cs_new_protected:Npn \@@_deprecated_generic_warn:n #1 { \@@_deprecated_generic_warn:w #1 \s_@@_mark } \cs_new_protected:Npn \@@_deprecated_generic_warn:w #1 / #2 / #3 \s_@@_mark { \if_cs_exist:w @@~#1/#2/#3 \cs_end: \else: \msg_warning:nnnnn { hooks } { generic-deprecated } {#1} {#2} {#3} \fi: \cs_gset_eq:cN { @@~#1/#2/#3 } \scan_stop: } % \end{macrocode} % \end{macro} % % \begin{macro}{ % \@@_do_deprecated_generic:Nn, % \@@_do_deprecated_generic:Nw, % \@@_declare_deprecated_generic:NNw, % \@@_declare_deprecated_generic:NNw, % } % Now that the user has been told about the deprecation, we proceed by % swapping \meta{name} and \meta{place} and adding the code to the % correct hook. % \begin{macrocode} \cs_new_protected:Npn \@@_do_deprecated_generic:Nn #1 #2 { \@@_do_deprecated_generic:Nw #1 #2 \s_@@_mark } \cs_new_protected:Npn \@@_do_deprecated_generic:Nw #1 #2 / #3 / #4 \s_@@_mark { #1 { #2 / #4 / #3 } } \cs_new_protected:Npn \@@_declare_deprecated_generic:NNn #1 #2 #3 { \@@_declare_deprecated_generic:NNw #1 #2 #3 \s_@@_mark } \cs_new_protected:Npn \@@_declare_deprecated_generic:NNw #1 #2 #3 / #4 / #5 \s_@@_mark { \@@_try_declaring_generic_hook:wnTF #3 / #5 / #4 / \scan_stop: { #3 / #5 / #4 } #1 #2 { #3 / #5 / #4 } } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % \begin{macrocode} %\IncludeInRelease{2021/11/15} % {\@@_try_declaring_generic_hook:wn} % {Standardise~generic~hook~names} %\prg_new_protected_conditional:Npnn % \@@_try_declaring_generic_hook:wn % #1 / #2 / #3 / #4 \scan_stop: #5 { TF } % { % \@@_if_generic:nTF {#5} % { % \@@_if_usable:nF {#5} % { % \str_if_eq:nnT {#1} { cmd } % { \@@_try_put_cmd_hook:n {#5} } % \@@_make_usable:n {#5} % } % \@@_if_generic_reversed:nT {#5} % { \tl_gset:cn { g_@@_#5_reversed_tl } { - } } % \prg_return_true: % } % { % \@@_if_deprecated_generic:nTF {#5} % { % \@@_deprecated_generic_warn:n {#5} % \exp_after:wN \@@_declare_deprecated_generic:NNn % \exp:w % \exp_end: % } % { \prg_return_false: } % } % } %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2021/06/01} % {\@@_try_declaring_generic_hook:wn} % {Support~cmd~hooks} %\prg_new_protected_conditional:Npnn % \@@_try_declaring_generic_hook:wn % #1 / #2 / #3 / #4 \scan_stop: #5 { TF } % { % \tl_if_empty:nTF {#2} % { \prg_return_false: } % { % \prop_if_in:NnTF \c_@@_generics_prop {#1} % { % \@@_if_usable:nF {#5} % { % \str_if_eq:nnT {#1} { cmd } % { \@@_try_put_cmd_hook:n {#5} } % \@@_make_usable:n {#5} % } % \prop_if_in:NnTF % \c_@@_generics_reversed_ii_prop {#2} % { \tl_gset:cn { g_@@_#5_reversed_tl } { - } } % { % \prop_if_in:NnT % \c_@@_generics_reversed_iii_prop {#3} % { \tl_gset:cn { g_@@_#5_reversed_tl } { - } } % } % \prg_return_true: % } % { \prg_return_false: } % } % } %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2020/10/01} % {\@@_try_declaring_generic_hook:wn} % {Support~cmd~hooks} %\prg_new_protected_conditional:Npnn % \@@_try_declaring_generic_hook:wn % #1 / #2 / #3 / #4 \scan_stop: #5 { TF } % { % \tl_if_empty:nTF {#2} % { \prg_return_false: } % { % \prop_if_in:NnTF \c_@@_generics_prop {#1} % { % \@@_if_declared:nF {#5} { \hook_new:n {#5} } % \prop_if_in:NnTF % \c_@@_generics_reversed_ii_prop {#2} % { \tl_gset:cn { g_@@_#5_reversed_tl } { - } } % { % \prop_if_in:NnT % \c_@@_generics_reversed_iii_prop {#3} % { \tl_gset:cn { g_@@_#5_reversed_tl } { - } } % } % \prg_return_true: % } % { \prg_return_false: } % } % } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % % \begin{macro}[pTF]{\@@_if_file_hook:w} % \cs{@@_if_file_hook:wTF} checks if the argument is a valid % file-specific hook (not, for example, |file/before|, but % |file/foo.tex/before|). If it is a file-specific hook, then it % executes the \meta{true} branch, otherwise \meta{false}. % \begin{macrocode} %\IncludeInRelease{2021/11/15}{\@@_if_file_hook:w} % {Standardise~generic~hook~names} %\EndIncludeInRelease %\IncludeInRelease{2020/10/01}{\@@_if_file_hook:w} % {Standardise~generic~hook~names} %\prg_new_conditional:Npnn \@@_if_file_hook:w % #1 / #2 / #3 \s_@@_mark { TF } % { % \str_if_eq:nnTF {#1} { file } % { % \bool_lazy_or:nnTF % { \tl_if_empty_p:n {#3} } % { \str_if_eq_p:nn {#3} { / } } % { \prg_return_false: } % { % \prop_if_in:NnTF \c_@@_generics_file_prop {#2} % { \prg_return_true: } % { \prg_return_false: } % } % } % { \prg_return_false: } % } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % \begin{macro}[EXP]{\@@_file_hook_normalize:n} % \begin{macro}[EXP]{\@@_strip_double_slash:n,\@@_strip_double_slash:w} % % \begin{macrocode} %\IncludeInRelease{2021/11/15}{\@@_file_hook_normalize:n} % {Standardise~generic~hook~names} %\EndIncludeInRelease % \end{macrocode} % % When a file-specific hook is found, before being declared it is % lightly normalized by \cs{@@_file_hook_normalize:n}. The current % implementation just replaces two consecutive slashes (|//|) by a % single one, to cope with simple cases where the user did something % like \verb|\def\input@path{{./mypath/}}|, in which case a hook would % have to be \verb|\AddToHook{file/./mypath//file.tex/after}|. % \begin{macrocode} %\IncludeInRelease{2020/10/01}{\@@_file_hook_normalize:n} % {Standardise~generic~hook~names} %\cs_new:Npn \@@_file_hook_normalize:n #1 % { \@@_strip_double_slash:n {#1} } %\cs_new:Npn \@@_strip_double_slash:n #1 % { \@@_strip_double_slash:w #1 // \s_@@_mark } % \end{macrocode} % This function is always called after testing if the argument is a % file hook with \cs{@@_if_file_hook:wTF}, so we can assume it has % three parts (it is either \verb|file/.../before| or % \verb|file/.../after|), so we use \verb|#1/#2/#3 //| instead of just % \verb|#1 //| to prevent losing a slash if the file name is empty. % \changes{v1.0h}{2021/01/07}{Assume hook name has at least three % nonempty parts (gh/464)} % \begin{macrocode} %\cs_new:Npn \@@_strip_double_slash:w #1/#2/#3//#4\s_@@_mark % { % \tl_if_empty:nTF {#4} % { #1/#2/#3 } % { \@@_strip_double_slash:w #1/#2/#3 /#4\s_@@_mark } % } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{ % \c_@@_generic_cmd/./before_tl,\c_@@_generic_cmd/./after_tl, % \c_@@_generic_env/./before_tl,\c_@@_generic_env/./after_tl, % \c_@@_generic_file/./before_tl,\c_@@_generic_file/./after_tl, % \c_@@_generic_package/./before_tl,\c_@@_generic_package/./after_tl, % \c_@@_generic_class/./before_tl,\c_@@_generic_class/./after_tl, % \c_@@_generic_include/./before_tl,\c_@@_generic_include/./after_tl, % \c_@@_generic_env/./begin_tl,\c_@@_generic_env/./end_tl, % \c_@@_generic_include/./end_tl % } % Token lists defining the possible generic hooks. We don't provide % any user interface to this as this is meant to be static. % \begin{description} % \item[\texttt{cmd}] % The generic hooks used for commands. % \item[\texttt{env}] % The generic hooks used in \cs{begin} and \cs{end}. % \item[\texttt{file}, \texttt{package}, \texttt{class}, \texttt{include}] % The generic hooks used when loading a file % \end{description} % \begin{macrocode} %\IncludeInRelease{2021/11/15}{\c_@@_generics_prop} % {Standardise~generic~hook~names} \clist_map_inline:nn { cmd , env , file , package , class , include } { \tl_const:cn { c_@@_generic_#1/./before_tl } { + } \tl_const:cn { c_@@_generic_#1/./after_tl } { - } } \tl_const:cn { c_@@_generic_env/./begin_tl } { + } \tl_const:cn { c_@@_generic_env/./end_tl } { + } % \end{macrocode} % % \changes{v1.0t}{2022/04/01}{Support generic \texttt{include/.../excluded} hooks} % \begin{macrocode} \tl_const:cn { c_@@_generic_include/./end_tl } { - } \tl_const:cn { c_@@_generic_include/./excluded_tl } { + } % \end{macrocode} % % Deprecated generic hooks: % \begin{macrocode} \clist_map_inline:nn { file , package , class , include } { \tl_const:cn { c_@@_deprecated_#1/./before_tl } { } \tl_const:cn { c_@@_deprecated_#1/./after_tl } { } } \tl_const:cn { c_@@_deprecated_include/./end_tl } { } %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2020/10/01}{\c_@@_generics_prop} % {Standardise~generic~hook~names} %\prop_const_from_keyval:Nn \c_@@_generics_prop % {cmd=,env=,file=,package=,class=,include=} %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % \begin{macro}{\c_@@_generics_reversed_ii_prop, % \c_@@_generics_reversed_iii_prop, % \c_@@_generics_file_prop} % The following generic hooks are supposed to use reverse ordering % (the |ii| and |iii| names are kept for the deprecation cycle): % \begin{macrocode} %\IncludeInRelease{2021/11/15}{\c_@@_generics_reversed_ii_prop} % {Standardise~generic~hook~names} %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2020/10/01}{\c_@@_generics_reversed_ii_prop} % {Standardise~generic~hook~names} %\prop_const_from_keyval:Nn % \c_@@_generics_reversed_ii_prop {after=,end=} %\prop_const_from_keyval:Nn % \c_@@_generics_reversed_iii_prop {after=} %\prop_const_from_keyval:Nn % \c_@@_generics_file_prop {before=,after=} %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % % \begin{macro}{ % \c_@@_parameter_cmd/./before_tl,\c_@@_parameter_cmd/./after_tl, % } % Token lists defining the number of arguments for a given type of % generic hook. % \changes{v1.1d}{2023/05/21} % {Token lists added (cmd-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\c_@@_parameter_cmd/./before_tl} % {Hooks~with~args} % \end{macrocode} % % \hook{cmd} hooks are declared with 9 arguments because they have a % variable number of arguments (depending on the command they are % attached to), so we use the maximum here. % \begin{macrocode} \tl_const:cn { c_@@_parameter_cmd/./before_tl } { #1#2#3#4#5#6#7#8#9 } \tl_const:cn { c_@@_parameter_cmd/./after_tl } { #1#2#3#4#5#6#7#8#9 } % \end{macrocode} % % \begin{macrocode} %\EndIncludeInRelease %\IncludeInRelease{2020/10/01}{\c_@@_parameter_cmd/./before_tl} % {Hooks~with~args} %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % \begin{macro}{\hook_gremove_code:nn} % \begin{macro}{\@@_gremove_code:nn} % % With \cs{hook_gremove_code:nn}\Arg{hook}\Arg{label} any code % for \meta{hook} stored under \meta{label} is removed. % \changes{v1.0o}{2021/07/22}{Do not queue removals (gh/625)} % \changes{v1.1a}{2023/04/06} % {Changes to add hook arguments (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\hook_gremove_code:nn} % {Hooks~with~args} \cs_new_protected:Npn \hook_gremove_code:nn #1 #2 { \@@_normalize_hook_args:Nnn \@@_gremove_code:nn {#1} {#2} } \cs_new_protected:Npn \@@_gremove_code:nn #1 #2 { % \end{macrocode} % First check that the hook code pool exists. \cs{@@_if_usable:nTF} % isn't used here because it should be possible to remove code from a % hook before its defined (see section~\ref{sec:querying}). % \begin{macrocode} \@@_if_structure_exist:nTF {#1} { % \end{macrocode} % Then remove the chunk and run \cs{@@_update_hook_code:n} so % that the execution token list reflects the change if we are after % \verb=\begin{document}=. % % If all code is to be removed, clear the code pool % \cs{g_@@_\meta{hook}_code_prop}, the top-level code % \cs{@@_toplevel\textvisiblespace\meta{hook}}, and the next-execution code % \cs{@@_next\textvisiblespace\meta{hook}}. % \begin{macrocode} \str_if_eq:nnTF {#2} {*} { \prop_gclear:c { g_@@_#1_code_prop } \@@_toplevel_gset:nn {#1} { } \@@_next_gset:nn {#1} { } } { % \end{macrocode} % If the label is |top-level| then clear the token list, as all code % there is under the same label. % \begin{macrocode} \str_if_eq:nnTF {#2} { top-level } { \@@_toplevel_gset:nn {#1} { } } { \prop_gpop:cnNF { g_@@_#1_code_prop } {#2} \l_@@_return_tl { \msg_warning:nnnn { hooks } { cannot-remove } {#1} {#2} } } } % \end{macrocode} % Finally update the code, if the hook exists. % \begin{macrocode} \@@_if_usable:nT {#1} { \@@_update_hook_code:n {#1} } } % \end{macrocode} % % If the code pool for this hook doesn't exist, show a warning: % \begin{macrocode} { \@@_if_deprecated_generic:nTF {#1} { \@@_deprecated_generic_warn:n {#1} \@@_do_deprecated_generic:Nn \@@_gremove_code:nn {#1} {#2} } { \msg_warning:nnnn { hooks } { cannot-remove } {#1} {#2} } } } %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2020/10/01}{\hook_gremove_code:nn} % {Hooks~with~args} %\cs_new_protected:Npn \@@_gremove_code:nn #1 #2 % { % \@@_if_structure_exist:nTF {#1} % { % \str_if_eq:nnTF {#2} {*} % { % \prop_gclear:c { g_@@_#1_code_prop } % \@@_tl_gclear:c { @@_toplevel~#1 } % \@@_tl_gclear:c { @@_next~#1 } % } % { % \str_if_eq:nnTF {#2} { top-level } % { \@@_tl_gclear:c { @@_toplevel~#1 } } % { % \prop_gpop:cnNF { g_@@_#1_code_prop } % {#2} \l_@@_return_tl % { \msg_warning:nnnn { hooks } { cannot-remove } % {#1} {#2} } % } % } % \@@_if_usable:nT {#1} % { \@@_update_hook_code:n {#1} } % } % { % \@@_if_deprecated_generic:nTF {#1} % { % \@@_deprecated_generic_warn:n {#1} % \@@_do_deprecated_generic:Nn % \@@_gremove_code:nn {#1} {#2} % } % { \msg_warning:nnnn { hooks } { cannot-remove } % {#1} {#2} } % } % } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\@@_cs_gput_right:nnn} % \begin{macro}{\@@_cs_gput_right_fast:nnn,\@@_cs_gput_right_slow:nnn} % \begin{macro}{\@@_code_gset_auxi:nnnn,\@@_code_gset_auxi:eeen} % This macro is used to append code to the \verb|toplevel| and % \verb|next| token lists, trating them correctly depending on their % number of arguments, and depending if the code being added should % have parameter tokens understood as parameters, or doubled to be % stored as parameter tokens. % \changes{v1.1a}{2023/04/06} % {Macro added (hook-args).} % \changes{v1.1e}{2023/06/06} % {Short-circuit when the hook is declared without args (gh1078).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\@@_cs_gput_right:nnn} % {Hooks~with~args} % \end{macrocode} % % Check if the current hook is declared and takes no arguments. In % this case, we short-circuit and use the simpler and much faster % approach that doesn't require hash-doubling. % \begin{macrocode} \cs_new_protected:Npn \@@_cs_gput_right:nnn #1 #2 { \if:w T \@@_if_declared:nF {#2} { F } \tl_if_empty:cF { c_@@_#2_parameter_tl } { F } T \exp_after:wN \@@_cs_gput_right_fast:nnn \else: \exp_after:wN \@@_cs_gput_right_slow:nnn \fi: {#1} {#2} } \cs_new_protected:Npn \@@_cs_gput_right_fast:nnn #1 #2 #3 { \cs_gset:cpx { @@#1~#2 } { \exp_not:v { @@#1~#2 } \exp_not:n {#3} } } \cs_new_protected:Npn \@@_cs_gput_right_slow:nnn #1 #2 #3 { % \end{macrocode} % The auxiliary \cs{@@_code_gset_auxi:eeen} just does the assignment % at the end. Its first argument is the parameter text of the macro, % which is chosen here depending if % \cs[no-index]{c_@@_\meta{hook}_parameter_tl} exists, if the hook is % declared, and if it's a generic hook. % \begin{macrocode} \cs_if_exist:cF { @@#1~#2 } { \@@_code_gset_aux:nnn {#1} {#2} { } } \@@_code_gset_auxi:eeen { \@@_if_declared:nTF {#2} { \tl_use:c { c_@@_#2_parameter_tl } } { \@@_if_generic:nTF {#2} { \@@_generic_parameter:n {#2} } { \c_@@_nine_parameters_tl } } } % \end{macrocode} % Here we take the existing code in the macro, expand it with as many % arguments as it takes, then double the hashes so the code can be % reused. \pho{Maybe can be improved. The case of adding to an empty % cs can be optimised by quickly checking \cs{cs_replacement_spec}.} % \begin{macrocode} { \exp_args:NNo \exp_args:No \@@_double_hashes:n { \cs:w @@#1~#2 \exp_last_unbraced:Ne \cs_end: { \@@_braced_cs_parameter:n { @@#1~#2 } } } } % \end{macrocode} % Now the new code: if we are replacing arguments, then hashes are % left untouched, otherwise they are doubled. % \begin{macrocode} { \@@_if_replacing_args:TF { \exp_not:n } { \@@_double_hashes:n } {#3} } % \end{macrocode} % And finally, the csname which we'll define with all the above. % \begin{macrocode} { @@#1~#2 } } % \end{macrocode} % % And as promised, the auxiliary that does the definition. % \begin{macrocode} \cs_new_protected:Npn \@@_code_gset_auxi:nnnn #1 #2 #3 #4 { \cs_gset:cpn {#4} #1 { #2 #3 } } \cs_generate_variant:Nn \@@_code_gset_auxi:nnnn { eeen } % \end{macrocode} % % \begin{macrocode} %\EndIncludeInRelease %\IncludeInRelease{2020/10/01}{\@@_cs_gput_right:nnn} % {Hooks~with~args} %\cs_undefine:N \@@_cs_gput_right:nnn %\cs_undefine:N \@@_cs_gput_right_fast:nnn %\cs_undefine:N \@@_cs_gput_right_slow:nnn %\cs_undefine:N \@@_code_gset_auxi:nnnn %\EndIncludeInRelease % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{ % \@@_code_gset:nn,\@@_code_gset:ne, % \@@_toplevel_gset:nn, % \@@_next_gset:nn, % \@@_code_gset_aux:nnn % } % These macros define % \cs[no-index]{@@\meta{type}\textvisiblespace\meta{hook}} (with % \meta{type} being \verb|_next|, \verb|_toplevel|, or empty) with the % given code and the parameters stored in % \cs[no-index]{c_@@_\meta{hook}_parameter_tl} (or none, if that % doesn't exist). % \changes{v1.1a}{2023/04/06} % {Macro added (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\@@_code_gset:nn} % {Hooks~with~args} \cs_new_protected:Npn \@@_code_gset:nn { \@@_code_gset_aux:nnn { } } \cs_new_protected:Npn \@@_toplevel_gset:nn { \@@_code_gset_aux:nnn { _toplevel } } \cs_new_protected:Npn \@@_next_gset:nn { \@@_code_gset_aux:nnn { _next } } \cs_new_protected:Npn \@@_code_gset_aux:nnn #1 #2 #3 { \cs_gset:cpn { @@#1~#2 \exp_last_unbraced:Ne } { \@@_parameter:n {#2} } {#3} } \cs_generate_variant:Nn \@@_code_gset:nn { ne } % \end{macrocode} % % \begin{macrocode} %\EndIncludeInRelease %\IncludeInRelease{2020/10/01}{\@@_code_gset:nn} % {Hooks~with~args} %\cs_undefine:N \@@_code_gset:nn %\cs_undefine:N \@@_toplevel_gset:nn %\cs_undefine:N \@@_next_gset:nn %\cs_undefine:N \@@_code_gset_aux:nnn %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_normalise_cs_args:nn} % This macro normalises the parameters of the macros % \cs[no-index]{@@\meta{type}\textvisiblespace\meta{hook}} to take the % right number of arguments after a hook is declared. At this point % we know \cs[no-index]{c_@@_\meta{hook}_parameter_tl} exists, so use % that to count the arguments and use that as \meta{parameter text} % for the newly (re)defined macro. % \changes{v1.1a}{2023/04/06} % {Macro added (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\@@_normalise_cs_args:nn} % {Hooks~with~args} \cs_new_protected:Npn \@@_normalise_cs_args:nn #1 #2 { \cs_if_exist:cT { @@#1~#2 } { \@@_code_gset_auxi:eeen { \tl_use:c { c_@@_#2_parameter_tl } } { \exp_args:NNo \exp_args:No \@@_double_hashes:n { \cs:w @@#1~#2 \exp_last_unbraced:Ne \cs_end: { \@@_braced_cs_parameter:n { @@#1~#2 } } } } { } { @@#1~#2 } } } %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2020/10/01}{\@@_normalise_cs_args:nn} % {Hooks~with~args} %\cs_undefine:N \@@_normalise_cs_args:nn %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_normalise_code_pool:n} % \begin{macro}{\@@_set_normalise_fn:nn} % This one's a bit of a hack. It takes a hook, and iterates over its % code pool (\cs[no-index]{g_@@_\meta{hook}_code_prop}), redefining % each code label to use only valid arguments. This is used when, for % example, a code is added referencing arguments \verb|#1| and % \verb|#2|, but the hook has only \verb|#1|. In this example, every % reference to \verb|#2| is changed to \verb|##2|. This is done % because otherwise \TeX{} will throw a low-level error every time % some change happens to the hook (code is added, a rule is set, etc), % which can get quite repetitive for no good reason. % \changes{v1.1a}{2023/04/06} % {Macro added (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\@@_normalise_code_pool:n} % {Hooks~with~args} \cs_new_protected:Npn \@@_normalise_code_pool:n #1 { % \end{macrocode} % First, call \cs{@@_set_normalise_fn:nn} with the hook name to set % everything up, then we'll loop over the % hook's code pool applying the normalisation above. After that's % done, copy the temporary property list back to the hook's. % \begin{macrocode} \@@_set_normalise_fn:nn {#1} { Offending~label:~'##1' } \prop_clear:N \l_@@_work_prop \prop_map_function:cN { g_@@_#1_code_prop } \@@_normalise_fn:nn \prop_gset_eq:cN { g_@@_#1_code_prop } \l_@@_work_prop } % \end{macrocode} % % The sole purpose of this function is to define % \cs{@@_normalise_fn:nn}, which will then do the correcting of the % code being added to the hook. % \begin{macrocode} \cs_new_protected:Npn \@@_set_normalise_fn:nn #1 #2 { % \end{macrocode} % To start, we define two auxiliary token lists. % \cs[no-index]{l_@@_tmpb_tl} contains: %\begin{verbatim} % {\c__hook_hashes_tl 1} % {\c__hook_hashes_tl 2} % ... % {\c__hook_hashes_tl 9} %\end{verbatim} % \begin{macrocode} \cs_set:Npn \@@_tmp:w ##1##2##3##4##5##6##7##8##9 { } \tl_set:Ne \l_@@_tmpb_tl { \@@_braced_cs_parameter:n { @@_tmp:w } } \group_begin: \@@_tl_set:cn { c_@@_hash_tl } { \exp_not:N \c_@@_hashes_tl } \use:e { \group_end: \tl_set:Nn \exp_not:N \l_@@_tmpb_tl { \l_@@_tmpb_tl } } % \end{macrocode} % And \cs[no-index]{l_@@_tmpa_tl} contains: %\begin{verbatim} % {\c__hook_hash_tl 1} % {\c__hook_hash_tl 2} % ... % {\c__hook_hash_tl } %\end{verbatim} % with \meta{n} being the number of arguments declared for the hook. % \begin{macrocode} \exp_last_unbraced:NNf \cs_set:Npn \@@_tmp:w { \@@_parameter:n {#1} } { } \tl_set:Ne \l_@@_tmpa_tl { \@@_braced_cs_parameter:n { @@_tmp:w } } % \end{macrocode} % Now this function does the fun part. It is meant to be used with % \cs{prop_map_function:NN}, taking a label name in \verb|##1| and the % code stored in that label in \verb|##2|. % \begin{macrocode} \cs_gset_protected:Npx \@@_normalise_fn:nn ##1 ##2 { % \end{macrocode} % Here we'll define two auxiliary macros: the first one throws an % error when it detects an invalid argument reference. It piggybacks % on \TeX's low-level \enquote{Illegal parameter number} error, but it % defines a weirdly-named control sequence so that the error comes out % nicely formatted. For example, if the label \enquote{badpkg} adds % some code that references argument \verb|#3| in the hook % \enquote{foo}, which takes only two arguments, the error will be: %\begin{verbatim} % ! Illegal parameter number in definition of hook 'foo'. % (hooks) Offending label: 'badpkg'. % % 3 %\end{verbatim} % At the point of this definition, the error is raised if the code % happens to reference an invalid argument. If it was possible to % detect that this definition raised no error, the next step would be % unnecessary. We'll do all this in a group so this weird definition % doesn't leak out, and set \cs{tex_escapechar:D} to $-1$ so this hack % shows up extra nice in the case of an error. % \begin{macrocode} \group_begin: \int_set:Nn \tex_escapechar:D { -1 } \cs_set:cpn { hook~'#1'. ^^J (hooks) \prg_replicate:nn { 13 } { ~ } #2 % more message text } \exp_not:v { c_@@_#1_parameter_tl } {##2} \group_end: % \end{macrocode} % This next macro, with a much less fabulous name, takes always nine % arguments, and it just transfers the code \verb|##2| under the label % \verb|##1| to the temporary property list. The first \meta{n} % arguments are taken from \cs[no-index]{l_@@_tmpa_tl}, and the other % $9-\meta{n}$ taken from \cs[no-index]{l_@@_tmpb_tl} (which contains % twice as many \verb|#| tokens as the former). Then, % \cs{@@_double_hashes:n} is used to double non-argument hashes, and % expand the \cs{c_@@_hash_tl} and \cs{c_@@_hashes_tl} to the actual % parameter tokens. % \begin{macrocode} \cs_set:Npn \exp_not:N \@@_tmp:w \exp_not:V \c_@@_nine_parameters_tl { \prop_put:Nne \exp_not:N \l_@@_work_prop {##1} { \exp_not:N \@@_double_hashes:n {##2} } } % \end{macrocode} % This next macro, with a much less fabulous name, takes always nine % arguments, and it just transfers the code \verb|##2| under the label % \verb|##1| to the temporary property list. The first \meta{n} % arguments are taken from \cs[no-index]{l_@@_tmpa_tl}, and the other % $9-\meta{n}$ taken from \cs[no-index]{l_@@_tmpb_tl} (which contains % twice as many \verb|#| tokens as the former). Then, % \cs{@@_double_hashes:n} is used to double non-argument hashes, and % expand the \cs{c_@@_hash_tl} and \cs{c_@@_hashes_tl} to the actual % parameter tokens. % \begin{macrocode} \exp_not:N \@@_tmp:w \exp_not:V \l_@@_tmpa_tl \exp_args:No \exp_not:o { \exp_after:wN \@@_tmp:w \l_@@_tmpb_tl } } } \cs_new_eq:NN \@@_normalise_fn:nn ? %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2020/10/01}{\@@_normalise_code_pool:n} % {Hooks~with~args} %\cs_undefine:N \@@_normalise_code_pool:n %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % \begin{macro}[pTF]{\@@_cs_if_empty:c} % Check if the expansion of a control sequence is empty by looking at % its replacement text. % \changes{v1.1a}{2023/04/06} % {Macro added (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\@@_cs_if_empty:c} % {Hooks~with~args} \prg_new_conditional:Npnn \@@_cs_if_empty:c #1 { p, T, F, TF } { \if:w \scan_stop: \@@_replacement_spec:c {#1} \scan_stop: \prg_return_true: \else: \prg_return_false: \fi: } \cs_new:Npn \@@_replacement_spec:c #1 { \exp_args:Nc \token_if_macro:NT {#1} { \cs_replacement_spec:c {#1} } } %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2020/10/01}{\@@_cs_if_empty:c} % {Hooks~with~args} %\cs_undefine:N \@@_cs_if_empty:c %\EndIncludeInRelease % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\@@_braced_cs_parameter:n} % \begin{macro}{\@@_braced_hidden_loop:w} % \begin{macro}{\@@_cs_parameter_count:N} % \begin{macro}{\@@_cs_parameter_count:w,\@@_cs_end:w} % Looks at the \meta{parameter text} of a control sequence, and % returns a run of \enquote{hidden} braced parameters for that macro. % This works as long as the macros take a simple run of zero to nine % arguments. The parameters are \enquote{hidden} because the % parameter tokens are returned inside \cs{c_@@_hash_tl} instead of % explicitly, so that \cs{@@_double_hashes:n} won't touch these. % \changes{v1.1a}{2023/04/06} % {Macro added (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\@@_braced_cs_parameter:n} % {Hooks~with~args} \cs_new:Npn \@@_braced_cs_parameter:n #1 { \exp_last_unbraced:Ne \@@_braced_hidden_loop:w { \exp_args:Nc \@@_cs_parameter_count:N {#1} } ? \s_@@_mark } \cs_new:Npn \@@_braced_hidden_loop:w #1 { \if:w ? #1 \@@_use_i_delimit_by_s_mark:nw \fi: { \exp_not:N \c_@@_hash_tl #1 } \@@_braced_hidden_loop:w } \cs_new:Npn \@@_cs_parameter_count:N #1 { \exp_last_unbraced:Nf \@@_cs_parameter_count:w { \token_if_macro:NT #1 { \cs_parameter_spec:N #1 } } ? \@@_cs_end:w ? \@@_cs_end:w ? \@@_cs_end:w ? \@@_cs_end:w ? \@@_cs_end:w ? \@@_cs_end:w ? \@@_cs_end:w ? \@@_cs_end:w ? \@@_cs_end:w \s_@@_mark } \cs_new:Npn \@@_cs_parameter_count:w #1#2 #3#4 #5#6 #7#8 { #2 #4 #6 #8 \@@_cs_parameter_count:w } \cs_new:Npn \@@_cs_end:w #1 \s_@@_mark { } %\EndIncludeInRelease % \end{macrocode} % % This function can't be undefined when rolling back because it's used % at the end of this module to adequate the hook data structures to % previous versions. % \begin{macrocode} %\IncludeInRelease{2020/10/01}{\@@_braced_cs_parameter:n} % {Hooks~with~args} %\EndIncludeInRelease % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\@@_braced_parameter:n} % \begin{macro}{\@@_braced_real_loop:w} % This one is used in simpler cases, where no special handling of % hashes is required. This is used only inside % \cs{@@_initialize_hook_code:n}, so it assumes % \cs[no-index]{c_@@_\meta{hook}_parameter_tl} is defined, but should % work otherwise. % \changes{v1.1a}{2023/04/06} % {Macro added (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\@@_braced_parameter:n} % {Hooks~with~args} \cs_new:Npn \@@_braced_parameter:n #1 { \if_case:w \int_eval:n { \exp_args:Nv \str_count:n { c_@@_#1_parameter_tl } / 3 } \exp_stop_f: \or: {##1} \or: {##1} {##2} \or: {##1} {##2} {##3} \or: {##1} {##2} {##3} {##4} \or: {##1} {##2} {##3} {##4} {##5} \or: {##1} {##2} {##3} {##4} {##5} {##6} \or: {##1} {##2} {##3} {##4} {##5} {##6} {##7} \or: {##1} {##2} {##3} {##4} {##5} {##6} {##7} {##8} \or: {##1} {##2} {##3} {##4} {##5} {##6} {##7} {##8} {##9} \else: \msg_expandable_error:nnn { latex2e } { should-not-happen } { Invalid~parameter~spec. } \fi: } %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2020/10/01}{\@@_braced_parameter:n} % {Hooks~with~args} %\cs_undefine:N \@@_braced_parameter:n %\EndIncludeInRelease % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\@@_parameter:n} % This is just a shortcut to \verb|e|- or \verb|f|-expand to the % \meta{parameter text} of the hook. % \changes{v1.1a}{2023/04/06} % {Macro added (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\@@_parameter:n} % {Hooks~with~args} \cs_new:Npn \@@_parameter:n #1 { \cs:w c_@@_ \tl_if_exist:cTF { c_@@_#1_parameter_tl } { #1_parameter } { empty } _tl \cs_end: } \cs_new:Npn \@@_generic_parameter:n #1 { \@@_generic_parameter:w #1 / / / \s_@@_mark } \cs_new:Npn \@@_generic_parameter:w #1 / #2 / #3 / #4 \s_@@_mark { \cs_if_exist_use:cF { c_@@_parameter_#1/./#3_tl } { \c_@@_empty_tl } } %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2020/10/01}{\@@_parameter:n} % {Hooks~with~args} %\cs_undefine:N \@@_parameter:n %\cs_undefine:N \@@_generic_parameter:n %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % % \subsection{Setting rules for hooks code} % % \begin{macro}{ % \g_@@_??_code_prop, % \@@~??, % \g_@@_??_reversed_tl, % \c_@@_??_parameter_tl, % } % % Initially these variables simply used an empty ``label'' name (not % two question marks). This was a bit unfortunate, because then % \texttt{l3doc} complains about \verb=__= in the middle of a % command name when trying to typeset the documentation. However % using a ``normal'' name such as \texttt{default} has the % disadvantage of that being not really distinguishable from a real % hook name. I now have settled for \texttt{??} which needs some % gymnastics to get it into the csname, but since this is used a % lot, the code should be fast, so this is not done with \texttt{c} % expansion in the code later on. % % \cs{@@\textvisiblespace??} isn't used, but it has to be defined to % trick the code into thinking that \texttt{??} is actually a hook. % \begin{macrocode} \prop_new:c { g_@@_??_code_prop } \prop_new:c { @@~?? } % \end{macrocode} % % Default rules are always given in normal ordering (never in % reversed ordering). If such a rule is applied to a reversed % hook it behaves as if the rule is reversed (e.g., % \texttt{after} becomes \texttt{before}) % because those rules are applied first and then the order is reversed. % \begin{macrocode} \tl_new:c { g_@@_??_reversed_tl } % \end{macrocode} % % The parameter text for the \enquote{default} hook is empty. % \changes{v1.1a}{2023/04/06} % {Token list added (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\c_@@_??_parameter_tl} % {Hooks~with~args} \tl_const:cn { c_@@_??_parameter_tl } { } %\EndIncludeInRelease %\IncludeInRelease{2020/10/01}{\c_@@_??_parameter_tl} % {Hooks~with~args} %\cs_undefine:c { c_@@_??_parameter_tl } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % \begin{macro}{\hook_gset_rule:nnnn} % \begin{macro}{\@@_gset_rule:nnnn} % With % \cs{hook_gset_rule:nnnn}\Arg{hook}\Arg{label1}\Arg{relation}\Arg{label2} % a relation is defined between the two code labels for the given % \meta{hook}. The special hook \texttt{??} stands for \emph{any} % hook, which sets a default rule (to be used if no other relation % between the two hooks exist). % \begin{macrocode} \cs_new_protected:Npn \hook_gset_rule:nnnn #1#2#3#4 { \@@_normalize_hook_rule_args:Nnnnn \@@_gset_rule:nnnn {#1} {#2} {#3} {#4} } % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2022/06/01}{\@@_gset_rule:nnnn} % {Refuse~setting~rule~for~one-time~hooks} % \end{macrocode} % % \begin{macrocode} \cs_new_protected:Npn \@@_gset_rule:nnnn #1#2#3#4 { \@@_if_deprecated_generic:nT {#1} { \@@_deprecated_generic_warn:n {#1} \@@_do_deprecated_generic:Nn \@@_gset_rule:nnnn {#1} {#2} {#3} {#4} \@@_use_none_delimit_by_s_mark:w } \@@_if_execute_immediately:nT {#1} { \msg_error:nnnnnn { hooks } { rule-too-late } {#1} {#2} {#3} {#4} \@@_use_none_delimit_by_s_mark:w } % \end{macrocode} % First we ensure the basic data structure of the hook exists: % \begin{macrocode} \@@_init_structure:n {#1} % \end{macrocode} % Then we clear any previous relationship between both labels. % \begin{macrocode} \@@_rule_gclear:nnn {#1} {#2} {#4} % \end{macrocode} % Then we call the function to handle the given rule. Throw an error if the % rule is invalid. % \begin{macrocode} \cs_if_exist_use:cTF { @@_rule_#3_gset:nnn } { {#1} {#2} {#4} \@@_update_hook_code:n {#1} } { \msg_error:nnnnnn { hooks } { unknown-rule } {#1} {#2} {#3} {#4} } \s_@@_mark } % \end{macrocode} % % \begin{macrocode} %\EndIncludeInRelease %\IncludeInRelease{2020/10/01}{\@@_gset_rule:nnnn} % {Refuse~setting~rule~for~one-time~hooks} %\cs_new_protected:Npn \@@_gset_rule:nnnn #1#2#3#4 % { % \@@_if_deprecated_generic:nT {#1} % { % \@@_deprecated_generic_warn:n {#1} % \@@_do_deprecated_generic:Nn \@@_gset_rule:nnnn % {#1} {#2} {#3} {#4} % \exp_after:wN \use_none:nnnnnnnnn \use_none:n % } % \@@_init_structure:n {#1} % \@@_rule_gclear:nnn {#1} {#2} {#4} % \cs_if_exist_use:cTF { @@_rule_#3_gset:nnn } % { % {#1} {#2} {#4} % \@@_update_hook_code:n {#1} % } % { % \msg_error:nnnnnn { hooks } { unknown-rule } % {#1} {#2} {#3} {#4} % } % } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\@@_rule_before_gset:nnn, \@@_rule_after_gset:nnn, % \@@_rule_<_gset:nnn, \@@_rule_>_gset:nnn} % Then we add the new rule. We need to normalize the rules here to % allow for faster processing later. Given a pair of labels % $l_A$ and $l_B$, the rule $l_A>l_B$ is the same as $l_B } } } \cs_new_eq:cN { @@_rule_<_gset:nnn } \@@_rule_before_gset:nnn % \end{macrocode} % % \begin{macrocode} \cs_new_protected:Npn \@@_rule_after_gset:nnn #1#2#3 { \@@_tl_gset:cx { g_@@_#1_rule_ \@@_label_pair:nn {#3} {#2} _tl } { \@@_label_ordered:nnTF {#3} {#2} { < } { > } } } \cs_new_eq:cN { @@_rule_>_gset:nnn } \@@_rule_after_gset:nnn % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_rule_voids_gset:nnn} % This rule removes (clears, actually) the code from label |#3| if % label |#2| is in the hook |#1|. % \begin{macrocode} \cs_new_protected:Npn \@@_rule_voids_gset:nnn #1#2#3 { \@@_tl_gset:cx { g_@@_#1_rule_ \@@_label_pair:nn {#2} {#3} _tl } { \@@_label_ordered:nnTF {#2} {#3} { -> } { <- } } } % \end{macrocode} % \end{macro} % % \begin{macro}{ % \@@_rule_incompatible-error_gset:nnn, % \@@_rule_incompatible-warning_gset:nnn, % } % These relations make an error/warning if labels |#2| and |#3| appear % together in hook |#1|. % \begin{macrocode} \cs_new_protected:cpn { @@_rule_incompatible-error_gset:nnn } #1#2#3 { \@@_tl_gset:cn { g_@@_#1_rule_ \@@_label_pair:nn {#2} {#3} _tl } { xE } } \cs_new_protected:cpn { @@_rule_incompatible-warning_gset:nnn } #1#2#3 { \@@_tl_gset:cn { g_@@_#1_rule_ \@@_label_pair:nn {#2} {#3} _tl } { xW } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_rule_unrelated_gset:nnn, \@@_rule_gclear:nnn} % Undo a setting. \cs{@@_rule_unrelated_gset:nnn} doesn't need to do anything, % since we use \cs{@@_rule_gclear:nnn} before setting any rule. % \begin{macrocode} \cs_new_protected:Npn \@@_rule_unrelated_gset:nnn #1#2#3 { } \cs_new_protected:Npn \@@_rule_gclear:nnn #1#2#3 { \cs_undefine:c { g_@@_#1_rule_ \@@_label_pair:nn {#2} {#3} _tl } } % \end{macrocode} % \end{macro} % % \begin{macro}[EXP]{\@@_label_pair:nn} % Ensure that the lexically greater label comes first. % \begin{macrocode} \cs_new:Npn \@@_label_pair:nn #1#2 { \if_case:w \@@_str_compare:nn {#1} {#2} \exp_stop_f: #1 | #1 % 0 \or: #1 | #2 % +1 \else: #2 | #1 % -1 \fi: } % \end{macrocode} % \end{macro} % % \begin{macro}[pTF]{\@@_label_ordered:nn} % Check that labels |#1| and |#2| are in the correct order (as % returned by \cs{@@_label_pair:nn}) and if so return true, else % return false. % \begin{macrocode} \prg_new_conditional:Npnn \@@_label_ordered:nn #1#2 { TF } { \if_int_compare:w \@@_str_compare:nn {#1} {#2} > 0 \exp_stop_f: \prg_return_true: \else: \prg_return_false: \fi: } % \end{macrocode} % \end{macro} % % \begin{macro}[EXP]{\@@_if_label_case:nnnnn} % To avoid doing the string comparison twice in \cs{@@_initialize_single:NNn} % (once with \cs{str_if_eq:nn} and again with \cs{@@_label_ordered:nn}), % we use a three-way branching macro that will compare |#1| and |#2| % and expand to \cs{use_i:nnn} if they are equal, \cs{use_ii:nn} if % |#1| is lexically greater, and \cs{use_iii:nn} otherwise. % \begin{macrocode} \cs_new:Npn \@@_if_label_case:nnnnn #1#2 { \cs:w use_ \if_case:w \@@_str_compare:nn {#1} {#2} i \or: ii \else: iii \fi: :nnn \cs_end: } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_update_hook_code:n} % Before \verb=\begin{document}= this does nothing, in the body it % reinitializes the hook code using the altered data. % \begin{macrocode} \cs_new_eq:NN \@@_update_hook_code:n \use_none:n % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_initialize_all:} % Initialize all known hooks (at \verb=\begin{document}=), i.e., % update the fast execution token lists to hold the necessary code % in the right order. % \changes{v1.1a}{2023/04/06} % {Changes to add hook arguments (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\@@_initialize_all:} % {Hooks~with~args} \cs_new_protected:Npn \@@_initialize_all: { % \end{macrocode} % First we change \cs{@@_update_hook_code:n} which so far was a % no-op to now initialize one hook. This way any later updates to % the hook will run that code and also update the execution token % list. % \begin{macrocode} \cs_gset_eq:NN \@@_update_hook_code:n \@@_initialize_hook_code:n % \end{macrocode} % Now we loop over all hooks that have been defined and update each % of them. Here we have to determine if the hook has arguments so % that auxiliaries know what to do with hashes. We look at % \cs[no-index]{c_@@_\meta{hook}_parameter_tl}, if it has any % parameters, and set \verb|replacing_args| accordingly. % \begin{macrocode} \@@_debug:n { \prop_gclear:N \g_@@_used_prop } \seq_map_inline:Nn \g_@@_all_seq { \tl_if_empty:cTF { c_@@_##1_parameter_tl } { \@@_replacing_args_false: } { \@@_replacing_args_true: } \@@_update_hook_code:n {##1} \@@_replacing_args_reset: } % \end{macrocode} % If we are debugging we show results hook by hook for all hooks % that have data. % \begin{macrocode} \@@_debug:n { \iow_term:x { ^^J All~initialized~(non-empty)~hooks: } \prop_map_inline:Nn \g_@@_used_prop { \iow_term:x { ^^J ~ ##1 ~ -> ~ \cs_replacement_spec:c { @@~##1 } ~ } } } % \end{macrocode} % After all hooks are initialized we change the ``use'' to just % call the hook code and not initialize it (as this was already done in the % preamble. % \begin{macrocode} \@@_post_initialization_defs: } % \end{macrocode} % % \begin{macrocode} %\EndIncludeInRelease %\IncludeInRelease{2020/10/01}{\@@_initialize_all:} % {Hooks~with~args} %\cs_gset_protected:Npn \@@_initialize_all: % { % \cs_gset_eq:NN \@@_update_hook_code:n % \@@_initialize_hook_code:n % \@@_debug:n { \prop_gclear:N \g_@@_used_prop } % \seq_map_inline:Nn \g_@@_all_seq % { \@@_update_hook_code:n {##1} } % \@@_debug:n % { % \iow_term:x{^^JAll~ initialized~ (non-empty)~ hooks:} % \prop_map_inline:Nn \g_@@_used_prop % { % \iow_term:x % { ^^J ~ ##1 ~ -> ~ % \cs_replacement_spec:c { @@~##1 } ~ } % } % } % \cs_gset_eq:NN \hook_use:n \@@_use_initialized:n % \cs_gset_eq:NN \@@_preamble_hook:n \use_none:n % } %<@@=> %\cs_gset_eq:NN \@expl@@@initialize@all@@ % \__hook_initialize_all: %<@@=hook> %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_initialize_hook_code:n} % Initializing or reinitializing the fast execution hook code. In % the preamble this is selectively done in case a hook gets used % and at \verb=\begin{document}= this is done for all hooks and % afterwards only if the hook code changes. % \changes{v1.1a}{2023/04/06} % {Changes to add hook arguments (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\@@_initialize_hook_code:n} % {Hooks~with~args} \cs_new_protected:Npn \@@_initialize_hook_code:n #1 { \@@_debug:n { \iow_term:x { ^^J Update~code~for~hook~'#1' \on@line :^^J } } % \end{macrocode} % This does the sorting and the updates. % First thing we do is to check if a legacy hook macro exists and % if so we add it to the hook under the label \texttt{legacy}. This % might make the hook non-empty so we have to do this before % the then following test. % \begin{macrocode} \@@_include_legacy_code_chunk:n {#1} % \end{macrocode} % If there aren't any code % chunks for the current hook, there is no point in even starting % the sorting routine so we make a quick test for that and in that % case just update \cs{@@\textvisiblespace\meta{hook}} to hold the |top-level| and % |next| code chunks. If there are code chunks we call % \cs{@@_initialize_single:NNn} and pass to it ready made csnames % as they are needed several times inside. This way we save a bit % on processing time if we do that up front. % \changes{v1.0u}{2022/05/17}{Refuse sorting one-time hooks (gh/818).} % \begin{macrocode} \@@_if_usable:nT {#1} { \prop_if_empty:cTF { g_@@_#1_code_prop } { \@@_code_gset:ne {#1} { % \end{macrocode} % The hook may take arguments, so we add a run of braced parameters % after the \verb|_next| and \verb|_toplevel| macros, so that the % arguments passed to the hook are forwarded to them. % \begin{macrocode} \exp_not:c { @@_toplevel~#1 } \@@_braced_parameter:n {#1} \exp_not:c { @@_next~#1 } \@@_braced_parameter:n {#1} } } { % \end{macrocode} % By default the algorithm sorts the code chunks and then saves the % result in a token list for fast execution; this is done by adding the code chunks % one after another, using \cs{tl_gput_right:NV}. When we sort code for % a reversed hook, all we have to do is to add the code chunks in % the opposite order into the token list. So all we have to do % in preparation is to change two definitions that are used later on. % \begin{macrocode} \@@_if_reversed:nTF {#1} { \cs_set_eq:NN \@@_tl_gput:Nn \@@_tl_gput_left:Nn \cs_set_eq:NN \@@_clist_gput:NV \clist_gput_left:NV } { \cs_set_eq:NN \@@_tl_gput:Nn \@@_tl_gput_right:Nn \cs_set_eq:NN \@@_clist_gput:NV \clist_gput_right:NV } % \end{macrocode} % % When sorting, some relations (namely \verb|voids|) need to % act destructively on the code property lists to remove code that % shouldn't appear in the sorted hook token list, so we make a copy % of the code property list that we can safely work on without % changing the main one. % \begin{macrocode} \prop_set_eq:Nc \l_@@_work_prop { g_@@_#1_code_prop } \@@_initialize_single:ccn { @@~#1 } { g_@@_#1_labels_clist } {#1} % \end{macrocode} % For debug display we want to keep track of those hooks that % actually got code added to them, so we record that in plist. We % use a plist to ensure that we record each hook name only once, % i.e., we are only interested in storing the keys and the value is % arbitrary. % \begin{macrocode} \@@_debug:n { \exp_args:NNx \prop_gput:Nnn \g_@@_used_prop {#1} { } } } } } %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2020/10/01}{\@@_initialize_hook_code:n} % {Hooks~with~args} %\cs_gset_protected:Npn \@@_initialize_hook_code:n #1 % { % \@@_debug:n % { \iow_term:x { ^^J Update~code~for~hook~'#1' % \on@line :^^J } } % \@@_include_legacy_code_chunk:n {#1} % \@@_if_usable:nT {#1} % { % \prop_if_empty:cTF { g_@@_#1_code_prop } % { % \@@_tl_gset:co { @@~#1 } % { % \cs:w @@_toplevel~#1 \exp_after:wN \cs_end: % \cs:w @@_next~#1 \cs_end: % } % } % { % \@@_if_reversed:nTF {#1} % { \cs_set_eq:NN \@@_tl_gput:Nn % \@@_tl_gput_left:Nn % \cs_set_eq:NN \@@_clist_gput:NV % \clist_gput_left:NV } % { \cs_set_eq:NN \@@_tl_gput:Nn % \@@_tl_gput_right:Nn % \cs_set_eq:NN \@@_clist_gput:NV % \clist_gput_right:NV } % \prop_set_eq:Nc \l_@@_work_prop % { g_@@_#1_code_prop } % \@@_initialize_single:ccn % { @@~#1 } { g_@@_#1_labels_clist } {#1} % \@@_debug:n % { \exp_args:NNx \prop_gput:Nnn \g_@@_used_prop % {#1} { } } % } % } % } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % % \begin{macro}[EXP]{\@@_tl_csname:n,\@@_seq_csname:n} % It is faster to pass a single token and expand it when necessary % than to pass a bunch of character tokens around. % \fmiinline{note to myself: verify} % \begin{macrocode} \cs_new:Npn \@@_tl_csname:n #1 { l_@@_label_#1_tl } \cs_new:Npn \@@_seq_csname:n #1 { l_@@_label_#1_seq } % \end{macrocode} % \end{macro} % % % \begin{macro}{\l_@@_labels_seq,\l_@@_labels_int,\l_@@_front_tl, % \l_@@_rear_tl,\l_@@_label_0_tl} % % For the sorting I am basically implementing Knuth's algorithm for % topological sorting as given in TAOCP volume 1 pages 263--266. % For this algorithm we need a number of local variables: % \begin{itemize} % \item % List of labels used in the current hook to label code chunks: % \begin{macrocode} \seq_new:N \l_@@_labels_seq % \end{macrocode} % \item % Number of labels used in the current hook. In Knuth's algorithm % this is called $N$: % \begin{macrocode} \int_new:N \l_@@_labels_int % \end{macrocode} % \item % The sorted code list to be build is managed using two pointers % one to the front of the queue and one to the rear. We model this % using token list pointers. Knuth calls them $F$ and $R$: % \begin{macrocode} \tl_new:N \l_@@_front_tl \tl_new:N \l_@@_rear_tl % \end{macrocode} % \item % The data for the start of the queue is kept in this token list, % it corresponds to what Don calls \texttt{QLINK[0]} but since we % aren't manipulating individual words in memory it is slightly % differently done: % \begin{macrocode} \tl_new:c { \@@_tl_csname:n { 0 } } % \end{macrocode} % % \end{itemize} % \end{macro} % % % \begin{macro}{\@@_initialize_single:NNn,\@@_initialize_single:ccn} % % \cs{@@_initialize_single:NNn} implements the sorting of the code % chunks for a hook and saves the result in the token list for fast % execution (\verb=#4=). The arguments are \meta{hook-code-plist}, % \meta{hook-code-tl}, \meta{hook-top-level-code-tl}, % \meta{hook-next-code-tl}, % \meta{hook-ordered-labels-clist} and \meta{hook-name} (the latter % is only used for debugging---the \meta{hook-rule-plist} is accessed % using the \meta{hook-name}). % % The additional complexity compared to Don's algorithm is that we % do not use simple positive integers but have arbitrary % alphanumeric labels. As usual Don's data structures are chosen in % a way that one can omit a lot of tests and I have mimicked that as % far as possible. The result is a restriction I do not test for at % the moment: a label can't be equal to the number 0! % \fmiinline{Needs checking for, just in case ... maybe} % % ^^A #1 <- \@@~#1 % ^^A #2 <- \g_@@_#1_labels_clist % ^^A #3 <- #1 % % \changes{v1.1a}{2023/04/06} % {Changes to add hook arguments (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\@@_initialize_single:NNn} % {Hooks~with~args} \cs_new_protected:Npn \@@_initialize_single:NNn #1#2#3 { % \end{macrocode} % Step T1: Initialize the data structure \ldots % \begin{macrocode} \seq_clear:N \l_@@_labels_seq \int_zero:N \l_@@_labels_int % \end{macrocode} % % Store the name of the hook: % \begin{macrocode} \tl_set:Nn \l_@@_cur_hook_tl {#3} % \end{macrocode} % % We loop over the property list holding the code and record all % the labels listed there. Only the rules for those labels are of interest % to us. While we are at it we count them (which gives us the $N$ % in Knuth's algorithm). The prefix |label_| is added to the variables % to ensure that labels named |front|, |rear|, |labels|, or |return| % don't interact with our code. % \begin{macrocode} \prop_map_inline:Nn \l_@@_work_prop { \int_incr:N \l_@@_labels_int \seq_put_right:Nn \l_@@_labels_seq {##1} \@@_tl_set:cn { \@@_tl_csname:n {##1} } { 0 } \seq_clear_new:c { \@@_seq_csname:n {##1} } } % \end{macrocode} % Steps T2 and T3: Here we sort the relevant rules into the data structure\ldots % % This loop constitutes a square matrix of the labels in % \cs{l_@@_work_prop} in the % vertical and the horizontal directions. However, since the rule % $l_A\meta{rel}l_B$ is the same as $l_B\meta{rel}^{-1}l_A$ we can cut % the loop short at the diagonal of the matrix (\emph{i.e.}, when % both labels are equal), saving a good amount of time. The way the % rules were set up (see the implementation of \cs{@@_rule_before_gset:nnn} % above) ensures that we have no rule in the ignored side of the % matrix, and all rules are seen. The rules are applied in % \cs{@@_apply_label_pair:nnn}, which takes the properly-ordered pair % of labels as argument. % \begin{macrocode} \prop_map_inline:Nn \l_@@_work_prop { \prop_map_inline:Nn \l_@@_work_prop { \@@_if_label_case:nnnnn {##1} {####1} { \prop_map_break: } { \@@_apply_label_pair:nnn {##1} {####1} } { \@@_apply_label_pair:nnn {####1} {##1} } {#3} } } % \end{macrocode} % Now take a breath, and look at the data structures that have % been set up: % \begin{macrocode} \@@_debug:n { \@@_debug_label_data:N \l_@@_work_prop } % \end{macrocode} % % % Step T4: % \begin{macrocode} \tl_set:Nn \l_@@_rear_tl { 0 } \tl_set:cn { \@@_tl_csname:n { 0 } } { 0 } \seq_map_inline:Nn \l_@@_labels_seq { \int_compare:nNnT { \cs:w \@@_tl_csname:n {##1} \cs_end: } = 0 { \tl_set:cn { \@@_tl_csname:n { \l_@@_rear_tl } }{##1} \tl_set:Nn \l_@@_rear_tl {##1} } } \tl_set_eq:Nc \l_@@_front_tl { \@@_tl_csname:n { 0 } } % \end{macrocode} % % \begin{macrocode} \@@_tl_gclear:N #1 \clist_gclear:N #2 % \end{macrocode} % % The whole loop gets combined in steps T5--T7: % \begin{macrocode} \bool_while_do:nn { ! \str_if_eq_p:Vn \l_@@_front_tl { 0 } } { % \end{macrocode} % This part is step T5: % \begin{macrocode} \int_decr:N \l_@@_labels_int \prop_get:NVN \l_@@_work_prop \l_@@_front_tl \l_@@_return_tl \exp_args:NNV \@@_tl_gput:Nn #1 \l_@@_return_tl % \end{macrocode} % % \begin{macrocode} \@@_clist_gput:NV #2 \l_@@_front_tl \@@_debug:n{ \iow_term:x{Handled~ code~ for~ \l_@@_front_tl} } % \end{macrocode} % % This is step T6, except that we don't use a pointer $P$ to move % through the successors, but instead use \verb=##1= of the mapping % function. % \begin{macrocode} \seq_map_inline:cn { \@@_seq_csname:n { \l_@@_front_tl } } { \tl_set:cx { \@@_tl_csname:n {##1} } { \int_eval:n { \cs:w \@@_tl_csname:n {##1} \cs_end: - 1 } } \int_compare:nNnT { \cs:w \@@_tl_csname:n {##1} \cs_end: } = 0 { \tl_set:cn { \@@_tl_csname:n { \l_@@_rear_tl } } {##1} \tl_set:Nn \l_@@_rear_tl {##1} } } % \end{macrocode} % and here is step T7: % \begin{macrocode} \tl_set_eq:Nc \l_@@_front_tl { \@@_tl_csname:n { \l_@@_front_tl } } % \end{macrocode} % % This is step T8: If we haven't moved the code for all labels % (i.e., if \cs{l_@@_labels_int} is still greater than zero) we % have a loop and our partial order can't be flattened out. % \begin{macrocode} } \int_compare:nNnF \l_@@_labels_int = 0 { \iow_term:x{====================} \iow_term:x{Error:~ label~ rules~ are~ incompatible:} % \end{macrocode} % % This is not really the information one needs in the error case % but it will do for now \ldots \fmiinline{improve output on a rainy day} % \begin{macrocode} \@@_debug_label_data:N \l_@@_work_prop \iow_term:x{====================} } % \end{macrocode} % After we have added all hook code to \verb=#1=, we finish it off % by adding extra code for the |top-level| (\verb=#2=) and for one % time execution (\verb=#3=). These should normally be empty. The % |top-level| code is added with \cs{@@_tl_gput:Nn} as that might % change for a reversed hook (then |top-level| is the very first code % chunk added). The |next| code is always added last (to the right). % The hook may take arguments, so we add a run of braced parameters % after the \verb|_next| and \verb|_toplevel| macros, so that the % arguments passed to the hook are forwarded to them. % \begin{macrocode} \exp_args:NNe \@@_tl_gput:Nn #1 { \exp_not:c { @@_toplevel~#3 } \@@_braced_parameter:n {#3} } \@@_tl_gput_right:Ne #1 { \exp_not:c { @@_next~#3 } \@@_braced_parameter:n {#3} } \use:e { \cs_gset:cpn { @@~#3 } \use:c { c_@@_#3_parameter_tl } { \exp_not:V #1 } } } % \end{macrocode} % % \begin{macrocode} \cs_generate_variant:Nn \@@_initialize_single:NNn { cc } %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2020/10/01}{\@@_initialize_single:NNn} % {Hooks~with~args} %\cs_new_protected:Npn \@@_initialize_single:NNn #1#2#3 % { % \seq_clear:N \l_@@_labels_seq % \int_zero:N \l_@@_labels_int % \tl_set:Nn \l_@@_cur_hook_tl {#3} % \prop_map_inline:Nn \l_@@_work_prop % { % \int_incr:N \l_@@_labels_int % \seq_put_right:Nn \l_@@_labels_seq {##1} % \@@_tl_set:cn { \@@_tl_csname:n {##1} } { 0 } % \seq_clear_new:c { \@@_seq_csname:n {##1} } % } % \prop_map_inline:Nn \l_@@_work_prop % { % \prop_map_inline:Nn \l_@@_work_prop % { % \@@_if_label_case:nnnnn {##1} {####1} % { \prop_map_break: } % { \@@_apply_label_pair:nnn {##1} {####1} } % { \@@_apply_label_pair:nnn {####1} {##1} } % {#3} % } % } % \@@_debug:n % { \@@_debug_label_data:N \l_@@_work_prop } % \tl_set:Nn \l_@@_rear_tl { 0 } % \tl_set:cn { \@@_tl_csname:n { 0 } } { 0 } % \seq_map_inline:Nn \l_@@_labels_seq % { % \int_compare:nNnT % { \cs:w \@@_tl_csname:n {##1} \cs_end: } = 0 % { % \tl_set:cn { \@@_tl_csname:n % { \l_@@_rear_tl } } {##1} % \tl_set:Nn \l_@@_rear_tl {##1} % } % } % \tl_set_eq:Nc \l_@@_front_tl { \@@_tl_csname:n { 0 } } % \@@_tl_gclear:N #1 % \clist_gclear:N #2 % \bool_while_do:nn % { ! \str_if_eq_p:Vn \l_@@_front_tl { 0 } } % { % \int_decr:N \l_@@_labels_int % \prop_get:NVN \l_@@_work_prop % \l_@@_front_tl \l_@@_return_tl % \exp_args:NNV \@@_tl_gput:Nn #1 \l_@@_return_tl % \@@_clist_gput:NV #2 \l_@@_front_tl % \@@_debug:n{ \iow_term:x % {Handled~ code~ for~ \l_@@_front_tl} } % \seq_map_inline:cn % { \@@_seq_csname:n { \l_@@_front_tl } } % { % \tl_set:cx { \@@_tl_csname:n {##1} } % { \int_eval:n % { \cs:w \@@_tl_csname:n {##1} \cs_end: - 1 } % } % \int_compare:nNnT % { \cs:w \@@_tl_csname:n {##1} \cs_end: } = 0 % { % \tl_set:cn { \@@_tl_csname:n % { \l_@@_rear_tl } } {##1} % \tl_set:Nn \l_@@_rear_tl {##1} % } % } % \tl_set_eq:Nc \l_@@_front_tl % { \@@_tl_csname:n { \l_@@_front_tl } } % } % \int_compare:nNnF \l_@@_labels_int = 0 % { % \iow_term:x{====================} % \iow_term:x{Error:~ label~ rules~ are~ incompatible:} % \@@_debug_label_data:N \l_@@_work_prop % \iow_term:x{====================} % } % \exp_args:NNo \@@_tl_gput:Nn #1 % { \cs:w @@_toplevel~#3 \cs_end: } % \@@_tl_gput_right:No #1 { \cs:w @@_next~#3 \cs_end: } % } %\cs_generate_variant:Nn \@@_tl_gput_right:Nn { No } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % % % \begin{macro}{\@@_tl_gput:Nn,\@@_clist_gput:NV} % These append either on the right (normal hook) or on the left % (reversed hook). This is setup up in % \cs{@@_initialize_hook_code:n}, elsewhere their behavior is undefined. % \begin{macrocode} \cs_new:Npn \@@_tl_gput:Nn { \ERROR } \cs_new:Npn \@@_clist_gput:NV { \ERROR } % \end{macrocode} % \end{macro} % % % % \begin{macro}{\@@_apply_label_pair:nnn,\@@_label_if_exist_apply:nnnF} % % This is the payload of steps T2 and T3 executed in the loop described % above. This macro assumes |#1| and |#2| are ordered, which means that % any rule pertaining the pair |#1| and |#2| is % \cs{g_@@_\meta{hook}_rule_\#1\string|\#2_tl}, and not % \cs{g_@@_\meta{hook}_rule_\#2\string|\#1_tl}. This also saves a great deal % of time since we only need to check the order of the labels once. % % The arguments here are \meta{label1}, \meta{label2}, \meta{hook}, and % \meta{hook-code-plist}. We are about to apply the next rule and % enter it into the data structure. \cs{@@_apply_label_pair:nnn} will % just call \cs{@@_label_if_exist_apply:nnnF} for the \meta{hook}, and % if no rule is found, also try the \meta{hook} name \texttt{??} % denoting a default hook rule. % % \cs{@@_label_if_exist_apply:nnnF} will check if the rule exists for % the given hook, and if so call \cs{@@_apply_rule:nnn}. % \begin{macrocode} \cs_new_protected:Npn \@@_apply_label_pair:nnn #1#2#3 { % \end{macrocode} % Extra complication: as we use default rules and local hook specific % rules we first have to check if there is a local rule and if that % exist use it. Otherwise check if there is a default rule and use % that. % \begin{macrocode} \@@_label_if_exist_apply:nnnF {#1} {#2} {#3} { % \end{macrocode} % If there is no hook-specific rule we check for a default one and % use that if it exists. % \begin{macrocode} \@@_label_if_exist_apply:nnnF {#1} {#2} { ?? } { } } } \cs_new_protected:Npn \@@_label_if_exist_apply:nnnF #1#2#3 { \if_cs_exist:w g_@@_ #3 _rule_ #1 | #2 _tl \cs_end: % \end{macrocode} % What to do precisely depends on the type of rule we have % encountered. If it is a \texttt{before} rule it will be handled by the % algorithm but other types need to be managed differently. All % this is done in \cs{@@_apply_rule:nnnN}. % \begin{macrocode} \@@_apply_rule:nnn {#1} {#2} {#3} \exp_after:wN \use_none:n \else: \use:nn \fi: } % \end{macrocode} % \end{macro} % % % % % \begin{macro}{\@@_apply_rule:nnn} % This is the code executed in steps T2 and T3 while looping through % the matrix This is part of step T3. We are about to apply the next % rule and enter it into the data structure. The arguments are % \meta{label1}, \meta{label2}, \meta{hook-name}, and \meta{hook-code-plist}. % \begin{macrocode} \cs_new_protected:Npn \@@_apply_rule:nnn #1#2#3 { \cs:w @@_apply_ \cs:w g_@@_#3_reversed_tl \cs_end: rule_ \cs:w g_@@_ #3 _rule_ #1 | #2 _tl \cs_end: :nnn \cs_end: {#1} {#2} {#3} } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_apply_rule_<:nnn,\@@_apply_rule_>:nnn} % The most common cases are \texttt{\string<} and \texttt{\string>} so we handle % that first. They are relations $\prec$ and $\succ$ in TAOCP, and % they dictate sorting. % \begin{macrocode} \cs_new_protected:cpn { @@_apply_rule_<:nnn } #1#2#3 { \@@_debug:n { \@@_msg_pair_found:nnn {#1} {#2} {#3} } \tl_set:cx { \@@_tl_csname:n {#2} } { \int_eval:n{ \cs:w \@@_tl_csname:n {#2} \cs_end: + 1 } } \seq_put_right:cn{ \@@_seq_csname:n {#1} }{#2} } \cs_new_protected:cpn { @@_apply_rule_>:nnn } #1#2#3 { \@@_debug:n { \@@_msg_pair_found:nnn {#1} {#2} {#3} } \tl_set:cx { \@@_tl_csname:n {#1} } { \int_eval:n{ \cs:w \@@_tl_csname:n {#1} \cs_end: + 1 } } \seq_put_right:cn{ \@@_seq_csname:n {#2} }{#1} } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_apply_rule_xE:nnn,\@@_apply_rule_xW:nnn} % These relations make two labels incompatible within a hook. % |xE| makes raises an error if the labels are found in the same % hook, and |xW| makes it a warning. % \begin{macrocode} \cs_new_protected:cpn { @@_apply_rule_xE:nnn } #1#2#3 { \@@_debug:n { \@@_msg_pair_found:nnn {#1} {#2} {#3} } \msg_error:nnnnnn { hooks } { labels-incompatible } {#1} {#2} {#3} { 1 } \use:c { @@_apply_rule_->:nnn } {#1} {#2} {#3} \use:c { @@_apply_rule_<-:nnn } {#1} {#2} {#3} } \cs_new_protected:cpn { @@_apply_rule_xW:nnn } #1#2#3 { \@@_debug:n { \@@_msg_pair_found:nnn {#1} {#2} {#3} } \msg_warning:nnnnnn { hooks } { labels-incompatible } {#1} {#2} {#3} { 0 } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_apply_rule_->:nnn,\@@_apply_rule_<-:nnn} % If we see \texttt{\detokenize{->}} we have to drop code for label % \verb=#3= and carry on. We could do a little better and drop % everything for that label since it doesn't matter where we put % such empty code. However that would complicate the algorithm a % lot with little gain.\footnote{This also has the advantage that % the result of the sorting doesn't change, as it might otherwise do % (for unrelated chunks) if we aren't careful.} So we still % unnecessarily try to sort it in and depending on the rules that % might result in a loop that is otherwise resolved. If that turns % out to be a real issue, we can improve the code. % % Here the code is removed from \cs{l_@@_cur_hook_tl} rather than % \verb=#3= because the latter may be \texttt{??}, and the default % hook doesn't store any code. Removing it instead from \cs{l_@@_cur_hook_tl} % makes the default rules \verb=->= and \verb=<-= work properly. % \begin{macrocode} \cs_new_protected:cpn { @@_apply_rule_->:nnn } #1#2#3 { \@@_debug:n { \@@_msg_pair_found:nnn {#1} {#2} {#3} \iow_term:x{--->~ Drop~ '#2'~ code~ from~ \iow_char:N \\ g_@@_ \l_@@_cur_hook_tl _code_prop ~ because~ of~ '#1' } } \prop_put:Nnn \l_@@_work_prop {#2} { } } \cs_new_protected:cpn { @@_apply_rule_<-:nnn } #1#2#3 { \@@_debug:n { \@@_msg_pair_found:nnn {#1} {#2} {#3} \iow_term:x{--->~ Drop~ '#1'~ code~ from~ \iow_char:N \\ g_@@_ \l_@@_cur_hook_tl _code_prop ~ because~ of~ '#2' } } \prop_put:Nnn \l_@@_work_prop {#1} { } } % \end{macrocode} % \end{macro} % % \begin{macro}{ % \@@_apply_-rule_<:nnn, % \@@_apply_-rule_>:nnn, % \@@_apply_-rule_<-:nnn, % \@@_apply_-rule_->:nnn, % \@@_apply_-rule_xW:nnn, % \@@_apply_-rule_xE:nnn, % } % Reversed rules. % \begin{macrocode} \cs_new_eq:cc { @@_apply_-rule_<:nnn } { @@_apply_rule_>:nnn } \cs_new_eq:cc { @@_apply_-rule_>:nnn } { @@_apply_rule_<:nnn } \cs_new_eq:cc { @@_apply_-rule_<-:nnn } { @@_apply_rule_<-:nnn } \cs_new_eq:cc { @@_apply_-rule_->:nnn } { @@_apply_rule_->:nnn } \cs_new_eq:cc { @@_apply_-rule_xE:nnn } { @@_apply_rule_xE:nnn } \cs_new_eq:cc { @@_apply_-rule_xW:nnn } { @@_apply_rule_xW:nnn } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_msg_pair_found:nnn} % A macro to avoid moving this many tokens around. % \begin{macrocode} \cs_new_protected:Npn \@@_msg_pair_found:nnn #1#2#3 { \iow_term:x{~ \str_if_eq:nnTF {#3} {??} {default} {~normal} ~ rule~ \@@_label_pair:nn {#1} {#2}:~ \use:c { g_@@_#3_rule_ \@@_label_pair:nn {#1} {#2} _tl } ~ found} } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_debug_label_data:N} % % \begin{macrocode} \cs_new_protected:Npn \@@_debug_label_data:N #1 { \iow_term:x{Code~ labels~ for~ sorting:} \iow_term:x{~ \seq_use:Nnnn\l_@@_labels_seq {~and~}{,~}{~and~} } \iow_term:x{^^J Data~ structure~ for~ label~ rules:} \prop_map_inline:Nn #1 { \iow_term:x{~ ##1~ =~ \tl_use:c{ \@@_tl_csname:n {##1} }~ ->~ \seq_use:cnnn{ \@@_seq_csname:n {##1} }{~->~}{~->~}{~->~} } } \iow_term:x{} } % \end{macrocode} % \end{macro} % % % % \begin{macro}{\hook_show:n,\hook_log:n} % \begin{macro}{\@@_log_line:x,\@@_log_line_indent:x} % \begin{macro}{\@@_log:nN} % This writes out information about the hook given in its argument % onto the \texttt{.log} file and the terminal, if \cs{show_hook:n} is % used. Internally both share the same structure, except that at the % end, \cs{hook_show:n} triggers \TeX's prompt. % \begin{macrocode} \cs_new_protected:Npn \hook_log:n #1 { \cs_set_eq:NN \@@_log_cmd:x \iow_log:x \@@_normalize_hook_args:Nn \@@_log:nN {#1} \tl_log:x } \cs_new_protected:Npn \hook_show:n #1 { \cs_set_eq:NN \@@_log_cmd:x \iow_term:x \@@_normalize_hook_args:Nn \@@_log:nN {#1} \tl_show:x } \cs_new_protected:Npn \@@_log_line:x #1 { \@@_log_cmd:x { >~#1 } } \cs_new_protected:Npn \@@_log_line_indent:x #1 { \@@_log_cmd:x { >~\@spaces #1 } } % \end{macrocode} % % \changes{v1.1a}{2023/04/06} % {Changes to add hook arguments (hook-args).} % \changes{v1.1g}{2024/01/03} % {Fix expansion of \cs{@@_print_args:nn} argument (gh/1221).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\@@_log:nN} % {Hooks~with~args} \cs_new_protected:Npn \@@_log:nN #1 #2 { \@@_if_deprecated_generic:nT {#1} { \@@_deprecated_generic_warn:n {#1} \@@_do_deprecated_generic:Nn \@@_log:nN {#1} #2 \exp_after:wN \use_none:nnnnnnnnn \use_none:nnnnn } \@@_preamble_hook:n {#1} \@@_log_cmd:x { ^^J ->~The~ \@@_if_generic:nT {#1} { generic~ } hook~'#1' \@@_if_disabled:nF {#1} { \exp_args:Nne \@@_print_args:nn {#1} { \int_eval:n { \str_count:e { \@@_parameter:n {#1} } / 3 } } } : } % \end{macrocode} % % \begin{macrocode} \@@_if_usable:nF {#1} { \@@_log_line:x { The~hook~is~not~declared. } } \@@_if_disabled:nT {#1} { \@@_log_line:x { The~hook~is~disabled. } } \hook_if_empty:nTF {#1} { #2 { The~hook~is~empty } } { \@@_log_line:x { Code~chunks: } \prop_if_empty:cTF { g_@@_#1_code_prop } { \@@_log_line_indent:x { --- } } { \prop_map_inline:cn { g_@@_#1_code_prop } { \exp_after:wN \cs_set:Npn \exp_after:wN \@@_tmp:w \c_@@_nine_parameters_tl {##2} \@@_log_line_indent:x { ##1~->~\cs_replacement_spec:N \@@_tmp:w } } } % \end{macrocode} % % If there is code in the |top-level| token list, print it: % \begin{macrocode} \@@_log_line:x { Document-level~(top-level)~code \@@_if_usable:nT {#1} { ~(executed~\@@_if_reversed:nTF {#1} {first} {last} ) } : } \@@_log_line_indent:x { \@@_cs_if_empty:cTF { @@_toplevel~#1 } { --- } { -> ~ \cs_replacement_spec:c { @@_toplevel~#1 } } } % \end{macrocode} % % \begin{macrocode} \@@_log_line:x { Extra~code~for~next~invocation: } \@@_log_line_indent:x { \@@_cs_if_empty:cTF { @@_next~#1 } { --- } % \end{macrocode} % % If the token list is not empty we want to display it but without % the first tokens (the code to clear itself) so we call a helper % command to get rid of them. % \begin{macrocode} { -> ~ \exp_last_unbraced:Nf \@@_log_next_code:w { \cs_replacement_spec:c { @@_next~#1 } } } } % \end{macrocode} % % Loop through the rules in a hook and for every rule found, print it. % If no rule is there, print |---|. The boolean \cs{l_@@_tmpa_bool} % here indicates if the hook has no rules. % \begin{macrocode} \@@_log_line:x { Rules: } \bool_set_true:N \l_@@_tmpa_bool \@@_list_rules:nn {#1} { \bool_set_false:N \l_@@_tmpa_bool \@@_log_line_indent:x { ##2~ with~ \str_if_eq:nnT {##3} {??} { default~ } relation~ ##1 } } \bool_if:NT \l_@@_tmpa_bool { \@@_log_line_indent:x { --- } } % \end{macrocode} % % When the hook is declared (that is, the sorting algorithm is applied % to that hook) and not empty % \begin{macrocode} \bool_lazy_and:nnTF { \@@_if_usable_p:n {#1} } { ! \hook_if_empty_p:n {#1} } { \@@_log_line:x { Execution~order \bool_if:NTF \l_@@_tmpa_bool { \@@_if_reversed:nT {#1} { ~(after~reversal) } } { ~(after~ \@@_if_reversed:nT {#1} { reversal~and~ } applying~rules) } : } #2 % \tl_show:n { \@spaces \clist_if_empty:cTF { g_@@_#1_labels_clist } { --- } { \clist_use:cn { g_@@_#1_labels_clist } { ,~ } } } } { \@@_log_line:x { Execution~order: } #2 { \@spaces Not~set~because~the~hook~ \@@_if_usable:nTF {#1} { code~pool~is~empty } { is~\@@_if_disabled:nTF {#1} {disabled} {undeclared} } } } } } %\EndIncludeInRelease % %\IncludeInRelease{2020/10/01}{\@@_log:nN} % {Hooks~with~args} %\cs_new_protected:Npn \@@_log:nN #1 #2 % { % \@@_if_deprecated_generic:nT {#1} % { % \@@_deprecated_generic_warn:n {#1} % \@@_do_deprecated_generic:Nn \@@_log:nN {#1} #2 % \exp_after:wN \use_none:nnnnnnnnn \use_none:nnnnn % } % \@@_preamble_hook:n {#1} % \@@_log_cmd:x % { ^^J ->~The~ \@@_if_generic:nT % {#1} { generic~ } hook~'#1': } % \@@_if_usable:nF {#1} % { \@@_log_line:x { The~hook~is~not~declared. } } % \@@_if_disabled:nT {#1} % { \@@_log_line:x { The~hook~is~disabled. } } % \hook_if_empty:nTF {#1} % { #2 { The~hook~is~empty } } % { % \@@_log_line:x { Code~chunks: } % \prop_if_empty:cTF { g_@@_#1_code_prop } % { \@@_log_line_indent:x { --- } } % { % \prop_map_inline:cn { g_@@_#1_code_prop } % { \@@_log_line_indent:x % { ##1~->~\tl_to_str:n {##2} } } % } % \@@_log_line:x % { % Document-level~(top-level)~code % \@@_if_usable:nT {#1} % { ~(executed~ % \@@_if_reversed:nTF {#1} {first} {last} ) } : % } % \@@_log_line_indent:x % { % \tl_if_empty:cTF { @@_toplevel~#1 } % { --- } % { -> ~ \exp_args:Nv \tl_to_str:n % { @@_toplevel~#1 } } % } % \@@_log_line:x { Extra~code~for~next~invocation: } % \@@_log_line_indent:x % { % \tl_if_empty:cTF { @@_next~#1 } % { --- } % { ->~ \exp_args:Nv \@@_log_next_code:n % { @@_next~#1 } } % } % \@@_log_line:x { Rules: } % \bool_set_true:N \l_@@_tmpa_bool % \@@_list_rules:nn {#1} % { % \bool_set_false:N \l_@@_tmpa_bool % \@@_log_line_indent:x % { % ##2~ with~ % \str_if_eq:nnT {##3} {??} { default~ } % relation~ ##1 % } % } % \bool_if:NT \l_@@_tmpa_bool % { \@@_log_line_indent:x { --- } } % \bool_lazy_and:nnTF % { \@@_if_usable_p:n {#1} } % { ! \hook_if_empty_p:n {#1} } % { % \@@_log_line:x % { % Execution~order % \bool_if:NTF \l_@@_tmpa_bool % { \@@_if_reversed:nT % {#1}{ ~(after~reversal) } } % { ~(after~ % \@@_if_reversed:nT {#1} { reversal~and~ } % applying~rules) % } : % } % #2 % \tl_show:n % { % \@spaces % \clist_if_empty:cTF { g_@@_#1_labels_clist } % { --- } % { \clist_use:cn % { g_@@_#1_labels_clist } { ,~ } } % } % } % { % \@@_log_line:x { Execution~order: } % #2 % { % \@spaces Not~set~because~the~hook~ % \@@_if_usable:nTF {#1} % { code~pool~is~empty } % { is~\@@_if_disabled:nTF % {#1} {disabled} {undeclared} } % } % } % } % } %\EndIncludeInRelease % \end{macrocode} % % \begin{macro}{\@@_log_next_code:n} % To display the code for next invocation only (i.e., from % \cs{AddToHookNext} we have to remove the string % \cs{@@_clear_next:n}\Arg{hook}, so the simplest is to use a macro % delimited by a \verb|}|$_12$. % \changes{v1.1a}{2023/04/06} % {Changes to add hook arguments (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\@@_log_next_code:n} % {Hooks~with~args} \exp_last_unbraced:NNNNo \cs_new:Npn \@@_log_next_code:w #1 \c_right_brace_str { } %\EndIncludeInRelease %\IncludeInRelease{2020/10/01}{\@@_log_next_code:n} % {Hooks~with~args} %\cs_gset:Npn \@@_log_next_code:n #1 % { \exp_args:No \tl_to_str:n { \use_none:nn #1 } } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_print_args:n} % Pretty-prints the number of arguments of a hook. % \begin{macrocode} \cs_new:Npn \@@_print_args:nn #1 #2 { \int_compare:nNnT {#2} > { 0 } { \@@_if_declared:nT {#1} { \use_none:nnn } \@@_if_cmd_hook:nT {#1} { \use_i:nnn { ~ (unknown ~ } } \use:n { ~ (#2 ~ } argument \int_compare:nNnT {#2} > { 1 } { s } ) } } % \end{macrocode} % \end{macro} % % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\@@_list_rules:nn} % \begin{macro}{\@@_list_one_rule:nnn,\@@_list_if_rule_exists:nnnF} % This macro takes a \meta{hook} and an \meta{inline function} and % loops through each pair of \meta{labels} in the \meta{hook}, and if % there is a relation between this pair of \meta{labels}, the % \meta{inline function} is executed with |#1|${}={}$\meta{relation}, % |#2|${}={}$\meta{label_1}\verb=|=\meta{label_2}, % and |#3|${}={}$\meta{hook} (the latter may be the argument |#1| to % \cs{@@_list_rules:nn}, or \texttt{??} if it is a default rule). % \begin{macrocode} \cs_new_protected:Npn \@@_list_rules:nn #1 #2 { \cs_set_protected:Npn \@@_tmp:w ##1 ##2 ##3 {#2} \prop_map_inline:cn { g_@@_#1_code_prop } { \prop_map_inline:cn { g_@@_#1_code_prop } { \@@_if_label_case:nnnnn {##1} {####1} { \prop_map_break: } { \@@_list_one_rule:nnn {##1} {####1} } { \@@_list_one_rule:nnn {####1} {##1} } {#1} } } } % \end{macrocode} % % These two are quite similar to \cs{@@_apply_label_pair:nnn} and % \cs{@@_label_if_exist_apply:nnnF}, respectively, but rather than % applying the rule, they pass it to the \meta{inline function}. % \begin{macrocode} \cs_new_protected:Npn \@@_list_one_rule:nnn #1#2#3 { \@@_list_if_rule_exists:nnnF {#1} {#2} {#3} { \@@_list_if_rule_exists:nnnF {#1} {#2} { ?? } { } } } \cs_new_protected:Npn \@@_list_if_rule_exists:nnnF #1#2#3 { \if_cs_exist:w g_@@_ #3 _rule_ #1 | #2 _tl \cs_end: \exp_args:Nv \@@_tmp:w { g_@@_ #3 _rule_ #1 | #2 _tl } { #1 | #2 } {#3} \exp_after:wN \use_none:nn \fi: \use:n } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\@@_debug_print_rules:n} % A shorthand for debugging that prints similar to \cs{prop_show:N}. % \begin{macrocode} \cs_new_protected:Npn \@@_debug_print_rules:n #1 { \iow_term:n { The~hook~#1~contains~the~rules: } \cs_set_protected:Npn \@@_tmp:w ##1 { \@@_list_rules:nn {#1} { \iow_term:x { > ##1 {####2} ##1 => ##1 {####1} \str_if_eq:nnT {####3} {??} { ~(default) } } } } \exp_args:No \@@_tmp:w { \use:nn { ~ } { ~ } } } % \end{macrocode} % \end{macro} % % % % % \subsection{Specifying code for next invocation} % % \begin{macro}{\hook_gput_next_code:nn} % \changes{v1.1a}{2023/04/06} % {Add \cs{hook_gput_next_code_with_args:nn} (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\hook_gput_next_code:nn} % {Hooks~with~args} \cs_new_protected:Npn \hook_gput_next_code:nn #1 #2 { \@@_replacing_args_false: \@@_normalize_hook_args:Nn \@@_gput_next_code:nn {#1} {#2} \@@_replacing_args_reset: } \cs_new_protected:Npn \hook_gput_next_code_with_args:nn #1 #2 { \@@_replacing_args_true: \@@_normalize_hook_args:Nn \@@_gput_next_code:nn {#1} {#2} \@@_replacing_args_reset: } %\EndIncludeInRelease %\IncludeInRelease{2020/10/01}{\hook_gput_next_code:nn} % {Hooks~with~args} %\cs_gset_protected:Npn \hook_gput_next_code:nn #1 % { \@@_normalize_hook_args:Nn % \@@_gput_next_code:nn {#1} } %\cs_gset_protected:Npn \hook_gput_next_code_with_args:nn #1 #2 { } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_gput_next_code:nn} % \begin{macrocode} \cs_new_protected:Npn \@@_gput_next_code:nn #1 #2 { \@@_if_disabled:nTF {#1} { \msg_error:nnn { hooks } { hook-disabled } {#1} } { \@@_if_structure_exist:nTF {#1} { \@@_gput_next_do:nn } { \@@_try_declaring_generic_next_hook:nn } {#1} {#2} } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_gput_next_do:nn} % Start by sanity-checking with \cs{@@_chk_args_allowed:nn}. % Then check if the ``next code'' token list is empty: if so we need % to add a \cs{tl_gclear:c} to clear it, so the code lasts for one % usage only. The token list is cleared early so that nested usages % don't get lost. \cs{tl_gclear:c} is used instead of % \cs{tl_gclear:N} in case the hook is used in an expansion-only % context, so the token list doesn't expand before \cs{tl_gclear:N}: % that would make an infinite loop. Also in case the main code token % list is empty, the hook code has to be updated to add the next % execution token list. % \changes{v1.1a}{2023/04/06} % {Changes to add hook arguments (hook-args).} % \changes{v1.1c}{2023/04/19} % {Initialise hook structure when adding 'next' code (gh/1052).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\@@_gput_next_do:nn} % {Hooks~with~args} \cs_new_protected:Npn \@@_gput_next_do:nn #1 { \@@_init_structure:n {#1} \@@_chk_args_allowed:nn {#1} { AddToHookNext } \@@_cs_if_empty:cT { @@~#1 } { \@@_update_hook_code:n {#1} } \@@_cs_if_empty:cT { @@_next~#1 } { \@@_next_gset:nn {#1} { \@@_clear_next:n {#1} } } \@@_cs_gput_right:nnn { _next } {#1} } %\EndIncludeInRelease %\IncludeInRelease{2020/10/01}{\@@_gput_next_do:nn} % {Hooks~with~args} %\cs_gset_protected:Npn \@@_gput_next_do:nn #1 % { % \exp_args:Nc \@@_gput_next_do:Nnn % { @@_next~#1 } {#1} % } %\cs_gset_protected:Npn \@@_gput_next_do:Nnn #1 #2 % { % \tl_if_empty:cT { @@~#2 } % { \@@_update_hook_code:n {#2} } % \tl_if_empty:NT #1 % { \@@_tl_gset:Nn #1 { \@@_clear_next:n {#2} } } % \@@_tl_gput_right:Nn #1 % } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % \begin{macro}{\hook_gclear_next_code:n} % Discard anything set up for next invocation of the hook. % \changes{v1.0o}{2021/07/27}{Macro made public} % \begin{macrocode} \cs_new_protected:Npn \hook_gclear_next_code:n #1 { \@@_normalize_hook_args:Nn \@@_clear_next:n {#1} } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_clear_next:n} % \changes{v1.1a}{2023/04/06} % {Changes to add hook arguments (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\@@_clear_next:n} % {Hooks~with~args} \cs_new_protected:Npn \@@_clear_next:n #1 { \@@_next_gset:nn {#1} { } } %\EndIncludeInRelease %\IncludeInRelease{2020/10/01}{\@@_clear_next:n} % {Hooks~with~args} %\cs_gset_protected:Npn \@@_clear_next:n #1 % { \cs_gset_eq:cN { @@_next~#1 } \c_empty_tl } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % % \subsection{Using the hook} % % \begin{macro}{\hook_use:n} % \begin{macro}[EXP]{\@@_use_initialized:n} % \begin{macro}{\@@_preamble_hook:n} % \cs{hook_use:n} as defined here is used in the preamble, where % hooks aren't initialized by default. \cs{@@_use_initialized:n} is % also defined, which is the non-\tn{protected} version for use within % the document. Their definition is identical, except for the % \cs{@@_preamble_hook:n} (which wouldn't hurt in the expandable % version, but it would be an unnecessary extra expansion). % % \cs{@@_use_initialized:n} holds the expandable definition while in % the preamble. \cs{@@_preamble_hook:n} initializes the hook in the % preamble, and is redefined to \cs{use_none:n} at |\begin{document}|. % % Both versions do the same thing internally: they check that the hook exists as % given, and if so they use it as quickly as possible. % % At |\begin{document}|, all hooks are initialized, and any change in % them causes an update, so \cs{hook_use:n} can be made expandable. % This one is better not protected so that it can expand into nothing % if containing no code. Also important in case of generic hooks that % we do not generate a \cs[no-index]{relax} as a side effect of % checking for a csname. In contrast to the \TeX{} low-level % \verb=\csname ...\endcsname= construct \cs{tl_if_exist:c} is % careful to avoid this. % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\hook_use:n} % {Hooks~with~args} \cs_new_protected:Npn \hook_use:n #1 { \@@_preamble_hook:n {#1} \@@_use_initialized:n {#1} } \cs_new:Npn \@@_use_initialized:n #1 { \if_cs_exist:w @@~#1 \cs_end: \cs:w @@~#1 \use_i:nn \fi: \use_none:n \cs_end: } \cs_new_protected:Npn \@@_preamble_hook:n #1 { \if_cs_exist:w @@~#1 \cs_end: \@@_initialize_hook_code:n {#1} \fi: } %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2021/11/15}{\hook_use:n} % {Standardise~generic~hook~names} %\cs_new_protected:Npn \hook_use:n #1 % { % \tl_if_exist:cT { @@~#1 } % { % \@@_preamble_hook:n {#1} % \cs:w @@~#1 \cs_end: % } % } %\cs_new:Npn \@@_use_initialized:n #1 % { % \if_cs_exist:w @@~#1 \cs_end: % \cs:w @@~#1 \exp_after:wN \cs_end: % \fi: % } %\cs_new_protected:Npn \@@_preamble_hook:n #1 % { \@@_initialize_hook_code:n {#1} } %\cs_new:Npn \hook_use:nnw #1 { } %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2020/10/01}{\hook_use:n} % {Standardise~generic~hook~names} %\cs_new_protected:Npn \hook_use:n #1 % { % \tl_if_exist:cTF { @@~#1 } % { % \@@_preamble_hook:n {#1} % \cs:w @@~#1 \cs_end: % } % { \@@_use:wn #1 / \s_@@_mark {#1} } % } %\cs_new:Npn \@@_use_initialized:n #1 % { % \if_cs_exist:w @@~#1 \cs_end: % \else: % \@@_use_undefined:w % \fi: % \cs:w @@~#1 \@@_use_end: % } %\cs_new:Npn \@@_use_undefined:w % #1 #2 @@~#3 \@@_use_end: % { % #1 % fi % \@@_use:wn #3 / \s_@@_mark {#3} % } %\cs_new_protected:Npn \@@_preamble_hook:n #1 % { \@@_initialize_hook_code:n {#1} } %\cs_new_eq:NN \@@_use_end: \cs_end: %\cs_new:Npn \hook_use:nnw #1 { } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % % \begin{macro}{\hook_use:nnw} % \begin{macro}[EXP]{\@@_use_initialized:nnw} % \changes{v1.1a}{2023/04/06} % {Add \cs{hook_use:nnw} (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\hook_use:nnw} % {Hooks~with~args} \cs_new_protected:Npn \hook_use:nnw #1 { \@@_preamble_hook:n {#1} \@@_use_initialized:nnw {#1} } \cs_new:Npn \@@_use_initialized:nnw #1 #2 { \cs:w \if_cs_exist:w @@~#1 \cs_end: @@~#1 \else: use_none: \prg_replicate:nn {#2} { n } \fi: \cs_end: } %\EndIncludeInRelease %\IncludeInRelease{2020/10/01}{\hook_use:nnw} % {Hooks~with~args} %\cs_gset:Npn \hook_use:nnw #1 #2 % { \use:c { use_none: \prg_replicate:nn {#2} { n } } } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % \end{macro} % % % \begin{macro}{\@@_post_initialization_defs:} % \changes{v1.1a}{2023/04/06} % {Macro added (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\@@_post_initialization_defs:} % {Hooks~with~args} \cs_new_protected:Npn \@@_post_initialization_defs: { \cs_gset_eq:NN \hook_use:n \@@_use_initialized:n \cs_gset_eq:NN \hook_use:nnw \@@_use_initialized:nnw \cs_gset_eq:NN \@@_preamble_hook:n \use_none:n \cs_gset_eq:NN \@@_post_initialization_defs: \prg_do_nothing: } %\EndIncludeInRelease %\IncludeInRelease{2020/10/01}{\@@_post_initialization_defs:} % {Hooks~with~args} %\cs_undefine:N \@@_post_initialization_defs: %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % % \begin{macro}[EXP]{\@@_use:wn} % \begin{macro}{\@@_try_file_hook:n,\@@_if_usable_use:n} % \cs{@@_use:wn} does a quick check to test if the current hook is a % file hook: those need a special treatment. If it is not, the hook % does not exist. If it is, then \cs{@@_try_file_hook:n} is called, % and checks that the current hook is a file-specific hook using % \cs{@@_if_file_hook:wTF}. If it's not, then it's a generic |file/| % hook and is used if it exist. % % If it is a file-specific hook, it passes through the same % normalization as during declaration, and then it is used if defined. % \cs{@@_if_usable_use:n} checks if the hook exist, and calls % \cs{@@_preamble_hook:n} if so, then uses the hook. % \begin{macrocode} %\IncludeInRelease{2021/11/15}{\@@_use:wn} % {Standardise~generic~hook~names} %\EndIncludeInRelease %\IncludeInRelease{2020/10/01}{\@@_use:wn} % {Standardise~generic~hook~names} %\cs_new:Npn \@@_use:wn #1 / #2 \s_@@_mark #3 % { % \str_if_eq:nnTF {#1} { file } % { \@@_try_file_hook:n {#3} } % { } % Hook doesn't exist % } % \end{macrocode} % % \changes{v1.0s}{2021/09/28} % {Correct usage of older \cs{@@_if_file_hook:wTF} (gh/675)} % \changes{v1.1h}{2024/01/24} % {Correct usage of older \cs{@@_if_file_hook:wTF} (gh/1243)} % \begin{macrocode} %\cs_new_protected:Npn \@@_try_file_hook:n #1 % { % \@@_if_file_hook:wTF #1 / / \s_@@_mark % { % \exp_args:Ne \@@_if_usable_use:n % { \exp_args:Ne \@@_file_hook_normalize:n {#1} } % } % { \@@_if_usable_use:n {#1} } % % file/ generic hook (e.g. file/before) % } % \end{macrocode} % % \begin{macrocode} %\cs_new_protected:Npn \@@_if_usable_use:n #1 % { % \tl_if_exist:cT { @@~#1 } % { % \@@_preamble_hook:n {#1} % \cs:w @@~#1 \cs_end: % } % } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\hook_use_once:n,\hook_use_once:nnw} % For hooks that can and should be used only once we have a special % use command that further inhibits the hook from getting more code % added to it. This has the effect that any % further code added to the hook is executed immediately rather % than stored in the hook. % % The code needs some gymnastics to prevent space trimming from the % hook name, since \cs{hook_use:n} and \cs{hook_use_once:n} are % documented to not trim spaces. % % \changes{v1.0r}{2021/09/06}{Clean up after \cs{UseOneTimeHook} (gh/606)} % \changes{v1.1a}{2023/04/06} % {Add \cs{hook_use_once:nnw} (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\hook_use_once:nnw} % {Hooks~with~args} \cs_new_protected:Npn \hook_use_once:n #1 { \@@_if_execute_immediately:nF {#1} { \@@_normalize_hook_args:Nn \@@_use_once:nn { \use:n {#1} } { 0 } } } \cs_new_protected:Npn \hook_use_once:nnw #1 #2 { \@@_if_execute_immediately:nF {#1} { \@@_normalize_hook_args:Nn \@@_use_once:nn { \use:n {#1} } {#2} } } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % \begin{macrocode} %\IncludeInRelease{2020/10/01}{\hook_use_once:nnw} % {Hooks~with~args} %\cs_gset_protected:Npn \hook_use_once:n #1 % { % \@@_if_execute_immediately:nF {#1} % { \@@_normalize_hook_args:Nn \@@_use_once:n % { \use:n {#1} } } % } %\cs_gset:Npn \hook_use_once:nnw #1 #2 % { \use:c { use_none: \prg_replicate:nn {#2} { n } } } %\EndIncludeInRelease % \end{macrocode} % % \begin{macro}{\@@_use_once:nn} % \changes{v1.1a}{2023/04/06} % {Changes to add hook arguments (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\@@_use_once:nn} % {Hooks~with~args} \cs_new_protected:Npn \@@_use_once:nn #1 #2 { \@@_preamble_hook:n {#1} \@@_use_once_set:n {#1} % \end{macrocode} % When a hook has arguments, the call to \cs{@@_use_initialized:n}, % should be the very last thing to happen, otherwise the arguments % grabbed will be wrong. So, to clean up after the hook we need to % cheat a bit and sneak the cleanup code at the end of the hook, % along with the next execution code. % \begin{macrocode} \@@_replacing_args_false: \@@_cs_gput_right:nnn { _next } {#1} { \@@_use_once_clear:n {#1} } \@@_replacing_args_reset: \@@_if_usable:nTF {#1} { \@@_use_initialized:n {#1} } { \int_compare:nNnT {#2} > { 0 } { \use:c { use_none: \prg_replicate:nn {#2} { n } } } } } %\EndIncludeInRelease % %\IncludeInRelease{2020/10/01}{\@@_use_once:nn} % {Hooks~with~args} %\cs_gset_protected:Npn \@@_use_once:n #1 % { % \@@_preamble_hook:n {#1} % \@@_use_once_set:n {#1} % \@@_use_initialized:n {#1} % \@@_use_once_clear:n {#1} % } %\cs_undefine:N \@@_use_once:nn %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_use_once_set:n} % \begin{macro}{\@@_use_once_clear:n} % \cs{@@_use_once_set:n} is used before the actual hook code is % executed so that any usage of \cs{AddToHook} inside the hook causes % the code to execute immediately. Setting % \cs[no-index]{g_@@_\meta{hook}_reversed_tl} to |I| prevents further % code from being added to the hook. \cs{@@_use_once_clear:n} then % clears the hook so that any further call to \cs{hook_use:n} or % \cs{hook_use_once:n} will expand to nothing. % \changes{v1.0r}{2021/09/06}{Clean up after \cs{UseOneTimeHook} (gh/606)} % \changes{v1.0u}{2022/05/13}{Check if prop exists to avoid l3debug error} % \changes{v1.1a}{2023/04/06} % {Changes to add hook arguments (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\@@_use_once_clear:n} % {Hooks~with~args} \cs_new_protected:Npn \@@_use_once_set:n #1 { \@@_tl_gset:cn { g_@@_#1_reversed_tl } { I } } \cs_new_protected:Npn \@@_use_once_clear:n #1 { \@@_code_gset:nn {#1} { } \@@_next_gset:nn {#1} { } \@@_toplevel_gset:nn {#1} { } \prop_gclear_new:c { g_@@_#1_code_prop } } %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2020/10/01}{\@@_use_once_clear:n} % {Hooks~with~args} %\cs_new_protected:Npn \@@_use_once_clear:n #1 % { % \@@_tl_gclear:c { @@~#1 } % \@@_tl_gclear:c { @@_next~#1 } % \@@_tl_gclear:c { @@_toplevel~#1 } % \prop_gclear_new:c { g_@@_#1_code_prop } % } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[pTF]{\@@_if_execute_immediately:n} % To check whether the code being added should be executed immediately % (that is, if the hook is a one-time hook), we check if % \cs[no-index]{g_@@_\meta{hook}_reversed_tl} is |I|. The gymnastics % around \cs{if:w} is there to allow the |reversed| token list to be % empty. % \changes{v1.0r}{2021/09/06}{Macro added (gh/606)} % \changes{v1.1a}{2023/04/06} % {Changes to add hook arguments (hook-args).} % \begin{macrocode} \prg_new_conditional:Npnn \@@_if_execute_immediately:n #1 { T, F, TF } { \exp_after:wN \@@_use_none_delimit_by_s_mark:w \if:w I \if_cs_exist:w g_@@_#1_reversed_tl \cs_end: \cs:w g_@@_#1_reversed_tl \exp_after:wN \cs_end: \fi: X \s_@@_mark \prg_return_true: \else: \s_@@_mark \prg_return_false: \fi: } % \end{macrocode} % \end{macro} % % \subsection{Querying a hook} % % Simpler data types, like token lists, have three possible states; they % can exist and be empty, exist and be non-empty, and they may not % exist, in which case emptiness doesn't apply (though % \cs{tl_if_empty:N} returns false in this case). % % Hooks are a bit more complicated: they have several other states as % discussed in \ref{sec:existence}. % A hook may exist or not, and either way it may or may not be empty % (even a hook that doesn't exist may be non-empty) or may be disabled. % % A hook is said to be empty when no code was added to it, either to % its permanent code pool, or to its ``next'' token list. The hook % doesn't need to be declared to have code added to its code pool % (it may happen that a package $A$ defines a hook \hook{foo}, but % it's loaded after package $B$, which adds some code to that hook. % In this case it is important that the code added by package $B$ is % remembered until package $A$ is loaded). % % All other states can only be queried with internal tests as the % different states are irrelevant for package code. % % \begin{macro}[pTF]{\hook_if_empty:n} % Test if a hook is empty (that is, no code was added to that hook). % A \meta{hook} being empty means that all three of its % \cs{g_@@_\meta{hook}_code_prop}, its % \cs{@@_toplevel\textvisiblespace\meta{hook}} and its % \cs{@@_next\textvisiblespace\meta{hook}} are empty. % \changes{v1.1a}{2023/04/06} % {Changes to add hook arguments (hook-args).} % \changes{v1.1c}{2023/04/19} % {Simpler and faster version (gh/1052).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\hook_if_empty:n} % {Hooks~with~args} \prg_new_conditional:Npnn \hook_if_empty:n #1 { p , T , F , TF } { \if:w T \prop_if_exist:cT { g_@@_#1_code_prop } { \prop_if_empty:cF { g_@@_#1_code_prop } { F } } \@@_cs_if_empty:cF { @@_toplevel~#1 } { F } \@@_cs_if_empty:cF { @@_next~#1 } { F } T \prg_return_true: \else: \prg_return_false: \fi: } %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2020/10/01}{\hook_if_empty:n} % {Hooks~with~args} %\prg_new_conditional:Npnn \hook_if_empty:n #1 { p , T , F , TF } % { % \@@_if_structure_exist:nTF {#1} % { % \bool_lazy_and:nnTF % { \prop_if_empty_p:c { g_@@_#1_code_prop } } % { % \bool_lazy_and_p:nn % { \tl_if_empty_p:c { @@_toplevel~#1 } } % { \tl_if_empty_p:c { @@_next~#1 } } % } % { \prg_return_true: } % { \prg_return_false: } % } % { \prg_return_true: } % } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % % \begin{macro}[pTF]{\@@_if_usable:n} % A hook is usable if the % token list that stores the sorted code for that hook, % \cs[no-index]{@@\textvisiblespace\meta{hook}}, exists. The property % list \cs[no-index]{g_@@_\meta{hook}_code_prop} cannot be used here % because often it is necessary to add code to a hook without knowing % if such hook was already declared, or even if it will ever be % (for example, in case the package that defines it isn't loaded). % \begin{macrocode} \prg_new_conditional:Npnn \@@_if_usable:n #1 { p , T , F , TF } { \cs_if_exist:cTF { @@~#1 } { \prg_return_true: } { \prg_return_false: } } % \end{macrocode} % \end{macro} % % \begin{macro}[pTF]{\@@_if_structure_exist:n} % % An internal check if the hook has already its basic internal % structure set up with % \cs{@@_init_structure:n}. This means that the hook was already used somehow % (a code chunk or rule was added to it), but it still wasn't declared % with \cs{hook_new:n}. % \begin{macrocode} \prg_new_conditional:Npnn \@@_if_structure_exist:n #1 { p , T , F , TF } { \prop_if_exist:cTF { g_@@_#1_code_prop } { \prg_return_true: } { \prg_return_false: } } % \end{macrocode} % \end{macro} % % % \begin{macro}[pTF]{\@@_if_declared:n} % % Internal test to check if the hook was officially declared with % \cs{hook_new:n} or a variant. % \begin{macrocode} \prg_new_conditional:Npnn \@@_if_declared:n #1 { p, T, F, TF } { \tl_if_exist:cTF { g_@@_#1_declared_tl } { \prg_return_true: } { \prg_return_false: } } % \end{macrocode} % \end{macro} % % \begin{macro}[pTF]{\@@_if_reversed:n} % An internal conditional that checks if a hook is reversed. % \begin{macrocode} \prg_new_conditional:Npnn \@@_if_reversed:n #1 { p , T , F , TF } { \exp_after:wN \@@_use_none_delimit_by_s_mark:w \if:w - \cs:w g_@@_#1_reversed_tl \cs_end: \s_@@_mark \prg_return_true: \else: \s_@@_mark \prg_return_false: \fi: } % \end{macrocode} % \end{macro} % % \begin{macro}[pTF]{\@@_if_generic:n} % \begin{macro}[pTF]{\@@_if_deprecated_generic:n} % An internal conditional that checks if a name belongs to a generic % hook. The deprecated version needs to check if |#3| is empty to % avoid returning true on \hook{file/before}, for example. % \begin{macrocode} \prg_new_conditional:Npnn \@@_if_generic:n #1 { T, TF } { \@@_if_generic:w #1 / / / \s_@@_mark } \cs_new:Npn \@@_if_generic:w #1 / #2 / #3 / #4 \s_@@_mark { \cs_if_exist:cTF { c_@@_generic_#1/./#3_tl } { \prg_return_true: } { \prg_return_false: } } \prg_new_conditional:Npnn \@@_if_deprecated_generic:n #1 { T, TF } { \@@_if_deprecated_generic:w #1 / / / \s_@@_mark } \cs_new:Npn \@@_if_deprecated_generic:w #1 / #2 / #3 / #4 \s_@@_mark { \cs_if_exist:cTF { c_@@_deprecated_#1/./#2_tl } { \tl_if_empty:nTF {#3} { \prg_return_false: } { \prg_return_true: } } { \prg_return_false: } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[pTF]{\@@_if_cmd_hook:n} % \begin{macro}[pTF]{\@@_if_cmd_hook:w} % An internal conditional that checks if a given hook is a valid % generic \hook{cmd} hook. % \changes{v1.1d}{2023/05/21} % {Changes to allow support arguments in cmd hooks (cmd-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\@@_if_cmd_hook:n} % {Hooks~with~args} \prg_new_conditional:Npnn \@@_if_cmd_hook:n #1 { T } { \@@_if_cmd_hook:w #1 / / / \s_@@_mark } \cs_new:Npn \@@_if_cmd_hook:w #1 / #2 / #3 / #4 \s_@@_mark { \if:w Y \str_if_eq:nnF {#1} { cmd } { N } \tl_if_exist:cF { c_@@_generic_#1/./#3_tl } { N } Y \prg_return_true: \else: \prg_return_false: \fi: } %\EndIncludeInRelease %\IncludeInRelease{2020/10/01}{\@@_if_cmd_hook:n} % {Hooks~with~args} %\cs_undefine:N \@@_if_cmd_hook:nT %\EndIncludeInRelease % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[pTF]{\@@_if_generic_reversed:n} % An internal conditional that checks if a name belongs to a generic % reversed hook. % \begin{macrocode} \prg_new_conditional:Npnn \@@_if_generic_reversed:n #1 { T } { \@@_if_generic_reversed:w #1 / / / \scan_stop: } \cs_new:Npn \@@_if_generic_reversed:w #1 / #2 / #3 / #4 \scan_stop: { \if_charcode:w - \cs:w c_@@_generic_#1/./#3_tl \cs_end: \prg_return_true: \else: \prg_return_false: \fi: } % \end{macrocode} % \end{macro} % % \begin{macro}[EXP]{\@@_if_replacing_args:TF} % \begin{macro}[EXP]{\@@_misused_if_replacing_args:nn} % \begin{macro}{\@@_replacing_args_true:} % \begin{macro}{\@@_replacing_args_false:} % \begin{macro}{\@@_replacing_args_reset:} % \begin{macro}{\g_@@_replacing_stack_seq} % An internal conditional that checks if the code being added to the % hook contains arguments. % \changes{v1.1a}{2023/04/06} % {Macro added (hook-args).} % \begin{macrocode} \seq_new:N \g_@@_replacing_stack_seq \cs_new:Npn \@@_misused_if_replacing_args:nn #1 #2 { \msg_expandable_error:nnn { latex2e } { should-not-happen } { Misused~\@@_if_replacing_args:. } } \cs_new:Npn \@@_if_replacing_args:TF { \@@_misused_if_replacing_args:nn } \cs_new_protected:Npn \@@_replacing_args_true: { \seq_gpush:No \g_@@_replacing_stack_seq { \@@_if_replacing_args:TF } \cs_set:Npn \@@_if_replacing_args:TF { \use_i:nn } } \cs_new_protected:Npn \@@_replacing_args_false: { \seq_gpush:No \g_@@_replacing_stack_seq { \@@_if_replacing_args:TF } \cs_set:Npn \@@_if_replacing_args:TF { \use_ii:nn } } \cs_new_protected:Npn \@@_replacing_args_reset: { \seq_gpop:NN \g_@@_replacing_stack_seq \l_@@_return_tl \cs_gset_eq:NN \@@_if_replacing_args:TF \l_@@_return_tl } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \subsection{Messages} % % Hook errors are LaTeX kernel errors: % \begin{macrocode} \prop_gput:Nnn \g_msg_module_type_prop { hooks } { LaTeX } % \end{macrocode} % \changes{v1.0q}{2021/08/27}{Internal message name changes} % And so are kernel errors (this should move elsewhere eventually). % \begin{macrocode} \prop_gput:Nnn \g_msg_module_type_prop { latex2e } { LaTeX } \prop_gput:Nnn \g_msg_module_name_prop { latex2e } { kernel } % \end{macrocode} % % \begin{macrocode} \msg_new:nnnn { hooks } { labels-incompatible } { Labels~'#1'~and~'#2'~are~incompatible \str_if_eq:nnF {#3} {??} { ~in~hook~'#3' } .~ \int_compare:nNnTF {#4} = { 1 } { The~ code~ for~ both~ labels~ will~ be~ dropped. } { You~ may~ see~ errors~ later. } } { LaTeX~found~two~incompatible~labels~in~the~same~hook.~ This~indicates~an~incompatibility~between~packages. } % \end{macrocode} % % \begin{macrocode} \msg_new:nnnn { hooks } { exists } { Hook~'#1'~ has~ already~ been~ declared. } { There~ already~ exists~ a~ hook~ declaration~ with~ this~ name.\\ Please~ use~ a~ different~ name~ for~ your~ hook.} % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2023/06/01}{too-many-args} % {Hooks~with~args} % \end{macrocode} % % \changes{v1.1a}{2023/04/06} % {Messages 'too-many-args', 'without-args' and 'one-time-args' added (hook-args).} % \begin{macrocode} \msg_new:nnnn { hooks } { too-many-args } { Too~many~arguments~for~hook~'#1'. } { You~tried~to~declare~a~hook~with~#2~arguments,~but~a~ hook~can~only~have~up~to~nine.~LaTeX~will~define~this~ hook~with~nine~arguments. } % \end{macrocode} % % \begin{macrocode} \msg_new:nnnn { hooks } { without-args } { Hook~'#1'~has~no~arguments. } { You~tried~to~use~\iow_char:N\\#2WithArguments~ on~a~hook~that~takes~no~arguments.\\ Check~the~usage~of~the~hook~or~use~\iow_char:N\\#2~instead.\\ \\ LaTeX~will~use~\iow_char:N\\#2. } % \end{macrocode} % % \begin{macrocode} \msg_new:nnnn { hooks } { one-time-args } { You~can't~have~arguments~in~used~one-time~hook~'#1'. } { You~tried~to~use~\iow_char:N\\#2WithArguments~ on~a~one-time~hook~that~has~already~been~used.~ You~have~to~add~the~code~before~the~hook~is~used,~ or~add~the~code~without~arguments~using~\iow_char:N\\#2~instead.\\ \\ LaTeX~will~use~\iow_char:N\\#2. } % \end{macrocode} % % \begin{macrocode} %\EndIncludeInRelease %\IncludeInRelease{2020/10/01}{too-many-args} % {Hooks~with~args} %\EndIncludeInRelease % \end{macrocode} % % % \begin{macrocode} \msg_new:nnnn { hooks } { hook-disabled } { Cannot~add~code~to~disabled~hook~'#1'. } { The~hook~'#1'~you~tried~to~add~code~to~was~previously~disabled~ with~\iow_char:N\\hook_disable_generic:n~or~ \iow_char:N\\DisableGenericHook,~so~ it~cannot~have~code~added~to~it. } % \end{macrocode} % % \begin{macrocode} \msg_new:nnn { hooks } { empty-label } { Empty~code~label~\msg_line_context:.~ Using~'\@@_currname_or_default:'~instead. } % \end{macrocode} % % \begin{macrocode} \msg_new:nnn { hooks } { no-default-label } { Missing~(empty)~default~label~\msg_line_context:. \\ This~command~was~ignored. } % \end{macrocode} % % \begin{macrocode} \msg_new:nnnn { hooks } { unknown-rule } { Unknown~ relationship~ '#3'~ between~ labels~ '#2'~ and~ '#4'~ \str_if_eq:nnF {#1} {??} { ~in~hook~'#1' }. ~ Perhaps~ a~ misspelling? } { The~ relation~ used~ not~ known~ to~ the~ system.~ Allowed~ values~ are~ 'before'~ or~ '<',~ 'after'~ or~ '>',~ 'incompatible-warning',~ 'incompatible-error',~ 'voids'~ or~ 'unrelated'. } % \end{macrocode} % % \begin{macrocode} \msg_new:nnnn { hooks } { rule-too-late } { Sorting~rule~for~'#1'~hook~applied~too~late.\\ Try~setting~this~rule~earlier. } { You~tried~to~set~the~ordering~of~hook~'#1'~using\\ \ \ \iow_char:N\\DeclareHookRule{#1}{#2}{#3}{#4}\\ but~hook~'#1'~was~already~used~as~a~one-time~hook,~ thus~sorting~is\\ no~longer~possible.~Declare~the~rule~ before~the~hook~is~used. } % \end{macrocode} % % \begin{macrocode} \msg_new:nnnn { hooks } { misused-top-level } { Illegal~use~of~\iow_char:N \\AddToHook{#1}[top-level]{...}.\\ 'top-level'~is~reserved~for~the~user's~document. } { The~'top-level'~label~is~meant~for~user~code~only,~and~should~only~ be~used~(sparingly)~in~the~main~document.~Use~the~default~label~ '\@@_currname_or_default:'~for~this~\@cls@pkg,~or~another~ suitable~label. } % \end{macrocode} % % \begin{macrocode} \msg_new:nnn { hooks } { set-top-level } { You~cannot~change~the~default~label~#1~'top-level'.~Illegal \\ \use:nn { ~ } { ~ } \iow_char:N \\#2{#3} \\ \msg_line_context:. } % \end{macrocode} % % \begin{macrocode} \msg_new:nnn { hooks } { extra-pop-label } { Extra~\iow_char:N \\PopDefaultHookLabel. \\ This~command~will~be~ignored. } \msg_new:nnn { hooks } { missing-pop-label } { Missing~\iow_char:N \\PopDefaultHookLabel. \\ The~label~'#1'~was~pushed~but~never~popped.~Something~is~wrong. } % \end{macrocode} % % \begin{macrocode} \msg_new:nnn { latex2e } { should-not-happen } { This~should~not~happen.~#1 \\ Please~report~at~https://github.com/latex3/latex2e. } % \end{macrocode} % % % % \begin{macrocode} \msg_new:nnn { hooks } { activate-disabled } { Cannot~ activate~ hook~ '#1'~ because~ it~ is~ disabled! } % \end{macrocode} % % \begin{macrocode} \msg_new:nnn { hooks } { cannot-remove } { Cannot~remove~chunk~'#2'~from~hook~'#1'~because~ \@@_if_structure_exist:nTF {#1} { it~does~not~exist~in~that~hook. } { the~hook~does~not~exist. } } % \end{macrocode} % % \begin{macrocode} \msg_new:nnn { hooks } { generic-deprecated } { Generic~hook~'#1/#2/#3'~is~deprecated. \\ Use~hook~'#1/#3/#2'~instead. } % \end{macrocode} % % % \subsection{\LaTeXe{} package interface commands} % % % % \begin{macro}{\NewHook,\NewReversedHook,\NewMirroredHookPair} % Declaring new hooks \ldots % \begin{macrocode} \NewDocumentCommand \NewHook { m } { \hook_new:n {#1} } \NewDocumentCommand \NewReversedHook { m } { \hook_new_reversed:n {#1} } \NewDocumentCommand \NewMirroredHookPair { mm } { \hook_new_pair:nn {#1}{#2} } % \end{macrocode} % \end{macro} % % % % \begin{macro}{ % \NewHookWithArguments, % \NewReversedHookWithArguments, % \NewMirroredHookPairWithArguments % } % Declaring new hooks with arguments\ldots % \changes{v1.1a}{2023/04/06} % {Add \cs{NewHookWithArguments} (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\NewHookWithArguments} % {Hooks~with~args} \NewDocumentCommand \NewHookWithArguments { mm } { \hook_new_with_args:nn {#1} {#2} } \NewDocumentCommand \NewReversedHookWithArguments { mm } { \hook_new_reversed_with_args:nn {#1} {#2} } \NewDocumentCommand \NewMirroredHookPairWithArguments { mmm } { \hook_new_pair_with_args:nnn {#1} {#2} {#3} } %\EndIncludeInRelease %\IncludeInRelease{2020/10/01}{\NewHookWithArguments} % {Hooks~with~args} %\cs_new_protected:Npn \NewHookWithArguments #1 #2 { } %\cs_new_protected:Npn \NewReversedHookWithArguments #1 #2 { } %\cs_new_protected:Npn \NewMirroredHookPairWithArguments #1 #2 #3{} %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % % % \begin{macrocode} %\IncludeInRelease{2021/06/01}{\ActivateGenericHook} % {Providing~hooks} % \end{macrocode} % % \begin{macro}{\ActivateGenericHook} % Providing new hooks \ldots % \changes{v1.0m}{2021/04/29}{Add \cs{ProvideHook} etc.} % \changes{v1.0o}{2021/08/02}{Change name} % \begin{macrocode} \NewDocumentCommand \ActivateGenericHook { m } { \hook_activate_generic:n {#1} } % \end{macrocode} % \end{macro} % % \begin{macro}{\DisableGenericHook} % Disabling a generic hook. % \changes{v1.0o}{2021/08/02}{Change name} % \begin{macrocode} \NewDocumentCommand \DisableGenericHook { m } { \hook_disable_generic:n {#1} } % \end{macrocode} % \end{macro} % % \begin{macrocode} %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2020/10/01}{\ActivateGenericHook} % {Providing~hooks} %\def \ActivateGenericHook #1 { } %\def \DisableGenericHook #1 { } %\EndIncludeInRelease % \end{macrocode} % % % \begin{macro}{\AddToHook,\AddToHookWithArguments} % \changes{v1.1a}{2023/04/06} % {Add \cs{AddToHookWithArguments} (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\AddToHookWithArguments} % {Hooks~with~args} \NewDocumentCommand \AddToHook { m o +m } { \hook_gput_code:nnn {#1} {#2} {#3} } \NewDocumentCommand \AddToHookWithArguments { m o +m } { \hook_gput_code_with_args:nnn {#1} {#2} {#3} } %\EndIncludeInRelease %\IncludeInRelease{2020/10/01}{\AddToHookWithArguments} % {Hooks~with~args} %\cs_new_protected:Npn \AddToHookWithArguments #1 #2 #3 { } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % \begin{macro}{\AddToHookNext,\AddToHookNextWithArguments} % \changes{v1.1a}{2023/04/06} % {Add \cs{AddToHookNextWithArguments} (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\AddToHookNextWithArguments} % {Hooks~with~args} \NewDocumentCommand \AddToHookNext { m +m } { \hook_gput_next_code:nn {#1} {#2} } \NewDocumentCommand \AddToHookNextWithArguments { m +m } { \hook_gput_next_code_with_args:nn {#1} {#2} } %\EndIncludeInRelease %\IncludeInRelease{2020/10/01}{\AddToHookNextWithArguments} % {Hooks~with~args} %\cs_new_protected:Npn \AddToHookNextWithArguments #1 #2 { } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % \begin{macro}{\ClearHookNext} % \changes{v1.0o}{2021/07/27}{Macro added} % \begin{macrocode} \NewDocumentCommand \ClearHookNext { m } { \hook_gclear_next_code:n {#1} } % \end{macrocode} % \end{macro} % % % \begin{macro}{\RemoveFromHook} % \begin{macrocode} \NewDocumentCommand \RemoveFromHook { m o } { \hook_gremove_code:nn {#1} {#2} } % \end{macrocode} % \end{macro} % % \begin{macro}{\SetDefaultHookLabel} % \begin{macro}{\PushDefaultHookLabel} % \begin{macro}{\PopDefaultHookLabel} % Now define a wrapper that replaces the top of the stack with the % argument, and updates \cs{g_@@_hook_curr_name_tl} accordingly. % \begin{macrocode} \NewDocumentCommand \SetDefaultHookLabel { m } { \@@_set_default_hook_label:n {#1} } % % The label is only automatically updated with \cs{@onefilewithoptions} % (\cs{usepackage} and \cs{documentclass}), but some packages, like % Ti\emph{k}Z, define package-like interfaces, like % \cs{usetikzlibrary} that are wrappers around \cs{input}, so they % inherit the default label currently in force (usually |top-level|, % but it may change if loaded in another package). To provide a % package-like behavior also for hooks in these files, we provide % high-level access to the default label stack. % \begin{macrocode} \NewDocumentCommand \PushDefaultHookLabel { m } { \@@_curr_name_push:n {#1} } \NewDocumentCommand \PopDefaultHookLabel { } { \@@_curr_name_pop: } % \end{macrocode} % % The current label stack holds the labels for all files but the % current one (more or less like \cs{@currnamestack}), and the current % label token list, \cs{g_@@_hook_curr_name_tl}, holds the label for % the current file. However \cs{@pushfilename} happens before % \cs{@currname} is set, so we need to look ahead to get the % \cs{@currname} for the label. \pkg{expl3} also requires the current % file in \cs{@pushfilename}, so here we abuse % \cs{@expl@push@filename@aux@@@@} to do \cs{@@_curr_name_push:n}. % \begin{macrocode} \cs_gset_protected:Npn \@expl@push@filename@aux@@@@ #1#2#3 { \@@_curr_name_push:n {#3} \str_gset:Nx \g_file_curr_name_str {#3} #1 #2 {#3} } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % % % % \begin{macro}{ % \UseHook, % \UseOneTimeHook, % \UseHookWithArguments, % \UseOneTimeHookWithArguments, % } % Avoid the overhead of \pkg{xparse} and its protection that we % don't want here (since the hook should vanish without trace if empty)! % \changes{v1.1a}{2023/04/06} % {Add \cs{UseHookWithArguments} (hook-args).} % \begin{macrocode} %\IncludeInRelease{2023/06/01}{\UseHookWithArguments} % {Hooks~with~args} \cs_new:Npn \UseHook { \hook_use:n } \cs_new:Npn \UseOneTimeHook { \hook_use_once:n } \cs_new:Npn \UseHookWithArguments { \hook_use:nnw } \cs_new:Npn \UseOneTimeHookWithArguments { \hook_use_once:nnw } %\EndIncludeInRelease %\IncludeInRelease{2020/10/01}{\UseHookWithArguments} % {Hooks~with~args} %\cs_new:Npn \UseHookWithArguments #1 #2 { } %\cs_new:Npn \UseOneTimeHookWithArguments #1 #2 { } %\EndIncludeInRelease % \end{macrocode} % \end{macro} % % % % \begin{macro}{\ShowHook,\LogHook} % \begin{macrocode} \cs_new_protected:Npn \ShowHook { \hook_show:n } \cs_new_protected:Npn \LogHook { \hook_log:n } % \end{macrocode} % \end{macro} % % \begin{macro}{\DebugHooksOn,\DebugHooksOff} % % \begin{macrocode} \cs_new_protected:Npn \DebugHooksOn { \hook_debug_on: } \cs_new_protected:Npn \DebugHooksOff { \hook_debug_off: } % \end{macrocode} % \end{macro} % % % % \begin{macro}{\DeclareHookRule} % % \begin{macrocode} \NewDocumentCommand \DeclareHookRule { m m m m } { \hook_gset_rule:nnnn {#1}{#2}{#3}{#4} } % \end{macrocode} % \end{macro} % % \begin{macro}{\DeclareDefaultHookRule} % This declaration is only supported before \verb=\begin{document}=. % \begin{macrocode} \NewDocumentCommand \DeclareDefaultHookRule { m m m } { \hook_gset_rule:nnnn {??}{#1}{#2}{#3} } \@onlypreamble\DeclareDefaultHookRule % \end{macrocode} % \end{macro} % % \begin{macro}{\ClearHookRule} % A special setup rule that removes an existing relation. % Basically {@@_rule_gclear:nnn} plus fixing the property list for debugging. % \fmiinline{Needs perhaps an L3 interface, or maybe it should get dropped?} % \begin{macrocode} \NewDocumentCommand \ClearHookRule { m m m } { \hook_gset_rule:nnnn {#1}{#2}{unrelated}{#3} } % \end{macrocode} % \end{macro} % % % \begin{macro}[EXP]{\IfHookEmptyTF} % Here we avoid the overhead of \pkg{xparse}, since \cs{IfHookEmptyTF} % is used in \cs{end} (that is, every \LaTeX{} environment). As a % further optimization, use \cs{let} rather than \cs{def} to avoid one % expansion step. % \begin{macrocode} \cs_new_eq:NN \IfHookEmptyTF \hook_if_empty:nTF % \end{macrocode} % \end{macro} % % \begin{macro}[EXP,int]{\IfHookExistsTF} % Marked for removal and no longer documented in the doc section! % \phoinline{\cs{IfHookExistsTF} is used in \texttt{jlreq.cls}, % \texttt{pxatbegshi.sty}, \texttt{pxeverysel.sty}, % \texttt{pxeveryshi.sty}, so the public name may be an alias of the % internal conditional for a while. Regardless, those packages' use for % \cs{IfHookExistsTF} is not really correct and can be changed.} % \begin{macrocode} \cs_new_eq:NN \IfHookExistsTF \@@_if_usable:nTF % \end{macrocode} % \end{macro} % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % \subsection{Deprecated that needs cleanup at some point} % % \changes{v1.0p}{2021/08/20}{Added deprecation warnings for % old generic hook commands (gh/638)} % % \begin{macro}[int]{ % \hook_disable:n, % \hook_provide:n, % \hook_provide_reversed:n, % \hook_provide_pair:nn, % \@@_activate_generic_reversed:n, % \@@_activate_generic_pair:nn, % } % Deprecated. % \begin{macrocode} \cs_new_protected:Npn \hook_disable:n { \@@_deprecated_warn:nn { hook_disable:n } { hook_disable_generic:n } \hook_disable_generic:n } \cs_new_protected:Npn \hook_provide:n { \@@_deprecated_warn:nn { hook_provide:n } { hook_activate_generic:n } \hook_activate_generic:n } \cs_new_protected:Npn \hook_provide_reversed:n { \@@_deprecated_warn:nn { hook_provide_reversed:n } { hook_activate_generic:n } \@@_activate_generic_reversed:n } \cs_new_protected:Npn \hook_provide_pair:nn { \@@_deprecated_warn:nn { hook_provide_pair:nn } { hook_activate_generic:n } \@@_activate_generic_pair:nn } \cs_new_protected:Npn \@@_activate_generic_reversed:n #1 { \@@_normalize_hook_args:Nn \@@_activate_generic:nn {#1} { - } } \cs_new_protected:Npn \@@_activate_generic_pair:nn #1#2 { \hook_activate_generic:n {#1} \@@_activate_generic_reversed:n {#2} } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{ % \DisableHook, % \ProvideHook, % \ProvideReversedHook, % \ProvideMirroredHookPair, % } % Deprecated. % \begin{macrocode} \cs_new_protected:Npn \DisableHook { \@@_deprecated_warn:nn { DisableHook } { DisableGenericHook } \hook_disable_generic:n } \cs_new_protected:Npn \ProvideHook { \@@_deprecated_warn:nn { ProvideHook } { ActivateGenericHook } \hook_activate_generic:n } \cs_new_protected:Npn \ProvideReversedHook { \@@_deprecated_warn:nn { ProvideReversedHook } { ActivateGenericHook } \@@_activate_generic_reversed:n } \cs_new_protected:Npn \ProvideMirroredHookPair { \@@_deprecated_warn:nn { ProvideMirroredHookPair } { ActivateGenericHook } \@@_activate_generic_pair:nn } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_deprecated_warn:nn} % Warns about a deprecation, telling what should be used instead. % \begin{macrocode} \cs_new_protected:Npn \@@_deprecated_warn:nn #1 #2 { \msg_warning:nnnn { hooks } { deprecated } {#1} {#2} } \msg_new:nnn { hooks } { deprecated } { Command~\iow_char:N\\#1~is~deprecated~and~will~be~removed~in~a~ future~release. \\ \\ Use~\iow_char:N\\#2~instead. } % \end{macrocode} % \end{macro} % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % \subsection{Internal commands needed elsewhere} % % Here we set up a few horrible (but consistent) \LaTeXe{} names to % allow for internal commands to be used outside this module. We % have to unset the \texttt{@\/@} since we want double ``at'' sign % in place of double underscores. % % \begin{macrocode} %<@@=> % \end{macrocode} % % \begin{macro}[int]{ % \@expl@@@initialize@all@@, % \@expl@@@hook@curr@name@pop@@ % } % % \InternalDetectionOff % \begin{macrocode} \cs_new_eq:NN \@expl@@@initialize@all@@ \__hook_initialize_all: % \end{macrocode} % % \begin{macrocode} \cs_new_eq:NN \@expl@@@hook@curr@name@pop@@ \__hook_curr_name_pop: % \end{macrocode} % \InternalDetectionOn % \end{macro} % % Rolling back here doesn't undefine the interface commands as they % may be used in packages without rollback functionality. So we % just make them do nothing which may or may not work depending on % the code usage. % \changes{v1.0d}{2020/10/04}{Definition \cs{AddToHookNext} was supposed % to be for \cs{AddToHook} vice versa (gh/401)} % \begin{macrocode} % %\IncludeInRelease{0000/00/00}{lthooks} % {The~hook~management}% % %\def \NewHook#1{} %\def \NewReversedHook#1{} %\def \NewMirroredHookPair#1#2{} % %\def \DisableGenericHook #1{} % %\long\def\AddToHookNext#1#2{} % %\def\AddToHook#1{\@gobble@AddToHook@args} %\providecommand\@gobble@AddToHook@args[2][]{} % %\def\RemoveFromHook#1{\@gobble@RemoveFromHook@arg} %\providecommand\@gobble@RemoveFromHook@arg[1][]{} % %\def \UseHook #1{} %\def \UseOneTimeHook #1{} %\def \ShowHook #1{} %\let \DebugHooksOn \@empty %\let \DebugHooksOff\@empty % %\def \DeclareHookRule #1#2#3#4{} %\def \DeclareDefaultHookRule #1#2#3{} %\def \ClearHookRule #1#2#3{} % \end{macrocode} % If the hook management is not provided we make the test for existence % false and the test for empty true in the hope that this is most % of the time reasonable. If not a package would need to guard % against running in an old kernel. % \begin{macrocode} %\long\def \IfHookExistsTF #1#2#3{#3} %\long\def \IfHookEmptyTF #1#2#3{#2} % %\EndModuleRelease % \end{macrocode} % % \begin{macrocode} %<@@=hook> % \end{macrocode} % % \changes{v1.1a}{2023/04/06} % {Add dedicated rollback code to revert data structures (hook-args).} % \begin{macrocode} %\cs:w @@_rollback_tidying: \cs_end: %\bool_lazy_and:nnT % { \int_compare_p:nNn { \sourceLaTeXdate } > { 20230600 } } % { \int_compare_p:nNn { \requestedLaTeXdate } < { 20230601 } } % { % \cs_gset_protected:Npn \@@_rollback_tidying: % { % \@latex@error { Rollback~code~executed~twice } % { % Something~went~wrong~(unless~this~was~ % done~on~purpose~in~a~testing~environment). % } % \use_none:nnnn % } % \cs_set:Npn \@@_tmp:w #1 #2 % { % \@@_tl_gset:cx { @@#1~#2 } % { % \exp_args:No \exp_not:o % { % \cs:w @@#1~#2 \exp_last_unbraced:Ne \cs_end: % { \@@_braced_cs_parameter:n % { @@#1~#2 } } % } % } % } % \seq_map_inline:Nn \g_@@_all_seq % { % \exp_after:wN \cs_gset_nopar:Npn % \cs:w g_@@_#1_code_prop \exp_args:NNo \exp_args:No % \cs_end: { \cs:w g_@@_#1_code_prop \cs_end: } % \@@_tmp:w { _toplevel } {#1} % \@@_tmp:w { _next } {#1} % } % } \ExplSyntaxOff % % \end{macrocode} % % \begin{macrocode} %<@@=> % \end{macrocode} % % \Finale % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \endinput ^^A Needed for emacs ^^A ^^A Local Variables: ^^A mode: latex ^^A coding: utf-8-unix ^^A End: