I have a question about a strange behavior of the etoolbox package. Roughly speaking, I want to define a command for defining macros so that the user says \mynewcommand\acommand{some code} and it will define \acommand to do some setup stuff, run some code then do some cleanup. I want to use \patchcmd to turn any occurrences of \options in some code into an argument (say #1). However, I run into some difficulties with \patchcmd saying macro cannot be retokenized cleanly due to some strange difficulty with spaces.
One solution to this problem is to disable the etoolbox scan safety check by saying \def\etb@ifscanable##1{\@firstoftwo}. I don't really like this solution because it reaches into the etoolbox package.
If I could understand what is happening in the following example, I think I would be able to fix my definition without doing this. So are there any etoolbox experts out there who can tell me why the following code behaves the way it does?
\documentclass{article}
\usepackage{etoolbox}
\tracingpatches
\makeatletter
\begin{document}
% Breaks sometimes:
\def\mynewcmda#1#2{
\gdef#1{%
\acommand
#2%
\options
}
\patchcmd#1{\options}{\athing}
{\message{replacement worked}}
{\message{replacement failed}}
}
% Always works:
\def\mynewcmdb#1#2{
\gdef#1{%
\acommand
#2%
\options
}
\bgroup
% Skip retokenization check, so "++ macro can be retokenized cleanly":
\def\etb@ifscanable##1{\@firstoftwo}
\patchcmd#1{\options}{\athing}
{\message{replacement worked}}
{\message{replacement failed}}
\global\let#1#1
\egroup
}
% Always works:
\def\mynewcmdc#1#2{
\gdef#1{%
\acommand{}%
#2%
\options
}
\patchcmd#1{\options}{\athing}
{\message{replacement worked}}
{\message{replacement failed}}
}
% Doesn't work for some mysterious reason
\mynewcmda\testa{
do things
}
% All the rest of these work:
\mynewcmda\testaprime{%
do things
}
\mynewcmdb\testb{
do things
}
\mynewcmdc\testc{
do things
}
\end{document}
The log file says:
[debug] tracing \patchcmd on input line 50
[debug] analyzing '\testa'
[debug] ++ control sequence is defined
[debug] ++ control sequence is a macro
[debug] -- macro cannot be retokenized cleanly
[debug] -> the macro may have been defined under a category
[debug] code regime different from the current one
[debug] -> the replacement text may contain special control
[debug] sequence tokens formed with \csname...\endcsname;
[debug] -> the replacement text may contain carriage return,
[debug] newline, or similar characters
replacement failed
and then a bunch of messages saying the other \patchcmds succeeded.
\athingcould be directly given in the macro definition instead of the detour with\options? Also, it is on purpose that the macro is defined globally (\gdef), but redefined locally (\patchcmd)? – Heiko Oberdiek Nov 23 '16 at 03:24\optionsinside the second argument? Or at most one? – Werner Nov 23 '16 at 03:26\def\mynewcmd#1#2{\gdef#1{\acommand {\let\options\athing #2}\options}}– Werner Nov 23 '16 at 03:35\optionscan happen, so I defined a helper command\patchrepeatwhich recursively applies\patchcommanduntil it fails. I'm not sure if this is the best way to do it. – Hood Chatham Nov 23 '16 at 04:00\let#1#1immediately afterwards. Is there a global version of\patchcmd? – Hood Chatham Nov 23 '16 at 04:03\mynewcmd? – Werner Nov 23 '16 at 04:10\global\let#1#1. – Heiko Oberdiek Nov 23 '16 at 05:56\patchcmddoes not work in all cases, it could be better to use some expansion trickery to replace\optionsduring the definition, e.g.\xdef#1{\unexpanded{\accomand{}#2}\unexpanded\expandafter{\options}}– Heiko Oberdiek Nov 23 '16 at 05:59