% \iffalse meta-comment % % Copyright (C) 2020-2021 % Frank Mittelbach, Phelype Oleinik & LaTeX Team % % 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 % % \begin{macrocode} \def\lthooksversion{v1.0n} \def\lthooksdate{2021/05/26} % \end{macrocode} % %<*driver> \documentclass{l3doc} % bug fix fo l3doc.cls \ExplSyntaxOn \cs_set_protected:Npn \__codedoc_macro_typeset_one:nN #1#2 { \vbox_set:Nn \l__codedoc_macro_box { \vbox_unpack_drop:N \l__codedoc_macro_box \hbox { \llap { \__codedoc_print_macroname:nN {#1} #2 \MacroFont % <----- without it the \ is in lmr10 if a link is made \ } } } \int_incr:N \l__codedoc_macro_int } \ExplSyntaxOff \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{The \texttt{lthooks} package\thanks{This package has version % \lthooksversion\ dated \lthooksdate, \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, % environments (i.e., executed at \cs{begin} and \cs{end}) and % hooks run when loading files, e.g. before and after a package is % loaded, etc. Their hook names depend on the command, % environment or the % file name and so declaring them beforehand is not practical. % % % \begin{function}{\NewHook} % \begin{syntax} % \cs{NewHook} \Arg{hook} % \end{syntax} % Creates a new \meta{hook}. % If this is a hook provided as part of a package it is suggested % that the \meta{hook} 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{hooks} 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 hooks} % % The declarations here should normally not be used. They are available % to provide support for special use cases mainly involving % generic command hooks. % % \begin{function}{\DisableHook} % \begin{syntax} % \cs{DisableHook} \Arg{hook} % \end{syntax} % After this declaration the \meta{hook} is no longer usable: Any % attempt to add further 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} % % % \begin{function}{\ProvideHook} % \begin{syntax} % \cs{ProvideHook} \Arg{hook} % \end{syntax} % Like \cs{NewHook} but does nothing if the hook was previously % declared with \cs{NewHook}. This declaration should only be used % in special situations, e.g., when command of another package % need to be altered and it is is not clear if for that command a % generic hook was already explicitly declared before. % % Normally \cs{NewHook} should be used instead. % \end{function} % % \begin{function}{\ProvideReversedHook} % \begin{syntax} % \cs{ProvideReversedHook} \Arg{hook} % \end{syntax} % Like \cs{NewReversedHook} but does nothing if the hook was % previously declared as a reversed hook. % \end{function} % % \begin{function}{\ProvideMirroredHookPair} % \begin{syntax} % \cs{ProvideMirroredHookPair} \Arg{hook-1} \Arg{hook-2} % \end{syntax} % A shorthand for % \cs{ProvideHook}\Arg{hook-1}\cs{ProvideReversedHook}\Arg{hook-2}. % \end{function} % % % % \subsubsection{Using hooks in code} % % % \begin{function}{\UseHook} % \begin{syntax} % \cs{UseHook} \Arg{hook} % \end{syntax} % Execute the hook code inside a command or environment. % % 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}{\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}=. Once we have passed that point 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. % % \fmiinline{Maybe add an error version as well?} % % 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}{\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 the code for that \meta{label} wasn't yet added to the % \meta{hook}, an order is set so that when some code attempts to add % that label, the removal order takes action and the code is not % added. % % If the optional \meta{label} 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 and should therefore not by used % by 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 the 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 happens after the 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 % % It is possible to nest declarations 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).} % % \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/after/foo.tex}{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/after/foo.tex}{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, 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 % it in logical parts, but still use the main package name as % \meta{label}, then the \meta{default label} can be set using % \cs{SetDefaultHookLabel} or % \cs{PushDefaultHookLabel}\verb={..}=\,\ldots\cs{PopDefaultHookLabel}. % % \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 emulate \cs{usepackage}-like hook behaviour % 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} % % 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 either way 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} % \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 that 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} % \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}{\hook_disable:n} % \begin{syntax} % \cs{hook_disable: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_provide:n} % \begin{syntax} % \cs{hook_provide:n} \Arg{hook} % \end{syntax} % Like \cs{hook_new:n} but does nothing if the hook was previously % declared with \cs{hook_new:n}. This declaration should only be used % in special situations, e.g., when a command of another package % needs to be altered and it is is not clear if for that command a % generic \hook{cmd} hook was already explicitly declared before. % % Normally \cs{hook_new:n} should be used instead. % \end{function} % % \begin{function}{\hook_provide_reversed:n} % \begin{syntax} % \cs{hook_provide_reversed:n} \Arg{hook} % \end{syntax} % Like \cs{hook_new_reversed:n} but does nothing if the hook was % previously declared as a reversed hook. % \end{function} % % \begin{function}{\hook_provide_pair:nn} % \begin{syntax} % \cs{hook_provide_pair:nn} \Arg{hook-1} \Arg{hook-2} % \end{syntax} % A shorthand for % \cs{hook_provide:n}\Arg{hook-1}\cs{hook_provide_reversed:n}\Arg{hook-2}. % \end{function} % % % % \begin{function}{\hook_use:n} % \begin{syntax} % \cs{hook_use:n} \Arg{hook} % \end{syntax} % Executes the \Arg{hook} code followed (if set up) by the code for next % invocation only, then empties that next invocation code. % % 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} % 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. % % 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} % 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 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} % Adds a chunk of \meta{code} for use only in the next invocation of the % \meta{hook}. Once used it is gone. % % 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}. % \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 the code for that \meta{label} wasn't yet added to the % \meta{hook}, an order is set so that when some code attempts to add % that label, the removal order takes action and the code is not % added. % % 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} % \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, or 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 that \verb=\begin{color}= 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{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 through 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 further 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} % % There is also \cs{AtBeginDvi} which is discussed in conjunction % with the shipout hooks. % % \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. % % % \subsection{\LaTeXe{} commands and environments augmented by % hooks} % % \emph{intro to be written} % % \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 % 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 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. % % % \begin{function}{\BeforeBeginEnvironment} % \begin{syntax} % \cs{BeforeBeginEnvironment} \oarg{label} \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{code} % \end{syntax} % Like \cs{BeforeBeginEnvironment} but adds to the \hook{env/\meta{env}/begin} hook. % \end{function} % % \begin{function}{\AtEndEnvironment} % \begin{syntax} % \cs{AtEndEnvironment} \oarg{label} \Arg{code} % \end{syntax} % Like \cs{BeforeBeginEnvironment} but adds to the \hook{env/\meta{env}/end} hook. % \end{function} % % \begin{function}{\AfterEndEnvironment} % \begin{syntax} % \cs{AfterEndEnvironment} \oarg{label} \Arg{code} % \end{syntax} % Like \cs{BeforeBeginEnvironment} but 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}. % % % % \subsubsection{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 when using \cs{AtBeginDocument} and it is % executed after the \texttt{.aux} file as be read in 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. % % % % % \subsubsection{Hooks provided by \cs{end}\texttt{\{document\}}} % % \LaTeXe{} always provided \cs{AtEndDocument} to add code to the % execution of \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. % % % % % \subsubsection{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}. % % % % \subsubsection{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., 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 have hooks in which such support can % be added. % \begin{description} % % \item[\hook{rmfamily}] % % After \cs{rmfamily} has done its initial checks and prepared a % any font series update this hook is executed and only % afterwards \cs{selectfont}. % % \item[\hook{sffamily}] % % Like the \hook{rmfamily} hook but for the \cs{sffamily} command. % % \item[\hook{ttfamily}] % % Like the \hook{rmfamily} hook but for the \cs{ttfamily} command. % % \item[\hook{normalfont}] % % The \cs{normalfont} command resets 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. In the % \hook{bfseries/defaults} hook further adjustments can 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 used in % \cs{mdseries} command. % % \end{description} % % % \StopEventually{\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}{\g_@@_removal_list_prop} % A token list to hold delayed removals. % \begin{macrocode} \tl_new:N \g_@@_removal_list_tl % \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_@@_execute_immediately_prop} % List of hooks that from no on should not longer receive code. % \begin{macrocode} \prop_new:N \g_@@_execute_immediately_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}[int]{\tl_gremove_once:Nx,\tl_show:x,\tl_log:x} % 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 } % \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}{\@@_tl_set:Nn,\@@_tl_set:Nx, % \@@_tl_set:cn,\@@_tl_set:cx} % 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} % \begin{macrocode} \cs_new_protected:Npn \@@_tl_set:Nn #1#2 { \cs_set_nopar:Npx #1 { \__kernel_exp_not:w {#2} } } \cs_new_protected:Npn \@@_tl_set:Nx #1#2 { \cs_set_nopar:Npx #1 {#2} } \cs_generate_variant:Nn \@@_tl_set:Nn { c } \cs_generate_variant:Nn \@@_tl_set:Nx { c } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_tl_gset:Nn,\@@_tl_gset:No,\@@_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:No #1#2 { \cs_gset_nopar:Npx #1 { \__kernel_exp_not:w \exp_after:wN {#2} } } \cs_new_protected:Npn \@@_tl_gset:Nx #1#2 { \cs_gset_nopar:Npx #1 {#2} } \cs_generate_variant:Nn \@@_tl_gset:Nn { c } \cs_generate_variant:Nn \@@_tl_gset:No { c } \cs_generate_variant:Nn \@@_tl_gset:Nx { c } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_tl_gput_right:Nn,\@@_tl_gput_right:No,\@@_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 { No, cn } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_tl_gput_left:Nn,\@@_tl_gput_left:No} % 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} } } \cs_generate_variant:Nn \@@_tl_gput_left:Nn { No } % \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{\@@_next\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 |??|. % % \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 marker for the hook being officially declared. Its % existence is tested to raise an error in case another declaration % is attempted. % % \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 internal commands to set up hooks and explain how the % different states are distinguished. The actual implementation % then follows in the next 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 been declared yet. For example, one package needs to write % into a hook of another package, but that package ay not get % loaded or only loaded later. Another problem most hooks require % declaration but this is not the case for the generic hooks. % % We therefore distinguish the following states for a hook and they % are managed with 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 acctions are the same as in the \enquote{not % existing} state. % % \item[declared] % % A hook is in this state it is not disabled and was explicity 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 hook in any state is moved to this state when % \cs{DisableHook} 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,\@@_new:n} % The \cs{hook_new:n} declaration declares a new hook and expects % the hook \meta{name} as its argument, e.g., % \hook{begindocument}. % \begin{macrocode} \cs_new_protected:Npn \hook_new:n #1 { \@@_normalize_hook_args:Nn \@@_new:n {#1} } % \end{macrocode} % % \begin{macrocode} \cs_new_protected:Npn \@@_new:n #1 { % \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. % \begin{macrocode} \@@_if_declared:nTF {#1} { \msg_error:nnn { hooks } { exists } {#1} } { \tl_new:c { g_@@_#1_declared_tl } \@@_make_usable:n {#1} } } % \end{macrocode} % \end{macro} % % % % \begin{macro}{\@@_make_usable:n} % % 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:n} when adding code to a generic hook. % \begin{macrocode} \cs_new_protected:Npn \@@_make_usable:n #1 { % \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} \tl_if_exist:cF { @@~#1 } { \seq_gput_right:Nn \g_@@_all_seq {#1} % \end{macrocode} % This is only used by the actual code of the current hook, so % declare it normally: % \begin{macrocode} \tl_new:c { @@~#1 } % \end{macrocode} % Now ensure that the base data structure for the hook exists: % \begin{macrocode} \@@_init_structure: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. % \begin{macrocode} \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} } } % \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. % Inizializing a hook just with \cs{@@_init_structure:n} will not % make it usable with \cs{hook_use:n}. % \begin{macrocode} \cs_new_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 } } } % \end{macrocode} % \end{macro} % % % % \begin{macro}{\hook_new_reversed:n,\@@_new_reversed:n} % % Declare a new hook. The default ordering of code chunks is % reversed, signaled by setting the token list to a minus sign. % \begin{macrocode} \cs_new_protected:Npn \hook_new_reversed:n #1 { \@@_normalize_hook_args:Nn \@@_new_reversed:n {#1} } \cs_new_protected:Npn \@@_new_reversed:n #1 { \@@_new:n {#1} % \end{macrocode} % If the hook already exists the above will generate an error % message, so the next line should be executed (but it is --- too % bad). % \begin{macrocode} \tl_gset:cn { g_@@_#1_reversed_tl } { - } } % \end{macrocode} % \end{macro} % % % % % \begin{macro}{\hook_new_pair:nn} % A shorthand for declaring a normal and a (matching) reversed hook in one go. % \begin{macrocode} \cs_new_protected:Npn \hook_new_pair:nn #1#2 { \hook_new:n {#1} \hook_new_reversed:n {#2} } % \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} \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 } { \exp_args:Nnnv \@@_hook_gput_code_do:nnn {#1} { legacy } { @#1hook } % \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 } } } } % \end{macrocode} % \end{macro} % % % % \subsubsection{Disabling and providing hooks} % % \begin{macro}{\hook_disable: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:n}{Disable~hooks} % \end{macrocode} % % \begin{macrocode} \cs_new_protected:Npn \hook_disable: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 } } { ! \tl_if_exist_p:c { @@~#1 } } { \prg_return_true: } { \prg_return_false: } } %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2020/10/01} % {\hook_disable:n}{Disable~hooks} % %\cs_new_protected:Npn \hook_disable:n #1 {} % %\EndIncludeInRelease % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\hook_provide:n} % \begin{macro}{\hook_provide_reversed:n} % \begin{macro}{\@@_provide:n} % The \cs{hook_provide: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. % The \cs{hook_provide_reversed:n} does the same for reversed hooks. % \hook{begindocument}. % \begin{macrocode} %\IncludeInRelease{2021/06/01}% % {\hook_provide:n}{Providing~hooks} % \end{macrocode} % % \begin{macrocode} \cs_new_protected:Npn \hook_provide:n #1 { \@@_normalize_hook_args:Nn \@@_provide:nn {#1} { } } \cs_new_protected:Npn \hook_provide_reversed:n #1 { \@@_normalize_hook_args:Nn \@@_provide:nn {#1} { - } } % \end{macrocode} % % \begin{macrocode} \cs_new_protected:Npn \@@_provide:nn #1 #2 { % \end{macrocode} % If the hook to be provided was disabled we warn (for now --- this % may change). % \begin{macrocode} \@@_if_disabled:nTF {#1} { \msg_warning:nnn { hooks } { provide-disabled } {#1} } % \end{macrocode} % Otherwise we check if it was already declared. % \begin{macrocode} { \@@_if_declared:nTF {#1} { % \end{macrocode} % Issue an error if we try to provide a a hook that is reversed and % the already existing one is not (or vice versa). % \begin{macrocode} \str_if_eq:eeF { \tl_use:c { g_@@_#1_reversed_tl } } {#2} { \msg_error:nnn { hooks } { provide-error } {#1} } } % \end{macrocode} % If it wasn't declared, we declared as a normal or reversed hook % as appropriate. % \begin{macrocode} { \tl_new:c { g_@@_#1_declared_tl } \@@_make_usable:n {#1} \tl_gset:cn { g_@@_#1_reversed_tl } {#2} } } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\hook_provide_pair:nn} % A shorthand for providing a normal and a (matching) reversed hook in one go. % \begin{macrocode} \cs_new_protected:Npn \hook_provide_pair:nn #1#2 { \hook_provide:n {#1} \hook_provide_reversed:n {#2} } % \end{macrocode} % \end{macro} % % \begin{macrocode} %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2020/10/01} % {\hook_provide:n}{Providing~hooks} % %\cs_new_protected:Npn \hook_provide_reversed:n #1 {} %\cs_new_protected:Npn \hook_provide:n #1 {} %\cs_new_protected:Npn \hook_provide_pair:nn #1#2 {} % %\EndIncludeInRelease % \end{macrocode} % % % \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:} % Uses \cs{g_@@_hook_curr_name_tl} if it is set, otherwise tries % \cs{@currname}. If neither is set, 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 { kernel } { 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} % 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} % Standard route for normalising 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} % % % % \subsection{Adding or removing hook code} % % \begin{macro}{\hook_gput_code:nnn} % \begin{macro}{\@@_gput_code:nnn,\@@_gput_code:nxv,\@@_hook_gput_code_do:nnn} % % 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}. % \begin{macrocode} \cs_new_protected:Npn \hook_gput_code:nnn #1 #2 { \@@_normalize_hook_args:Nnn \@@_gput_code:nnn {#1} {#2} } % \end{macrocode} % % \begin{macrocode} \cs_new_protected:Npn \@@_gput_code:nnn #1 #2 #3 { % \end{macrocode} % First check if the hook was used as a one-time hook: % \begin{macrocode} \prop_if_in:NnTF \g_@@_execute_immediately_prop {#1} {#3} { % \end{macrocode} % Then check if the current \meta{hook}/\meta{label} pair was marked % for removal, in which case \cs{@@_unmark_removal:nn} is used to % remove that mark (once). This may happen when a package removes % code from another package which was not yet loaded: the removal % order is stored, and at this stage it is executed by not adding to % the hook. % \begin{macrocode} \@@_if_marked_removal:nnTF {#1} {#2} { \@@_unmark_removal:nn {#1} {#2} } { % \end{macrocode} % If no removal is queued, we are free to add. Start by checking if % the hook exists. % \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 does not exist, however, before giving up 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} % % \begin{macrocode} \cs_generate_variant:Nn \@@_gput_code:nnn { nxv } % \end{macrocode} % % This macro will unconditionally add a chunk of code to the given hook. % \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} \@@_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} } } } % \end{macrocode} % \end{macro} % \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. % \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. % \begin{macrocode} \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. % \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} } % \end{macrocode} % \end{macro} % % % \begin{macro}[TF]{\@@_try_declaring_generic_hook:wn} % % \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} { % \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. % \begin{macrocode} \str_if_eq:nnT {#1} { cmd } { \@@_try_put_cmd_hook:n {#5} } % \end{macrocode} % % Declare the hook always even if it can't really be used (error % message generated elsewhere). % % Here we use \cs{@@_make_usable:n}, so that a \cs{hook_new:n} is still % possible later. % \begin{macrocode} \@@_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/before/foo.tex|). If it is a file-specific hook, then it % executes the \meta{true} branch, otherwise \meta{false}. % % A file-specific hook is \hook{file/\meta{position}/\meta{name}}. % If any of these parts don't exist, it is a general file hook or not % a file hook at all, so the conditional evaluates to \meta{false}. % Otherwise, it checks that the first part is |file| and that the % \meta{position} is in the \cs{c_@@_generics_file_prop}. % % A property list is used here to avoid having to worry with catcodes, % because \pkg{expl3}'s file name parsing turns all characters into % catcode-12 tokens, which might differ from hand-input letters. % \begin{macrocode} \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: } } % \end{macrocode} % \end{macro} % % \begin{macro}[EXP]{\@@_file_hook_normalize:n} % \begin{macro}[EXP]{\@@_strip_double_slash:n,\@@_strip_double_slash:w} % 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/after/./mypath//file.tex}|. % \begin{macrocode} \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 } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\c_@@_generics_prop} % Property list holding the generic names. 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} \prop_const_from_keyval:Nn \c_@@_generics_prop {cmd=,env=,file=,package=,class=,include=} % \end{macrocode} % \end{macro} % % \begin{macro}{\c_@@_generics_reversed_ii_prop, % \c_@@_generics_reversed_iii_prop, % \c_@@_generics_file_prop} % Some of the generic hooks are supposed to use reverse ordering, these are % the following (only the second or third sub-component is checked): % \begin{macrocode} \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=} % \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. % \begin{macrocode} \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 } \@@_tl_gclear:c { @@_toplevel~#1 } \@@_tl_gclear:c { @@_next~#1 } } { % \end{macrocode} % If the label is |top-level| then clear the token list, as all code % there is under the same label. Marked removal is not implemented % for |top-level| because it is hard to reliably know that no code % was added to \cs{@@_toplevel\textvisiblespace\meta{hook}} (granted that % an empty code could be interpreted as that, but then it differs in % behaviour from other labels, in which an empty chunk is still valid % for removal). Besides, it doesn't make much (if any) sense for % packages to remove |top-level| code. So here the chunk is just % cleared unconditionally. % \begin{macrocode} \str_if_eq:nnTF {#2} { top-level } { \@@_tl_gclear:c { @@_toplevel~#1 } } { % \end{macrocode} % Otherwise check if the label being removed exists in the code pool. % If it does, just call \cs{@@_gremove_code_do:nn} to do the removal, % otherwise mark it to be removed. % \begin{macrocode} \prop_get:cnNTF { g_@@_#1_code_prop } {#2} \l_@@_return_tl { \@@_gremove_code_do:nn } { \@@_mark_removal:nn } {#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 it means that nothing % tried to add to it before, so we just queue this removal order for % later. % \begin{macrocode} { \@@_mark_removal:nn {#1} {#2} } } % \end{macrocode} % % \begin{macro}{\@@_gremove_code_do:nn} % Remove code for a given label. % \begin{macrocode} \cs_new_protected:Npn \@@_gremove_code_do:nn #1 #2 { \prop_gremove:cn { g_@@_#1_code_prop } {#2} } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\@@_mark_removal:nn} % Marks \meta{label} (\verb=#2=) to be removed from \meta{hook} % (\verb=#1=). The number of removals should be fairly small, and % \cs{tl_gremove_once:Nx} is fairly efficient even for longer token % lists, so we use a single global token list, rather than one for % each hook. % % A hand-crafted token list is used here because property lists don't % hold repeated items, so multiple usages of \cs{@@_mark_removal:nn} % would be cancelled by a single \cs{@@_unmark_removal:nn}. % \begin{macrocode} \cs_new_protected:Npn \@@_mark_removal:nn #1 #2 { \tl_gput_right:Nx \g_@@_removal_list_tl { \@@_removal_tl:nn {#1} {#2} } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_unmark_removal:nn} % Unmarks \meta{label} (\verb=#2=) to be removed from \meta{hook} % (\verb=#1=). \cs{tl_gremove_once:Nx} is used rather than % \cs{tl_gremove_all:Nx} so that two additions are needed to cancel % two marked removals, rather than only one. % \begin{macrocode} \cs_new_protected:Npn \@@_unmark_removal:nn #1 #2 { \tl_gremove_once:Nx \g_@@_removal_list_tl { \@@_removal_tl:nn {#1} {#2} } } % \end{macrocode} % \end{macro} % % \begin{macro}[TF]{\@@_if_marked_removal:nn} % Checks if the \cs{g_@@_removal_list_tl} contains the current % \meta{label} (\verb=#2=) and \meta{hook} (\verb=#1=). % \begin{macrocode} \prg_new_protected_conditional:Npnn \@@_if_marked_removal:nn #1 #2 { TF } { \exp_args:NNx \tl_if_in:NnTF \g_@@_removal_list_tl { \@@_removal_tl:nn {#1} {#2} } { \prg_return_true: } { \prg_return_false: } } % \end{macrocode} % \end{macro} % % \begin{macro}[rEXP]{\@@_removal_tl:nn} % Builds a token list with \verb=#1= and \verb=#2= which can only be % matched by \verb=#1= and \verb=#2=. The |&|$_4$ anchors a removal, % so that \verb=#1= can't be mistaken by \verb=#2= and vice versa, and % the two |$|$_3$ delimit the two arguments % \begin{macrocode} \cs_new:Npn \@@_removal_tl:nn #1 #2 { & \tl_to_str:n {#2} $ \tl_to_str:n {#1} $ } % \end{macrocode} % \end{macro} % % % \begin{macro}{ % \g_@@_??_code_prop, % \@@~??, % \g_@@_??_reversed_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 \verb=??= 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} % \end{macro} % % \subsection{Setting rules for hooks code} % % \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} \cs_new_protected:Npn \@@_gset_rule:nnnn #1#2#3#4 { % \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} } } % \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. % \begin{macrocode} \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. % \begin{macrocode} \@@_debug:n { \prop_gclear:N \g_@@_used_prop } \seq_map_inline:Nn \g_@@_all_seq { \@@_update_hook_code:n {##1} } % \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{^^JAll~ initialized~ (non-empty)~ hooks:} \prop_map_inline:Nn \g_@@_used_prop { \iow_term:x{^^J~ ##1~ ->~ \exp_not:v {@@~##1}~ } } } % \end{macrocode} % After all hooks are initialized we change the ``use'' to just % call the hook code and not initialize it (as it was done in the % preamble. % \begin{macrocode} \cs_gset_eq:NN \hook_use:n \@@_use_initialized:n \cs_gset_eq:NN \@@_preamble_hook:n \use_none:n } % \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. % \begin{macrocode} \cs_new_protected:Npn \@@_initialize_hook_code:n #1 { \@@_debug:n{ \iow_term:x{^^JUpdate~ 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. % \begin{macrocode} \@@_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: } } { % \end{macrocode} % By default the algorithm sorts the code chunks and then saves the % result in a token list for fast execution by adding the code 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 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 temporarily % save the old code property list so that it can be restored later. % \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}{} } } } } % \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! \fmi{Needs % checking for, just in case ... maybe} % % ^^A #1 <- \@@~#1 % ^^A #2 <- \g_@@_#1_labels_clist % ^^A #3 <- #1 % \begin{macrocode} \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 % labels listed there. Only 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: 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} % Take a breath and take a 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 combines 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 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 will do for now \ldots \fmi{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 % with 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. % \begin{macrocode} \exp_args:NNo \@@_tl_gput:Nn #1 { \cs:w @@_toplevel~#3 \cs_end: } \@@_tl_gput_right:No #1 { \cs:w @@_next~#3 \cs_end: } } % \end{macrocode} % % \begin{macrocode} \cs_generate_variant:Nn \@@_initialize_single:NNn { cc } % \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 \verb=??= % 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 sort % in the empty code. However that would complicate the algorithm a % lot with little gain.\footnote{This also hase the advantage that % the result of the sorting doesn't change which might otherwise % (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 \verb=??=, and the default % hook doesn't store any code. Removing from \cs{l_@@_cur_hook_tl} % makes 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_x: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} % % \begin{macrocode} \cs_new_protected:Npn \@@_log:nN #1 #2 { \@@_preamble_hook:n {#1} \@@_log_cmd:x { ^^J ->~The~hook~'#1': } % \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 } { \@@_log_line_indent:x { ##1~->~\tl_to_str:n {##2} } } } % \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 { \tl_if_empty:cTF { @@_toplevel~#1 } { --- } { -> ~ \exp_args:Nv \tl_to_str:n { @@_toplevel~#1 } } } % \end{macrocode} % % \begin{macrocode} \@@_log_line:x { Extra~code~for~next~invocation: } \@@_log_line_indent:x { \tl_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_args:Nv \@@_log_next_code:n { @@_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} } } } } } % \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 first two tokens at the % front which are \cs{tl_gclear:N} and the token list to clear. % \begin{macrocode} \cs_new:Npn \@@_log_next_code:n #1 { \exp_args:No \tl_to_str:n { \use_none:nn #1 } } % \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 |??| 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} % \begin{macrocode} \cs_new_protected:Npn \hook_gput_next_code:nn #1 { \@@_normalize_hook_args:Nn \@@_gput_next_code:nn {#1} } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_gput_next_code:nn, % \@@_gput_next_do:nn, % \@@_gput_next_do:Nnn, % \@@_clear_next:n} % \begin{macrocode} \cs_new_protected:Npn \@@_gput_next_code:nn #1 #2 { \@@_if_disabled:nTF {#1} { \msg_error:nnn { hooks } { hook-disabled } {#1} } { \@@_init_structure:n {#1} \@@_if_usable:nTF {#1} { \@@_gput_next_do:nn {#1} {#2} } { \@@_try_declaring_generic_next_hook:nn {#1} {#2} } } } % \end{macrocode} % % \begin{macrocode} \cs_new_protected:Npn \@@_gput_next_do:nn #1 { \exp_args:Nc \@@_gput_next_do:Nnn { @@_next~#1 } {#1} } % \end{macrocode} % First 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. % \begin{macrocode} \cs_new_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 } \cs_new_protected:Npn \@@_clear_next:n #1 { \cs_gset_eq:cN { @@_next~#1 } \c_empty_tl } % \end{macrocode} % \end{macro} % % % % % % % \subsection{Using the hook} % % \begin{macro}{\hook_use:n} % \begin{macro}[EXP]{\@@_use_initialized:n} % \begin{macro}{\@@_use_undefined:w,\@@_use_end:} % \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 internally: check if the hook exist as % given, and if so use it as quickly as possible. If it doesn't % exist, the a call to \cs{@@_use:wn} checks for file hooks. % % 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} \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: % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \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} \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} % % \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: } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\hook_use_once:n} % For hooks that can and should be used only once we have a special % use command that remembers the hook name in % \cs{g_@@_execute_immediately_prop}. 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. % % \begin{macrocode} \cs_new_protected:Npn \hook_use_once:n #1 { \tl_if_exist:cT { @@~#1 } { \tl_set:Nn \l_@@_return_tl {#1} \@@_normalize_hook_args:Nn \@@_use_once_store:n { \l_@@_return_tl } \hook_use:n {#1} } } \cs_new_protected:Npn \@@_use_once_store:n #1 { \prop_gput:Nnn \g_@@_execute_immediately_prop {#1} { } } % \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. % \begin{macrocode} \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: } } % \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]{@@~\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 } { \tl_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 } { \if_int_compare:w \cs:w g_@@_#1_reversed_tl \cs_end: 1 < 0 \exp_stop_f: \prg_return_true: \else: \prg_return_false: \fi: } % \end{macrocode} % \end{macro} % % % \subsection{Messages} % % Hook errors are LaTeX kernel errors: % \begin{macrocode} \prop_gput:Nnn \g_msg_module_type_prop { hooks } { LaTeX } % \end{macrocode} % And so are kernel errors (this should move elsewhere eventually). % \begin{macrocode} \prop_gput:Nnn \g_msg_module_type_prop { kernel } { LaTeX } %\prop_gput:Nnn \g_msg_module_name_prop { kernel } { } % <-- currently not working % \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} \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:n~or~\iow_char:N\\DisableHook,~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~ missspelling? } { 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 } { 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 { kernel } { should-not-happen } { This~should~not~happen.~#1 \\ Please~report~at~https://github.com/latex3/latex2e. } % \end{macrocode} % % % % \begin{macrocode} \msg_new:nnn { hooks } { provide-disabled } { Cannot~ provide~ hook~ '#1'~ because~ it~ is~ disabled! } % \end{macrocode} % % \begin{macrocode} \msg_new:nnnn { hooks } { provide-error } { Hook~'#1'~ already~ declared~ as~ a~ \@@_if_reversed:nTF {#1} { reversed } { normal }~ hook! } { You~ attempted~ to~ provide~ the~ hook~'#1'~ as~ a~ \@@_if_reversed:nTF {#1} { normal } { reversed }~ hook,~ but~ it~ was~ already~ previously~ declared~ as~ a~ \@@_if_reversed:nTF {#1} { reversed } { normal }~ hook.~ A~ redeclaration~ is~ not~ possible. } % \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{macrocode} %\IncludeInRelease{2021/06/01}% % {\hook_provide:n}{Providing~hooks} % \end{macrocode} % % \begin{macro}{\ProvideHook,\ProvideReversedHook,\ProvideMirroredHookPair} % Providing new hooks \ldots % \changes{v1.0m}{2021/04/29}{Add \cs{ProvideHook} etc.} % \begin{macrocode} \NewDocumentCommand \ProvideHook { m }{ \hook_provide:n {#1} } \NewDocumentCommand \ProvideReversedHook { m }{ \hook_provide_reversed:n {#1} } \NewDocumentCommand \ProvideMirroredHookPair { mm }{ \hook_provide_pair:nn {#1}{#2} } % \end{macrocode} % \end{macro} % % % \begin{macro}{\DisableHook} % Disabling a (generic) hook. % \begin{macrocode} \NewDocumentCommand \DisableHook { m }{ \hook_disable:n {#1} } % \end{macrocode} % \end{macro} % % \begin{macrocode} %\EndIncludeInRelease %\IncludeInRelease{2020/10/01} % {\hook_provide:n}{Providing~hooks} % %\def \ProvideHook#1{} %\def \ProvideReversedHook#1{} %\def \ProvideMirroredHookPair#1#2{} % %\EndIncludeInRelease % \end{macrocode} % % % \begin{macro}{\AddToHook} % % \begin{macrocode} \NewDocumentCommand \AddToHook { m o +m } { \hook_gput_code:nnn {#1} {#2} {#3} } % \end{macrocode} % \end{macro} % % \begin{macro}{\AddToHookNext} % % \begin{macrocode} \NewDocumentCommand \AddToHookNext { m +m } { \hook_gput_next_code:nn {#1} {#2} } % \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} % % \fmiinline{Docu task: At some point this code for this should be moved to the % label section earlier and here we should keep only the interface commands.} % % \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 label for 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 to the % stack, and sets the new default label 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 the label will not be used again, and \cs{g_@@_hook_curr_name_tl} % is updated to 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. % % 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 } { \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} % % 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 behaviour 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} % \end{macro} % \end{macro} % \end{macro} % % % % % \begin{macro}{\UseHook,\UseOneTimeHook} % Avoid the overhead of \pkg{xparse} and its protection that we % don't want here (since the hook should vanish without trace if empty)! % \begin{macrocode} \cs_new:Npn \UseHook { \hook_use:n } \cs_new:Npn \UseOneTimeHook { \hook_use_once:n } % \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 optimisation, 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]{\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{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}{\@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 \DisableHook #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 \ExplSyntaxOff % % \end{macrocode} % % % \Finale % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \endinput %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%