Package ganeti :: Package build :: Module sphinx_ext
[hide private]
[frames] | no frames]

Source Code for Module ganeti.build.sphinx_ext

  1  # 
  2  # 
  3   
  4  # Copyright (C) 2011, 2012 Google Inc. 
  5  # 
  6  # This program is free software; you can redistribute it and/or modify 
  7  # it under the terms of the GNU General Public License as published by 
  8  # the Free Software Foundation; either version 2 of the License, or 
  9  # (at your option) any later version. 
 10  # 
 11  # This program is distributed in the hope that it will be useful, but 
 12  # WITHOUT ANY WARRANTY; without even the implied warranty of 
 13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
 14  # General Public License for more details. 
 15  # 
 16  # You should have received a copy of the GNU General Public License 
 17  # along with this program; if not, write to the Free Software 
 18  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 
 19  # 02110-1301, USA. 
 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  from ganeti import luxi 
 45   
 46  import ganeti.rapi.rlib2 # pylint: disable=W0611 
 47   
 48   
49 -def _GetCommonParamNames():
50 """Builds a list of parameters common to all opcodes. 51 52 """ 53 names = set(map(compat.fst, opcodes.OpCode.OP_PARAMS)) 54 55 # The "depends" attribute should be listed 56 names.remove(opcodes.DEPEND_ATTR) 57 58 return names
59 60 61 COMMON_PARAM_NAMES = _GetCommonParamNames() 62 63 #: Namespace for evaluating expressions 64 EVAL_NS = dict(compat=compat, constants=constants, utils=utils, errors=errors, 65 rlib2=rapi.rlib2, luxi=luxi) 66 67 # Constants documentation for man pages 68 CV_ECODES_DOC = "ecodes" 69 # We don't care about the leak of variables _, name and doc here. 70 # pylint: disable=W0621 71 CV_ECODES_DOC_LIST = [(name, doc) for (_, name, doc) in constants.CV_ALL_ECODES] 72 DOCUMENTED_CONSTANTS = { 73 CV_ECODES_DOC: CV_ECODES_DOC_LIST, 74 } 75 76
77 -class OpcodeError(sphinx.errors.SphinxError):
78 category = "Opcode error"
79 80
81 -def _SplitOption(text):
82 """Split simple option list. 83 84 @type text: string 85 @param text: Options, e.g. "foo, bar, baz" 86 87 """ 88 return [i.strip(",").strip() for i in text.split()]
89 90
91 -def _ParseAlias(text):
92 """Parse simple assignment option. 93 94 @type text: string 95 @param text: Assignments, e.g. "foo=bar, hello=world" 96 @rtype: dict 97 98 """ 99 result = {} 100 101 for part in _SplitOption(text): 102 if "=" not in part: 103 raise OpcodeError("Invalid option format, missing equal sign") 104 105 (name, value) = part.split("=", 1) 106 107 result[name.strip()] = value.strip() 108 109 return result
110 111
112 -def _BuildOpcodeParams(op_id, include, exclude, alias):
113 """Build opcode parameter documentation. 114 115 @type op_id: string 116 @param op_id: Opcode ID 117 118 """ 119 op_cls = opcodes.OP_MAPPING[op_id] 120 121 params_with_alias = \ 122 utils.NiceSort([(alias.get(name, name), name, default, test, doc) 123 for (name, default, test, doc) in op_cls.GetAllParams()], 124 key=compat.fst) 125 126 for (rapi_name, name, default, test, doc) in params_with_alias: 127 # Hide common parameters if not explicitly included 128 if (name in COMMON_PARAM_NAMES and 129 (not include or name not in include)): 130 continue 131 if exclude is not None and name in exclude: 132 continue 133 if include is not None and name not in include: 134 continue 135 136 has_default = default is not ht.NoDefault 137 has_test = not (test is None or test is ht.NoType) 138 139 buf = StringIO() 140 buf.write("``%s``" % rapi_name) 141 if has_default or has_test: 142 buf.write(" (") 143 if has_default: 144 buf.write("defaults to ``%s``" % default) 145 if has_test: 146 buf.write(", ") 147 if has_test: 148 buf.write("must be ``%s``" % test) 149 buf.write(")") 150 yield buf.getvalue() 151 152 # Add text 153 for line in doc.splitlines(): 154 yield " %s" % line
155 156
157 -def _BuildOpcodeResult(op_id):
158 """Build opcode result documentation. 159 160 @type op_id: string 161 @param op_id: Opcode ID 162 163 """ 164 op_cls = opcodes.OP_MAPPING[op_id] 165 166 result_fn = getattr(op_cls, "OP_RESULT", None) 167 168 if not result_fn: 169 raise OpcodeError("Opcode '%s' has no result description" % op_id) 170 171 return "``%s``" % result_fn
172 173
174 -class OpcodeParams(s_compat.Directive):
175 """Custom directive for opcode parameters. 176 177 See also <http://docutils.sourceforge.net/docs/howto/rst-directives.html>. 178 179 """ 180 has_content = False 181 required_arguments = 1 182 optional_arguments = 0 183 final_argument_whitespace = False 184 option_spec = dict(include=_SplitOption, exclude=_SplitOption, 185 alias=_ParseAlias) 186
187 - def run(self):
188 op_id = self.arguments[0] 189 include = self.options.get("include", None) 190 exclude = self.options.get("exclude", None) 191 alias = self.options.get("alias", {}) 192 193 tab_width = 2 194 path = op_id 195 include_text = "\n".join(_BuildOpcodeParams(op_id, include, exclude, alias)) 196 197 # Inject into state machine 198 include_lines = docutils.statemachine.string2lines(include_text, tab_width, 199 convert_whitespace=1) 200 self.state_machine.insert_input(include_lines, path) 201 202 return []
203 204
205 -class OpcodeResult(s_compat.Directive):
206 """Custom directive for opcode result. 207 208 See also <http://docutils.sourceforge.net/docs/howto/rst-directives.html>. 209 210 """ 211 has_content = False 212 required_arguments = 1 213 optional_arguments = 0 214 final_argument_whitespace = False 215
216 - def run(self):
217 op_id = self.arguments[0] 218 219 tab_width = 2 220 path = op_id 221 include_text = _BuildOpcodeResult(op_id) 222 223 # Inject into state machine 224 include_lines = docutils.statemachine.string2lines(include_text, tab_width, 225 convert_whitespace=1) 226 self.state_machine.insert_input(include_lines, path) 227 228 return []
229 230
231 -def PythonEvalRole(role, rawtext, text, lineno, inliner, 232 options={}, content=[]):
233 """Custom role to evaluate Python expressions. 234 235 The expression's result is included as a literal. 236 237 """ 238 # pylint: disable=W0102,W0613,W0142 239 # W0102: Dangerous default value as argument 240 # W0142: Used * or ** magic 241 # W0613: Unused argument 242 243 code = docutils.utils.unescape(text, restore_backslashes=True) 244 245 try: 246 result = eval(code, EVAL_NS) 247 except Exception, err: # pylint: disable=W0703 248 msg = inliner.reporter.error("Failed to evaluate %r: %s" % (code, err), 249 line=lineno) 250 return ([inliner.problematic(rawtext, rawtext, msg)], [msg]) 251 252 node = docutils.nodes.literal("", unicode(result), **options) 253 254 return ([node], [])
255 256
257 -class PythonAssert(s_compat.Directive):
258 """Custom directive for writing assertions. 259 260 The content must be a valid Python expression. If its result does not 261 evaluate to C{True}, the assertion fails. 262 263 """ 264 has_content = True 265 required_arguments = 0 266 optional_arguments = 0 267 final_argument_whitespace = False 268
269 - def run(self):
270 # Handle combinations of Sphinx and docutils not providing the wanted method 271 if hasattr(self, "assert_has_content"): 272 self.assert_has_content() 273 else: 274 assert self.content 275 276 code = "\n".join(self.content) 277 278 try: 279 result = eval(code, EVAL_NS) 280 except Exception, err: 281 raise self.error("Failed to evaluate %r: %s" % (code, err)) 282 283 if not result: 284 raise self.error("Assertion failed: %s" % (code, )) 285 286 return []
287 288
289 -def BuildQueryFields(fields):
290 """Build query fields documentation. 291 292 @type fields: dict (field name as key, field details as value) 293 294 """ 295 defs = [(fdef.name, fdef.doc) 296 for (_, (fdef, _, _, _)) in utils.NiceSort(fields.items(), 297 key=compat.fst)] 298 return BuildValuesDoc(defs)
299 300
301 -def BuildValuesDoc(values):
302 """Builds documentation for a list of values 303 304 @type values: list of tuples in the form (value, documentation) 305 306 """ 307 for name, doc in values: 308 assert len(doc.splitlines()) == 1 309 yield "``%s``" % name 310 yield " %s" % doc
311 312 313 # TODO: Implement Sphinx directive for query fields 314 315
316 -def setup(app):
317 """Sphinx extension callback. 318 319 """ 320 app.add_directive("opcode_params", OpcodeParams) 321 app.add_directive("opcode_result", OpcodeResult) 322 app.add_directive("pyassert", PythonAssert) 323 app.add_role("pyeval", PythonEvalRole)
324