Statistics
| Branch: | Tag: | Revision:

root / lib / build / sphinx_ext.py @ 685d3b42

History | View | Annotate | Download (5.6 kB)

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
import operator
27
from cStringIO import StringIO
28

    
29
import docutils.statemachine
30
import docutils.nodes
31
import docutils.utils
32

    
33
import sphinx.errors
34
import sphinx.util.compat
35

    
36
from ganeti import constants
37
from ganeti import compat
38
from ganeti import errors
39
from ganeti import utils
40
from ganeti import opcodes
41
from ganeti import ht
42

    
43

    
44
COMMON_PARAM_NAMES = map(operator.itemgetter(0), opcodes.OpCode.OP_PARAMS)
45

    
46
#: Namespace for evaluating expressions
47
EVAL_NS = dict(compat=compat, constants=constants, utils=utils, errors=errors)
48

    
49

    
50
class OpcodeError(sphinx.errors.SphinxError):
51
  category = "Opcode error"
52

    
53

    
54
def _SplitOption(text):
55
  """Split simple option list.
56

57
  @type text: string
58
  @param text: Options, e.g. "foo, bar, baz"
59

60
  """
61
  return [i.strip(",").strip() for i in text.split()]
62

    
63

    
64
def _ParseAlias(text):
65
  """Parse simple assignment option.
66

67
  @type text: string
68
  @param text: Assignments, e.g. "foo=bar, hello=world"
69
  @rtype: dict
70

71
  """
72
  result = {}
73

    
74
  for part in _SplitOption(text):
75
    if "=" not in part:
76
      raise OpcodeError("Invalid option format, missing equal sign")
77

    
78
    (name, value) = part.split("=", 1)
79

    
80
    result[name.strip()] = value.strip()
81

    
82
  return result
83

    
84

    
85
def _BuildOpcodeParams(op_id, include, exclude, alias):
86
  """Build opcode parameter documentation.
87

88
  @type op_id: string
89
  @param op_id: Opcode ID
90

91
  """
92
  op_cls = opcodes.OP_MAPPING[op_id]
93

    
94
  params_with_alias = \
95
    utils.NiceSort([(alias.get(name, name), name, default, test, doc)
96
                    for (name, default, test, doc) in op_cls.GetAllParams()],
97
                   key=operator.itemgetter(0))
98

    
99
  for (rapi_name, name, default, test, doc) in params_with_alias:
100
    # Hide common parameters if not explicitely included
101
    if (name in COMMON_PARAM_NAMES and
102
        (not include or name not in include)):
103
      continue
104
    if exclude is not None and name in exclude:
105
      continue
106
    if include is not None and name not in include:
107
      continue
108

    
109
    has_default = default is not ht.NoDefault
110
    has_test = not (test is None or test is ht.NoType)
111

    
112
    buf = StringIO()
113
    buf.write("``%s``" % rapi_name)
114
    if has_default or has_test:
115
      buf.write(" (")
116
      if has_default:
117
        buf.write("defaults to ``%s``" % default)
118
        if has_test:
119
          buf.write(", ")
120
      if has_test:
121
        buf.write("must be ``%s``" % test)
122
      buf.write(")")
123
    yield buf.getvalue()
124

    
125
    # Add text
126
    for line in doc.splitlines():
127
      yield "  %s" % line
128

    
129

    
130
class OpcodeParams(sphinx.util.compat.Directive):
131
  """Custom directive for opcode parameters.
132

133
  See also <http://docutils.sourceforge.net/docs/howto/rst-directives.html>.
134

135
  """
136
  has_content = False
137
  required_arguments = 1
138
  optional_arguments = 0
139
  final_argument_whitespace = False
140
  option_spec = dict(include=_SplitOption, exclude=_SplitOption,
141
                     alias=_ParseAlias)
142

    
143
  def run(self):
144
    op_id = self.arguments[0]
145
    include = self.options.get("include", None)
146
    exclude = self.options.get("exclude", None)
147
    alias = self.options.get("alias", {})
148

    
149
    tab_width = 2
150
    path = op_id
151
    include_text = "\n".join(_BuildOpcodeParams(op_id, include, exclude, alias))
152

    
153
    # Inject into state machine
154
    include_lines = docutils.statemachine.string2lines(include_text, tab_width,
155
                                                       convert_whitespace=1)
156
    self.state_machine.insert_input(include_lines, path)
157

    
158
    return []
159

    
160

    
161
def PythonEvalRole(role, rawtext, text, lineno, inliner,
162
                   options={}, content=[]):
163
  """Custom role to evaluate Python expressions.
164

165
  The expression's result is included as a literal.
166

167
  """
168
  code = docutils.utils.unescape(text, restore_backslashes=True)
169

    
170
  try:
171
    result = eval(code, EVAL_NS)
172
  except Exception, err:
173
    msg = inliner.reporter.error("Failed to evaluate %r: %s" % (code, err),
174
                                 line=lineno)
175
    return ([inliner.problematic(rawtext, rawtext, msg)], [msg])
176

    
177
  node = docutils.nodes.literal("", unicode(result), **options)
178

    
179
  return ([node], [])
180

    
181

    
182
class PythonAssert(sphinx.util.compat.Directive):
183
  """Custom directive for writing assertions.
184

185
  The content must be a valid Python expression. If its result does not
186
  evaluate to C{True}, the assertion fails.
187

188
  """
189
  has_content = True
190
  required_arguments = 0
191
  optional_arguments = 0
192
  final_argument_whitespace = False
193

    
194
  def run(self):
195
    self.assert_has_content()
196

    
197
    code = "\n".join(self.content)
198

    
199
    try:
200
      result = eval(code, EVAL_NS)
201
    except Exception, err:
202
      raise self.error("Failed to evaluate %r: %s" % (code, err))
203

    
204
    if not result:
205
      raise self.error("Assertion failed: %s" % (code, ))
206

    
207
    return []
208

    
209

    
210
def setup(app):
211
  """Sphinx extension callback.
212

213
  """
214
  app.add_directive("opcode_params", OpcodeParams)
215
  app.add_directive("pyassert", PythonAssert)
216
  app.add_role("pyeval", PythonEvalRole)