Statistics
| Branch: | Tag: | Revision:

root / lib / build / sphinx_ext.py @ b4fcee5b

History | View | Annotate | Download (6.4 kB)

1 5f43f393 Michael Hanselmann
#
2 5f43f393 Michael Hanselmann
#
3 5f43f393 Michael Hanselmann
4 5f43f393 Michael Hanselmann
# Copyright (C) 2011 Google Inc.
5 5f43f393 Michael Hanselmann
#
6 5f43f393 Michael Hanselmann
# This program is free software; you can redistribute it and/or modify
7 5f43f393 Michael Hanselmann
# it under the terms of the GNU General Public License as published by
8 5f43f393 Michael Hanselmann
# the Free Software Foundation; either version 2 of the License, or
9 5f43f393 Michael Hanselmann
# (at your option) any later version.
10 5f43f393 Michael Hanselmann
#
11 5f43f393 Michael Hanselmann
# This program is distributed in the hope that it will be useful, but
12 5f43f393 Michael Hanselmann
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 5f43f393 Michael Hanselmann
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 5f43f393 Michael Hanselmann
# General Public License for more details.
15 5f43f393 Michael Hanselmann
#
16 5f43f393 Michael Hanselmann
# You should have received a copy of the GNU General Public License
17 5f43f393 Michael Hanselmann
# along with this program; if not, write to the Free Software
18 5f43f393 Michael Hanselmann
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 5f43f393 Michael Hanselmann
# 02110-1301, USA.
20 5f43f393 Michael Hanselmann
21 5f43f393 Michael Hanselmann
22 5f43f393 Michael Hanselmann
"""Sphinx extension for building opcode documentation.
23 5f43f393 Michael Hanselmann

24 5f43f393 Michael Hanselmann
"""
25 5f43f393 Michael Hanselmann
26 5f43f393 Michael Hanselmann
import operator
27 5f43f393 Michael Hanselmann
from cStringIO import StringIO
28 5f43f393 Michael Hanselmann
29 dac59ac5 Michael Hanselmann
import docutils.statemachine
30 685d3b42 Michael Hanselmann
import docutils.nodes
31 685d3b42 Michael Hanselmann
import docutils.utils
32 5f43f393 Michael Hanselmann
33 dac59ac5 Michael Hanselmann
import sphinx.errors
34 dac59ac5 Michael Hanselmann
import sphinx.util.compat
35 5f43f393 Michael Hanselmann
36 685d3b42 Michael Hanselmann
from ganeti import constants
37 685d3b42 Michael Hanselmann
from ganeti import compat
38 685d3b42 Michael Hanselmann
from ganeti import errors
39 5f43f393 Michael Hanselmann
from ganeti import utils
40 5f43f393 Michael Hanselmann
from ganeti import opcodes
41 5f43f393 Michael Hanselmann
from ganeti import ht
42 b4fcee5b Michael Hanselmann
from ganeti import rapi
43 b4fcee5b Michael Hanselmann
44 b4fcee5b Michael Hanselmann
import ganeti.rapi.rlib2
45 5f43f393 Michael Hanselmann
46 5f43f393 Michael Hanselmann
47 5f43f393 Michael Hanselmann
COMMON_PARAM_NAMES = map(operator.itemgetter(0), opcodes.OpCode.OP_PARAMS)
48 5f43f393 Michael Hanselmann
49 685d3b42 Michael Hanselmann
#: Namespace for evaluating expressions
50 b4fcee5b Michael Hanselmann
EVAL_NS = dict(compat=compat, constants=constants, utils=utils, errors=errors,
51 b4fcee5b Michael Hanselmann
               rlib2=rapi.rlib2)
52 685d3b42 Michael Hanselmann
53 5f43f393 Michael Hanselmann
54 dac59ac5 Michael Hanselmann
class OpcodeError(sphinx.errors.SphinxError):
55 5f43f393 Michael Hanselmann
  category = "Opcode error"
56 5f43f393 Michael Hanselmann
57 5f43f393 Michael Hanselmann
58 5f43f393 Michael Hanselmann
def _SplitOption(text):
59 5f43f393 Michael Hanselmann
  """Split simple option list.
60 5f43f393 Michael Hanselmann

61 5f43f393 Michael Hanselmann
  @type text: string
62 5f43f393 Michael Hanselmann
  @param text: Options, e.g. "foo, bar, baz"
63 5f43f393 Michael Hanselmann

64 5f43f393 Michael Hanselmann
  """
65 5f43f393 Michael Hanselmann
  return [i.strip(",").strip() for i in text.split()]
66 5f43f393 Michael Hanselmann
67 5f43f393 Michael Hanselmann
68 5f43f393 Michael Hanselmann
def _ParseAlias(text):
69 5f43f393 Michael Hanselmann
  """Parse simple assignment option.
70 5f43f393 Michael Hanselmann

71 5f43f393 Michael Hanselmann
  @type text: string
72 5f43f393 Michael Hanselmann
  @param text: Assignments, e.g. "foo=bar, hello=world"
73 5f43f393 Michael Hanselmann
  @rtype: dict
74 5f43f393 Michael Hanselmann

75 5f43f393 Michael Hanselmann
  """
76 5f43f393 Michael Hanselmann
  result = {}
77 5f43f393 Michael Hanselmann
78 5f43f393 Michael Hanselmann
  for part in _SplitOption(text):
79 5f43f393 Michael Hanselmann
    if "=" not in part:
80 5f43f393 Michael Hanselmann
      raise OpcodeError("Invalid option format, missing equal sign")
81 5f43f393 Michael Hanselmann
82 5f43f393 Michael Hanselmann
    (name, value) = part.split("=", 1)
83 5f43f393 Michael Hanselmann
84 5f43f393 Michael Hanselmann
    result[name.strip()] = value.strip()
85 5f43f393 Michael Hanselmann
86 5f43f393 Michael Hanselmann
  return result
87 5f43f393 Michael Hanselmann
88 5f43f393 Michael Hanselmann
89 5f43f393 Michael Hanselmann
def _BuildOpcodeParams(op_id, include, exclude, alias):
90 5f43f393 Michael Hanselmann
  """Build opcode parameter documentation.
91 5f43f393 Michael Hanselmann

92 5f43f393 Michael Hanselmann
  @type op_id: string
93 5f43f393 Michael Hanselmann
  @param op_id: Opcode ID
94 5f43f393 Michael Hanselmann

95 5f43f393 Michael Hanselmann
  """
96 5f43f393 Michael Hanselmann
  op_cls = opcodes.OP_MAPPING[op_id]
97 5f43f393 Michael Hanselmann
98 5f43f393 Michael Hanselmann
  params_with_alias = \
99 5f43f393 Michael Hanselmann
    utils.NiceSort([(alias.get(name, name), name, default, test, doc)
100 5f43f393 Michael Hanselmann
                    for (name, default, test, doc) in op_cls.GetAllParams()],
101 5f43f393 Michael Hanselmann
                   key=operator.itemgetter(0))
102 5f43f393 Michael Hanselmann
103 5f43f393 Michael Hanselmann
  for (rapi_name, name, default, test, doc) in params_with_alias:
104 5f43f393 Michael Hanselmann
    # Hide common parameters if not explicitely included
105 5f43f393 Michael Hanselmann
    if (name in COMMON_PARAM_NAMES and
106 5f43f393 Michael Hanselmann
        (not include or name not in include)):
107 5f43f393 Michael Hanselmann
      continue
108 5f43f393 Michael Hanselmann
    if exclude is not None and name in exclude:
109 5f43f393 Michael Hanselmann
      continue
110 5f43f393 Michael Hanselmann
    if include is not None and name not in include:
111 5f43f393 Michael Hanselmann
      continue
112 5f43f393 Michael Hanselmann
113 5f43f393 Michael Hanselmann
    has_default = default is not ht.NoDefault
114 5f43f393 Michael Hanselmann
    has_test = not (test is None or test is ht.NoType)
115 5f43f393 Michael Hanselmann
116 5f43f393 Michael Hanselmann
    buf = StringIO()
117 5f43f393 Michael Hanselmann
    buf.write("``%s``" % rapi_name)
118 5f43f393 Michael Hanselmann
    if has_default or has_test:
119 5f43f393 Michael Hanselmann
      buf.write(" (")
120 5f43f393 Michael Hanselmann
      if has_default:
121 5f43f393 Michael Hanselmann
        buf.write("defaults to ``%s``" % default)
122 5f43f393 Michael Hanselmann
        if has_test:
123 5f43f393 Michael Hanselmann
          buf.write(", ")
124 5f43f393 Michael Hanselmann
      if has_test:
125 5f43f393 Michael Hanselmann
        buf.write("must be ``%s``" % test)
126 5f43f393 Michael Hanselmann
      buf.write(")")
127 5f43f393 Michael Hanselmann
    yield buf.getvalue()
128 5f43f393 Michael Hanselmann
129 5f43f393 Michael Hanselmann
    # Add text
130 5f43f393 Michael Hanselmann
    for line in doc.splitlines():
131 5f43f393 Michael Hanselmann
      yield "  %s" % line
132 5f43f393 Michael Hanselmann
133 5f43f393 Michael Hanselmann
134 dac59ac5 Michael Hanselmann
class OpcodeParams(sphinx.util.compat.Directive):
135 5f43f393 Michael Hanselmann
  """Custom directive for opcode parameters.
136 5f43f393 Michael Hanselmann

137 5f43f393 Michael Hanselmann
  See also <http://docutils.sourceforge.net/docs/howto/rst-directives.html>.
138 5f43f393 Michael Hanselmann

139 5f43f393 Michael Hanselmann
  """
140 5f43f393 Michael Hanselmann
  has_content = False
141 5f43f393 Michael Hanselmann
  required_arguments = 1
142 5f43f393 Michael Hanselmann
  optional_arguments = 0
143 5f43f393 Michael Hanselmann
  final_argument_whitespace = False
144 5f43f393 Michael Hanselmann
  option_spec = dict(include=_SplitOption, exclude=_SplitOption,
145 5f43f393 Michael Hanselmann
                     alias=_ParseAlias)
146 5f43f393 Michael Hanselmann
147 5f43f393 Michael Hanselmann
  def run(self):
148 5f43f393 Michael Hanselmann
    op_id = self.arguments[0]
149 5f43f393 Michael Hanselmann
    include = self.options.get("include", None)
150 5f43f393 Michael Hanselmann
    exclude = self.options.get("exclude", None)
151 5f43f393 Michael Hanselmann
    alias = self.options.get("alias", {})
152 5f43f393 Michael Hanselmann
153 5f43f393 Michael Hanselmann
    tab_width = 2
154 5f43f393 Michael Hanselmann
    path = op_id
155 5f43f393 Michael Hanselmann
    include_text = "\n".join(_BuildOpcodeParams(op_id, include, exclude, alias))
156 5f43f393 Michael Hanselmann
157 5f43f393 Michael Hanselmann
    # Inject into state machine
158 dac59ac5 Michael Hanselmann
    include_lines = docutils.statemachine.string2lines(include_text, tab_width,
159 dac59ac5 Michael Hanselmann
                                                       convert_whitespace=1)
160 5f43f393 Michael Hanselmann
    self.state_machine.insert_input(include_lines, path)
161 5f43f393 Michael Hanselmann
162 5f43f393 Michael Hanselmann
    return []
163 5f43f393 Michael Hanselmann
164 5f43f393 Michael Hanselmann
165 685d3b42 Michael Hanselmann
def PythonEvalRole(role, rawtext, text, lineno, inliner,
166 685d3b42 Michael Hanselmann
                   options={}, content=[]):
167 685d3b42 Michael Hanselmann
  """Custom role to evaluate Python expressions.
168 685d3b42 Michael Hanselmann

169 685d3b42 Michael Hanselmann
  The expression's result is included as a literal.
170 685d3b42 Michael Hanselmann

171 685d3b42 Michael Hanselmann
  """
172 1aa50158 Michael Hanselmann
  # pylint: disable-msg=W0102,W0613,W0142
173 1aa50158 Michael Hanselmann
  # W0102: Dangerous default value as argument
174 1aa50158 Michael Hanselmann
  # W0142: Used * or ** magic
175 1aa50158 Michael Hanselmann
  # W0613: Unused argument
176 1aa50158 Michael Hanselmann
177 685d3b42 Michael Hanselmann
  code = docutils.utils.unescape(text, restore_backslashes=True)
178 685d3b42 Michael Hanselmann
179 685d3b42 Michael Hanselmann
  try:
180 685d3b42 Michael Hanselmann
    result = eval(code, EVAL_NS)
181 1aa50158 Michael Hanselmann
  except Exception, err: # pylint: disable-msg=W0703
182 685d3b42 Michael Hanselmann
    msg = inliner.reporter.error("Failed to evaluate %r: %s" % (code, err),
183 685d3b42 Michael Hanselmann
                                 line=lineno)
184 685d3b42 Michael Hanselmann
    return ([inliner.problematic(rawtext, rawtext, msg)], [msg])
185 685d3b42 Michael Hanselmann
186 685d3b42 Michael Hanselmann
  node = docutils.nodes.literal("", unicode(result), **options)
187 685d3b42 Michael Hanselmann
188 685d3b42 Michael Hanselmann
  return ([node], [])
189 685d3b42 Michael Hanselmann
190 685d3b42 Michael Hanselmann
191 685d3b42 Michael Hanselmann
class PythonAssert(sphinx.util.compat.Directive):
192 685d3b42 Michael Hanselmann
  """Custom directive for writing assertions.
193 685d3b42 Michael Hanselmann

194 685d3b42 Michael Hanselmann
  The content must be a valid Python expression. If its result does not
195 685d3b42 Michael Hanselmann
  evaluate to C{True}, the assertion fails.
196 685d3b42 Michael Hanselmann

197 685d3b42 Michael Hanselmann
  """
198 685d3b42 Michael Hanselmann
  has_content = True
199 685d3b42 Michael Hanselmann
  required_arguments = 0
200 685d3b42 Michael Hanselmann
  optional_arguments = 0
201 685d3b42 Michael Hanselmann
  final_argument_whitespace = False
202 685d3b42 Michael Hanselmann
203 685d3b42 Michael Hanselmann
  def run(self):
204 cbb86b63 Michael Hanselmann
    # Handle combinations of Sphinx and docutils not providing the wanted method
205 cbb86b63 Michael Hanselmann
    if hasattr(self, "assert_has_content"):
206 cbb86b63 Michael Hanselmann
      self.assert_has_content()
207 cbb86b63 Michael Hanselmann
    else:
208 cbb86b63 Michael Hanselmann
      assert self.content
209 685d3b42 Michael Hanselmann
210 685d3b42 Michael Hanselmann
    code = "\n".join(self.content)
211 685d3b42 Michael Hanselmann
212 685d3b42 Michael Hanselmann
    try:
213 685d3b42 Michael Hanselmann
      result = eval(code, EVAL_NS)
214 685d3b42 Michael Hanselmann
    except Exception, err:
215 685d3b42 Michael Hanselmann
      raise self.error("Failed to evaluate %r: %s" % (code, err))
216 685d3b42 Michael Hanselmann
217 685d3b42 Michael Hanselmann
    if not result:
218 685d3b42 Michael Hanselmann
      raise self.error("Assertion failed: %s" % (code, ))
219 685d3b42 Michael Hanselmann
220 685d3b42 Michael Hanselmann
    return []
221 685d3b42 Michael Hanselmann
222 685d3b42 Michael Hanselmann
223 95eb4188 Michael Hanselmann
def BuildQueryFields(fields):
224 95eb4188 Michael Hanselmann
  """Build query fields documentation.
225 95eb4188 Michael Hanselmann

226 95eb4188 Michael Hanselmann
  @type fields: dict (field name as key, field details as value)
227 95eb4188 Michael Hanselmann

228 95eb4188 Michael Hanselmann
  """
229 111bf531 Michael Hanselmann
  for (_, (fdef, _, _, _)) in utils.NiceSort(fields.items(),
230 111bf531 Michael Hanselmann
                                             key=operator.itemgetter(0)):
231 95eb4188 Michael Hanselmann
    assert len(fdef.doc.splitlines()) == 1
232 95eb4188 Michael Hanselmann
    yield "``%s``" % fdef.name
233 95eb4188 Michael Hanselmann
    yield "  %s" % fdef.doc
234 95eb4188 Michael Hanselmann
235 95eb4188 Michael Hanselmann
236 95eb4188 Michael Hanselmann
# TODO: Implement Sphinx directive for query fields
237 95eb4188 Michael Hanselmann
238 95eb4188 Michael Hanselmann
239 5f43f393 Michael Hanselmann
def setup(app):
240 5f43f393 Michael Hanselmann
  """Sphinx extension callback.
241 5f43f393 Michael Hanselmann

242 5f43f393 Michael Hanselmann
  """
243 5f43f393 Michael Hanselmann
  app.add_directive("opcode_params", OpcodeParams)
244 685d3b42 Michael Hanselmann
  app.add_directive("pyassert", PythonAssert)
245 685d3b42 Michael Hanselmann
  app.add_role("pyeval", PythonEvalRole)