3
sphinx_pipe.py - this file is part of S3QL (http://s3ql.googlecode.com)
5
Implements a Sphinx extension that provides a `pipeinclude` directive
6
to include the output of a program.
9
Copyright (C) 2008-2011 Nikolaus Rath <Nikolaus@rath.org>
11
This program can be distributed under the terms of the GNU LGPL.
14
from docutils.parsers.rst.directives.misc import Include
17
from docutils import io, nodes, statemachine
20
class PipeInclude(Include):
22
Include program output as ReST source.
26
source = self.state_machine.input_lines.source(
27
self.lineno - self.state_machine.input_offset - 1)
28
source_dir = os.path.dirname(os.path.abspath(source))
30
command = self.arguments[0].encode('UTF-8')
31
encoding = self.options.get(
32
'encoding', self.state.document.settings.input_encoding)
33
tab_width = self.options.get(
34
'tab-width', self.state.document.settings.tab_width)
37
child = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE,
39
include_file = io.FileInput(
40
source=child.stdout, encoding=encoding,
41
error_handler=(self.state.document.settings.\
42
input_encoding_error_handler),
43
handle_io_errors=None)
44
except IOError, error:
45
raise self.severe('Problems with "%s" directive path:\n%s: %s.' %
46
(self.name, error.__class__.__name__, str(error)))
47
# Hack: Since Python 2.6, the string interpolation returns a
48
# unicode object if one of the supplied %s replacements is a
49
# unicode object. IOError has no `__unicode__` method and the
50
# fallback `__repr__` does not report the file name. Explicitely
51
# converting to str fixes this for now::
52
# print '%s\n%s\n%s\n' %(error, str(error), repr(error))
53
startline = self.options.get('start-line', None)
54
endline = self.options.get('end-line', None)
56
if startline or (endline is not None):
57
include_lines = include_file.readlines()
58
include_text = ''.join(include_lines[startline:endline])
60
include_text = include_file.read()
61
except UnicodeError, error:
63
'Problem with "%s" directive:\n%s: %s'
64
% (self.name, error.__class__.__name__, error))
65
# start-after/end-before: no restrictions on newlines in match-text,
66
# and no restrictions on matching inside lines vs. line boundaries
67
after_text = self.options.get('start-after', None)
69
# skip content in include_text before *and incl.* a matching text
70
after_index = include_text.find(after_text)
72
raise self.severe('Problem with "start-after" option of "%s" '
73
'directive:\nText not found.' % self.name)
74
include_text = include_text[after_index + len(after_text):]
75
before_text = self.options.get('end-before', None)
77
# skip content in include_text after *and incl.* a matching text
78
before_index = include_text.find(before_text)
80
raise self.severe('Problem with "end-before" option of "%s" '
81
'directive:\nText not found.' % self.name)
82
include_text = include_text[:before_index]
83
if 'literal' in self.options:
84
# Convert tabs to spaces, if `tab_width` is positive.
86
text = include_text.expandtabs(tab_width)
89
literal_block = nodes.literal_block(include_text, text,
91
literal_block.line = 1
92
return [literal_block]
94
include_lines = statemachine.string2lines(
95
include_text, tab_width, convert_whitespace=1)
96
self.state_machine.insert_input(include_lines, command)
101
app.add_directive('pipeinclude', PipeInclude)