summaryrefslogtreecommitdiff
path: root/macros/latex/base/latexrelease.dtx
diff options
context:
space:
mode:
Diffstat (limited to 'macros/latex/base/latexrelease.dtx')
-rw-r--r--macros/latex/base/latexrelease.dtx350
1 files changed, 330 insertions, 20 deletions
diff --git a/macros/latex/base/latexrelease.dtx b/macros/latex/base/latexrelease.dtx
index 5ee39a3a2d..7d2290f4d8 100644
--- a/macros/latex/base/latexrelease.dtx
+++ b/macros/latex/base/latexrelease.dtx
@@ -1,7 +1,7 @@
% \iffalse meta-comment
%
-% Copyright (C) 1993-2020
-% The LaTeX3 Project and any individual authors listed elsewhere
+% Copyright (C) 1993-2021
+% The LaTeX Project and any individual authors listed elsewhere
% in this file.
%
% This file is part of the LaTeX base system.
@@ -37,13 +37,13 @@
%<fixltx2e>\ProvidesPackage{fixltx2e}
%<fixltx2e> [2016/12/29 v2.1a fixes to LaTeX (obsolete)]
%<latexrelease>\ProvidesPackage{latexrelease}
-%<*!fixltx2e>
+%<*latexrelease>
% \fi
% \ProvidesFile{latexrelease.dtx}
- [2018/05/08 v1.0k LaTeX release emulation and tests
+ [2021/04/29 v1.0n LaTeX release emulation and tests
(including releases up to \latexreleaseversion)]
% \iffalse
-%</!fixltx2e>
+%</latexrelease>
%<*driver>
\documentclass{ltxdoc}
\newcommand\Lopt[1]{\textsf{#1}}
@@ -51,6 +51,8 @@
\providecommand{\file}[1]{\texttt{#1}}
\providecommand{\MF}{\textsf{Metafont}}
\providecommand{\danger}{\marginpar[\hfill\protect\Huge!!]{\protect\Huge!!\hfill}}
+ \DeclareRobustCommand\cs[2][]{\texttt{\char`\\\detokenize{#2}}}
+ \DeclareRobustCommand\pkg[1]{\textsf{#1}}
\begin{document}
\DocInput{latexrelease.dtx}
\end{document}
@@ -69,7 +71,7 @@
% \title{The \Lpack{latexrelease} package\thanks{This file
% has version number \fileversion, last
% revised \filedate.}}
-% \author{The \LaTeX3 Project}
+% \author{The \LaTeX{} Project}
% \date{\filedate}
% \MaintainedByLaTeXTeam{latex}
% \maketitle
@@ -129,7 +131,6 @@
% package has no information will generate a warning. Dates
% earlier than 2015 will work but will roll back to some point in 2015
% when the method was introduced.
-% |\IncludeInRelease| command is defined.
% The |\requestedLaTeXdate| is set to the normalized date argument
% so that package rollback defaults to the specified date.
%
@@ -137,9 +138,6 @@
% |current| This is the default behaviour, it does not change the
% effective date of the format but does ensure that the
% |\IncludeInRelease| command is defined.
-% the |\requestedLaTeXdate| is reset to 0 so that package rollback
-% does not use the implicit date.
-% |\IncludeInRelease| command is defined.
% The |\requestedLaTeXdate| macro is reset to 0 so that package rollback
% does not use the implicit date.
%
@@ -147,11 +145,16 @@
% |latest| sets the effective date of the format to the release date
% of this file, so in an older format applies all patches currently
% available.
-% |\IncludeInRelease| command is defined.
% The |\requestedLaTeXdate| macro is reset to 0 so that package rollback
% does not use the implicit date.
% \end{itemize}
%
+% In all cases, when the package is loaded, the |\sourceLaTeXdate| is
+% defined to be the numerical representation of |\fmtversion| before the
+% rollback/forward actually happens, so it is possible to test from which
+% was the original \LaTeX{} version before \Lpack{latexrelease} was loaded.
+% This is particularly useful when some code in a package has to be
+% redefined differently if rolling backwards in time or forwards.
%
% \section{Release Specific Code}
%
@@ -345,7 +348,7 @@
%
% Now if you make a further change to |\widget| in the future you
% simply copy the current definition into the empty block and add a new
-% empty declaration with todays date and the current format date. This
+% empty declaration with today's date and the current format date. This
% way your main code stays readable and the old versions accumulate at
% the end of the package.\footnote{Of course there may be some cases
% in which the old code has to be in a specific place within the
@@ -363,6 +366,99 @@
% necessary as the new code only gets defined if that release is
% chosen.
%
+% \section{Declaring entire modules}
+%
+% Sometimes a large chunk of code is added as a module to another larger
+% code base. As example of that in the 2020-10-01 release \LaTeX{} got
+% a new hook management system, \pkg{lthooks}, which was added in one go
+% and, as with all changes to the kernel, it was added to
+% \pkg{latexrelease}. However rolling back from a future date to the
+% 2020-10-01 release didn't work because \pkg{latexrelease} would try to
+% define again all those commands, which would result in many
+% ``already defined'' errors and similar issues.
+%
+% To solve that problem, completely new modules can be defined in
+% \pkg{latexrelease} using the commands:
+% \begin{quote}
+% \cs{NewModuleRelease}%^^A
+% \verb|{|\meta{initial release date}%^^A
+% \verb|}{|\meta{name}\verb|}{|\meta{message}\verb|}|\\
+% \null\quad\meta{module code}\\
+% \cs{IncludeInRelease}%^^A
+% \verb|{0000/00/00}{|\meta{name}\verb|}{|\meta{message}\verb|}|\\
+% \null\quad\meta{undefine module code}\\
+% \cs{EndModuleRelease}
+% \end{quote}
+% With that setup, the module \meta{name} will be declared to exist only
+% in releases equal or later \meta{initial release date}.
+%
+% If \pkg{latexrelease} is rolling backwards or forwards between dates
+% after \meta{initial release date}, then all the \meta{module code} is
+% skipped, except when inside \meta{IncludeInRelease} guards, in which
+% case the code is applied or skipped as discussed above.
+%
+% If rolling forward from a date before the module's \meta{initial
+% release date} to a date after that, then all the \meta{module code} is
+% executed to define the module, and \cs{IncludeInRelease} guards are
+% executed accordingly, depending on the date declared and the target
+% date.
+%
+% If \pkg{latexrelease} is rolling back to a date before \meta{release
+% date}, then the code in the \cs{IncludeInRelease} guard dated
+% \verb|0000/00/00| is executed instead to undefine the module. This
+% guard \emph{is not} ended by the usual \cs{EndIncludeInRelease}, but
+% instead by \cs{EndModuleRelease}.
+%
+% Finally, if rolling backwards or forwards between dates both before
+% \meta{initial release date}, the entire code between
+% \meta{NewModuleRelease} and \meta{EndModuleRelease} is entirely
+% skipped.
+%
+% \subsection{Example}
+%
+% Here is an example usage of the structure described above, as it would
+% be used in the \LaTeX{} kernel, taking \pkg{lthooks} as example:
+% \begin{verbatim}
+% %<*2ekernel|latexrelease>
+% \ExplSyntaxOn
+% %<latexrelease>\NewModuleRelease{2020/10/01}{lthooks}%
+% %<latexrelease> {The~hook~management~system}
+% \NewDocumentCommand \NewHook { m }
+% { \hook_new:n {#1} }
+% %<latexrelease>\IncludeInRelease{2021/06/01}{\AddToHook}{Long~argument}
+% \NewDocumentCommand \AddToHook { m o +m }
+% { \hook_gput_code:nnn {#1} {#2} {#3} }
+% %<latexrelease>\EndIncludeInRelease
+% %<latexrelease>
+% %<latexrelease>\IncludeInRelease{2020/10/01}{\AddToHook}{Long~argument}
+% %<latexrelease>\NewDocumentCommand \AddToHook { m o m }
+% %<latexrelease> { \hook_gput_code:nnn {#1} {#2} {#3} }
+% %<latexrelease>\EndIncludeInRelease
+% %<latexrelease>
+% %<latexrelease>\IncludeInRelease{0000/00/00}{lthooks}{Undefine~lthooks}
+% %<latexrelease>\cs_undefine:N \NewHook
+% %<latexrelease>\cs_undefine:N \AddToHook
+% %<latexrelease>\EndModuleRelease
+% \ExplSyntaxOff
+% %</2ekernel|latexrelease>
+% \end{verbatim}
+%
+% In the example above, \cs{NewHook} is declared only once, and
+% unchanged in the next release (2021/06/01 in the example), so it has
+% no \cs{IncludeInRelease} guards, and will only be defined if needed.
+% \cs{AddToHook}, on the other hand, changed between the two releases
+% (made up for the example; it didn't really happen)
+% and has an \cs{IncludeInRelease} block for the current release (off
+% \pkg{docstrip} guards, so it goes into the kernel too), and another
+% for the previous release (in \pkg{docstrip} guards so it goes only
+% into \pkg{latexrelease}).
+%
+% Note that in the example above, \cs{ExplSyntaxOn} and
+% \cs{ExplSyntaxOff} were added \emph{outside} the module code because,
+% as discussed above, sometimes the code outside \cs{IncludeInRelease}
+% guards may be skipped, but not the code inside them, and in that case
+% the catcodes would be wrong when defining the code.
+%
% \section{fixltx2e}
%
% As noted above, prior to the 2015 \LaTeX\ release updates to the
@@ -386,17 +482,34 @@
\NeedsTeXFormat{LaTeX2e}[1996/06/01]
% \end{macrocode}
%
-% \section{Setup}
+% \subsection{Setup}
+%
+% \begin{macro}{\sourceLaTeXdate}
+% \changes{v1.0l}{2021/01/23}{\cs{sourceLaTeXdate} added}
+% \changes{v1.0m}{2021/03/18}{Define \cs{sourceLaTeXdate} conditionally}
+% Store the original \LaTeX{} format version
+% as a number in the format |YYYYMMDD|. This macro has to
+% be defined conditionally, so that it isn't changed in case
+% \file{latexrelease.sty} is loaded twice (for tests), but it
+% can't be defined in the kernel only, otherwise
+% \file{latexrelease.sty} wouldn't work in older \LaTeX{} due
+% to the missing macro.
+% \begin{macrocode}
+\@ifundefined{sourceLaTeXdate}{%
+ \edef\sourceLaTeXdate{%
+ \expandafter\@parse@version\fmtversion//00\@nil}}{}%
+% \end{macrocode}
+% \end{macro}
%
% \begin{macro}{\IncludeInRelease}
% \begin{macro}{\EndIncludeInRelease}
-% \begin{macrocode}
-% \end{macrocode}
+% These are defined in \file{ltvers.dtx}.
% \end{macro}
% \end{macro}
%
% \changes{v1.0c}{2015/02/19}{Swap argument order}
-% \changes{v1.0k}{2018/05/08}{reset \cs{\requestedLaTeXdate} for current and latest options, gitub issue 43}
+% \changes{v1.0k}{2018/05/08}{reset \cs{\requestedLaTeXdate} for current
+% and latest options, github issue 43}
% \begin{macrocode}
\DeclareOption*{%
\def\@IncludeInRelease#1[#2]{\@IncludeInRele@se{#1}}%
@@ -483,16 +596,213 @@ of this package available from CTAN}
% \end{macrocode}
%
%
-% \section{Individual Changes}
+% \subsection{Ignoring \texttt{\string_new} errors when rolling back}
%
-% The code for each change will be inserted at this point, extracted
-% from the kernel source files.
+% Enforce \cs{ExplSyntaxOn} and \cs{ExplSyntaxOff} to be \cs{relax} in
+% \pkg{latexrelease} if they are not yet defined. They are later
+% restored to be undefined if needed.
+% \begin{macrocode}
+\csname ExplSyntaxOn\endcsname
+\csname ExplSyntaxOff\endcsname
+% \end{macrocode}
+%
+% \changes{v1.0m}{2021/03/18}
+% {Make \texttt{\string_new} definitions harmless}
+%
+% Define a set of changes here, but we'll only use them later to make
+% sure they are applied after \pkg{expl3} is loaded. If loading from a
+% rather old format, we don't have \cs{ExplSyntaxOn} yet.
+% \begin{macrocode}
+\begingroup
+ \endlinechar=-1
+ \catcode95=11 % _
+ \catcode58=11 % :
+ \catcode126=10 % ~
+ \catcode32=09 % <space>
+ \xdef\latexrelease@postexpl{\unexpanded{%
+%<@@=latexrelease>
+% \end{macrocode}
+%
+% First we'll define a \cs{declarecommand} that does \cs{renewcommand}
+% if the command being defined already exists, and \cs{newcommand}
+% otherwise.
+% \begin{macrocode}
+\cs_gset_protected:Npn \@@_declare_command:w
+ { \@star@or@long \@@_declare_command:Nw }
+\cs_gset_protected:Npn \@@_declare_command:Nw #1
+ { \cs_if_exist:NTF #1 { \renew@command } { \new@command } #1 }
+% \end{macrocode}
+%
+% Then define a version of \cs{e@alloc} that checks if the control
+% sequence being defined already exists, and if so, checks if its
+% meaning is the same as the one that would be defined with the call to
+% \cs{e@alloc}. If both tests pass, nothing is defined to save a
+% register. This version also takes care of setting
+% \cs{allocationnumber} to the value it would have after the register is
+% allocated.
+% \begin{macrocode}
+\cs_gset_protected:Npn \@@_e@alloc:NnnnnN #1 #2 #3 #4 #5 #6
+ {
+ \cs_if_free:NTF #6
+ { \use:n }
+ {
+ \exp_after:wN \@@_e@alloc:N
+ \token_to_meaning:N #6 \scan_stop: {#2} #6
+ }
+ { \@@_e@alloc #1 {#2} {#3} {#4} {#5} #6 }
+ }
+% \end{macrocode}
+% Walk through the meaning of the control sequence token by token,
+% looking for the register allocation number.
+% \begin{macrocode}
+\cs_gset_protected:Npn \@@_e@alloc:N #1
+ {
+ \if_int_compare:w 0 < 0
+ \if_int_compare:w 10 < 9#1 ~ 1 \fi:
+ \if_charcode:w " #1 1 \fi: \exp_stop_f:
+ \tex_afterassignment:D \@@_e@alloc:w
+ \@tempcnta #1
+ \use_i:nnn
+ \fi:
+ \use:n
+ {
+ \if_meaning:w \scan_stop: #1
+ \exp_after:wN \use_iv:nnnn
+ \fi:
+ \@@_e@alloc:N
+ }
+ }
+% \end{macrocode}
+% When found, check if it is the exact same register as it would be
+% allocated, and if it is, set \cs{allocationnumber} accordingly and
+% exit, otherwise undefine the register and allocate from scratch.
+% \begin{macrocode}
+\cs_gset_protected:Npn \@@_e@alloc:w #1 \scan_stop: #2 #3
+ {
+ #2 \@@_tmp:w = \@tempcnta
+ \token_if_eq_meaning:NNTF #3 \@@_tmp:w
+ { \int_set_eq:NN \allocationnumber \@tempcnta \use_none:n }
+ { \cs_set_eq:NN #3 \tex_undefined:D \use:n }
+ }
+% \end{macrocode}
%
+% Now create a token list to hold the list of changed commands, and
+% define a temporary macro that will loop through the command list,
+% store each in \cs{l_@@_restores_tl}, save a copy, and redefine each.
% \begin{macrocode}
+\tl_clear_new:N \l_@@_restores_tl
+\cs_gset:Npn \@@_tmp:w #1 #2
+ {
+ \quark_if_recursion_tail_stop_do:Nn #1
+ { \cs_undefine:N \@@_tmp:w }
+ \tl_put_right:Nn \l_@@_restores_tl {#1}
+ \cs_set_eq:cN { @@_ \cs_to_str:N #1 } #1
+ \cs_set_eq:NN #1 #2
+ \@@_tmp:w
+ }
+% \end{macrocode}
+%
+% The redefinitions below are needed because:
+% \begin{description}
+% \def\makelabel#1{\cs{#1}}
+% \item[__kernel_chk_if_free_cs:N] This function is used ubiquitously
+% in the \pkg{l3kernel} to check if a control sequence is definable,
+% and give an error otherwise (similar to \cs{@ifdefinable}).
+% Making it a no-op is enough for most cases (except when defining
+% new registers);
+% \item[e@alloc] In the case of new registers, we waste an allocation
+% number if we do \cs[no-index]{new\meta{thing}} in a register
+% that's already allocated, so the redefinition of \cs{e@alloc}
+% checks if the new register is really necessary. This code does
+% not clear the register, which might cause problems in the future,
+% if a register is allocated but not properly cleared before using;
+% \item[__kernel_msg_error:nnx] This command is used to error on
+% already defined scan marks. Just making the error do nothing is
+% enough, as no action is taken in that case;
+% \item[msg_new:nnnn] Used to define new messages. Making it
+% \texttt{\string_gset} is enough. Other msg commands like
+% \cs{msg_new:nnn} and \cs[no-index]{__kernel_msg_new:nnn(n)} are
+% defined in terms of \cs{msg_new:nnnn}, so there is no need to
+% change the other ones;
+% \item[NewDocumentCommand] Used to define user-level commands in the
+% kernel. Making it equal to \cs{DeclareDocumentCommand} solves the
+% problem;
+% \item[newcommand] Same as above.
+% \end{description}
+% And here we go:
+% \begin{macrocode}
+\@@_tmp:w
+ \__kernel_chk_if_free_cs:N \use_none:n
+ \e@alloc \@@_e@alloc:NnnnnN
+ \__kernel_msg_error:nnx \use_none:nnn
+ \msg_new:nnnn \msg_gset:nnnn
+ \NewDocumentCommand \DeclareDocumentCommand
+ \newcommand \@@_declare_command:w
+% \end{macrocode}
+% Temp addition \ldots
+% \begin{macrocode}
+ \__kernel_msg_error:nnn \use_none:nnn % needed while redirect for kernel msgs doesn't work
+ \q_recursion_tail \q_recursion_tail
+ \q_recursion_stop
+% \end{macrocode}
+%
+% Finally, redirect the error thrown by \cs{NewHook} to nowhere so it
+% can be safely reused (the hook isn't redeclared if it already exists).
+% \begin{macrocode}
+\msg_redirect_name:nnn { hooks } { exists } { none }
+% \end{macrocode}
+%
+% \begin{macrocode}
+ }}%
+\endgroup
%</latexrelease>
% \end{macrocode}
%
-% \section{fixltx2e}
+% \subsection{Undoing the temp modifications}
+%
+% If \cs{ExplSyntaxOn} exists (defined and not equal \cs{relax}), then
+% use the \pkg{expl3} restore code, otherwise restore \cs{ExplSyntaxOn}
+% and \cs{ExplSyntaxOff} to be undefined.
+% \begin{macrocode}
+%<*latexrelease-finish>
+\@ifundefined{ExplSyntaxOn}%
+ {\let\ExplSyntaxOn\@undefined
+ \let\ExplSyntaxOff\@undefined
+ \@gobble}%
+ {\ExplSyntaxOn
+ \@firstofone}%
+ {%
+% \end{macrocode}
+%
+% Now just loop through the list of redefined commands and restore their
+% previous meanings.
+% \begin{macrocode}
+\tl_map_inline:Nn \l_@@_restores_tl
+ {
+ \cs_set_eq:Nc #1 { @@_ \cs_to_str:N #1 }
+ \cs_undefine:c { @@_ \cs_to_str:N #1 }
+ }
+\tl_clear:N \l_@@_restores_tl
+% \end{macrocode}
+%
+% And restore the hook error message.
+% \begin{macrocode}
+\msg_redirect_name:nnn { hooks } { exists } { }
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<@@=>
+ \ExplSyntaxOff}%
+%</latexrelease-finish>
+% \end{macrocode}
+%
+% \subsection{Individual Changes}
+%
+% The code for each change will be inserted at this point, extracted
+% from the kernel source files.
+%
+%
+% \subsection{fixltx2e}
%
% Generate a stub \Lpack{fixltx2e} package:
% \begin{macrocode}