newcommand with comma separated argument, and optional arguments

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.


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


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