14

I have the following command \defmeta that defines two other commands (one starred and one unstarred)

\documentclass{article}
\usepackage{suffix}
\newcommand{\genericcmd}[3]{#1 #3 #2 }
\newcommand{\genericcmdstarred}[3]{ #1 starred, #3 #2}
\newcommand{\defmeta}[3]{
\expandafter\newcommand\csname #1\endcsname[1]{\genericcmd{#2}{#3}{##1}}
\WithSuffix\expandafter\newcommand\csname #1\endcsname *[1]{\genericcmdstarred{#2}{#3}{##1}}
}

\defmeta{mynewcommand}{Hello}{world}

\begin{document}
\mynewcommand{big}.
\mynewcommand*{big}.
\end{document}

This works perfectly. However, I wonder if there is a way to remove the dependency on suffix? I would like to not use the suffix package because I do not want to have unnecessary dependencies and as a way to learn more LaTeX magic.

  • 5
    Asterix vs. Asterisk. – Henri Menke Apr 3 '16 at 19:45
  • 1
    I would recommend using xparse and \NewDocumentCommand which supports this out of the box. It is part of the development of the LaTeX 3 project. – cfr Apr 3 '16 at 19:46
  • 5
    @HenriMenke But you must admit that Asterixed versions of commands would bring a bit of badly needed excitement to many documents! – cfr Apr 3 '16 at 19:47
13

No dependencies on packages, only on the LaTeX kernel.

\documentclass{article}

\newcommand{\genericcmd}[3]{#1 #3 #2}
\newcommand{\genericcmdstarred}[3]{#1 starred, #3 #2}

\makeatletter
\newcommand\metadef[3]{%
  \DeclareRobustCommand#1{%
    \@ifstar{\csname s\string#1\endcsname}{\csname n\string#1\endcsname}%
  }%
  \@namedef{s\string#1}##1{\genericcmdstarred{#2}{#3}{##1}}%
  \@namedef{n\string#1}##1{\genericcmd{#2}{#3}{##1}}%
}
\makeatother

\metadef{\mynewcommand}{Hello}{world}

\begin{document}

\mynewcommand{big}.

\mynewcommand*{big}.

\end{document}

enter image description here

More interesting, perhaps, is a definition without the need to define \genericcmd and \genericcmdstarred. The command \metadef has the same syntax as \newcommand (but no * version, which could be added with some more trickery), but with an argument more:

\metadef{<macro name>}[<args>]{<code for normal version>}{<code for starred version>}

Optional arguments to the defined macro are not allowed, use the following xparse based code for adding whatever list of arguments you want.

\documentclass{article}

\makeatletter
\newcommand{\metadef}[1]{%
  \DeclareRobustCommand#1{%
    \@ifstar{\csname s\string#1\endcsname}{\csname n\string#1\endcsname}%
  }%
  \edef\meta@def@name{\string#1}%
  \meta@def
}
\newcommand\meta@def[3][0]{%
  \expandafter\newcommand\csname n\meta@def@name\endcsname[#1]{#2}%
  \expandafter\newcommand\csname s\meta@def@name\endcsname[#1]{#3}%
}
\makeatother

\metadef{\mynewcommand}[1]{Hello #1 world}{Hello starred, #1 world}

\begin{document}

\mynewcommand{big}.

\mynewcommand*{big}.

\end{document}

The same with suffix:

\documentclass{article}
\usepackage{suffix}

\makeatletter
\newcommand{\metadef}[1]{%
  \def\meta@def@name{#1}%
  \meta@def
}
\newcommand\meta@def[3][0]{%
  \expandafter\newcommand\meta@def@name[#1]{#2}%
  \WithSuffix\expandafter\newcommand\meta@def@name*[#1]{#3}%
}
\makeatother

\metadef{\mynewcommand}[1]{Hello #1 world}{Hello starred, #1 world}

\begin{document}

\mynewcommand{big}.

\mynewcommand*{big}.

\end{document}

The same with xparse, where you just have to remember of shifting by one the number of the arguments, because #1 is the possible *. The arguments to the “metadefined” macro must be expressed in xparse syntax, of course.

\documentclass{article}
\usepackage{xparse}

\NewDocumentCommand{\metadef}{mmmm}{%
  \NewDocumentCommand{#1}{s#2}{%
    \IfBooleanTF{##1}{#4}{#3}%
  }%
}

\metadef{\mynewcommand}{m}{Hello #2 world}{Hello starred, #2 world}

\begin{document}

\mynewcommand{big}.

\mynewcommand*{big}.

\end{document}
  • This looks great. I was trying to do something like this in the beginning but I didn't know about the \string and @namdef commands. – Tohiko Apr 3 '16 at 20:15
  • @Tohiko I added what I believe is a more interesting approach. – egreg Apr 3 '16 at 20:20
  • The 'more interesting' version is a neat trick. – jon Apr 3 '16 at 20:41
8

I would recommend using xparse which is part of the work by the LaTeX 3 project developers.

The following emulates the code in the question except that it eliminates the spurious space caused by the unstarred version of the command.

\documentclass{article}
\usepackage{xparse}
\NewDocumentCommand \mynewcommand { s m }
{%
  Hello
  \IfBooleanT {#1}
  {%
    starred,
  }%
  #2 world%
}

\begin{document}
\mynewcommand{big}.
\mynewcommand*{big}.
\end{document}

starry worlds

  • Your version does not define a command that defines a starred command ;-) – user31729 Apr 3 '16 at 21:07
  • True. Was that the question? Maybe I just didn't get it. – cfr Apr 3 '16 at 23:09
  • Yes, that was the question, see the title ;-) Irony is that I answered it that way but it's disregarded. Seems to be my fate here, on TeX.SX ;-) – user31729 Apr 4 '16 at 9:10
  • I guess I took the goal to be the starred command creation, with the question thinking that creating a new command to do that was necessary as opposed to the goal being the new command itself. – cfr Apr 4 '16 at 11:36
2

No dependencies on packages, no dependencies on LaTeX kernel:).

\def\isnextchar#1#2#3{\toks0={#2}\toks1={#3}%
   \let\tmp=#1\futurelet\next\isnextcharA}
\def\isnextcharA{\the\toks\ifx\tmp\next0\else1\fi\space}

\def\metadef#1#2#3{\def#1{\isnextchar*{\metadefA{#3}}{\metadefB{#2}}}}
\def\metadefA#1*{\def\tmp##1{#1}\tmp}
\def\metadefB#1{\def\tmp##1{#1}\tmp}

\metadef \mynewcommand {Hello ##1 world} {Hello starred, ##1 world}

\mynewcommand{big}.

\mynewcommand*{big}.
1

Here's a version without any package, using \@ifstar and relying on a moving argument, i.e. the #1 command sequence has no real argument, but \@ifstar looks for the * character and branches into the relevant command either doing this or the other version.

\documentclass{article}

\newcommand{\genericcmdunstarred}[3]{#1 #3 #2 }
\newcommand{\genericcmdstarred}[3]{ #1 starred, #3 #2}
\makeatletter
\newcommand{\defmeta}[3]{%
  \expandafter\newcommand\csname #1\endcsname{%
    \@ifstar{%
      \genericcmdstarred{#2}{#3}%
    }{%
      \genericcmdunstarred{#2}{#3}%
    }%
  }%      
}

\defmeta{mynewcommand}{Hello}{world}

\defmeta{foo}{And now for something}{different}

\begin{document}
\mynewcommand{big}.

\mynewcommand*{big}.

\foo{completely}

\foo*{completely}

\end{document}

enter image description here

  • I guess this solution relies on the fact that the "variable" argument ("big" or "completely") that is passed to genericcmd is the last one, right? – Tohiko Apr 3 '16 at 20:13
  • @Tohiko: Yes, that's a possible caveat. – user31729 Apr 3 '16 at 20:14

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.