% \iffalse meta-comment % % Copyright (C) 2020-2024 % Frank Mittelbach, Phelype Oleinik and The LaTeX Project % % This file is part of the LaTeX base system. % ------------------------------------------- % % It may be distributed and/or modified under the % conditions of the LaTeX Project Public License, either version 1.3c % of this license or (at your option) any later version. % The latest version of this license is in % https://www.latex-project.org/lppl.txt % and version 1.3c or later is part of all distributions of LaTeX % version 2008 or later. % % This file has the LPPL maintenance status "maintained". % % The list of all files belonging to the LaTeX base distribution is % given in the file `manifest.txt'. See also `legal.txt' for additional % information. % % The list of derived (unpacked) files belonging to the distribution % and covered by LPPL is defined by the unpacking scripts (with % extension .ins) which are part of the distribution. % % \fi % % \iffalse % %%% From File: ltfilehook.dtx % % \begin{macrocode} \providecommand\ltfilehookversion{v1.0o} \providecommand\ltfilehookdate{2023/07/10} % \end{macrocode} % %<*driver> \documentclass{l3doc} \providecommand\InternalDetectionOff{} \providecommand\InternalDetectionOn{} \usepackage{structuredlog} % for demonstration \EnableCrossrefs \CodelineIndex \begin{document} \DocInput{ltfilehook.dtx} \end{document} % % % \fi % % % \long\def\fmi#1{\begin{quote}\itshape Todo: #1\end{quote}} % % \providecommand\hook[1]{\texttt{#1}} % % % \title{The \texttt{ltfilehook} documentation\thanks{This code has version % \ltfilehookversion\ dated \ltfilehookdate, \copyright\ \LaTeX\ % Project.}} % % \author{Frank Mittelbach, Phelype Oleinik, \LaTeX{} Project Team} % % \maketitle % % % % \tableofcontents % % \section{Introduction} % % % % \subsection{Provided hooks} % % The code offers a number of hooks into which packages (or the % user) can add code to support different use cases. % Many hooks are offered as pairs (i.e., the second hook is % reversed. Also important to know is that these pairs are % properly nested with respect to other pairs of hooks. % % There are hooks that are executed for all files of a certain type % (if they contain code), e.g., for all \enquote{include files} or % all \enquote{packages}, % and there are also hooks that are specific to a single file, % e.g., do something after the package \texttt{foo.sty} has been % loaded. % % % \subsection{General hooks for file reading} % \label{sec:general-file-hooks} % % There are four hooks that are called for each file that is read % using document-level commands such as \cs{input}, \cs{include}, % \cs{usepackage}, etc. They are not called for files read using % internal low-level methods, such as \cs{@input} or \cs{openin}. % % \begin{variable}{file/before,file/.../before, % file/.../after,file/after, % } % These are: % \begin{description} % \item[\hook{file/before}, \hook{file/\meta{file-name}/before}] % % These hooks are executed in that order just before the file is % loaded for reading. The code of the first hook is used % with every file, while the second is executed only for the % file with matching \meta{file-name} allowing you to specify % code that only applies to one file. % % \item[\hook{file/\meta{file-name}/after}, \hook{file/after}] % % These hooks are after the file with name \meta{file-name} has % been fully consumed. The order is swapped (the specific one % comes first) so that the \hook{/before} and \hook{/after} % hooks nest properly, which is important if any of them involve % grouping (e.g., contain environments, for example). % Furthermore both hooks are reversed hooks to support correct % nesting of different packages adding code to both % \hook{/before} and \hook{/after} hooks. % % \end{description} % \end{variable} % % % So the overall sequence of hook processing for any file read % through the user interface commands of \LaTeX{} is: % % \begin{tabbing} % mm\=mm\=mm\=mm\=\kill % \>\cs{UseHook}\marg{\hook{file/before}} \\ % \>\cs{UseHook}\marg{\hook{file/\meta{file name}/before}} \\ % \>\> \meta{file contents} \\ % \>\cs{UseHook}\marg{\hook{file/\meta{file name}/after}} \\ % \>\cs{UseHook}\marg{\hook{file/after}} % \end{tabbing} % % The file hooks only refer to the file by its name and extension, % so the \meta{file name} should be the file name as it is on the % filesystem with extension (if any) and without paths. Different % from \cs{input} and similar commands, the \texttt{.tex} % extension is not assumed in hook \meta{file name}, so \texttt{.tex} % files must be specified % with their extension to be recognized. % Files within subfolders should also be addressed by their name and % extension only. % % Extensionless files also work, and should then be given without % extension. Note however that \TeX{} prioritizes \texttt{.tex} % files, so if two files \texttt{foo} and \texttt{foo.tex} exist in % the search path, only the latter will be seen. % % When a file is input, the \meta{file name} is available in % \cs{CurrentFile}, which is then used when accessing the % \hook{file/\meta{file name}/before} and % \hook{file/\meta{file name}/after}. % % \begin{variable}{\CurrentFile} % The name of the file about to be read (or just finished) is % available to the hooks through \cs{CurrentFile} (there is no % \texttt{expl3} name for it for now). The file is always provided % with its extension, i.e., how it appears on your hard drive, but % without any specified path to it. For example, % \verb=\input{sample}= and \verb=\input{app/sample.tex}= would % both have \cs{CurrentFile} being \texttt{sample.tex}. % \end{variable} % % \begin{variable}{\CurrentFilePath} % The path to the current file (complement to \cs{CurrentFile}) is % available in \cs{CurrentFilePath} if needed. % The paths returned in \cs{CurrentFilePath} are only user paths, % given through \cs{input@path} (or \pkg{expl3}'s equivalent % \cs{l_file_search_path_seq}) or by directly typing in the path % in the \cs{input} command or equivalent. Files located by % \texttt{kpsewhich} get the path added internally by the \TeX{} % implementation, so at the macro level it looks as if the file were % in the current folder, so the path in \cs{CurrentFilePath} is empty % in these cases (package and class files, mostly). % \end{variable} % % \begin{variable}{\CurrentFileUsed,\CurrentFilePathUsed} % In normal circumstances these are identical to \cs{CurrentFile} and % \cs{CurrentFilePath}. They will differ when a file substitution % has occurred for \cs{CurrentFile}. In that case, % \cs{CurrentFileUsed} and \cs{CurrentFilePathUsed} will hold the % actual file name and path loaded by \LaTeX, while \cs{CurrentFile} % and \cs{CurrentFilePath} will hold the names that were % \emph{asked for}. Unless doing very specific work on the file % being read, \cs{CurrentFile} and \cs{CurrentFilePath} should be % enough. % \end{variable} % % \subsection{Hooks for package and class files} % % Commands to load package and class files (e.g., \cs{usepackage}, % \cs{RequirePackage}, \cs{LoadPackageWithOptions}, etc.) offer the % hooks from section~\ref{sec:general-file-hooks} when they are % used to load a package or class file, e.g., % \hook{file/array.sty/after} would be called after the % \pkg{array} package got loaded. But as packages and classes form as special group % of files, there are some additional hooks available that only % apply when a package or class is loaded. % % % \begin{variable}{ % package/before,package/after, % package/.../before,package/.../after, % class/before,class/after, % class/.../before,class/.../after, % } % These are: % \begin{description} % \item[\hook{package/before}, \hook{package/after}] % % These hooks are called for each package being loaded. % % \item[\hook{package/\meta{name}/before}, % \hook{package/\meta{name}/after}] % % These hooks are additionally called if the package name is % \meta{name} (without extension). % % \item[\hook{class/before}, \hook{class/after}] % % These hooks are called for each class being loaded. % % \item[\hook{class/\meta{name}/before}, \hook{class/\meta{name}/after}] % % These hooks are additionally called if the class name is % \meta{name} (without extension). % % \end{description} % \end{variable} % All \hook{/after} hooks are implemented as reversed hooks. % % \noindent The overall sequence of execution for \cs{usepackage} % and friends is therefore: % \begin{tabbing} % mm\=mm\=mm\=mm\=\kill % \>\cs{UseHook}\marg{\hook{package/before}} \\ % \>\cs{UseHook}\marg{\hook{package/\meta{package name}/before}} \\[5pt] % \>\>\cs{UseHook}\marg{\hook{file/before}} \\ % \>\>\cs{UseHook}\marg{\hook{file/\meta{package name}.sty/before}} \\ % \>\>\> \meta{package contents} \\ % \>\>\cs{UseHook}\marg{\hook{file/\meta{package name}.sty/after}} \\ % \>\>\cs{UseHook}\marg{\hook{file/after}} \\[5pt] % \>\>\emph{code from \cs{AtEndOfPackage} if % used inside the package} \\[5pt] % \>\cs{UseHook}\marg{\hook{package/\meta{package name}/after}} \\ % \>\cs{UseHook}\marg{\hook{package/after}} % \end{tabbing} % and similar for class file loading, except that \hook{package/} % is replaced by \hook{class/} and \cs{AtEndOfPackage} by % \cs{AtEndOfClass}. % % If a package or class is not loaded (or it was loaded before the % hooks were set) none of the hooks are executed! % % All class or package hooks involving the name of the class or % package are implemented as % one-time hooks, whereas all other such hooks are normal hooks. % This allows for the following use case %\begin{verbatim} % \AddToHook{package/varioref/after} % { ... apply my customizations if the package gets % loaded (or was loaded already) ... } %\end{verbatim} % without the need to first test if the package is already loaded. % % % % % \subsection{Hooks for \cs{include} files} % % To manage \cs{include} files, \LaTeX{} issues a \cs{clearpage} % before and after loading such a file. Depending on the use case % one may want to execute code before or after these % \cs{clearpage}s especially for the one that is issued at the end. % % Executing code before the final \cs{clearpage}, means that the % code is processed while the last page of the included material is % still under construction. Executing code after it means that all % floats from inside the include file are placed (which % might have added further pages) and the final page has finished. % % Because of these different scenarios we offer hooks in three % places.\footnote{If you want to execute code before the first % \cs{clearpage} there is no need to use a hook---you can write it % directly in front of the \cs{include}.} % None of the hooks are executed when an \cs{include} file is % bypassed because of an \cs{includeonly} declaration. They are, % however, all executed if \LaTeX{} makes an attempt to load the % \cs{include} file (even if it doesn't exist and all that happens % is \enquote{\texttt{No file \meta{filename}.tex}}). % % % \begin{variable}{include/before,include/.../before, % include/end,include/.../end, % include/after,include/.../after, % } % These are: % \begin{description} % % \item[\hook{include/before}, \hook{include/\meta{name}/before}] % % These hooks are executed (in that order) after the initial % \cs{clearpage} and after \texttt{.aux} file is changed to use % \texttt{\meta{name}.aux}, but before the % \texttt{\meta{name}.tex} file is loaded. In other words they are executed % at the very beginning of the first page of the \cs{include} % file. % % % \item[\hook{include/\meta{name}/end}, \hook{include/end}] % % These hooks are executed (in that order) after \LaTeX{} has % stopped reading from the \cs{include} file, but before it has % issued a \cs{clearpage} to output any deferred floats. % % % \item[\hook{include/\meta{name}/after}, \hook{include/after}] % % These hooks are executed (in that order) after \LaTeX{} has % issued the \cs{clearpage} but before is has switched back % writing to the main \texttt{.aux} file. Thus technically we are % still inside the \cs{include} and if the hooks generate any % further typeset material including anything that writes to the % \texttt{.aux} file, then it would be considered part of the % included material and bypassed if it is not loaded because of % some \cs{includeonly} statement.\footnotemark % % \item[\hook{include/excluded}, \hook{include/\meta{name}/excluded}] % % The above hooks for \cs{include} files are only executed when % the file is loaded (or more exactly the load is attempted). If, % however, the \cs{include} file is explicitly excluded (through % an \cs{includeonly} statement) the above % hooks are bypassed and instead the \hook{include/excluded} % hook followed by the \hook{include/\meta{name}/excluded} hook % are executed. This happens after % \LaTeX{} has loaded the \texttt{.aux} file for this include file, % i.e., after \LaTeX{} has updated its counters to pretend that the file % was seen. % % \end{description} % \end{variable}\footnotetext{For that reason % another \cs{clearpage} is executed after these hooks which % normally does nothing, but starts a new page if further material % got added this way.} % % % All \hook{include} hooks involving the name of the included file are implemented as % one-time hooks (whereas all other such hooks are normal hooks). % % If you want to execute code that is run for every \cs{include} % regardless of whether or not it is excluded, use the % \hook{cmd/include/before} or \hook{cmd/include/after} hooks. % % % % \subsection{High-level interfaces for \LaTeX{}} % % We do not provide any additional wrappers around the hooks (like % \pkg{filehook} or \pkg{scrlfile} do) because we believe that for % package writers the high-level commands from the hook management, % e.g., \cs{AddToHook}, etc.\ % are sufficient and in fact easier to work with, given that the hooks % have consistent naming conventions. % % % % \subsection{Internal interfaces for \LaTeX{}} % % \begin{function}{\declare@file@substitution,\undeclare@file@substitution} % \begin{syntax} % \cs{declare@file@substitution} \Arg{file} \Arg{replacement-file} % \cs{undeclare@file@substitution} \Arg{file} % \end{syntax} % If \meta{file} is requested for loading replace it with % \meta{replacement-file}. \cs{CurrentFile} remains pointing to % \meta{file} but \cs{CurrentFileUsed} will show the file actually % loaded. % % The main use case for this declaration is to provide a corrected % version of a package that can't be changed (due to its license) % but no longer functions because of \LaTeX{} kernel changes, for % example, or to provide a version that makes use of new kernel % functionality while the original package remains available for % use with older releases. % % The \cs{undeclare@file@substitution} declaration undoes a % substitution made earlier. % % \begin{quote} % \em % Please do not misuse this functionality and replace a file with % another unless if really needed and only if the new version is % implementing the same functionality as the original one! % \end{quote} % \end{function} % % \begin{function}{\disable@package@load,\reenable@package@load} % \begin{syntax} % \cs{disable@package@load} \Arg{package} \Arg{alternate-code} % \cs{reenable@package@load} \Arg{package} % \end{syntax} % If \meta{package} is requested do not load it but instead run % \meta{alternate-code} which could issue a warning, error or any % other code. % % The main use case is for classes that want to restrict the set of % supported packages or contain code that make the use of some % packages impossible. So rather than waiting until the document % breaks they can set up informative messages why certain packages % are not available. % % The function is only implemented for packages not for arbitrary % files. % \end{function} % % % \subsection{A sample package for structuring the log output} % % As an application we provide the package \pkg{structuredlog} that % adds lines to the \texttt{.log} when a file is opened and closed % for reading keeping track of nesting level es well. % For example, for the current document it adds the lines %\begin{verbatim} % = (LEVEL 1 START) t1lmr.fd % = (LEVEL 1 STOP) t1lmr.fd % = (LEVEL 1 START) supp-pdf.mkii % = (LEVEL 1 STOP) supp-pdf.mkii % = (LEVEL 1 START) nameref.sty % == (LEVEL 2 START) refcount.sty % == (LEVEL 2 STOP) refcount.sty % == (LEVEL 2 START) gettitlestring.sty % == (LEVEL 2 STOP) gettitlestring.sty % = (LEVEL 1 STOP) nameref.sty % = (LEVEL 1 START) ltfilehook-doc.out % = (LEVEL 1 STOP) ltfilehook-doc.out % = (LEVEL 1 START) ltfilehook-doc.out % = (LEVEL 1 STOP) ltfilehook-doc.out % = (LEVEL 1 START) ltfilehook-doc.hd % = (LEVEL 1 STOP) ltfilehook-doc.hd % = (LEVEL 1 START) ltfilehook.dtx % == (LEVEL 2 START) ot1lmr.fd % == (LEVEL 2 STOP) ot1lmr.fd % == (LEVEL 2 START) omllmm.fd % == (LEVEL 2 STOP) omllmm.fd % == (LEVEL 2 START) omslmsy.fd % == (LEVEL 2 STOP) omslmsy.fd % == (LEVEL 2 START) omxlmex.fd % == (LEVEL 2 STOP) omxlmex.fd % == (LEVEL 2 START) umsa.fd % == (LEVEL 2 STOP) umsa.fd % == (LEVEL 2 START) umsb.fd % == (LEVEL 2 STOP) umsb.fd % == (LEVEL 2 START) ts1lmr.fd % == (LEVEL 2 STOP) ts1lmr.fd % == (LEVEL 2 START) t1lmss.fd % == (LEVEL 2 STOP) t1lmss.fd % = (LEVEL 1 STOP) ltfilehook.dtx %\end{verbatim} % Thus if you inspect an issue in the \texttt{.log} it is easy to % figure out in which file it occurred, simply by searching back for % \texttt{LEVEL} and if it is a \texttt{STOP} then remove 1 from % the level value and search further for \texttt{LEVEL} with that value % which should then be the \texttt{START} level of the file you are in. % % \MaybeStop{\setlength\IndexMin{200pt} \PrintIndex } % % % \section{The Implementation} % % \begin{macrocode} %<*2ekernel> % \end{macrocode} % % \begin{macrocode} %<@@=filehook> % \end{macrocode} % % \changes{v1.0k}{2021/05/24}{Use \cs{msg_...} instead of \cs{__kernel_msg...}} % % % \subsection{Document and package-level commands} % % % \begin{macro}{\CurrentFile,\CurrentFilePath} % \begin{macro}{\CurrentFileUsed,\CurrentFilePathUsed} % User-level macros that hold the current file name and file path. % These are used internally as well because the code takes care to % protect against a possible redefinition of these macros in the % loaded file (it's necessary anyway to make hooks work with nested % \cs{input}). The versions |\...Used| hold the \emph{actual} file % name and path that is loaded by \LaTeX, whereas the other two hold % the name as requested. They will differ in case there's a file % substitution. % \begin{macrocode} % %<*2ekernel|latexrelease> %\IncludeInRelease{2020/10/01}% % {\CurrentFile}{Hook management file}% \ExplSyntaxOn \tl_new:N \CurrentFile \tl_new:N \CurrentFilePath \tl_new:N \CurrentFileUsed \tl_new:N \CurrentFilePathUsed \ExplSyntaxOff % %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{0000/00/00}% % {\CurrentFile}{Hook management file}% % %\let \CurrentFile \@undefined %\let \CurrentFilePath \@undefined %\let \CurrentFileUsed \@undefined %\let \CurrentFilePathUsed \@undefined % %\EndIncludeInRelease %<*2ekernel> % \end{macrocode} % \end{macro} % \end{macro} % % % % \subsection{\pkg{expl3} helpers} % % \begin{macrocode} % %<*2ekernel|latexrelease> %\IncludeInRelease{2020/10/01}% % {\@@_file_parse_full_name:nN}{File helpers}% \ExplSyntaxOn % \end{macrocode} % % \begin{macro}{ % \@@_file_parse_full_name:nN, % \@@_full_name:nn, % } % A utility macro to trigger \pkg{expl3}'s file-parsing and lookup, % and return a normalized representation of the file name. If the % queried file doesn't exist, no normalization takes place. % The output of \cs{@@_file_parse_full_name:nN} is passed on to the % |#2|---a 3-argument macro that takes the \meta{path}, \meta{base}, % and \meta{ext} parts of the file name. % % \begin{macrocode} \cs_new:Npn \@@_file_parse_full_name:nN #1 { \exp_args:Nf \file_parse_full_name_apply:nN { \exp_args:Nf \@@_full_name:nn { \file_full_name:n {#1} } {#1} } } \cs_new:Npn \@@_full_name:nn #1 #2 { \tl_if_empty:nTF {#1} { \tl_trim_spaces:n {#2} } { \tl_trim_spaces:n {#1} } } % \end{macrocode} % \end{macro} % % \begin{macro}{ % \@@_if_no_extension:nTF, % \@@_drop_extension:N % } % Some actions depend on whether the file extension was explicitly % given, and sometimes the extension has to be removed. The macros % below use \cs{@@_file_parse_full_name:nN} to split up the file name % and either check if \meta{ext} (|#3|) is empty, or discard it. % \begin{macrocode} \cs_new:Npn \@@_if_no_extension:nTF #1 { \exp_args:Ne \tl_if_empty:nTF { \file_parse_full_name_apply:nN {#1} \use_iii:nnn } } \cs_new_protected:Npn \@@_drop_extension:N #1 { \tl_gset:Nx #1 { \exp_args:NV \@@_file_parse_full_name:nN #1 \@@_drop_extension_aux:nnn } } \cs_new:Npn \@@_drop_extension_aux:nnn #1 #2 #3 { \tl_if_empty:nF {#1} { #1 / } #2 } % \end{macrocode} % \end{macro} % % \begin{macro}{\g_@@_input_file_seq,\l_@@_internal_tl} % \begin{macro}{\@@_file_push:,\@@_file_pop:} % \begin{macro}{\@@_file_pop_assign:nnnn} % Yet another stack, to keep track of \cs{CurrentFile} and % \cs{CurrentFilePath} with nested \cs{input}s. At the beginning of % \cs{InputIfFileExists}, the current value of \cs{CurrentFilePath} % and \cs{CurrentFile} is pushed to \cs{g_@@_input_file_seq}, and % at the end, it is popped and the value reassigned. Some other % places don't use \cs{InputIfFileExists} directly (\cs{include}) or % need \cs{CurrentFile} earlier (\cs{@onefilewithoptions}), so these % are manually used elsewhere as well. % \changes{v1.0h}{2021/03/18} % {Define \cs{g_@@_input_file_seq} to avoid losing data when % rolling back.} % \changes{v1.0l}{2021/08/27}{Internal message name changes} % \begin{macrocode} \tl_new:N \l_@@_internal_tl \seq_if_exist:NF \g_@@_input_file_seq { \seq_new:N \g_@@_input_file_seq } \cs_new_protected:Npn \@@_file_push: { \seq_gpush:Nx \g_@@_input_file_seq { { \CurrentFilePathUsed } { \CurrentFileUsed } { \CurrentFilePath } { \CurrentFile } } } \cs_new_protected:Npn \@@_file_pop: { \seq_gpop:NNTF \g_@@_input_file_seq \l_@@_internal_tl { \exp_after:wN \@@_file_pop_assign:nnnn \l_@@_internal_tl } { \msg_error:nnn { latex2e } { should-not-happen } { Tried~to~pop~from~an~empty~file~name~stack. } } } \cs_new_protected:Npn \@@_file_pop_assign:nnnn #1 #2 #3 #4 { \tl_set:Nn \CurrentFilePathUsed {#1} \tl_set:Nn \CurrentFileUsed {#2} \tl_set:Nn \CurrentFilePath {#3} \tl_set:Nn \CurrentFile {#4} } \ExplSyntaxOff % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macrocode} % %\EndIncludeInRelease % \end{macrocode} % % When rolling forward the following expl3 functions may not be defined. % If we roll back the code does nothing. % \changes{v1.0d}{2020/11/24}{Support for roll forward (gh/434)} % \InternalDetectionOff % \begin{macrocode} %\IncludeInRelease{2020/10/01}% % {\file_parse_full_name_apply:nN}{Roll forward help}% % %\ExplSyntaxOn %\cs_if_exist:NF\file_parse_full_name_apply:nN %{ %\cs_new:Npn \file_parse_full_name_apply:nN #1 % { % \exp_args:Ne \__file_parse_full_name_auxi:nN % { \__kernel_file_name_sanitize:n {#1} } % } %\cs_new:Npn \__file_parse_full_name_auxi:nN #1 % { % \__file_parse_full_name_area:nw { } #1 % / \s__file_stop % } %\cs_new:Npn \__file_parse_full_name_area:nw #1 #2 / #3 \s__file_stop % { % \tl_if_empty:nTF {#3} % { \__file_parse_full_name_base:nw { } #2 . \s__file_stop {#1} } % { \__file_parse_full_name_area:nw { #1 / #2 } % #3 \s__file_stop } % } %\cs_new:Npn \__file_parse_full_name_base:nw #1 #2 . #3 \s__file_stop % { % \tl_if_empty:nTF {#3} % { % \tl_if_empty:nTF {#1} % { % \tl_if_empty:nTF {#2} % { \__file_parse_full_name_tidy:nnnN { } { } } % { \__file_parse_full_name_tidy:nnnN { .#2 } { } } % } % { \__file_parse_full_name_tidy:nnnN {#1} { .#2 } } % } % { \__file_parse_full_name_base:nw { #1 . #2 } % #3 \s__file_stop } % } %\cs_new:Npn \__file_parse_full_name_tidy:nnnN #1 #2 #3 #4 % { % \exp_args:Nee #4 % { % \str_if_eq:nnF {#3} { / } { \use_none:n } % #3 \prg_do_nothing: % } % { \use_none:n #1 \prg_do_nothing: } % {#2} % } %} %\ExplSyntaxOff % %\EndIncludeInRelease %<*2ekernel> % \end{macrocode} % \InternalDetectionOn % % \begin{macrocode} %<@@=> % \end{macrocode} % % \subsection{Declaring the file-related hooks} % % These hooks have names with three-parts that % start with \hook{file/}, \hook{include/}, % \hook{class/} or \hook{package/} and end with \hook{/before} or % \hook{/after} (or \hook{/end} in the case of \hook{include/}). % They are all generic hooks % so will be declared only if code is added to them; % this declaration is done for you automatically and, indeed, they should % not be declared explicitly. % % Those named \hook{.../after} and \hook{include/.../end} % are, when code is added, declared as reversed hooks. % % % \subsection{Patching \LaTeX{}'s \cs{InputIfFileExists} command} % % Most of what we have to do is adding \cs{UseHook} into several % \LaTeXe{} core commands, because of some circular dependencies in the % kernel we do this only now and not in \texttt{ltfiles}. % % \begin{macro}{\InputIfFileExists} % \begin{macro}{\@input@file@exists@with@hooks} % \begin{macro}{\unqu@tefilef@und} % \cs{InputIfFileExists} loads any file if it is available so we % have to add the hooks \hook{file/before} and % \hook{file/after} in the right places. If the file doesn't % exist no hooks should be executed. % \begin{macrocode} % %\IncludeInRelease{2020/10/01}% % {\InputIfFileExists}{Hook management (files)}% %<*2ekernel|latexrelease> % \end{macrocode} % % \begin{macrocode} \let\InputIfFileExists\@undefined \DeclareRobustCommand \InputIfFileExists[2]{% \IfFileExists{#1}% {% \@expl@@@filehook@file@push@@ \@filehook@set@CurrentFile % \end{macrocode} % We pre-expand \cs{@filef@und} so that in case another file is % loaded in the true branch of \cs{InputIfFileExists}, these don't % change their value meanwhile. This isn't a worry with % \cs[no-index]{CurrentFile...} because they are kept in a stack. % % \changes{v1.0d}{2020/11/20} % {Move loading to \cs{@input@file@exists@with@hooks} and expand % \cs{@filef@und} to avoid getting the wrong file name in the case of % a substitution.} % \begin{macrocode} \expandafter\@swaptwoargs\expandafter {\expandafter\@input@file@exists@with@hooks \expandafter{\@filef@und}}% {#2}% \@expl@@@filehook@file@pop@@ }% } \def\@input@file@exists@with@hooks#1{% % \end{macrocode} % If the file exists then \cs{CurrentFile} holds its name. But we % can't rely on that still being true after the file has been % processed. Thus for using the name in the file hooks we need to % preserve the name and then restore it for the % \hook{file/.../after} hook. % % The hook always refers to the file requested by the user. The hook % is \emph{always} loaded for \cs{CurrentFile} which usually is the % same as \cs{CurrentFileUsed}. In the case of a file replacement, % the \cs{CurrentFileUsed} holds the actual file loaded. In any case % the file names are normalized so that the hooks work on the real % file name, rather than what the user typed in. % % \pkg{expl3}'s \cs{file_full_name:n} normalizes the file % name (to factor out differences in the |.tex| extension), and % then does a file lookup to take into account a possible path from % \cs{l_file_search_path_seq} and \cs{input@path}. However only % the file name and extension are returned so that file hooks can % refer to the file by their name only. The path to the file is % returned in \cs{CurrentFilePath}. % \changes{v1.0e}{2021/01/07}{Restore \cs[no-index]{CurrentFile(Path)(Used)} % after the input (gh/464)} % \begin{macrocode} \edef\reserved@a{% \@expl@@@filehook@file@pop@assign@@nnnn {\CurrentFilePathUsed}% {\CurrentFileUsed}% {\CurrentFilePath}% {\CurrentFile}}% \expandafter\@swaptwoargs\expandafter{\reserved@a}% % \end{macrocode} % % Before adding to the file list we need to make all (letter) characters % catcode~11, because several packages use constructions like % \begin{verbatim} % \filename@parse{} % \ifx\filename@ext\@clsextension % ... % \fi % \end{verbatim} % and that doesn't work if \cs{filename@ext} is \cs{detokenize}d. % Making \cs{@clsextension} a string doesn't help much because some % packages define their own \cs[no-index]{@someextension} with % normal catcodes. This is not entirely correct because packages loaded % (somehow) with catcode~12 alphabetic tokens (say, as the result of % a \cs{string} or \cs{detokenize} command, or from a \TeX{} string like % \cs{jobname}) will have these character tokens incorrectly turned into % letter tokens. This however is rare, so we'll go for the all-letters % approach (grepping the packages in \TeX{} Live didn't bring up any % obvious candidate for breaking with this catcode change). % \begin{macrocode} {\edef\reserved@a{\unqu@tefilef@und#1\@nil}% \@addtofilelist{\string@makeletter\reserved@a}% \UseHook{file/before}% % \end{macrocode} % The current file name is available in \cs{CurrentFile} so we use % that in the specific hook. % \begin{macrocode} \UseHook{file/\CurrentFile/before}% \@@input #1% <- trailing space comes from \@filef@und }% % \end{macrocode} % And here, \cs{CurrentFile} is restored % (by \cs{@expl@@@filehook@file@pop@assign@@nnnn}) so we can use it once more. % \begin{macrocode} \UseHook{file/\CurrentFile/after}% \UseHook{file/after}} \def\unqu@tefilef@und"#1" \@nil{#1} % \end{macrocode} % % \changes{v1.0l}{2021/08/25}{Declare non-generic file hooks} % Now declare the non-generic file hooks used above: % \begin{macrocode} \NewHook{file/before} \NewReversedHook{file/after} %\EndIncludeInRelease % % \end{macrocode} % % \changes{v0.9b} % {1993/12/04}{Macro added} % \changes{v0.9p} % {1994/01/18}{New Definition} % \changes{v0.3b}{1994/03/13} % {Use new cmd \cs{@addtofilelist}} % Now define |\InputIfFileExists| to input |#1| if it seems to exist. % Immediately prior to the input, |#2| is executed. % If the file |#1| does not exist, execute `|#3|'. % \changes{v1.0t}{1995/05/25} % {(CAR) added \cs{long}} % \changes{v1.1o}{2019/02/07}{Expand \cs{@filef@und} before executing % second argument (github/109)} % \changes{v1.2b}{2019/08/27}{Make command robust} % \begin{macrocode} %\IncludeInRelease{2019/10/01}% % {\InputIfFileExists}{Hook management (files)}% % %\DeclareRobustCommand \InputIfFileExists[2]{% % \IfFileExists{#1}% % {% % \expandafter\@swaptwoargs\expandafter % {\@filef@und}{#2\@addtofilelist{#1}\@@input}}} %\let\@input@file@exists@with@hooks\@undefined %\let\unqu@tefilef@und\@undefined %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{0000/00/00}% % {\InputIfFileExists}{Hook management (files)}% %\long\def \InputIfFileExists#1#2{% % \IfFileExists{#1}% % {#2\@addtofilelist{#1}\@@input \@filef@und}} % \end{macrocode} % % Also undo the internal command as some packages unfortunately test % for their existence instead of using \cs{IfFormatAtLeastTF}. % \changes{v1.0g}{2021/02/08}{Undo the internal for robust % \cs{InputIfFileExists} in rollback (gh/494)} % \begin{macrocode} %\expandafter\let\csname InputIfFileExists \endcsname\@undefined % \end{macrocode} % % \begin{macrocode} %\let\@input@file@exists@with@hooks\@undefined %\let\unqu@tefilef@und\@undefined %\EndIncludeInRelease %<*2ekernel> % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % % % % % % \subsection{Declaring a file substitution} % % \begin{macrocode} %<@@=filehook> % \end{macrocode} % % \begin{macrocode} % %<*2ekernel|latexrelease> %\IncludeInRelease{2020/10/01}% % {\@@_subst_add:nn}{Declaring file substitution}% \ExplSyntaxOn % \end{macrocode} % % % \begin{macro}{\@@_subst_add:nn,\@@_subst_remove:n, % \@@_subst_file_normalize:Nn,\@@_subst_empty_name_chk:NN} % \cs{@@_subst_add:nn} declares a file substitution by % doing a (global) definition of the form % |\def|\cs{@file-subst@\meta{file}}|{|\meta{replacement}|}|. % The file names are properly sanitised, and normalized with the same % treatment done for the file hooks. That is, a file replacement is % declared by using the file name (and extension, if any) only, and % the file path should not be given. If a file name is empty it is % replaced by |.tex| (the empty csname is used to check that). % \begin{macrocode} \cs_new_protected:Npn \@@_subst_add:nn #1 #2 { \group_begin: \cs_set:cpx { } { \exp_not:o { \cs:w\cs_end: } } \int_set:Nn \tex_escapechar:D { -1 } \cs_gset:cpx { @file-subst@ \@@_subst_file_normalize:Nn \use_ii_iii:nnn {#1} } { \@@_subst_file_normalize:Nn \@@_file_name_compose:nnn {#2} } \group_end: } \cs_new_protected:Npn \@@_subst_remove:n #1 { \group_begin: \cs_set:cpx { } { \exp_not:o { \cs:w\cs_end: } } \int_set:Nn \tex_escapechar:D { -1 } \cs_undefine:c { @file-subst@ \@@_subst_file_normalize:Nn \use_ii_iii:nnn {#1} } \group_end: } \cs_new:Npn \@@_subst_file_normalize:Nn #1 #2 { \exp_after:wN \@@_subst_empty_name_chk:NN \cs:w \exp_after:wN \cs_end: \cs:w \@@_file_parse_full_name:nN {#2} #1 \cs_end: } \cs_new:Npn \@@_subst_empty_name_chk:NN #1 #2 { \if_meaning:w #1 #2 .tex \else: \token_to_str:N #2 \fi: } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\use_ii_iii:nnn} % A variant of \cs[no-index]{use_...} to discard the first of three % arguments. % \fmi{this should move to \pkg{expl3}} % \begin{macrocode} \cs_gset:Npn \use_ii_iii:nnn #1 #2 #3 {#2 #3} % \end{macrocode} % \end{macro} % % % \begin{macrocode} \ExplSyntaxOff % %\EndIncludeInRelease %<*2ekernel> % \end{macrocode} % % % % \begin{macro}{\declare@file@substitution} % \begin{macro}{\undeclare@file@substitution} % For two internals we provide \LaTeXe{} names so that we can use % them elsewhere in the kernel (and so that they can be used in % packages if really needed, e.g., \pkg{scrlfile}). % \begin{macrocode} % %<*2ekernel|latexrelease> %\IncludeInRelease{2020/10/01}% % {\declare@file@substitution}{File substitution}% \ExplSyntaxOn \cs_new_eq:NN \declare@file@substitution \@@_subst_add:nn \cs_new_eq:NN \undeclare@file@substitution \@@_subst_remove:n \ExplSyntaxOff % %\EndIncludeInRelease % \end{macrocode} % % We are not fully rolling back the file substitutions in case a % rollback encounters a package that contains them, but is itself % not setup for rollback. So we just bypass them and hope for the % best. % \changes{v1.0d}{2020/12/04}{Don't drop file substitution commands on % rollback} % \begin{macrocode} %\IncludeInRelease{0000/00/00}% % {\declare@file@substitution}{File substitution}% % %\let \declare@file@substitution \@gobbletwo %\let \undeclare@file@substitution \@gobble % %\EndIncludeInRelease %<*2ekernel> % \end{macrocode} % \end{macro} % \end{macro} % % % % % \begin{macrocode} %<@@=> % \end{macrocode} % % \subsection{Selecting a file (\cs{set@curr@file})} % % \begin{macro}{\set@curr@file,\set@curr@file@nosearch} % \begin{macro}{\@curr@file,\@curr@file@reqd} % \changes{v1.0f}{2021/01/31}{set \cs{protect} to \cs{string} gh/481} % Now we hook into \cs{set@curr@file} to resolve a possible file % substitution, and add \cs{@expl@@@filehook@set@curr@file@@nNN} % at the end, after \cs{@curr@file} is set. % % A file name is built using % \cs{expandafter}\cs{string}\cs{csname}\meta{filename}\cs{endcsname} % to avoid expanding utf8 active characters. The \cs{csname} expands % the normalization machinery and the routine to resolve a file % substitution, returning a control sequence with the same name as the % file. % % It happens that when \meta{filename} is empty, the generated control % sequence is \cs{csname\cs{endcsname}}, and doing \cs{string} on % that results in the file |csnameendcsname.tex|. To guard against % that we \cs{ifx}-compare the generated control sequence with the % empty csname. To do so, \cs{csname\cs{endcsname}} has to be % defined, otherwise it would be equal to \cs{relax} and we would have % false positives. Here we define \cs{csname\cs{endcsname}} to % expand to itself to avoid it matching the definition of some other % control sequence. % \changes{v1.0i}{2021/04/20} % {Make \string~ expand to a string (tracks change in l3kernel)} % \changes{v1.0m}{2022/03/10} % {Add \cs{set@curr@file@nosearch} for \pkg{graphicx}} % \begin{macrocode} % %<*2ekernel|latexrelease> %\IncludeInRelease{2022/06/01}% % {\set@curr@file}{Setting current file name}% \def\set@curr@file{% \begingroup \set@curr@file@aux} \edef\set@curr@file@nosearch{% \begingroup \let\noexpand\input@path\noexpand\@empty \csname seq_clear:N\endcsname \expandafter\noexpand\csname l_file_search_path_seq\endcsname \noexpand\set@curr@file@aux} \def\set@curr@file@aux#1{% \escapechar\m@ne \let\protect\string \edef~{\string~}% \expandafter\def\csname\expandafter\endcsname \expandafter{\csname\endcsname}% % \end{macrocode} % Two file names are set here: \cs{@curr@file@reqd} which is the file % requested by the user, and \cs{@curr@file} which should be the same, % except when we have a file substitution, in which case it holds the % actual loaded file. \cs{@curr@file} is resolved first, to check if % a substitution happens. If it doesn't, % \cs{@expl@@@filehook@if@file@replaced@@TF} short-cuts and just copies % \cs{@curr@file}, otherwise the full normalization procedure is % executed. % % At this stage the file name is parsed and normalized, but if the % input doesn't have an extension, the default |.tex| is \emph{not} % added to \cs{@curr@file} because for applications other than % \cs{input} (graphics, for example) the default extension may not % be |.tex|. First check if the input has an extension, then if the % input had no extension, call \cs{@expl@@@filehook@drop@extension@@N}. In case % of a file substitution, \cs{@curr@file} will have an extension. % \begin{macrocode} \@expl@@@filehook@if@no@extension@@nTF{#1}% {\@tempswatrue}{\@tempswafalse}% \@kernel@make@file@csname\@curr@file \@expl@@@filehook@resolve@file@subst@@w {#1}% \@expl@@@filehook@if@file@replaced@@TF {\@kernel@make@file@csname\@curr@file@reqd \@expl@@@filehook@normalize@file@name@@w{#1}% \if@tempswa \@expl@@@filehook@drop@extension@@N\@curr@file@reqd \fi}% {\if@tempswa \@expl@@@filehook@drop@extension@@N\@curr@file \fi \global\let\@curr@file@reqd\@curr@file}% \@expl@@@filehook@clear@replacement@flag@@ \endgroup} % %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2021/06/01}% % {\set@curr@file}{Setting current file name}% %\def\set@curr@file#1{% % \begingroup % \escapechar\m@ne % \let\protect\string % \edef~{\string~}% % \expandafter\def\csname\expandafter\endcsname % \expandafter{\csname\endcsname}% % \@expl@@@filehook@if@no@extension@@nTF{#1}% % {\@tempswatrue}{\@tempswafalse}% % \@kernel@make@file@csname\@curr@file % \@expl@@@filehook@resolve@file@subst@@w {#1}% % \@expl@@@filehook@if@file@replaced@@TF % {\@kernel@make@file@csname\@curr@file@reqd % \@expl@@@filehook@normalize@file@name@@w{#1}% % \if@tempswa \@expl@@@filehook@drop@extension@@N\@curr@file@reqd \fi}% % {\if@tempswa \@expl@@@filehook@drop@extension@@N\@curr@file \fi % \global\let\@curr@file@reqd\@curr@file}% % \@expl@@@filehook@clear@replacement@flag@@ % \endgroup} %\let\set@curr@file@nosearch\@undefined %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2020/10/01}% % {\set@curr@file}{Setting current file name}% %\def\set@curr@file#1{% % \begingroup % \escapechar\m@ne % \expandafter\def\csname\expandafter\endcsname % \expandafter{\csname\endcsname}% % \@expl@@@filehook@if@no@extension@@nTF{#1}% % {\@tempswatrue}{\@tempswafalse}% % \@kernel@make@file@csname\@curr@file % \@expl@@@filehook@resolve@file@subst@@w {#1}% % \@expl@@@filehook@if@file@replaced@@TF % {\@kernel@make@file@csname\@curr@file@reqd % \@expl@@@filehook@normalize@file@name@@w{#1}% % \if@tempswa \@expl@@@filehook@drop@extension@@N\@curr@file@reqd \fi}% % {\if@tempswa \@expl@@@filehook@drop@extension@@N\@curr@file \fi % \global\let\@curr@file@reqd\@curr@file}% % \@expl@@@filehook@clear@replacement@flag@@ % \endgroup} %\let\set@curr@file@nosearch\@undefined %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{2019/10/01}% % {\set@curr@file}{Setting current file name}% %\def\set@curr@file#1{% % \begingroup % \escapechar\m@ne % \xdef\@curr@file{% % \expandafter\expandafter\expandafter\unquote@name % \expandafter\expandafter\expandafter{% % \expandafter\string % \csname\@firstofone#1\@empty\endcsname}}% % \endgroup %} %\let\set@curr@file@nosearch\@undefined %\EndIncludeInRelease % \end{macrocode} % % \begin{macrocode} %\IncludeInRelease{0000/00/00}% % {\set@curr@file}{Setting current file name}% %\let\set@curr@file\@undefined %\let\set@curr@file@nosearch\@undefined %\EndIncludeInRelease %<*2ekernel> % \end{macrocode} % \end{macro} % \end{macro} % % % % % \begin{macro}{\@filehook@set@CurrentFile} % \begin{macro}{\@kernel@make@file@csname,\@set@curr@file@aux} % % \fmi{This should get internalized using \texttt{@expl@} names} % \begin{macrocode} % %<*2ekernel|latexrelease> %\IncludeInRelease{2020/10/01}% % {\@kernel@make@file@csname}{Make file csname}% % \end{macrocode} % % \begin{macrocode} \def\@kernel@make@file@csname#1#2#3{% \xdef#1{\expandafter\@set@curr@file@aux \csname\expandafter#2\@firstofone#3\@nil\endcsname}} % \end{macrocode} % This auxiliary compares \cs{\meta{filename}} with % \cs{csname\cs{endcsname}} to check if the empty |.tex| file was % requested. % \changes{v1.0o}{2023/04/02} % {Make \cs{@set@curr@file@aux} \cs{long} gh/942} % \begin{macrocode} \long\def\@set@curr@file@aux#1{% \expandafter\ifx\csname\endcsname#1% .tex\else\string#1\fi} % \end{macrocode} % % \begin{sloppypar} % Then we call \cs{@expl@@@filehook@set@curr@file@@nNN} once for % \cs{@curr@file} to set \cs[no-index]{CurrentFile(Path)Used} and once for % \cs{@curr@file@reqd} to set \cs[no-index]{CurrentFile(Path)}. % Here too the slower route is only used if a substitution happened, % but here \cs{@expl@@@filehook@if@file@replaced@@TF} can't be used because % the flag is reset at the \cs{endgroup} above, so we check if % \cs{@curr@file} and \cs{@curr@file@reqd} differ. This macro is % issued separate from \cs{set@curr@file} because it changes % \cs{CurrentFile}, and side-effects would quickly get out of control. % \end{sloppypar} % \begin{macrocode} \def\@filehook@set@CurrentFile{% \@expl@@@filehook@set@curr@file@@nNN{\@curr@file}% \CurrentFileUsed\CurrentFilePathUsed \ifx\@curr@file@reqd\@curr@file \let\CurrentFile\CurrentFileUsed \let\CurrentFilePath\CurrentFilePathUsed \else \@expl@@@filehook@set@curr@file@@nNN{\@curr@file@reqd}% \CurrentFile\CurrentFilePath \fi} % %\EndIncludeInRelease %<*2ekernel> % \end{macrocode} % \end{macro} % \end{macro} % % % % \begin{macro}{\@@_set_curr_file:nNN, % \@@_set_curr_file_assign:nnnNN} % When inputting a file, \cs{set@curr@file} does a file lookup % (in \cs{input@path} and \cs{l_file_search_path_seq}) and returns the % actual file name (\meta{base} plus \meta{ext}) in % \cs{CurrentFileUsed}, and in case there's a file substitution, the % requested file in \cs{CurrentFile} (otherwise both are the same). % Only the base and extension are returned, % regardless of the input (both \texttt{path/to/file.tex} and % \texttt{file.tex} end up as \texttt{file.tex} in \cs{CurrentFile}). % The path is returned in \cs{CurrentFilePath}, in case it's needed. % \begin{macrocode} % %<*2ekernel|latexrelease> %\IncludeInRelease{2020/10/01}% % {@@_set_curr_file:nNN}{Set curr file}% \ExplSyntaxOn %<@@=filehook> \cs_new_protected:Npn \@@_set_curr_file:nNN #1 { \exp_args:Nf \@@_file_parse_full_name:nN {#1} \@@_set_curr_file_assign:nnnNN } \cs_new_protected:Npn \@@_set_curr_file_assign:nnnNN #1 #2 #3 #4 #5 { \str_set:Nn #5 {#1} \str_set:Nn #4 {#2#3} } \ExplSyntaxOff % %\EndIncludeInRelease %<*2ekernel> % \end{macrocode} % \end{macro} % % % % % \subsection{Replacing a file and detecting loops} % % \begin{macro}{\@@_resolve_file_subst:w} % \begin{macro}{\@@_normalize_file_name:w} % \begin{macro}{\@@_file_name_compose:nnn} % Start by sanitizing the file with \cs{@@_file_parse_full_name:nN} % then do \cs{@@_file_subst_begin:nnn}\Arg{path}\Arg{name}\Arg{ext}. % \begin{macrocode} % %<*2ekernel|latexrelease> %\IncludeInRelease{2020/10/01}% % {\@@_resolve_file_subst:w}{Replace files detect loops}% \ExplSyntaxOn \cs_new:Npn \@@_resolve_file_subst:w #1 \@nil { \@@_file_parse_full_name:nN {#1} \@@_file_subst_begin:nnn } \cs_new:Npn \@@_normalize_file_name:w #1 \@nil { \@@_file_parse_full_name:nN {#1} \@@_file_name_compose:nnn } \cs_new:Npn \@@_file_name_compose:nnn #1 #2 #3 { \tl_if_empty:nF {#1} { #1 / } #2#3 } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{flag @@_file_replaced} % \begin{macro}{\@@_if_file_replaced:TF} % \begin{macro}{\@@_clear_replacement_flag:} % Since the file replacement is done expandably in a \cs{csname}, use % a flag to remember if a substitution happened. We use this in % \cs{set@curr@file} to short-circuit some of it in case no % substitution happened (by far the most common case, so it's worth % optimizing). The flag raised during the file substitution algorithm % must be explicitly cleared after the \cs{@@_if_file_replaced:TF} % conditional is no longer needed, otherwise further uses of % \cs{@@_if_file_replaced:TF} will wrongly return true. % \begin{macrocode} \flag_new:n { @@_file_replaced } \cs_new:Npn \@@_if_file_replaced:TF #1 #2 { \flag_if_raised:nTF { @@_file_replaced } {#1} {#2} } \cs_new_protected:Npn \@@_clear_replacement_flag: { \flag_clear:n { @@_file_replaced } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\@@_file_subst_begin:nnn} % First off, start by checking if the current file ($\meta{name} + % \meta{ext}$) has a declared substitution. If not, then just put % that as the name (including a possible \meta{path} in this case): % this is the default case with no substitutions, so it's the first to % be checked. The auxiliary \cs{@@_file_subst_tortoise_hare:nn} sees % that there's no replacement for |#2#3| and does nothing else. % \begin{macrocode} \cs_new:Npn \@@_file_subst_begin:nnn #1 #2 #3 { \@@_file_subst_tortoise_hare:nn { #2#3 } { #2#3 } { \@@_file_name_compose:nnn {#1} {#2} {#3} } } \ExplSyntaxOff % %\EndIncludeInRelease %<*2ekernel> % \end{macrocode} % \end{macro} % % % % % \subsubsection{The Tortoise and Hare algorithm} % % \begin{macro}{\@@_file_subst_tortoise_hare:nn} % \begin{macro}{\@@_file_subst_loop:NN,\@@_file_subst_loop:cc} % If there is a substitution (\meta{true} in the first % \cs{cs_if_exist:cTF} below), then first check if there is no % substitution down the line: this should be the second most common % case, of one file replaced by another. In that case just leave the % substitution there and the job is done. If any substitution % happens, then the \cs{flag @@_file_replaced} is raised % (conditionally, because checking if a flag is raised is much faster % than raising it over and over again). % % If, however there are more substitutions, then we need to check for % a possible loop in the substitutions, which would otherwise put % \TeX{} in an infinite loop if just an exhaustive expansion was used. % % To detect a loop, the \emph{Tortoise and Hare} algorithm is used. % The name of the algorithm is an analogy to Aesop's fable, in which % the Hare outruns a Tortoise. The two pointers here are the csnames % which contains each file replacement, both of which start at the % position zero, which is the file requested. In the inner part of % the macro below, \cs{@@_file_subst_loop:cc} is called with % \cs[no-index]{@file-subst@\meta{file}} and % \cs[no-index]{@file-subst@\cs[no-index]{@file-subst@\meta{file}}}; % that is, the substitution of \meta{file} and the substitution of that % substitution: the Tortoise walks one step while the Hare walks two. % % Within \cs{@@_file_subst_loop:NN} the two substitutions are % compared, and if they lead to the same file it means that there is % a loop in the substitutions. If there's no loop, % \cs{@@_file_subst_tortoise_hare:nn} is called again with the % Tortoise at position~1 and the hare at~2. Again, the substitutions % are checked ahead of the Hare pointer to check that it won't run too % far; in case there is no loop in the declarations, eventually one % of the \cs{cs_if_exist:cTF} below will go \meta{false} and the % algorithm will end; otherwise it will run until the Hare reaches % the same spot as the tortoise and a loop is detected. % \begin{macrocode} % %<*2ekernel|latexrelease> %\IncludeInRelease{2020/10/01}% % {\@@_file_subst_tortoise_hare:nn}{Tortoise and Hare}% \ExplSyntaxOn \cs_new:Npn \@@_file_subst_tortoise_hare:nn #1 #2 #3 { \cs_if_exist:cTF { @file-subst@ #2 } { \flag_if_raised:nF { @@_file_replaced } { \flag_raise:n { @@_file_replaced } } \cs_if_exist:cTF { @file-subst@ \use:c { @file-subst@ #2 } } { \@@_file_subst_loop:cc { @file-subst@ #1 } { @file-subst@ \use:c { @file-subst@ #2 } } } { \use:c { @file-subst@ #2 } } } { #3 } } % \end{macrocode} % This is just an auxiliary to check if a loop was found, and continue % the algorithm otherwise. If a loop is found, the |.tex| file is % used as fallback and \cs{@@_file_subst_cycle_error:cN} is called to % report the error. % \begin{macrocode} \cs_new:Npn \@@_file_subst_loop:NN #1 #2 { \token_if_eq_meaning:NNTF #1 #2 { .tex \@@_file_subst_cycle_error:cN { @file-subst@ #1 } #1 } { \@@_file_subst_tortoise_hare:nn {#1} {#2} {#2} } } \cs_generate_variant:Nn \@@_file_subst_loop:NN { cc } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{ % \@@_file_subst_cycle_error:NN, % \@@_file_subst_cycle_error:cN, % } % \changes{v1.0l}{2021/08/27}{Use \cs{msg_...} not \cs{__kernel_msg_...}} % Showing this type of error expandably is tricky, as we have a very % limited amount of characters to show and a potentially large list. % As a work around, several errors are printed, each showing one step % of the loop, until all the error messages combined show the loop. % \begin{macrocode} \cs_new:Npn \@@_file_subst_cycle_error:NN #1 #2 { \msg_expandable_error:nnff { latex2e } { file-cycle } {#1} { \use:c { @file-subst@ #1 } } \token_if_eq_meaning:NNF #1 #2 { \@@_file_subst_cycle_error:cN { @file-subst@ #1 } #2 } } \cs_generate_variant:Nn \@@_file_subst_cycle_error:NN { c } % \end{macrocode} % % And the error message: % \begin{macrocode} \msg_new:nnn { latex2e } { file-cycle } { File~loop!~#1~replaced~by~#2... } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macrocode} \ExplSyntaxOff % %\EndIncludeInRelease %<*2ekernel> % \end{macrocode} % % % \begin{macrocode} %<@@=> % \end{macrocode} % % % \subsection{Preventing a package from loading} % % We support the use case of preventing a package from loading but not % any other type of files (e.g., classes). % % \begin{macro}{\disable@package@load} % \begin{macro}{\reenable@package@load} % \begin{macro}{\@disable@packageload@do} % \cs{disable@package@load} defines % \cs[no-index]{@pkg-disable@\meta{package}} to expand to some code |#2| % instead of loading the package. % \begin{macrocode} % %<*2ekernel|latexrelease> %\IncludeInRelease{2020/10/01}% % {\disable@package@load}{Disable packages}% \def\disable@package@load#1#2{% \global\@namedef{@pkg-disable@#1.\@pkgextension}{#2}} % \end{macrocode} % % \changes{v1.0n}{2022/08/18}{Inhibit checking the loaded version when % package is load-disabled, and write to the .log (gh/888)} % Here we check if a control sequence named % \cs[no-index]{@pkg-disable@\meta{name}.sty} is defined, and if so % don't use the package loading code |#2|, but use the replacement % code stored in that control sequence, write something to the log, % and then prevent \cs{@onefilewithoptions} from sanity-checking the % requested package date (the \tn{expandafter} here triggers one in % \cs{@onefilewithoptions} that ends a conditional there, and the % \tn{@gobbletwo} removes the date checking code from the input % stream). % \begin{macrocode} \def\@disable@packageload@do#1#2{% \@ifundefined{@pkg-disable@#1}% {#2}% {\@nameuse{@pkg-disable@#1}% \@latex@info{Package '#1' has been disabled.% \MessageBreak Load request ignored}% \expandafter\@gobbletwo}} % \end{macrocode} % % \cs{reenable@package@load} undefines % \cs[no-index]{@pkg-disable@\meta{package}} to reallow loading a package. % \begin{macrocode} \def\reenable@package@load#1{% \global\expandafter\let \csname @pkg-disable@#1.\@pkgextension \endcsname \@undefined} % \end{macrocode} % % % \begin{macrocode} % %\EndIncludeInRelease %\IncludeInRelease{0000/00/00}% % {\disable@package@load}{Disable packages}% % %\let\disable@package@load \@undefined %\let\@disable@packageload@do\@undefined %\let\reenable@package@load \@undefined %\EndIncludeInRelease %<*2ekernel> % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % % % % % \subsection{High-level interfaces for \LaTeX{}} % % None so far and the general feeling for now is that the hooks are % enough. Packages like \pkg{filehook}, etc., may use them to set % up their interfaces (samples are given below) but for the now the % kernel will not provide any. % % % % \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 (and % in parts that still use \LaTeXe{} syntax. We have to unset the % \texttt{@\/@} since we want double ``at'' sign in place of double % underscores. % % \begin{macrocode} %<@@=> % \end{macrocode} % \InternalDetectionOff % \begin{macrocode} % %<*2ekernel|latexrelease> %\IncludeInRelease{2020/10/01}% % {\@expl@@@filehook@if@no@extension@@nTF}{2e tmp interfaces}% \ExplSyntaxOn % \end{macrocode} % % \begin{macrocode} \cs_new_eq:NN \@expl@@@filehook@if@no@extension@@nTF \__filehook_if_no_extension:nTF % \end{macrocode} % % \begin{macrocode} \cs_new_eq:NN \@expl@@@filehook@set@curr@file@@nNN \__filehook_set_curr_file:nNN % \end{macrocode} % % \begin{macrocode} \cs_new_eq:NN \@expl@@@filehook@resolve@file@subst@@w \__filehook_resolve_file_subst:w % \end{macrocode} % % \begin{macrocode} \cs_new_eq:NN \@expl@@@filehook@normalize@file@name@@w \__filehook_normalize_file_name:w % \end{macrocode} % % \begin{macrocode} \cs_new_eq:NN \@expl@@@filehook@if@file@replaced@@TF \__filehook_if_file_replaced:TF % \end{macrocode} % % \begin{macrocode} \cs_new_eq:NN \@expl@@@filehook@clear@replacement@flag@@ \__filehook_clear_replacement_flag: % \end{macrocode} % % \begin{macrocode} \cs_new_eq:NN \@expl@@@filehook@drop@extension@@N \__filehook_drop_extension:N % \end{macrocode} % % \begin{macrocode} \cs_new_eq:NN \@expl@@@filehook@file@push@@ \__filehook_file_push: % \end{macrocode} % % \begin{macrocode} \cs_new_eq:NN \@expl@@@filehook@file@pop@@ \__filehook_file_pop: % \end{macrocode} % % \begin{macrocode} \cs_new_eq:NN \@expl@@@filehook@file@pop@assign@@nnnn \__filehook_file_pop_assign:nnnn % \end{macrocode} % \InternalDetectionOn % % % \begin{macrocode} \ExplSyntaxOff % \end{macrocode} % % This one specifically has to be undefined because it is left over in % the input stream from \cs{InputIfFileExists} and executed when % \pkg{latexrelease} is loaded. It cannot be \cs{let} to \cs{@undefined} % otherwise it would error as well, so it is \cs{let} to \cs{relax} to % be silently ignored when loading \cs{latexrelease}. % \changes{v1.0e}{2021/01/07}{Added rollback for this case to avoid % spurious errors (part of gh/463)} % \begin{macrocode} % %\EndIncludeInRelease % %\IncludeInRelease{0000/00/00}% % {\@expl@@@filehook@if@no@extension@@nTF}{2e tmp interfaces}% %\let\@expl@@@filehook@file@pop@@\relax %\EndIncludeInRelease %<*2ekernel> % \end{macrocode} % % This ends the kernel code in this file. % \begin{macrocode} % % \end{macrocode} % % % % \section{A sample package for structuring the log output} % % \begin{macrocode} %<*structuredlog> %<@@=filehook> % \end{macrocode} % % \begin{macrocode} \ProvidesExplPackage {structuredlog}{\ltfilehookdate}{\ltfilehookversion} {Structuring the TeX transcript file} % \end{macrocode} % % \begin{macro}{\g_@@_nesting_level_int} % Stores the current package nesting level. % \begin{macrocode} \int_new:N \g_@@_nesting_level_int % \end{macrocode} % Initialise the counter with the number of files in the % \cs{@currnamestack} (the number of items divided by $3$) minus one, % because this package is skipped when printing to the log. % \begin{macrocode} \int_gset:Nn \g_@@_nesting_level_int { ( \tl_count:N \@currnamestack ) / 3 - 1 } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_log_file_record:n} % This macro is responsible for increasing and decreasing the file % nesting level, as well as printing to the log. The argument is % either |STOPTART| or |STOP| and the action it takes on the nesting % integer depends on that. % \begin{macrocode} \cs_new_protected:Npn \@@_log_file_record:n #1 { \str_if_eq:nnT {#1} {START} { \int_gincr:N \g_@@_nesting_level_int } \iow_term:x { \prg_replicate:nn { \g_@@_nesting_level_int } { = } ~ ( LEVEL ~ \int_use:N \g_@@_nesting_level_int \c_space_tl #1 ) ~ \CurrentFileUsed % \end{macrocode} % If there was a file replacement, show that as well: % \begin{macrocode} \str_if_eq:NNF \CurrentFileUsed \CurrentFile { ~ ( \CurrentFile \c_space_tl requested ) } \iow_newline: } \str_if_eq:nnT {#1} {STOP} { \int_gdecr:N \g_@@_nesting_level_int } } % \end{macrocode} % % Now just hook the macro above in the generic |file/before|\ldots % \begin{macrocode} \AddToHook{file/before}{ \@@_log_file_record:n { START } } % \end{macrocode} % \ldots and |file/after| hooks. % We don't want to install the \hook{file/after} hook immediately, % because that would mean it is the first time executed when the % package finishes. We therefore put the declaration inside % \cs{AddToHookNext} so that it gets only installed when we have % left this package. % \begin{macrocode} \AddToHookNext{file/after} { \AddToHook{file/after}{ \@@_log_file_record:n { STOP } } } % \end{macrocode} % \end{macro} % % \begin{macrocode} %<@@=> % % \end{macrocode} % % % % % % % \section{Package emulations} % % % \subsection{Package \pkg{atveryend} emulation} % % With the new hook management and the hooks in \cs{enddocument} % all of \pkg{atveryend} is taken care of. % We can make an emulation only here after the substitution % functionality is available: % \begin{macrocode} %<*2ekernel> \declare@file@substitution{atveryend.sty}{atveryend-ltx.sty} % % \end{macrocode} % % Here is the package file we point to: % \begin{macrocode} %<*atveryend-ltx> \ProvidesPackage{atveryend-ltx} [2020/08/19 v1.0a Emulation of the original atveryend package^^Jwith kernel methods] % \end{macrocode} % % % Here are new definitions for its interfaces now pointing to the % hooks in \cs{enddocument} % \begin{macrocode} \newcommand\AfterLastShipout {\AddToHook{enddocument/afterlastpage}} \newcommand\AtVeryEndDocument {\AddToHook{enddocument/afteraux}} % \end{macrocode} % Next one is a bit of a fake, but the result should normally be as % expected. If not, one needs to add a rule to sort the code chunks % in \hook{enddocument/info}. % \begin{macrocode} \newcommand\AtEndAfterFileList{\AddToHook{enddocument/info}} % \end{macrocode} % % \begin{macrocode} \newcommand\AtVeryVeryEnd {\AddToHook{enddocument/end}} % \end{macrocode} % % \begin{macro}{\BeforeClearDocument} % This one is the only one we don't implement or rather don't have % a dedicated hook in the code. % \begin{macrocode} \ExplSyntaxOn \newcommand\BeforeClearDocument[1] { \AtEndDocument{#1} \atveryend@DEPRECATED{BeforeClearDocument \tl_to_str:n{#1}} } % \end{macrocode} % % \begin{macrocode} \cs_new:Npn\atveryend@DEPRECATED #1 {\iow_term:x{======~DEPRECATED~USAGE~#1~==========}} \ExplSyntaxOff % \end{macrocode} % \end{macro} % % % \begin{macrocode} % % \end{macrocode} % % % % \Finale % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \endinput %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %