diff options
Diffstat (limited to 'macros/latex/base/ltkeys.dtx')
-rw-r--r-- | macros/latex/base/ltkeys.dtx | 518 |
1 files changed, 518 insertions, 0 deletions
diff --git a/macros/latex/base/ltkeys.dtx b/macros/latex/base/ltkeys.dtx new file mode 100644 index 0000000000..00c2600289 --- /dev/null +++ b/macros/latex/base/ltkeys.dtx @@ -0,0 +1,518 @@ +% \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/02/21 v1.0f 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{.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, .store, .usage} +% \changes{v1.0b}{2022/02/05} +% {Create properties in \texttt{ltlkeys}} +% \changes{v1.0c}{2022/02/07} +% {Correct \texttt.{.code} 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 } + { 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} +% \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 + \keys_set:nV {#1} \l_@@_options_clist + \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} +% 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 \@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} +% 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{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} |