8

I have a macro \command to ensure that the names of macros in my documentation are consistently formatted. Been work great, until I had an urge to procrastinate and had this brilliant idea that I can easily catch most spelling errors of the macro names, by checking that the macro exists.

Seems simple enough. So I added:

\ifcsname#1\endcsname%
\else%
    \par\textbf{\textcolor{red}{\textbackslash#1} is not defined.}
\fi%

to the macro that does the formating, where #1 is the name of the macro. This seems to work fine in the basic case as illustrated by the MWE below that produces:

enter image description here

and correctly tells me that bfseriess and \foo are not defined.

However, if I attempt to use this in a nested fashion

\command{foo=\command{MyDef}}

I get the error message

Missing \endcsname inserted.

<to be read again> \protect l.25 \command{foo=\command{MyDef}}

Question:

I have a feeling that the solution is in the question listed in the references but I don't know exactly how to \protect things. So, what changes can I make to the \command macro so that I can uncomment the last line in the MWE and the proper error messages.

Reference:

Code:

\documentclass{article}
\usepackage{xcolor}

%% The last line should not produce any error %% messages (red text) if these two are defined. % %\newcommand{\foo}{}% %\newcommand{\MyDef}{}%

\newcommand{\command}[1]{% \textbf{\textbackslash#1}% %% Since this is used to typeset macro names, we %% can check for typos by ensuring the macro exists \ifcsname#1\endcsname% \else% \par\fcolorbox{red}{red!20}{\textcolor{blue}{\textbackslash#1} is not defined.} \fi% }%

\begin{document} To bold text use \command{textbf} or \command{bfseriess}.

A useful token is \command{foo}.

This token needs to be set with: %\command{foo=\command{MyDef}} \end{document}

Peter Grill
  • 223,288
  • 1
    This is not due to nested \ifcsname. For example, if you change the definition to \newcommand\command[1]{(#1 is \ifcsname#1\endcsname \else not \fi defined)} then the document compiles correctly. – Aditya Jan 21 '13 at 01:55
  • @Aditya: Hmmm, you are indeed correct. So its just my fancy formatting that is getting the way somehow? – Peter Grill Jan 21 '13 at 01:57
  • Yes, but I don't know the LaTeX internals well to explain why that is happening. – Aditya Jan 21 '13 at 01:59
  • @Aditya: Yeah, thought it was a simple matter of adding a few \protects here and there, but haven't guessed the correct sequence yet. – Peter Grill Jan 21 '13 at 02:00
  • I think when it encounters the second backslash of MyDef, TeX thinks the \csname should have ended by now! and triggers that error so that needs to be hidden somehow. And what should be the error message by the way? Because that's not a valid token anyway. – percusse Jan 21 '13 at 02:17
  • @percusse: I think you are correct. This won't generate the correct result (as per my comment on Aditya's solution). Was that what you were referring to? – Peter Grill Jan 21 '13 at 03:44
  • 1
    The problem is the \protect that's hidden in the definition of \textbf. You can trigger the error message e.g. with \DeclareRobustCommand\foo{}\csname\foo\endcsname – cgnieder Jan 21 '13 at 10:16
  • @cgnieder It's not only the \protect (one could get rid of it); but \textbf does many other unexpandable things that can't go inside \csname...\endcsname. – egreg Jan 21 '13 at 10:56
  • @egreg should I have said then: “the first problem is ...“? I'm aware that all the \text... commands pose problems in expansion contexts but the details are not yet clear to me. Maybe I should ask a question... – cgnieder Jan 21 '13 at 11:06

3 Answers3

7

By simply imitating the definition of \doifundefined from ConTeXt (defined in file syst-gen.mkii), adding \detokenize to the definition works.

\newcommand\command[1]
    {\textbf{\textbackslash#1}%
    %% Since this is used to typeset macro names, we
    %% can check for typos by ensuring the macro exists
     \ifcsname\detokenize{#1}\endcsname%
    \else%
        \par\fcolorbox{red}{red!20}{\textcolor{blue}{\textbackslash#1} is not defined.}
    \fi%
}% 

enter image description here

Aditya
  • 62,301
  • Interesting... Indeed it does appear to fix the situation, in that the code compiles. However, the macro is not yielding correct results. If you un-comment the \newcommand*{\foo}{} and \newcommand*{\MyDef}{}, the only error message should be the one for bfseriess, but this is not the case. .... Oh, wait, I think that is what @percusse's comment is about... – Peter Grill Jan 21 '13 at 03:41
  • When you uncomment the \newcommands, the macro is checking if \csname foo=\textbf{\textbackslash MyDef}\endcsname is defined or not. Clearly, that montrosity is not defined. – Aditya Jan 21 '13 at 06:30
  • Yeah that is what I meant about precusse's comment. I need to rethink how to handle the nested case properly. – Peter Grill Jan 21 '13 at 07:30
6

I would go for something like:

\documentclass{article}
\begin{document}
\makeatletter
\def\mytypeset#1{%
\def\command##1{%
     \ifcsname##1\endcsname%
        \expandafter\@firstoftwo
    \else%
        \expandafter\@secondoftwo
    \fi%
}%
 \def\z{\detokenize{#1}}
 \command{\z}{\fbox{TRUE}}{\fbox{FALSE}}
}


\mytypeset{deff{f}f}

\mytypeset{def}

\mytypeset{foo=\command{MyDef}}
\end{document}

Note that when you are checking for a "nested" command, as a matter of fact the code checks to see if:

  foo=\command{MyDef}

is a defined macro! With csname...endcsname, arbitrary commands of any characters ca be used. You can have a command !$%.#!.o>,p\def} for example. You can add the follow snippet to the example that demonstrates the concept.

\expandafter\def\csname My$Def\endcsname{MyDef...}

\mytypeset{My$Def}

% prints MyDef...
\csname My$Def\endcsname

\My$Def now is a command and prints MyDef.., when executed.

yannisl
  • 117,160
4

I'm not at all sure what the intended behaviour is when the argument to \command isn't a command name, but this does something:

enter image description here

\documentclass{article}
\usepackage{xcolor}

%% The last line should not produce any error 
%% messages (red text) if these two are defined.
%
%\newcommand*{\foo}{}%
%\newcommand*{\MyDef}{}%

\protected\def\command#1{%
    \textbf{\textbackslash#1}%
    %% Since this is used to typeset macro names, we
    %% can check for typos by ensuring the macro exists
    \ifcsname\detokenize{#1}\endcsname%
    \else%
        \par\fcolorbox{red}{red!20}{\textcolor{blue}{\textbackslash#1} is not defined.}
    \fi%
}%


\begin{document}
To bold text use \command{textbf} or \command{bfseriess}.

A useful token is \command{foo}.

This token needs to be set with: 
\command{foo=\command{MyDef}}
\end{document}

Or updated as requested in comments to strip the argument before =

enter image description here

\documentclass{article}
\usepackage{xcolor}

%% The last line should not produce any error 
%% messages (red text) if these two are defined.
%
%\newcommand*{\foo}{}%
%\newcommand*{\MyDef}{}%

\protected\def\command#1{%
    \textbf{\textbackslash#1}%
    %% Since this is used to typeset macro names, we
    %% can check for typos by ensuring the macro exists
    \ifcsname\expandafter\beforeeq\detokenize{#1}=\beforeeq\endcsname%
    \else%
        \par\fcolorbox{red}{red!20}{\textcolor{blue}{\textbackslash\expandafter\beforeeq\detokenize{#1}=\beforeeq} is not defined.}
    \fi%
}%

\def\beforeeq#1=#2\beforeeq{#1}

\begin{document}
To bold text use \command{textbf} or \command{bfseriess}.

A useful token is \command{foo}.

This token needs to be set with: 
\command{foo=\command{MyDef}}
\end{document}
David Carlisle
  • 757,742
  • For the nested usage, I would like to be able to use something like \StrBefore{\detokenize{#1}}{=}[\MacroName] so that I could check for \ifcsname\MacroName\endcsname but can't quite figure how to get that to work in this case. – Peter Grill Jan 21 '13 at 12:05
  • @PeterGrill so in this example you want to test for foo? (This seems a very odd requirement:-) (Update on way....) – David Carlisle Jan 21 '13 at 12:14
  • I agree that is strange, but I am only trying to make it work for the cases I need, not a general solution as this is for internal documentation purposes. This does indeed work as I need but was hoping that it used xstring so that I could easily expand it if needed to handle other cases. – Peter Grill Jan 21 '13 at 21:39