[Tex/LaTex] How to redefine or patch the ‘\newcommand’ command

macrospatching

The Problem

There are several packages I would like to write which require me to redefine the \newcommand (\renewcommand, etc.) command so I can track or change the commands that authors subsequently define. But I have no idea how to go about it. My naive attempts so far have failed. Probably because I am still very weak at the TeX level.

I'm surprised that I couldn't find more than vague hints towards this concept. It feels like such an obvious thing to do.

Motivation

A simple usecase, and the first package I plan write, is to ensure the following: that using \newcommand to define a command – say: \cmd – that has already been defined does not immediately generate an error. \cmd would simply be in a state of conflict. Subsequently trying to expand \cmd would then generate an error (since it's ambiguous which of the two definitions you want).

The conflict could be resolved by subsequently redefining \cmd using \renewcommand, after which \cmd can once again safely be expanded. Example:

\newcommand{\cmd}{FIRST}   %
\cmd                       % outputs FIRST
\newcommand{\cmd}{SECOND}  % no problem yet
\cmd                       % error: expanding ambiguous command
\renewcommand{\cmd}{THIRD} %
\cmd                       % outputs THIRD

This could, for example, be used to mediate conflicts between packages (that use \newcommand). Of course, this concept is still quite weak (for example, what to do if two packages independently use \renewcommand on the same command?). But it is enough to serve as a usecase for my question.

After I learn more about this, I plan to exercise more fine-grained control.

Pseudo Code Solution

It feels like I have to do something like this (ignoring the optional argument for now):

\let\old@newcommand\newcommand
\MetaRenewCommand{\newcommand}{
    \ifdefined#1
        \old@newcommand{#1}{
            \PackageError{lazyfail}{Expanding ambiguous command \protect #1}
        }
    \else
        \old@newcommand{#1}{#3}
    \fi
}

Of course, there are many things wrong with this code. There is no \MetaRenewCommand and I still have to handle the optional argument of \newcommand.

So, how do I start? It feels like it must be possible, as \newcommand and friends are not primitives of LaTeX, but defined in terms of lower level commands.

Further Motivation

Here's another use-case I have in mind for this. When including a package, I would like to ignore all commands it provides except for a small list which I specify:

\usepackagefor[\Lightning]{marvosym}

Here I am loading the marvosym package, but only to use \Ligntning. I choose this example because the marvosym \CheckedBox command conflicts with the one in the llncs class.

I already routinely specify the commands I plan to use a package for with a comment, but it would be nice to actually enforce that.

Best Answer

Tackling the problem as posed is tricky due to the way \newcommand works. Heiko's approach is probably more elegant, but one possible method is to use xparse to deal with the syntax of \newcommand, and letltxmacro to deal with the way \newcommand is set up

\documentclass{article}
\usepackage{letltxmacro,xparse}
\makeatletter
\LetLtxMacro{\saved@newcommand}{\newcommand}
\DeclareDocumentCommand{\newcommand}{s+mo+o+m}{%
  \begingroup
  \edef\x{%
    \endgroup
    \ifdefined#2%
      \unexpanded{%
          \renewcommand{#2}%
            {%
              \PackageError{lazyfail}
                {Expanding ambiguous command \protect #2}%
                \@ehc
            }%
        }
    \else
      \noexpand\saved@newcommand
        \IfBooleanT{#1}{*}%
        {\noexpand#2}%
        \IfNoValueF{#3}{[#3]}%
        \IfNoValueF{#4}{[\unexpanded{#4}]}%
        {\unexpanded{#5}}
    \fi
  }%
  \x
}
\makeatother
\begin{document}
\newcommand{\cmd}{FIRST}   %
\cmd                       % outputs FIRST
\newcommand{\cmd}{SECOND}  % no problem yet
\cmd                       % error: expanding ambiguous command
\renewcommand{\cmd}{THIRD} %
\cmd                       % outputs THIRD
\end{document}

The approach here is to grab all of the arguments to the redefined \newcommand in one go, then work out whether they are to be 'recycled' or not.

Of course, you could set all of this up without xparse, but it would be a pain in the next: lots of \@ifstar and \@ifnextchar ( or slightly better \@testopt) stuff and several auxiliaries.

Related Question