summaryrefslogtreecommitdiff
path: root/macros/latex-dev/base/ltfilehook.dtx
diff options
context:
space:
mode:
Diffstat (limited to 'macros/latex-dev/base/ltfilehook.dtx')
-rw-r--r--macros/latex-dev/base/ltfilehook.dtx1538
1 files changed, 1049 insertions, 489 deletions
diff --git a/macros/latex-dev/base/ltfilehook.dtx b/macros/latex-dev/base/ltfilehook.dtx
index 6c16a9832e..73018c682d 100644
--- a/macros/latex-dev/base/ltfilehook.dtx
+++ b/macros/latex-dev/base/ltfilehook.dtx
@@ -1,30 +1,42 @@
% \iffalse meta-comment
+%%
+%% File: ltfilehook.dtx (C) Copyright 2020 Frank Mittelbach,
+%% Phelype Oleinik & LaTeX Team
%
-%% File: ltfilehook.dtx (C) Copyright 2020 Frank Mittelbach, LaTeX Team
-%
-% It may be distributed and/or modified under the conditions of the
-% LaTeX Project Public License (LPPL), either version 1.3c of this
-% license or (at your option) any later version. The latest version
-% of this license is in the file
+% 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 development version of the bundle can be found below
+% 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
%
-% https://github.com/FrankMittelbach/...
+% \iffalse
%
-% for those people who are interested or want to report an issue.
+%%% From File: ltshipout.dtx
%
% \begin{macrocode}
-\providecommand\ltfilehookversion{v0.9a}
-\providecommand\ltfilehookdate{2020/07/19}
+\providecommand\ltfilehookversion{v1.0a}
+\providecommand\ltfilehookdate{2020/08/30}
% \end{macrocode}
%
%<*driver>
-\RequirePackage{lthooks} % needed for structedlog if running old format
-
\documentclass{l3doc}
% bug fix fo l3doc.cls
@@ -107,7 +119,7 @@
% \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 is used
+% 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.
@@ -119,8 +131,8 @@
% 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 add code to both
+% 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}
@@ -139,16 +151,13 @@
% \>\cs{UseHook}\marg{\hook{file/after}}
% \end{tabbing}
%
-% \fmi{With a higher-level interface that doesn't matter, but it
-% is a bit weird, if you use \cs{AddToHook} or \cs{hook_gput:nnn}
-% directly, so I guess that has to be done differently!}
-%
% 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, for hooks the \texttt{.tex}
-% extension is not assumed, so \texttt{.tex} files must also be given
-% with their extension.
+% 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.
%
@@ -185,6 +194,18 @@
% 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},
@@ -284,7 +305,7 @@
%
% \item[\texttt{include/before}, \texttt{include/before/\meta{name}}]
%
-% These hooks are executed one after another after the initial
+% 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
@@ -322,6 +343,58 @@
% \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
@@ -378,91 +451,176 @@
%<*2ekernel>
% \end{macrocode}
%
-% \subsection{\pkg{expl3} helpers}
-%
% \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}).
+% 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}
-\def\CurrentFile{}
-\def\CurrentFilePath{}
+%<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{macro}{\l_@@_internal_tl}
-% \begin{macro}{\@filehook@set@curr@file,\@@_normalise_file_name:n}
-% When inputting a file, \cs{@filehook@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{CurrentFile}. 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.
-% \cs{CurrentFile} is then used to run the file hooks with
-% \texttt{file/before/\cs{CurrentFile}} and
-% \texttt{file/after/\cs{CurrentFile}}.
% \begin{macrocode}
+%</2ekernel>
+%<*2ekernel|latexrelease>
+%<latexrelease>\IncludeInRelease{2020/10/01}%
+%<latexrelease> {\@@_file_parse_full_name:nN}{File helpers}%
\ExplSyntaxOn
-\tl_new:N \l_@@_internal_tl
-\cs_new_protected:Npn \@filehook@set@curr@file #1
- { \exp_args:NV \@@_normalise_file_name:n #1 }
-\cs_new_protected:Npn \@@_normalise_file_name:n #1
+% \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
{
- \file_if_exist:nTF {#1}
+ \exp_args:Nf \file_parse_full_name_apply:nN
{
- \exp_args:Nx \file_parse_full_name:nNNN
- { \file_full_name:n {#1} }
+ \exp_args:Nf \@@_full_name:nn
+ { \file_full_name:n {#1} } {#1}
}
- { \file_parse_full_name:nNNN {#1} }
- \CurrentFilePath \CurrentFile \l_@@_internal_tl
- \tl_set:Nx \CurrentFile { \CurrentFile \l_@@_internal_tl }
+ }
+\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}
-% \begin{macro}{\@filehook@file@push,\@filehook@file@pop}
-% \begin{macro}{\@@_file_pop_assign:nn}
+% \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.
-% \cs{IfFileExists} does \cs{set@curr@file} internally, which changes
-% \cs{CurrentFile}, so \cs{@filehook@file@push} has to be executed
-% before \cs{IfFileExists}.
+% 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 \@filehook@file@push
+\cs_new_protected:Npn \@@_file_push:
{
\seq_gpush:Nx \g_@@_input_file_seq
- { { \CurrentFilePath } { \CurrentFile } }
+ {
+ { \CurrentFilePathUsed } { \CurrentFileUsed }
+ { \CurrentFilePath } { \CurrentFile }
+ }
}
-\cs_new_protected:Npn \@filehook@file@pop
+\cs_new_protected:Npn \@@_file_pop:
{
\seq_gpop:NNTF \g_@@_input_file_seq \l_@@_internal_tl
- { \exp_after:wN \@@_file_pop_assign:nn \l_@@_internal_tl }
- { \ERROR_should_not_happen }
+ { \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:nn #1 #2
+\cs_new_protected:Npn \@@_file_pop_assign:nnnn #1 #2 #3 #4
{
- \tl_set:Nn \CurrentFilePath {#1}
- \tl_set:Nn \CurrentFile {#2}
+ \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}
%
@@ -475,13 +633,14 @@
% automatically declared as reversed hooks if filled with code, so this
% is also automatically taken care of.
%
-% \subsection{Patching \LaTeX{} commands (need proper integration later)}
%
-% Most of what we have to do is adding \cs{UseHook} into several
-% \LaTeXe{} core commands, which is done for now by patching them.
-% \begin{macrocode}
-%<@@=>
-% \end{macrocode}
+%
+%
+% \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
@@ -489,21 +648,34 @@
% \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[3]{%
- \@filehook@file@push
+\DeclareRobustCommand \InputIfFileExists[2]{%
\IfFileExists{#1}%
{%
+ \@expl@@@filehook@file@push@@
+ \@filehook@set@CurrentFile
% \end{macrocode}
-% If the file exists then \cs{@curr@file} holds its name. But we
+% 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 \emph{actual} file that will be
-% operated on, regardless of how the user had it written in the
-% document. \pkg{expl3}'s \cs{file_full_name:n} normalizes the file
+% 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
@@ -512,9 +684,11 @@
% returned in \cs{CurrentFilePath}.
% \begin{macrocode}
\edef\reserved@a{\@filef@und
- \def\noexpand\CurrentFile{\CurrentFile}%
- \def\noexpand\CurrentFilePath{\CurrentFilePath}%
- }%
+ \@expl@@@filehook@file@pop@assign@@nnnn
+ {\CurrentFilePathUsed}%
+ {\CurrentFileUsed}%
+ {\CurrentFilePath}%
+ {\CurrentFile}}%
\expandafter\@swaptwoargs\expandafter
{\reserved@a}%
{%
@@ -522,7 +696,7 @@
\@addtofilelist{#1}%
\UseHook{file/before}%
% \end{macrocode}
-% The current file name is available in \cs{@curr@file} so we use
+% The current file name is available in \cs{CurrentFile} so we use
% that in the specific hook.
% \begin{macrocode}
\UseHook{file/before/\CurrentFile}%
@@ -533,356 +707,592 @@
% \begin{macrocode}
\UseHook{file/after/\CurrentFile}%
\UseHook{file/after}%
- \@filehook@file@pop
+ \@expl@@@filehook@file@pop@@
}%
- {\@filehook@file@pop #3}%
}
+%<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{macro}{\set@curr@file}
-% Now we just hook into \cs{set@curr@file} to add
-% \cs{@filehook@set@curr@file} at the end, after \cs{@curr@file} is
-% set.
+%
+%
+%
% \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
- \xdef\@curr@file{%
- \expandafter\expandafter\expandafter\unquote@name
- \expandafter\expandafter\expandafter{%
- \expandafter\string
- \csname\@firstofone#1\@empty\endcsname}}%
- \endgroup
- \@filehook@set@curr@file{\@curr@file}%
-}
+ \expandafter\def\csname\expandafter\endcsname
+ \expandafter{\csname\endcsname}%
% \end{macrocode}
-% \end{macro}
+% 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}{\load@onefilewithoptions}
-% This macro is used when loading packages or classes.
-% \begin{macrocode}
-\def\load@onefilewithoptions#1[#2][#3]#4{%
- \@pushfilename
- \xdef\@currname{#1}%
- \global\let\@currext#4%
- \let\CurrentOption\@empty
- \@reset@ptions
- \makeatletter
- \def\reserved@a{%
- \@ifl@aded\@currext{#1}%
- {\@if@ptions\@currext{#1}{#2}{}%
- {\@latex@error
- {Option clash for \@cls@pkg\space #1}%
- {The package #1 has already been loaded
- with options:\MessageBreak
- \space\space[\@ptionlist{#1.\@currext}]\MessageBreak
- There has now been an attempt to load it
- with options\MessageBreak
- \space\space[#2]\MessageBreak
- Adding the global options:\MessageBreak
- \space\space
- \@ptionlist{#1.\@currext},#2\MessageBreak
- to your \noexpand\documentclass declaration may fix this.%
- \MessageBreak
- Try typing \space <return> \space to proceed.}}}%
- {\@pass@ptions\@currext{#2}{#1}%
- \global\expandafter
- \let\csname ver@\@currname.\@currext\endcsname\@empty
- \expandafter\let\csname\@currname.\@currext-h@@k\endcsname\@empty
- \InputIfFileExists
- {\@currname.\@currext}%
- {%
-%-----------------------------------------
-% \end{macrocode}
-% When the current extension is \cs{@pkgextension} we are loading a
-% package otherwise, if it is \cs{@clsextension}, a class, so
-% depending on that we execute different hooks. If the extension is
-% neither, then it is another type of file without special hooks.
-% \begin{macrocode}
- \ifx\@currext\@pkgextension
- \UseHook{package/before}%
- \UseHook{package/before/\@currname}%
- \else
- \ifx\@currext\@clsextension
- \UseHook{class/before}%
- \UseHook{class/before/\@currname}%
- \fi
- \fi
-% \end{macrocode}
-% The value of \cs{CurrentFile} holds during \cs{InputIfFileExists},
-% so the hooks above have that available for using. However at this
-% point \cs{CurrentFile} is reset to its previous value by
-% \cs{@filehook@file@pop} because it doesn't know that we'll have
-% more hooks ahead. So here (still in the \meta{true} branch of
-% \cs{InputIfFileExists}, right after actually reading the file in)
-% we'll cheat: use \cs{@filehook@file@push} once more, so there are
-% two entries for the current file in the name stack, so that when
-% \cs{InputIfFileExists} pops it, there's still one (identical) left.
-% \begin{macrocode}
- \@filehook@file@push
-%-----------------------------------------
- }%
- {\@missingfileerror\@currname\@currext}%
- \let\@unprocessedoptions\@@unprocessedoptions
- \csname\@currname.\@currext-h@@k\endcsname
- \expandafter\let\csname\@currname.\@currext-h@@k\endcsname
- \@undefined
-%-----------------------------------------
-% \end{macrocode}
-% And same procedure, James, when we are finished loading, except
-% that the hook order is now reversed.
-% \begin{macrocode}
- \ifx\@currext\@pkgextension
- \UseHook{package/after/\@currname}%
- \UseHook{package/after}%
- \else
- \ifx\@currext\@clsextension
- \UseHook{class/after/\@currname}%
- \UseHook{class/after}%
- \fi
- \fi
-% \end{macrocode}
-% Now here we do \cs{@filehook@file@pop} to restore the
-% \cs{CurrentFile} before this file being loaded and fix what we've
-% done in the stack right above.
-% \begin{macrocode}
- \@filehook@file@pop
-%-----------------------------------------
- \@unprocessedoptions}%
- \@ifl@ter\@currext{#1}{#3}{}%
- {\@latex@warning@no@line
- {You have requested,\on@line,
- version\MessageBreak
- `#3' of \@cls@pkg\space #1,\MessageBreak
- but only version\MessageBreak
- `\csname ver@#1.\@currext\endcsname'\MessageBreak
- is available}}%
- \ifx\@currext\@clsextension\let\LoadClass\@twoloadclasserror\fi
- \@popfilename
- \@reset@ptions}%
- \reserved@a}
-% \end{macrocode}
-%
-% The code for this macro has changed between 2020/02/02 and
-% 2020/10/01 so the never version is this:
-% \begin{macrocode}
-\@ifl@t@r\fmtversion{2020/10/01}
-{%
-\def\load@onefilewithoptions#1[#2][#3]#4{%
- \@pushfilename
- \xdef\@currname{#1}%
- \global\let\@currext#4%
- \let\CurrentOption\@empty
- \@reset@ptions
- \makeatletter
- \def\reserved@a{%
- \@ifl@aded\@currext{#1}%
- {\@if@ptions\@currext{#1}{#2}{}%
- {\@latex@error
- {Option clash for \@cls@pkg\space #1}%
- {The package #1 has already been loaded
- with options:\MessageBreak
- \space\space[\@ptionlist{#1.\@currext}]\MessageBreak
- There has now been an attempt to load it
- with options\MessageBreak
- \space\space[#2]\MessageBreak
- Adding the global options:\MessageBreak
- \space\space
- \@ptionlist{#1.\@currext},#2\MessageBreak
- to your \noexpand\documentclass declaration may fix this.%
- \MessageBreak
- Try typing \space <return> \space to proceed.}}}%
- {\@pass@ptions\@currext{#2}{#1}%
- \global\expandafter
- \let\csname ver@\@currname.\@currext\endcsname\@empty
- \expandafter\let\csname\@currname.\@currext-h@@k\endcsname\@empty
- \InputIfFileExists
- {\@currname.\@currext}%
- {%
-%-----------------------------------------
- \ifx\@currext\@pkgextension
- \UseHook{package/before}%
- \UseHook{package/before/\@currname}%
- \else
- \ifx\@currext\@clsextension
- \UseHook{class/before}%
- \UseHook{class/before/\@currname}%
- \fi
- \fi
- \@filehook@file@push
-%-----------------------------------------
- }%
- {\@missingfileerror\@currname\@currext}%
- \expandafter\let\csname unprocessedoptions-\@currname.\@currext\endcsname
- \@@unprocessedoptions
- \csname\@currname.\@currext-h@@k\endcsname
- \expandafter\let\csname\@currname.\@currext-h@@k\endcsname
- \@undefined
- \ifx\@unprocessedoptions\relax
- \let\@unprocessedoptions\@undefined
- \else
- \csname unprocessedoptions-\@currname.\@currext\endcsname
- \fi
- \expandafter\let
- \csname unprocessedoptions-\@currname.\@currext\endcsname
- \@undefined
-%-----------------------------------------
- \ifx\@currext\@pkgextension
- \UseHook{package/after/\@currname}%
- \UseHook{package/after}%
- \else
- \ifx\@currext\@clsextension
- \UseHook{class/after/\@currname}%
- \UseHook{class/after}%
- \fi
- \fi
- \@filehook@file@pop
-%-----------------------------------------
- }%
- \@ifl@ter\@currext{#1}{#3}{}%
- {\@latex@warning@no@line
- {You have requested,\on@line,
- version\MessageBreak
- `#3' of \@cls@pkg\space #1,\MessageBreak
- but only version\MessageBreak
- `\csname ver@#1.\@currext\endcsname'\MessageBreak
- is available}}%
- \ifx\@currext\@clsextension\let\LoadClass\@twoloadclasserror\fi
- \@popfilename
- \@reset@ptions}%
- \reserved@a}
-}{}%
+%
+%
+%
+% \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}
-% \end{macro}
-
-
-
-
-% \begin{macro}{\@include}
-% \begin{macrocode}
-\def\@include#1 {%
- \clearpage
- \if@filesw
- \immediate\write\@mainaux{\string\@input{#1.aux}}%
- \fi
- \@tempswatrue
- \if@partsw
- \@tempswafalse
- \edef\reserved@b{#1}%
- \@for\reserved@a:=\@partlist\do
- {\ifx\reserved@a\reserved@b\@tempswatrue\fi}%
- \fi
- \if@tempswa
- \let\@auxout\@partaux
- \if@filesw
- \immediate\openout\@partaux #1.aux
- \immediate\write\@partaux{\relax}%
- \fi
-%-----------------------------------------
-% \end{macrocode}
-% First we need to fix \cs{CurrentFile}, because due the pesky
-% space-delimited \cs{@include}, \cs{CurrentFile} contains the
-% \cs{include}'d file with a space. To fix that, we re-do
-% \cs{set@curr@file}. The 2020/10/01 release doesn't need this as
-% \cs{include} was changed to do \cs{set@curr@file} on the correct
-% file name, rather than one with a trailing space.
-% \begin{macrocode}
- \set@curr@file{#1}%
-% \end{macrocode}
-% Execute the \texttt{before} hooks just after we switched the
-% \texttt{.aux} file \ldots
-% \begin{macrocode}
- \UseHook{include/before}%
- \UseHook{include/before/#1}%
-%-----------------------------------------
- \@input@{#1.tex}%
-% \end{macrocode}
-% \ldots{} then \texttt{end} hooks \ldots
-% \begin{macrocode}
-%-----------------------------------------
- \UseHook{include/end/#1}%
- \UseHook{include/end}%
-%-----------------------------------------
- \clearpage
-% \end{macrocode}
-% \ldots{} and after the \cs{clearpage} the \texttt{after} hooks
-% followed by another \cs{clearpage} just in case new material got
-% added (after all we need to be in well defined state after the
-% \cs{include}).
-% \begin{macrocode}
-%-----------------------------------------
- \UseHook{include/after/#1}%
- \UseHook{include/after}%
-% \end{macrocode}
-% The additional \cs{clearpage} is needed to ensure that switching
-% the \texttt{.aux} files happen at a defined point even if the
-% above hooks add further material.
-% \begin{macrocode}
- \clearpage
-%-----------------------------------------
- \@writeckpt{#1}%
- \if@filesw
- \immediate\closeout\@partaux
- \fi
+%
+% \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
- \deadcycles\z@
- \@nameuse{cp@#1}%
- \fi
- \let\@auxout\@mainaux
-}
+ \@expl@@@filehook@set@curr@file@@N{\@curr@file@reqd}%
+ \CurrentFile\CurrentFilePath
+ \fi}
+%</2ekernel|latexrelease>
+%<latexrelease>\EndIncludeInRelease
+%<*2ekernel>
% \end{macrocode}
+% \end{macro}
+% \end{macro}
%
-% The code for this macro has changed between 2020/02/02 and
-% 2020/10/01 so the never version is this:
-% \begin{macrocode}
-\@ifl@t@r\fmtversion{2020/10/01}
-{%
-\def\@include#1 {%
- \clearpage
- \if@filesw
- \immediate\write\@mainaux{\string\@input{"#1.aux"}}%
- \fi
- \@tempswatrue
- \if@partsw
- \@tempswafalse
- \edef\reserved@b{#1}%
- \@for\reserved@a:=\@partlist\do
- {\ifx\reserved@a\reserved@b\@tempswatrue\fi}%
- \fi
- \if@tempswa
- \let\@auxout\@partaux
- \if@filesw
- \immediate\openout\@partaux "#1.aux"
- \immediate\write\@partaux{\relax}%
- \fi
-%-----------------------------------------
- \UseHook{include/before}%
- \UseHook{include/before/#1}%
-%-----------------------------------------
- \@input@{#1.tex}%
-%-----------------------------------------
- \UseHook{include/end/#1}%
- \UseHook{include/end}%
-%-----------------------------------------
- \clearpage
-%-----------------------------------------
- \UseHook{include/after/#1}%
- \UseHook{include/after}%
- \clearpage
-%-----------------------------------------
- \@writeckpt{#1}%
- \if@filesw
- \immediate\closeout\@partaux
- \fi
- \else
- \deadcycles\z@
- \@nameuse{cp@#1}%
- \fi
- \let\@auxout\@mainaux}
-}{}
+%
+%
+% \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}
-% \end{macro}
+%
+% \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}
+%
+%
%
%
%
@@ -895,13 +1305,231 @@
%
%
%
+% \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{Package emulation for compatibility}
+% \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 \space 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}
+ [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}
%
%
% \subsection{Package \pkg{filehook} emulation}
@@ -1083,79 +1711,11 @@
%
%
%
-% \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{macrocode}
-\int_new:N \g_@@_nesting_level_int
-% \end{macrocode}
-
-% \begin{macrocode}
-\tl_new:N \g_@@_nesting_prefix_tl
-\tl_gset:Nn \g_@@_nesting_prefix_tl { }
-% \end{macrocode}
-%
-% \begin{macrocode}
-\AddToHook{file/before}{
- \int_gincr:N \g_@@_nesting_level_int
- \tl_gput_right:Nn\g_@@_nesting_prefix_tl {=}
- \iow_term:x {
- \g_@@_nesting_prefix_tl \space
- ( LEVEL~ \int_use:N \g_@@_nesting_level_int \space START )~
- \CurrentFile ^^J
- }
-}
-% \end{macrocode}
-% 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 the package.
-% \begin{macrocode}
-\AddToHookNext{file/after}{
- \AddToHook{file/after}{
- \iow_term:x {
- \g_@@_nesting_prefix_tl \space
- ( LEVEL~ \int_use:N \g_@@_nesting_level_int \space STOP )~
- \CurrentFile ^^J
- }
- \int_gdecr:N \g_@@_nesting_level_int
- \tl_gset:Nx \g_@@_nesting_prefix_tl
- {\exp_after:wN \use_none:n \g_@@_nesting_prefix_tl}
- }
-}
-% \end{macrocode}
-%
-% We have to manually increment the level because now that we have
-% installed the code in \texttt{file/after} it gets decremented
-% when we leave the package without ever being incremented upon
-% entry.
-% \begin{macrocode}
-%\int_incr:N\g_@@_nesting_level_int
-% \end{macrocode}
-%
-% \begin{macrocode}
-%</structuredlog>
-% \end{macrocode}
-
% \Finale
%
-
-
+%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\endinput
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
+%