2

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.

14
  • Have you tried anything? I don't see any attempt in your code?
    – cfr
    Commented Jun 21 at 3:01
  • Are you looking for \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.
    – cfr
    Commented Jun 21 at 3:09
  • Only adaptations of (tex.stackexchange.com/q/719051) but they just dumb down the question. I know that keys_if_exist:nnTF is the path for the first argument, but after that I'm lost. Commented Jun 21 at 3:11
  • @cfr I tried to adapt (gist.github.com/pablgonz/…) but that code is already semi incorrect and too long to post here..so I did do my homework before asking :D Commented Jun 21 at 3:28
  • What you're trying to do isn't quite what 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. Commented Jun 21 at 3:54

1 Answer 1

4

The following does that (if I understood you correctly). It needs two error messages that I didn't define, the first is unknown-set that's thrown when the optional argument can't be resolved to one of your defined key sets. The second is already-defined that's thrown when the new meta key already exists either in the set at hand, or in the myenv* set.

There might be more elegant ways to resolve myenv,<n> to level-<n>, but I guess the prop is fine (and spaces are ignored in my implementation, so myenv, 1 would work as well -- but also m y e n v , 1).

\documentclass{article}
\ExplSyntaxOn
% Some vars
\tl_new:N \l__mymodule_path_tl
\prop_const_from_keyval:Nn \c__mymodule_paths_prop
  {
    {myenv,1} = level-1,
    {myenv,2} = level-2,
    {myenv,3} = level-3,
    {myenv,4} = level-4,
    {myenv*}  = myenv*
  }
\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
  }{}

\msg_new:nnn { mymodule } { prohibited-unknown }
  { The~ name~ `unknown'~ can't~ be~ chosen~ for~ a~ meta~ key. }

\NewDocumentCommand \myenvmeta { s O{myenv,1} m m }
  {
    \str_if_eq:eeTF { \tl_trim_spaces:n {#3} } { unknown }
      { \msg_error:nn { mymodule } { prohibited-unknown } }
      {
        \IfBooleanTF {#1}
          {
            \int_step_inline:nn { 4 }
              { \mymodule_env_meta:nnn { myenv, ##1 } {#3} {#4} }
            \mymodule_env_meta:nnn { myenv* } {#3} {#4}
          }
          { \mymodule_env_meta:nnn {#2} {#3} {#4} }
      }
  }
\cs_new_protected:Npn \mymodule_env_meta:nnn #1
  {
    \tl_set:Nn \l__mymodule_path_tl {#1}
    \tl_replace_all:Nnn \l__mymodule_path_tl { ~ } {}
    \prop_get:NVNTF
      \c__mymodule_paths_prop \l__mymodule_path_tl \l__mymodule_path_tl
      { \__mymodule_env_meta:Vnn \l__mymodule_path_tl }
      {
        \msg_error:nnn { mymodule } { unknown-set } {#1}
        \use_none:nn
      }
  }
\cs_new_protected:Npn \__mymodule_env_meta:nnn #1#2#3
  {
    \bool_lazy_or:nnTF
      { \keys_if_exist_p:nn { mymodule / #1} {#2} }
      { \keys_if_exist_p:nn { mymodule / myenv* } {#2} }
      { \msg_error:nnn { mymodule } { already-defined } {#2} }
      {
        \keys_define:nn { mymodule / #1 }
          {
            #2 .meta:n = {#3},
            #2 .value_forbidden:n = true
          }
      }
  }
\cs_generate_variant:Nn \__mymodule_env_meta:nnn { V }
\ExplSyntaxOff

\myenvmeta[myenv,1]{keyc}{ keya=foo, keyb=bazz }
\myenvmeta[myenv,2]{keyd}{ keya=foo, keyb=bazz }

\begin{document}
\begin{myenv}%[keyd] % error not exist in this level
  \begin{myenv}[keyd] % OK
  \end{myenv}
\end{myenv}
\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}
3
  • That's exactly what I was looking for, thank you very much :D Commented Jun 21 at 22:07
  • 1
    (+1) for just understanding the question. I have zero idea how.
    – cfr
    Commented Jun 22 at 0:57
  • 1
    @cfr good guess, hence the note in parentheses :) At first I understood it completely different.
    – Skillmon
    Commented Jun 22 at 7:47

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .