8

I want to define a command (not with \def) the simplest possible way that it

  • takes arguments separated by a comma,
  • supports an optional argument (the first one).

maybe another solution with extra optional arguments which are not necessarily the firsts will be welcome in the future.

I want to have something like this: \mycmd[opt arg1, opt arg2]{arg3, arg4}.

I already saw https://tex.stackexchange.com/a/29975/92620 and https://tex.stackexchange.com/a/2860/92620.

\documentclass{report}
\newcommand\mycmd[4][]{optional: #1\\ mandatory: #2 - #3 - #4}

\begin{document}
\mycmd[optional arg]{second arg,third arg,fourth arg}
\end{document}
1

7 Answers 7

6

The following example uses \comma@parse of package kvsetkeys to parse the comma separated argument lists.

\documentclass{report}
\usepackage{kvsetkeys}% provides \comma@parse
\usepackage{etexcmds}% provides \etex@unexpanded

\makeatletter
\newcount\arg@count
\newcommand{\arg@parser}[1]{%
  \advance\arg@count\@ne
  \expandafter\let\csname arg\romannumeral\arg@count\endcsname\comma@entry
}
% \mycmd[opt arg1, opt arg2]{arg 3, arg4, arg5}
\newcommand\mycmd[2][]{% Default is empty and will be configured later
  % Set default values
  \arg@count=\z@
  \comma@parse{default1, default2}\arg@parser % Default values
  % Parse optional argument
  \arg@count=\z@
  \comma@parse{#1}\arg@parser
  \ifnum\arg@count>2 %
    \@latex@error{Too many optional arguments}{%
      The macro \string\mycmd\space got \the\arg@count\space
      optional arguments,\MessageBreak
      but expected are 2 optional arguments.\MessageBreak
      \@ehd
    }%
  \fi
  % Mandatory arguments
  \arg@count=2
  \comma@parse{#2}\arg@parser
  \ifnum\arg@count=5 %
  \else
    \@latex@error{Wrong number of mandatory arguments}{%
      The macro \string\mycmd\space got \the\numexpr\arg@count-2\relax\space
      mandatory arguments,\MessageBreak
      but expected are 3 mandatory arguments.\MessageBreak
      \@ehd
    }%
  \fi
  % Either using \argi, \argii, \argiii, \argiv, \argv
  % or
  % \@mycmd\argi\argii\argiii\argiv\argv
  % or
  \edef\process@me{%
    \noexpand\@mycmd
    {\etex@unexpanded\expandafter{\argi}}%
    {\etex@unexpanded\expandafter{\argii}}%
    {\etex@unexpanded\expandafter{\argiii}}%
    {\etex@unexpanded\expandafter{\argiv}}%
    {\etex@unexpanded\expandafter{\argv}}%
  }%
  \process@me
}
\newcommand{\@mycmd}[5]{%
  \noindent
  optional: #1 -- #2\\
  mandatory: #3 -- #4 -- #5%
}
\makeatother

\begin{document}
  \mycmd[optional arg]{third arg,fourth arg, fifth arg }
\end{document}

Result

Remarks:

  • The list of optional arguments can be shorter, the omitted arguments gets default values.

  • \comma@parse trims the arguments by removing leading and trailing space.

  • \comma@parse removes empty entries. A workaround for empty arguments is using \relax or \empty.

6

A variant of jon's answer, where the original setup is used. It's mainly for showing how expl3 makes things easy.

I'll assume you want to separate the prefix the optional arguments by “Optional:”, then typeset them on a line, separated by “space, en-dash, space; the mandatory arguments are prefixed by “Mandatory” and separated by “space, em-dash, space”.

\documentclass{article}

\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\mycmd}{om}
 {
  \IfValueT{#1}
   {
    Optional:~\guest_print_list:nn { #1 } { ~--~ } \\*
   }
  Mandatory:~\guest_print_list:nn { #2 } { ~---~ }
 }

\seq_new:N \l_guest_list_seq
\cs_new_protected:Nn \guest_print_list:nn
 {
  \seq_set_from_clist:Nn \l_guest_list_seq { #1 }
  \seq_use:Nn \l_guest_list_seq { #2 }
 }

\ExplSyntaxOff

\setlength{\parindent}{0pt} % just for the example

\begin{document}

\mycmd{a, b , c}

\mycmd{a}

\mycmd[A]{a,b,c}

\mycmd[A, B]{a,b,c}

\end{document}

Quite straightforward, with no code duplication, by reusing the \guest_print_list:nn function, which takes the list of arguments to be printed as the first argument and the separator as the second argument. Note that spaces around the commas in the input are ignored.

enter image description here

5

This solution works with two light-weight comma parsers defined in the solution itself (\p@rse@csl@opt and \p@rse@csl@mnd). What is executed on the parsed arguments is layed out to \domycmdopt and \domycmdmnd (resp.):

\documentclass{article}

\makeatletter
\newcommand\mycmd[2][]{%
  \if\relax\detokenize{#1}\relax\else\p@rse@csl@opt#1,\@nil\fi
  \p@rse@csl@mnd#2,\@nil
}
\def\p@rse@csl@opt#1,#2{%
  \domycmdopt{#1}%
  \ifx#2\@nil\else\expandafter\p@rse@csl@opt#2\fi
}
\def\p@rse@csl@mnd#1,#2{%
  \domycmdmnd{#1}%
  \ifx#2\@nil\else\expandafter\p@rse@csl@mnd#2\fi
}
\def\domycmdopt#1{opt-arg:#1\par}
\def\domycmdmnd#1{mnd-arg:#1\par}
\makeatletter

\begin{document}
\def\abc{def}
\mycmd[f00,bar]{hell0,w0rld}
\mycmd{1,2,3,$\alpha$,\abc}
\end{document}

output_crop

3

If you know exact number of arguments, then simple \def with comma separated arguments is sufficient and you can denote the parameters by #1, #2, etc. in macro body. If the exact number of argument isn't known then different approach must be chosen and you cannot denote parameters as #1, ... #n in macro body.

The solution for first variant follows:

\def\sdef#1{\expandafter\def\csname#1\endcsname}
\long\def\isnextchar#1#2#3{\begingroup\toks0={\endgroup#2}\toks1={\endgroup#3}%
   \let\tmp=#1\futurelet\next\isnextcharA
}
\def\isnextcharA{\the\toks\ifx\tmp\next0\else1\fi\space}

\def\optdef#1[#2]{%
   \def#1{\def\opt{#2}\isnextchar[{\csname oA:\string#1\endcsname}{\csname oB:\string#1\endcsname}}%
   \sdef{oA:\string#1}[##1]{\def\opt{##1}\csname oB:\string#1\nospaceafter\endcsname}%
   \sdef{oB:\string#1\nospaceafter}%
}
\def\nospaceafter#1{\expandafter#1\romannumeral-`\.}
\def\optp#1{\expandafter\optpA\opt,,,,,,,\end{#1}}
\def\optpA#1,#2,#3,#4,#5,#6,#7\end#8{\ifcase#8 \or#1\or#2\or#3\or#4\or#5\or#6\fi}

\optdef\mycmd [defaultA,defaultB] #1{\mycmdA#1,}
\def\mycmdA#1,#2,#3,{optional: \optp1 -- \optp2, mandatory: #1 -- #2 -- #3}

\mycmd [opt-argA, opt-argB] {first, second, third}

\end
2
  • i am testing which is the best simple solution to suit my needs, it looks good however how to support more than one optional arg \mycmd [opt-arg , opt-arg2] {first, second, third} (assuming number of arguments is known)
    – Cfun
    Apr 12, 2016 at 19:38
  • OK, I added code for more optional argument (max. 6). They are called by \optp1, \optp2 etc. in macro body.
    – wipet
    Apr 12, 2016 at 19:57
2

If the number of arguments is not known, one can use pgfkeys for something like this:

\documentclass{article}

\usepackage{pgfkeys,pgffor}

\newcounter{mainargs}
\pgfkeys{mainargs/.is family, mainargs,
step counter/.code=\stepcounter{mainargs},
add argument/.style={step counter, arg\themainargs/.initial={#1}},
}

\newcounter{optargs}
\newif\ifoptargs
\pgfkeys{optargs/.is family, optargs,
    opt args present/.is if=optargs,
    step counter/.code=\stepcounter{optargs},
    add argument/.style={opt args present=true, step counter, arg\theoptargs/.initial={#1}},
}

\newcommand{\mycommand}[2][]{%
    \setcounter{mainargs}{0}%
    \pgfkeys{mainargs, add argument/.list={#2}}%
    \setcounter{optargs}{0}%
    \pgfkeys{optargs, add argument/.list={#1}}%
    %
    main arguments:\\%
    \foreach \n in {1,...,\themainargs}{%
    \pgfkeysvalueof{/mainargs/arg\n}\\%
    }%
    %
    \ifoptargs%
        optional arguments:\\%
        \foreach \n in {1,...,\theoptargs}{%
            \pgfkeysvalueof{/optargs/arg\n}\\%
        }%
    \fi%
}

\parindent=0pt
\begin{document}
    \mycommand{foo, bar}\\
    \mycommand[a,b]{foo, bar, baz}
\end{document}

Of course, if the number of arguments is known, one can use \pgfkeysvalueof{/mainargs/arg1}, \pgfkeysvalueof{/mainargs/arg2}, etc. without the foreach loop.

As the argument values are available even after the execution of the command finished, it is probably safer to use a group to keep the definitions local or to define reset keys like:

\pgfkeys{mainargs,
    reset one arg/.style={arg#1/.initial={}},
    reset args/.style={reset one arg/.list={1,...,\themainargs}}    
}

\pgfkeys{optargs,
    reset one arg/.style={arg#1/.initial={}},
    reset args/.style={reset one arg/.list={1,...,\theoptargs}, opt args present=false} 
}

Then one should call them at the end of the command definition:

\pgfkeys{mainargs, reset args}
\pgfkeys{optargs, reset args}

The complete command with the formatting used in the question is:

\newcommand{\mycommand}[2][]{%
    \setcounter{mainargs}{0}%
    \pgfkeys{mainargs, add argument/.list={#2}}%
    \setcounter{optargs}{0}%
    \pgfkeys{optargs, add argument/.list={#1}}%
    %
    \ifoptargs%
        optional: %
        \pgfkeysvalueof{/optargs/arg1}
        \ifnum\theoptargs>1%
        \foreach \n in {2,...,\theoptargs}{%
            --- \pgfkeysvalueof{/optargs/arg\n}%
        }%
        \fi\\%
        \pgfkeys{optargs, reset args}%
    \fi%
    %
    mandatory: %
    \pgfkeysvalueof{/mainargs/arg1} 
    \ifnum\themainargs>1%
        \foreach \n in {2,...,\themainargs}{%
            --- \pgfkeysvalueof{/mainargs/arg\n}%
        }%
    \fi%
    %
    \pgfkeys{mainargs, reset args}%
}
2

Perhaps something like this? I used 'bold' formatting as an arbitrary choice to help show which parts are from the command and which parts from the input to \mycmd.

\documentclass{article}
\parindent 0pt % just for this example

\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\mycmd}{ om }
{
 \IfValueT{#1}{Optional: ~ #1\\*}
  \bold_items:n {#2}
}
\cs_new_protected:Npn \bold_items:n #1
{
  % set a sequence from the clist
  \seq_set_from_clist:Nn \l_guest_tmpa_seq {#1}
  % applies final arg to each element of second seq and stores result
  % in first seq
  \seq_set_map:NNn \l_guest_tmpb_seq \l_guest_tmpa_seq {
    \exp_not:n { \textbf { ##1 } }
  }
  % \seq_use puts the items from the seq back in the input
  \seq_use:Nnnn \l_guest_tmpb_seq {,~}{,~}{, ~ and ~}
}

\seq_new:N \l_guest_tmpa_seq
\seq_new:N \l_guest_tmpb_seq

\ExplSyntaxOff

\begin{document} 

\mycmd[OPT]{second arg, third arg, fourth arg}

\mycmd{one, and two only}

\mycmd[OPT]{This,that,{this one}}

\mycmd{1.1,1.2,1.3, 1.4, 1.5}

\end{document}

This way you are not limited to a specific number of arguments.

... However, if you are going to go down the 'list' route, you should strongly consider whether a 'key-value' approach would be more suitable. Commands that simply 'list' the arguments (whether delimited by commas or braces) quickly become opaque to other readers of the code --- including your future self.

2
1

Another solution using LuaLatex:

\documentclass{article}

\usepackage{fontspec}
\usepackage{luacode}

\newcommand{\mycmd}[2][]{%
\luadirect{
    oargs = \luastring{#1}
    if oargs \string~= "" then
        tex.print("optional: ")
        oargs = string.explode(oargs,',+')
        for i, arg in ipairs(oargs) do oargs[i] = string.strip(arg) end
        tex.print(table.concat(oargs, " --- ") .. \luastring{\string\\})
    end
    tex.print("mandatory: ")
    margs = \luastring{#2}
    margs = string.explode(margs,",+")
    for i, arg in ipairs(margs) do margs[i] = string.strip(arg) end
    tex.print(table.concat(margs, " --- "))
    }
}

\parindent=0pt
\begin{document}
    \mycmd[a, b]{foo, bar, baz}

    \bigskip
    \mycmd{foo, bar}

    \bigskip
    \mycmd[a,]{,foo}
\end{document}

output

You must log in to answer this question.

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