https://github.com/latex3/latex2e
Tip revision: 45f16180ee26bc5db3717f66cc2ce56e9bdc9942 authored by Joseph Wright on 17 July 2022, 09:28:53 UTC
Step pre-release tag
Step pre-release tag
Tip revision: 45f1618
ltkeys.dtx
% \iffalse meta-comment
%
% Copyright (C) 2021-2022
% 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}
[2022/07/05 v1.0i 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{.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{.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@draft ,
% draft.usage = preamble ,
% name.store = \@mypkg@name ,
% name.usage = load ,
% second-name.store = \@mypkg@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@mypkg@draft}.
% The option \texttt{name} can only be given during package loading, and will
% save whatever value it is given in \cs{@mypkg@name}. Finally, the option
% \texttt{second-name} can be given anywhere, and will save its value in
% \cs{@mypkg@other@name}.
%
% Keys created \emph{before} the use of \cs{ProcessKeyOptions}act as
% package options.
% \end{function}
%
% \begin{function}{\DeclareUnknownKeyHandler}
% \begin{syntax}
% \cs{DeclareUnknownKeyHandler} \oarg{family} \marg{code}
% \end{syntax}
% The function \cs{DeclareUnknownKeyHandler} may be used to define
% the behavior when an undefined key is encountered. The \meta{code}
% will receive the unknown key name as |#1| and the value as |#2|.
% These can then be processed as appropriate, e.g.~by forwarding
% to another package.
% \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}{\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}.
% \end{function}
%
% \StopEventually{}
%
% \subsection{Implementation of \pkg{ltkeys}}
%
% \begin{macrocode}
%<@@=keys>
% \end{macrocode}
%
% \begin{macrocode}
%<*2ekernel>
% \end{macrocode}
%
% \begin{macrocode}
\ExplSyntaxOn
% \end{macrocode}
%
% \subsection{Key properties}
%
% \begin{macro}{.code, .if, .ifnot, .store, .usage}
% \changes{v1.0b}{2022/02/05}
% {Create properties in \texttt{ltlkeys}}
% \changes{v1.0c}{2022/02/07}
% {Correct \texttt.{.code} property}
% \changes{v1.0i}{2022/06/22}
% {Add \texttt.{.notif} property}
% \begin{macrocode}
\group_begin:
\cs_set_protected:Npn \@@_tmp:nn #1#2
{
\quark_if_recursion_tail_stop:n {#1}
\cs_new_eq:cc
{ \c_@@_props_root_str . #2 }
{ \c_@@_props_root_str . #1 }
\@@_tmp:nn
}
\@@_tmp:nn
{ code:n } { code }
{ legacy_if_set:n } { if }
{ legacy_if_set_inverse:n } { ifnot }
{ tl_set:N } { store }
{ usage:n } { usage }
{ \q_recursion_tail } { }
\q_recursion_stop
\group_end:
% \end{macrocode}
% \end{macro}
%
% \subsection{Main mechanism}
%
% \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:n}
% \changes{v1.0f}{2022/03/18}{Simplify to always cover global options}
% \begin{macro}{\@@_options_aux:n}
% \changes{v1.0b}{2022/01/15}
% {Clear option list in end-of-package hook}
% \changes{v1.0i}{2022/07/05}{Support \cs{CurrentOption}}
% \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:n #1
{ \@@_options_expand_module:Nn \@@_options_aux:n {#1} }
\cs_new_protected:Npn \@@_options_aux:n #1
{
\cs_gset_nopar:cpn { opt@fam@\@currname.\@currext } {#1}
\cs_set_protected:Npn \@@_option_end: { }
\clist_clear:N \l_@@_options_clist
\@@_options_global:n {#1}
\@@_options_local:
\keys_if_exist:nnF {#1} { unknown }
{
\keys_define:nn {#1}
{
unknown .code:n =
{
\msg_error:nnxx { keys } { option-unknown }
{ \l_keys_key_str } { \@currname }
}
}
\cs_set_protected:Npn \@@_option_end:
{ \keys_define:nn {#1} { unknown .undefine: } }
}
\bool_set_true:N \l_@@_options_loading_bool
\clist_map_variable:NNn \l_@@_options_clist \CurrentOption
{ \keys_set:nV {#1} \CurrentOption }
\bool_set_false:N \l_@@_options_loading_bool
\AtEndOfPackage { \cs_set_eq:NN \@unprocessedoptions \scan_stop: }
\@@_option_end:
\@@_options_loaded:n {#1}
}
\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}
% \end{macro}
%
% \begin{macro}{\@@_options_global:n}
% \changes{v1.0f}{2022/03/18}{Simplify to always cover global options}
% \changes{v1.0h}{2022/06/20}{Use raw options data}
% 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:n #1
{
\cs_if_eq:NNF \@raw@classoptionslist \scan_stop:
{
\cs_if_eq:NNTF \@currext \@clsextension
{ \@@_options_class:n {#1} }
{ \@@_options_package:n {#1} }
}
}
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_options_class:n}
% \changes{v1.0g}{2022/06/16}{Better handling of option removal}
% \changes{v1.0h}{2022/06/19}{Further work on handling of option removal}
% \changes{v1.0h}{2022/06/20}{Use raw options data}
% \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}
% 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 { @raw@opt@ \@currname . \@currext }
{
\keys_if_exist:nnTF {#1} { unknown }
{
\clist_put_right:Nv \l_@@_options_clist
{ @raw@opt@ \@currname . \@currext }
}
{
\clist_map_inline:cn { @raw@opt@ \@currname . \@currext }
{
\exp_args:Ne \@@_options_class:nnn
{ \@@_remove_equals:n {##1} }
{##1} {#1}
}
}
}
}
\cs_new_protected:Npn \@@_options_class:nnn #1#2#3
{
\keys_if_exist:nnTF {#3} {#1}
{ \clist_put_right:Nn \l_@@_options_clist {#2} }
{
\str_if_eq:eeF
{ \exp_not:v { @raw@opt@ \@currname . \@currext } }
{ \exp_not:V \@raw@classoptionslist }
{ \clist_put_right:Nn \@unusedoptionlist {#1} }
}
}
% \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\@@_options_package:n}
% \changes{v1.0g}{2022/06/16}{Better handling of option removal}
% \changes{v1.0h}{2022/06/19}{Further work on handling of option removal}
% \changes{v1.0h}{2022/06/20}{Use raw options data}
% \begin{macro}{\@@_options_package:nnn}
% \changes{v1.0h}{2022/06/19}{New function}
% 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 \@raw@classoptionslist
{
\exp_args:Ne \@@_options_package:nnn
{ \@@_remove_equals:n {##1} }
{##1} {#1}
}
}
\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}
}
}
% \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\@@_options_local:}
% \changes{v1.0h}{2022/06/20}{Use raw options data}
% If local options are found, they 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 { @raw@opt@ \@currname . \@currext }
{
\clist_put_right:Nv \l_@@_options_clist
{ @raw@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{macrocode}
\cs_generate_variant:Nn \keys_define:nn { nx }
% \end{macrocode}
%
% \begin{macro}{\@@_options_expand_module:Nn}
% \changes{v1.0e}{2022/02/21}
% {Faster approach to module expansion}
% \begin{macro}{\@@_options_expand_module:nN}
% To deal with active characters inside the module argument whilst also
% expanding that argument, we use a combination of \texttt{c}- and
% \texttt{f}-type expansion. This works as the definitions for active
% UTF-8 bytes contain an \cs{ifincsname} test.
% \begin{macrocode}
\cs_new_protected:Npn \@@_options_expand_module:Nn #1#2
{
\cs:w @@_options_expand_module:nN \use:e { \cs_end: {#2} } #1
}
\cs_new_protected:Npn \@@_options_expand_module:nN #1#2
{ #2 {#1} }
% \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\DeclareKeys}
% \changes{v1.0c}{2022/02/15}{Expand module argument}
% \changes{v1.0d}{2022/02/16}{Allow for active characters in module argument}
% Defining key options is quite straight-forward: we have an intermediate
% function to allow for potential set-up steps.
% \begin{macrocode}
\NewDocumentCommand \DeclareKeys { O { \@currname } +m }
{ \@@_options_expand_module:Nn \keys_define:nn {#1} {#2} }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\DeclareUnknownKeyHandler}
% \changes{v1.0c}{2022/02/15}{Added \cs{DeclareUnknownKeysHandler}}
% \changes{v1.0d}{2022/02/16}{Allow for active characters in module argument}
% \changes{v1.0d}{2022/02/16}
% {Rename \cs{DeclareUnknownKeysHandler} to \cs{DeclareUnknownKeyHandler}}
% \begin{macrocode}
\NewDocumentCommand \DeclareUnknownKeyHandler { O { \@currname } +m }
{
\cs_set_protected:cpn { @@_unknown_handler_ #1 :nn } ##1##2 {#2}
\@@_options_expand_module:Nn \keys_define:nx {#1}
{
unknown .code:n =
\exp_not:N \exp_args:NV
\exp_not:c { @@_unknown_handler_ #1 :nn }
\exp_not:N \l_keys_key_str {####1}
}
}
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\ProcessKeyOptions}
% \changes{v1.0c}{2022/02/15}{Expand module argument}
% \changes{v1.0d}{2022/02/16}{Allow for active characters in module argument}
% \changes{v1.0f}{2022/03/18}{Remove \cs{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 { \@currname } }
{ \@@_options:n {#1} }
\@onlypreamble \ProcessKeyOptions
% \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@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@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}
% \changes{v1.0c}{2022/02/15}{Expand module argument}
% \changes{v1.0d}{2022/02/16}{Allow for active characters in module argument}
% A simple wrapper.
% \begin{macrocode}
\NewDocumentCommand \SetKeys { O { \@currname } +m }
{ \@@_options_expand_module:Nn \keys_set:nn {#1} {#2} }
% \end{macrocode}
% \end{macro}
%
% \begin{macrocode}
\ExplSyntaxOff
% \end{macrocode}
%
% \begin{macrocode}
%</2ekernel>
% \end{macrocode}