Merged in jpihl/sphinx-fork (pull request #301)

#1583: Allow the line numbering of the directive `literalinclude` to match that of the included file, using a new ``lineno-match`` option.
This commit is contained in:
Takayuki Shimizukawa 2014-10-07 22:55:43 +09:00
commit 5d12c20d2a
9 changed files with 187 additions and 23 deletions

View File

@ -41,6 +41,7 @@ Other contributors, listed alphabetically, are:
* Christopher Perkins -- autosummary integration
* Benjamin Peterson -- unittests
* T. Powers -- HTML output improvements
* Jeppe Pihl -- literalinclude improvements
* Rob Ruana -- napoleon extension
* Stefan Seefeld -- toctree improvements
* Shibukawa Yoshiki -- pluggable search API and Japanese search

View File

@ -91,6 +91,8 @@ Features added
Thanks to Takeshi Komiya.
* #1344: add :confval:`gettext_enables` to enable extracting 'index' to gettext
catalog output / applying translation catalog to generated documentation.
* #1583: Allow the line numbering of the directive `literalinclude` to match
that of the included file, using a new ``lineno-match`` option.
* PR#299: add various options to sphinx-quickstart. Quiet mode option
``--quiet`` will skips wizard mode. Thanks to WAKAYAMA shirou.

View File

@ -184,6 +184,10 @@ Includes
string option, only lines that precede the first lines containing that string
are included.
When specifying particular parts of a file to display, it can be useful to
display exactly which lines are being presented.
This can be done using the ``lineno-match`` option.
You can prepend and/or append a line to the included code, using the
``prepend`` and ``append`` option, respectively. This is useful e.g. for
highlighting PHP code that doesn't include the ``<?php``/``?>`` markers.
@ -195,8 +199,8 @@ Includes
.. literalinclude:: example.py
:diff: example.py.orig
This shows the diff between example.py and example.py.orig with unified diff format.
This shows the diff between example.py and example.py.orig with unified diff
format.
.. versionadded:: 0.4.3
The ``encoding`` option.
@ -207,6 +211,7 @@ Includes
The ``prepend`` and ``append`` options, as well as ``tab-width``.
.. versionadded:: 1.3
The ``diff`` option.
The ``lineno-match`` option.
Showing a file name

View File

@ -146,6 +146,7 @@ class LiteralInclude(Directive):
'dedent': int,
'linenos': directives.flag,
'lineno-start': int,
'lineno-match': directives.flag,
'tab-width': int,
'language': directives.unchanged_required,
'encoding': directives.encoding,
@ -163,8 +164,8 @@ class LiteralInclude(Directive):
def read_with_encoding(self, filename, document, codec_info, encoding):
f = None
try:
f = codecs.StreamReaderWriter(open(filename, 'rb'),
codec_info[2], codec_info[3], 'strict')
f = codecs.StreamReaderWriter(open(filename, 'rb'), codec_info[2],
codec_info[3], 'strict')
lines = f.readlines()
lines = dedent_lines(lines, self.options.get('dedent'))
return lines
@ -194,6 +195,17 @@ class LiteralInclude(Directive):
'Cannot use both "pyobject" and "lines" options',
line=self.lineno)]
if 'lineno-match' in self.options and 'lineno-start' in self.options:
return [document.reporter.warning(
'Cannot use both "lineno-match" and "lineno-start"',
line=self.lineno)]
if 'lineno-match' in self.options and \
(set(['append', 'prepend']) & set(self.options.keys())):
return [document.reporter.warning(
'Cannot use "lineno-match" and "append" or "prepend"',
line=self.lineno)]
encoding = self.options.get('encoding', env.config.source_encoding)
codec_info = codecs.lookup(encoding)
@ -207,7 +219,7 @@ class LiteralInclude(Directive):
tmp, fulldiffsource = env.relfn2path(diffsource)
difflines = self.read_with_encoding(fulldiffsource, document,
codec_info, encoding)
codec_info, encoding)
if not isinstance(difflines[0], string_types):
return difflines
diff = unified_diff(
@ -217,6 +229,7 @@ class LiteralInclude(Directive):
self.arguments[0])
lines = list(diff)
linenostart = self.options.get('lineno-start', 1)
objectname = self.options.get('pyobject')
if objectname is not None:
from sphinx.pycode import ModuleAnalyzer
@ -227,17 +240,30 @@ class LiteralInclude(Directive):
'Object named %r not found in include file %r' %
(objectname, filename), line=self.lineno)]
else:
lines = lines[tags[objectname][1]-1 : tags[objectname][2]-1]
lines = lines[tags[objectname][1]-1: tags[objectname][2]-1]
if 'lineno-match' in self.options:
linenostart = tags[objectname][1]
linespec = self.options.get('lines')
if linespec is not None:
if linespec:
try:
linelist = parselinenos(linespec, len(lines))
except ValueError as err:
return [document.reporter.warning(str(err), line=self.lineno)]
# just ignore nonexisting lines
nlines = len(lines)
lines = [lines[i] for i in linelist if i < nlines]
if 'lineno-match' in self.options:
# make sure the line list is not "disjoint".
previous = linelist[0]
for line_number in linelist[1:]:
if line_number == previous + 1:
previous = line_number
continue
return [document.reporter.warning(
'Cannot use "lineno-match" with a disjoint set of '
'"lines"', line=self.lineno)]
linenostart = linelist[0] + 1
# just ignore non-existing lines
lines = [lines[i] for i in linelist if i < len(lines)]
if not lines:
return [document.reporter.warning(
'Line spec %r: no lines pulled from include file %r' %
@ -253,43 +279,54 @@ class LiteralInclude(Directive):
hl_lines = None
startafter = self.options.get('start-after')
endbefore = self.options.get('end-before')
prepend = self.options.get('prepend')
append = self.options.get('append')
endbefore = self.options.get('end-before')
if startafter is not None or endbefore is not None:
use = not startafter
res = []
for line in lines:
for line_number, line in enumerate(lines):
if not use and startafter and startafter in line:
if 'lineno-match' in self.options:
linenostart += line_number + 1
use = True
elif use and endbefore and endbefore in line:
use = False
break
elif use:
res.append(line)
lines = res
if 'lineno-match' in self.options:
# handle that docutils remove preceding lines which only contains
# line separation.
for line in lines:
# check if line contains anything else than line separation.
if line and line.splitlines()[0]:
break
linenostart += 1
prepend = self.options.get('prepend')
if prepend:
lines.insert(0, prepend + '\n')
lines.insert(0, prepend + '\n')
append = self.options.get('append')
if append:
lines.append(append + '\n')
lines.append(append + '\n')
text = ''.join(lines)
if self.options.get('tab-width'):
text = text.expandtabs(self.options['tab-width'])
retnode = nodes.literal_block(text, text, source=filename)
set_source_info(self, retnode)
if diffsource is not None: # if diff is set, set udiff
if diffsource: # if diff is set, set udiff
retnode['language'] = 'udiff'
if self.options.get('language', ''):
if 'language' in self.options:
retnode['language'] = self.options['language']
retnode['linenos'] = 'linenos' in self.options or \
'lineno-start' in self.options
'lineno-start' in self.options or \
'lineno-match' in self.options
extra_args = retnode['highlight_args'] = {}
if hl_lines is not None:
extra_args['hl_lines'] = hl_lines
if 'lineno-start' in self.options:
extra_args['linenostart'] = self.options['lineno-start']
extra_args['linenostart'] = linenostart
env.note_dependency(rel_filename)
caption = self.options.get('caption')
@ -303,7 +340,7 @@ class LiteralInclude(Directive):
directives.register_directive('highlight', Highlight)
directives.register_directive('highlightlang', Highlight) # old
directives.register_directive('highlightlang', Highlight) # old
directives.register_directive('code-block', CodeBlock)
directives.register_directive('sourcecode', CodeBlock)
directives.register_directive('literalinclude', LiteralInclude)

View File

@ -71,6 +71,22 @@ Literalinclude options
:tab-width: 8
:language: python
.. cssclass:: inc-pyobj-lines-match
.. literalinclude:: literal.inc
:pyobject: Foo
:lineno-match:
.. cssclass:: inc-lines-match
.. literalinclude:: literal.inc
:lines: 6-7,8
:lineno-match:
.. cssclass:: inc-startend-match
.. literalinclude:: literal.inc
:start-after: coding: utf-8
:end-before: class Foo
:lineno-match:
Test if dedenting before parsing works.
.. highlight:: python

View File

@ -0,0 +1,17 @@
Literal Includes with Line Numbers Matching
===========================================
.. literalinclude:: literal.inc
:language: python
:pyobject: Bar
:lineno-match:
.. literalinclude:: literal.inc
:language: python
:lines: 5-6,7,8-9
:lineno-match:
.. literalinclude:: literal.inc
:language: python
:start-after: pass
:lineno-match:

View File

@ -0,0 +1,6 @@
Literal Includes with Line Numbers Starting from 200
====================================================
.. literalinclude:: literal.inc
:language: python
:lineno-start: 200

View File

@ -0,0 +1,6 @@
Literal Includes with Line Numbers
==================================
.. literalinclude:: literal.inc
:language: python
:linenos:

View File

@ -98,6 +98,80 @@ def test_literal_include_dedent(app, status, warning):
assert blocks[5].text == '\n\n' # dedent: 1000
@with_app('html', testroot='directive-code')
def test_literal_include_linenos(app, status, warning):
app.builder.build(['linenos'])
html = (app.outdir / 'linenos.html').text()
linenos = (
'<td class="linenos"><div class="linenodiv"><pre>'
' 1\n'
' 2\n'
' 3\n'
' 4\n'
' 5\n'
' 6\n'
' 7\n'
' 8\n'
' 9\n'
'10\n'
'11\n'
'12\n'
'13</pre></div></td>')
assert linenos in html
@with_app('html', testroot='directive-code')
def test_literal_include_lineno_start(app, status, warning):
app.builder.build(['lineno_start'])
html = (app.outdir / 'lineno_start.html').text()
linenos = (
'<td class="linenos"><div class="linenodiv"><pre>'
'200\n'
'201\n'
'202\n'
'203\n'
'204\n'
'205\n'
'206\n'
'207\n'
'208\n'
'209\n'
'210\n'
'211\n'
'212</pre></div></td>')
assert linenos in html
@with_app('html', testroot='directive-code')
def test_literal_include_lineno_match(app, status, warning):
app.builder.build(['lineno_match'])
html = (app.outdir / 'lineno_match.html').text()
pyobject = (
'<td class="linenos"><div class="linenodiv"><pre>'
' 9\n'
'10\n'
'11</pre></div></td>')
assert pyobject in html
lines = (
'<td class="linenos"><div class="linenodiv"><pre>'
'6\n'
'7\n'
'8\n'
'9</pre></div></td>')
assert lines in html
start_after = (
'<td class="linenos"><div class="linenodiv"><pre>'
' 9\n'
'10\n'
'11\n'
'12\n'
'13</pre></div></td>')
assert start_after in html
@with_app('html', testroot='directive-code')
def test_literalinclude_caption_html(app, status, warning):
app.builder.build('index')