% \iffalse^^A meta-comment % ====================================================================== % scrlfile-hook.dtx % Copyright (c) Markus Kohm, 2021 % % This file is part of the work `scrlfile' which is part of the LaTeX2e % KOMA-Script bundle. % % This work may be distributed and/or modified under the conditions of % the LaTeX Project Public License, version 1.3c of the license. % The latest version of this license is in % http://www.latex-project.org/lppl.txt % and version 1.3c or later is part of all distributions of LaTeX % version 2005/12/01 or later and of this work. % % This work has the LPPL maintenance status "author-maintained". % % The Current Maintainer and author of this work is Markus Kohm. % % This work consists of all files listed in manifest.txt. % ---------------------------------------------------------------------- % scrlfile-hook.dtx % Copyright (c) Markus Kohm, 2021 % % Diese Datei ist Teil des Werks `scrlfile', das wiederum Teil des % LaTeX2e KOMA-Script Pakets ist. % % Dieses Werk darf nach den Bedingungen der LaTeX Project Public Lizenz, % Version 1.3c, verteilt und/oder veraendert werden. % Die neuste Version dieser Lizenz ist % http://www.latex-project.org/lppl.txt % und Version 1.3c ist Teil aller Verteilungen von LaTeX % Version 2005/12/01 oder spaeter und dieses Werks. % % Dieses Werk hat den LPPL-Verwaltungs-Status "author-maintained" % (allein durch den Autor verwaltet). % % Der Aktuelle Verwalter und Autor dieses Werkes ist Markus Kohm. % % Dieses Werk besteht aus den in manifest.txt aufgeführten Dateien. % ====================================================================== % %%% From File: $Id: scrlfile-hook.dtx$ %<*dtx> \ifx\ProvidesFile\undefined\def\ProvidesFile#1[#2]{}\fi \begingroup \def\filedate$#1: #2-#3-#4 #5${\gdef\filedate{#2/#3/#4}} \filedate$Date: 2021-04-21 12:11:38 +0200 (Wed, 21 Apr 2021) $ \def\filerevision$#1: #2 ${\gdef\filerevision{r#2}} \filerevision$Revision: 3593 $ \edef\reserved@a{% \noexpand\endgroup \noexpand\ProvidesFile{scrlfile-hook.dtx}% [\filedate\space\filerevision\space KOMA-Script package source }% \reserved@a % %\ProvidesPackage{scrlfile-hook}[% %!KOMAScriptVersion % package (using LaTeX hooks)] %<*dtx> \ifx\documentclass\undefined \input scrdocstrip.tex \@@input scrkernel-version.dtx \@@input scrstrip.inc \KOMAdefVariable{COPYRIGHTFROM}{2002} \generate{\usepreamble\defaultpreamble \file{scrlfile-hook.sty}{% \from{scrlfile-hook.dtx}{package}% }% }% \@@input scrstrop.inc \else \let\endbatchfile\relax \fi \endbatchfile \documentclass{l3doc} \usepackage[english]{babel} \DeclareRobustCommand{\KOMAScript}{\textsf{K\kern.05em O\kern.05em% M\kern.05em A\kern.1em-\kern.1em Script}} \CodelineIndex \GetFileInfo{scrlfile-hook.dtx} \title{\KOMAScript{} \partname\ \texttt{\filename}% \thanks{This file is revision \fileversion\ of file \texttt{\filename}.}} \date{\filedate} \author{Markus Kohm\thanks{mailto:komascript@gmx.info}} \begin{document} \maketitle \begin{abstract} This package provides hooks before and after loading files, packages or classes. It also provides a hook after the last \cs{clearpage} of the document. It allowes to replace files, packages and classes by other files, packages and classes. It is inteded to be used by package and class authors but may also be used by \LaTeX{} users. \end{abstract} \tableofcontents \DocInput{\filename} \end{document} % % \fi^^A meta-comment % % \changes{v3.32}{2020/08/25}{new (sub-)package} %^^A TODO: We should use docstrip3 instead of of docstrip. This would enable %^^A usability of @@-syntax in the source. But for this, additional %^^A changes would be needed to setup the version string. Because of %^^A this it should be done together with switch over to l3build. % % \section{The User Manual of \textsf{scrlfile-hook}} % % \textsf{scrlfile-hook} implements the \LaTeX-hook-based part of % \textsf{scrlfile}. % % There isn't any user manual for the user level \LaTeXe{} commands in this % file. Please see the manual of \textsf{scrlfile} for more information about % \textsf{scrlfile-hook}. % % This section, however, contains the user manual of the \LaTeX3{} package % author commands. % % \begin{function}[TF, added = 2020-08-26] % {\scrlfile_if_class_loaded:n,\scrlfile_if_package_loaded:n} % \begin{syntax} % \cs{scrlfile_if_class_loaded:nTF} \Arg{class name} \Arg{true code} \Arg{false code} % \cs{scrlfile_if_package_loaded:nTF} \Arg{package name} \Arg{true code} \Arg{false code} % \end{syntax} % Tests if the class \meta{class name} resp. the package \meta{package name} % has been loaded completely. It runs the \Arg{true code} only, if the input % of the class file with the name \meta{class % name}\texttt{.}\cs{@clsextension} resp. the package file with the name % \meta{package name}\texttt{.}\cs{@pkgextension} has already been % finished. It runs the \Arg{false code}, if the class or package has not % been loaded or the input of the class or package file is still in % progress. % \end{function} % % % \StopEventually{\PrintIndex} % % \section{The Implementation of \textsf{scrlfile-hook}} % % \begin{macrocode} %<@@=scrlfile> % \end{macrocode} % % Test whether the used \LaTeX{} provides all commands we need. % \changes{v3.34}{2021/04/21}{make it more robust agains not recommended % direct usage} % \begin{macrocode} \@ifundefined{IfFormatAtLeastTF}{% \PackageError{scrlfile-hook}{not recommended usage of package}{% It seems this package has been loaded directly using a LaTeX version\MessageBreak prior to 2020-10-01. This is not recommend. Please always load package\MessageBreak scrlfile instead of scrlfile-hook.\MessageBreak If you would continue, I will try to load scrlfile-patcholdlatex% }% \RequirePackage{scrlfile-patcholdlatex}% \endinput }{% \IfFormatAtLeastTF{2020/10/01}{}{% \PackageError{scrlfile-hook}{LaTeX too old for this package}{% It seems this package has been loaded directly using a LaTeX version\MessageBreak prior to 2020-10-01. This is not recommend. Please always load package\MessageBreak scrlfile instead of scrlfile-hook.\MessageBreak If you would continue, I will try to load scrlfile-patcholdlatex% }% \RequirePackage{scrlfile-patcholdlatex}% \endinput }{}% } % \end{macrocode} % % % \subsection{Before and After Commands} % % The hook implementation is based on a \LaTeX{} version that provides % \LaTeX3. So it makes sense to use it. % % \begin{macrocode} \ExplSyntaxOn % \end{macrocode} % % \begin{macro}{\BeforeFile} % The hook version of this command is just a wrapper to the corresponding % \LaTeX{} file hooks. It supports a mandatory \meta{file} argument, an % optional \meta{label} argument and a mandatory \meta{hook code} argument. % \begin{macrocode} \NewDocumentCommand \BeforeFile { m } { \AddToHook { file / before / #1 } } % \end{macrocode} % \end{macro} % % \begin{macro}{\AfterFile} % The hook version of this command is just a wrapper to the corresponding % \LaTeX{} file hooks. It supports a mandatory \meta{file} argument, an % optional \meta{label} argument and a mandatory \meta{hook code} argument. % \begin{macrocode} \NewDocumentCommand \AfterFile { m } { \AddToHook { file / after / #1 } } % \end{macrocode} % \end{macro} % % \begin{macro}{\BeforeClass,\BeforePackage} % The hook version of these commands are also wrappers to the % \texttt{file/before} hooks, because the \meta{code} should also be executed % already in the class or package context. % \begin{macrocode} \NewDocumentCommand \BeforeClass { m } { \BeforeFile { #1.\@clsextension } } \NewDocumentCommand \BeforePackage { m } { \BeforeFile { #1.\@pkgextension } } % \end{macrocode} % \end{macro} % % \begin{macro}{\AfterAtEndOfClass,\AfterAtEndOfPackage} % With version 3.32 the syntax of these commands have been changed. Now, there % is also a star variant, that runs the \meta{code} immediately if the class % or package has already been loaded completely. Otherwise and in the normal % variant the \meta{code} is added to the \texttt{class/after} or % \texttt{package/after} hook, because this hook is used outside the context % of the class or package and after the \cs{AtEndOfClass} or % \cs{AtEndOfPackage} code. % \begin{macrocode} \NewDocumentCommand \AfterAtEndOfClass { s m o +m } { \IfBooleanTF { #1 } { \scrlfile_if_class_loaded:nTF { #2 } { #4 } { \hook_gput_code:nnn { class / after / #2 } { #3 } { #4 } } } { \hook_gput_code:nnn { class / after / #2 } { #3 } { #4 } } } \NewDocumentCommand \AfterAtEndOfPackage { s m o +m } { \IfBooleanTF { #1 } { \scrlfile_if_package_loaded:nTF { #2 } { #4 } { \hook_gput_code:nnn { package / after / #2 } { #3 } { #4 } } } { \hook_gput_code:nnn { package / after / #2} { #3 } { #4 } } } % \end{macrocode} % \end{macro} % % \begin{variable}[added = 2020-06-26, % updated = 2021-01-01]{\g__scrlfile_input_file_seq} % \changes{3.33}{2021/01/01}{message definition moved outside function} % \cs{g__scrlfile_input_file_seq} is a sequence of active file inputs (without % path information). Two global hooks are used to setup the sequence. The new % conditional tests if a file is in the sequence. And a message is defined for % the case of more pops than pushs to the stack. % \begin{macrocode} \seq_new:N \g__scrlfile_input_file_seq \hook_gput_code:nnn { file / before } { . } { \seq_gpush:Nx \g__scrlfile_input_file_seq { \CurrentFile } } \msg_new:nnn { scrlfile-hook } { too-many-pops } { More~file~names~popped~from~stack~than~put~to.~ This~should~never~happen.~ However,~it~could~happen~if~scrlfile-hook~is~loaded~by~another~ package~or~class.~In~this~case~some~packages~or~classes~are~not~ recognised~correctly. } \hook_gput_code:nnn { file / after } { . } { \seq_gpop:NNF \g__scrlfile_input_file_seq \l_tmpa_seq { \msg_warning:nn { scrlfile-hook } { too-many-pops } } } % \end{macrocode} % Unfortunately we need an ugly hack to initialise the stack using an internal % kernel variable. This is a no go but I do not know a better solution for % this, because loading of the package could be done late. % TODO: Decide, if the second or fourth token is correct. If fourth, % \cs{CurrentFile} has to be used always instead of % \cs{CurrentFileUsed}. % \begin{macrocode} \cs_if_exist:NTF \g__filehook_input_file_seq { \seq_map_inline:Nn \g__filehook_input_file_seq { \seq_gput_right:Nx \g__scrlfile_input_file_seq { \tl_item:nn { #1 } { 2 } } } } { \seq_gpush:Nx \g__scrlfile_input_file_seq { } \cs_if_exist:NTF \CurrentFileUsed { \seq_gpush:Nx \g__scrlfile_input_file_seq { \CurrentFileUsed } } { \seq_gpush:Nx \g__scrlfile_input_file_seq { \CurrentFile } } } % \end{macrocode} % \end{variable} % \begin{function}[TF, added = 2020-06-26]{\@@_if_loading:n} % Test if the file name is in the file name list. % \begin{macrocode} \prg_new_protected_conditional:Npnn \__scrlfile_if_loading:n #1 { T, F, TF } { \str_set:Nx \l_tmpa_str { #1 } \seq_if_in:NxTF \g__scrlfile_input_file_seq { \str_use:N \l_tmpa_str } { \prg_return_true: } { \prg_return_false: } } % \end{macrocode} % \end{function} % % \begin{function}[TF, added = 2020/08/22] % {\scrlfile_if_class_loaded:n,\scrlfile_if_package_loaded:n} % \cs{scrlfile_if_class_loaded:nTF} is similar to \cs{@ifclassloaded} and % \cs{scrlfile_if_package_loaded:nTF} is similar to \cs{@ifpackageloaded} but in % opposite to those they test, if the class or package has been loaded % completely. % \begin{macrocode} \prg_new_protected_conditional:Npnn \scrlfile_if_class_loaded:n #1 { T, F, TF } { \@ifclassloaded { #1 } { \__scrlfile_if_loading:nTF { #1.\@clsextension } { \prg_return_false: } { \prg_return_true: } } { \prg_return_false: } } \prg_new_protected_conditional:Npnn \scrlfile_if_package_loaded:n #1 { T, F, TF } { \@ifpackageloaded { #1 } { \__scrlfile_if_loading:nTF { #1.\@pkgextension } { \prg_return_false: } { \prg_return_true: } } { \prg_return_false: } } % \end{macrocode} % \end{function} % % \begin{macro}{\AfterClass,\AfterPackage} % With version 3.32 these do not support plus or exclamation mark variants, % but only the normal and the star variants. Instead of the plus or % exclamation mark variants users should use the star variant of % \cs{AfterAtEndOfClass} and \cs{AfterAtEndOfPackage}. The commands use the % \texttt{file/after} hook, because the user manual declares, that \meta{code} % is used before the code of \cs{AtEndOfClass} or \cs{AtEndOfPackage}. % \begin{macrocode} \NewDocumentCommand \scrlfile@AfterClass { s m o +m } { \IfBooleanTF { #1 } { \@ifclassloaded{ #2 } { #4 } { \hook_gput_code:nnn { file / after / #2.\@clsextension } { #3 } { #4 } } } { \hook_gput_code:nnn { file / after / #2.\@clsextension } { #3 } { #4 } } } \NewDocumentCommand \AfterClass { } { \scrlfile@AfterClass } \NewDocumentCommand \scrlfile@AfterPackage { s m o +m } { \IfBooleanTF { #1 } { \@ifpackageloaded{ #2 } { #4 } { \hook_gput_code:nnn { file / after / #2.\@pkgextension } { #3 } { #4 } } } { \hook_gput_code:nnn { file / after / #2.\@pkgextension } { #3 } { #4 } } } \NewDocumentCommand \AfterPackage { } { \scrlfile@AfterPackage } % \end{macrocode} % \end{macro} % % % \subsection{File Substitution} % % With new file hooks the substitutions are so easy we even do not need % \LaTeX3 syntax to implement them. % % \begin{macro}{\ReplaceInput} % This is only the simplest wrapper to \cs{declare@file@substitution}. % \begin{macrocode} \NewDocumentCommand{\ReplaceInput}{}{\declare@file@substitution} % \end{macrocode} % \end{macro} % % \begin{macro}{\ReplaceClass} % \begin{macro}{\ReplacePackage} % These are also wrappers to \cs{declare@file@substitution}. But in this case % we also have to add the extension. % \begin{macrocode} \NewDocumentCommand\ReplaceClass{mm}{% \declare@file@substitution{#1.\@clsextension}{#2.\@clsextension}% } \NewDocumentCommand\ReplacePackage{mm}{% \declare@file@substitution{#1.\@pkgextension}{#2.\@pkgextension}% } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\UnReplaceInput} % This is the simplest wrapper to \cs{undeclare@file@substitution}. % \begin{macrocode} \NewDocumentCommand{\UnReplaceInput}{}{\undeclare@file@substitution} % \end{macrocode} % \end{macro} % % \begin{macro}{\UnReplaceClass} % \begin{macro}{\UnReplacePackage} % Also very simple but again we have to add the extension. % \begin{macrocode} \NewDocumentCommand\UnReplaceClass{m}{% \undeclare@file@substitution{#1.\@clsextension}% } \NewDocumentCommand\UnReplacePackage{mm}{% \undeclare@file@substitution{#1.\@pkgextension}% } % \end{macrocode} % \end{macro} % \end{macro} % % \subsection{Prevent Package from Loading} % % To store and reset the hole list of prevents we use a comma separated % list. So we need again use \LaTeX3. % % \begin{variable}[added = 2020-09-02]{\g__scrlfile_prevents_clist} % This local variable stores the list of files, that should be prevented from % loading. It is needed to stay compatible with old \file{scrlfile}. Without % this compatibility purpose we would be able to use simple wrappers to % \cs{disable@package@load} and \cs{reenable@package@load}. % \begin{macrocode} \clist_new:N \g__scrlfile_prevent_clist % \end{macrocode} % \end{variable} % % \begin{macro}{\PreventPackageFromLoading} % This is more than a wrapper to \cs{disable@package@load} because we have to % manage an internal list and it is documented, that the re-enabling does not % undefine the \meta{alternate-code} setting but only disables it. So we have % to extra store it. Note: Local loading of packages does not make sense, so % local changes of the prevent list also does not make sense. Therefore this % is a global acting command! % \begin{macrocode} \NewDocumentCommand \PreventPackageFromLoading { s +o m } { \clist_set:Nx \l__scrlfile_package_clist { #3 } \clist_map_inline:Nn \l__scrlfile_package_clist { \@ifpackageloaded { ##1 } { \IfBooleanTF { #1 } { \msg_info:nnn } { \msg_warning:nnn } { scrlfile } { no-prevent-for-already-loaded } { ##1 } } { \clist_if_in:NnF \g__scrlfile_prevent_clist { ##1 } { \clist_gput_right:Nn \g__scrlfile_prevent_clist { ##1 } } \tl_if_exist:cF { g__scrlfile_exclude_package_##1_tl } { \tl_new:c { g__scrlfile_exclude_package_##1_tl } } \IfValueT { #2 } { \tl_gput_right:cn { g__scrlfile_exclude_package_##1_tl } { #2 } } \disable@package@load { ##1 } { \tl_use:c { g__scrlfile_exclude_package_##1_tl } } } } \clist_clear:N \l__scrlfile_package_clist } % \end{macrocode} % % \begin{macro}{\l__scrlfile_package_clist} % One local variable is used to process the \meta{package-list} argument of % \cs{PreventPackageFromLoading}. % \begin{macrocode} \clist_new:N \l__scrlfile_package_clist % \end{macrocode} % \end{macro} % % And here comes the message, that could be used either as a warning or as an % info. % \begin{macrocode} \msg_new:nnn { scrlfile } { no-prevent-for-already-loaded } { Cannot~prevent~package~`#1'~from~being~loaded,~ because~it~has~been~loaded~already~before~line~\msg_line_number: } % \end{macrocode} % \end{macro} % % \begin{macro}{\StorePreventPackageFromLoading} % This simply copies the internal \texttt{clist} to a macro. % \begin{macrocode} \NewDocumentCommand \StorePreventPackageFromLoading { m } { \edef #1 { \clist_use:Nn \g__scrlfile_prevent_clist { , } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\ResetPreventPackageFromLoading} % Map the internal list to a function that re-enables the package. At the end % the internal list is cleared. % \begin{macrocode} \NewDocumentCommand \ResetPreventPackageFromLoading {} { \clist_map_function:NN \g__scrlfile_prevent_clist \reenable@package@load \clist_gclear:N \g__scrlfile_prevent_clist } % \end{macrocode} % \end{macro} % % \begin{macro}{\UnPreventPackageFromLoading} % Here again the argument is a \meta{package-list} not only one % \meta{package}. So we have to build a local \texttt{clist} and walk through % it. % \begin{macrocode} \NewDocumentCommand \UnPreventPackageFromLoading { s m } { \clist_set:Nx \l__scrlfile_package_clist { #2 } \clist_map_inline:Nn \l__scrlfile_package_clist { \clist_if_in:NnT \g__scrlfile_prevent_clist { ##1 } { \clist_gremove_all:Nn \g__scrlfile_prevent_clist { ##1 } \reenable@package@load { ##1 } \IfBooleanT { #1 } { \cs_undefine:c { g__scrlfile_exclude_package_##1_tl } } } } } % \end{macrocode} % \end{macro} % % % \subsection{Extra Document Hooks} % % \begin{macro}{\BeforeClosingMainAux} % \changes{v3.32}{2020/09/10}{optional argument added} % Here we cannot simply wrap this to the hook % \texttt{enddocument/afterlastpage}, because it is documented, that inside % the \meta{code} \cs{protected@write} is replaced by % \cs{immediate@protected@write}. So we have to take extra care to it. % \begin{macrocode} \NewDocumentCommand \BeforeClosingMainAux { o m } { \hook_gput_code:nnn { enddocument / afterlastpage } { #1 } { \debug_suspend: \RenewDocumentCommand \BeforeClosingMainAux { m } { ##1 } \cs_set_eq:NN \__scrlfile_protected@write:Nnn \protected@write \cs_set_eq:NN \protected@write \protected@immediate@write #2 \cs_set_eq:NN \protected@write \__scrlfile_protected@write:Nnn \debug_resume: } } % \end{macrocode} % \end{macro} % % \begin{macro}{\AfterReadingMainAux} % \changes{v3.32}{2020/09/10}{optional argument added} % Here we the exact same problem as with \cs{BeforeClosingMainAux}. % \begin{macrocode} \NewDocumentCommand \AfterReadingMainAux { o m } { \hook_gput_code:nnn { enddocument / afteraux } { #1 } { \debug_suspend: \RenewDocumentCommand \AfterReadingMainAux { m } { ##1 } \cs_set_eq:NN \__scrlfile_protected@write:Nnn \protected@write \cs_set_eq:NN \protected@write \protected@immediate@write #2 \cs_set_eq:NN \protected@write \__scrlfile_protected@write:Nnn \debug_resume: } } % \end{macrocode} % \end{macro} % % \begin{macrocode} \ExplSyntaxOff % \end{macrocode} % % \subsection{Kernel Extensions not Using \LaTeX3} % % \begin{macro}{\protected@immediate@write} % Like \LaTeX{} kernel's |\protected@write| but using |\immediate\write|. In % this case it is even not a good idea to protect |\thepage|! % \begin{macrocode} \ProvideDocumentCommand\protected@immediate@write{m+m+m} {% \begingroup #2% \let\protect\@unexpandable@protect \edef\reserved@a{\immediate\write#1{#3}}% \reserved@a \endgroup \if@nobreak\ifvmode\nobreak\fi\fi } % \end{macrocode} % \end{macro} % % \subsection{The \KOMAScript{} Logo} % % Last but not least this is a \KOMAScript{} package, so we also define the % \KOMAScript{} logo: % \begin{macrocode} \RequirePackage{scrlogo} % \end{macrocode} % % \Finale % % \endinput % Local Variables: % mode: doctex % TeX-master: t % End: