diff options
Diffstat (limited to 'Master/texmf-dist/source/latex/base/latexrelease.dtx')
-rw-r--r-- | Master/texmf-dist/source/latex/base/latexrelease.dtx | 350 |
1 files changed, 330 insertions, 20 deletions
diff --git a/Master/texmf-dist/source/latex/base/latexrelease.dtx b/Master/texmf-dist/source/latex/base/latexrelease.dtx index 5ee39a3a2df..7d2290f4d8a 100644 --- a/Master/texmf-dist/source/latex/base/latexrelease.dtx +++ b/Master/texmf-dist/source/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} |