2012-02-01: Updated to allow for adjustment of spacing between subsequent lines.
Here is a solution for the first two problems adapted from In listings, how to show referenced linenumbers instead of standard ascending linenumbers.
Syntax:
\ShowListingForLineNumber*[percentage]{<line number>}}{<file name>}
where:
*
adjust space above current line (optinal)
[percentage]
= percentage of \baselineskip
to leave above current line (optional, ignored if *
is not used). If not specified this is defaults to be 1.0
meaning the entire spacing above is suppressed.
<line number>}
is the line number of which the listing is to be printed
<file name>
is the file name of the input file
Notes:
Code:
\documentclass{article}
\usepackage{filecontents}
\usepackage{xcolor}
\usepackage{xparse}% to define star variant of macro
\usepackage{listings}
\begin{filecontents*}{foo.java}
public int nextInt(int n) {
if (n<=0)
throw new IllegalArgumentException("n must be positive");
if ((n & -n) == n) // i.e., n is a power of 2
return (int)((n * (long)next(31)) >> 31);
int bits, val;
do {
bits = next(31);
val = bits % n;
} while(bits - val + (n-1) < 0);
return val;
}
\end{filecontents*}
\lstdefinestyle{MyListStyle} {
numbers=left,
language=Java,
backgroundcolor={\color{yellow}},
breaklines=true
}
\begin{document}
\noindent
Showing line range 3-5:
\lstinputlisting[
style=MyListStyle,
linerange={3-5},
firstnumber=3,
]{foo.java}
\bigskip\noindent
Showing lines 1,3,7,12 (note that Line 7 is blank) with starred version between lines 1 and 3 to supress the space and the space before line 12 set to 50\% of the \verb|\baselineskip|:
\NewDocumentCommand{\ShowListingForLineNumber}{s O{1.0} m m}{
\IfBooleanTF{#1}{\vspace{-#2\baselineskip}}{}
\lstinputlisting[
style=MyListStyle,
linerange={#3-#3},
firstnumber=#3,
]{#4}
}%
\ShowListingForLineNumber{1}{foo.java}
\ShowListingForLineNumber*{3}{foo.java}% supress space before
\ShowListingForLineNumber*[0.5]{7}{foo.java}% supress 50% of the space before
\ShowListingForLineNumber*{12}{foo.java}
\end{document}
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
Best Answer
Each "listing" environment understands the
firstline
andlastline
keys: