From e0c6872cf40896c7be36b11dcc744620f10adf1d Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Mon, 2 Sep 2019 13:46:59 +0900 Subject: Initial commit --- macros/latex/contrib/l3kernel/l3keys.dtx | 2831 ++++++++++++++++++++++++++++++ 1 file changed, 2831 insertions(+) create mode 100644 macros/latex/contrib/l3kernel/l3keys.dtx (limited to 'macros/latex/contrib/l3kernel/l3keys.dtx') diff --git a/macros/latex/contrib/l3kernel/l3keys.dtx b/macros/latex/contrib/l3kernel/l3keys.dtx new file mode 100644 index 0000000000..074ca85b6e --- /dev/null +++ b/macros/latex/contrib/l3kernel/l3keys.dtx @@ -0,0 +1,2831 @@ +% \iffalse meta-comment +% +%% File: l3keys.dtx +% +% Copyright (C) 2006-2019 The LaTeX3 Project +% +% It may be distributed and/or modified under the conditions of the +% LaTeX Project Public License (LPPL), either version 1.3c of this +% license or (at your option) any later version. The latest version +% of this license is in the file +% +% https://www.latex-project.org/lppl.txt +% +% This file is part of the "l3kernel bundle" (The Work in LPPL) +% and all files in that bundle must be distributed together. +% +% ----------------------------------------------------------------------- +% +% The development version of the bundle can be found at +% +% https://github.com/latex3/latex3 +% +% for those people who are interested. +% +%<*driver> +\documentclass[full,kernel]{l3doc} +\begin{document} + \DocInput{\jobname.dtx} +\end{document} +% +% \fi +% +% \title{^^A +% The \pkg{l3keys} package\\ Key--value interfaces^^A +% } +% +% \author{^^A +% The \LaTeX3 Project\thanks +% {^^A +% E-mail: +% \href{mailto:latex-team@latex-project.org} +% {latex-team@latex-project.org}^^A +% }^^A +% } +% +% \date{Released 2019-08-25} +% +% \maketitle +% +% \begin{documentation} +% +% The key--value method is a popular system for creating large numbers +% of settings for controlling function or package behaviour. The +% system normally results in input of the form +% \begin{verbatim} +% \MyModuleSetup{ +% key-one = value one, +% key-two = value two +% } +% \end{verbatim} +% or +% \begin{verbatim} +% \MyModuleMacro[ +% key-one = value one, +% key-two = value two +% ]{argument} +% \end{verbatim} +% for the user. +% +% The high level functions here are intended as a method to create +% key--value controls. Keys are themselves created using a key--value +% interface, minimising the number of functions and arguments +% required. Each key is created by setting one or more \emph{properties} +% of the key: +% \begin{verbatim} +% \keys_define:nn { mymodule } +% { +% key-one .code:n = code including parameter #1, +% key-two .tl_set:N = \l_mymodule_store_tl +% } +% \end{verbatim} +% These values can then be set as with other key--value approaches: +% \begin{verbatim} +% \keys_set:nn { mymodule } +% { +% key-one = value one, +% key-two = value two +% } +% \end{verbatim} +% +% At a document level, \cs{keys_set:nn} is used within a +% document function, for example +% \begin{verbatim} +% \DeclareDocumentCommand \MyModuleSetup { m } +% { \keys_set:nn { mymodule } { #1 } } +% \DeclareDocumentCommand \MyModuleMacro { o m } +% { +% \group_begin: +% \keys_set:nn { mymodule } { #1 } +% % Main code for \MyModuleMacro +% \group_end: +% } +% \end{verbatim} +% +% Key names may contain any tokens, as they are handled internally +% using \cs{tl_to_str:n}. As discussed in +% section~\ref{sec:l3keys:subdivision}, it is suggested that the character +% |/| is reserved for sub-division of keys into logical +% groups. Functions and variables are \emph{not} expanded when creating +% key names, and so +% \begin{verbatim} +% \tl_set:Nn \l_mymodule_tmp_tl { key } +% \keys_define:nn { mymodule } +% { +% \l_mymodule_tmp_tl .code:n = code +% } +% \end{verbatim} +% creates a key called |\l_mymodule_tmp_tl|, and not one called +% \texttt{key}. +% +% \section{Creating keys} +% +% \begin{function}[updated = 2017-11-14]{\keys_define:nn} +% \begin{syntax} +% \cs{keys_define:nn} \Arg{module} \Arg{keyval list} +% \end{syntax} +% Parses the \meta{keyval list} and defines the keys listed there for +% \meta{module}. The \meta{module} name is treated as a string. +% In practice the +% \meta{module} should be chosen to be unique to the module in question +% (unless deliberately adding keys to an existing module). +% +% The \meta{keyval list} should consist of one or more key names along +% with an associated key \emph{property}. The properties of a key +% determine how it acts. The individual properties are described +% in the following text; a typical use of \cs{keys_define:nn} might +% read +% \begin{verbatim} +% \keys_define:nn { mymodule } +% { +% keyname .code:n = Some~code~using~#1, +% keyname .value_required:n = true +% } +% \end{verbatim} +% where the properties of the key begin from the |.| after the key +% name. +% \end{function} +% +% The various properties available take either no arguments at +% all, or require one or more arguments. This is indicated in the +% name of the property using an argument specification. In the following +% discussion, each property is illustrated attached to an +% arbitrary \meta{key}, which when used may be supplied with a +% \meta{value}. All key \emph{definitions} are local. +% +% Key properties are applied in the reading order and so the ordering +% is significant. Key properties which define \enquote{actions}, such +% as |.code:n|, |.tl_set:N|, \emph{etc.}, override one another. +% Some other properties are mutually exclusive, notably |.value_required:n| +% and |.value_forbidden:n|, and so they replace one another. However, +% properties covering non-exclusive behaviours may be given in any order. Thus +% for example the following definitions are equivalent. +% \begin{verbatim} +% \keys_define:nn { mymodule } +% { +% keyname .code:n = Some~code~using~#1, +% keyname .value_required:n = true +% } +% \keys_define:nn { mymodule } +% { +% keyname .value_required:n = true, +% keyname .code:n = Some~code~using~#1 +% } +% \end{verbatim} +% Note that with the exception of the special |.undefine:| property, all +% key properties define the key within the current \TeX{} scope. +% +% \begin{function}[updated = 2013-07-08] +% {.bool_set:N, .bool_set:c, .bool_gset:N, .bool_gset:c} +% \begin{syntax} +% \meta{key} .bool_set:N = \meta{boolean} +% \end{syntax} +% Defines \meta{key} to set \meta{boolean} to \meta{value} (which +% must be either \texttt{true} or \texttt{false}). If the variable +% does not exist, it will be created globally at the point that +% the key is set up. +% \end{function} +% +% \begin{function}[added = 2011-08-28, updated = 2013-07-08] +% { +% .bool_set_inverse:N, .bool_set_inverse:c, +% .bool_gset_inverse:N, .bool_gset_inverse:c +% } +% \begin{syntax} +% \meta{key} .bool_set_inverse:N = \meta{boolean} +% \end{syntax} +% Defines \meta{key} to set \meta{boolean} to the logical +% inverse of \meta{value} (which must be either \texttt{true} or +% \texttt{false}). +% If the \meta{boolean} does not exist, it will be created globally +% at the point that the key is set up. +% \end{function} +% +% \begin{function}{.choice:} +% \begin{syntax} +% \meta{key} .choice: +% \end{syntax} +% Sets \meta{key} to act as a choice key. Each valid choice +% for \meta{key} must then be created, as discussed in +% section~\ref{sec:l3keys:choice}. +% \end{function} +% +% \begin{function}[added = 2011-08-21, updated = 2013-07-10] +% {.choices:nn, .choices:Vn, .choices:on, .choices:xn} +% \begin{syntax} +% \meta{key} .choices:nn = \Arg{choices} \Arg{code} +% \end{syntax} +% Sets \meta{key} to act as a choice key, and defines a series \meta{choices} +% which are implemented using the \meta{code}. Inside \meta{code}, +% \cs{l_keys_choice_tl} will be the name of the choice made, and +% \cs{l_keys_choice_int} will be the position of the choice in the list +% of \meta{choices} (indexed from~$1$). +% Choices are discussed in detail in section~\ref{sec:l3keys:choice}. +% \end{function} +% +% \begin{function}[added = 2011-09-11] +% {.clist_set:N, .clist_set:c, .clist_gset:N, .clist_gset:c} +% \begin{syntax} +% \meta{key} .clist_set:N = \meta{comma list variable} +% \end{syntax} +% Defines \meta{key} to set \meta{comma list variable} to \meta{value}. +% Spaces around commas and empty items will be stripped. +% If the variable does not exist, it +% is created globally at the point that the key is set up. +% \end{function} +% +% \begin{function}[updated = 2013-07-10]{.code:n} +% \begin{syntax} +% \meta{key} .code:n = \Arg{code} +% \end{syntax} +% Stores the \meta{code} for execution when \meta{key} is used. +% The \meta{code} can include one parameter (|#1|), which will be the +% \meta{value} given for the \meta{key}. +% \end{function} +% +% \begin{function}[updated = 2013-07-09] +% {.default:n, .default:V, .default:o, .default:x} +% \begin{syntax} +% \meta{key} .default:n = \Arg{default} +% \end{syntax} +% Creates a \meta{default} value for \meta{key}, which is used if no +% value is given. This will be used if only the key name is given, +% but not if a blank \meta{value} is given: +% \begin{verbatim} +% \keys_define:nn { mymodule } +% { +% key .code:n = Hello~#1, +% key .default:n = World +% } +% \keys_set:nn { mymodule } +% { +% key = Fred, % Prints 'Hello Fred' +% key, % Prints 'Hello World' +% key = , % Prints 'Hello ' +% } +% \end{verbatim} +% The default does not affect keys where values are required or +% forbidden. Thus a required value cannot be supplied by a default +% value, and giving a default value for a key which cannot take a value +% does not trigger an error. +% \end{function} +% +% \begin{function}{.dim_set:N, .dim_set:c, .dim_gset:N, .dim_gset:c} +% \begin{syntax} +% \meta{key} .dim_set:N = \meta{dimension} +% \end{syntax} +% Defines \meta{key} to set \meta{dimension} to \meta{value} (which +% must a dimension expression). If the variable does not exist, it +% is created globally at the point that the key is set up. +% \end{function} +% +% \begin{function}{.fp_set:N, .fp_set:c, .fp_gset:N, .fp_gset:c} +% \begin{syntax} +% \meta{key} .fp_set:N = \meta{floating point} +% \end{syntax} +% Defines \meta{key} to set \meta{floating point} to \meta{value} +% (which must a floating point expression). If the variable does not exist, +% it is created globally at the point that the key is set up. +% \end{function} +% +% \begin{function}[added = 2013-07-14] +% {.groups:n} +% \begin{syntax} +% \meta{key} .groups:n = \Arg{groups} +% \end{syntax} +% Defines \meta{key} as belonging to the \meta{groups} declared. Groups +% provide a \enquote{secondary axis} for selectively setting keys, and are +% described in Section~\ref{sec:l3keys:selective}. +% \end{function} +% +% \begin{function}[added = 2016-11-22]{.inherit:n} +% \begin{syntax} +% \meta{key} .inherit:n = \Arg{parents} +% \end{syntax} +% Specifies that the \meta{key} path should inherit the keys listed +% as \meta{parents}. For example, after setting +% \begin{verbatim} +% \keys_define:nn { foo } { test .code:n = \tl_show:n {#1} } +% \keys_define:nn { } { bar .inherit:n = foo } +% \end{verbatim} +% setting +% \begin{verbatim} +% \keys_set:nn { bar } { test = a } +% \end{verbatim} +% will be equivalent to +% \begin{verbatim} +% \keys_set:nn { foo } { test = a } +% \end{verbatim} +% \end{function} +% +% \begin{function}[updated = 2013-07-09] +% {.initial:n, .initial:V, .initial:o, .initial:x} +% \begin{syntax} +% \meta{key} .initial:n = \Arg{value} +% \end{syntax} +% Initialises the \meta{key} with the \meta{value}, equivalent to +% \begin{quote} +% \cs{keys_set:nn} \Arg{module} \{ \meta{key} = \meta{value} \} +% \end{quote} +% \end{function} +% +% \begin{function}{.int_set:N, .int_set:c, .int_gset:N, .int_gset:c} +% \begin{syntax} +% \meta{key} .int_set:N = \meta{integer} +% \end{syntax} +% Defines \meta{key} to set \meta{integer} to \meta{value} (which +% must be an integer expression). If the variable does not exist, it +% is created globally at the point that the key is set up. +% \end{function} +% +% \begin{function}[updated = 2013-07-10]{.meta:n} +% \begin{syntax} +% \meta{key} .meta:n = \Arg{keyval list} +% \end{syntax} +% Makes \meta{key} a meta-key, which will set \meta{keyval list} in +% one go. The \meta{keyval list} can refer as |#1| to the value given +% at the time the \meta{key} is used (or, if no value is given, the +% \meta{key}'s default value). +% \end{function} +% +% \begin{function}[added = 2013-07-10]{.meta:nn} +% \begin{syntax} +% \meta{key} .meta:nn = \Arg{path} \Arg{keyval list} +% \end{syntax} +% Makes \meta{key} a meta-key, which will set \meta{keyval list} in +% one go using the \meta{path} in place of the current one. The +% \meta{keyval list} can refer as |#1| to the value given at the time +% the \meta{key} is used (or, if no value is given, the \meta{key}'s +% default value). +% \end{function} +% +% \begin{function}[added = 2011-08-21]{.multichoice:} +% \begin{syntax} +% \meta{key} .multichoice: +% \end{syntax} +% Sets \meta{key} to act as a multiple choice key. Each valid choice +% for \meta{key} must then be created, as discussed in +% section~\ref{sec:l3keys:choice}. +% \end{function} +% +% \begin{function}[added = 2011-08-21, updated = 2013-07-10] +% {.multichoices:nn, .multichoices:Vn, .multichoices:on, .multichoices:xn} +% \begin{syntax} +% \meta{key} .multichoices:nn \Arg{choices} \Arg{code} +% \end{syntax} +% Sets \meta{key} to act as a multiple choice key, and defines a series +% \meta{choices} +% which are implemented using the \meta{code}. Inside \meta{code}, +% \cs{l_keys_choice_tl} will be the name of the choice made, and +% \cs{l_keys_choice_int} will be the position of the choice in the list +% of \meta{choices} (indexed from~$1$). +% Choices are discussed in detail in section~\ref{sec:l3keys:choice}. +% \end{function} +% +% \begin{function}[added = 2019-05-05] +% {.muskip_set:N, .muskip_set:c, .muskip_gset:N, .muskip_gset:c} +% \begin{syntax} +% \meta{key} .muskip_set:N = \meta{muskip} +% \end{syntax} +% Defines \meta{key} to set \meta{muskip} to \meta{value} (which +% must be a muskip expression). If the variable does not exist, it +% is created globally at the point that the key is set up. +% \end{function} +% +% \begin{function}[added = 2019-01-31] +% {.prop_put:N, .prop_put:c, .prop_gput:N, .prop_gput:c} +% \begin{syntax} +% \meta{key} .prop_put:N = \meta{property list} +% \end{syntax} +% Defines \meta{key} to put the \meta{value} onto the \meta{property list} +% stored under the \meta{key}. +% If the variable does not exist, it +% is created globally at the point that the key is set up. +% \end{function} +% +% \begin{function}{.skip_set:N, .skip_set:c, .skip_gset:N, .skip_gset:c} +% \begin{syntax} +% \meta{key} .skip_set:N = \meta{skip} +% \end{syntax} +% Defines \meta{key} to set \meta{skip} to \meta{value} (which +% must be a skip expression). If the variable does not exist, it +% is created globally at the point that the key is set up. +% \end{function} +% +% \begin{function}{.tl_set:N, .tl_set:c, .tl_gset:N, .tl_gset:c} +% \begin{syntax} +% \meta{key} .tl_set:N = \meta{token list variable} +% \end{syntax} +% Defines \meta{key} to set \meta{token list variable} to \meta{value}. +% If the variable does not exist, it is created globally +% at the point that the key is set up. +% \end{function} +% +% \begin{function}{.tl_set_x:N, .tl_set_x:c, .tl_gset_x:N, .tl_gset_x:c} +% \begin{syntax} +% \meta{key} .tl_set_x:N = \meta{token list variable} +% \end{syntax} +% Defines \meta{key} to set \meta{token list variable} to \meta{value}, +% which will be subjected to an \texttt{x}-type expansion +% (\emph{i.e.}~using \cs{tl_set:Nx}). If the variable does not exist, +% it is created globally at the point that the key is set up. +% \end{function} +% +% \begin{function}[added = 2015-07-14]{.undefine:} +% \begin{syntax} +% \meta{key} .undefine: +% \end{syntax} +% Removes the definition of the \meta{key} within the current scope. +% \end{function} +% +% \begin{function}[added = 2015-07-14]{.value_forbidden:n} +% \begin{syntax} +% \meta{key} .value_forbidden:n = \texttt{true\string|false} +% \end{syntax} +% Specifies that \meta{key} cannot receive a \meta{value} when used. +% If a \meta{value} is given then an error will be issued. Setting +% the property \texttt{false} cancels the restriction. +% \end{function} +% +% \begin{function}[added = 2015-07-14]{.value_required:n} +% \begin{syntax} +% \meta{key} .value_required:n = \texttt{true\string|false} +% \end{syntax} +% Specifies that \meta{key} must receive a \meta{value} when used. +% If a \meta{value} is not given then an error will be issued. Setting +% the property \texttt{false} cancels the restriction. +% \end{function} +% +% \section{Sub-dividing keys} +% \label{sec:l3keys:subdivision} +% +% When creating large numbers of keys, it may be desirable to divide +% them into several sub-groups for a given module. This can be achieved +% either by adding a sub-division to the module name: +% \begin{verbatim} +% \keys_define:nn { mymodule / subgroup } +% { key .code:n = code } +% \end{verbatim} +% or to the key name: +% \begin{verbatim} +% \keys_define:nn { mymodule } +% { subgroup / key .code:n = code } +% \end{verbatim} +% As illustrated, the best choice of token for sub-dividing keys in +% this way is |/|. This is because of the method that is +% used to represent keys internally. Both of the above code fragments +% set the same key, which has full name \texttt{mymodule/subgroup/key}. +% +% As illustrated in the next section, this subdivision is +% particularly relevant to making multiple choices. +% +% \section{Choice and multiple choice keys} +% \label{sec:l3keys:choice} +% +% The \pkg{l3keys} system supports two types of choice key, in which a series +% of pre-defined input values are linked to varying implementations. Choice +% keys are usually created so that the various values are mutually-exclusive: +% only one can apply at any one time. \enquote{Multiple} choice keys are also +% supported: these allow a selection of values to be chosen at the same time. +% +% Mutually-exclusive choices are created by setting the \texttt{.choice:} +% property: +% \begin{verbatim} +% \keys_define:nn { mymodule } +% { key .choice: } +% \end{verbatim} +% For keys which are set up as choices, the valid choices are generated +% by creating sub-keys of the choice key. This can be carried out in +% two ways. +% +% In many cases, choices execute similar code which is dependant only +% on the name of the choice or the position of the choice in the +% list of all possibilities. Here, the keys can share the same code, and can +% be rapidly created using the \texttt{.choices:nn} property. +% \begin{verbatim} +% \keys_define:nn { mymodule } +% { +% key .choices:nn = +% { choice-a, choice-b, choice-c } +% { +% You~gave~choice~'\tl_use:N \l_keys_choice_tl',~ +% which~is~in~position~\int_use:N \l_keys_choice_int \c_space_tl +% in~the~list. +% } +% } +% \end{verbatim} +% The index \cs{l_keys_choice_int} in the list of choices starts at~$1$. +% +% \begin{variable}{\l_keys_choice_int, \l_keys_choice_tl} +% Inside the code block for a choice generated using \texttt{.choices:nn}, +% the variables \cs{l_keys_choice_tl} and \cs{l_keys_choice_int} are +% available to indicate the name of the current choice, and its position in +% the comma list. The position is indexed from~$1$. Note that, as with +% standard key code generated using \texttt{.code:n}, the value passed to +% the key (i.e.~the choice name) is also available as |#1|. +% \end{variable} +% +% On the other hand, it is sometimes useful to create choices which +% use entirely different code from one another. This can be achieved +% by setting the \texttt{.choice:} property of a key, then manually +% defining sub-keys. +% \begin{verbatim} +% \keys_define:nn { mymodule } +% { +% key .choice:, +% key / choice-a .code:n = code-a, +% key / choice-b .code:n = code-b, +% key / choice-c .code:n = code-c, +% } +% \end{verbatim} +% +% It is possible to mix the two methods, but manually-created choices +% should \emph{not} use \cs{l_keys_choice_tl} or \cs{l_keys_choice_int}. +% These variables do not have defined behaviour when used outside of +% code created using \texttt{.choices:nn} +% (\emph{i.e.}~anything might happen). +% +% It is possible to allow choice keys to take values which have not previously +% been defined by adding code for the special \texttt{unknown} choice. The +% general behavior of the \texttt{unknown} key is described in +% Section~\ref{sec:l3keys:unknown}. A typical example in the case of a choice +% would be to issue a custom error message: +% \begin{verbatim} +% \keys_define:nn { mymodule } +% { +% key .choice:, +% key / choice-a .code:n = code-a, +% key / choice-b .code:n = code-b, +% key / choice-c .code:n = code-c, +% key / unknown .code:n = +% \msg_error:nnxxx { mymodule } { unknown-choice } +% { key } % Name of choice key +% { choice-a , choice-b , choice-c } % Valid choices +% { \exp_not:n {#1} } % Invalid choice given +% % +% % +% } +% \end{verbatim} +% +% Multiple choices are created in a very similar manner to mutually-exclusive +% choices, using the properties \texttt{.multichoice:} and +% \texttt{.multichoices:nn}. As with mutually exclusive choices, multiple +% choices are define as sub-keys. Thus both +% \begin{verbatim} +% \keys_define:nn { mymodule } +% { +% key .multichoices:nn = +% { choice-a, choice-b, choice-c } +% { +% You~gave~choice~'\tl_use:N \l_keys_choice_tl',~ +% which~is~in~position~ +% \int_use:N \l_keys_choice_int \c_space_tl +% in~the~list. +% } +% } +% \end{verbatim} +% and +% \begin{verbatim} +% \keys_define:nn { mymodule } +% { +% key .multichoice:, +% key / choice-a .code:n = code-a, +% key / choice-b .code:n = code-b, +% key / choice-c .code:n = code-c, +% } +% \end{verbatim} +% are valid. +% +% When a multiple choice key is set +% \begin{verbatim} +% \keys_set:nn { mymodule } +% { +% key = { a , b , c } % 'key' defined as a multiple choice +% } +% \end{verbatim} +% each choice is applied in turn, equivalent to a \texttt{clist} mapping or +% to applying each value individually: +% \begin{verbatim} +% \keys_set:nn { mymodule } +% { +% key = a , +% key = b , +% key = c , +% } +% \end{verbatim} +% Thus each separate choice will have passed to it the +% \cs{l_keys_choice_tl} and \cs{l_keys_choice_int} in exactly +% the same way as described for \texttt{.choices:nn}. +% +% \section{Setting keys} +% +% \begin{function}[updated = 2017-11-14] +% {\keys_set:nn, \keys_set:nV, \keys_set:nv, \keys_set:no} +% \begin{syntax} +% \cs{keys_set:nn} \Arg{module} \Arg{keyval list} +% \end{syntax} +% Parses the \meta{keyval list}, and sets those keys which are defined +% for \meta{module}. The behaviour on finding an unknown key can be set +% by defining a special \texttt{unknown} key: this is illustrated +% later. +% \end{function} +% +% \begin{variable}[updated = 2015-07-14] +% {\l_keys_key_tl, \l_keys_path_tl, \l_keys_value_tl} +% For each key processed, information of the full \emph{path} of the +% key, the \emph{name} of the key and the \emph{value} of the key is +% available within three token list variables. These may be used within +% the code of the key. +% +% The \emph{value} is everything after the \texttt{=}, which may be +% empty if no value was given. This is stored in \cs{l_keys_value_tl}, and +% is not processed in any way by \cs{keys_set:nn}. +% +% The \emph{path} of the key is a \enquote{full} description of the key, +% and is unique for each key. It consists of the module and full key name, +% thus for example +% \begin{verbatim} +% \keys_set:nn { mymodule } { key-a = some-value } +% \end{verbatim} +% has path \texttt{mymodule/key-a} while +% \begin{verbatim} +% \keys_set:nn { mymodule } { subset / key-a = some-value } +% \end{verbatim} +% has path \texttt{mymodule/subset/key-a}. This information is stored in +% \cs{l_keys_path_tl}, and will have been processed by \cs{tl_to_str:n}. +% +% The \emph{name} of the key is the part of the path after the last +% \texttt{/}, and thus is not unique. In the preceding examples, both keys +% have name \texttt{key-a} despite having different paths. This information +% is stored in \cs{l_keys_key_tl}, and will have been processed by +% \cs{tl_to_str:n}. +% \end{variable} +% +% \section{Handling of unknown keys} +% \label{sec:l3keys:unknown} +% +% If a key has not previously been defined (is unknown), \cs{keys_set:nn} +% looks for a special \texttt{unknown} key for the same module, and if this is +% not defined raises an error indicating that the key name was unknown. This +% mechanism can be used for example to issue custom error texts. +% \begin{verbatim} +% \keys_define:nn { mymodule } +% { +% unknown .code:n = +% You~tried~to~set~key~'\l_keys_key_tl'~to~'#1'. +% } +% \end{verbatim} +% +% \begin{function}[added = 2011-08-23, updated = 2019-01-29] +% { +% \keys_set_known:nn, \keys_set_known:nV, +% \keys_set_known:nv, \keys_set_known:no, +% \keys_set_known:nnN, \keys_set_known:nVN, +% \keys_set_known:nvN, \keys_set_known:noN, +% \keys_set_known:nnnN, \keys_set_known:nVnN, +% \keys_set_known:nvnN, \keys_set_known:nonN +% } +% \begin{syntax} +% \cs{keys_set_known:nn} \Arg{module} \Arg{keyval list} +% \cs{keys_set_known:nnN} \Arg{module} \Arg{keyval list} \meta{tl} +% \cs{keys_set_known:nnnN} \Arg{module} \Arg{keyval list} \Arg{root} \meta{tl} +% \end{syntax} +% These functions set keys which are known for the \meta{module}, and +% simply ignore other keys. The \cs{keys_set_known:nn} function parses the +% \meta{keyval list}, and sets those keys which are defined for +% \meta{module}. Any keys which are unknown are not processed further by +% the parser. In addition, \cs{keys_set_known:nnN} stores the key--value +% pairs in the \meta{tl} in comma-separated form (\emph{i.e.}~an edited +% version of the \meta{keyval list}). When a \meta{root} is given +% (\cs{keys_set_known:nnnN}), the key--value entries are returned +% relative to this point in the key tree. When it is absent, only the +% key name and value are provided. The correct list is returned by +% nested calls. +% \end{function} +% +% \section{Selective key setting} +% \label{sec:l3keys:selective} +% +% In some cases it may be useful to be able to select only some keys for +% setting, even though these keys have the same path. For example, with +% a set of keys defined using +% \begin{verbatim} +% \keys define:nn { mymodule } +% { +% key-one .code:n = { \my_func:n {#1} } , +% key-two .tl_set:N = \l_my_a_tl , +% key-three .tl_set:N = \l_my_b_tl , +% key-four .fp_set:N = \l_my_a_fp , +% } +% \end{verbatim} +% the use of \cs{keys_set:nn} attempts to set all four keys. However, in +% some contexts it may only be sensible to set some keys, or to control the +% order of setting. To do this, keys may be assigned to \emph{groups}: +% arbitrary sets which are independent of the key tree. Thus modifying the +% example to read +% \begin{verbatim} +% \keys define:nn { mymodule } +% { +% key-one .code:n = { \my_func:n {#1} } , +% key-one .groups:n = { first } , +% key-two .tl_set:N = \l_my_a_tl , +% key-two .groups:n = { first } , +% key-three .tl_set:N = \l_my_b_tl , +% key-three .groups:n = { second } , +% key-four .fp_set:N = \l_my_a_fp , +% } +% \end{verbatim} +% assigns \texttt{key-one} and \texttt{key-two} to group \texttt{first}, +% \texttt{key-three} to group \texttt{second}, while \texttt{key-four} is +% not assigned to a group. +% +% Selective key setting may be achieved either by selecting one or more +% groups to be made \enquote{active}, or by marking one or more groups to +% be ignored in key setting. +% +% \begin{function}[added = 2013-07-14, updated = 2019-01-29] +% { +% \keys_set_filter:nnn, \keys_set_filter:nnV, +% \keys_set_filter:nnv, \keys_set_filter:nno, +% \keys_set_filter:nnnN, \keys_set_filter:nnVN, +% \keys_set_filter:nnvN, \keys_set_filter:nnoN, +% \keys_set_filter:nnnnN, \keys_set_filter:nnVnN, +% \keys_set_filter:nnvnN, \keys_set_filter:nnonN, +% } +% \begin{syntax} +% \cs{keys_set_filter:nnn} \Arg{module} \Arg{groups} \Arg{keyval list} +% \cs{keys_set_filter:nnnN} \Arg{module} \Arg{groups} \Arg{keyval list} \meta{tl} +% \cs{keys_set_filter:nnnnN} \Arg{module} \Arg{groups} \Arg{keyval list} \meta{root} \meta{tl} +% \end{syntax} +% Activates key filtering in an \enquote{opt-out} sense: keys assigned to any +% of the \meta{groups} specified are ignored. The \meta{groups} are +% given as a comma-separated list. Unknown keys are not assigned to any +% group and are thus always set. The key--value pairs for each +% key which is filtered out are stored in the \meta{tl} in a +% comma-separated form (\emph{i.e.}~an edited version of the \meta{keyval +% list}). The \cs{keys_set_filter:nnn} version skips this stage. +% +% Use of \cs{keys_set_filter:nnnN} can be nested, with the correct residual +% \meta{keyval list} returned at each stage. In the version which takes +% a \meta{root} argument, the key list is returned relative to that point +% in the key tree. In the cases without a \meta{root} argument, only +% the key names and values are returned. +% \end{function} +% +% \begin{function}[added = 2013-07-14, updated = 2017-05-27] +% { +% \keys_set_groups:nnn, \keys_set_groups:nnV, +% \keys_set_groups:nnv, \keys_set_groups:nno +% } +% \begin{syntax} +% \cs{keys_set_groups:nnn} \Arg{module} \Arg{groups} \Arg{keyval list} +% \end{syntax} +% Activates key filtering in an \enquote{opt-in} sense: only keys assigned to +% one or more of the \meta{groups} specified are set. The \meta{groups} are +% given as a comma-separated list. Unknown keys are not assigned to any +% group and are thus never set. +% \end{function} +% +% \section{Utility functions for keys} +% +% \begin{function}[EXP, pTF, updated = 2017-11-14]{\keys_if_exist:nn} +% \begin{syntax} +% \cs{keys_if_exist_p:nn} \Arg{module} \Arg{key} \\ +% \cs{keys_if_exist:nnTF} \Arg{module} \Arg{key} \Arg{true code} \Arg{false code} +% \end{syntax} +% Tests if the \meta{key} exists for \meta{module}, \emph{i.e.}~if any code +% has been defined for \meta{key}. +% \end{function} +% +% \begin{function}[added = 2011-08-21,EXP,pTF, updated = 2017-11-14] +% {\keys_if_choice_exist:nnn} +% \begin{syntax} +% \cs{keys_if_choice_exist_p:nnn} \Arg{module} \Arg{key} \Arg{choice} \\ +% \cs{keys_if_choice_exist:nnnTF} \Arg{module} \Arg{key} \Arg{choice} \Arg{true code} \Arg{false code} +% \end{syntax} +% Tests if the \meta{choice} is defined for the \meta{key} within the +% \meta{module}, \emph{i.e.}~if any code has been defined for +% \meta{key}/\meta{choice}. The test is \texttt{false} if the \meta{key} +% itself is not defined. +% \end{function} +% +% \begin{function}[updated = 2015-08-09]{\keys_show:nn} +% \begin{syntax} +% \cs{keys_show:nn} \Arg{module} \Arg{key} +% \end{syntax} +% Displays in the terminal +% the information associated to the \meta{key} for a \meta{module}, +% including the function which is used to actually implement it. +% \end{function} +% +% \begin{function}[added = 2014-08-22, updated = 2015-08-09]{\keys_log:nn} +% \begin{syntax} +% \cs{keys_log:nn} \Arg{module} \Arg{key} +% \end{syntax} +% Writes in the log file the information associated to the \meta{key} +% for a \meta{module}. See also \cs{keys_show:nn} which displays the +% result in the terminal. +% \end{function} +% +% \section{Low-level interface for parsing key--val lists} +% +% To re-cap from earlier, a key--value list is input of the form +% \begin{verbatim} +% KeyOne = ValueOne , +% KeyTwo = ValueTwo , +% KeyThree +% \end{verbatim} +% where each key--value pair is separated by a comma from the rest of +% the list, and each key--value pair does not necessarily contain an +% equals sign or a value! Processing this type of input correctly +% requires a number of careful steps, to correctly account for +% braces, spaces and the category codes of separators. +% +% While the functions described earlier are used as a high-level interface +% for processing such input, in special circumstances you may wish to use +% a lower-level approach. +% The low-level parsing system converts a \meta{key--value list} +% into \meta{keys} and associated \meta{values}. After the parsing phase +% is completed, the resulting keys and values (or keys alone) are +% available for further processing. This processing is not carried out by the +% low-level parser itself, and so the parser requires the names of +% two functions along with the key--value list. One function is +% needed to process key--value pairs (it receives two arguments), +% and a second function is required for keys given without any value +% (it is called with a single argument). +% +% The parser does not double |#| tokens or expand any input. Active +% tokens |=| and |,| appearing at the outer level of braces are converted +% to category \enquote{other} (12) so that the parser does not \enquote{miss} +% any due to category code changes. Spaces are removed from the ends +% of the keys and values. Keys and values which are given in braces +% have exactly one set removed (after space trimming), thus +% \begin{verbatim} +% key = {value here}, +% \end{verbatim} +% and +% \begin{verbatim} +% key = value here, +% \end{verbatim} +% are treated identically. +% +% \begin{function}[updated = 2011-09-08]{\keyval_parse:NNn} +% \begin{syntax} +% \cs{keyval_parse:NNn} \meta{function_1} \meta{function_2} \Arg{key--value list} +% \end{syntax} +% Parses the \meta{key--value list} into a series of \meta{keys} and +% associated \meta{values}, or keys alone (if no \meta{value} was +% given). \meta{function_1} should take one argument, while +% \meta{function_2} should absorb two arguments. After +% \cs{keyval_parse:NNn} has parsed the \meta{key--value list}, +% \meta{function_1} is used to process keys given with no value +% and \meta{function_2} is used to process keys given with a +% value. The order of the \meta{keys} in the \meta{key--value list} +% is preserved. Thus +% \begin{verbatim} +% \keyval_parse:NNn \function:n \function:nn +% { key1 = value1 , key2 = value2, key3 = , key4 } +% \end{verbatim} +% is converted into an input stream +% \begin{verbatim} +% \function:nn { key1 } { value1 } +% \function:nn { key2 } { value2 } +% \function:nn { key3 } { } +% \function:n { key4 } +% \end{verbatim} +% Note that there is a difference between an empty value (an equals +% sign followed by nothing) and a missing value (no equals sign at +% all). Spaces are trimmed from the ends of the \meta{key} and \meta{value}, +% then one \emph{outer} set of braces is removed from the \meta{key} +% and \meta{value} as part of the processing. +% \end{function} +% +% \end{documentation} +% +% \begin{implementation} +% +% \section{\pkg{l3keys} Implementation} +% +% \begin{macrocode} +%<*initex|package> +% \end{macrocode} +% +% \subsection{Low-level interface} +% +% The low-level key parser is based heavily on \pkg{keyval}, but with a number +% of additional \enquote{safety} requirements and with the idea that the +% parsed list of key--value pairs can be processed in a variety of ways. +% The net result is that this code needs around twice the amount of time +% as \pkg{keyval} to parse the same list of keys. To optimise speed as far +% as reasonably practical, a number of lower-level approaches are taken +% rather than using the higher-level \pkg{expl3} interfaces. +% +% \begin{macrocode} +%<@@=keyval> +% \end{macrocode} +% +% \begin{variable}{\l_@@_key_tl, \l_@@_value_tl} +% The current key name and value. +% \begin{macrocode} +\tl_new:N \l_@@_key_tl +\tl_new:N \l_@@_value_tl +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\l_@@_sanitise_tl} +% A token list variable for dealing with awkward category codes in the +% input. +% \begin{macrocode} +\tl_new:N \l_@@_sanitise_tl +% \end{macrocode} +% \end{variable} +% +% \begin{macro}{\keyval_parse:NNn} +% The main function starts off by normalising category codes in package mode. +% That's relatively \enquote{expensive} so is skipped (hopefully) in format +% mode. We then hand off to the parser. The use of \cs{q_mark} here prevents +% loss of braces from the key argument. Notice that by passing the two +% processor commands along the input stack we avoid the need to track these +% at all. +% \begin{macrocode} +\cs_new_protected:Npn \keyval_parse:NNn #1#2#3 + { +%<*initex> + \@@_loop:NNw #1#2 \q_mark #3 , \q_recursion_tail , +% +%<*package> + \tl_set:Nn \l_@@_sanitise_tl {#3} + \@@_sanitise_equals: + \@@_sanitise_comma: + \exp_after:wN \@@_loop:NNw \exp_after:wN #1 \exp_after:wN #2 + \exp_after:wN \q_mark \l_@@_sanitise_tl , \q_recursion_tail , +% + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\@@_sanitise_equals:, \@@_sanitise_comma:} +% \begin{macro} +% { +% \@@_sanitise_equals_auxi:w, \@@_sanitise_equals_auxii:w, +% \@@_sanitise_comma_auxi:w, \@@_sanitise_comma_auxii:w, +% \@@_sanitise_aux:w +% } +% A reasonably fast search and replace set up specifically for the active +% tokens. The nature of the input is known so everything is hard-coded. +% With only two tokens to cover, the speed gain from using dedicated +% functions is worth it. +% \begin{macrocode} +%<*package> +\group_begin: + \char_set_catcode_active:n { `\= } + \char_set_catcode_active:n { `\, } + \cs_new_protected:Npn \@@_sanitise_equals: + { + \exp_after:wN \@@_sanitise_equals_auxi:w \l_@@_sanitise_tl + \q_mark = \q_nil = + \exp_after:wN \@@_sanitise_aux:w \l_@@_sanitise_tl + } + \cs_new_protected:Npn \@@_sanitise_equals_auxi:w #1 = + { + \tl_set:Nn \l_@@_sanitise_tl {#1} + \@@_sanitise_equals_auxii:w + } + \cs_new_protected:Npn \@@_sanitise_equals_auxii:w #1 = + { + \if_meaning:w \q_nil #1 \scan_stop: + \else: + \tl_set:Nx \l_@@_sanitise_tl + { + \exp_not:o \l_@@_sanitise_tl + \token_to_str:N = + \exp_not:n {#1} + } + \exp_after:wN \@@_sanitise_equals_auxii:w + \fi: + } + \cs_new_protected:Npn \@@_sanitise_comma: + { + \exp_after:wN \@@_sanitise_comma_auxi:w \l_@@_sanitise_tl + \q_mark , \q_nil , + \exp_after:wN \@@_sanitise_aux:w \l_@@_sanitise_tl + } + \cs_new_protected:Npn \@@_sanitise_comma_auxi:w #1 , + { + \tl_set:Nn \l_@@_sanitise_tl {#1} + \@@_sanitise_comma_auxii:w + } + \cs_new_protected:Npn \@@_sanitise_comma_auxii:w #1 , + { + \if_meaning:w \q_nil #1 \scan_stop: + \else: + \tl_set:Nx \l_@@_sanitise_tl + { + \exp_not:o \l_@@_sanitise_tl + \token_to_str:N , + \exp_not:n {#1} + } + \exp_after:wN \@@_sanitise_comma_auxii:w + \fi: + } +\group_end: +\cs_new_protected:Npn \@@_sanitise_aux:w #1 \q_mark + { \tl_set:Nn \l_@@_sanitise_tl {#1} } +% +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\@@_loop:NNw} +% A fast test for the end of the loop, remembering to remove the leading +% quark first. Assuming that is not the case, look for a key and value then +% loop around, re-inserting a leading quark in front of the next position. +% \begin{macrocode} +\cs_new_protected:Npn \@@_loop:NNw #1#2#3 , + { + \exp_after:wN \if_meaning:w \exp_after:wN \q_recursion_tail + \use_none:n #3 \prg_do_nothing: + \else: + \@@_split:NNw #1#2#3 == \q_stop + \exp_after:wN \@@_loop:NNw \exp_after:wN #1 \exp_after:wN #2 + \exp_after:wN \q_mark + \fi: + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\@@_split:NNw, \@@_split_value:NNw} +% \begin{macro}{\@@_split_tidy:w} +% \begin{macro}{\@@_action:} +% The value is picked up separately from the key so there can be another +% quark inserted at the front, keeping braces and allowing both parts to +% share the same code paths. The key is found first then there's a check +% that there is something there: this is biased to the common case of there +% actually being a key. For the value, we first need to see if there is +% anything to do: if there is, extract it. The appropriate action is then +% inserted in front of the key and value. Doing this using an assignment is +% marginally faster than an an expansion chain. +% \begin{macrocode} +\cs_new_protected:Npn \@@_split:NNw #1#2#3 = + { + \@@_def:Nn \l_@@_key_tl {#3} + \if_meaning:w \l_@@_key_tl \c_empty_tl + \exp_after:wN \@@_split_tidy:w + \else: + \exp_after:wN \@@_split_value:NNw + \exp_after:wN #1 + \exp_after:wN #2 + \exp_after:wN \q_mark + \fi: + } +\cs_new_protected:Npn \@@_split_value:NNw #1#2#3 = #4 \q_stop + { + \if:w \scan_stop: \tl_to_str:n {#4} \scan_stop: + \cs_set:Npx \@@_action: + { \exp_not:N #1 { \exp_not:o \l_@@_key_tl } } + \else: + \if:w + \scan_stop: + \__kernel_tl_to_str:w \exp_after:wN { \use_none:n #4 } + \scan_stop: + \@@_def:Nn \l_@@_value_tl {#3} + \cs_set:Npx \@@_action: + { + \exp_not:N #2 + { \exp_not:o \l_@@_key_tl } + { \exp_not:o \l_@@_value_tl } + } + \else: + \cs_set:Npn \@@_action: + { + \__kernel_msg_error:nn { kernel } + { misplaced-equals-sign } + } + \fi: + \fi: + \@@_action: + } +\cs_new_protected:Npn \@@_split_tidy:w #1 \q_stop + { + \if:w + \scan_stop: + \__kernel_tl_to_str:w \exp_after:wN { \use_none:n #1 } + \scan_stop: + \else: + \exp_after:wN \@@_empty_key: + \fi: + } +\cs_new:Npn \@@_action: { } +\cs_new_protected:Npn \@@_empty_key: + { \__kernel_msg_error:nn { kernel } { misplaced-equals-sign } } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\@@_def:Nn} +% \begin{macro}[EXP]{\@@_def_aux:n} +% \begin{macro}[EXP]{\@@_def_aux:w} +% First remove the leading quark, then trim spaces off, and finally remove +% a set of braces. +% \begin{macrocode} +\cs_new_protected:Npn \@@_def:Nn #1#2 + { + \tl_set:Nx #1 + { \tl_trim_spaces_apply:oN { \use_none:n #2 } \@@_def_aux:n } + } +\cs_new:Npn \@@_def_aux:n #1 + { \@@_def_aux:w #1 \q_stop } +\cs_new:Npn \@@_def_aux:w #1 \q_stop { \exp_not:n {#1} } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% +% One message for the low level parsing system. +% \begin{macrocode} +\__kernel_msg_new:nnnn { kernel } { misplaced-equals-sign } + { Misplaced~equals~sign~in~key-value~input~\msg_line_number: } + { + LaTeX~is~attempting~to~parse~some~key-value~input~but~found~ + two~equals~signs~not~separated~by~a~comma. + } +% \end{macrocode} +% +% \subsection{Constants and variables} +% +% \begin{macrocode} +%<@@=keys> +% \end{macrocode} +% +% \begin{variable} +% { +% \c_@@_code_root_tl , +% \c_@@_default_root_tl , +% \c_@@_groups_root_tl , +% \c_@@_inherit_root_tl , +% \c_@@_type_root_tl , +% \c_@@_validate_root_tl +% } +% Various storage areas for the different data which make up keys. +% \begin{macrocode} +\tl_const:Nn \c_@@_code_root_tl { key~code~>~ } +\tl_const:Nn \c_@@_default_root_tl { key~default~>~ } +\tl_const:Nn \c_@@_groups_root_tl { key~groups~>~ } +\tl_const:Nn \c_@@_inherit_root_tl { key~inherit~>~ } +\tl_const:Nn \c_@@_type_root_tl { key~type~>~ } +\tl_const:Nn \c_@@_validate_root_tl { key~validate~>~ } +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\c_@@_props_root_tl} +% The prefix for storing properties. +% \begin{macrocode} +\tl_const:Nn \c_@@_props_root_tl { key~prop~>~ } +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\l_keys_choice_int, \l_keys_choice_tl} +% Publicly accessible data on which choice is being used when several +% are generated as a set. +% \begin{macrocode} +\int_new:N \l_keys_choice_int +\tl_new:N \l_keys_choice_tl +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\l_@@_groups_clist} +% Used for storing and recovering the list of groups which apply to a key: +% set as a comma list but at one point we have to use this for a token +% list recovery. +% \begin{macrocode} +\clist_new:N \l_@@_groups_clist +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\l_keys_key_tl} +% The name of a key itself: needed when setting keys. +% \begin{macrocode} +\tl_new:N \l_keys_key_tl +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\l_@@_module_tl} +% The module for an entire set of keys. +% \begin{macrocode} +\tl_new:N \l_@@_module_tl +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\l_@@_no_value_bool} +% A marker is needed internally to show if only a key or a key plus a +% value was seen: this is recorded here. +% \begin{macrocode} +\bool_new:N \l_@@_no_value_bool +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\l_@@_only_known_bool} +% Used to track if only \enquote{known} keys are being set. +% \begin{macrocode} +\bool_new:N \l_@@_only_known_bool +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\l_keys_path_tl} +% The \enquote{path} of the current key is stored here: this is +% available to the programmer and so is public. +% \begin{macrocode} +\tl_new:N \l_keys_path_tl +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\l_@@_inherit_tl} +% \begin{macrocode} +\tl_new:N \l_@@_inherit_tl +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\l_@@_relative_tl} +% The relative path for passing keys back to the user. +% \begin{macrocode} +\tl_new:N \l_@@_relative_tl +\tl_set:Nn \l_@@_relative_tl { \q_no_value } +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\l_@@_property_tl} +% The \enquote{property} begin set for a key at definition time is +% stored here. +% \begin{macrocode} +\tl_new:N \l_@@_property_tl +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\l_@@_selective_bool, \l_@@_filtered_bool} +% Two flags for using key groups: one to indicate that \enquote{selective} +% setting is active, a second to specify which type (\enquote{opt-in} +% or \enquote{opt-out}). +% \begin{macrocode} +\bool_new:N \l_@@_selective_bool +\bool_new:N \l_@@_filtered_bool +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\l_@@_selective_seq} +% The list of key groups being filtered in or out during selective setting. +% \begin{macrocode} +\seq_new:N \l_@@_selective_seq +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\l_@@_unused_clist} +% Used when setting only some keys to store those left over. +% \begin{macrocode} +\tl_new:N \l_@@_unused_clist +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\l_keys_value_tl} +% The value given for a key: may be empty if no value was given. +% \begin{macrocode} +\tl_new:N \l_keys_value_tl +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\l_@@_tmp_bool, \l_@@_tmpa_tl, \l_@@_tmpb_tl} +% Scratch space. +% \begin{macrocode} +\bool_new:N \l_@@_tmp_bool +\tl_new:N \l_@@_tmpa_tl +\tl_new:N \l_@@_tmpb_tl +% \end{macrocode} +% \end{variable} +% +% \subsection{The key defining mechanism} +% +% \begin{macro}{\keys_define:nn} +% \begin{macro}{\@@_define:nnn, \@@_define:onn} +% The public function for definitions is just a wrapper for the lower +% level mechanism, more or less. The outer function is designed to +% keep a track of the current module, to allow safe nesting. The module is set +% removing any leading |/| (which is not needed here). +% \begin{macrocode} +\cs_new_protected:Npn \keys_define:nn + { \@@_define:onn \l_@@_module_tl } +\cs_new_protected:Npn \@@_define:nnn #1#2#3 + { + \tl_set:Nx \l_@@_module_tl { \@@_trim_spaces:n {#2} } + \keyval_parse:NNn \@@_define:n \@@_define:nn {#3} + \tl_set:Nn \l_@@_module_tl {#1} + } +\cs_generate_variant:Nn \@@_define:nnn { o } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\@@_define:n} +% \begin{macro}{\@@_define:nn} +% \begin{macro}{\@@_define_aux:nn} +% The outer functions here record whether a value was given and then +% converge on a common internal mechanism. There is first a search for +% a property in the current key name, then a check to make sure it is +% known before the code hands off to the next step. +% \begin{macrocode} +\cs_new_protected:Npn \@@_define:n #1 + { + \bool_set_true:N \l_@@_no_value_bool + \@@_define_aux:nn {#1} { } + } +\cs_new_protected:Npn \@@_define:nn #1#2 + { + \bool_set_false:N \l_@@_no_value_bool + \@@_define_aux:nn {#1} {#2} + } +\cs_new_protected:Npn \@@_define_aux:nn #1#2 + { + \@@_property_find:n {#1} + \cs_if_exist:cTF { \c_@@_props_root_tl \l_@@_property_tl } + { \@@_define_code:n {#2} + } + { + \tl_if_empty:NF \l_@@_property_tl + { + \__kernel_msg_error:nnxx { kernel } { key-property-unknown } + { \l_@@_property_tl } { \l_keys_path_tl } + } + } + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\@@_property_find:n} +% \begin{macro}{\@@_property_find:w} +% Searching for a property means finding the last |.| in the input, +% and storing the text before and after it. Everything is turned into +% strings, so there is no problem using an \texttt{x}-type expansion. +% \begin{macrocode} +\cs_new_protected:Npn \@@_property_find:n #1 + { + \tl_set:Nx \l_@@_property_tl { \@@_trim_spaces:n {#1} } + \exp_after:wN \@@_property_find:w \l_@@_property_tl . . + \q_stop {#1} + } +\cs_new_protected:Npn \@@_property_find:w #1 . #2 . #3 \q_stop #4 + { + \tl_if_blank:nTF {#3} + { + \tl_clear:N \l_@@_property_tl + \__kernel_msg_error:nnn { kernel } { key-no-property } {#4} + } + { + \str_if_eq:nnTF {#3} { . } + { + \tl_set:Nx \l_keys_path_tl + { + \tl_if_empty:NF \l_@@_module_tl + { \l_@@_module_tl / } + \tl_trim_spaces:n {#1} + } + \tl_set:Nn \l_@@_property_tl { . #2 } + } + { + \tl_set:Nx \l_keys_path_tl { \l_@@_module_tl / #1 . #2 } + \@@_property_search:w #3 \q_stop + } + } + } +\cs_new_protected:Npn \@@_property_search:w #1 . #2 \q_stop + { + \str_if_eq:nnTF {#2} { . } + { + \tl_set:Nx \l_keys_path_tl { \l_keys_path_tl } + \tl_set:Nn \l_@@_property_tl { . #1 } + } + { + \tl_set:Nx \l_keys_path_tl { \l_keys_path_tl . #1 } + \@@_property_search:w #2 \q_stop + } + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\@@_define_code:n} +% \begin{macro}[EXP]{\@@_define_code:w} +% Two possible cases. If there is a value for the key, then just use +% the function. If not, then a check to make sure there is no need for +% a value with the property. If there should be one then complain, +% otherwise execute it. There is no need to check for a |:| as if it +% was missing the earlier tests would have failed. +% \begin{macrocode} +\cs_new_protected:Npn \@@_define_code:n #1 + { + \bool_if:NTF \l_@@_no_value_bool + { + \exp_after:wN \@@_define_code:w + \l_@@_property_tl \q_stop + { \use:c { \c_@@_props_root_tl \l_@@_property_tl } } + { + \__kernel_msg_error:nnxx { kernel } + { key-property-requires-value } { \l_@@_property_tl } + { \l_keys_path_tl } + } + } + { \use:c { \c_@@_props_root_tl \l_@@_property_tl } {#1} } + } +\exp_last_unbraced:NNNNo + \cs_new:Npn \@@_define_code:w #1 \c_colon_str #2 \q_stop + { \tl_if_empty:nTF {#2} } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \subsection{Turning properties into actions} +% +% \begin{macro}{\@@_bool_set:Nn, \@@_bool_set:cn} +% Boolean keys are really just choices, but all done by hand. The +% second argument here is the scope: either empty or \texttt{ g } for +% global. +% \begin{macrocode} +\cs_new_protected:Npn \@@_bool_set:Nn #1#2 + { + \bool_if_exist:NF #1 { \bool_new:N #1 } + \@@_choice_make: + \@@_cmd_set:nx { \l_keys_path_tl / true } + { \exp_not:c { bool_ #2 set_true:N } \exp_not:N #1 } + \@@_cmd_set:nx { \l_keys_path_tl / false } + { \exp_not:c { bool_ #2 set_false:N } \exp_not:N #1 } + \@@_cmd_set:nn { \l_keys_path_tl / unknown } + { + \__kernel_msg_error:nnx { kernel } { boolean-values-only } + { \l_keys_key_tl } + } + \@@_default_set:n { true } + } +\cs_generate_variant:Nn \@@_bool_set:Nn { c } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\@@_bool_set_inverse:Nn, \@@_bool_set_inverse:cn} +% Inverse boolean setting is much the same. +% \begin{macrocode} +\cs_new_protected:Npn \@@_bool_set_inverse:Nn #1#2 + { + \bool_if_exist:NF #1 { \bool_new:N #1 } + \@@_choice_make: + \@@_cmd_set:nx { \l_keys_path_tl / true } + { \exp_not:c { bool_ #2 set_false:N } \exp_not:N #1 } + \@@_cmd_set:nx { \l_keys_path_tl / false } + { \exp_not:c { bool_ #2 set_true:N } \exp_not:N #1 } + \@@_cmd_set:nn { \l_keys_path_tl / unknown } + { + \__kernel_msg_error:nnx { kernel } { boolean-values-only } + { \l_keys_key_tl } + } + \@@_default_set:n { true } + } +\cs_generate_variant:Nn \@@_bool_set_inverse:Nn { c } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\@@_choice_make:, \@@_multichoice_make:} +% \begin{macro}{\@@_choice_make:N} +% \begin{macro}{\@@_choice_make_aux:N} +% To make a choice from a key, two steps: set the code, and set the +% unknown key. As multichoices and choices are essentially the same bar one +% function, the code is given together. +% \begin{macrocode} +\cs_new_protected:Npn \@@_choice_make: + { \@@_choice_make:N \@@_choice_find:n } +\cs_new_protected:Npn \@@_multichoice_make: + { \@@_choice_make:N \@@_multichoice_find:n } +\cs_new_protected:Npn \@@_choice_make:N #1 + { + \cs_if_exist:cTF + { \c_@@_type_root_tl \@@_parent:o \l_keys_path_tl } + { + \str_if_eq:vnTF + { \c_@@_type_root_tl \@@_parent:o \l_keys_path_tl } + { choice } + { + \__kernel_msg_error:nnxx { kernel } { nested-choice-key } + { \l_keys_path_tl } { \@@_parent:o \l_keys_path_tl } + } + { \@@_choice_make_aux:N #1 } + } + { \@@_choice_make_aux:N #1 } + } +\cs_new_protected:Npn \@@_choice_make_aux:N #1 + { + \cs_set_nopar:cpn { \c_@@_type_root_tl \l_keys_path_tl } + { choice } + \@@_cmd_set:nn { \l_keys_path_tl } { #1 {##1} } + \@@_cmd_set:nn { \l_keys_path_tl / unknown } + { + \__kernel_msg_error:nnxx { kernel } { key-choice-unknown } + { \l_keys_path_tl } {##1} + } + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\@@_choices_make:nn, \@@_multichoices_make:nn} +% \begin{macro}{\@@_choices_make:Nnn} +% Auto-generating choices means setting up the root key as a choice, then +% defining each choice in turn. +% \begin{macrocode} +\cs_new_protected:Npn \@@_choices_make:nn + { \@@_choices_make:Nnn \@@_choice_make: } +\cs_new_protected:Npn \@@_multichoices_make:nn + { \@@_choices_make:Nnn \@@_multichoice_make: } +\cs_new_protected:Npn \@@_choices_make:Nnn #1#2#3 + { + #1 + \int_zero:N \l_keys_choice_int + \clist_map_inline:nn {#2} + { + \int_incr:N \l_keys_choice_int + \@@_cmd_set:nx + { \l_keys_path_tl / \@@_trim_spaces:n {##1} } + { + \tl_set:Nn \exp_not:N \l_keys_choice_tl {##1} + \int_set:Nn \exp_not:N \l_keys_choice_int + { \int_use:N \l_keys_choice_int } + \exp_not:n {#3} + } + } + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro} +% {\@@_cmd_set:nn, \@@_cmd_set:nx, \@@_cmd_set:Vn, \@@_cmd_set:Vo} +% Setting the code for a key first logs if appropriate that we are +% defining a new key, then saves the code. +% \begin{macrocode} +\__kernel_patch:nnNNpn + { + \cs_if_exist:cF { \c_@@_code_root_tl #1 } + { \__kernel_debug_log:x { Defining~key~#1~\msg_line_context: } } + } + { } +\cs_new_protected:Npn \@@_cmd_set:nn #1#2 + { \cs_set_protected:cpn { \c_@@_code_root_tl #1 } ##1 {#2} } +\cs_generate_variant:Nn \@@_cmd_set:nn { nx , Vn , Vo } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\@@_default_set:n} +% Setting a default value is easy. These are stored using \cs{cs_set:cpx} as this +% avoids any worries about whether a token list exists. +% \begin{macrocode} +\cs_new_protected:Npn \@@_default_set:n #1 + { + \tl_if_empty:nTF {#1} + { + \cs_set_eq:cN + { \c_@@_default_root_tl \l_keys_path_tl } + \tex_undefined:D + } + { + \cs_set_nopar:cpx + { \c_@@_default_root_tl \l_keys_path_tl } + { \exp_not:n {#1} } + } + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\@@_groups_set:n} +% Assigning a key to one or more groups uses comma lists. As the list of +% groups only exists if there is anything to do, the setting is done using +% a scratch list. For the usual grouping reasons we use the low-level +% approach to undefining a list. We also use the low-level approach for +% the other case to avoid tripping up the |check-declarations| code. +% \begin{macrocode} +\cs_new_protected:Npn \@@_groups_set:n #1 + { + \clist_set:Nn \l_@@_groups_clist {#1} + \clist_if_empty:NTF \l_@@_groups_clist + { + \cs_set_eq:cN { \c_@@_groups_root_tl \l_keys_path_tl } + \tex_undefined:D + } + { + \cs_set_eq:cN { \c_@@_groups_root_tl \l_keys_path_tl } + \l_@@_groups_clist + } + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\@@_inherit:n} +% Inheritance means ignoring anything already said about the key: +% zap the lot and set up. +% \begin{macrocode} +\cs_new_protected:Npn \@@_inherit:n #1 + { + \@@_undefine: + \cs_set_nopar:cpn { \c_@@_inherit_root_tl \l_keys_path_tl } {#1} + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\@@_initialise:n} +% A set up for initialisation: just run the code if it exists. +% \begin{macrocode} +\cs_new_protected:Npn \@@_initialise:n #1 + { + \cs_if_exist_use:cT { \c_@@_code_root_tl \l_keys_path_tl } { {#1} } + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\@@_meta_make:n} +% \begin{macro}{\@@_meta_make:nn} +% To create a meta-key, simply set up to pass data through. +% \begin{macrocode} +\cs_new_protected:Npn \@@_meta_make:n #1 + { + \@@_cmd_set:Vo \l_keys_path_tl + { + \exp_after:wN \keys_set:nn + \exp_after:wN { \l_@@_module_tl } {#1} + } + } +\cs_new_protected:Npn \@@_meta_make:nn #1#2 + { \@@_cmd_set:Vn \l_keys_path_tl { \keys_set:nn {#1} {#2} } } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\@@_prop_put:Nn, \@@_prop_put:cn} +% Much the same as other variables, but needs a dedicated auxiliary. +% \begin{macrocode} +\cs_new_protected:Npn \@@_prop_put:Nn #1#2 + { + \prop_if_exist:NF #1 { \prop_new:N #1 } + \exp_after:wN \@@_find_key_module:NNw + \exp_after:wN \l_@@_tmpa_tl + \exp_after:wN \l_@@_tmpb_tl + \l_keys_path_tl / \q_stop + \@@_cmd_set:nx { \l_keys_path_tl } + { + \exp_not:c { prop_ #2 put:Nnn } + \exp_not:N #1 + { \l_@@_tmpb_tl } + \exp_not:n { {##1} } + } + } +\cs_generate_variant:Nn \@@_prop_put:Nn { c } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\@@_undefine:} +% Undefining a key has to be done without \cs{cs_undefine:c} as that +% function acts globally. +% \begin{macrocode} +\cs_new_protected:Npn \@@_undefine: + { + \clist_map_inline:nn + { code , default , groups , inherit , type , validate } + { + \cs_set_eq:cN + { \tl_use:c { c_@@_ ##1 _root_tl } \l_keys_path_tl } + \tex_undefined:D + } + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\@@_value_requirement:nn} +% \begin{macro}{\@@_validate_forbidden:, \@@_validate_required:} +% \begin{macro}{\@@_validate_cleanup:w} +% Validating key input is done using a second function which runs before +% the main key code. Setting that up means setting it equal to a generic +% stub which does the check. This approach makes the lookup very fast at +% the cost of one additional csname per key that needs it. The cleanup here +% has to know the structure of the following code. +% \begin{macrocode} +\cs_new_protected:Npn \@@_value_requirement:nn #1#2 + { + \str_case:nnF {#2} + { + { true } + { + \cs_set_eq:cc + { \c_@@_validate_root_tl \l_keys_path_tl } + { @@_validate_ #1 : } + } + { false } + { + \cs_if_eq:ccT + { \c_@@_validate_root_tl \l_keys_path_tl } + { @@_validate_ #1 : } + { + \cs_set_eq:cN + { \c_@@_validate_root_tl \l_keys_path_tl } + \tex_undefined:D + } + } + } + { + \__kernel_msg_error:nnx { kernel } + { key-property-boolean-values-only } + { .value_ #1 :n } + } + } +\cs_new_protected:Npn \@@_validate_forbidden: + { + \bool_if:NF \l_@@_no_value_bool + { + \__kernel_msg_error:nnxx { kernel } { value-forbidden } + { \l_keys_path_tl } { \l_keys_value_tl } + \@@_validate_cleanup:w + } + } +\cs_new_protected:Npn \@@_validate_required: + { + \bool_if:NT \l_@@_no_value_bool + { + \__kernel_msg_error:nnx { kernel } { value-required } + { \l_keys_path_tl } + \@@_validate_cleanup:w + } + } +\cs_new_protected:Npn \@@_validate_cleanup:w #1 \cs_end: #2#3 { } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\@@_variable_set:NnnN, \@@_variable_set:cnnN} +% Setting a variable takes the type and scope separately so that +% it is easy to make a new variable if needed. +% \begin{macrocode} +\cs_new_protected:Npn \@@_variable_set:NnnN #1#2#3#4 + { + \use:c { #2_if_exist:NF } #1 { \use:c { #2 _new:N } #1 } + \@@_cmd_set:nx { \l_keys_path_tl } + { + \exp_not:c { #2 _ #3 set:N #4 } + \exp_not:N #1 + \exp_not:n { {##1} } + } + } +\cs_generate_variant:Nn \@@_variable_set:NnnN { c } +% \end{macrocode} +% \end{macro} +% +% \subsection{Creating key properties} +% +% The key property functions are all wrappers for internal functions, +% meaning that things stay readable and can also be altered later on. +% +% Importantly, while key properties have \enquote{normal} argument specs, the +% underlying code always supplies one braced argument to these. As such, argument +% expansion is handled by hand rather than using the standard tools. This shows +% up particularly for the two-argument properties, where things would otherwise +% go badly wrong. +% +% \begin{macro}{.bool_set:N, .bool_set:c} +% \begin{macro}{.bool_gset:N, .bool_gset:c} +% One function for this. +% \begin{macrocode} +\cs_new_protected:cpn { \c_@@_props_root_tl .bool_set:N } #1 + { \@@_bool_set:Nn #1 { } } +\cs_new_protected:cpn { \c_@@_props_root_tl .bool_set:c } #1 + { \@@_bool_set:cn {#1} { } } +\cs_new_protected:cpn { \c_@@_props_root_tl .bool_gset:N } #1 + { \@@_bool_set:Nn #1 { g } } +\cs_new_protected:cpn { \c_@@_props_root_tl .bool_gset:c } #1 + { \@@_bool_set:cn {#1} { g } } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}{.bool_set_inverse:N, .bool_set_inverse:c} +% \begin{macro}{.bool_gset_inverse:N, .bool_gset_inverse:c} +% One function for this. +% \begin{macrocode} +\cs_new_protected:cpn { \c_@@_props_root_tl .bool_set_inverse:N } #1 + { \@@_bool_set_inverse:Nn #1 { } } +\cs_new_protected:cpn { \c_@@_props_root_tl .bool_set_inverse:c } #1 + { \@@_bool_set_inverse:cn {#1} { } } +\cs_new_protected:cpn { \c_@@_props_root_tl .bool_gset_inverse:N } #1 + { \@@_bool_set_inverse:Nn #1 { g } } +\cs_new_protected:cpn { \c_@@_props_root_tl .bool_gset_inverse:c } #1 + { \@@_bool_set_inverse:cn {#1} { g } } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}{.choice:} +% Making a choice is handled internally, as it is also needed by +% \texttt{.generate_choices:n}. +% \begin{macrocode} +\cs_new_protected:cpn { \c_@@_props_root_tl .choice: } + { \@@_choice_make: } +% \end{macrocode} +% \end{macro} +% +% \begin{macro} +% {.choices:nn, .choices:Vn, .choices:on, .choices:xn} +% For auto-generation of a series of mutually-exclusive choices. +% Here, |#1| consists of two separate +% arguments, hence the slightly odd-looking implementation. +% \begin{macrocode} +\cs_new_protected:cpn { \c_@@_props_root_tl .choices:nn } #1 + { \@@_choices_make:nn #1 } +\cs_new_protected:cpn { \c_@@_props_root_tl .choices:Vn } #1 + { \exp_args:NV \@@_choices_make:nn #1 } +\cs_new_protected:cpn { \c_@@_props_root_tl .choices:on } #1 + { \exp_args:No \@@_choices_make:nn #1 } +\cs_new_protected:cpn { \c_@@_props_root_tl .choices:xn } #1 + { \exp_args:Nx \@@_choices_make:nn #1 } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{.code:n} +% Creating code is simply a case of passing through to the underlying +% \texttt{set} function. +% \begin{macrocode} +\cs_new_protected:cpn { \c_@@_props_root_tl .code:n } #1 + { \@@_cmd_set:nn { \l_keys_path_tl } {#1} } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{.clist_set:N, .clist_set:c} +% \begin{macro}{.clist_gset:N, .clist_gset:c} +% \begin{macrocode} +\cs_new_protected:cpn { \c_@@_props_root_tl .clist_set:N } #1 + { \@@_variable_set:NnnN #1 { clist } { } n } +\cs_new_protected:cpn { \c_@@_props_root_tl .clist_set:c } #1 + { \@@_variable_set:cnnN {#1} { clist } { } n } +\cs_new_protected:cpn { \c_@@_props_root_tl .clist_gset:N } #1 + { \@@_variable_set:NnnN #1 { clist } { g } n } +\cs_new_protected:cpn { \c_@@_props_root_tl .clist_gset:c } #1 + { \@@_variable_set:cnnN {#1} { clist } { g } n } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}{.default:n, .default:V, .default:o, .default:x} +% Expansion is left to the internal functions. +% \begin{macrocode} +\cs_new_protected:cpn { \c_@@_props_root_tl .default:n } #1 + { \@@_default_set:n {#1} } +\cs_new_protected:cpn { \c_@@_props_root_tl .default:V } #1 + { \exp_args:NV \@@_default_set:n #1 } +\cs_new_protected:cpn { \c_@@_props_root_tl .default:o } #1 + { \exp_args:No \@@_default_set:n {#1} } +\cs_new_protected:cpn { \c_@@_props_root_tl .default:x } #1 + { \exp_args:Nx \@@_default_set:n {#1} } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{.dim_set:N, .dim_set:c} +% \begin{macro}{.dim_gset:N, .dim_gset:c} +% Setting a variable is very easy: just pass the data along. +% \begin{macrocode} +\cs_new_protected:cpn { \c_@@_props_root_tl .dim_set:N } #1 + { \@@_variable_set:NnnN #1 { dim } { } n } +\cs_new_protected:cpn { \c_@@_props_root_tl .dim_set:c } #1 + { \@@_variable_set:cnnN {#1} { dim } { } n } +\cs_new_protected:cpn { \c_@@_props_root_tl .dim_gset:N } #1 + { \@@_variable_set:NnnN #1 { dim } { g } n } +\cs_new_protected:cpn { \c_@@_props_root_tl .dim_gset:c } #1 + { \@@_variable_set:cnnN {#1} { dim } { g } n } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}{.fp_set:N, .fp_set:c} +% \begin{macro}{.fp_gset:N, .fp_gset:c} +% Setting a variable is very easy: just pass the data along. +% \begin{macrocode} +\cs_new_protected:cpn { \c_@@_props_root_tl .fp_set:N } #1 + { \@@_variable_set:NnnN #1 { fp } { } n } +\cs_new_protected:cpn { \c_@@_props_root_tl .fp_set:c } #1 + { \@@_variable_set:cnnN {#1} { fp } { } n } +\cs_new_protected:cpn { \c_@@_props_root_tl .fp_gset:N } #1 + { \@@_variable_set:NnnN #1 { fp } { g } n } +\cs_new_protected:cpn { \c_@@_props_root_tl .fp_gset:c } #1 + { \@@_variable_set:cnnN {#1} { fp } { g } n } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}{.groups:n} +% A single property to create groups of keys. +% \begin{macrocode} +\cs_new_protected:cpn { \c_@@_props_root_tl .groups:n } #1 + { \@@_groups_set:n {#1} } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{.inherit:n} +% Nothing complex: only one variant at the moment! +% \begin{macrocode} +\cs_new_protected:cpn { \c_@@_props_root_tl .inherit:n } #1 + { \@@_inherit:n {#1} } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{.initial:n, .initial:V, .initial:o, .initial:x} +% The standard hand-off approach. +% \begin{macrocode} +\cs_new_protected:cpn { \c_@@_props_root_tl .initial:n } #1 + { \@@_initialise:n {#1} } +\cs_new_protected:cpn { \c_@@_props_root_tl .initial:V } #1 + { \exp_args:NV \@@_initialise:n #1 } +\cs_new_protected:cpn { \c_@@_props_root_tl .initial:o } #1 + { \exp_args:No \@@_initialise:n {#1} } +\cs_new_protected:cpn { \c_@@_props_root_tl .initial:x } #1 + { \exp_args:Nx \@@_initialise:n {#1} } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{.int_set:N, .int_set:c} +% \begin{macro}{.int_gset:N, .int_gset:c} +% Setting a variable is very easy: just pass the data along. +% \begin{macrocode} +\cs_new_protected:cpn { \c_@@_props_root_tl .int_set:N } #1 + { \@@_variable_set:NnnN #1 { int } { } n } +\cs_new_protected:cpn { \c_@@_props_root_tl .int_set:c } #1 + { \@@_variable_set:cnnN {#1} { int } { } n } +\cs_new_protected:cpn { \c_@@_props_root_tl .int_gset:N } #1 + { \@@_variable_set:NnnN #1 { int } { g } n } +\cs_new_protected:cpn { \c_@@_props_root_tl .int_gset:c } #1 + { \@@_variable_set:cnnN {#1} { int } { g } n } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}{.meta:n} +% Making a meta is handled internally. +% \begin{macrocode} +\cs_new_protected:cpn { \c_@@_props_root_tl .meta:n } #1 + { \@@_meta_make:n {#1} } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{.meta:nn} +% Meta with path: potentially lots of variants, but for the moment +% no so many defined. +% \begin{macrocode} +\cs_new_protected:cpn { \c_@@_props_root_tl .meta:nn } #1 + { \@@_meta_make:nn #1 } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{.multichoice:} +% \begin{macro} +% { +% .multichoices:nn, .multichoices:Vn, .multichoices:on, +% .multichoices:xn, +% } +% The same idea as \texttt{.choice:} and \texttt{.choices:nn}, but +% where more than one choice is allowed. +% \begin{macrocode} +\cs_new_protected:cpn { \c_@@_props_root_tl .multichoice: } + { \@@_multichoice_make: } +\cs_new_protected:cpn { \c_@@_props_root_tl .multichoices:nn } #1 + { \@@_multichoices_make:nn #1 } +\cs_new_protected:cpn { \c_@@_props_root_tl .multichoices:Vn } #1 + { \exp_args:NV \@@_multichoices_make:nn #1 } +\cs_new_protected:cpn { \c_@@_props_root_tl .multichoices:on } #1 + { \exp_args:No \@@_multichoices_make:nn #1 } +\cs_new_protected:cpn { \c_@@_props_root_tl .multichoices:xn } #1 + { \exp_args:Nx \@@_multichoices_make:nn #1 } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}{.muskip_set:N, .muskip_set:c, .muskip_gset:N, .muskip_gset:c} +% Setting a variable is very easy: just pass the data along. +% \begin{macrocode} +\cs_new_protected:cpn { \c_@@_props_root_tl .muskip_set:N } #1 + { \@@_variable_set:NnnN #1 { muskip } { } n } +\cs_new_protected:cpn { \c_@@_props_root_tl .muskip_set:c } #1 + { \@@_variable_set:cnnN {#1} { muskip } { } n } +\cs_new_protected:cpn { \c_@@_props_root_tl .muskip_gset:N } #1 + { \@@_variable_set:NnnN #1 { muskip } { g } n } +\cs_new_protected:cpn { \c_@@_props_root_tl .muskip_gset:c } #1 + { \@@_variable_set:cnnN {#1} { muskip } { g } n } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{.prop_put:N, .prop_put:c, .prop_gput:N, .prop_gput:c} +% Setting a variable is very easy: just pass the data along. +% \begin{macrocode} +\cs_new_protected:cpn { \c_@@_props_root_tl .prop_put:N } #1 + { \@@_prop_put:Nn #1 { } } +\cs_new_protected:cpn { \c_@@_props_root_tl .prop_put:c } #1 + { \@@_prop_put:cn {#1} { } } +\cs_new_protected:cpn { \c_@@_props_root_tl .prop_gput:N } #1 + { \@@_prop_put:Nn #1 { g } } +\cs_new_protected:cpn { \c_@@_props_root_tl .prop_gput:c } #1 + { \@@_prop_put:cn {#1} { g } } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{.skip_set:N, .skip_set:c} +% \begin{macro}{.skip_gset:N, .skip_gset:c} +% Setting a variable is very easy: just pass the data along. +% \begin{macrocode} +\cs_new_protected:cpn { \c_@@_props_root_tl .skip_set:N } #1 + { \@@_variable_set:NnnN #1 { skip } { } n } +\cs_new_protected:cpn { \c_@@_props_root_tl .skip_set:c } #1 + { \@@_variable_set:cnnN {#1} { skip } { } n } +\cs_new_protected:cpn { \c_@@_props_root_tl .skip_gset:N } #1 + { \@@_variable_set:NnnN #1 { skip } { g } n } +\cs_new_protected:cpn { \c_@@_props_root_tl .skip_gset:c } #1 + { \@@_variable_set:cnnN {#1} { skip } { g } n } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}{.tl_set:N, .tl_set:c} +% \begin{macro}{.tl_gset:N, .tl_gset:c} +% \begin{macro}{.tl_set_x:N, .tl_set_x:c} +% \begin{macro}{.tl_gset_x:N, .tl_gset_x:c} +% Setting a variable is very easy: just pass the data along. +% \begin{macrocode} +\cs_new_protected:cpn { \c_@@_props_root_tl .tl_set:N } #1 + { \@@_variable_set:NnnN #1 { tl } { } n } +\cs_new_protected:cpn { \c_@@_props_root_tl .tl_set:c } #1 + { \@@_variable_set:cnnN {#1} { tl } { } n } +\cs_new_protected:cpn { \c_@@_props_root_tl .tl_set_x:N } #1 + { \@@_variable_set:NnnN #1 { tl } { } x } +\cs_new_protected:cpn { \c_@@_props_root_tl .tl_set_x:c } #1 + { \@@_variable_set:cnnN {#1} { tl } { } x } +\cs_new_protected:cpn { \c_@@_props_root_tl .tl_gset:N } #1 + { \@@_variable_set:NnnN #1 { tl } { g } n } +\cs_new_protected:cpn { \c_@@_props_root_tl .tl_gset:c } #1 + { \@@_variable_set:cnnN {#1} { tl } { g } n } +\cs_new_protected:cpn { \c_@@_props_root_tl .tl_gset_x:N } #1 + { \@@_variable_set:NnnN #1 { tl } { g } x } +\cs_new_protected:cpn { \c_@@_props_root_tl .tl_gset_x:c } #1 + { \@@_variable_set:cnnN {#1} { tl } { g } x } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}{.undefine:} +% Another simple wrapper. +% \begin{macrocode} +\cs_new_protected:cpn { \c_@@_props_root_tl .undefine: } + { \@@_undefine: } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{.value_forbidden:n} +% \begin{macro}{.value_required:n} +% These are very similar, so both call the same function. +% \begin{macrocode} +\cs_new_protected:cpn { \c_@@_props_root_tl .value_forbidden:n } #1 + { \@@_value_requirement:nn { forbidden } {#1} } +\cs_new_protected:cpn { \c_@@_props_root_tl .value_required:n } #1 + { \@@_value_requirement:nn { required } {#1} } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \subsection{Setting keys} +% +% \begin{macro} +% { +% \keys_set:nn, \keys_set:nV, \keys_set:nv, \keys_set:no, +% \@@_set:nn +% } +% \begin{macro}{\@@_set:nnn} +% A simple wrapper allowing for nesting. +% \begin{macrocode} +\cs_new_protected:Npn \keys_set:nn #1#2 + { + \use:x + { + \bool_set_false:N \exp_not:N \l_@@_only_known_bool + \bool_set_false:N \exp_not:N \l_@@_filtered_bool + \bool_set_false:N \exp_not:N \l_@@_selective_bool + \tl_set:Nn \exp_not:N \l_@@_relative_tl + { \exp_not:N \q_no_value } + \@@_set:nn \exp_not:n { {#1} {#2} } + \bool_if:NT \l_@@_only_known_bool + { \bool_set_true:N \exp_not:N \l_@@_only_known_bool } + \bool_if:NT \l_@@_filtered_bool + { \bool_set_true:N \exp_not:N \l_@@_filtered_bool } + \bool_if:NT \l_@@_selective_bool + { \bool_set_true:N \exp_not:N \l_@@_selective_bool } + \tl_set:Nn \exp_not:N \l_@@_relative_tl + { \exp_not:o \l_@@_relative_tl } + } + } +\cs_generate_variant:Nn \keys_set:nn { nV , nv , no } +\cs_new_protected:Npn \@@_set:nn #1#2 + { \exp_args:No \@@_set:nnn \l_@@_module_tl {#1} {#2} } +\cs_new_protected:Npn \@@_set:nnn #1#2#3 + { + \tl_set:Nx \l_@@_module_tl { \@@_trim_spaces:n {#2} } + \keyval_parse:NNn \@@_set_keyval:n \@@_set_keyval:nn {#3} + \tl_set:Nn \l_@@_module_tl {#1} + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro} +% { +% \keys_set_known:nnN, \keys_set_known:nVN, +% \keys_set_known:nvN, \keys_set_known:noN +% } +% \begin{macro} +% { +% \keys_set_known:nnnN, \keys_set_known:nVnN, +% \keys_set_known:nvnN, \keys_set_known:nonN +% } +% \begin{macro}{\@@_set_known:nnnnN} +% \begin{macro} +% { +% \keys_set_known:nn, \keys_set_known:nV, +% \keys_set_known:nv, \keys_set_known:no +% } +% \begin{macro}{\@@_set_known:nnn} +% Setting known keys simply means setting the appropriate flag, then +% running the standard code. To allow for nested setting, any existing +% value of \cs{l_@@_unused_clist} is saved on the stack and reset +% afterwards. Note that for speed/simplicity reasons we use a \texttt{tl} +% operation to set the \texttt{clist} here! +% \begin{macrocode} +\cs_new_protected:Npn \keys_set_known:nnN #1#2#3 + { + \exp_args:No \@@_set_known:nnnnN + \l_@@_unused_clist { \q_no_value } {#1} {#2} #3 + } +\cs_generate_variant:Nn \keys_set_known:nnN { nV , nv , no } +\cs_new_protected:Npn \keys_set_known:nnnN #1#2#3#4 + { + \exp_args:No \@@_set_known:nnnnN + \l_@@_unused_clist {#3} {#1} {#2} #4 + } +\cs_generate_variant:Nn \keys_set_known:nnnN { nV , nv , no } +\cs_new_protected:Npn \@@_set_known:nnnnN #1#2#3#4#5 + { + \clist_clear:N \l_@@_unused_clist + \@@_set_known:nnn {#2} {#3} {#4} + \tl_set:Nx #5 { \exp_not:o { \l_@@_unused_clist } } + \tl_set:Nn \l_@@_unused_clist {#1} + } +\cs_new_protected:Npn \keys_set_known:nn #1#2 + { \@@_set_known:nnn { \q_no_value } {#1} {#2} } +\cs_generate_variant:Nn \keys_set_known:nn { nV , nv , no } +\cs_new_protected:Npn \@@_set_known:nnn #1#2#3 + { + \use:x + { + \bool_set_true:N \exp_not:N \l_@@_only_known_bool + \bool_set_false:N \exp_not:N \l_@@_filtered_bool + \bool_set_false:N \exp_not:N \l_@@_selective_bool + \tl_set:Nn \exp_not:N \l_@@_relative_tl { \exp_not:n {#1} } + \@@_set:nn \exp_not:n { {#2} {#3} } + \bool_if:NF \l_@@_only_known_bool + { \bool_set_false:N \exp_not:N \l_@@_only_known_bool } + \bool_if:NT \l_@@_filtered_bool + { \bool_set_true:N \exp_not:N \l_@@_filtered_bool } + \bool_if:NT \l_@@_selective_bool + { \bool_set_true:N \exp_not:N \l_@@_selective_bool } + \tl_set:Nn \exp_not:N \l_@@_relative_tl + { \exp_not:o \l_@@_relative_tl } + } + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro} +% { +% \keys_set_filter:nnnN, \keys_set_filter:nnVN, \keys_set_filter:nnvN, +% \keys_set_filter:nnoN +% } +% \begin{macro} +% { +% \keys_set_filter:nnnnN, \keys_set_filter:nnVnN, +% \keys_set_filter:nnvnN, \keys_set_filter:nnonN +% } +% \begin{macro}{\@@_set_filter:nnnnnN} +% \begin{macro} +% { +% \keys_set_filter:nnn, \keys_set_filter:nnV, \keys_set_filter:nnv, +% \keys_set_filter:nno +% } +% \begin{macro}{\@@_set_filter:nnnn} +% \begin{macro} +% { +% \keys_set_groups:nnn, \keys_set_groups:nnV, \keys_set_groups:nnv, +% \keys_set_groups:nno +% } +% \begin{macro}{\@@_set_selective:nnn} +% \begin{macro}{\@@_set_selective:nnnn} +% The idea of setting keys in a selective manner again uses flags +% wrapped around the basic code. The comments on \cs{keys_set_known:nnN} +% also apply here. We have a bit more shuffling to do to keep everything +% nestable. +% \begin{macrocode} +\cs_new_protected:Npn \keys_set_filter:nnnN #1#2#3#4 + { + \exp_args:No \@@_set_filter:nnnnnN + \l_@@_unused_clist + { \q_no_value } {#1} {#2} {#3} #4 + } +\cs_generate_variant:Nn \keys_set_filter:nnnN { nnV , nnv , nno } +\cs_new_protected:Npn \keys_set_filter:nnnnN #1#2#3#4#5 + { + \exp_args:No \@@_set_filter:nnnnnN + \l_@@_unused_clist {#4} {#1} {#2} {#3} #5 + } +\cs_generate_variant:Nn \keys_set_filter:nnnnN { nnV , nnv , nno } +\cs_new_protected:Npn \@@_set_filter:nnnnnN #1#2#3#4#5#6 + { + \clist_clear:N \l_@@_unused_clist + \@@_set_filter:nnnn {#2} {#3} {#4} {#5} + \tl_set:Nx #6 { \exp_not:o { \l_@@_unused_clist } } + \tl_set:Nn \l_@@_unused_clist {#1} + } +\cs_new_protected:Npn \keys_set_filter:nnn #1#2#3 + {\@@_set_filter:nnnn { \q_no_value } {#1} {#2} {#3} } +\cs_generate_variant:Nn \keys_set_filter:nnn { nnV , nnv , nno } +\cs_new_protected:Npn \@@_set_filter:nnnn #1#2#3#4 + { + \use:x + { + \bool_set_false:N \exp_not:N \l_@@_only_known_bool + \bool_set_true:N \exp_not:N \l_@@_filtered_bool + \bool_set_true:N \exp_not:N \l_@@_selective_bool + \tl_set:Nn \exp_not:N \l_@@_relative_tl { \exp_not:n {#1} } + \@@_set_selective:nnn \exp_not:n { {#2} {#3} {#4} } + \bool_if:NT \l_@@_only_known_bool + { \bool_set_true:N \exp_not:N \l_@@_only_known_bool } + \bool_if:NF \l_@@_filtered_bool + { \bool_set_false:N \exp_not:N \l_@@_filtered_bool } + \bool_if:NF \l_@@_selective_bool + { \bool_set_false:N \exp_not:N \l_@@_selective_bool } + \tl_set:Nn \exp_not:N \l_@@_relative_tl + { \exp_not:o \l_@@_relative_tl } + } + } +\cs_new_protected:Npn \keys_set_groups:nnn #1#2#3 + { + \use:x + { + \bool_set_false:N \exp_not:N \l_@@_only_known_bool + \bool_set_false:N \exp_not:N \l_@@_filtered_bool + \bool_set_true:N \exp_not:N \l_@@_selective_bool + \tl_set:Nn \exp_not:N \l_@@_relative_tl + { \exp_not:N \q_no_value } + \@@_set_selective:nnn \exp_not:n { {#1} {#2} {#3} } + \bool_if:NT \l_@@_only_known_bool + { \bool_set_true:N \exp_not:N \l_@@_only_known_bool } + \bool_if:NF \l_@@_filtered_bool + { \bool_set_true:N \exp_not:N \l_@@_filtered_bool } + \bool_if:NF \l_@@_selective_bool + { \bool_set_false:N \exp_not:N \l_@@_selective_bool } + \tl_set:Nn \exp_not:N \l_@@_relative_tl + { \exp_not:o \l_@@_relative_tl } + } + } +\cs_generate_variant:Nn \keys_set_groups:nnn { nnV , nnv , nno } +\cs_new_protected:Npn \@@_set_selective:nnn + { \exp_args:No \@@_set_selective:nnnn \l_@@_selective_seq } +\cs_new_protected:Npn \@@_set_selective:nnnn #1#2#3#4 + { + \seq_set_from_clist:Nn \l_@@_selective_seq {#3} + \@@_set:nn {#2} {#4} + \tl_set:Nn \l_@@_selective_seq {#1} + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\@@_set_keyval:n, \@@_set_keyval:nn} +% \begin{macro}{\@@_set_keyval:nnn, \@@_set_keyval:onn} +% \begin{macro}{\@@_find_key_module:NNw} +% \begin{macro}{\@@_set_selective:} +% A shared system once again. First, set the current path and add a +% default if needed. There are then checks to see if the a value is +% required or forbidden. If everything passes, move on to execute the +% code. +% \begin{macrocode} +\cs_new_protected:Npn \@@_set_keyval:n #1 + { + \bool_set_true:N \l_@@_no_value_bool + \@@_set_keyval:onn \l_@@_module_tl {#1} { } + } +\cs_new_protected:Npn \@@_set_keyval:nn #1#2 + { + \bool_set_false:N \l_@@_no_value_bool + \@@_set_keyval:onn \l_@@_module_tl {#1} {#2} + } +% \end{macrocode} +% The key path here can be fully defined, after which there is a search +% for the key and module names: the user may have passed them with part +% of what is actually the module (for our purposes) in the key name. As +% that happens on a per-key basis, we use the stack approach to restore +% the module name without a group. +% \begin{macrocode} +\cs_new_protected:Npn \@@_set_keyval:nnn #1#2#3 + { + \tl_set:Nx \l_keys_path_tl + { + \tl_if_blank:nF {#1} + { #1 / } + \@@_trim_spaces:n {#2} + } + \tl_clear:N \l_@@_module_tl + \tl_clear:N \l_@@_inherit_tl + \exp_after:wN \@@_find_key_module:NNw + \exp_after:wN \l_@@_module_tl + \exp_after:wN \l_keys_key_tl + \l_keys_path_tl / \q_stop + \@@_value_or_default:n {#3} + \bool_if:NTF \l_@@_selective_bool + { \@@_set_selective: } + { \@@_execute: } + \tl_set:Nn \l_@@_module_tl {#1} + } +\cs_generate_variant:Nn \@@_set_keyval:nnn { o } +\cs_new_protected:Npn \@@_find_key_module:NNw #1#2#3 / #4 \q_stop + { + \tl_if_blank:nTF {#4} + { \tl_set:Nn #2 {#3} } + { + \tl_put_right:Nx #1 + { + \tl_if_empty:NF #1 { / } + #3 + } + \@@_find_key_module:NNw #1#2 #4 \q_stop + } + } +% \end{macrocode} +% If selective setting is active, there are a number of possible sub-cases +% to consider. The key name may not be known at all or if it is, it may not +% have any groups assigned. There is then the question of whether the +% selection is opt-in or opt-out. +% \begin{macrocode} +\cs_new_protected:Npn \@@_set_selective: + { + \cs_if_exist:cTF { \c_@@_groups_root_tl \l_keys_path_tl } + { + \clist_set_eq:Nc \l_@@_groups_clist + { \c_@@_groups_root_tl \l_keys_path_tl } + \@@_check_groups: + } + { + \bool_if:NTF \l_@@_filtered_bool + { \@@_execute: } + { \@@_store_unused: } + } + } +% \end{macrocode} +% In the case where selective setting requires a comparison of the list +% of groups which apply to a key with the list of those which have been +% set active. That requires two mappings, and again a different outcome +% depending on whether opt-in or opt-out is set. +% \begin{macrocode} +\cs_new_protected:Npn \@@_check_groups: + { + \bool_set_false:N \l_@@_tmp_bool + \seq_map_inline:Nn \l_@@_selective_seq + { + \clist_map_inline:Nn \l_@@_groups_clist + { + \str_if_eq:nnT {##1} {####1} + { + \bool_set_true:N \l_@@_tmp_bool + \clist_map_break:n { \seq_map_break: } + } + } + } + \bool_if:NTF \l_@@_tmp_bool + { + \bool_if:NTF \l_@@_filtered_bool + { \@@_store_unused: } + { \@@_execute: } + } + { + \bool_if:NTF \l_@@_filtered_bool + { \@@_execute: } + { \@@_store_unused: } + } + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\@@_value_or_default:n} +% \begin{macro}{\@@_default_inherit:} +% If a value is given, return it as |#1|, otherwise send a default if +% available. +% \begin{macrocode} +\cs_new_protected:Npn \@@_value_or_default:n #1 + { + \bool_if:NTF \l_@@_no_value_bool + { + \cs_if_exist:cTF { \c_@@_default_root_tl \l_keys_path_tl } + { + \tl_set_eq:Nc + \l_keys_value_tl + { \c_@@_default_root_tl \l_keys_path_tl } + } + { + \tl_clear:N \l_keys_value_tl + \cs_if_exist:cT + { \c_@@_inherit_root_tl \@@_parent:o \l_keys_path_tl } + { \@@_default_inherit: } + } + } + { \tl_set:Nn \l_keys_value_tl {#1} } + } +\cs_new_protected:Npn \@@_default_inherit: + { + \clist_map_inline:cn + { \c_@@_inherit_root_tl \@@_parent:o \l_keys_path_tl } + { + \cs_if_exist:cT + { \c_@@_default_root_tl ##1 / \l_keys_key_tl } + { + \tl_set_eq:Nc + \l_keys_value_tl + { \c_@@_default_root_tl ##1 / \l_keys_key_tl } + \clist_map_break: + } + } + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\@@_execute:, \@@_execute_inherit:, \@@_execute_unknown:} +% \begin{macro}[EXP]{\@@_execute:nn} +% \begin{macro}{\@@_store_unused:,\@@_store_unused_aux:} +% Actually executing a key is done in two parts. First, look for the +% key itself, then look for the \texttt{unknown} key with the same +% path. If both of these fail, complain. What exactly happens if a key +% is unknown depends on whether unknown keys are being skipped or if +% an error should be raised. +% \begin{macrocode} +\cs_new_protected:Npn \@@_execute: + { + \cs_if_exist:cTF { \c_@@_code_root_tl \l_keys_path_tl } + { + \cs_if_exist_use:c { \c_@@_validate_root_tl \l_keys_path_tl } + \cs:w \c_@@_code_root_tl \l_keys_path_tl \exp_after:wN \cs_end: + \exp_after:wN { \l_keys_value_tl } + } + { + \cs_if_exist:cTF + { \c_@@_inherit_root_tl \@@_parent:o \l_keys_path_tl } + { \@@_execute_inherit: } + { \@@_execute_unknown: } + } + } +% \end{macrocode} +% To deal with the case where there is no hit, we leave +% \cs{@@_execute_unknown:} in the input stream and clean it up using the +% break function: that avoids needing a boolean. +% \begin{macrocode} +\cs_new_protected:Npn \@@_execute_inherit: + { + \clist_map_inline:cn + { \c_@@_inherit_root_tl \@@_parent:o \l_keys_path_tl } + { + \cs_if_exist:cT + { \c_@@_code_root_tl ##1 / \l_keys_key_tl } + { + \tl_set:Nn \l_@@_inherit_tl {##1} + \cs:w \c_@@_code_root_tl ##1 / \l_keys_key_tl + \exp_after:wN \cs_end: \exp_after:wN + { \l_keys_value_tl } + \clist_map_break:n { \use_none:n } + } + } + \@@_execute_unknown: + } +\cs_new_protected:Npn \@@_execute_unknown: + { + \bool_if:NTF \l_@@_only_known_bool + { \@@_store_unused: } + { + \cs_if_exist:cTF + { \c_@@_code_root_tl \l_@@_module_tl / unknown } + { + \cs:w \c_@@_code_root_tl \l_@@_module_tl / unknown + \exp_after:wN \cs_end: \exp_after:wN { \l_keys_value_tl } + } + { + \__kernel_msg_error:nnxx { kernel } { key-unknown } + { \l_keys_path_tl } { \l_@@_module_tl } + } + } + } +\cs_new:Npn \@@_execute:nn #1#2 + { + \cs_if_exist:cTF { \c_@@_code_root_tl #1 } + { + \cs:w \c_@@_code_root_tl #1 \exp_after:wN \cs_end: + \exp_after:wN { \l_keys_value_tl } + } + {#2} + } +% \end{macrocode} +% When there is no relative path, things here are easy: just save the key +% name and value. When we are working with a relative path, first we +% need to turn it into a string: that can't happen earlier as we need +% to store \cs{q_no_value}. Then, use a standard delimited approach to fish +% out the partial path. +% \begin{macrocode} +\cs_new_protected:Npn \@@_store_unused: + { + \quark_if_no_value:NTF \l_@@_relative_tl + { + \clist_put_right:Nx \l_@@_unused_clist + { + \exp_not:o \l_keys_key_tl + \bool_if:NF \l_@@_no_value_bool + { = { \exp_not:o \l_keys_value_tl } } + } + } + { + \tl_if_empty:NTF \l_@@_relative_tl + { + \clist_put_right:Nx \l_@@_unused_clist + { + \exp_not:o \l_keys_path_tl + \bool_if:NF \l_@@_no_value_bool + { = { \exp_not:o \l_keys_value_tl } } + } + } + { \@@_store_unused_aux: } + } + } +\cs_new_protected:Npn \@@_store_unused_aux: + { + \tl_set:Nx \l_@@_relative_tl + { \exp_args:No \@@_trim_spaces:n \l_@@_relative_tl } + \use:x + { + \cs_set_protected:Npn \@@_store_unused:w + ####1 \l_@@_relative_tl / + ####2 \l_@@_relative_tl / + ####3 \exp_not:N \q_stop + } + { + \tl_if_blank:nF {##1} + { + \__kernel_msg_error:nnxx { kernel } { bad-relative-key-path } + \l_keys_path_tl + \l_@@_relative_tl + } + \clist_put_right:Nx \l_@@_unused_clist + { + \exp_not:n {##2} + \bool_if:NF \l_@@_no_value_bool + { = { \exp_not:o \l_keys_value_tl } } + } + } + \use:x + { + \@@_store_unused:w \l_keys_path_tl + \l_@@_relative_tl / \l_@@_relative_tl / + \exp_not:N \q_stop + } + } +\cs_new_protected:Npn \@@_store_unused:w { } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}[EXP]{\@@_choice_find:n} +% \begin{macro}[EXP]{\@@_choice_find:nn} +% \begin{macro}[EXP]{\@@_multichoice_find:n} +% Executing a choice has two parts. First, try the choice given, then +% if that fails call the unknown key. That always exists, as it is created +% when a choice is first made. So there is no need for any escape code. +% For multiple choices, the same code ends up used in a mapping. +% \begin{macrocode} +\cs_new:Npn \@@_choice_find:n #1 + { + \tl_if_empty:NTF \l_@@_inherit_tl + { \@@_choice_find:nn { \l_keys_path_tl } {#1} } + { + \@@_choice_find:nn + { \l_@@_inherit_tl / \l_keys_key_tl } {#1} + } + } +\cs_new:Npn \@@_choice_find:nn #1#2 + { + \cs_if_exist:cTF { \c_@@_code_root_tl #1 / \@@_trim_spaces:n {#2} } + { \use:c { \c_@@_code_root_tl #1 / \@@_trim_spaces:n {#2} } {#2} } + { \use:c { \c_@@_code_root_tl #1 / unknown } {#2} } + } +\cs_new:Npn \@@_multichoice_find:n #1 + { \clist_map_function:nN {#1} \@@_choice_find:n } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \subsection{Utilities} +% +% \begin{macro}[EXP]{\@@_parent:n, \@@_parent:o} +% \begin{macro}[EXP]{\@@_parent:w} +% Used to strip off the ending part of the key path after the last~|/|. +% \begin{macrocode} +\cs_new:Npn \@@_parent:n #1 + { \@@_parent:w #1 / / \q_stop { } } +\cs_generate_variant:Nn \@@_parent:n { o } +\cs_new:Npn \@@_parent:w #1 / #2 / #3 \q_stop #4 + { + \tl_if_blank:nTF {#2} + { + \tl_if_blank:nF {#4} + { \use_none:n #4 } + } + { + \@@_parent:w #2 / #3 \q_stop { #4 / #1 } + } + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}[EXP]{\@@_trim_spaces:n} +% \begin{macro}[EXP] +% { +% \@@_trim_spaces_auxi:w , +% \@@_trim_spaces_auxii:w , +% \@@_trim_spaces_auxiii:w +% } +% Space stripping has to allow for the fact that the key here might have +% several parts, and spaces need to be stripped from each part. +% \begin{macrocode} +\cs_new:Npn \@@_trim_spaces:n #1 + { + \exp_after:wN \@@_trim_spaces_auxi:w \tl_to_str:n {#1} + / \q_nil \q_stop + + } +\cs_new:Npn \@@_trim_spaces_auxi:w #1 / #2 \q_stop + { + \quark_if_nil:nTF {#2} + { \tl_trim_spaces:n {#1} } + { \@@_trim_spaces_auxii:w #1 / #2 } + } +\cs_new:Npn \@@_trim_spaces_auxii:w #1 / #2 / \q_nil + { + \tl_trim_spaces:n {#1} + \@@_trim_spaces_auxiii:w #2 / \q_recursion_tail / \q_recursion_stop + } +\cs_set:Npn \@@_trim_spaces_auxiii:w #1 / + { + \quark_if_recursion_tail_stop:n {#1} + \tl_trim_spaces:n { / #1 } + \@@_trim_spaces_auxiii:w + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}[EXP,pTF]{\keys_if_exist:nn} +% A utility for others to see if a key exists. +% \begin{macrocode} +\prg_new_conditional:Npnn \keys_if_exist:nn #1#2 { p , T , F , TF } + { + \cs_if_exist:cTF + { \c_@@_code_root_tl \@@_trim_spaces:n { #1 / #2 } } + { \prg_return_true: } + { \prg_return_false: } + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[EXP,pTF]{\keys_if_choice_exist:nnn} +% Just an alternative view on \cs{keys_if_exist:nnTF}. +% \begin{macrocode} +\prg_new_conditional:Npnn \keys_if_choice_exist:nnn #1#2#3 + { p , T , F , TF } + { + \cs_if_exist:cTF + { \c_@@_code_root_tl \@@_trim_spaces:n { #1 / #2 / #3 } } + { \prg_return_true: } + { \prg_return_false: } + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\keys_show:nn, \keys_log:nn, \@@_show:Nnn} +% To show a key, show its code using a message. +% \begin{macrocode} +\cs_new_protected:Npn \keys_show:nn + { \@@_show:Nnn \msg_show:nnxxxx } +\cs_new_protected:Npn \keys_log:nn + { \@@_show:Nnn \msg_log:nnxxxx } +\cs_new_protected:Npn \@@_show:Nnn #1#2#3 + { + #1 { LaTeX / kernel } { show-key } + { \@@_trim_spaces:n { #2 / #3 } } + { + \keys_if_exist:nnT {#2} {#3} + { + \exp_args:Nnf \msg_show_item_unbraced:nn { code } + { + \exp_args:Nc \cs_replacement_spec:N + { + \c_@@_code_root_tl + \@@_trim_spaces:n { #2 / #3 } + } + } + } + } + { } { } + } +% \end{macrocode} +% \end{macro} +% +% \subsection{Messages} +% +% For when there is a need to complain. +% \begin{macrocode} +\__kernel_msg_new:nnnn { kernel } { bad-relative-key-path } + { The~key~'#1'~is~not~inside~the~'#2'~path. } + { The~key~'#1'~cannot~be~expressed~relative~to~path~'#2'. } +\__kernel_msg_new:nnnn { kernel } { boolean-values-only } + { Key~'#1'~accepts~boolean~values~only. } + { The~key~'#1'~only~accepts~the~values~'true'~and~'false'. } +\__kernel_msg_new:nnnn { kernel } { key-choice-unknown } + { Key~'#1'~accepts~only~a~fixed~set~of~choices. } + { + The~key~'#1'~only~accepts~predefined~values,~ + and~'#2'~is~not~one~of~these. + } +\__kernel_msg_new:nnnn { kernel } { key-unknown } + { The~key~'#1'~is~unknown~and~is~being~ignored. } + { + The~module~'#2'~does~not~have~a~key~called~'#1'.\\ + Check~that~you~have~spelled~the~key~name~correctly. + } +\__kernel_msg_new:nnnn { kernel } { nested-choice-key } + { Attempt~to~define~'#1'~as~a~nested~choice~key. } + { + The~key~'#1'~cannot~be~defined~as~a~choice~as~the~parent~key~'#2'~is~ + itself~a~choice. + } +\__kernel_msg_new:nnnn { kernel } { value-forbidden } + { The~key~'#1'~does~not~take~a~value. } + { + The~key~'#1'~should~be~given~without~a~value.\\ + The~value~'#2'~was~present:~the~key~will~be~ignored. + } +\__kernel_msg_new:nnnn { kernel } { value-required } + { The~key~'#1'~requires~a~value. } + { + The~key~'#1'~must~have~a~value.\\ + No~value~was~present:~the~key~will~be~ignored. + } +\__kernel_msg_new:nnn { kernel } { show-key } + { + The~key~#1~ + \tl_if_empty:nTF {#2} + { is~undefined. } + { has~the~properties: #2 . } + } +% \end{macrocode} +% +% \begin{macrocode} +% +% \end{macrocode} +% +%\end{implementation} +% +%\PrintIndex -- cgit v1.2.3