8

I’ve created a simple package with options settable via l3keys2e. I was able to figure out how to make the syntax

\usepackage[format = international]{phone}

work, but I’d like to make the syntax

\usepackage[international]{phone}

be a synonym. The method I’m using in the code below works, but is very repetitive, especially when I’ve got more formatting options. Is there a simpler way?

Here’s the code:

\RequirePackage{expl3, l3keys2e, xparse}
\ProvidesExplPackage
    {phone}{2013/06/19}{0.1}{Format phone numbers}

\cs_new_nopar:Npn \phone_international_fmt:NNN  #1#2#3
  { +1 - #1 - #2 - #3 }

\cs_new_nopar:Npn \phone_parendash_fmt:NNN  #1#2#3
  { (#1) \nobreakspace #2 - #3 }

\keys_define:nn { phone }
  {
    format .choice_code:n =
      {
        \cs_new_eq:Nc \phone_fmt:NNN 
          { phone_ \tl_use:N \l_keys_choice_tl _fmt:NNN }
      },
    format .generate_choices:n =
      {
        international,
        parendash,
      },
% These next lines will become repetitive if I have many more formatting options
    international .meta:n = { format = international },
    parendash     .meta:n = { format = parendash     },
  }

\ProcessKeysOptions { phone }

\NewDocumentCommand \phone
  { >{ \SplitArgument {2} {-} } m }
  { \phone_fmt:NNN #1 }

\endinput

A minimal example using this package looks like this:

\documentclass{article}

% \usepackage[parendash]{phone}
\usepackage[format = parendash]{phone}

\begin{document}

\phone{212-555-1212}

\end{document}

This yields the desired result

(212) 555-1212

  • I’m not at all certain I’ve got the nomenclature correct; feel free to rename this question to fix this. – J. C. Salomon Jun 19 '13 at 23:23
  • Aside: \cs_new_nopar:Npn \phone_international_fmt:NNN isn't really encouraged, and I'd suggest \cs_new:Npn \phone_international_fmt:NNN instead. Apart from a few specific internals, we've standardised on a position that functions with arguments should be 'long'. – Joseph Wright Jun 20 '13 at 07:46
  • Shouldn't the :NNN argument spec rather be :nnn? – cgnieder Jun 20 '13 at 08:04
  • @cgnieder Yes; in that code all the :NNN should be :nnn – egreg Jun 20 '13 at 08:55
  • @JosephWright, is that the case, even though xparse defaults to short arguments? – J. C. Salomon Jun 20 '13 at 14:46
  • @J.C.Salomon Yes, as xparse is a different thing. At the document level it's most sensible to be restrictive as standard (most document commands don't make sense for multiple paragraphs), but at the code level this is different. A reasonable number of LaTeX bugs were caused by internal code not accepting \par tokens. – Joseph Wright Jun 20 '13 at 14:50
  • @JosephWright, but in this particular case the semantics do imply that \par tokens should be forbidden. But now I’ll make that choice intentionally, and know what the default should be; thanks. – J. C. Salomon Jun 20 '13 at 14:53
  • @J.C.Salomon Unless you control the input at the code level (for example an auxiliary), the risk of a warning about a \par token from an internal function is really not worth it. Much better to pick up at the document level and know that at the code level everything is 'long'. – Joseph Wright Jun 20 '13 at 17:14
  • @JosephWright The xparse-generated function \phone is the only thing that calls these internal functions; is that insufficient? Or is allowing long arguments just that much safer? – J. C. Salomon Jun 20 '13 at 18:06

2 Answers2

8

The approach you've taken looks correct. Internally, the .meta:n property is creating a standard key which is more-or-less equivalent to

key-name-1 .code:n = { \keys_set:nn { module } { key-name-2 = #1 } }

If you have a large number of these meta keys to create, you might want to use a comma-list mapping

\clist_map_inline:nn { key-one , key-two , key-three ... }
  { \keys_define:nn { mymodule } { #1 .meta:n = { format = #1 } }

but for just a couple this is probably making your code more not less complex.

Joseph Wright
  • 259,911
  • 34
  • 706
  • 1,036
  • Considering that I’ll also be adding .value_forbidden: for each of these shortcuts, the benefit starts to outweigh the cost very quickly. – J. C. Salomon Jun 20 '13 at 14:59
6

You could use an unknown key handler for this whereby you pass any unknown key back into the system as format = <unknown key> (a similar system is used by TikZ to recognise colours and node shapes). Here's an example which uses that. It gives a warning if there isn't a corresponding format.

I have to use a helper command to feed the unknown key back into the key handler since if I just did format / unknown .code:n = { \keys_set:nn { phone} { format = #1 } } then the #1 would be \l_keys_key_tl and L3 would be extremely careful and not expand the \l_keys_key_tl until the last minute, by which time it would have changed its value from the unknown key to format. The helper macro allows me to use the value of \l_keys_key_tl instead of the token list variable.

I also fixed the NNN to nnn. Because I wanted to set a default, the setter inside the key handler is now \cs_set_eq:Nc instead of \cs_new_eq:Nc.

\documentclass{article}
%\url{http://tex.stackexchange.com/q/120102/86}
\usepackage{filecontents}

\begin{filecontents}{phone.sty}
\RequirePackage{expl3, l3keys2e, xparse}
\ProvidesExplPackage
    {phone}{2013/06/19}{0.1}{Format phone numbers}

\cs_new_nopar:Npn \phone_international_fmt:nnn  #1#2#3
  { +1 - #1 - #2 - #3 }

\cs_new_nopar:Npn \phone_parendash_fmt:nnn  #1#2#3
  { (#1) \nobreakspace #2 - #3 }

% Set a default
\cs_set_eq:NN \phone_fmt:nnn \phone_parendash_fmt:nnn

\msg_new:nnnn {phone} {no~ format} {Format~ `#1'~ is~ not~ recognised~ by~ this~ package,~ using~ the~ default~ `parendash'~ instead.} {}

\keys_define:nn { phone }
  {
    format .choice:,
    format .choice_code:n =
      {
        \cs_set_eq:Nc \phone_fmt:nnn 
        { phone_ \tl_use:N \l_keys_choice_tl _fmt:nnn }
      },
    format .generate_choices:n =
      {
        international,
        parendash,
      },
      format / unknown .code:n = {
        \msg_warning:nnn {phone} {no~ format} {#1}
      },
    unknown .code:n = {
      \phone_unknown_key:V \l_keys_key_tl
    },
  }

\cs_new:Npn \phone_unknown_key:n #1
{
  \keys_set:nn { phone} { format = #1 }
}

\cs_generate_variant:Nn \phone_unknown_key:n {V}


\ProcessKeysOptions { phone }

\NewDocumentCommand \phone
  { >{ \SplitArgument {2} {-} } m }
  { \phone_fmt:nnn #1 }

\endinput
\end{filecontents}
 \usepackage[british]{phone}

\begin{document}

\phone{212-555-1212}

\end{document}
Andrew Stacey
  • 153,724
  • 43
  • 389
  • 751
  • This answers the question I asked, hence the upvote and acceptance. As it happens, the actual package has multiple keys for which I want the values as shortcuts, so I’ll actually be using Joseph’s solution. – J. C. Salomon Jun 20 '13 at 14:51