0

I would like to make a document that displays figures and the corresponding code, and want to cut down on repeating myself. MWE below. I think there is something wonky about lstlisting in a newcommand.

\documentclass{article}
\usepackage{graphicx}
\usepackage{listings}
\usepackage{mwe}


\newcommand{\figMe}[3]{
\begin{figure}
#3
\caption{#2. See Listing~\ref{lst#1} for code.}
\label{fig#1}
\end{figure}
\begin{lstlisting}[caption={#2. See Figure~\ref{fig#1}.},label={lst#1}]
#3
\end{lstlisting}
}

\begin{document}


\figMe{ex1}{Example drawing}{
\includegraphics{example-image-a}
}

\end{document}

Thanks for the answers. This is what I ended up doing, using external files...

\newcommand{\figMeToo}[3]{
\begin{figure}
\input{#3}
\caption{#2 See Listing~\ref{lst#1} for code.}
\label{fig#1}
\end{figure}%\FloatBarrier
\lstinputlisting[frame=tb,caption={#2 See Figure~\ref{fig#1}.},label={lst#1},captionpos=b,language={[latex]TeX}]{#3}%\FloatBarrier
}
2
  • you can't use any verb-like commands in such a definition. the listings package documents specific new commands to define new environments based on listings Commented Mar 27, 2023 at 15:09
  • Have a look at tcblisting from the tcolorbox package if you want to show both the latex input and result. Commented Mar 27, 2023 at 15:11

2 Answers 2

1

I'd written some code in the past for myself (one of my documentations), that allowed me to decouple input and output as far as I needed myself. The code in this answer reuses much of that code, but there are likely some dead keys in these definitions that won't work for you because I created them for my own output.

I removed some of the keys, but I might've overlooked one or the other. The gappend, no-append, and after keys are all functional, but you'd have to build something on your own with them (I created gappend to be able to store away some code and add it later on to the output of another environment, but since your code only ever has a 1:1 correspondence that isn't too useful for you, I guess).

This implementation of enverb will ignore the first n characters of each line of its body, where n is the number of whitespaces before the matching \end (so here \end{figMe}) plus the number specified for the more-ignore key (2 in this example -- which is the amount of indentation I personally use in my documents). My own documents don't use <Tab> but only spaces, I have no idea how well tabs work with this (let's say it should work, but is not extensively tested).

These are many lines of implementation (well, it's a package draft, so just pretend everything in between \makeatletter and \makeatother is hidden behind \usepackage{enverb}), the code I used for your output is really just the 26 lines of code:

\NewDocumentEnvironment{figMe}{mm}
  {\enverb} % enverb will collet an additional o-arg for its own options
  {%
    \enverbUnexpandedOutput
      {\begin{figure}}% pre-contents
      {% post-contents
        \caption
          {%
            #2. See Listing~\ref{lst#1} for code.%
            \label{fig#1}%
          }%
        \end{figure}%
      }%
    \enverbExpandedOutput
      {% pre-contents
        % we don't want to expand the begin part
        \unexpanded
          {%
            \begin{lstlisting}
              [caption={#2. See Figure~\ref{fig#1}.},label={lst#1}]%
          }%
      }
      % verbatim output in lstlistings needs to see the \end as a string without
      % a trailing space
      {\string\end{lstlisting}}% post-contents
  }

The entire document with the enverb code:

\documentclass{article}
\usepackage{graphicx}
\usepackage{listings}
\usepackage{mwe}

\usepackage{expkv-def}

\makeatletter
\ExplSyntaxOn
\cs_new_eq:NN \enverb@count \tl_count:n
\cs_set:Npx \enverb@othercr { \char_generate:nn {13} {12} }
\ExplSyntaxOff
%% key setup
\ekvdefinekeys{enverb}
  {
     boolTF  auto-ignore   = \enverb@ifautoignore
    ,initial auto-ignore
    ,protected code ignore = \let\enverb@ifautoignore\@secondoftwo
    ,also eint      ignore = \enverb@ignore
    ,eint    more-ignore   = \enverb@moreignore
    ,initial more-ignore   = 2
    ,long store bol = \enverb@bol@content
    ,long store eol = \enverb@eol@content
    ,einitial   eol = \enverb@othercr
    ,data gappend   = \enverb@append
    ,code no-append = \let\enverb@append\@secondoftwo
    ,store after    = \enverb@after
  }
\protected\long\ekvsetdef\enverbsetup{enverb}

%% auxiliary error function
\newcommand\enverb@error[1]
  {%
    \GenericError
      {(enverb)\@spaces\@spaces\@spaces\@spaces\@spaces}%
      {Environment enverb error: #1}%
      {Just use it correctly!}%
      {Read the sources.}%
  }

%% setup for weird category code regime
\begingroup
\lccode`\~=`\^^M
\catcode`\:=13
\lccode`\:=`\ % <- space
\catcode`\;=13
\lccode`\;=`\^^I % <- tab
\lowercase{\endgroup
%% code for spaces and CR
\def\enverb@body@space{}%
\def\enverb@body@tab{}%
\def\enverb@body@newline#1~%
  {\enverb@ifnotend{#1}{\enverb@bol\unexpanded{#1}\enverb@eol~}}%
%% activate the category code regime of the body
\protected\def\enverb@body@setup
  {%
    \let\enverb@collected@body\@empty
    \let\do\@makeother\dospecials
    \catcode`\^^M=13 \let~\enverb@body@newline
    \catcode`\   =13 \let:\enverb@body@space
    \catcode`\^^I=13 \let;\enverb@body@tab
    \let\enverb@bol\relax
    \let\enverb@eol\relax
  }
%% check for optional argument
\newcommand\enverb@search@oarg@a
  {%
    \ifx:\next
      \ifenverb@firsteol
      \else
        \enverb@body@add{:}%
      \fi
      \let\next\enverb@search@oarg@b
    \else
      \ifx~\next
        \ifenverb@firsteol
          \enverb@firsteolfalse
        \else
          \enverb@body@add{~}%
        \fi
        \let\next\enverb@search@oarg@b
      \else
        \ifx[\next\@gobble]%
          \let\next\enverb@oarg
        \else
          \ifenverb@firsteol
            \let\next\enverb@body@after@begin
          \else
            \let\next\enverb@body
          \fi
        \fi
      \fi
    \fi
    \next
  }
%% start body collection
\newcommand\enverb@body
  {\edef\enverb@collected@body{\iffalse}\fi\expandafter~\enverb@collected@body}
%% check the line after an oarg
\def\enverb@body@after@oarg#1~%
  {\enverb@ensure@blank{#1}{closing bracket}\enverb@body}
%% check the line after the \begin statement
\def\enverb@body@after@begin#1~%
  {\enverb@ensure@blank{#1}{\string\begin}\enverb@body}
}
%% quick check for empty line
\newcommand\enverb@ensure@blank[2]
  {%
    \expandafter\enverb@ifempty\expanded{{#1}}{}%
      {%
        \expanded{%
        \noexpand\enverb@error
          {%
            Line after #2 not empty.\noexpand\MessageBreak
            Contains: \detokenize\expandafter{\romannumeral`\^^@#1}%
          }%
        }%
      }%
  }
%% quick check for empty argument
\newcommand\enverb@ifempty[1]
  {%
    \enverb@ifempty@\enverb@ifempty@A#1\enverb@ifempty@B.\enverb@ifempty@true
      \enverb@ifempty@A\enverb@ifempty@B
  }
\def\enverb@ifempty@#1\enverb@ifempty@A\enverb@ifempty@B#2#3{#3}
\def\enverb@ifempty@true\enverb@ifempty@A\enverb@ifempty@B#1#2{#1}
\newcommand\enverb@gadd[2]{\xdef#1{\unexpanded\expandafter{#1#2}}}
\newcommand\enverb@body@add[1]
  {%
    \edef\enverb@collected@body
      {\unexpanded\expandafter{\enverb@collected@body#1}}%
  }
% start of environment `enverb'
\newcommand\enverb
  {%
    \begingroup
      \def\tmp{enverb}%
      \expandafter
    \endgroup
    \expandafter\enverb@ifnotend@setup@perhaps\expanded
      {{\string{\@currenvir\string}}}%
    \begingroup
    \enverb@body@setup
    \enverb@firsteoltrue
    \let\enverb@collected@oarg\@empty
    \enverb@search@oarg
  }
\newif\ifenverb@firsteol
\newcommand\enverb@search@oarg{\futurelet\next\enverb@search@oarg@a}
\newcommand\enverb@search@oarg@b{\expandafter\enverb@search@oarg\@gobble}
\newcommand\enverb@oarg{\endgroup\enverb@oarg@}
\NewDocumentCommand\enverb@oarg@{O{}}
  {%
    \edef\enverb@collected@oarg{\unexpanded{#1}}%
    \begingroup
    \enverb@body@setup
    \enverb@body@after@oarg
  }
\def\enverb@ifnotend#1%
  {%
    \def\enverb@ifnotend##1%
      {%
        \enverb@ifnotend@
          ##1\enverb@mark\enverb@ifnotend@maybe
          #1\enverb@mark\@thirdofthree
          \enverb@stop
      }%
    \def\enverb@ifnotend@##1#1##2\enverb@mark##3##4\enverb@stop{##3{##1}{##2}}%
  }
\expandafter\enverb@ifnotend\expanded{{\expandafter\@gobble\string\\end}}
\newcommand\enverb@ifnotend@maybe[2]
  {\expandafter\enverb@ifnotend@perhaps\expandafter{\romannumeral`\^^@#2}{#1}}
\newcommand\enverb@ifnotend@setup@perhaps[1]
  {%
    \def\enverb@ifnotend@perhaps##1%
      {%
        \enverb@ifnotend@perhaps@\enverb@mark##1\enverb@mark\enverb@ifnotend@end
          \enverb@mark#1\enverb@mark\@thirdofthree
          \enverb@stop
      }%
    \def\enverb@ifnotend@perhaps@
        ##1\enverb@mark#1##2\enverb@mark##3##4\enverb@stop
      {##3{##2}}%
  }
\providecommand\@thirdofthree[3]{#3}
\newcommand\enverb@ifnotend@end[3]
  {%
    \iffalse{\fi}%
    \enverb@ensure@blank{#1}{\string\end}%
    \expanded
      {%
        \endgroup
        \enverbsetup
          {\unexpanded\expandafter{\enverb@collected@oarg}}%
        \noexpand\enverb@final
          {\unexpanded\expandafter{\enverb@collected@body}}%
      }%
      {#2}%
    \expandafter\end\expandafter{\@currenvir}%
  }
\long\def\enverb@final#1#2%
  {%
    \enverb@ifautoignore
      {\enverb@setup@ignore{\enverb@count{#2}+\enverb@moreignore}}%
      {\enverb@setup@ignore\enverb@ignore}%
    \edef\enverb@line##1\enverb@eol
      {%
        \noexpand\detokenize{##1}%
        \noexpand\unexpanded{\unexpanded\expandafter{\enverb@eol@content}}%
      }%
    \edef\enverb@collected@body{#1}%
    \expanded
      {\unexpanded{\enverb@append\enverb@gadd\@gobble}\expandafter}%
      \expandafter{\enverb@collected@body}%
  }
\providecommand\@firstofnine[9]{#1}
\newcommand\enverb@setup@ignore[1]
  {\expandafter\enverb@setup@ignore@\the\numexpr#1\relax;\enverb@line}
\def\enverb@setup@ignore@#1;#2%
  {%
    \ifnum#1>9
      \expandafter\@firstoftwo
    \else
      \expandafter\@secondoftwo
    \fi
    {\expandafter\enverb@setup@ignore@\the\numexpr#1-8;{\@firstofnine{#2}}}%
    {%
      \let\enverb@bol@gobble\@empty % just so that renewcommand doesn't go nuts
      \expanded
        {%
          \unexpanded{\renewcommand\enverb@bol@gobble}\ifnum#1>\z@[#1]\fi
            {\unexpanded{#2}}%
          \unexpanded{\def\enverb@bol##1\enverb@eol}%
            {%
              \noexpand\unexpanded
                {\unexpanded\expandafter{\enverb@bol@content}}%
              \unexpanded{\expandafter\enverb@ifempty\expanded}{{##1}}%
                {\noexpand\enverb@line}%
                {\noexpand\enverb@bol@gobble}%
              ##1\unexpanded{\enverb@eol}%
            }%
        }%
    }%
  }
% the output routine if you'd use only an `enverb` environment, empty for you
\let\endenverb\@empty
\NewDocumentCommand\enverbExpandedOutput{O{} +m +m}
  {%
    \begingroup
      \everyeof{\noexpand}%
      #1\relax
      \expandafter
    \endgroup
    \scantokens\expanded
      {{%
        #2\enverb@othercr
        \enverb@collected@body
        #3\enverb@othercr
      }}%
  }
\NewDocumentCommand\enverbUnexpandedOutput{O{} +m +m}
  {%
    \begingroup
      \everyeof{\noexpand}%
      \newlinechar=\endlinechar
      #1\relax
      \expandafter
    \endgroup
    \scantokens\expanded
      {{%
        \unexpanded{#2}%
        \enverb@collected@body
        \unexpanded{#3}%
      }}%
  }
\makeatother


\NewDocumentEnvironment{figMe}{mm}
  {\enverb} % enverb will collet an additional o-arg for its own options
  {%
    \enverbUnexpandedOutput
      {\begin{figure}}
      {%
        \caption
          {%
            #2. See Listing~\ref{lst#1} for code.%
            \label{fig#1}%
          }%
        \end{figure}%
      }%
    \enverbExpandedOutput
      {%
        % we don't want to expand the begin part
        \unexpanded
          {%
            \begin{lstlisting}%
              [caption={#2. See Figure~\ref{fig#1}.},label={lst#1}]%
          }%
      }
      % verbatim output in lstlistings needs to see the \end as a string without
      % a trailing space
      {\string\end{lstlisting}}%
  }

\begin{document}
  \begin{figMe}{ex1}{Example drawing}
    \includegraphics{example-image-a}
  \end{figMe}
\end{document}

Output:

enter image description here

0
1

You can also use Ulrich Diez's DefineVerbatimToScantokens macro (can be found in a few answers, for example https://tex.stackexchange.com/a/626166/250119) as follows:

\documentclass{article}
\usepackage{graphicx}
\usepackage{listings}
\usepackage{mwe}



%=== Code of \DefineVerbatimToScantokens ========================
% With older LaTeX-releases uncomment the following line:
%\usepackage{xparse}   
\NewDocumentCommand\DefineVerbatimToScantokens{mm}{%
  \begingroup
  \catcode`\^^I=12\relax
  \InnerDefineVerbatimToScantokens{#1}{#2}%
}%
\begingroup
\makeatletter
\def\InnerDefineVerbatimToScantokens#1#2{%
  \endgroup
  \NewDocumentCommand\InnerDefineVerbatimToScantokens{mm+v}{%
    \endgroup\ReplaceHashloop{##3}{##1}{##2}%
  }%
  \newcommand\ReplaceHashloop[3]{%
    \ifcat$\detokenize\expandafter{\Hashcheck##1#1}$%
    \expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
    {%
      \NewDocumentCommand{##2}{##3}{%
         \begingroup\newlinechar=\endlinechar
         \scantokens{\endgroup##1#2}%
      }%
    }{%
      \expandafter\ReplaceHashloop\expandafter{\Hashreplace##1}{##2}{##3}%
    }%
  }%
  \@ifdefinable\Hashcheck{\long\def\Hashcheck##1#1{}}%
  \@ifdefinable\Hashreplace{\long\def\Hashreplace##1#1{##1####}}%
}%
\catcode`\%=12\relax
\catcode`\#=12\relax
\InnerDefineVerbatimToScantokens{#}{%}%
%=== End of code of \DefineVerbatimToScantokens =================




\DefineVerbatimToScantokens{\figMe}{m m +v}{
\begin{figure}
#3
\caption{#2. See Listing~\ref{lst#1} for code.}
\label{fig#1}
\end{figure}
\begin{lstlisting}[caption={#2. See Figure~\ref{fig#1}.},label={lst#1}]
#3
\end{lstlisting}
}

\begin{document}


\figMe{ex1}{Example drawing}{\includegraphics{example-image-a}}

\end{document}

Although using {...} to delimit verbatim environment is a bit fragile in my opinion that comments must be {}-brace-balanced.

You must log in to answer this question.

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