4

I am trying to create many different commands based on a single command (essentially a custom header) in a predictable way. Is there a way to do this in TeX/LaTeX?

My specific example:

I'm writing a module specification, where each of the sub headings is denoted by \modulespec{description}{the description} or \modulespec{learning outcomes}{the learning outcomes} etc. I am hoping that TeX can generate commands like \mdescription{the description} and \mlearningoutcomes{the learning outcomes}

I know that in bash-like expansion combined with TeX I would write

for i in 'description' 'learning outcomes'
do
    \def\csname m\getRidOfSpaces{$i} \endcsname{\modulespec{$i}{#1}}
done

(if you'll excuse the combination of languages) where \getRidOfSpaces changes learning outcomes to learningoutcomes. Is this possible/feasible with TeX/LaTeX?

M(not)WE:

\documentclass{article}
\usepackage{blindtext}

% In the real thing, this definition is much more complicated
\newcommand{\modulespec}[2]{\section{#1} #2}

% The code I want in pure TeX/LaTeX
for i in 'description' 'learning outcomes'
do
    \def\csname m\getRidOfSpaces{$i} \endcsname{\modulespec{$i}{#1}}
done
% end of non TeX code


\begin{document}

% Both work
\modulespec{description}{\blindtext}
\modulespec{learning outcomes}{\blindtext}

% These two commands should produce the same output as the previous two
\mdescription{\blindtext}
\mlearningoutcomes{\blindtext}

\end{document}
4
  • Is removing the spaces an absolute requirement? It's (marginally) easier if we don't have to
    – Joseph Wright
    Commented Mar 6, 2014 at 11:55
  • Please have a look on the forloop package, but I am not sure whether it allows for generation of commands inside. Please add a MWE also
    – user31729
    Commented Mar 6, 2014 at 11:55
  • @JosephWright Removing the spaces is not an absolute requirement, I think there are few enough commands to define them by hand. Commented Mar 6, 2014 at 12:05
  • @ChristianH. I'm afraid I don't know enough TeX to produce anything working. I can add a minimal document to the question if that helps? Commented Mar 6, 2014 at 12:06

3 Answers 3

6

Here's a LaTeX3 implementation.

\documentclass{article}
\usepackage[margin=1cm]{geometry} % to fit in one page
\usepackage{blindtext}
\usepackage{xparse}

\newcommand{\modulespec}[2]{\section{#1} #2}

\ExplSyntaxOn

\NewDocumentCommand{\definemodules}{m}
 {
  \farley_definemodules:n { #1 }
 }

\tl_new:N \l_farley_temp_tl
\cs_new_protected:Npn \farley_definemodules:n #1
 {
  \clist_map_inline:nn { #1 }
   {
    \tl_set:Nn \l_farley_temp_tl { ##1 }
    \tl_replace_all:Nnn \l_farley_temp_tl { ~ } { }
    \cs_new_protected:cpn { m \l_farley_temp_tl } { \modulespec { ##1 } }
   }
 }

\ExplSyntaxOff

\definemodules{
  description,
  learning outcomes
}

\begin{document}

\modulespec{description}{\blindtext}
\modulespec{learning outcomes}{\blindtext}

% These two commands should produce the same output as the previous two
\mdescription{\blindtext}
\mlearningoutcomes{\blindtext}

\end{document}

enter image description here

Some comments on the code.

We define a user level macro for defining all modules in one swoop; this is \definemodules that simply hands control to the programmer's level function \farley_definemodules:n (this is the best practice according to LaTeX3 programming guidelines).

Now the function \farley_definemodules:n splits its argument at commas and does a loop over the items. The current item is available as #1, but we need ##1 because we're inside a definition.

Each cycle stores the item in the scratch token list variable \l_farley_temp_tl so that we can apply \tl_replace_all:Nnn to it. The replacement is “any space is replaced by nothing”; in the programming environment spaces are ignored, so a “real space” is denoted by ~.

Now we use the \cs_new_protected:cpn function that's the analog of the old style \@namedef: the first argument is made into a control sequence name with \csname...\endcsname (the token list is expanded here by TeX rule).

1
  • This is impressive! Could you explain a little how the code logic works? I've never seen anything like this before. Commented Mar 6, 2014 at 14:46
5

It looks like you want to do a 'for each' loop. The LaTeX kernel has a macro \@for which does this for each element of a comma-separated list. You also need \zap@space or similar to remove spaces. For example:

\newcommand{\modulespec}[2]{\section{#1} #2}
\makeatletter
\@for\@tempa:=description,learning outcomes\do{%
  \edef\@tempa
    {\expandafter\expandafter\expandafter\zap@space
      \expandafter\@tempa\space\@empty}%
  \expandafter\edef\csname m\@tempa\endcsname
    {\noexpand\modulespec{\@tempa}}%
}
\makeatother

will define \mdescription and \mlearningoutcomes as required. I've used an \edef to force expansion of the variable in the correct places. (No use applying \protected@edef here as the 'text' has to be 'safe' inside \csname.)

2
  • It would be good if the title remained with the two words separated. I think one possible way is by \let\@tempb\@tempa at the start and then putting \modulespec{\@tempb} instead? Commented Mar 6, 2014 at 13:03
  • @NathanaelFarley If you use two separate words you can't easily them as a document-level command
    – Joseph Wright
    Commented Mar 6, 2014 at 13:52
3

Using \xintFor and the LaTeX2e kernel utilities (with an @ in their names):

\documentclass{article}
\usepackage[english]{babel}
\usepackage{blindtext}
\usepackage{xinttools}
% In the real thing, this definition is much more complicated
\newcommand{\modulespec}[2]{\section{#1} #2}

% % The code I want in pure TeX/LaTeX
% for i in 'description' 'learning outcomes'
% do
%     \def\csname m\getRidOfSpaces{$i} \endcsname{\modulespec{$i}{#1}}
% done
% % end of non TeX code

% Nota bene: in the above you probably meant \long\def, not \def
\makeatletter
\xintFor #1 in {description, learning outcomes}\do
{\long\@namedef{m\zap@space #1 \@empty}##1{\modulespec{#1}{##1}}}
\makeatother

\begin{document}

% Both work
\modulespec{description}{\blindtext}
\modulespec{learning outcomes}{\blindtext}

% These two commands should produce the same output as the previous two
\mdescription{\blindtext}
\mlearningoutcomes{\blindtext}

\end{document}

def loop

1
  • the \long\@namedef is not quite a \newcommand as it doesn't check for preexisting command name.
    – user4686
    Commented Mar 6, 2014 at 14:09

You must log in to answer this question.

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