[latex3-commits] [latex3/latex2e] UF-latexlab-toc, defaultT1, develop, gh-1265, gh1183, main, tlc3-errata, update-UseTaggingSocket: Add mech to exclude class options from package processing (5dd025c1f)

github at latex-project.org github at latex-project.org
Sat Jul 13 18:02:17 CEST 2024


Repository : https://github.com/latex3/latex2e
On branches: UF-latexlab-toc,defaultT1,develop,gh-1265,gh1183,main,tlc3-errata,update-UseTaggingSocket
Link       : https://github.com/latex3/latex2e/commit/5dd025c1fc8898cdc0558cbc2b0f1bde9fcc8730

>---------------------------------------------------------------

commit 5dd025c1fc8898cdc0558cbc2b0f1bde9fcc8730
Author: Joseph Wright <joseph at texdev.net>
Date:   Wed Jun 19 08:27:42 2024 +0100

    Add mech to exclude class options from package processing


>---------------------------------------------------------------

5dd025c1fc8898cdc0558cbc2b0f1bde9fcc8730
 base/changes.txt               |   5 ++
 base/doc/clsguide.tex          |   5 +-
 base/doc/ltnews40.tex          |  29 +++++++++
 base/ltkeys.dtx                | 136 ++++++++++++++++++++++++++++++++++-------
 base/testfiles/github-0892.lvt |   3 +-
 base/testfiles/github-1279.lvt |  43 +++++++++++++
 base/testfiles/github-1279.tlg |  29 +++++++++
 7 files changed, 227 insertions(+), 23 deletions(-)

diff --git a/base/changes.txt b/base/changes.txt
index 0fa283c39..4fd4b7020 100644
--- a/base/changes.txt
+++ b/base/changes.txt
@@ -6,6 +6,11 @@ to completeness or accuracy and it contains some references to files that are
 not part of the distribution.
 ================================================================================
 
+2024-06-19 Joseph Wright  <Joseph.Wright at latex-project.org>
+	* ltkeys.dtx, clsguide.tex
+	Refactor class option code
+	Add mechanism to exclude class options from package processing
+
 2024-06-17  David Carlisle  <David.Carlisle at latex-project.org>
 
 	* ltfssbas.dtx: set \tracinglostchars in \showhyphens. gh/1380
diff --git a/base/doc/clsguide.tex b/base/doc/clsguide.tex
index 6b9e49c4e..c7c2ac5e2 100644
--- a/base/doc/clsguide.tex
+++ b/base/doc/clsguide.tex
@@ -42,7 +42,7 @@
     \texttt{clsguide.tex} for full details.}%
 }
 
-\date{2024-05-24}
+\date{2024-06-19}
 
 \NewDocumentCommand\cs{m}{\texttt{\textbackslash\detokenize{#1}}}
 \NewDocumentCommand\marg{m}{\arg{#1}}
@@ -841,6 +841,9 @@ The basic properties provided here are
   \item \texttt{.code} --- execute arbitrary code
   \item \texttt{.if} --- sets a \TeX{} |\if...| switch
   \item \texttt{.ifnot} --- sets an inverted \TeX{} |\if...| switch
+  \item \texttt{.pass-to-packages} --- for class options, this specifies
+    whether the option should be treated \enquote{global} (read by packages
+    from the global list); for package options this property has no effect
   \item \texttt{.store} --- stores a value in a macro
   \item \texttt{.usage} -- defines whether the option can be given only
     when loading (\texttt{load}), in the preamble (\texttt{preamble}) or
diff --git a/base/doc/ltnews40.tex b/base/doc/ltnews40.tex
index f9755ed81..10384e1a8 100644
--- a/base/doc/ltnews40.tex
+++ b/base/doc/ltnews40.tex
@@ -149,6 +149,35 @@
 
 \section{Code improvements}
 
+\subsection{Avoiding keyval option clashes between classes and packages}
+
+In \LaTeX{} News~35~\cite{40:ltnews35} we introduced keyval option processing
+to the kernel. Following the standard for \LaTeXe{} options, keyval options
+given to the \cs{documentclass} line were treated as global and so parsed by
+every package. However, with keyvals, the likelihood of a name clash between a
+class-specific option and one used by a package is much higher than it is with
+simple strings. We have therefore refined the mechanism in the current release.
+
+When a class uses the kernel keyval processor, any options it recognises are
+recorded and any packages using the keyval processor will then \emph{skip}
+these \enquote{global} options. To allow for the case where a class directly
+uses an option which should be global (for example \texttt{draft}), a new key
+property \texttt{.pass-to-packages} has been added. This can then be set to
+indicate that this key is not to be skipped. For example
+\begin{verbatim}
+\DeclareKeys{
+  draft .if = {ifl at cls@draft},
+  draft .pass-to-packages = true,
+  mode  .store = \cls at mode
+}
+\end{verbatim}
+in a class would create two options, \texttt{draft} and \texttt{mode}. The
+\texttt{draft} option will be treated in the normal way by packages using
+keyvals, but they will ignore the \texttt{mode} option: it is effectively
+marked as \enquote{private} to the class.
+
+\githubissue{1279}
+
 \section{Bug fixes}
 
 \subsection{Fix wrong file type in a rollback warning}
diff --git a/base/ltkeys.dtx b/base/ltkeys.dtx
index 34a0ab4b4..657248c01 100644
--- a/base/ltkeys.dtx
+++ b/base/ltkeys.dtx
@@ -33,7 +33,7 @@
 %<*driver>
 % \fi
 \ProvidesFile{ltkeys.dtx}
-             [2024/01/13 v1.0m LaTeX Kernel (Keyval options)]
+             [2024/06/19 v1.0n LaTeX Kernel (Keyval options)]
 % \iffalse
 \documentclass{l3doc}
 \GetFileInfo{ltkeys.dtx}
@@ -73,6 +73,9 @@
 %    \item \texttt{.code} --- execute arbitrary code
 %     \item \texttt{.if} --- sets a \TeX{} \cs{if...} switch
 %     \item \texttt{.ifnot} --- sets an inverted \TeX{} \cs{if...} switch
+%     \item \texttt{.pass-to-packages} --- for class options, this specifies
+%       whether the option should be treated \enquote{global} (read by packages
+%       from the global list); for package options this property has no effect
 %     \item \texttt{.store} --- stores a value in a macro
 %     \item \texttt{.usage} -- defines whether the option can be given only
 %       when loading (\texttt{load}), in the preamble (\texttt{preamble}) or
@@ -182,12 +185,75 @@
 %    \end{macrocode}
 % \end{macro}
 %
+% \begin{macro}{.pass-to-packages}
+% \changes{v1.0n}{2024/06/19}{New key property}
+% \begin{macro}{\@@_scope:n}
+% \changes{v1.0n}{2024/06/19}{New function}
+% \begin{macro}{\@@_scope:N}
+% \changes{v1.0n}{2024/06/19}{New function}
+% \begin{macro}{\@@_scope:w}
+% \changes{v1.0n}{2024/06/19}{New function}
+%   Used to force options to be global: as this property (uniquely) has
+%   an \emph{optional} value, there is a bit of work to do.
+%    \begin{macrocode}
+\cs_new_protected:cpn { \c_@@_props_root_str .pass-to-packages }
+  {
+    \bool_if:NTF \l_@@_no_value_bool
+      { \@@_scope:n { true } }
+      { \@@_scope:n }
+  }
+\cs_new_protected:Npn \@@_scope:n #1
+  {
+    \str_case:nnF {#1}
+      {
+        { true }
+          { \@@_scope:N \clist_put_right:Ne }
+        { false }
+          { \@@_scope:N \clist_remove_all:Ne }
+      }
+      {
+        \msg_error:nnnn { keys }
+          { choice-unknown }
+          { .pass-to-packages }
+          {#1}
+      }
+  }
+\cs_new_protected:Npn \@@_scope:N #1
+  {
+    #1 \l_@@_forced_global_clist
+      { \exp_after:wN \@@_scope:w \l_keys_path_str \s_@@_stop }
+  }
+\cs_new:Npn \@@_scope:w #1 / #2 \s_@@_stop {#2}
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
 % \subsection{Main mechanism}
 % 
 %    \begin{macrocode}
+\cs_generate_variant:Nn \clist_if_in:NnT { Ne }
+\cs_generate_variant:Nn \clist_if_in:NnTF { Ne }
 \cs_generate_variant:Nn \clist_put_right:Nn { Nv }
 %    \end{macrocode}
 %
+% \begin{macro}{\l_@@_class_only_clist}
+% \changes{v1.0n}{2024/06/19}{New variable}
+%   Used to track class-only options.
+%    \begin{macrocode}
+\clist_new:N \l_@@_class_only_clist
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\l_@@_forced_global_clist}
+% \changes{v1.0n}{2024/06/19}{New variable}
+%   Used to force options to be global.
+%    \begin{macrocode}
+\clist_new:N \l_@@_forced_global_clist
+%    \end{macrocode}
+% \end{macro}
+%
 % \begin{macro}{\l_@@_options_clist}
 %   A single list is used for all options, into which they are collected
 %   before processing.
@@ -295,33 +361,34 @@
 % \changes{v1.0h}{2022/06/19}{Further work on handling of option removal}
 % \changes{v1.0h}{2022/06/20}{Use raw options data}
 % \changes{v1.0m}{2024/01/13}{Trim spaces off key names}
+% \changes{v1.0n}{2024/06/19}{Refactor function}
 % \begin{macro}{\@@_options_class:nnn}
 % \changes{v1.0h}{2022/06/20}{New function}
 % \changes{v1.0i}{2022/07/05}{Correct naming of raw class options storage}
 % \changes{v1.0l}{2022/10/22}{Correct handling of unused option list}
+% \changes{v1.0n}{2024/06/19}{Refactor function}
+% \changes{v1.0n}{2024/06/19}{Track options used by classes}
+% \begin{macro}{\@@_options_class:nn}
+% \changes{v1.0n}{2024/06/19}{New function}
 %   For classes, each option (stripped of any content after |=|)
 %   is checked for existence as a key. If found, the option is added to
 %   the combined list for processing. On the other hand, unused options
-%   are stored up in \cs{@unusedoptionlist}. Before any of that, though,
-%   there is a simple check to see if there is an |unknown| key. If there
-%   is, then \emph{everything} will match and the mapping can be skipped.
+%   are stored up in \cs{@unusedoptionlist}. An earlier version of
+%   this code checked for the \texttt{unknown} key just once and
+%   if found short-cutted the loop: that though makes handling more
+%   complex situations harder, so we take the performance hit instead.
+%   Options used by classes are tracked but the catch-all \texttt{unknown}
+%   is excluded (hence not using a lazy evaluation for the key testing).
 %    \begin{macrocode}
 \cs_new_protected:Npn \@@_options_class:n #1
   {
     \cs_if_free:cF { @raw at opt@ \@currname . \@currext }
       {
-        \keys_if_exist:nnTF {#1} { unknown }
+        \clist_map_inline:cn { @raw at opt@ \@currname . \@currext }
           {
-            \clist_put_right:Nv \l_@@_options_clist
-              { @raw at opt@ \@currname . \@currext }
-          }
-          {
-            \clist_map_inline:cn { @raw at opt@ \@currname . \@currext }
-              {
-                \exp_args:Ne \@@_options_class:nnn
-                  { \tl_trim_spaces:e { \@@_remove_equals:n {##1} } }
-                  {##1} {#1}
-              }
+            \exp_args:Ne \@@_options_class:nnn
+              { \tl_trim_spaces:e { \@@_remove_equals:n {##1} } }
+                {##1} {#1}
           }
       }
   }
@@ -329,17 +396,27 @@
   {
     \keys_if_exist:nnTF {#3} {#1}
       {
-        \clist_put_right:Nn \l_@@_options_clist {#2}
-        \clist_remove_all:Nn \@unusedoptionlist {#1}
+        \@@_options_class:nn {#1} {#2}
+        \clist_put_right:Ne \l_@@_class_only_clist { \tl_to_str:n {#1} }
       }
       {
-        \clist_if_in:NnF \@unusedoptionlist {#1}
-          { \clist_put_right:Nn \@unusedoptionlist {#1} }
+        \keys_if_exist:nnTF {#3} { unknown }
+          { \@@_options_class:nn {#1} {#2} }
+          {
+            \clist_if_in:NnF \@unusedoptionlist {#1}
+              { \clist_put_right:Nn \@unusedoptionlist {#1} }
+          }
       }
   }
+\cs_new_protected:Npn \@@_options_class:nn #1#2
+  {
+    \clist_remove_all:Nn \@unusedoptionlist {#1}
+    \clist_put_right:Nn \l_@@_options_clist {#2}
+  }
 %    \end{macrocode}
 % \end{macro}
 % \end{macro}
+% \end{macro}
 %
 % \begin{macro}{\@@_options_package:n}
 % \changes{v1.0g}{2022/06/16}{Better handling of option removal}
@@ -348,6 +425,9 @@
 % \changes{v1.0m}{2024/01/13}{Trim spaces off key names}
 % \begin{macro}{\@@_options_package:nnn}
 % \changes{v1.0h}{2022/06/19}{New function}
+% \changes{v1.0n}{2024/06/19}{Skip options given to packages}
+% \begin{macro}{\@@_options_package:nn}
+% \changes{v1.0n}{2024/06/19}{New functions}
 %   For global options when processing a package, the tasks are slightly
 %   different from those for a class. The check is the same, but here
 %   there is nothing to do if the option is not applicable. Each valid
@@ -362,17 +442,31 @@
           {##1} {#1}
       }
   }
+%    \end{macrocode}
+%   The forced-global test here needs to use \cs{tl_to_str:n} as the data come
+%   from a key name, which is always a string.
+%    \begin{macrocode}
 \cs_new_protected:Npn \@@_options_package:nnn #1#2#3
   {
     \keys_if_exist:nnT {#3} {#1}
       {
-        \clist_put_right:Nn \l_@@_options_clist {#2}
-        \clist_remove_all:Nn \@unusedoptionlist {#1}
+        \clist_if_in:NeTF \l_@@_class_only_clist { \tl_to_str:n {#1} }
+          {
+            \clist_if_in:NeT \l_@@_forced_global_clist { \tl_to_str:n {#1} }
+              { \@@_options_package:nn {#1} {#2} }
+          }
+          { \@@_options_package:nn {#1} {#2} }
       }
    }
+\cs_new_protected:Npn \@@_options_package:nn #1#2
+  {
+    \clist_put_right:Nn \l_@@_options_clist {#2}
+    \clist_remove_all:Nn \@unusedoptionlist {#1}
+  }
 %    \end{macrocode}
 % \end{macro}
 % \end{macro}
+% \end{macro}
 %
 % \begin{macro}{\@@_options_local:}
 % \changes{v1.0h}{2022/06/20}{Use raw options data}
diff --git a/base/testfiles/github-0892.lvt b/base/testfiles/github-0892.lvt
index 3f3922034..3fa5344f0 100644
--- a/base/testfiles/github-0892.lvt
+++ b/base/testfiles/github-0892.lvt
@@ -3,7 +3,8 @@
  %% \RequirePackage{l3keys2e}
   \ProvidesExplClass{l3keys2e-class}{0000/00/00}{0.0}{test}
   \DeclareKeys {
-    option1 .code:n = {\wlog{You~gave~`#1'~for~option1}}
+    option1 .code:n = {\wlog{You~gave~`#1'~for~option1}},
+    option1 .pass-to-packages
   }
   \ProcessKeyOptions
 \end{filecontents}
diff --git a/base/testfiles/github-1279.lvt b/base/testfiles/github-1279.lvt
new file mode 100644
index 000000000..8bc150d97
--- /dev/null
+++ b/base/testfiles/github-1279.lvt
@@ -0,0 +1,43 @@
+\begin{filecontents}[force]{\jobname.cls}
+\newcommand*\l at cls@a at tl{}
+\newcommand*\l at cls@b at tl{}
+\DeclareKeys{
+  option-a .store = \l at cls@a at tl,
+  option-a .pass-to-packages,
+  option-b .store = \l at cls@b at tl,
+  option-b .pass-to-packages = true,
+  option-c .store = \l at cls@c at tl,
+  option-c .pass-to-packages = false,
+  option-d .store = \l at cls@d at tl
+  }
+\ProcessKeyOptions
+\show\l at cls@a at tl
+\show\l at cls@b at tl
+\show\l at cls@c at tl
+\show\l at cls@d at tl
+\end{filecontents}
+\begin{filecontents}[force]{\jobname.sty}
+\newcommand*\l at pkg@a at tl{}
+\newcommand*\l at pkg@b at tl{}
+\newcommand*\l at pkg@c at tl{}
+\newcommand*\l at pkg@d at tl{}
+\DeclareKeys{
+  option-a .store = \l at pkg@a at tl,
+  option-b .store = \l at pkg@b at tl,
+  option-c .store = \l at pkg@c at tl,
+  option-d .store = \l at pkg@d at tl
+  }
+\ProcessKeyOptions
+\show\l at pkg@a at tl
+\show\l at pkg@b at tl
+\show\l at pkg@c at tl
+\show\l at pkg@d at tl
+\end{filecontents}
+
+\input{test2e}
+
+\START
+\documentclass[option-a = AAA, option-b = BBB, option-c = CCC, option-d = DDD]{\jobname}
+\usepackage{\jobname}
+
+\END
diff --git a/base/testfiles/github-1279.tlg b/base/testfiles/github-1279.tlg
new file mode 100644
index 000000000..907a940f5
--- /dev/null
+++ b/base/testfiles/github-1279.tlg
@@ -0,0 +1,29 @@
+This is a generated file for the LaTeX2e validation system.
+Don't change this file in any respect.
+(github-1279.cls
+> \l at cls@a at tl=macro:
+->AAA.
+l. ...\show\l at cls@a at tl
+> \l at cls@b at tl=macro:
+->BBB.
+l. ...\show\l at cls@b at tl
+> \l at cls@c at tl=macro:
+->CCC.
+l. ...\show\l at cls@c at tl
+> \l at cls@d at tl=macro:
+->DDD.
+l. ...\show\l at cls@d at tl
+) (github-1279.sty
+> \l at pkg@a at tl=macro:
+->AAA.
+l. ...\show\l at pkg@a at tl
+> \l at pkg@b at tl=macro:
+->BBB.
+l. ...\show\l at pkg@b at tl
+> \l at pkg@c at tl=macro:
+->.
+l. ...\show\l at pkg@c at tl
+> \l at pkg@d at tl=macro:
+->.
+l. ...\show\l at pkg@d at tl
+)





More information about the latex3-commits mailing list.