diff options
Diffstat (limited to 'macros/latex-dev/base/ltkeys.dtx')
-rw-r--r-- | macros/latex-dev/base/ltkeys.dtx | 454 |
1 files changed, 454 insertions, 0 deletions
diff --git a/macros/latex-dev/base/ltkeys.dtx b/macros/latex-dev/base/ltkeys.dtx new file mode 100644 index 0000000000..3c58585de9 --- /dev/null +++ b/macros/latex-dev/base/ltkeys.dtx @@ -0,0 +1,454 @@ +% \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} + [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@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}/\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@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@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} +% 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} |