I have two list environments called myenv
and myenv*
where only the first one has four levels, the keys for the first one are stored in { mymodule / level-1 }
, ...,{ mymodule /level-4 }
and for the second in { mymodule / myenv* }
.
I want to add a command of the form \myenvmeta[myenv,1]{new-key}{keya=foo, keyb=baz}
where when executed it adds:
\keys_define:nn { mymodule / level-1 }
{
new-key .meta:n = { keya=foo, keyb=baz },
new-key .value_forbidden:n = true,
}
when run \myenvmeta[myenv,2]{new-key}{keya=foo, keyb=baz}
do the same for the level-2
and when run \myenvmeta[myenv*]{new-key}{keya=foo, keyb=baz}
:
\keys_define:nn { mymodule / myenv* }
{
new-key .meta:n = {keya=foo, keyb=baz},
new-key .value_forbidden:n = true,
}
Running it without the optional argument should use [myenv,1]
and running it with star \myenvmeta*{new-key}{keya=foo, keyb=baz}
should add the new-key to myenv
at all levels and to myenv*
.
In the path { mymodule / myenv* }
there are all the keys that are already defined and I do not want them to be rewritten, new-key
must have a name different from these.
The code (MWE) that I have for testing is the following:
\documentclass{article}
\ExplSyntaxOn
% Some vars
\int_new:N \l_mymodule_int
\int_new:N \l_mymodule_s_int
% set level
\cs_new:Nn \__mymodule_level: { \int_to_roman:n { \l_mymodule_int } }
% Add keys common for environments
\cs_set_protected:Npn \mymodule_tmp:nn #1 #2
{
\keys_define:nn { mymodule / #1 }
{
keya .tl_set:c = { l_mymodule_keya_#2_tl },
keyb .tl_set:c = { l_mymodule_keyb_#2_tl },
}
}
\clist_map_inline:nn
{ {level-1}{i}, {level-2}{ii}, {level-3}{iii}, {level-4}{iv}, {myenv*}{v} }
{ \mymodule_tmp:nn #1 }
% Add key only for myenv*
\keys_define:nn { mymodule / myenv* }
{
keyc .tl_set:N = \l_mymodule_keyc_v_tl,
}
% envs
\NewDocumentEnvironment{myenv}{ O{} }
{
\int_incr:N \l_mymodule_int
\tl_if_novalue:nF {#1}
{
\exp_args:Ne \keys_set:nn
{ mymodule / level-\int_use:N \l_mymodule_int } {#1}
}
\texttt{myenv ~ level: \int_use:N \l_mymodule_int}; ~
\texttt{keya ~ = ~ \tl_use:c { l_mymodule_keya_ \__mymodule_level: _tl } } ; ~
\texttt{keyb ~ = ~ \tl_use:c { l_mymodule_keyb_ \__mymodule_level: _tl } } \par
}{}
\NewDocumentEnvironment{myenv*}{ O{} }
{
\int_incr:N \l_mymodule_s_int
\tl_if_novalue:nF {#1}
{ \keys_set:nn { mymodule / myenv* } {#1} }
\texttt{myenv* ~ level: \int_use:N \l_mymodule_s_int }; ~
\texttt{keya ~ = ~ \tl_use:N \l_mymodule_keya_v_tl }; ~
\texttt{keyb ~ = ~ \tl_use:N \l_mymodule_keyb_v_tl} ; ~
\texttt{keyc ~ = ~ \tl_use:N \l_mymodule_keyc_v_tl} ; \par
}{}
\ExplSyntaxOff
\begin{document}
\begin{myenv}[keya=Foo,keyb=Baz]
\begin{myenv}[keya=BAZ]
\end{myenv}
\end{myenv}
% myenv*
\begin{myenv*}[keya=STAR]
\end{myenv*}
\begin{myenv}
\end{myenv}
\end{document}
Using this code I would expect it to return an error when using:
\myenvmeta[myenv,1]{keyc}{ keya=foo, keyb=bazz }
since keyc
already exists in { mymodule / myenv* }
If I execute:
\myenvmeta[myenv,2]{keyd}{ keya=foo, keyb=bazz }
I hope it happens:
\begin{myenv}[keyd] % error not exist in this level
\begin{myenv}[keyd] % OK
\end{myenv}
\end{myenv}
and if I execute:
\myenvmeta*{keyd}{ keya=foo, keyb=bazz }
I hope it happens:
\begin{myenv}[keyd] % OK
\begin{myenv}[keyd] % OK
\begin{myenv*}[keyd] % OK
\end{myenv*}
\end{myenv}
\end{myenv}
Can this be done?
I have a similar parser at Adapt and extend a command for key-val parse (l3keys) but I have not been able to adapt it to this and in the question How to append options to an existing .meta:n l3key? there is an adaptable code, but without the security that I would like to have.
\keys_if_exist:nnTF
? If I were trying to do this, I would focus on implementing the simplest case first. Handling the more complex cases is only really a matter of conditional branching, so you don't need a complex design as far as I can see.keys_if_exist:nnTF
is the path for the first argument, but after that I'm lost.l3keys
is designed for, so maybe it would be easier to use\prop_put_from_keyval:Nn
or\keyval_parse:nnn
and implement the desired behaviours yourself.