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 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   
 45  import ganeti.rapi.rlib2 # pylint: disable=W0611 
 46   
 47   
 48  COMMON_PARAM_NAMES = map(compat.fst, opcodes.OpCode.OP_PARAMS) 
 49   
 50  #: Namespace for evaluating expressions 
 51  EVAL_NS = dict(compat=compat, constants=constants, utils=utils, errors=errors, 
 52                 rlib2=rapi.rlib2) 
 53   
 54   
55 -class OpcodeError(sphinx.errors.SphinxError):
56 category = "Opcode error"
57 58
59 -def _SplitOption(text):
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
69 -def _ParseAlias(text):
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
90 -def _BuildOpcodeParams(op_id, include, exclude, alias):
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 # Hide common parameters if not explicitely included 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 # Add text 131 for line in doc.splitlines(): 132 yield " %s" % line
133 134
135 -def _BuildOpcodeResult(op_id):
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
152 -class OpcodeParams(s_compat.Directive):
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
165 - def run(self):
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 # Inject into state machine 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
183 -class OpcodeResult(s_compat.Directive):
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
194 - def run(self):
195 op_id = self.arguments[0] 196 197 tab_width = 2 198 path = op_id 199 include_text = _BuildOpcodeResult(op_id) 200 201 # Inject into state machine 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 # pylint: disable=W0102,W0613,W0142 217 # W0102: Dangerous default value as argument 218 # W0142: Used * or ** magic 219 # W0613: Unused argument 220 221 code = docutils.utils.unescape(text, restore_backslashes=True) 222 223 try: 224 result = eval(code, EVAL_NS) 225 except Exception, err: # pylint: disable=W0703 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
235 -class PythonAssert(s_compat.Directive):
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
247 - def run(self):
248 # Handle combinations of Sphinx and docutils not providing the wanted method 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
267 -def BuildQueryFields(fields):
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 # TODO: Implement Sphinx directive for query fields 281 282
283 -def setup(app):
284 """Sphinx extension callback. 285 286 """ 287 app.add_directive("opcode_params", OpcodeParams) 288 app.add_directive("opcode_result", OpcodeResult) 289 app.add_directive("pyassert", PythonAssert) 290 app.add_role("pyeval", PythonEvalRole)
291