[Tex/LaTex] A macro with either one or two arguments

macrosoptional arguments

I am trying to define a macro with variable number of arguments. For example I want to define a macro with two arguments that can work with one argument as well. So the same macro works in two ways.

\textcolor{red}{sample text}

This would limit the red color to the sample text and whereas just calling \textcolor{red} would turn on the red color until the end of the document. So the second way of using the macro would be

\textcolor{red}

I was trying to implement the macro using \@ifnextchar. I am not having much success. Could you please give me some hints?

Based on @Joseph's suggestion below I modified my toy code and it works. I still have problem understanding the actual execution flow in the code. Here is my code.

\documentclass[12pt]{article}
\makeatletter
\def\oneortwoargs#1{%
\@ifnextchar\bgroup%
   {\twoargs@aux{#1}}
   {\onearg@only{#1}}
}

\def\onearg@only#1{%
   Only argument provided: #1 %
}

\def\twoargs@aux#1#2{%
   Two arguments are provided. \\%
   First argument: #1 \\ %
   Second argument: #2%
}
\makeatother

\begin{document}
\oneortwoargs{gg}{fff}
\oneortwoargs{qq}
\end{document}

As @Joseph explained below one token is left in the input stream. What is confusing me is that \onearg@only and \twoargs@aux are invoked the same way with only one argument.

Best Answer

I'd use the xparse package to do this, as everything is then 'pre-packaged':

\documentclass{article}
\usepackage{color,xparse}
\NewDocumentCommand\MyTextColor{m+g}{%
  \IfNoValueTF{#2}
    {\color{#1}}
    {\textcolor{#1}{#2}}%
}
\begin{document}
\MyTextColor{green}{stuff}

\MyTextColor{red} Some text
\end{document} 

The same can of course be done using \@ifnextchar

\documentclass{article}
\usepackage{color}
\makeatletter
\newcommand*\MyTextColor[1]{%
  \@ifnextchar\bgroup
    {\textcolor{#1}}
    {\color{#1}}%
}
\makeatother
\begin{document}
\MyTextColor{green}{stuff}

\MyTextColor{red} Some text
\end{document} 

The key point is that you need to look for { using \bgroup rather than trying to use it directly (as it is the begin-group character).


To explain how the \@ifnextchar part deals with the need for either 1 or 2 arguments, what happens in my second approach is that

\newcommand*\MyTextColor[1]{%

defines a macro which absorbs one argument. So:

\MyTextColor{red}{stuff}

absorbs red as #1 and leaves {stuff} in the input stream, while

\MyTextColor{red} other stuff

also absorbs red as #1 and leaves other stuff in the input stream. We now apply \@ifnextchar, which 'peeks' at the next token without absorbing it. The test reads

  \@ifnextchar\bgroup
    {\textcolor{#1}}
    {\color{#1}}%

and so depending on the result, either \textcolor{red} or \color{red} is inserted into the input stream. Thus we end up with either

\textcolor{red}{stuff}

or

\color{red} other stuff

which means that \textcolor will get the required two arguments, while \color only gets one. The key is that {red} is reinserted into the input after either \textcolor or \color.


Although it is not directly related to the question, a few notes on the xparse approach may be useful. When using the xparse package, arguments are described by letter, with m representing a mandatory argument and g representing an optional argument in braces (i.e. an optional TeX group). A + before the letter allows that argument to accept paragraph tokens, so in my definition the first argument cannot (it's supposed to be the name of a colour), while the second one can (it is arbitrary text).

The g argument returns a special token (\NoValue) if the optional argument was not present at all. If a default value was wanted, I'd have used G{<default>}. The same approach applies to standard LaTeX optional arguments, which are represented as o or O, depending on whether a default value is required. I have tested for \NoValue with the test \IfNoValueTF; \IfNoValueT and \IfNoValueT are also available.