13

UPDATE: I've opened a follow-up question: How can I typeset an environment and its literal equivalent in an environment?


I would like to pass a command and arguments to an environment. The output should be threefold:

  1. The literal command
  2. The typeset version (how it should appear when used)
  3. The definition of the command. (This is just a textual description of what the command is used for.)

My Attempt

For this example, I try to use the \key command provided by the menukeys package.

\documentclass{article}
\usepackage{fontspec}
\usepackage{environ}
\usepackage{marginnote}
\usepackage{menukeys}
\NewEnviron{command}[1]{% 
\par
\reversemarginpar\marginnote{\texttt{\string#1}}
\BODY
\par
\textbf{Example:} % And here is #1 with #2, but literally typeset (e.g. literally \keys{Shift + F5})
\par
%#1#2 % <-- I'd like to typeset these two inputs properly (e.g. non-literal \keys{Shift + F5})
\par
}%


\begin{document}

\begin{command}{\keys}% {{Shift + F5}} % Example input does not work
This command allows you to add keyboard strokes. % here is the definition followed by an example on the next line.
\end{command}

\end{document}

Desired Output

enter image description here

My Thoughts

Maybe this is not even the right approach. I do not know how to go about dealing the commands with multiple arguments efficiently (e.g. created with the xparse package).

3 Answers3

12

You should absorb the two arguments verbatim, which xparse allows to do; then you can “rescan” the two arguments when you want to show the effect.

\documentclass{article}
\usepackage{fontspec}
\usepackage{menukeys}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentEnvironment{command}{vv}
 {
  \tl_set:Nn \l_macmad_argument_i_tl { #1 }
  \tl_set:Nn \l_macmad_argument_ii_tl { #2 }
  \par
  \noindent
  \makebox[0pt][r]{\ttfamily\l_macmad_argument_i_tl\hspace{2em}}
  \ignorespaces
 }
 {
  \par\nopagebreak
  \noindent
  \textbf{Example:~}
  \texttt
   {
    \l_macmad_argument_i_tl
    \tl_if_blank:VF \l_macmad_argument_ii_tl
     { \{ \l_macmad_argument_ii_tl \} }
   }
  \\*
  \tl_set_rescan:NnV \l_macmad_argument_tl {} \l_macmad_argument_i_tl
  \tl_if_blank:VF \l_macmad_argument_ii_tl
   {
    \tl_set_rescan:NnV \l_macmad_temp_tl {} \l_macmad_argument_ii_tl
    \tl_put_right:Nx \l_macmad_argument_tl { { \exp_not:V \l_macmad_temp_tl } }
   }
  \l_macmad_argument_tl
  \par
 }
\tl_new:N \l_macmad_argument_tl
\tl_new:N \l_macmad_argument_i_tl
\tl_new:N \l_macmad_argument_ii_tl
\tl_new:N \l_macmad_temp_tl
\cs_generate_variant:Nn \tl_set_rescan:Nnn { NnV }
\ExplSyntaxOff

\begin{document}

\begin{command}{\keys}{Shift + F5}
This command allows you to add keyboard strokes.
\end{command}

\begin{command}{\TeX}{}
This command prints the \TeX\ logo.
\end{command}

\begin{command}{\textbf}{\TeX}
This command prints its argument in bold.
\end{command}

\end{document}

enter image description here

You can accommodate more than one argument, but in this case you need to use braces for the mandatory ones, even if there's only one of them.

\documentclass{article}
\usepackage{fontspec}
\usepackage{menukeys}
\usepackage{xparse} % but it's already loaded by fontspec

\ExplSyntaxOn
% "v" means "verbatim argument"
\NewDocumentEnvironment{command}{vv}
 {
  % store the two arguments in variables
  % xparse has absorbed them "verbatim"
  \tl_set:Nn \l_macmad_argument_i_tl { #1 }
  \tl_set:Nn \l_macmad_argument_ii_tl { #2 }
  \par
  \noindent
  % print the first argument in the margin
  \makebox[0pt][r]{\ttfamily\l_macmad_argument_i_tl\hspace{2em}}
  \ignorespaces
 }
 {
  \par\nopagebreak
  \noindent
  \textbf{Example:~}
  \texttt
   {
    % print the first argument
    \l_macmad_argument_i_tl
    % print the second argument (but only if non empty)
    \tl_if_blank:VF \l_macmad_argument_ii_tl
     { \l_macmad_argument_ii_tl }
   }
  \\*
  % transform back the first argument from verbatim into "standard tokens"
  \tl_set_rescan:NnV \l_macmad_argument_tl {} \l_macmad_argument_i_tl
  \tl_if_blank:VF \l_macmad_argument_ii_tl
   {
    % do the same for the second argument (if non empty)
    \tl_set_rescan:NnV \l_macmad_temp_tl {} \l_macmad_argument_ii_tl
    % append the rescanned text to the previous
    \tl_put_right:Nx \l_macmad_argument_tl { \exp_not:V \l_macmad_temp_tl }
   }
  % process the contents of the rescanned arguments
  \l_macmad_argument_tl
  \par
 }

% allocate the variables
\tl_new:N \l_macmad_argument_tl
\tl_new:N \l_macmad_argument_i_tl
\tl_new:N \l_macmad_argument_ii_tl
\tl_new:N \l_macmad_temp_tl
% generate a variant command
\cs_generate_variant:Nn \tl_set_rescan:Nnn { NnV }
\ExplSyntaxOff

\begin{document}

\begin{command}{\keys}{{Shift + F5}}
This command allows you to add keyboard strokes.
\end{command}

\begin{command}{\TeX}{}
This command prints the \TeX\ logo.
\end{command}

\begin{command}{\textbf}{{\TeX}}
This command prints its argument in bold.
\end{command}

\begin{command}{\framebox}{[3cm][l]{\TeX}}
This command frames text in a specified area.
\end{command}

\end{document}

enter image description here

egreg
  • 1,121,712
  • This method currently doesn't allow commands with multiple arguments, although I'm sure that could be fixed. – Jonas Granholm Apr 21 '15 at 14:18
  • @JonasGranholm Yes, but the syntax changes a bit: you need to supply the braces around a single mandatory argument. Not a big deal, as you gain the possibility of expressing whatever combination of arguments. – egreg Apr 21 '15 at 14:26
  • @egreg My favorite is the recursive definition: This command prints the \TeX\ logo. – Jonathan Komar Apr 22 '15 at 06:09
  • @egreg What is this \tl_set:Nn business? I lost control of the code because I don't understand it. As it turns out, I don't want the commands to be in the margin. Normally I would just get rid of the marginnote and use a minipage or something. Also, I find it confusing that the first example uses xparse, but the second one does not. Really great work! Thanks for the help. – Jonathan Komar Apr 22 '15 at 06:33
  • @macmadness86 I commented the code, do texdoc interface3 for more information. – egreg Apr 22 '15 at 06:46
  • @egreg Ok, I was able to get the literal command into a node like this \tikz \node [fill=blue!15] {\l_macmad_argument_i_tl};\par. However, I was not able to do the same to the typeset/reread version \l_macmad_argument_tl for some reason. Thanks for pointing me in the right direction! I am sitting here with a paper copy of the xparse documentation in front of me. I will check out interface3 – Jonathan Komar Apr 22 '15 at 06:47
  • @egreg The interface3 manual states that tl=token list and that l= parameters whose value should only be set locally. Are those referring to what you have as \tl and \l? I also see the _tl appended to the variable name. – Jonathan Komar Apr 22 '15 at 12:23
  • @macmadness86 Yes, precisely. Moreover, when a variable name is defined, it is good practice to end the name with _<type>, so _tl, _seq, _clist and so on. – egreg Apr 22 '15 at 13:01
  • @egreg Why is the second \l_macmad_argument_ii_tl in \tl_if_blank:VF \l_macmad_argument_ii_tl {\l_macmad_argument_ii_tl } in braces? – Jonathan Komar Apr 23 '15 at 06:21
  • @egreg Sorry to be such a nag, but I really want to understand this. In your rescan, you create a new arg \l_macmad_argument_tl to contain the standard tokens of arg1 e.g. \framebox Then you follow it with {} which I don't understand, then \l_macmad_argument_i_tl. The manual states that rescan takes "⟨tl var⟩ {⟨setup⟩} {⟨tokens⟩}". tl var=\l_macmad_argument_tl, setup={}, tokens=\l_macmad_argument_i_tl? Is that correct? Where is your setup? And if it is just {}, what does that mean? – Jonathan Komar Apr 23 '15 at 06:40
  • 1
    @macmadness86 \tl_set_rescan:Nnn requires three arguments: the variable to set, optional assignments (in this case none) and the tokens to rescan. Here I use a variant \tl_set_rescan:NnV where the third argument is taken to be the value of a variable. – egreg Apr 23 '15 at 08:30
8

Is not exactly what you asked, but may be is useful for someone. The advantage is that the tcolorbox package can print the LaTeX code and the result of this code in a environment with a lot of options.

MWE

\documentclass{article}
\usepackage[most]{tcolorbox}
\usepackage{menukeys}
\newtcblisting{command}{sidebyside,width=.55\linewidth,nobeforeafter,baseline=5mm,lefthand ratio=0.65}
\parskip1em
\begin{document}

Print the \TeX\ logo \dotfill \begin{command}\TeX\end{command} 

Add keyboard strokes \dotfill \begin{command}\keys{Shift + F5}\end{command}

Print argument in bold \dotfill \begin{command}\textbf{\TeX}\end{command}

\end{document}

Edit:

Another rigid but simple solution is the package example:

MWE

\documentclass[a5paper]{article}
\usepackage{menukeys,example,lipsum}
\parindent0in\parskip1em
\begin{document}

This command allows you to add keyboard strokes.%
\begin{example}
\keys{Shift + F5}
\end{example}

This command prints the \TeX\ logo.
\begin{example}
\TeX
\end{example}

This command prints its argument in bold.
\begin{example}
\textbf{\TeX}
\end{example}

This print a boxed dummy text.  

\begin{example}
\fbox{\begin{minipage}{4cm}
\raggedright
\tiny\lipsum[2]
\end{minipage}}
\end{example}

\end{document}
Fran
  • 80,769
  • Great! Thanks for sharing another option. My problem with the tcolorbox option is that it does not fully support hyperref. Perhaps in future versions this will not be the case. – Jonathan Komar Apr 22 '15 at 08:47
4

This should do as wanted, and also allows multiple arguments. The only problem is that if there are control sequences in the second argument they are appended with a space.

\documentclass{article}
\usepackage{fontspec}
\usepackage{environ}
\usepackage{marginnote}
\usepackage{menukeys}
\NewEnviron{command}[2]{% 
\par
\reversemarginpar\marginnote{\texttt{\string#1}}
\BODY
\par
\textbf{Example:}
\texttt{\string#1\detokenize{#2}}
\par
#1#2
\par
}%


\begin{document}

\begin{command}{\keys}{{Shift + F5}}
This command allows you to add keyboard strokes.
\end{command}

\begin{command}{\TeX}{}
This command prints the \TeX\ logo.
\end{command}

\begin{command}{\textbf}{{\TeX}}
This command prints its argument in bold.
\end{command}

\begin{command}{\rule}{[-2pt]{1em}{1em}}
This command print a black box.
\end{command}

\end{document}

The output produced by the code