# How can I use \patchcmd with commands with optional arguments?

This question led to a new package:
xpatch

This is likely a simple question, but I'm getting stuck on the small details. I have an internal command, defined by a package as

\newcommand\foo@[3][]{...stuff...}


and somewhere in there I'd like to patch the command to include a call to \label{#1}. I'm already using etoolbox, so I planned to use

\patchcmd{\foo@}{hook}{\label{#1}hook}{success}{failure}


Unfortunately this doesn't work, because \foo@ is defined as

> \foo@=macro:
->\@protected@testopt \foo@ \\foo@ {}.
l.359 \show\foo@


Which has no parameters and therefore the patching code fails. So I tried to figure out how to get \\foo@  (note the doubled slash and the trailing space) as a csname, and I got snarled up.


So. What is the correct name for the internal macro defined by \newcommand when optional arguments are present, and how do I build the csname for that so I can patch it with \patchcmd?

-

You can get \\foo@ using \csname\string\foo@\endcsname. The space shown behind it with \show is not part of the macro name (but could be added using a \space before \endcsname). So you can use:

\expandafter\patchcmd\csname\string\foo@\endcsname{hook}{\label{#1}hook}{success}{failure}


Full example:

\documentclass{article}

\usepackage{etoolbox}

\begin{document}

\makeatletter
\newcommand\foo@[3][]{stuff (#1) more stuff :#2: hook even more stuff = *#3*}

\expandafter\patchcmd\csname\string\foo@\endcsname{hook}{\label{#1}hook}{success}{failure}

\texttt{\string\foo@ = \meaning\foo@}

\texttt{\expandafter\string\csname\string\foo@\endcsname = \expandafter\meaning\csname\string\foo@\endcsname}

\makeatother

\section{Test}
\makeatletter
\foo@[test]{One}{Two}
\makeatother

\section{Test2}
See section \ref{test}!

\end{document}


-
Thanks, that works. (Well, with the slight tweak that I need a trailing space, so \csname\string\foo@ \endcsname, but the \string command was what I was missing) –  Ben Lerner Jul 27 '11 at 17:16
I don't see why you need the space. \show places one space after every control sequence (aka macro), so there would be two after \\foo@ if it would include one space. I know that robust commands use a trailing space in the internal macro name, but this isn't used here. –  Martin Scharrer Jul 27 '11 at 17:42
@Ben Lerne: besides, the space before \endcsname in your comment is ignored by TeX, since it follows a control word. –  Bruno Le Floch Jul 28 '11 at 13:34

NOTE: There are some minor weaknesses in the code shown below, so it's best to use the corresponding package.

The following macros define the commands \xpatchcmd, \xpretocmd and \xapptocmd that recognize robust commands (defined by \DeclareRobustCommand or \newrobustcmd) and also if they have an optional argument.

\usepackage{etoolbox,xparse}

\ExplSyntaxOn
\makeatletter
% generate a variant of \tl_if_in:NnT
\cs_generate_variant:Nn \tl_if_in:NnT { Nx }
% a boolean for testing robust commands
\bool_new:N \l_xpatch_protect_bool
% a "bizarre" token list that's quite improbable to find in a replacement text
\tl_set:Nx \c_xpatch_bizarre_tl { \tl_to_str:n { **)-(**/**[-]** } }
% the main command; it takes as first argument one of \patchcmd, \pretocmd or
% \apptocmd; the second argument is the command we want to patch
\cs_new:Npn \xpatchcmd_main:NN #1 #2
{
% first of all we gather the command-to-patch name
\tl_set:Nx \l_xpatch_name_tl { \exp_after:wN \use_none:n \token_to_str:N #2  }
% now we get the replacement text of the command-to-patch, but adding the bizarre
% token list in front of it which consists of all category 12 characters
\tl_set:Nx \l_xpatch_tmpa_tl { \c_xpatch_bizarre_tl \token_get_replacement_spec:N #2 }
% now we look whether the token list contains the bizarre list followed by \protect
% which happens if it's a control sequence defined by \DeclareRobustCommand, so
% we add a space to the command name
\tl_if_in:NxT \l_xpatch_tmpa_tl { \c_xpatch_bizarre_tl \token_to_str:N \protect}
{
\bool_set_true:N \l_xpatch_protect_bool
}
% now we look whether the token list contains the bizarre list followed by \x@protect
% which happens if it's a control symbol defined by \DeclareRobustCommand, so
% we add a space to the command name
\tl_if_in:NxT \l_xpatch_tmpa_tl { \c_xpatch_bizarre_tl \token_to_str:N \x@protect}
{
\bool_set_true:N \l_xpatch_protect_bool
}
% now we look whether the token list contains the bizarre list followed by \@protected@testopt
% which happens if it's a command with an optional argument (from \newcommand)
\tl_if_in:NxT \l_xpatch_tmpa_tl { \c_xpatch_bizarre_tl \token_to_str:N \@protected@testopt}
% now we look whether the token list contains the bizarre list followed by \@testopt
% which happens if it's a command with an optional argument (from \newrobustcmd)
\tl_if_in:NxT \l_xpatch_tmpa_tl { \c_xpatch_bizarre_tl \token_to_str:N \@testopt}
% if the command-to-patch was defined by \DeclareRobustCommand we look whether the
% command name with backslash in front and space at the end exists and we assume that
% it has an optional argument
\bool_if:NT \l_xpatch_protect_bool
{
\cs_if_exist:cT { \c_backslash_tl \l_xpatch_name_tl }
}
% now we pass the real command-to-patch name to the patching macro
\exp_after:wN #1 \cs:w \l_xpatch_name_tl \cs_end:
}
% how we add a space after the name
{ \tl_set:Nx \l_xpatch_name_tl { \l_xpatch_name_tl \c_space_tl } }
% how we add a backslash in front of the name
{ \tl_set:Nx \l_xpatch_name_tl { \c_backslash_tl \l_xpatch_name_tl } }
% a constant containing a backslash (category 12)
\tl_set:Nx \c_backslash_tl { \exp_after:wN \use_none:n \token_to_str:N \\ }
% the user level commands
\NewDocumentCommand{\xpatchcmd}{}{ \xpatchcmd_main:NN \patchcmd }
\NewDocumentCommand{\xpretocmd}{}{ \xpatchcmd_main:NN \pretocmd }
\NewDocumentCommand{\xapptocmd}{}{ \xpatchcmd_main:NN \apptocmd }
\NewDocumentCommand{\xshowcmd}{}{ \xpatchcmd_main:NN \show }

\NewDocumentCommand{\xpatchbibmacro} { m }
{ \exp_after:wN \xpatchcmd_main:NN \exp_after:wN \patchcmd \cs:w abx@macro@ \tl_to_str:n {#1} \cs_end: }
\NewDocumentCommand{\xpretobibmacro} { m }
{ \exp_after:wN \xpatchcmd_main:NN \exp_after:wN \pretocmd \cs:w abx@macro@ \tl_to_str:n {#1} \cs_end: }
\NewDocumentCommand{\xapptobibmacro} { m }
{ \exp_after:wN \xpatchcmd_main:NN \exp_after:wN \apptocmd \cs:w abx@macro@ \tl_to_str:n {#1} \cs_end: }
\NewDocumentCommand{\xshowbibmacro} { m }
{ \exp_after:wN \xpatchcmd_main:NN \exp_after:wN \show \cs:w abx@macro@ \tl_to_str:n {#1} \cs_end: }

\makeatother
\ExplSyntaxOff


Of course commands defined in a devious way can break these macros, but this is true of almost any set of macros.

Patching commands defined by \NewDocumentCommand or the others provided by xparse is not supported.

## Usage

\newcommand\foo[3][]{...hook...}
\xpatchcmd{\foo}{hook}{\label{#1}hook}{success}{failure}

\xshowcmd\foo


The last one shows the "real" meaning of \foo. The commands

\xpatchbibmacro \xpretobibmacro \xapptobibmacro


can be used to patch commands defined via \newbibmacro of biblatex; with \xshowbibmacro one can see the "real" meaning of the macro.

-

To patch a command (with or without an optional argument) defined by means of \newcommand or \DeclareRobustCommand I use a wrapper \xpatchcmd around \patchcmd. \xpatchcmd has an additional optional argument in front of the first argument of \patchcmd that indicates the nature of the command to be patched:

• n --> \newcommand
• o --> \newcommand + optional argument
• N --> \DeclareRobustCommand
• O --> \DeclareRobustCommand + optional argument

Definition and application of \xpatchcmd:

\documentclass{article}

\usepackage{etoolbox}

\usepackage{xifthen}

\makeatletter

\newcommand*{\xpatchcmd}[1][]{%
\bgroup%
\let\@gobble@\@gobble\let\@space@\empty%
\ifthenelse{\isempty{#1}\OR\equal{\unexpanded{#1}}{n}}{}{%
\ifthenelse{\equal{\unexpanded{#1}}{o}}{\let\@gobble@\empty}{%
\ifthenelse{\equal{\unexpanded{#1}}{N}}{\let\@space@\space}{%
\ifthenelse{\equal{\unexpanded{#1}}{O}}{\let\@gobble@\empty\let\@space@\space}{%
\typeout{%
Command \protect\xpatchcmd\space Warning: Invalid command specifier "\unexpanded{#1}".%
}%
}%
}%
}%
}%
\catcode\#=12%
\@xpatchcmd%
}

\newcommand{\@xpatchcmd}[6][\undefined]{%
\catcode\#=6%
\edef\undefined{\noexpand\undefined}%
\def\@prefix{#1}%
\letcs{\@command}{\expandafter\@gobble@\string#2\@space@}%
\ifx\@prefix\undefined%
\patchcmd{\@command}{#3}{#4}{#5}{#6}%
\else%
\patchcmd[\@prefix]{\@command}{#3}{#4}{#5}{#6}%
\fi%
\global\cslet{\expandafter\@gobble@\string#2\@space@}{\@command}%
\egroup%
}

\makeatother

\begin{document}

\newcommand*{\test}[2]{#1me#2}
\show\test
\xpatchcmd[n][\protected]{\test}{me}{you}{}{}
\show\test

\renewcommand{\test}[2][]{#1me#2}
\expandafter\show\csname\string\test\endcsname
\xpatchcmd[o][]{\test}{me#2}{you#1}{}{}
\expandafter\show\csname\string\test\endcsname

\DeclareRobustCommand*{\test}[2]{#1me#2}
\expandafter\show\csname test \endcsname
\xpatchcmd[N][\long]{\test}{#1me}{#2you}{}{}
\expandafter\show\csname test \endcsname

\DeclareRobustCommand*{\test}[2][only]{#1me#2}
\expandafter\show\csname\string\test\space\endcsname
\xpatchcmd[O]{\test}{#2}{}{}{}
\expandafter\show\csname\string\test\space\endcsname

\end{document}

-