diff options
author | Karl Berry <karl@freefriends.org> | 2020-10-02 21:49:18 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2020-10-02 21:49:18 +0000 |
commit | 5255c37137345163348d22ab46af2304cf9f55a6 (patch) | |
tree | 88246497477ddcc26834e5708566ad4c1c862a19 /Master/texmf-dist/source/latex/base/ltfilehook.dtx | |
parent | 1922ba4684a59cde742dcef9ed152381d2d7b110 (diff) |
latex2e (2oct20)
git-svn-id: svn://tug.org/texlive/trunk@56514 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/source/latex/base/ltfilehook.dtx')
-rw-r--r-- | Master/texmf-dist/source/latex/base/ltfilehook.dtx | 1543 |
1 files changed, 1543 insertions, 0 deletions
diff --git a/Master/texmf-dist/source/latex/base/ltfilehook.dtx b/Master/texmf-dist/source/latex/base/ltfilehook.dtx new file mode 100644 index 00000000000..24ba88f576c --- /dev/null +++ b/Master/texmf-dist/source/latex/base/ltfilehook.dtx @@ -0,0 +1,1543 @@ +% \iffalse meta-comment +%% +%% File: ltfilehook.dtx (C) Copyright 2020 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: ltfilehook.dtx +% +% \begin{macrocode} +\providecommand\ltfilehookversion{v1.0b} +\providecommand\ltfilehookdate{2020/09/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 + +\usepackage{structuredlog} % for demonstration + +\EnableCrossrefs +\CodelineIndex +\begin{document} + \DocInput{ltfilehook.dtx} +\end{document} +%</driver> +% +% \fi +% +% +% \long\def\fmi#1{\begin{quote}\itshape Todo: #1\end{quote}} +% +% \let\hook\texttt +% +% \title{The \texttt{ltfilehook} package\thanks{This package has version +% \ltfilehookversion\ dated \ltfilehookdate, \copyright\ \LaTeX\ +% Project.}} +% +% \author{Frank Mittelbach} +% +% \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[\texttt{file/before}, \texttt{file/before/\meta{file-name}}] +% +% 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[\texttt{file/after/\meta{file-name}}, \texttt{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 \texttt{before} and \texttt{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 +% \texttt{/before} and \texttt{/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/before/\meta{file name}}} \\ +% \>\> \meta{file contents} \\ +% \>\cs{UseHook}\marg{\hook{file/after/\meta{file name}}} \\ +% \>\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/before/\meta{file name}} and +% \hook{file/after/\meta{file name}}. +% +% \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., +% \texttt{file/after/array.sty} 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[\texttt{package/before}, \texttt{package/after}] +% +% These hooks are called for each package being loaded. +% +% \item[\texttt{package/before/\meta{name}}, +% \texttt{package/after/\meta{name}}] +% +% These hooks are additionally called if the package name is +% \meta{name} (without extension). +% +% \item[\texttt{class/before}, \texttt{class/after}] +% +% These hooks are called for each class being loaded. +% +% \item[\texttt{class/before/\meta{name}}, \texttt{class/after/\meta{name}}] +% +% 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/before/\meta{package name}}} \\[5pt] +% \>\>\cs{UseHook}\marg{\hook{file/before}} \\ +% \>\>\cs{UseHook}\marg{\hook{file/before/\meta{package name}.sty}} \\ +% \>\>\> \meta{package contents} \\ +% \>\>\cs{UseHook}\marg{\hook{file/after/\meta{package name}.sty}} \\ +% \>\>\cs{UseHook}\marg{\hook{file/after}} \\[5pt] +% \>\>\emph{code from \cs{AtEndOfPackage} if +% used inside the package} \\[5pt] +% \>\cs{UseHook}\marg{\hook{package/after/\meta{package name}}} \\ +% \>\cs{UseHook}\marg{\hook{package/after}} +% \end{tabbing} +% and similar for class file loading, except that \texttt{package/} +% is replaced by \texttt{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! +% +% \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[\texttt{include/before}, \texttt{include/before/\meta{name}}] +% +% 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[\texttt{include/end/\meta{name}}, \texttt{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[\texttt{include/after/\meta{name}}, \texttt{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 +% +% \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.} +% +% \subsection{High-level interfaces for \LaTeX{}} +% +% We do not provide any high-level \LaTeX{} commands (like +% \pkg{filehook} or \pkg{scrlfile} do) but think that for package +% writers the commands from for hook management are sufficient. +% +% +% +% \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 +% implementating 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. +% +% \StopEventually{\setlength\IndexMin{200pt} \PrintIndex } +% +% +% \section{The Implementation} +% \begin{macrocode} +%<*2ekernel> +% \end{macrocode} +% +% \begin{macrocode} +%<@@=filehook> +% \end{macrocode} +% +% +% \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> +%<*2ekernel|latexrelease> +%<latexrelease>\IncludeInRelease{2020/10/01}% +%<latexrelease> {\CurrentFile}{Hook management file}% +\ExplSyntaxOn +\tl_new:N \CurrentFile +\tl_new:N \CurrentFilePath +\tl_new:N \CurrentFileUsed +\tl_new:N \CurrentFilePathUsed +\ExplSyntaxOff +%</2ekernel|latexrelease> +%<latexrelease>\EndIncludeInRelease +% \end{macrocode} +% +% \begin{macrocode} +%<latexrelease>\IncludeInRelease{0000/00/00}% +%<latexrelease> {\CurrentFile}{Hook management file}% +%<latexrelease> +%<latexrelease>\let \CurrentFile \@undefined +%<latexrelease>\let \CurrentFilePath \@undefined +%<latexrelease>\let \CurrentFileUsed \@undefined +%<latexrelease>\let \CurrentFilePathUsed \@undefined +%<latexrelease> +%<latexrelease>\EndIncludeInRelease +%<*2ekernel> +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% +% +% \subsection{\pkg{expl3} helpers} +% +% \begin{macrocode} +%</2ekernel> +%<*2ekernel|latexrelease> +%<latexrelease>\IncludeInRelease{2020/10/01}% +%<latexrelease> {\@@_file_parse_full_name:nN}{File helpers}% +\ExplSyntaxOn +% \end{macrocode} +% +% \begin{macro}{ +% \@@_file_parse_full_name:nN, +% \@@_full_name:nn, +% \@@_set_curr_file_assign:nnnNN +% } +% 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 normalisation 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. +% \begin{macrocode} +\tl_new:N \l_@@_internal_tl +\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 { hooks } { 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} +%</2ekernel|latexrelease> +%<latexrelease>\EndIncludeInRelease +%<*2ekernel> +% \end{macrocode} +% +% \begin{macrocode} +%<@@=> +% \end{macrocode} +% +% \subsection{Declaring the file-related hooks} +% +% All hooks starting with \texttt{file/} \texttt{include/}, +% \texttt{class/} or \texttt{package/} are generic and will be +% allocated if code is added to them. Thus there is no need to +% explicitly declare any hook in the code below. +% +% Furthermore, those named \texttt{.../after} or \texttt{.../end} are +% automatically declared as reversed hooks if filled with code, so this +% is also automatically taken care of. +% +% +% +% +% \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} +% \cs{InputIfFileExists} loads any file if it is available so we +% have to add the hooks \texttt{file/before} and +% \texttt{file/after} in the right places. If the file doesn't +% exist no hooks should be executed. +% \begin{macrocode} +%</2ekernel> +%<latexrelease>\IncludeInRelease{2020/10/01}% +%<latexrelease> {\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} +% 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 restored it for the +% \texttt{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}. +% \begin{macrocode} + \edef\reserved@a{\@filef@und + \@expl@@@filehook@file@pop@assign@@nnnn + {\CurrentFilePathUsed}% + {\CurrentFileUsed}% + {\CurrentFilePath}% + {\CurrentFile}}% + \expandafter\@swaptwoargs\expandafter + {\reserved@a}% + {% + #2% + \@addtofilelist{#1}% + \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/before/\CurrentFile}% + \@@input + }% +% \end{macrocode} +% And it is restored here so we can use it once more. +% \begin{macrocode} + \UseHook{file/after/\CurrentFile}% + \UseHook{file/after}% + \@expl@@@filehook@file@pop@@ + }% +} +%<latexrelease>\EndIncludeInRelease +%</2ekernel|latexrelease> +% \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} +%<latexrelease>\IncludeInRelease{2019/10/01}% +%<latexrelease> {\InputIfFileExists}{Hook management (files)}% +%<latexrelease> +%<latexrelease>\DeclareRobustCommand \InputIfFileExists[2]{% +%<latexrelease> \IfFileExists{#1}% +%<latexrelease> {% +%<latexrelease> \expandafter\@swaptwoargs\expandafter +%<latexrelease> {\@filef@und}{#2\@addtofilelist{#1}\@@input}}} +%<latexrelease>\EndIncludeInRelease +% \end{macrocode} +% +% \begin{macrocode} +%<latexrelease>\IncludeInRelease{0000/00/00}% +%<latexrelease> {\InputIfFileExists}{Hook management (files)}% +%<latexrelease>\long\def \InputIfFileExists#1#2{% +%<latexrelease> \IfFileExists{#1}% +%<latexrelease> {#2\@addtofilelist{#1}\@@input \@filef@und}} +%<latexrelease>\EndIncludeInRelease +%<*2ekernel> +% \end{macrocode} +% \end{macro} +% +% +% +% +% +% +% \subsection{Declaring a file substitution} +% +% \begin{macrocode} +%<@@=filehook> +% \end{macrocode} +% +% \begin{macrocode} +%</2ekernel> +%<*2ekernel|latexrelease> +%<latexrelease>\IncludeInRelease{2020/10/01}% +%<latexrelease> {\@@_subst_add:nn}{Declaring file substitution}% +\ExplSyntaxOn +% \end{macrocode} +% +% +% \begin{macro}{\@@_subst_add:nn,\@@_subst_remove:n, +% \@@_subst_file_normalize:n,\@@_subst_empty_name_chk:NN} +% \cs{@@_substitution_lthooadd: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:n {#1} } + { \@@_subst_file_normalize:n {#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:n {#1} } + \group_end: + } +\cs_new:Npn \@@_subst_file_normalize:n #1 + { + \exp_after:wN \@@_subst_empty_name_chk:NN + \cs:w \exp_after:wN \cs_end: + \cs:w \@@_file_parse_full_name:nN {#1} \use_ii_iii:nnn \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 +%</2ekernel|latexrelease> +%<latexrelease>\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> +%<*2ekernel|latexrelease> +%<latexrelease>\IncludeInRelease{2020/10/01}% +%<latexrelease> {\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 +%</2ekernel|latexrelease> +%<latexrelease>\EndIncludeInRelease +% \end{macrocode} +% +% \begin{macrocode} +%<latexrelease>\IncludeInRelease{0000/00/00}% +%<latexrelease> {\declare@file@substitution}{File substitution}% +%<latexrelease> +%<latexrelease>\let \declare@file@substitution \@undefined +%<latexrelease>\let \undeclare@file@substitution \@undefined +%<latexrelease> +%<latexrelease>\EndIncludeInRelease +%<*2ekernel> +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% +% +% +% \begin{macrocode} +%<@@=> +\ExplSyntaxOff +% \end{macrocode} +% +% \subsection{Selecting a file (\cs{set@curr@file})} +% +% \begin{macro}{\set@curr@file,\@curr@file,\@curr@file@reqd} +% Now we hook into \cs{set@curr@file} to resolve a possible file +% substitution, and add \cs{@expl@@@filehook@set@curr@file@@N} 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 normalisation 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. +% \begin{macrocode} +%</2ekernel> +%<*2ekernel|latexrelease> +%<latexrelease>\IncludeInRelease{2020/10/01}% +%<latexrelease> {\set@curr@file}{Setting current file name}% +\def\set@curr@file#1{% + \begingroup + \escapechar\m@ne + \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 normalisation 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}% + \endgroup} +%</2ekernel|latexrelease> +%<latexrelease>\EndIncludeInRelease +% \end{macrocode} +% +% \begin{macrocode} +%<latexrelease>\IncludeInRelease{2019/10/01}% +%<latexrelease> {\set@curr@file}{Setting current file name}% +%<latexrelease>\def\set@curr@file#1{% +%<latexrelease> \begingroup +%<latexrelease> \escapechar\m@ne +%<latexrelease> \xdef\@curr@file{% +%<latexrelease> \expandafter\expandafter\expandafter\unquote@name +%<latexrelease> \expandafter\expandafter\expandafter{% +%<latexrelease> \expandafter\string +%<latexrelease> \csname\@firstofone#1\@empty\endcsname}}% +%<latexrelease> \endgroup +%<latexrelease>} +%<latexrelease>\EndIncludeInRelease +% \end{macrocode} +% +% \begin{macrocode} +%<latexrelease>\IncludeInRelease{0000/00/00}% +%<latexrelease> {\set@curr@file}{Setting current file name}% +%<latexrelease>\let\set@curr@file\@undefined +%<latexrelease>\EndIncludeInRelease +%<*2ekernel> +% \end{macrocode} +% \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> +%<*2ekernel|latexrelease> +%<latexrelease>\IncludeInRelease{2020/10/01}% +%<latexrelease> {\@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. +% \begin{macrocode} +\def\@set@curr@file@aux#1{% + \expandafter\ifx\csname\endcsname#1% + .tex\else\string#1\fi} +% \end{macrocode} +% +% Then we call \cs{@expl@@@filehook@set@curr@file@@N} 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. +% \begin{macrocode} +\def\@filehook@set@CurrentFile{% + \@expl@@@filehook@set@curr@file@@N{\@curr@file}% + \CurrentFileUsed\CurrentFilePathUsed + \ifx\@curr@file@reqd\@curr@file + \let\CurrentFile\CurrentFileUsed + \let\CurrentFilePath\CurrentFilePathUsed + \else + \@expl@@@filehook@set@curr@file@@N{\@curr@file@reqd}% + \CurrentFile\CurrentFilePath + \fi} +%</2ekernel|latexrelease> +%<latexrelease>\EndIncludeInRelease +%<*2ekernel> +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% +% +% \begin{macro}{\@@_set_curr_file:N, +% \@@_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> +%<*2ekernel|latexrelease> +%<latexrelease>\IncludeInRelease{2020/10/01}% +%<latexrelease> {@@_set_curr_file:N}{Set curr file}% +\ExplSyntaxOn +%<@@=filehook> +\cs_new_protected:Npn \@@_set_curr_file:N #1 + { \exp_args:NV \@@_set_curr_file:nNN #1 } +% \end{macrocode} +% +% \begin{macrocode} +\cs_new_protected:Npn \@@_set_curr_file:nNN #1 + { + \@@_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 +%</2ekernel|latexrelease> +%<latexrelease>\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 sanitising 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> +%<*2ekernel|latexrelease> +%<latexrelease>\IncludeInRelease{2020/10/01}% +%<latexrelease> {\@@_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} +% 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 +% optimising). +% \begin{macrocode} +\flag_new:n { @@_file_replaced } +\cs_new:Npn \@@_if_file_replaced:TF #1 #2 + { \flag_if_raised:nTF { @@_file_replaced } {#1} {#2} } +% \end{macrocode} +% \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 +%</2ekernel|latexrelease> +%<latexrelease>\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 substution of that +% substution: 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> +%<*2ekernel|latexrelease> +%<latexrelease>\IncludeInRelease{2020/10/01}% +%<latexrelease> {\@@_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, +% } +% 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 + { + \__kernel_msg_expandable_error:nnff { kernel } { 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} +\__kernel_msg_new:nnn { kernel } { file-cycle } + { File~loop!~#1~replaced~by~#2... } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macrocode} +\ExplSyntaxOff +%</2ekernel|latexrelease> +%<latexrelease>\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> +%<*2ekernel|latexrelease> +%<latexrelease>\IncludeInRelease{2020/10/01}% +%<latexrelease> {\disable@package@load}{Disable packages}% +\def\disable@package@load#1#2{% + \global\@namedef{@pkg-disable@#1.\@pkgextension}{#2}} +% \end{macrocode} +% +% \begin{macrocode} +\def\@disable@packageload@do#1#2{% + \@ifundefined{@pkg-disable@#1}{#2}% + {\@nameuse{@pkg-disable@#1}}} +% \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} +%</2ekernel|latexrelease> +%<latexrelease>\EndIncludeInRelease +%<latexrelease>\IncludeInRelease{0000/00/00}% +%<latexrelease> {\disable@package@load}{Disable packages}% +%<latexrelease> +%<latexrelease>\let\disable@package@load \@undefined +%<latexrelease>\let\@disable@packageload@do\@undefined +%<latexrelease>\let\reenable@package@load \@undefined +%<latexrelease>\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} +% +% \begin{macrocode} +%</2ekernel> +%<*2ekernel|latexrelease> +%<latexrelease>\IncludeInRelease{2020/10/01}% +%<latexrelease> {\@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@@N + \__filehook_set_curr_file:N +% \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@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} +% +% +% \begin{macrocode} +\ExplSyntaxOff +% \end{macrocode} +% +% \begin{macrocode} +%</2ekernel|latexrelease> +%<latexrelease>\EndIncludeInRelease +%<*2ekernel> +% \end{macrocode} +% +% This ends the kernel code in this file. +% \begin{macrocode} +%</2ekernel> +% \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 decresing 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} +%<@@=> +%</structuredlog> +% \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} +%</2ekernel> +% \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 atvery 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} +%</atveryend-ltx> +% \end{macrocode} +% +% +% +% \Finale +% +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\endinput +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% + |