Some facts:
- Non-delimited macro arguments consisting of a single token don't need to be nested in curly braces.
\csname..\endcsname
is used for creating a control-sequence-token.
- The
\expandafter\endcsname\csname
-trick can be used for creating several control sequence-tokens in a row:
\csname foo\expandafter\endcsname\csname bar\expandafter\endcsname\csname baz\endcsname
yields
\foo\bar\baz
by triggering a single expansion-step on the first \csname
.
A common trick is to have a helper-macro do the actual work which gets as arguments all the control-sequence-tokens needed, while within the definition of the main macro/user-level-macro using the \expandafter\endcsname\csname
-trick for creating and passing as arguments to the helper-macro all the control-sequence-tokens needed:
%Format: LaTeX2e
%
\newcommand\wrapcmd[2]{%
% \wrapcmd{foo}{argument} yields: \wrapcmdhelper\foo\oldfoo{argument}
\expandafter\wrapcmdhelper\csname#1\expandafter\endcsname\csname old#1\endcsname{#2}%
}%
\newcommand\wrapcmdhelper[3]{%
% Probably \protected\def instead of \def?
\csname @ifdefinable\endcsname#2{\let#2=#1\def#1{\wrapper{#3}{#2}}}%
}%
%%===============================================================================
%% Testing:
%%===============================================================================
\wrapcmd{cmda}{Some text for cmda}
\message{^^J\string\cmda=\meaning\cmda^^J}
%%-------------------------------------------------------------------------------
\wrapcmd{cmdb}{Some other text for cmdb}
\message{^^J\string\cmdb=\meaning\cmdb^^J}
%%-------------------------------------------------------------------------------
\wrapcmd{cmdc}{No text for cmdc}
\message{^^J\string\cmdc=\meaning\cmdc^^J}
%%-------------------------------------------------------------------------------
\stop
Console Output:
This is pdfTeX, Version 3.14159265-2.6-1.40.21 (TeX Live 2020) (preloaded format=pdflatex-dev)
restricted \write18 enabled.
entering extended mode
(./test.tex
LaTeX2e <2021-05-01> pre-release-1 (develop 2021-2-27 branch)
L3 programming layer <2021-02-18>
\cmda=macro:->\wrapper {Some text for cmda}{\oldcmda }
\cmdb=macro:->\wrapper {Some other text for cmdb}{\oldcmdb }
\cmdc=macro:->\wrapper {No text for cmdc}{\oldcmdc }
)
No pages of output.
Transcript written on test.log.
Probably a routine \CsNameToCsToken⟨stuff not in braces⟩{NameOfCs}
→ ⟨stuff not in braces⟩\NameOfCs
where ⟨stuff not in braces⟩ may be empty also does the trick:
%Format: LaTeX2e
%
\makeatletter
%%===============================================================================
%% End \romannumeral-driven expansion safely:
%%===============================================================================
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
%%===============================================================================
%% Obtain control sequence token from name of control sequence token:
%%===============================================================================
%% \CsNameToCsToken<stuff not in braces>{NameOfCs}
%% -> <stuff not in braces>\NameOfCs
%% (<stuff not in braces> may be empty.)
\@ifdefinable\CsNameToCsToken{%
\long\def\CsNameToCsToken#1#{\romannumeral\InnerCsNameToCsToken{#1}}%
}%
\newcommand\InnerCsNameToCsToken[2]{%
\expandafter\UD@Exchange\expandafter{\csname#2\endcsname}{\UD@stopromannumeral#1}%
}%
\newcommand\UD@Exchange[2]{#2#1}%
%%===============================================================================
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
%%===============================================================================
%% \wrapcmd{<csname>}{<arg>}
%%===============================================================================
\newcommand\wrapcmd[2]{%
\CsNameToCsToken\@ifdefinable{old#1}{%
\CsNameToCsToken\CsNameToCsToken\let{old#1}={#1}%
%\CsNameToCsToken\protected\expandafter\def\expandafter{#1}\expandafter{%
\CsNameToCsToken\expandafter\def\expandafter{#1}\expandafter{%
\romannumeral
\CsNameToCsToken\UD@PassFirstToSecond{old#1}{\UD@stopromannumeral\wrapper{#2}}%
}%
}%
}%
\makeatother
%%===============================================================================
%% Testing:
%%===============================================================================
\wrapcmd{cmda}{Some text for cmda}
\message{^^J\string\cmda=\meaning\cmda^^J}
%%-------------------------------------------------------------------------------
\wrapcmd{cmdb}{Some other text for cmdb}
\message{^^J\string\cmdb=\meaning\cmdb^^J}
%%-------------------------------------------------------------------------------
\wrapcmd{cmdc}{No text for cmdc}
\message{^^J\string\cmdc=\meaning\cmdc^^J}
%%-------------------------------------------------------------------------------
\stop
Console-output:
This is pdfTeX, Version 3.14159265-2.6-1.40.21 (TeX Live 2020) (preloaded format=pdflatex-dev)
restricted \write18 enabled.
entering extended mode
(./test.tex
LaTeX2e <2021-05-01> pre-release-1 (develop 2021-2-27 branch)
L3 programming layer <2021-02-18>
\cmda=macro:->\wrapper {Some text for cmda}{\oldcmda }
\cmdb=macro:->\wrapper {Some other text for cmdb}{\oldcmdb }
\cmdc=macro:->\wrapper {No text for cmdc}{\oldcmdc }
)
No pages of output.
Transcript written on test.log.
If the patched variants of commands are to be used only within code written by you so that patches to commands shall not affect code written by others/shall not affect macros when being used in package-code and the like provided by others, then you might consider not patching commands at all but just defining a macro \wrapcmd{NameOfCs}{arg}
which does \wrapper{arg}{\NameOfCs}
and within your code using that instead of \NameOfCs
:
%Format: LaTeX2e
%
\newcommand\PassFirstToSecond[2]{#2{#1}}%
\newcommand\wrapcmd[2]{%
\expandafter\PassFirstToSecond\expandafter{\csname#1\endcsname}{\wrapper{#2}}%
}%
% Dummy-definition for \wrapper
\newcommand\wrapper[2]{\message{^^J\detokenize{\wrapper{#1}{#2}}^^J}}%
\wrapcmd{cmda}{Some text for cmda}
\wrapcmd{cmdb}{Some other text for cmdb}
\wrapcmd{cmdc}{No text for cmdc}
\stop
Console-output:
This is pdfTeX, Version 3.14159265-2.6-1.40.21 (TeX Live 2020) (preloaded format=pdflatex-dev)
restricted \write18 enabled.
entering extended mode
(./test.tex
LaTeX2e <2021-05-01> pre-release-1 (develop 2021-2-27 branch)
L3 programming layer <2021-02-18>
\wrapper {Some text for cmda}{\cmda }
\wrapper {Some other text for cmdb}{\cmdb }
\wrapper {No text for cmdc}{\cmdc }
)
No pages of output.
Transcript written on test.log.
In order to ensure that the result is delivered by triggering two expansion-steps you can wrap this into \romannumeral
-expansion:
%Format: LaTeX2e
%
\makeatletter
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\wrapcmd[2]{%
\romannumeral
\expandafter\UD@PassFirstToSecond
\expandafter{\csname#1\endcsname}%
{\UD@stopromannumeral\wrapper{#2}}%
}%
\makeatother
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\temp
\expandafter\expandafter\expandafter{%
\wrapcmd{cmda}{Some text for cmda}%
}%
\message{^^J\string\temp=\meaning\temp^^J}
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\temp
\expandafter\expandafter\expandafter{%
\wrapcmd{cmdb}{Some other text for cmdb}%
}%
\message{^^J\string\temp=\meaning\temp^^J}
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\temp
\expandafter\expandafter\expandafter{%
\wrapcmd{cmdc}{No text for cmdc}%
}%
\message{^^J\string\temp=\meaning\temp^^J}
\stop
Console-output:
This is pdfTeX, Version 3.14159265-2.6-1.40.21 (TeX Live 2020) (preloaded format=pdflatex-dev)
restricted \write18 enabled.
entering extended mode
(./test.tex
LaTeX2e <2021-05-01> pre-release-1 (develop 2021-2-27 branch)
L3 programming layer <2021-02-18>
\temp=macro:->\wrapper {Some text for cmda}{\cmda }
\temp=macro:->\wrapper {Some other text for cmdb}{\cmdb }
\temp=macro:->\wrapper {No text for cmdc}{\cmdc }
)
No pages of output.
Transcript written on test.log.