15

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.

3
  • 6
    Asterix vs. Asterisk. Commented Apr 3, 2016 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
    Commented Apr 3, 2016 at 19:46
  • 6
    @HenriMenke But you must admit that Asterixed versions of commands would bring a bit of badly needed excitement to many documents!
    – cfr
    Commented Apr 3, 2016 at 19:47

4 Answers 4

14

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}
3
  • 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
    Commented Apr 3, 2016 at 20:15
  • @Tohiko I added what I believe is a more interesting approach.
    – egreg
    Commented Apr 3, 2016 at 20:20
  • The 'more interesting' version is a neat trick.
    – jon
    Commented Apr 3, 2016 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

4
  • Your version does not define a command that defines a starred command ;-)
    – user31729
    Commented Apr 3, 2016 at 21:07
  • True. Was that the question? Maybe I just didn't get it.
    – cfr
    Commented Apr 3, 2016 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
    Commented Apr 4, 2016 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
    Commented Apr 4, 2016 at 11:36
3

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

2
  • 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
    Commented Apr 3, 2016 at 20:13
  • @Tohiko: Yes, that's a possible caveat.
    – user31729
    Commented Apr 3, 2016 at 20:14

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .