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:
tcblisting
from the tcolorbox package if you want to show both the latex input and result.