Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

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}
share|improve this question
    
Is removing the spaces an absolute requirement? It's (marginally) easier if we don't have to –  Joseph Wright Mar 6 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 –  Christian Hupfer Mar 6 at 11:55
    
@JosephWright Removing the spaces is not an absolute requirement, I think there are few enough commands to define them by hand. –  Nathanael Farley Mar 6 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? –  Nathanael Farley Mar 6 at 12:06

3 Answers 3

up vote 6 down vote accepted

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

share|improve this answer
    
This is impressive! Could you explain a little how the code logic works? I've never seen anything like this before. –  Nathanael Farley Mar 6 at 14:46

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

share|improve this answer
    
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? –  Nathanael Farley Mar 6 at 13:03
    
@NathanaelFarley If you use two separate words you can't easily them as a document-level command –  Joseph Wright Mar 6 at 13:52

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

share|improve this answer
    
the \long\@namedef is not quite a \newcommand as it doesn't check for preexisting command name. –  jfbu Mar 6 at 14:09

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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