9

I have the following package

\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{mypkg}

\providecommand*\@mycmd{No {\MakeUppercase Mycmd} defined}
\newcommand*\Mycmd[1]{\renewcommand*\@mycmd{#1}}

\newcommand*\showDF[1]{\csname @#1\endcsname}

\endinput

It provides the user the command \Mycmd{} to renew \@mycmd, and \showDF{mycmd} to show it's content:

\documentclass[a4paper,titlepage,11pt,twoside,openright]{report}
\usepackage{mypkg}
\begin{document}

\showDF{mycmd}\par
\Mycmd{Test}
\showDF{mycmd}

\end{document}

I'd like to have the first two commands defined with a macro, in order to generate several cmd pairs.

\@newDF{mycmd}{Mycmd}

I've tried with

\newcommand*\@newDF[2]{
 \providecommand*\csname @#1\endcsname{No {\MakeUppercase #2} defined}
 \newcommand*\csname #2\endcsname[1]{\renewcommand\csname @#1\endcsname{##1}}
}

but it won't run.

\newcommand*\newDF[2]{
 \expandafter\providecommand\expandafter*\csname @#1\endcsname{No {\MakeUppercase #2} defined}
 \expandafter\newcommand\expandafter*\csname #2\endcsname[1]{\renewcommand\csname @#1\endcsname{##1}}
}

compiles, but I really don't know why I put those \expandafter there, and anyway \MakeUppercase is ignored when \showDF{mycmd} is executed, and \Mycmd{Hi} has no effect. Replacing \csname @#1\endcsname with either @nameuse{@#1} or \csuse{@#1} neither works. What am I missing?

11

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 \expandafters 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:

enter image description here

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.

  • Thanks a lot for this detailed answer. In fact, I wanted 'MYCMD' to be printed instead of 'Mycmd'. But the big picture is clearer to me now, and I'm definitely going to try the 'factory function'. – U.Martinez-Corral Sep 25 '14 at 5:27
  • 1
    As the "user" has to use \Mycmd I thought Mycmd was better but I have edited my post so that MYCMD is printed -- this just requires `\MakeUppercase{#1#1}. – Andrew Sep 25 '14 at 5:37
  • On using \forcsvlist, can a command be explicitly defined, instead of passing a token? I mean doing something like \forcsvlist{\expandafter\newdimen\csname dim@##1\endcsname}{left,right,top,bottom} instead of \newcommand\mynewdimen[1]{\expandafter\newdimen\csname dim@#1\endcsname} and \forcsvlist{\mynewdimen}{left,right,top,bottom}? In the documentation it is only said to accept taking the argument last implicitly. It's just curiosity. – U.Martinez-Corral Sep 27 '14 at 4:20
  • 1
    Hmm, not sure. Since you are asking, I guess this doesn't work:) – Andrew Sep 28 '14 at 1:53
7

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}.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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