% \iffalse meta-comment % % Copyright (C) 2021-2024 % 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} [2024/01/13 v1.0m LaTeX Kernel (Keyval options)] % \iffalse \documentclass{l3doc} \GetFileInfo{ltkeys.dtx} \title{\filename} \date{\filedate} \author{% \LaTeX{} Team} \begin{document} \maketitle \DocInput{ltkeys.dtx} \end{document} % % \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.0l}{2022/10/20} % {Define key option handler in \pkg{ltkeys}} % \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_protected:cpn { opt@handler@\@currname.\@currext } { \ProcessKeyOptions [ #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} } % \end{macrocode} % \changes{v1.0j}{2022/07/23} % {Output `public' package name in messages} % \begin{macrocode} \msg_new:nnnn { keys } { option-unknown } { Unknown~option~'#1'~for~package~#2. } { LaTeX~has~been~asked~to~set~an~option~called~'#1'~ but~the~package~"\msg_module_name:n {#2}"~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} % \changes{v1.0m}{2024/01/13}{Trim spaces off key names} % \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} % 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 { \tl_trim_spaces:e { \@@_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} \clist_remove_all:Nn \@unusedoptionlist {#1} } { \clist_if_in:NnF \@unusedoptionlist {#1} { \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} % \changes{v1.0m}{2024/01/13}{Trim spaces off key names} % \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 { \tl_trim_spaces:e { \@@_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} % \changes{v1.0l}{2022/10/20}{Correct an argument for a message} % 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:nnnn { keys } { load-option-ignored } } { \msg_error:nnnn { keys } { load-only } } {#1} {#2} } % \end{macrocode} % \changes{v1.0j}{2022/07/23} % {Output `public' package name in messages} % \begin{macrocode} \msg_new:nnn { keys } { load-option-ignored } { Package~"\msg_module_name:n {#1}"~has~already~been~loaded:~ ignoring~load-time~option~"#2". } % \end{macrocode} % \changes{v1.0j}{2022/07/23} % {Output `public' package name in messages} % \changes{v1.0k}{2022/08/21} % {Correct error message} % \begin{macrocode} \msg_new:nnnn { keys } { load-only } { Key~"#2"~may~only~be~used~during~loading~of~package~ "\msg_module_name:n {#1}". } { LaTeX~was~asked~to~set~a~key~called~"#2",~but~this~is~only~allowed~ in~the~optional~argument~when~loading~package~"\msg_module_name:n{#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} % % \end{macrocode}