1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """Sphinx extension for building opcode documentation.
23
24 """
25
26 from cStringIO import StringIO
27
28 import docutils.statemachine
29 import docutils.nodes
30 import docutils.utils
31
32 import sphinx.errors
33 import sphinx.util.compat
34
35 s_compat = sphinx.util.compat
36
37 from ganeti import constants
38 from ganeti import compat
39 from ganeti import errors
40 from ganeti import utils
41 from ganeti import opcodes
42 from ganeti import ht
43 from ganeti import rapi
44
45 import ganeti.rapi.rlib2
46
47
48 COMMON_PARAM_NAMES = map(compat.fst, opcodes.OpCode.OP_PARAMS)
49
50
51 EVAL_NS = dict(compat=compat, constants=constants, utils=utils, errors=errors,
52 rlib2=rapi.rlib2)
53
54
57
58
60 """Split simple option list.
61
62 @type text: string
63 @param text: Options, e.g. "foo, bar, baz"
64
65 """
66 return [i.strip(",").strip() for i in text.split()]
67
68
70 """Parse simple assignment option.
71
72 @type text: string
73 @param text: Assignments, e.g. "foo=bar, hello=world"
74 @rtype: dict
75
76 """
77 result = {}
78
79 for part in _SplitOption(text):
80 if "=" not in part:
81 raise OpcodeError("Invalid option format, missing equal sign")
82
83 (name, value) = part.split("=", 1)
84
85 result[name.strip()] = value.strip()
86
87 return result
88
89
91 """Build opcode parameter documentation.
92
93 @type op_id: string
94 @param op_id: Opcode ID
95
96 """
97 op_cls = opcodes.OP_MAPPING[op_id]
98
99 params_with_alias = \
100 utils.NiceSort([(alias.get(name, name), name, default, test, doc)
101 for (name, default, test, doc) in op_cls.GetAllParams()],
102 key=compat.fst)
103
104 for (rapi_name, name, default, test, doc) in params_with_alias:
105
106 if (name in COMMON_PARAM_NAMES and
107 (not include or name not in include)):
108 continue
109 if exclude is not None and name in exclude:
110 continue
111 if include is not None and name not in include:
112 continue
113
114 has_default = default is not ht.NoDefault
115 has_test = not (test is None or test is ht.NoType)
116
117 buf = StringIO()
118 buf.write("``%s``" % rapi_name)
119 if has_default or has_test:
120 buf.write(" (")
121 if has_default:
122 buf.write("defaults to ``%s``" % default)
123 if has_test:
124 buf.write(", ")
125 if has_test:
126 buf.write("must be ``%s``" % test)
127 buf.write(")")
128 yield buf.getvalue()
129
130
131 for line in doc.splitlines():
132 yield " %s" % line
133
134
136 """Build opcode result documentation.
137
138 @type op_id: string
139 @param op_id: Opcode ID
140
141 """
142 op_cls = opcodes.OP_MAPPING[op_id]
143
144 result_fn = getattr(op_cls, "OP_RESULT", None)
145
146 if not result_fn:
147 raise OpcodeError("Opcode '%s' has no result description" % op_id)
148
149 return "``%s``" % result_fn
150
151
153 """Custom directive for opcode parameters.
154
155 See also <http://docutils.sourceforge.net/docs/howto/rst-directives.html>.
156
157 """
158 has_content = False
159 required_arguments = 1
160 optional_arguments = 0
161 final_argument_whitespace = False
162 option_spec = dict(include=_SplitOption, exclude=_SplitOption,
163 alias=_ParseAlias)
164
166 op_id = self.arguments[0]
167 include = self.options.get("include", None)
168 exclude = self.options.get("exclude", None)
169 alias = self.options.get("alias", {})
170
171 tab_width = 2
172 path = op_id
173 include_text = "\n".join(_BuildOpcodeParams(op_id, include, exclude, alias))
174
175
176 include_lines = docutils.statemachine.string2lines(include_text, tab_width,
177 convert_whitespace=1)
178 self.state_machine.insert_input(include_lines, path)
179
180 return []
181
182
184 """Custom directive for opcode result.
185
186 See also <http://docutils.sourceforge.net/docs/howto/rst-directives.html>.
187
188 """
189 has_content = False
190 required_arguments = 1
191 optional_arguments = 0
192 final_argument_whitespace = False
193
195 op_id = self.arguments[0]
196
197 tab_width = 2
198 path = op_id
199 include_text = _BuildOpcodeResult(op_id)
200
201
202 include_lines = docutils.statemachine.string2lines(include_text, tab_width,
203 convert_whitespace=1)
204 self.state_machine.insert_input(include_lines, path)
205
206 return []
207
208
209 -def PythonEvalRole(role, rawtext, text, lineno, inliner,
210 options={}, content=[]):
211 """Custom role to evaluate Python expressions.
212
213 The expression's result is included as a literal.
214
215 """
216
217
218
219
220
221 code = docutils.utils.unescape(text, restore_backslashes=True)
222
223 try:
224 result = eval(code, EVAL_NS)
225 except Exception, err:
226 msg = inliner.reporter.error("Failed to evaluate %r: %s" % (code, err),
227 line=lineno)
228 return ([inliner.problematic(rawtext, rawtext, msg)], [msg])
229
230 node = docutils.nodes.literal("", unicode(result), **options)
231
232 return ([node], [])
233
234
236 """Custom directive for writing assertions.
237
238 The content must be a valid Python expression. If its result does not
239 evaluate to C{True}, the assertion fails.
240
241 """
242 has_content = True
243 required_arguments = 0
244 optional_arguments = 0
245 final_argument_whitespace = False
246
248
249 if hasattr(self, "assert_has_content"):
250 self.assert_has_content()
251 else:
252 assert self.content
253
254 code = "\n".join(self.content)
255
256 try:
257 result = eval(code, EVAL_NS)
258 except Exception, err:
259 raise self.error("Failed to evaluate %r: %s" % (code, err))
260
261 if not result:
262 raise self.error("Assertion failed: %s" % (code, ))
263
264 return []
265
266
268 """Build query fields documentation.
269
270 @type fields: dict (field name as key, field details as value)
271
272 """
273 for (_, (fdef, _, _, _)) in utils.NiceSort(fields.items(),
274 key=compat.fst):
275 assert len(fdef.doc.splitlines()) == 1
276 yield "``%s``" % fdef.name
277 yield " %s" % fdef.doc
278
279
280
281
282
291