Section 4.16 of the listings
package specifies that to define new environments you need to use the following with syntax similar to LaTeX's \newenvironment
.
\lstnewenvironment
{<name>}[<number>][<optional default arg>]
{<starting code>}
{<ending code>}
So, if you replace the \newenvironment
with the following:
\lstnewenvironment{showdemo}[1][]{%
\lstset{style=demoLatexStyle,#1}}{}%
you get the desired result:
![enter image description here](https://i.stack.imgur.com/GtY7u.png)
Update
You could also use LTXinputExample
and add the necessary code in separate files, or use the filecontents
package. Since you asked for an environment below I have defined showdemoEnv
, but I think the macro version showdemo
is probably better in this case:
\documentclass{scrbook}
\RequirePackage{showexpl}
\lstdefinestyle{demoLatexStyle}{
basicstyle=\small\ttfamily, % Standardschrift
numbers=none, % Ort der Zeilennummern
frame=none,
}
\newcommand{\TempFileName}{\jobname.filecontents.tmp}%
\usepackage{filecontents}
\begin{filecontents*}{\TempFileName}
\LaTeX{} \LaTeX{}
\end{filecontents*}
\newenvironment{showdemoEnv}[2][]{% Environment version
\LTXinputExample[style=demoLatexStyle,#1]{#2}%
}{%
% Add any end environemnt code here.
}%
\newcommand{\showdemo}[2][]{% Macro version
\LTXinputExample[style=demoLatexStyle,#1]{#2}%
}%
\begin{document}
\begin{LTXexample}[style=demoLatexStyle]
\LaTeX{} \LaTeX{}
\end{LTXexample}
\begin{showdemoEnv}{\TempFileName}
\end{showdemoEnv}
\showdemo{\TempFileName}
\immediate\write18{rm \TempFileName}% Remove file
\end{document}
Note that the starred version filecontents*
was used. This prevents the header comments that filecontents
would normally add to the file.
![enter image description here](https://i.stack.imgur.com/Od1HX.png)
The main problem is that you need to analyze the Python source code, which TeX can't (easily) do. Luckily Python is capable of analyzing itself, using abstract syntax trees (see also some more thorough documentation). The idea is that we have the Python ast analyze the code and write what it finds to a TeX file.
#!/usr/local/bin/python3
import ast
""" The python file we want to analyze. Happens to be itself """
pythonfilename = 'pythonlinenumbers.py'
newcommands = []
def makenewcommand(command,
output):
""" Turns the command and line number into the appropriate command.
The signature is split onto two lines to make it more complicated.
We have to play tricks with the trailing \, because we can't end a string
with a single backslash. """
return r'\newcommand{''\\'+command+'}{'+str(output)+'}\n'
class FuncLister(ast.NodeVisitor):
def visit_FunctionDef(self, node):
""" Recursively visit all functions, determining where each function
starts, where its signature ends, and where the function ends. Store
these in the TeX variables \firstline@funcname, \sigend@funcname,
\docend@funcname, and \lastline@funcname. """
newname=node.name.replace('_','@') # _ isn't allowed in a TeX command
newcommands.append(makenewcommand('firstline@'+newname,node.lineno))
sigend = max(node.lineno,lastline(node.args))
newcommands.append(makenewcommand('sigend@'+newname,sigend))
docstring = ast.get_docstring(node)
docstringlength = len(docstring.split('\n')) if docstring else -1
newcommands.append(makenewcommand('docend@'+newname,sigend+docstringlength))
newcommands.append(makenewcommand('lastline@'+newname,lastline(node)))
self.generic_visit(node)
def lastline(node):
""" Recursively find the last line of a node """
return max( [ node.lineno if hasattr(node,'lineno') else -1 , ]
+[lastline(child) for child in ast.iter_child_nodes(node)] )
with open(pythonfilename) as f:
code = f.read()
FuncLister().visit(ast.parse(code))
with open('linenumbers.tex','w') as f:
for newcommand in newcommands:
f.write(newcommand)
Creates the file linenumbers.tex
:
\newcommand{\firstline@makenewcommand}{10}
\newcommand{\sigend@makenewcommand}{11}
\newcommand{\docend@makenewcommand}{15}
\newcommand{\lastline@makenewcommand}{16}
\newcommand{\firstline@visit@FunctionDef}{19}
\newcommand{\sigend@visit@FunctionDef}{19}
\newcommand{\docend@visit@FunctionDef}{23}
\newcommand{\lastline@visit@FunctionDef}{32}
\newcommand{\firstline@lastline}{34}
\newcommand{\sigend@lastline}{34}
\newcommand{\docend@lastline}{35}
\newcommand{\lastline@lastline}{37}
Then, you can have your main TeX file input the derived TeX file, and use what it found:
\documentclass{article}
\usepackage{listings}
\immediate\write18{python3 pythonlinenumbers.py}
\makeatletter
\input{linenumbers}
\newcommand{\showfunc}[1]{%
#1 signature:
\lstinputlisting[
firstline=\csname firstline@#1\endcsname,
lastline=\csname sigend@#1\endcsname,
language=Python]
{pythonlinenumbers.py}
#1 in its entirety:
\lstinputlisting[
firstline=\csname firstline@#1\endcsname,
lastline=\csname lastline@#1\endcsname,
language=Python]
{pythonlinenumbers.py}}
\makeatother
\begin{document}
Here's the lastline function:
\showfunc{lastline}
\bigskip
Here's the makenewcommand function (also has a multi-line signature):
\showfunc{makenewcommand}
\end{document}
This results in
![pdf output](https://i.stack.imgur.com/TsbCE.png)
Best Answer
There are many problems in your code. The main one is that
LTXexample
is an environment much likeverbatim
and cannot be used in the argument of a command.It's possible to circumvent this problem in some cases, via
\scantokens
. However your way to proceed won't respect line breaks in the input; at least not without heavy surgery with category codes that will break in any case because the code would be read many times; once a character token enters TeX its category code is fixed. With\scantokens
it can be reread and category code reassigned, but once it's passed toLTXexample
the problem will reappear.Just to show a possibility, here is an attempt:
You can try it and verify it seems to work as intended. But
will print exactly in the same way (which is usually not intended in code samples). The major problem, however, is that
will break listings (that is used by
LTXexample
). Try it.Some comments
Your use of
\providecommand
is wrong;\providecommand\AddDemo
will be silently ignored if\AddDemo
is already defined. Say that some package defines it: you'll be very puzzled about the result.It's wrong also inside the definition of
\AddDemo
; say thatis, by mistake, used:
\PrintDemo{text}
would always usedemo1
and you'll not be notified about the double definition.Also
\expandafter\csname demo@content@#1\endcsname
is questionable: the\expandafter
will try to expandd
(which of course is not expandable); the effect is exactly the same as\csname demo@content@#1\endcsname
. In other situations a misplaced\expandafter
can produce disasters.I can understand that you'd like to have the code samples all in one place, but typesetting TeX code requires subtleties that make this goal quite difficult (if not impossible) to attain.