[latex3-commits] [git/LaTeX3-latex3-latex2e] develop: Add l3keys2e code as ltkeys (#710) (272f8e8d)

GitHub noreply at github.com
Wed Jan 12 18:43:35 CET 2022


Repository : https://github.com/latex3/latex2e
On branch  : develop
Link       : https://github.com/latex3/latex2e/commit/272f8e8d07d38f5b8f92406e2050fcda4d993d1f

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

commit 272f8e8d07d38f5b8f92406e2050fcda4d993d1f
Author: Joseph Wright <joseph.wright at morningstar2.co.uk>
Date:   Wed Jan 12 17:43:35 2022 +0000

    Add l3keys2e code as ltkeys (#710)


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

272f8e8d07d38f5b8f92406e2050fcda4d993d1f
 base/changes.txt                           |  10 +
 base/doc/ltnews35.tex                      |  41 +++
 base/doc/source2e.tex                      |   2 +
 base/format.ins                            |   1 +
 base/ltclass.dtx                           |  66 +++--
 base/ltkeys.dtx                            | 454 +++++++++++++++++++++++++++++
 base/testfiles-lthooks/ltcmdhooks-001.tlg  |   2 +-
 base/testfiles/github-0710.lvt             |  50 ++++
 base/testfiles/github-0710.tlg             |  70 +++++
 base/testfiles/tlb-rollback-005.luatex.tlg |   4 +-
 base/testfiles/tlb-rollback-005.tlg        |   4 +-
 base/testfiles/tlb-rollback-005.xetex.tlg  |   4 +-
 12 files changed, 679 insertions(+), 29 deletions(-)

diff --git a/base/changes.txt b/base/changes.txt
index c8f78ffe..8ae1fd7b 100644
--- a/base/changes.txt
+++ b/base/changes.txt
@@ -84,6 +84,16 @@ are not part of the distribution.
 
 	* All *.dtx: Replaced \StopEventually by \MaybeStop
 
+2021-11-30 Joseph Wright <joseph.wright at latex-project.org>
+
+  * ltclasses.dtx
+	New option handling routine using ltkeys
+
+2021-11-26 Joseph Wright <joseph.wright at latex-project.org>
+
+  * ltkeys.dtx
+	New file to integrate keyval option processing into the kernel
+
 2021-11-17  Marcel Krüger  <Marcel.Krueger at latex-project.org>
 
 	* ltluatex.dtx:
diff --git a/base/doc/ltnews35.tex b/base/doc/ltnews35.tex
index 9b8d9c0f..31bce8a1 100644
--- a/base/doc/ltnews35.tex
+++ b/base/doc/ltnews35.tex
@@ -193,6 +193,47 @@ and also in the documentation of the \pkg{pdfmanagement-testphase} package.
 
 \section{New or improved commands}
 
+\subsection{A kevyal approach to option handling}
+
+The classical \LaTeXe{} method for handling options, using \cs{ProcessOptions},
+treats each entry in the list as a string. Many package authors have sought to
+extend this handling by treating each entry as a key--value pair (keyval)
+instead. To-date, this has required the use of additional packages, for example
+\pkg{kvoptions}.
+
+The \LaTeX{} team have for some time offered the package \pkg{l3keys2e} to
+allow keyvals defined using the L3 programming layer module \pkg{l3keys} to act
+as package options. This ability has now been integrated directly into the
+kernel. As part of this integration, the syntax for processing keyval options
+has been refined, such that
+\begin{verbatim}
+\ProcessKeyOptions
+\end{verbatim}
+will now automatically pick up the package name as the key \emph{family},
+unless explicitly given as an optional argument.
+\begin{verbatim}
+\ProcessKeyOptions[family]
+\end{verbatim}
+A version which does not consider global options,
+\cs{ProcessKeyPackageOptions}, is also available.
+
+To support creating key options in for this mechanism, the new command
+\cs{DeclareKeys} has been added. This works using the same general
+approach as \pkg{l3keys} or \pkg{pgfkeys}: each key has one or more
+\emph{properties} which define its behavior.
+
+Options for packages which use this new approach will not be checked for
+clashes by the kernel. Instead, each time a \cs{usepackage} or
+\cs{RequirePackage} line is encountered, the list of options given will be
+passed to \cs{ProcessKeyPackageOptions}. Options which can only be given
+the first time a package is loaded can be marked using the property
+\texttt{.usage = load}, and will result in a warning if used in a subsequent
+package loading line.
+
+Package options defined in this way can also be set within a package using
+the new command \cs{SetKeys}, which again takes an optional argument
+to specify the \emph{family}, plus a mandatory one for the options themselves.
+
 \subsection{Floating point and integer calculations}
 
 The L3 programming layer offers expandable commands for calculating
diff --git a/base/doc/source2e.tex b/base/doc/source2e.tex
index 88252394..9eea9ddd 100644
--- a/base/doc/source2e.tex
+++ b/base/doc/source2e.tex
@@ -328,6 +328,8 @@ page_precedence "rnaA"
 
  \DocInclude{ltclass}  % Package & Class interface
 
+ \DocInclude{ltkeys}   % Key-based option management (L3 module)
+
  \DocInclude{ltfilehook}  % Hook management for files (L3 module)
 
  \DocInclude{ltshipout}% \shipout redefinition (L3 module)
diff --git a/base/format.ins b/base/format.ins
index ca16ee18..88d33d3c 100644
--- a/base/format.ins
+++ b/base/format.ins
@@ -204,6 +204,7 @@ the system are in the document `cfgguide.tex'.
           \from{ltbibl.dtx}{2ekernel}
           \from{ltpage.dtx}{2ekernel}
          \from{ltclass.dtx}{2ekernel,tracerollback}
+          \from{ltkeys.dtx}{2ekernel}           % L3 layer module
           \from{ltfilehook.dtx}{2ekernel}       % L3 layer module
           \from{ltshipout.dtx}{2ekernel}        % L3 layer module
           \from{ltoutput.dtx}{2ekernel}
diff --git a/base/ltclass.dtx b/base/ltclass.dtx
index 573cd081..0ff3f711 100644
--- a/base/ltclass.dtx
+++ b/base/ltclass.dtx
@@ -33,7 +33,7 @@
 %<*driver>
 % \fi
 \ProvidesFile{ltclass.dtx}
-             [2021/08/25 v1.4f LaTeX Kernel (Class & Package Interface)]
+             [2021/12/09 v1.5a LaTeX Kernel (Class & Package Interface)]
 % \iffalse
 \documentclass{ltxdoc}
 \GetFileInfo{ltclass.dtx}
@@ -2175,27 +2175,21 @@
 %    \begin{macrocode}
   \@ifl at aded\@currext\@currname
 %    \end{macrocode}
-%    If the package is already loaded, check that there were no option
-%    clashes:
-% \changes{v1.1b}{1998/05/07}
-%         {Modify help message for latex/2805}
-%    \begin{macrocode}
-    {\@if at ptions\@currext{\@currname}{#2}{}%
-      {\@latex at error
-        {Option clash for \@cls at pkg\space \@currname}%
-        {The package \@currname\space has already been loaded
-         with options:\MessageBreak
-         \space\space[\@ptionlist{\@currname.\@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{\@currname.\@currext},#2\MessageBreak
-         to your \noexpand\documentclass declaration may fix this.%
-         \MessageBreak
-         Try typing \space <return> \space to proceed.}}%
-     \@firstofone}%
+%    In the current preferred approach, a key family name will exist for
+%    processing using \pkg{ltkeys}. In that case, we replace the previous
+%    package options with with the new ones, then call the key handler.
+%    Otherwise, we use the more classical clash handler.
+%    \begin{macrocode}
+    {%
+      \@ifundefined{opt at fam@\@currname.\@currext}
+        {\@onefilewithoptions at clashchk{#2}}
+        {%
+          \@namedef{opt@\@currname.\@currext}{#2}%
+          \expandafter\expandafter\expandafter\ProcessKeyPackageOptions
+            \expandafter\expandafter\expandafter
+              [\csname opt at fam@\@currname.\@currext\endcsname]%
+        }%
+    }%
     {\makeatletter
 %    \end{macrocode}
 %    The next line seems to be necessary for 2.09 compatibility (the
@@ -2298,6 +2292,34 @@
     \@reset at ptions}
 %    \end{macrocode}
 %
+% \begin{macro}{\@onefilewithoptions at clashchk}
+%    If the package is already loaded, check that there were no option
+%    clashes.
+% \changes{v1.1b}{1998/05/07}
+%         {Modify help message for latex/2805}
+% \changes{v1.5a}{2021/11/30}
+%         {Separated out from \cs{@onefilewithoptions}}
+%    \begin{macrocode}
+\def\@onefilewithoptions at clashchk#1{%
+  \@if at ptions\@currext{\@currname}{#1}{}%
+      {\@latex at error
+        {Option clash for \@cls at pkg\space \@currname}%
+        {The package \@currname\space has already been loaded
+         with options:\MessageBreak
+         \space\space[\@ptionlist{\@currname.\@currext}]\MessageBreak
+         There has now been an attempt to load it
+          with options\MessageBreak
+         \space\space[#1]\MessageBreak
+         Adding the global options:\MessageBreak
+         \space\space
+              \@ptionlist{\@currname.\@currext},#1\MessageBreak
+         to your \noexpand\documentclass declaration may fix this.%
+         \MessageBreak
+         Try typing \space <return> \space to proceed.}}%
+     \@firstofone}
+%    \end{macrocode}
+% \end{macro}
+%
 %    \begin{macrocode}
 \let\@currpkg at reqd\@empty
 %    \end{macrocode}
diff --git a/base/ltkeys.dtx b/base/ltkeys.dtx
new file mode 100644
index 00000000..ef760c8a
--- /dev/null
+++ b/base/ltkeys.dtx
@@ -0,0 +1,454 @@
+% \iffalse meta-comment
+%
+% Copyright (C) 2021
+% The LaTeX Project and any individual authors listed elsewhere
+% in this 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 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
+%
+% \iffalse
+%%% From File: ltkeys.dtx
+%
+%<*driver>
+% \fi
+\ProvidesFile{ltkeys.dtx}
+             [2021/11/30 v1.0a LaTeX Kernel (Kevyal options)]
+% \iffalse
+\documentclass{l3doc}
+\GetFileInfo{ltkeys.dtx}
+\title{\filename}
+\date{\filedate}
+\author{%
+  \LaTeX{} Team}
+
+\begin{document}
+ \maketitle
+ \DocInput{ltkeys.dtx}
+\end{document}
+%</driver>
+% \fi
+%
+% \section{Creating and using keyval options}
+%
+% As with any key--value input, using key--value pairs as package or
+% class options has two parts: creating the key options and setting (using)
+% them. Options created in this way \emph{may} be used after package loading
+% as general key--value settings: this will depend on the nature of the
+% underlying code.
+%
+% \begin{function}{\DeclareKeys}
+%   \begin{syntax}
+%     \cs{DeclareKeys} \oarg{family} \marg{declarations}
+%   \end{syntax}
+%   Creates a series of options from a comma-separated \meta{declarations} list.
+%   Each entry in this list is a key--value pair, with the \meta{key} having one
+%   or more \meta{properties}. A small number of \enquote{basic}
+%   \meta{properties} are described below. The full range of properties,
+%   provided by \pkg{l3keys}, can also be used for more powerful processing.
+%   See \texttt{interface3} for the full details.
+%   
+%   The basic properties provided here are
+%   \begin{itemize}
+%     \item \texttt{.if} --- sets a \TeX{} \cs{if...} switch
+%     \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
+%       has no limitation on scope (\texttt{general})
+%   \end{itemize}
+%   The part of the \meta{key} before the \meta{property} is the \meta{name},
+%   with the \meta{value} working with the \meta{property} to define the
+%   behaviour of the option.
+%
+%   For example, with
+%   \begin{verbatim}
+%     \DeclareKeys[mypkg]
+%       {
+%         draft.if          = @mypkg at draft      ,
+%         draft.usage       = preamble          ,
+%         name.store        = \@mypkg at name      ,
+%         name.usage        = load              ,
+%         second-name.store = \@mypkg at other@name
+%       }
+%   \end{verbatim}
+%   three options would be create. The option \texttt{draft} can be given
+%   anywhere in the preamble, and will set a switch called \cs{if at mypkg@draft}.
+%   The option \texttt{name} can only be given during package loading, and will
+%   save whatever value it is given in \cs{@mypkg at name}. Finally, the option
+%   \texttt{second-name} can be given anywhere, and will save its value in
+%   \cs{@mypkg at other@name}.
+%
+%   Keys created \emph{before} the use of
+%   \cs{ProcessKeyOptions}/\cs{ProcessKeyPackageOptions} act as package options.
+% \end{function}
+%
+% \begin{function}{\ProcessKeyOptions}
+%   \begin{syntax}
+%     \cs{ProcessKeyOptions} \oarg{family}
+%   \end{syntax}
+%   The \cs{ProcessKeyOptions} function is used to check the current
+%   option list against the keys defined for \meta{family}. Global (class)
+%   options and local (package) options are checked when this function
+%   is called in a package.
+% \end{function}
+%
+% \begin{function}{\ProcessKeyPackageOptions}
+%   \begin{syntax}
+%     \cs{ProcessKeyPackageOptions} \oarg{family}
+%   \end{syntax}
+%   This function works in a similar manner to \cs{ProcessKeyOptions}.
+%   When used in a package, \cs{ProcessKeyPackageOptions}
+%   will not examine any global (class) class options available. In contrast,
+%   \cs{ProcessKeyOptions} does parse class options (in common with the
+%   classical \cs{ProcessOptions} command).
+% \end{function}
+%
+% \begin{function}{\SetKeys}
+%   \begin{syntax}
+%     \cs{SetKeys} \oarg{family} \Arg{keyvals}
+%   \end{syntax}
+%   Sets (applies) the explicit list of \meta{keyvals}  for the \meta{family}:
+%   it the latter is not given, the value of \cs{@currname} used. This command
+%   may be used within a package to set options before or after using
+%   \cs{ProcessKeyOptions}/\cs{ProcessKeyPackageOptions}.
+% \end{function}
+%
+% \StopEventually{}
+%
+% \subsection{Implementation of \pkg{ltkeys}}
+%
+%    \begin{macrocode}
+%<@@=keys>
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+%<*2ekernel>
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+\ExplSyntaxOn
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+\cs_generate_variant:Nn \clist_put_right:Nn { Nv }
+%    \end{macrocode}
+%
+% \begin{macro}{\l_@@_options_clist}
+%   A single list is used for all options, into which they are collected
+%   before processing.
+%    \begin{macrocode}
+\clist_new:N \l_@@_options_clist
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{variable}{\l_@@_options_loading_bool}
+%   Used to indicate we are in the loading phase: controls the outcome
+%   of warnings.
+%    \begin{macrocode}
+\bool_new:N \l_@@_options_loading_bool
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\@@_options:Nn, \@@_options:NV}
+% \begin{macro}{\@@_options_end:}
+%   The main function calls functions to collect up the global and local
+%   options into \cs{l_@@_options_clist} before calling the
+%   underlying functions to actually do the processing. So that a suitable
+%   message is produced if the option is unknown, the special
+%   \texttt{unknown} key is set if it does not already exist for the
+%   current family, and is cleaned up afterwards if required. To allow
+%   the \LaTeXe{} layer to know this mechanism is active, and to deal
+%   with the key family not matching the file name, we store the family
+%   in all cases.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_options:Nn #1#2
+  {
+    \cs_gset_nopar:cpn { opt at fam@\@currname.\@currext } {#2}
+    \cs_set_protected:Npn \@@_option_end: { }
+    \clist_clear:N \l_@@_options_clist
+    \@@_options_global:Nn #1 {#2}
+    \@@_options_local:
+    \keys_if_exist:nnF {#2} { unknown }
+      {
+        \keys_define:nn {#2}
+          {
+            unknown .code:n =
+              {
+                \msg_error:nnxx { keys } { option-unknown }
+                  { \l_keys_key_str } { \@currname }
+              }
+          }
+        \cs_set_protected:Npn \@@_option_end:
+          { \keys_define:nn {#2} { unknown .undefine: } }
+      }
+    \bool_set_true:N \l_@@_options_loading_bool
+    \keys_set:nV {#2} \l_@@_options_clist
+    \bool_set_false:N \l_@@_options_loading_bool
+    \cs_set_eq:NN \@unprocessedoptions \scan_stop:
+    \@@_option_end:
+    \@@_options_loaded:n {#2}
+  }
+\cs_generate_variant:Nn \@@_options:Nn { NV }
+\msg_new:nnnn { keys } { option-unknown }
+  { Unknown~option~'#1'~for~package~#2. }
+  {
+    LaTeX~has~been~asked~to~set~an~option~called~'#1'~
+    but~the~#2~package~has~not~created~an~option~with~this~name.
+  }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_options_global:Nn}
+%   Global (class) options are handled differently for \LaTeXe{} packages
+%   and classes. Hence this function is essentially a check on the current
+%  file type. The initial test is needed as \LaTeXe{} allows variables to
+%   be equal to \cs{scan_stop:}, which is usually forbidden in \pkg{expl3}
+%   code.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_options_global:Nn #1#2
+  {
+    \cs_if_eq:NNF \@classoptionslist \scan_stop:
+      {
+        \cs_if_eq:NNTF \@currext \@clsextension
+          { \@@_options_class:n {#2} }
+          {
+            \bool_if:NT #1
+             { \@@_options_package:n {#2} }
+          }
+      }
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_options_class:n}
+%   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.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_options_class:n #1
+  {
+    \cs_if_free:cF { opt@ \@currname . \@currext }
+      {
+        \keys_if_exist:nnTF {#1} { unknown }
+          {
+            \clist_put_right:Nv \l_@@_options_clist
+              { opt@ \@currname . \@currext }
+          }
+          {
+            \clist_map_inline:cn { opt@ \@currname . \@currext }
+              {
+                \keys_if_exist:neTF
+                  {#1} { \@@_remove_equals:n {##1} }
+                  { \clist_put_right:Nn \l_@@_options_clist {##1} }
+                  { \clist_put_right:Nn \@unusedoptionlist {##1} }
+              }
+          }
+      }
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_options_package:n}
+%   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
+%   option also needs to be removed from \cs{@unusedoptionlist}.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_options_package:n #1
+  {
+    \clist_map_inline:Nn \@classoptionslist
+      {
+        \keys_if_exist:neT {#1} { \@@_remove_equals:n {##1} }
+          {
+            \clist_put_right:Nn \l_@@_options_clist {##1}
+            \clist_remove_all:Nn \@unusedoptionlist {##1}
+          }
+      }
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_options_local:}
+%   If local options are found, the are added to the processing list.
+%   \LaTeXe{} stores options for each file in a macro which may or may not
+%   exist, hence the need to use \cs{cs_if_exist:c}.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_options_local:
+  {
+    \cs_if_eq:NNF \@currext \@clsextension
+      {
+        \cs_if_exist:cT { opt@ \@currname . \@currext }
+          {
+            \clist_put_right:Nv \l_@@_options_clist
+              { opt@ \@currname . \@currext }
+          }
+      }
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_remove_equals:n}
+% \begin{macro}[EXP]{\@@_remove_equals:w}
+%   As the name suggests, this is a simple function to remove an equals
+%   sign from the input. This is all wrapped up in an \texttt{n} function
+%   so that there will always be a sign available.
+%    \begin{macrocode}
+\cs_new:Npn \@@_remove_equals:n #1
+  { \@@_remove_equals:w #1 = \s_@@_stop }
+\cs_new:Npn \@@_remove_equals:w #1 = #2 \s_@@_stop { \exp_not:n {#1} }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{The document interfaces}
+%
+% \begin{macro}{\DeclareKeys}
+%   Defining key options is quite straight-forward: we have an intermediate
+%   function to allow for potential set-up steps.
+%    \begin{macrocode}
+\NewDocumentCommand \DeclareKeys { o +m }
+  {
+    \IfNoValueTF {#1}
+      { \exp_args:NV \keys_define:nn \@currname }
+      { \keys_define:nn {#1} }
+        {#2}
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\ProcessKeyOptions, \ProcessKeyPackageOptions}
+%   We need to deal with the older interface from \pkg{l3keys2e} here: it had
+%   a mandatory argument. We can mop that up using a look-ahead, and then
+%   exploit that information to determine whether the package option handling
+%   is set up for the new approach for clash handling.
+%    \begin{macrocode}
+\NewDocumentCommand \ProcessKeyOptions { o }
+  {
+    \IfNoValueTF {#1}
+      { \@@_options:NV \c_true_bool \@currname }
+      { \@@_options:Nn \c_true_bool {#1} }
+  }
+\NewDocumentCommand \ProcessKeyPackageOptions { o }
+  {
+    \IfNoValueTF {#1}
+      { \@@_options:NV \c_false_bool \@currname }
+      { \@@_options:Nn \c_false_bool {#1} }
+  }
+\@onlypreamble \ProcessKeyOptions
+\@onlypreamble \ProcessKeyPackageOptions
+%    \end{macrocode}
+% \end{macro}
+%
+% \subsection{Option usage scope}
+%
+% \begin{macro}{\@@_options_loaded:n}
+% \begin{macro}{\@@_options_loaded:nn}
+%   Indicates that the load-time options for a package have been processed:
+%   once this has happened, make them unavailable either with a warning or
+%   an error.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_options_loaded:n #1
+  {
+    \prop_get:NnNT \l_keys_usage_load_prop {#1} \l_@@_tmpa_tl
+      {
+        \clist_map_inline:Nn \l_@@_tmpa_tl
+          {
+            \keys_define:nn {#1}
+              {
+                ##1 .code:n =
+                  \@@_options_loaded:nn {#1} {##1}
+              }
+          }
+      }
+  }
+\cs_new_protected:Npn \@@_options_loaded:nn #1#2
+  {
+    \bool_if:NTF \l_@@_options_loading_bool
+      {
+        \msg_warning:nnxx { keys } { load-option-ignored }
+          { \use:c { opt at fam@\@currname.\@currext } } {#2}
+      }
+      { \msg_error:nnnn { keys } { load-only } {#1} {#2} }
+  }
+\msg_new:nnn { keys } { load-option-ignored }
+  { Package~"#1"~has~already~been~loaded:~ignoring~load-time~option~"#2". }
+\msg_new:nnnn { keys } { load-only }
+  { Key~"#2"~may~only~be~used~in~the~during~loading~of~package~"#1". }
+  {
+    LaTeX~was~asked~to~set~a~key~called~"#2",~but~this~is~only~allowed~
+    in~the~optional~argument~when~loading~package~"#1".
+  }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+%  Disable all preamble options in one shot.
+%    \begin{macrocode}
+\tl_gput_left:Nn \@kernel at after@begindocument
+  {
+    \prop_map_inline:Nn \l_keys_usage_preamble_prop
+      {
+        \clist_map_inline:nn {#2}
+          {
+            \keys_define:nn {#1}
+              {
+                ##1 .code:n =
+                  \msg_error:nnn { keys } { preamble-only } {##1}
+              }
+          }
+      }
+  }
+\msg_new:nnnn { keys } { preamble-only }
+  { Key~"#1"~may~only~be~used~in~the~preamble. }
+  {
+    LaTeX~was~asked~to~set~a~key~called~"#1",~but~this~is~only~allowed~
+    before~\begin{document}.~You~will~need~to~set~the~key~earlier.
+  }
+%    \end{macrocode}
+%
+% \subsection{General key setting}
+%
+% \begin{macro}{\SetKeys}
+%   A simple wrapper.
+%    \begin{macrocode}
+\NewDocumentCommand \SetKeys { o +m }
+  {
+    \IfNoValueTF {#1}
+      { \keys_set:Vn \@currname }
+      { \keys_set:nn {#1} }
+        {#2}
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+%    \begin{macrocode}
+\ExplSyntaxOff
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+%</2ekernel>
+%    \end{macrocode}
\ No newline at end of file
diff --git a/base/testfiles-lthooks/ltcmdhooks-001.tlg b/base/testfiles-lthooks/ltcmdhooks-001.tlg
index ae69a703..e8396389 100644
--- a/base/testfiles-lthooks/ltcmdhooks-001.tlg
+++ b/base/testfiles-lthooks/ltcmdhooks-001.tlg
@@ -58,7 +58,7 @@ l. ...\ShowHook{cmd/foo/after}
 #1#2->FOO #1 #2.
 l. ...\show\foo
 > \@kernel at after@begindocument=macro:
-->\__hook_cmd_begindocument_code: \bool_gset_true:N \g__pdf_init_bool .
+->\prop_map_inline:Nn \l_keys_usage_preamble_prop {\clist_map_inline:nn {##2}{\keys_define:nn {##1}{####1.code:n=\msg_error:nnn {keys}{preamble-only}{####1}}}}\__hook_cmd_begindocument_code: \bool_gset_true:N \g__pdf_init_bool .
 l. ...\show\@kernel at after@begindocument
 Update code for hook 'para/before' on input line ...:
 Update code for hook 'para/after' on input line ...:
diff --git a/base/testfiles/github-0710.lvt b/base/testfiles/github-0710.lvt
new file mode 100644
index 00000000..0a2c8e17
--- /dev/null
+++ b/base/testfiles/github-0710.lvt
@@ -0,0 +1,50 @@
+\documentclass{article}
+\begin{filecontents}[overwrite]{mypkg.sty}
+\DeclareKeys{
+  load-option-A .store = \my at A ,
+  load-option-A .usage = load  ,
+  load-option-B .store = \my at B ,
+  load-option-B .usage = load  ,
+  general-option-C .store = \my at C ,
+  preamble-option-D .store = \my at D ,
+  preamble-option-D .usage = preamble
+}
+\ProcessKeyOptions
+\newcommand\mypkgtest{%
+  \begingroup
+    \edef\x{\my at A:\my at B:\my at C:\my at D}%
+    \show\x
+  \endgroup
+}
+\end{filecontents}
+\input{test2e}
+
+\START
+\AUTHOR{Joseph Wright}
+
+\usepackage[load-option-A = 111]{mypkg}                           % 1
+\mypkgtest
+\usepackage[load-option-A = 222]{mypkg}                           % 2
+\mypkgtest
+\usepackage[load-option-A = 333, general-option-C  = 333]{mypkg}  % 3
+\mypkgtest
+\usepackage{mypkg}                                                % 4
+\mypkgtest
+\usepackage[load-option-B = 555]{mypkg}                           % 5
+\mypkgtest
+\SetKeys[mypkg]{load-option-A = 666}                       % 6
+\mypkgtest
+\usepackage[preamble-option-D = 777]{mypkg}                       % 7
+\mypkgtest
+\SetKeys[mypkg]{preamble-option-D = 888}                   % 8
+\mypkgtest
+
+\OMIT
+\begin{document}
+\TIMO
+
+\SetKeys[mypkg]{general-option-C = 999,
+  preamble-option-D = 999} % 9
+\mypkgtest
+
+\END
diff --git a/base/testfiles/github-0710.tlg b/base/testfiles/github-0710.tlg
new file mode 100644
index 00000000..58c2c762
--- /dev/null
+++ b/base/testfiles/github-0710.tlg
@@ -0,0 +1,70 @@
+This is a generated file for the LaTeX2e validation system.
+Don't change this file in any respect.
+Author: Joseph Wright
+(mypkg.sty)
+> \x=macro:
+->111:::.
+\mypkgtest ...my at A :\my at B :\my at C :\my at D }\show \x 
+                                                  \endgroup 
+l. ...\mypkgtest
+LaTeX3 Warning: Package "mypkg" has already been loaded: ignoring load-time
+(LaTeX3)        option "load-option-A".
+> \x=macro:
+->111:::.
+\mypkgtest ...my at A :\my at B :\my at C :\my at D }\show \x 
+                                                  \endgroup 
+l. ...\mypkgtest
+LaTeX3 Warning: Package "mypkg" has already been loaded: ignoring load-time
+(LaTeX3)        option "load-option-A".
+> \x=macro:
+->111::333:.
+\mypkgtest ...my at A :\my at B :\my at C :\my at D }\show \x 
+                                                  \endgroup 
+l. ...\mypkgtest
+> \x=macro:
+->111::333:.
+\mypkgtest ...my at A :\my at B :\my at C :\my at D }\show \x 
+                                                  \endgroup 
+l. ...\mypkgtest
+LaTeX3 Warning: Package "mypkg" has already been loaded: ignoring load-time
+(LaTeX3)        option "load-option-B".
+> \x=macro:
+->111::333:.
+\mypkgtest ...my at A :\my at B :\my at C :\my at D }\show \x 
+                                                  \endgroup 
+l. ...\mypkgtest
+! LaTeX3 Error: Key "load-option-A" may only be used in the during loading of
+(LaTeX3)        package "mypkg".
+For immediate help type H <return>.
+ ...                                              
+l. ...\SetKeys[mypkg]{load-option-A = 666}
+                                                                % 6
+LaTeX was asked to set a key called "load-option-A", but this is only allowed
+in the optional argument when loading package "mypkg".
+> \x=macro:
+->111::333:.
+\mypkgtest ...my at A :\my at B :\my at C :\my at D }\show \x 
+                                                  \endgroup 
+l. ...\mypkgtest
+> \x=macro:
+->111::333:777.
+\mypkgtest ...my at A :\my at B :\my at C :\my at D }\show \x 
+                                                  \endgroup 
+l. ...\mypkgtest
+> \x=macro:
+->111::333:888.
+\mypkgtest ...my at A :\my at B :\my at C :\my at D }\show \x 
+                                                  \endgroup 
+l. ...\mypkgtest
+! LaTeX3 Error: Key "preamble-option-D" may only be used in the preamble.
+For immediate help type H <return>.
+ ...                                              
+l. ...  preamble-option-D = 999}
+                                % 9
+LaTeX was asked to set a key called "preamble-option-D", but this is only
+allowed before \begin {document}. You will need to set the key earlier.
+> \x=macro:
+->111::999:888.
+\mypkgtest ...my at A :\my at B :\my at C :\my at D }\show \x 
+                                                  \endgroup 
+l. ...\mypkgtest
diff --git a/base/testfiles/tlb-rollback-005.luatex.tlg b/base/testfiles/tlb-rollback-005.luatex.tlg
index a15b1e85..b8fe3e6c 100644
--- a/base/testfiles/tlb-rollback-005.luatex.tlg
+++ b/base/testfiles/tlb-rollback-005.luatex.tlg
@@ -1,7 +1,7 @@
 This is a generated file for the l3build validation system.
 Don't change this file in any respect.
 > \@kernel at after@begindocument=macro:
-->\__hook_cmd_begindocument_code: \bool_gset_true:N \g__pdf_init_bool .
+->\prop_map_inline:Nn \l_keys_usage_preamble_prop {\clist_map_inline:nn {##2}{\keys_define:nn {##1}{####1.code:n=\msg_error:nnn {keys}{preamble-only}{####1}}}}\__hook_cmd_begindocument_code: \bool_gset_true:N \g__pdf_init_bool .
 l. ...\makeatletter\show\@kernel at after@begindocument
                                                 \makeatother
 (latexrelease.sty
@@ -709,7 +709,7 @@ Applying: [....-..-..] UTF-8 default on input line ....
 Already applied: [....-..-..] UTF-8 default on input line ....
 )
 > \@kernel at after@begindocument=macro:
-->\__hook_cmd_begindocument_code: \bool_gset_true:N \g__pdf_init_bool .
+->\prop_map_inline:Nn \l_keys_usage_preamble_prop {\clist_map_inline:nn {##2}{\keys_define:nn {##1}{####1.code:n=\msg_error:nnn {keys}{preamble-only}{####1}}}}\__hook_cmd_begindocument_code: \bool_gset_true:N \g__pdf_init_bool .
 l. ...\makeatletter\show\@kernel at after@begindocument
                                                  \makeatother
 (minimal.cls
diff --git a/base/testfiles/tlb-rollback-005.tlg b/base/testfiles/tlb-rollback-005.tlg
index 7e870ab6..62104dca 100644
--- a/base/testfiles/tlb-rollback-005.tlg
+++ b/base/testfiles/tlb-rollback-005.tlg
@@ -1,7 +1,7 @@
 This is a generated file for the l3build validation system.
 Don't change this file in any respect.
 > \@kernel at after@begindocument=macro:
-->\__hook_cmd_begindocument_code: \bool_gset_true:N \g__pdf_init_bool .
+->\prop_map_inline:Nn \l_keys_usage_preamble_prop {\clist_map_inline:nn {##2}{\keys_define:nn {##1}{####1.code:n=\msg_error:nnn {keys}{preamble-only}{####1}}}}\__hook_cmd_begindocument_code: \bool_gset_true:N \g__pdf_init_bool .
 l. ...\makeatletter\show\@kernel at after@begindocument
                                                   \makeatother
 (latexrelease.sty
@@ -1132,7 +1132,7 @@ Now handling font encoding U ...
 Already applied: [....-..-..] UTF-8 default on input line ....
 )
 > \@kernel at after@begindocument=macro:
-->\__hook_cmd_begindocument_code: \bool_gset_true:N \g__pdf_init_bool .
+->\prop_map_inline:Nn \l_keys_usage_preamble_prop {\clist_map_inline:nn {##2}{\keys_define:nn {##1}{####1.code:n=\msg_error:nnn {keys}{preamble-only}{####1}}}}\__hook_cmd_begindocument_code: \bool_gset_true:N \g__pdf_init_bool .
 l. ......eatletter\show\@kernel at after@begindocument
                                                   \makeatother
 (minimal.cls
diff --git a/base/testfiles/tlb-rollback-005.xetex.tlg b/base/testfiles/tlb-rollback-005.xetex.tlg
index 84352cad..3247a60a 100644
--- a/base/testfiles/tlb-rollback-005.xetex.tlg
+++ b/base/testfiles/tlb-rollback-005.xetex.tlg
@@ -1,7 +1,7 @@
 This is a generated file for the l3build validation system.
 Don't change this file in any respect.
 > \@kernel at after@begindocument=macro:
-->\__hook_cmd_begindocument_code: \bool_gset_true:N \g__pdf_init_bool .
+->\prop_map_inline:Nn \l_keys_usage_preamble_prop {\clist_map_inline:nn {##2}{\keys_define:nn {##1}{####1.code:n=\msg_error:nnn {keys}{preamble-only}{####1}}}}\__hook_cmd_begindocument_code: \bool_gset_true:N \g__pdf_init_bool .
 l. ...\makeatletter\show\@kernel at after@begindocument
                                                   \makeatother
 (latexrelease.sty
@@ -706,7 +706,7 @@ Applying: [....-..-..] UTF-8 default on input line ....
 Already applied: [....-..-..] UTF-8 default on input line ....
 )
 > \@kernel at after@begindocument=macro:
-->\__hook_cmd_begindocument_code: \bool_gset_true:N \g__pdf_init_bool .
+->\prop_map_inline:Nn \l_keys_usage_preamble_prop {\clist_map_inline:nn {##2}{\keys_define:nn {##1}{####1.code:n=\msg_error:nnn {keys}{preamble-only}{####1}}}}\__hook_cmd_begindocument_code: \bool_gset_true:N \g__pdf_init_bool .
 l. ......eatletter\show\@kernel at after@begindocument
                                                   \makeatother
 (minimal.cls





More information about the latex3-commits mailing list.