How to use \csname, \expandafter, \csuse, \@namedef ...?
Briefly, the reason why your use of \expandafter
fails is that you do not have enough of them. For more details see the eloquent explanations of this elsewhere on this site, such as why-do-we-need-eleven-expandafters-to-expand-four-tokens-in-the-right-order.
The issue that you are having with \MakeUppercase
is because it is not expandable, which means that you cannot use it directly inside a \csname...\endcsname
definition. This is explained in detail in the answers to the post
create-a-capitalized-macro-token-using-csname.
Werner has given one solution where he avoids the uppercase issue by passing both the capitalised and uncapitalised name to the macro creator. Here is another solution that "uppercases" the macro name automatically.
Rather than using \providescommand
to check to see if the \@my<command>
has already been defined I check this directly using \ifcsdef
as using \providescommand
would require some \expandafter
s that I would rather avoid. Finally, as a bonus, the code below also defines a "factory function" \@DefineMyCommands
for creating a family of such commands from a comma separated list.
\documentclass{article}
\usepackage{etoolbox}
\makeatletter
\newcommand\@DefineMyCommand[1]{\@ReallyDefineMyCommand#1\relax}
\def\@ReallyDefineMyCommand#1#2\relax{%
% Define command <#1#2> to set @my#1#2={arg of <#1#2>}
% Here #1 is the first character of the comamnd and #2 is the rest.
% We have isolated #1 so that we can uppercase it on the next line
\uppercase{\expandafter\gdef\csname #1}#2\endcsname##1{\@namedef{@my#1#2}{##1}}
% if \@my#1#2 is not already defined then define it
\ifcsdef{@my#1#2}{\relax}{\@namedef{@my#1#2}{No \MakeUppercase{#1#2} defined!}}
}
\newcommand{\@DefineMyCommands}{\forcsvlist{\@DefineMyCommand}}% factory function
\@DefineMyCommands{mycmd, test, fred, julie}% define a bunch of macros
\makeatother
\newcommand*\showDF[1]{\csname @my#1\endcsname}
\begin{document}
\showDF{mycmd}
\Mycmd{A command!}
\showDF{mycmd}
\showDF{test}
\Test{A test!}
\showDF{test}
\end{document}
Here is the "expected" output:
Edit Actually, defining a factory command is a little silly if it is only going to be used once. Unless there are a large number of these macros it would be smarter to write
\forcsvlist{\@DefineMyCommand}{mycmd, test, fred, julie}
as this applies the command \@DefineMyCommand
to the elements in the list without creating an unnecessary macro.
Here is how you can do this:
\documentclass{article}
\makeatletter
%\providecommand*\@mycmd{No {\MakeUppercase Mycmd} defined}
%\newcommand*\Mycmd[1]{\renewcommand*\@mycmd{#1}}
\newcommand{\@newDF}[2]{%
\@namedef{@#1}{No \MakeUppercase{#2} defined}%
%\expandafter\newcommand\csname @#1\endcsname{No \MakeUppercase{#2} defined}% Alternative to above
\expandafter\newcommand\csname #2\endcsname[1]{%
\expandafter\renewcommand\csname @#1\endcsname{##1}}
}
\@newDF{mycmd}{Mycmd}
\newcommand*\showDF[1]{\csname @#1\endcsname}
\makeatother
\begin{document}
\showDF{mycmd}\par
\Mycmd{Test}
\showDF{mycmd}
\end{document}
Output is:
No MYCMD defined
Test
The definitions are given with a \show
:
> \@mycmd=macro:
->No \MakeUppercase {Mycmd} defined.
> \Mycmd=\long macro:
#1->\expandafter \renewcommand \csname @mycmd\endcsname {#1}.