Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

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.

share|improve this question
2  
\providecommand provides your "no problem yet" requirement... but not generating an error upon subsequent usage. –  Werner Sep 19 '12 at 21:35
    
It might be useful to (perhaps separately) ask about the wider thing you're trying to do. You mention 'many packages', so it would be handy to see if there is some better way to achieve the bigger goal than fiddling around with LaTeX kernel commands. –  Joseph Wright Sep 19 '12 at 21:38
    
Werner: Assume that I am the package writer, and have no control over the code in the 'Motivation' section. –  mhelvens Sep 19 '12 at 21:39
    
Joseph: Well, the 'Motivation' section already describes a standalone usecase, I think. I want to expand it, but that's only going to require more control over how the author's commands are defined. Also, this is a good opportunity for me to learn more about the guts of TeX. Would you humour me? :-) –  mhelvens Sep 19 '12 at 21:42
2  
@mhelvens My point is that your use case should give an error: that's the point of \newcommand (if you just want to force the issue, you use \def in a package). I'm not sure I see what you are really up to: if two packages define \foo, then trouble will ensue, hence the existence of \newcommand to warn about this and allow some action to be taken. (That said, at a technical level this is doable, certainly as an exercise.) –  Joseph Wright Sep 19 '12 at 21:45

2 Answers 2

up vote 5 down vote accepted

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.

share|improve this answer
    
Nice! I can almost fully understand this one. And thanks for introducing me to xparse! Why doesn't everyone use it all the time? :-) –  mhelvens Sep 20 '12 at 9:52
    
@mhelvens xparse is quite new (at least in the usable form it's in now). –  Joseph Wright Sep 20 '12 at 10:19
    
I have one question about your code: Why do you need \begingroup and \endgroup there? Thanks! –  mhelvens Sep 20 '12 at 10:22
1  
@mhelvens The group here means that \x is not affected outside of the use here to expand material. This is a standard 'trick' to avoid breaking anyone else's code. –  Joseph Wright Sep 20 '12 at 10:34
    
Ah, I see. That's even better than using @, which I thought was the standard approach for that. Thanks! –  mhelvens Sep 20 '12 at 11:16

Instead of \newcommand I would redefine \@ifdefinable. Then also some other things like \newcounter or \newsavebox are catched:

\documentclass{article}

\makeatletter
% Save old meaning of \@ifdefinable in \saved@ifdefinable
\newcommand*{\saved@ifdefinable}{}
\let\saved@ifdefinable\@ifdefinable
% Redefine \@ifdefinable
% #1: command token
% #2: code that defines the command in #1
\renewcommand{\@ifdefinable}[2]{%
  % Here the same test for checking #1 is used as in the
  % original definition.
  \edef\reserved@a{\expandafter\@gobble\string#1}%
  % \reserved@a contains the name without backslash
  \@ifundefined\reserved@a{%
    \saved@ifdefinable{#1}{#2}%
  }{%
    % Report the command with the name clash in the .log file
    \@latex@info{Ambigous command: \string#1}%
    % Redefine the command to generate an error message.
    % \@ehd is the standard help text that starts with "You're in trouble here."
    \def#1{\@latex@error{Expanding ambiguous command}\@ehd}%
  }%
}
\makeatother

\begin{document}

\newcommand*{\cmd}[1]{FIRST(#1)}
\cmd{argument}

\newcommand*{\cmd}[2]{SECOND(#1,#2)}
\cmd{param1}{param2}

\renewcommand*{\cmd}{THIRD}
\cmd

\end{document}

"Border cases":

  • LaTeX complains if someone tries to define a command \end... starting with end or in the case of \relax. These cases are not redefined by the above redefinitions.

  • Treatment of arguments is ambiguous. In the case with the ambiguous error the arguments remain untouchted in the input.

  • Commands are only detected, if they are defined via the LaTeX interface (\newcommand, \newcounter, \newsavebox, …). Definitions can also be done by TeX's primitive commands \def, \edef, \gdef, … or plain TeX commands (\newcount et. al.) These commands do not differentiate between new and old commands.


Answer for Further Motiviation:

Of course, \newcommand could be redefined to drop some definitions and keep \Lightning as only command of package marvosym, but

  • Other macros, defined without \newcommand, might be available with undesired effects.
  • The kept macro might rely on other macros that are defined by \newcommand and not kept.
share|improve this answer
    
Hehe, great! I love it when code 'just works'. ;-) Of course, I have no idea how you did it (so I don't know how to reproduce it), nor if there are any corner-cases that this code doesn't yet cover. It would be great if you could point me to a book or website that could teach me to understand your code. For now, I've upvoted your answer, but will hold off on accepting it for now, until I've looked at some other answers (and have slept on it). Cheers! –  mhelvens Sep 19 '12 at 22:18
    
As for the 'further motivation' bit, see my answer to egreg's comment on my original post. I haven't figured out yet exactly how to go about it, but I'd like to try. –  mhelvens Sep 19 '12 at 22:19
    
@mhelvens Further reading for plain TeX: TeX by Topic. LaTeX internals: source2e.pdf. –  Heiko Oberdiek Sep 19 '12 at 22:36
    
Thanks a lot for the comments you put in your code! It's a lot more understandable now. –  mhelvens Sep 20 '12 at 11:31

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.