-
Notifications
You must be signed in to change notification settings - Fork 290
/
_sphinx.py
141 lines (112 loc) · 4.26 KB
/
_sphinx.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# Copyright Hybrid Logic Ltd. See LICENSE file for details.
"""
Sphinx extension to add a ``task`` directive
This directive allows sharing code between documentation and provisioning code.
.. code-block:: rest
.. task:: name_of_task
``name_of_task`` must the name of a task in ``flocker.provision._tasks``,
without the ``task_`` prefix. A task must take a single runner argument.
"""
from inspect import getsourcefile
from docutils.parsers.rst import Directive
from docutils import nodes
from docutils.statemachine import StringList
from flocker import __version__ as version
from flocker.common.version import get_installable_version
from flocker.docs.version_extensions import PLACEHOLDER
from . import _tasks as tasks
from ._ssh import Run, Sudo, Comment, Put
from ._effect import dispatcher as base_dispatcher, SequenceFailed
from effect import (
sync_perform, sync_performer,
ComposedDispatcher, TypeDispatcher,
NoPerformerFoundError,
)
def run_for_docs(effect):
commands = []
@sync_performer
def run(dispatcher, intent):
commands.append(intent.command)
@sync_performer
def sudo(dispatcher, intent):
commands.append("sudo %s" % (intent.command,))
@sync_performer
def comment(dispatcher, intent):
commands.append("# %s" % (intent.comment))
@sync_performer
def put(dispatcher, intent):
commands.append([
"cat <<EOF > %s" % (intent.path,),
] + intent.content.splitlines() + [
"EOF",
])
sync_perform(
ComposedDispatcher([
TypeDispatcher({
Run: run,
Sudo: sudo,
Comment: comment,
Put: put,
}),
base_dispatcher,
]),
effect,
)
return commands
class TaskDirective(Directive):
"""
Implementation of the C{task} directive.
"""
required_arguments = 1
optional_arguments = 1
final_argument_whitespace = True
option_spec = {
'prompt': str
}
def run(self):
task = getattr(tasks, 'task_%s' % (self.arguments[0],))
prompt = self.options.get('prompt', '$')
if len(self.arguments) > 1:
# Some tasks can include the latest installable version as (part
# of) an argument. This replaces a placeholder with that version.
arguments = self.arguments[1].split()
latest = get_installable_version(version)
task_arguments = [item.replace(PLACEHOLDER, latest).encode("utf-8")
for item in arguments]
else:
task_arguments = []
commands = task(*task_arguments)
lines = ['.. prompt:: bash %s,> auto' % (prompt,), '']
try:
command_lines = run_for_docs(commands)
except NoPerformerFoundError as e:
raise self.error("task: %s not supported"
% (type(e.args[0]).__name__,))
except SequenceFailed as e:
print e.error
for command_line in command_lines:
# handler can return either a string or a list. If it returns a
# list, treat the elements after the first as continuation lines.
if isinstance(command_line, list):
lines.append(' %s %s' % (prompt, command_line[0],))
lines.extend([' > %s' % (line,)
for line in command_line[1:]])
else:
lines.append(' %s %s' % (prompt, command_line,))
# The following three lines record (some?) of the dependencies of the
# directive, so automatic regeneration happens. Specifically, it
# records this file, and the file where the task is declared.
task_file = getsourcefile(task)
tasks_file = getsourcefile(tasks)
self.state.document.settings.record_dependencies.add(task_file)
self.state.document.settings.record_dependencies.add(tasks_file)
self.state.document.settings.record_dependencies.add(__file__)
node = nodes.Element()
text = StringList(lines)
self.state.nested_parse(text, self.content_offset, node)
return node.children
def setup(app):
"""
Entry point for sphinx extension.
"""
app.add_directive('task', TaskDirective)