7

Related Meta Question


From interface3.pdf, we can see l3keys has provided several key properties such as .code:n, .tl_set:N, .usage:n. But I don't know how to define a new key property.

At this time, my code is like this:

\keys_define:nn {test}
  {
    keya .code:n = \somemacro {#1},
    keyb .code:n = \othermacro {#1},
  }

Now I want to define a new key property .mycode:n such that

\keys_define:nn {test}
  {
    keya .mycode:n = \somemacro {#1},
    keyb .mycode:n = \othermacro {#1},
  }

is the same as

\keys_define:nn {test}
  {
    keya .code:n = \MyFunction \somemacro {#1},
    keyb .code:n = \MyFunction \othermacro {#1},
  }

How can I do it? A minimal example is included:

\documentclass{article}
\begin{document}

\newcommand\somemacro[1]{\detokenize{#1}}
\newcommand\othermacro[1]{\detokenize{#1}}
\newcommand\MyFunction[2]{\detokenize{#1,#2}}

\ExplSyntaxOn

\keys_define:nn {test}
  {
    keya .code:n = \somemacro {#1},
    keyb .code:n = \othermacro {#1},
  }
\keys_set:nn {test} {keya = a, keyb = b}

\keys_define:nn {test}
  {
    keya .code:n = \MyFunction \somemacro {#1},
    keyb .code:n = \MyFunction \othermacro {#1},
  }
\keys_set:nn {test} {keya = a, keyb = b}

\keys_define:nn {test}
  {
    keya .mycode:n = \somemacro {#1},
    keyb .mycode:n = \othermacro {#1},
  }
\keys_set:nn {test} {keya = a, keyb = b}

\ExplSyntaxOff

\end{document}

Question Backgournd

Recently I found tcolorbox has an interesting IfEmptyTF key, with which you can write

IfEmptyTF = {⟨token list⟩}{key=val1}{key=val2}

And I decide tabularray should have a similar feature, and can make the interface more natural and more powerful to users:

key = \tlIfEmptyTF{⟨token list⟩}{\prgReturn{val1}}{\prgReturn{val2}}

where \tlIfEmptyTF function is from functional package. That is to say, users can use all functions in functional package in the values and tabularray should replace them with their return values. And this feature should only be enabled if functional library is load by users.

To implement this new feature, while keeping the changes to the code minimal, I think it is the best solution to define a new key property in l3keys.

6
  • Comments are not for extended discussion; this conversation has been moved to chat.
    – Joseph Wright
    Jun 28, 2022 at 6:59
  • Is it possible for l3keys to support per-module (leading parts of a key path) properties? Jul 17, 2022 at 16:26
  • @muzimuzhiZ No, this is not possible with the current implementation. As demonstrated in my answer below, a key property such as .code:n is really just the command with name \key~prop~>~.code:n. There is no module prefix anywhere in that name.
    – Gaussler
    Jul 17, 2022 at 16:34
  • @Gaussler I mean, extending l3keys to support ... . Jul 17, 2022 at 16:37
  • @muzimuzhiZ Would it be possible to rewrite the code of l3keys to allow that feature? Absolutely. Is it going to happen? Based on the discussion in this thread, the answer seems to be a resounding no. It seems that the consensus (which I agree with) is that it would not add much to the system. You can already accomplish pretty much anything you want using .code:n.
    – Gaussler
    Jul 17, 2022 at 16:48

3 Answers 3

10

There isn't an official way to add new properties. As properties are available for all key creation, and thus form part of the language, adding new ones requires careful consideration. (The team did discuss at length how best to add the '2e-friendly' versions for the new option handler: in the end we did make e.g. .if an alias for .legacy_if_set:n in the same namespace, but this was not a trivial decision.)

As it is, .code:n should be used sparingly: most keys are best created to set a variable, which is then queried by the code at run time. If you want to do similar things with several keys, I would expect an internal function to be used

\cs_new_protected:Npn \__mypkg_key_processor:nn #1#2
  {
    % Payload
  }
\keys_define:nn { mypkg }
  {
    keya-a .code:n = \__mypkg_key_processor:nn { a } {#1} ,
    keya-b .code:n = \__mypkg_key_processor:nn { b } {#1} ,
  }

The team are currently considering adding a 'post-action' property. This may resolve to allow a 'set a variable then process' approach more readily. (The issue has arisen specifically for issuing debug data, so it may be that a more focussed property is added instead.)

1
  • 1
    Comments are not for extended discussion; this conversation has been moved to chat.
    – Joseph Wright
    Jun 24, 2022 at 8:51
2

This is clearly extremely bad style (in other words, don’t do this), but I think it’s the only way to accomplish what you want.

\documentclass{article}
\begin{document}

\newcommand\somemacro[1]{\detokenize{#1}}
\newcommand\othermacro[1]{\detokenize{#1}}
\newcommand\MyFunction[2]{\detokenize{#1,#2}}

\ExplSyntaxOn

\cs_new_protected:cpn { key~prop~>~.mycode:n } #1
  { \__keys_cmd_set:nn \l_keys_path_str { \MyFunction #1 } }

\keys_define:nn {test}
  {
    keya .code:n = \somemacro {#1},
    keyb .code:n = \othermacro {#1},
  }
\keys_set:nn {test} {keya = a, keyb = b}

\keys_define:nn {test}
  {
    keya .code:n = \MyFunction \somemacro {#1},
    keyb .code:n = \MyFunction \othermacro {#1},
  }
\keys_set:nn {test} {keya = a, keyb = b}

\keys_define:nn {test}
  {
    keya .mycode:n = \somemacro {#1},
    keyb .mycode:n = \othermacro {#1},
  }
\keys_set:nn {test} {keya = a, keyb = b}

\ExplSyntaxOff

\end{document}
2
  • Comments are not for extended discussion; this conversation has been moved to chat.
    – Joseph Wright
    Jun 28, 2022 at 5:37
  • I wouldn't mind doing this in code which I use privately/at home only and where I can take the risk of things needing to be adjusted as soon as the used expl3-internals get changed.I would be reluctant in doing this in code that gets released to to the public/to others who probably cannot handle problems themselves when expl3-internals are changed. Probably, instead of implementing another key-handler, tricky expansion of \keys_set:nn's 2nd argument can be done so that instead of keya.mycode=... a macro can be used which expands to keya.code=\MyFunction... ? I need to think about it. ;-) Jul 17, 2022 at 1:28
0

Seems adding new key-handlers currently is not supported in l3keys.

If you like code which is more cumbersome to use than what was suggested by Joseph Wright:

How about using a macro \mymodule_mycode:nw{keya}..., which expands to keya.code=\MyFunction..., and doing some e-expansion to \keys_define:nn's second argument?

\ExplSyntaxOn
\cs_new:Npn \mymodule_mycode:nw #1 {\__mymodule_mycode:nw {#1}{}} % prevent brace-removal by ensuring a leading {} with delimited #2
\cs_new:Npn \__mymodule_mycode:nw #1#2, {\exp_not:n {#1.code = \MyFunction } \exp_not:n \exp_after:wN {\use_none:n #2,}}

\exp_args:Nne \keys_define:nn {test}
  {
    \mymodule_mycode:nw{keya} = \somemacro {#1},
    \mymodule_mycode:nw{keyb} = \othermacro {#1},
    % wrap things that shall not be affected by e-expansion into \exp_not:n
    % ...
  }
\ExplSyntaxOff

If it was not l3keys but pgfkeys you could implement a key-handler /.mycode by placing things into the path /handlers/:

\documentclass{article}
\usepackage{pgfkeys}

\pgfkeys{%
  /handlers/.mycode/.style={\pgfkeyscurrentpath/.code={\MyFunction #1}},
  /MyPath/Worldkey/.mycode={World and #1!},
  /MyPath/Moonkey/.mycode={Moon and #1!}
}
\def\MyFunction{Hello, }

\begin{document}

\pgfkeys{/MyPath/Worldkey={You}}

\pgfkeys{/MyPath/Moonkey={You}}

\end{document}

enter image description here

You must log in to answer this question.

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