Statistics
| Branch: | Tag: | Revision:

root / lib / build / sphinx_ext.py @ fb62843c

History | View | Annotate | Download (16.8 kB)

1 5f43f393 Michael Hanselmann
#
2 5f43f393 Michael Hanselmann
#
3 5f43f393 Michael Hanselmann
4 c83c0410 Iustin Pop
# Copyright (C) 2011, 2012, 2013 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 5ce58234 Michael Hanselmann
import re
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 5ce58234 Michael Hanselmann
import docutils.parsers.rst
33 5f43f393 Michael Hanselmann
34 dac59ac5 Michael Hanselmann
import sphinx.errors
35 dac59ac5 Michael Hanselmann
import sphinx.util.compat
36 5ce58234 Michael Hanselmann
import sphinx.roles
37 5ce58234 Michael Hanselmann
import sphinx.addnodes
38 5f43f393 Michael Hanselmann
39 b6d02fa9 Iustin Pop
s_compat = sphinx.util.compat
40 b6d02fa9 Iustin Pop
41 5ce58234 Michael Hanselmann
try:
42 5ce58234 Michael Hanselmann
  # Access to a protected member of a client class
43 5ce58234 Michael Hanselmann
  # pylint: disable=W0212
44 5ce58234 Michael Hanselmann
  orig_manpage_role = docutils.parsers.rst.roles._roles["manpage"]
45 5ce58234 Michael Hanselmann
except (AttributeError, ValueError, KeyError), err:
46 5ce58234 Michael Hanselmann
  # Normally the "manpage" role is registered by sphinx/roles.py
47 5ce58234 Michael Hanselmann
  raise Exception("Can't find reST role named 'manpage': %s" % err)
48 5ce58234 Michael Hanselmann
49 685d3b42 Michael Hanselmann
from ganeti import constants
50 685d3b42 Michael Hanselmann
from ganeti import compat
51 685d3b42 Michael Hanselmann
from ganeti import errors
52 5f43f393 Michael Hanselmann
from ganeti import utils
53 5f43f393 Michael Hanselmann
from ganeti import opcodes
54 5f43f393 Michael Hanselmann
from ganeti import ht
55 b4fcee5b Michael Hanselmann
from ganeti import rapi
56 12e0ee0d Michael Hanselmann
from ganeti import luxi
57 46ab58d4 Michael Hanselmann
from ganeti import objects
58 1302ce18 Michael Hanselmann
from ganeti import http
59 5ce58234 Michael Hanselmann
from ganeti import _autoconf
60 b4fcee5b Michael Hanselmann
61 b459a848 Andrea Spadaccini
import ganeti.rapi.rlib2 # pylint: disable=W0611
62 daff2f81 Michael Hanselmann
import ganeti.rapi.connector # pylint: disable=W0611
63 5f43f393 Michael Hanselmann
64 5f43f393 Michael Hanselmann
65 5ce58234 Michael Hanselmann
#: Regular expression for man page names
66 5ce58234 Michael Hanselmann
_MAN_RE = re.compile(r"^(?P<name>[-\w_]+)\((?P<section>\d+)\)$")
67 5ce58234 Michael Hanselmann
68 c6793656 Michael Hanselmann
_TAB_WIDTH = 2
69 c6793656 Michael Hanselmann
70 daff2f81 Michael Hanselmann
RAPI_URI_ENCODE_RE = re.compile("[^_a-z0-9]+", re.I)
71 daff2f81 Michael Hanselmann
72 5ce58234 Michael Hanselmann
73 5ce58234 Michael Hanselmann
class ReSTError(Exception):
74 5ce58234 Michael Hanselmann
  """Custom class for generating errors in Sphinx.
75 5ce58234 Michael Hanselmann

76 5ce58234 Michael Hanselmann
  """
77 5ce58234 Michael Hanselmann
78 5ce58234 Michael Hanselmann
79 a19a6326 Michael Hanselmann
def _GetCommonParamNames():
80 a19a6326 Michael Hanselmann
  """Builds a list of parameters common to all opcodes.
81 a19a6326 Michael Hanselmann

82 a19a6326 Michael Hanselmann
  """
83 a19a6326 Michael Hanselmann
  names = set(map(compat.fst, opcodes.OpCode.OP_PARAMS))
84 a19a6326 Michael Hanselmann
85 a19a6326 Michael Hanselmann
  # The "depends" attribute should be listed
86 a19a6326 Michael Hanselmann
  names.remove(opcodes.DEPEND_ATTR)
87 a19a6326 Michael Hanselmann
88 a19a6326 Michael Hanselmann
  return names
89 a19a6326 Michael Hanselmann
90 a19a6326 Michael Hanselmann
91 a19a6326 Michael Hanselmann
COMMON_PARAM_NAMES = _GetCommonParamNames()
92 5f43f393 Michael Hanselmann
93 685d3b42 Michael Hanselmann
#: Namespace for evaluating expressions
94 b4fcee5b Michael Hanselmann
EVAL_NS = dict(compat=compat, constants=constants, utils=utils, errors=errors,
95 1302ce18 Michael Hanselmann
               rlib2=rapi.rlib2, luxi=luxi, rapi=rapi, objects=objects,
96 1302ce18 Michael Hanselmann
               http=http)
97 685d3b42 Michael Hanselmann
98 3ac3f5e4 Andrea Spadaccini
# Constants documentation for man pages
99 3ac3f5e4 Andrea Spadaccini
CV_ECODES_DOC = "ecodes"
100 3ac3f5e4 Andrea Spadaccini
# We don't care about the leak of variables _, name and doc here.
101 3ac3f5e4 Andrea Spadaccini
# pylint: disable=W0621
102 3ac3f5e4 Andrea Spadaccini
CV_ECODES_DOC_LIST = [(name, doc) for (_, name, doc) in constants.CV_ALL_ECODES]
103 3ac3f5e4 Andrea Spadaccini
DOCUMENTED_CONSTANTS = {
104 3ac3f5e4 Andrea Spadaccini
  CV_ECODES_DOC: CV_ECODES_DOC_LIST,
105 3ac3f5e4 Andrea Spadaccini
  }
106 3ac3f5e4 Andrea Spadaccini
107 5f43f393 Michael Hanselmann
108 dac59ac5 Michael Hanselmann
class OpcodeError(sphinx.errors.SphinxError):
109 5f43f393 Michael Hanselmann
  category = "Opcode error"
110 5f43f393 Michael Hanselmann
111 5f43f393 Michael Hanselmann
112 5f43f393 Michael Hanselmann
def _SplitOption(text):
113 5f43f393 Michael Hanselmann
  """Split simple option list.
114 5f43f393 Michael Hanselmann

115 5f43f393 Michael Hanselmann
  @type text: string
116 5f43f393 Michael Hanselmann
  @param text: Options, e.g. "foo, bar, baz"
117 5f43f393 Michael Hanselmann

118 5f43f393 Michael Hanselmann
  """
119 5f43f393 Michael Hanselmann
  return [i.strip(",").strip() for i in text.split()]
120 5f43f393 Michael Hanselmann
121 5f43f393 Michael Hanselmann
122 5f43f393 Michael Hanselmann
def _ParseAlias(text):
123 5f43f393 Michael Hanselmann
  """Parse simple assignment option.
124 5f43f393 Michael Hanselmann

125 5f43f393 Michael Hanselmann
  @type text: string
126 5f43f393 Michael Hanselmann
  @param text: Assignments, e.g. "foo=bar, hello=world"
127 5f43f393 Michael Hanselmann
  @rtype: dict
128 5f43f393 Michael Hanselmann

129 5f43f393 Michael Hanselmann
  """
130 5f43f393 Michael Hanselmann
  result = {}
131 5f43f393 Michael Hanselmann
132 5f43f393 Michael Hanselmann
  for part in _SplitOption(text):
133 5f43f393 Michael Hanselmann
    if "=" not in part:
134 5f43f393 Michael Hanselmann
      raise OpcodeError("Invalid option format, missing equal sign")
135 5f43f393 Michael Hanselmann
136 5f43f393 Michael Hanselmann
    (name, value) = part.split("=", 1)
137 5f43f393 Michael Hanselmann
138 5f43f393 Michael Hanselmann
    result[name.strip()] = value.strip()
139 5f43f393 Michael Hanselmann
140 5f43f393 Michael Hanselmann
  return result
141 5f43f393 Michael Hanselmann
142 5f43f393 Michael Hanselmann
143 5f43f393 Michael Hanselmann
def _BuildOpcodeParams(op_id, include, exclude, alias):
144 5f43f393 Michael Hanselmann
  """Build opcode parameter documentation.
145 5f43f393 Michael Hanselmann

146 5f43f393 Michael Hanselmann
  @type op_id: string
147 5f43f393 Michael Hanselmann
  @param op_id: Opcode ID
148 5f43f393 Michael Hanselmann

149 5f43f393 Michael Hanselmann
  """
150 5f43f393 Michael Hanselmann
  op_cls = opcodes.OP_MAPPING[op_id]
151 5f43f393 Michael Hanselmann
152 5f43f393 Michael Hanselmann
  params_with_alias = \
153 5f43f393 Michael Hanselmann
    utils.NiceSort([(alias.get(name, name), name, default, test, doc)
154 5f43f393 Michael Hanselmann
                    for (name, default, test, doc) in op_cls.GetAllParams()],
155 eb62069e Iustin Pop
                   key=compat.fst)
156 5f43f393 Michael Hanselmann
157 5f43f393 Michael Hanselmann
  for (rapi_name, name, default, test, doc) in params_with_alias:
158 2ed0e208 Iustin Pop
    # Hide common parameters if not explicitly included
159 5f43f393 Michael Hanselmann
    if (name in COMMON_PARAM_NAMES and
160 5f43f393 Michael Hanselmann
        (not include or name not in include)):
161 5f43f393 Michael Hanselmann
      continue
162 5f43f393 Michael Hanselmann
    if exclude is not None and name in exclude:
163 5f43f393 Michael Hanselmann
      continue
164 5f43f393 Michael Hanselmann
    if include is not None and name not in include:
165 5f43f393 Michael Hanselmann
      continue
166 5f43f393 Michael Hanselmann
167 5f43f393 Michael Hanselmann
    has_default = default is not ht.NoDefault
168 5f43f393 Michael Hanselmann
    has_test = not (test is None or test is ht.NoType)
169 5f43f393 Michael Hanselmann
170 5f43f393 Michael Hanselmann
    buf = StringIO()
171 c83c0410 Iustin Pop
    buf.write("``%s``" % (rapi_name,))
172 5f43f393 Michael Hanselmann
    if has_default or has_test:
173 5f43f393 Michael Hanselmann
      buf.write(" (")
174 5f43f393 Michael Hanselmann
      if has_default:
175 c83c0410 Iustin Pop
        buf.write("defaults to ``%s``" % (default,))
176 5f43f393 Michael Hanselmann
        if has_test:
177 5f43f393 Michael Hanselmann
          buf.write(", ")
178 5f43f393 Michael Hanselmann
      if has_test:
179 c83c0410 Iustin Pop
        buf.write("must be ``%s``" % (test,))
180 5f43f393 Michael Hanselmann
      buf.write(")")
181 5f43f393 Michael Hanselmann
    yield buf.getvalue()
182 5f43f393 Michael Hanselmann
183 5f43f393 Michael Hanselmann
    # Add text
184 5f43f393 Michael Hanselmann
    for line in doc.splitlines():
185 5f43f393 Michael Hanselmann
      yield "  %s" % line
186 5f43f393 Michael Hanselmann
187 5f43f393 Michael Hanselmann
188 f96c51a0 Michael Hanselmann
def _BuildOpcodeResult(op_id):
189 f96c51a0 Michael Hanselmann
  """Build opcode result documentation.
190 f96c51a0 Michael Hanselmann

191 f96c51a0 Michael Hanselmann
  @type op_id: string
192 f96c51a0 Michael Hanselmann
  @param op_id: Opcode ID
193 f96c51a0 Michael Hanselmann

194 f96c51a0 Michael Hanselmann
  """
195 f96c51a0 Michael Hanselmann
  op_cls = opcodes.OP_MAPPING[op_id]
196 f96c51a0 Michael Hanselmann
197 f96c51a0 Michael Hanselmann
  result_fn = getattr(op_cls, "OP_RESULT", None)
198 f96c51a0 Michael Hanselmann
199 f96c51a0 Michael Hanselmann
  if not result_fn:
200 f96c51a0 Michael Hanselmann
    raise OpcodeError("Opcode '%s' has no result description" % op_id)
201 f96c51a0 Michael Hanselmann
202 f96c51a0 Michael Hanselmann
  return "``%s``" % result_fn
203 f96c51a0 Michael Hanselmann
204 f96c51a0 Michael Hanselmann
205 b6d02fa9 Iustin Pop
class OpcodeParams(s_compat.Directive):
206 5f43f393 Michael Hanselmann
  """Custom directive for opcode parameters.
207 5f43f393 Michael Hanselmann

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

210 5f43f393 Michael Hanselmann
  """
211 5f43f393 Michael Hanselmann
  has_content = False
212 5f43f393 Michael Hanselmann
  required_arguments = 1
213 5f43f393 Michael Hanselmann
  optional_arguments = 0
214 5f43f393 Michael Hanselmann
  final_argument_whitespace = False
215 5f43f393 Michael Hanselmann
  option_spec = dict(include=_SplitOption, exclude=_SplitOption,
216 5f43f393 Michael Hanselmann
                     alias=_ParseAlias)
217 5f43f393 Michael Hanselmann
218 5f43f393 Michael Hanselmann
  def run(self):
219 5f43f393 Michael Hanselmann
    op_id = self.arguments[0]
220 5f43f393 Michael Hanselmann
    include = self.options.get("include", None)
221 5f43f393 Michael Hanselmann
    exclude = self.options.get("exclude", None)
222 5f43f393 Michael Hanselmann
    alias = self.options.get("alias", {})
223 5f43f393 Michael Hanselmann
224 5f43f393 Michael Hanselmann
    path = op_id
225 5f43f393 Michael Hanselmann
    include_text = "\n".join(_BuildOpcodeParams(op_id, include, exclude, alias))
226 5f43f393 Michael Hanselmann
227 5f43f393 Michael Hanselmann
    # Inject into state machine
228 c6793656 Michael Hanselmann
    include_lines = docutils.statemachine.string2lines(include_text, _TAB_WIDTH,
229 dac59ac5 Michael Hanselmann
                                                       convert_whitespace=1)
230 5f43f393 Michael Hanselmann
    self.state_machine.insert_input(include_lines, path)
231 5f43f393 Michael Hanselmann
232 5f43f393 Michael Hanselmann
    return []
233 5f43f393 Michael Hanselmann
234 5f43f393 Michael Hanselmann
235 b6d02fa9 Iustin Pop
class OpcodeResult(s_compat.Directive):
236 f96c51a0 Michael Hanselmann
  """Custom directive for opcode result.
237 f96c51a0 Michael Hanselmann

238 f96c51a0 Michael Hanselmann
  See also <http://docutils.sourceforge.net/docs/howto/rst-directives.html>.
239 f96c51a0 Michael Hanselmann

240 f96c51a0 Michael Hanselmann
  """
241 f96c51a0 Michael Hanselmann
  has_content = False
242 f96c51a0 Michael Hanselmann
  required_arguments = 1
243 f96c51a0 Michael Hanselmann
  optional_arguments = 0
244 f96c51a0 Michael Hanselmann
  final_argument_whitespace = False
245 f96c51a0 Michael Hanselmann
246 f96c51a0 Michael Hanselmann
  def run(self):
247 f96c51a0 Michael Hanselmann
    op_id = self.arguments[0]
248 f96c51a0 Michael Hanselmann
249 f96c51a0 Michael Hanselmann
    path = op_id
250 f96c51a0 Michael Hanselmann
    include_text = _BuildOpcodeResult(op_id)
251 f96c51a0 Michael Hanselmann
252 f96c51a0 Michael Hanselmann
    # Inject into state machine
253 c6793656 Michael Hanselmann
    include_lines = docutils.statemachine.string2lines(include_text, _TAB_WIDTH,
254 f96c51a0 Michael Hanselmann
                                                       convert_whitespace=1)
255 f96c51a0 Michael Hanselmann
    self.state_machine.insert_input(include_lines, path)
256 f96c51a0 Michael Hanselmann
257 f96c51a0 Michael Hanselmann
    return []
258 f96c51a0 Michael Hanselmann
259 f96c51a0 Michael Hanselmann
260 685d3b42 Michael Hanselmann
def PythonEvalRole(role, rawtext, text, lineno, inliner,
261 685d3b42 Michael Hanselmann
                   options={}, content=[]):
262 685d3b42 Michael Hanselmann
  """Custom role to evaluate Python expressions.
263 685d3b42 Michael Hanselmann

264 685d3b42 Michael Hanselmann
  The expression's result is included as a literal.
265 685d3b42 Michael Hanselmann

266 685d3b42 Michael Hanselmann
  """
267 b459a848 Andrea Spadaccini
  # pylint: disable=W0102,W0613,W0142
268 1aa50158 Michael Hanselmann
  # W0102: Dangerous default value as argument
269 1aa50158 Michael Hanselmann
  # W0142: Used * or ** magic
270 1aa50158 Michael Hanselmann
  # W0613: Unused argument
271 1aa50158 Michael Hanselmann
272 685d3b42 Michael Hanselmann
  code = docutils.utils.unescape(text, restore_backslashes=True)
273 685d3b42 Michael Hanselmann
274 685d3b42 Michael Hanselmann
  try:
275 685d3b42 Michael Hanselmann
    result = eval(code, EVAL_NS)
276 b459a848 Andrea Spadaccini
  except Exception, err: # pylint: disable=W0703
277 685d3b42 Michael Hanselmann
    msg = inliner.reporter.error("Failed to evaluate %r: %s" % (code, err),
278 685d3b42 Michael Hanselmann
                                 line=lineno)
279 685d3b42 Michael Hanselmann
    return ([inliner.problematic(rawtext, rawtext, msg)], [msg])
280 685d3b42 Michael Hanselmann
281 685d3b42 Michael Hanselmann
  node = docutils.nodes.literal("", unicode(result), **options)
282 685d3b42 Michael Hanselmann
283 685d3b42 Michael Hanselmann
  return ([node], [])
284 685d3b42 Michael Hanselmann
285 685d3b42 Michael Hanselmann
286 b6d02fa9 Iustin Pop
class PythonAssert(s_compat.Directive):
287 685d3b42 Michael Hanselmann
  """Custom directive for writing assertions.
288 685d3b42 Michael Hanselmann

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

292 685d3b42 Michael Hanselmann
  """
293 685d3b42 Michael Hanselmann
  has_content = True
294 685d3b42 Michael Hanselmann
  required_arguments = 0
295 685d3b42 Michael Hanselmann
  optional_arguments = 0
296 685d3b42 Michael Hanselmann
  final_argument_whitespace = False
297 685d3b42 Michael Hanselmann
298 685d3b42 Michael Hanselmann
  def run(self):
299 cbb86b63 Michael Hanselmann
    # Handle combinations of Sphinx and docutils not providing the wanted method
300 cbb86b63 Michael Hanselmann
    if hasattr(self, "assert_has_content"):
301 cbb86b63 Michael Hanselmann
      self.assert_has_content()
302 cbb86b63 Michael Hanselmann
    else:
303 cbb86b63 Michael Hanselmann
      assert self.content
304 685d3b42 Michael Hanselmann
305 685d3b42 Michael Hanselmann
    code = "\n".join(self.content)
306 685d3b42 Michael Hanselmann
307 685d3b42 Michael Hanselmann
    try:
308 685d3b42 Michael Hanselmann
      result = eval(code, EVAL_NS)
309 685d3b42 Michael Hanselmann
    except Exception, err:
310 685d3b42 Michael Hanselmann
      raise self.error("Failed to evaluate %r: %s" % (code, err))
311 685d3b42 Michael Hanselmann
312 685d3b42 Michael Hanselmann
    if not result:
313 685d3b42 Michael Hanselmann
      raise self.error("Assertion failed: %s" % (code, ))
314 685d3b42 Michael Hanselmann
315 685d3b42 Michael Hanselmann
    return []
316 685d3b42 Michael Hanselmann
317 685d3b42 Michael Hanselmann
318 95eb4188 Michael Hanselmann
def BuildQueryFields(fields):
319 95eb4188 Michael Hanselmann
  """Build query fields documentation.
320 95eb4188 Michael Hanselmann

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

323 95eb4188 Michael Hanselmann
  """
324 12637df5 Andrea Spadaccini
  defs = [(fdef.name, fdef.doc)
325 12637df5 Andrea Spadaccini
           for (_, (fdef, _, _, _)) in utils.NiceSort(fields.items(),
326 12637df5 Andrea Spadaccini
                                                      key=compat.fst)]
327 d505bc48 Iustin Pop
  return BuildValuesDoc(defs)
328 12637df5 Andrea Spadaccini
329 12637df5 Andrea Spadaccini
330 12637df5 Andrea Spadaccini
def BuildValuesDoc(values):
331 12637df5 Andrea Spadaccini
  """Builds documentation for a list of values
332 12637df5 Andrea Spadaccini

333 12637df5 Andrea Spadaccini
  @type values: list of tuples in the form (value, documentation)
334 12637df5 Andrea Spadaccini

335 12637df5 Andrea Spadaccini
  """
336 12637df5 Andrea Spadaccini
  for name, doc in values:
337 12637df5 Andrea Spadaccini
    assert len(doc.splitlines()) == 1
338 c83c0410 Iustin Pop
    yield "``%s``" % (name,)
339 c83c0410 Iustin Pop
    yield "  %s" % (doc,)
340 95eb4188 Michael Hanselmann
341 95eb4188 Michael Hanselmann
342 5ce58234 Michael Hanselmann
def _ManPageNodeClass(*args, **kwargs):
343 5ce58234 Michael Hanselmann
  """Generates a pending XRef like a ":doc:`...`" reference.
344 5ce58234 Michael Hanselmann

345 5ce58234 Michael Hanselmann
  """
346 5ce58234 Michael Hanselmann
  # Type for sphinx/environment.py:BuildEnvironment.resolve_references
347 5ce58234 Michael Hanselmann
  kwargs["reftype"] = "doc"
348 5ce58234 Michael Hanselmann
349 5ce58234 Michael Hanselmann
  # Force custom title
350 5ce58234 Michael Hanselmann
  kwargs["refexplicit"] = True
351 5ce58234 Michael Hanselmann
352 5ce58234 Michael Hanselmann
  return sphinx.addnodes.pending_xref(*args, **kwargs)
353 5ce58234 Michael Hanselmann
354 5ce58234 Michael Hanselmann
355 5ce58234 Michael Hanselmann
class _ManPageXRefRole(sphinx.roles.XRefRole):
356 5ce58234 Michael Hanselmann
  def __init__(self):
357 5ce58234 Michael Hanselmann
    """Initializes this class.
358 5ce58234 Michael Hanselmann

359 5ce58234 Michael Hanselmann
    """
360 5ce58234 Michael Hanselmann
    sphinx.roles.XRefRole.__init__(self, nodeclass=_ManPageNodeClass,
361 5ce58234 Michael Hanselmann
                                   warn_dangling=True)
362 5ce58234 Michael Hanselmann
363 5ce58234 Michael Hanselmann
    assert not hasattr(self, "converted"), \
364 5ce58234 Michael Hanselmann
      "Sphinx base class gained an attribute named 'converted'"
365 5ce58234 Michael Hanselmann
366 5ce58234 Michael Hanselmann
    self.converted = None
367 5ce58234 Michael Hanselmann
368 5ce58234 Michael Hanselmann
  def process_link(self, env, refnode, has_explicit_title, title, target):
369 5ce58234 Michael Hanselmann
    """Specialization for man page links.
370 5ce58234 Michael Hanselmann

371 5ce58234 Michael Hanselmann
    """
372 5ce58234 Michael Hanselmann
    if has_explicit_title:
373 5ce58234 Michael Hanselmann
      raise ReSTError("Setting explicit title is not allowed for man pages")
374 5ce58234 Michael Hanselmann
375 5ce58234 Michael Hanselmann
    # Check format and extract name and section
376 5ce58234 Michael Hanselmann
    m = _MAN_RE.match(title)
377 5ce58234 Michael Hanselmann
    if not m:
378 5ce58234 Michael Hanselmann
      raise ReSTError("Man page reference '%s' does not match regular"
379 5ce58234 Michael Hanselmann
                      " expression '%s'" % (title, _MAN_RE.pattern))
380 5ce58234 Michael Hanselmann
381 5ce58234 Michael Hanselmann
    name = m.group("name")
382 5ce58234 Michael Hanselmann
    section = int(m.group("section"))
383 5ce58234 Michael Hanselmann
384 5ce58234 Michael Hanselmann
    wanted_section = _autoconf.MAN_PAGES.get(name, None)
385 5ce58234 Michael Hanselmann
386 5ce58234 Michael Hanselmann
    if not (wanted_section is None or wanted_section == section):
387 5ce58234 Michael Hanselmann
      raise ReSTError("Referenced man page '%s' has section number %s, but the"
388 5ce58234 Michael Hanselmann
                      " reference uses section %s" %
389 5ce58234 Michael Hanselmann
                      (name, wanted_section, section))
390 5ce58234 Michael Hanselmann
391 5ce58234 Michael Hanselmann
    self.converted = bool(wanted_section is not None and
392 5ce58234 Michael Hanselmann
                          env.app.config.enable_manpages)
393 5ce58234 Michael Hanselmann
394 5ce58234 Michael Hanselmann
    if self.converted:
395 5ce58234 Michael Hanselmann
      # Create link to known man page
396 5ce58234 Michael Hanselmann
      return (title, "man-%s" % name)
397 5ce58234 Michael Hanselmann
    else:
398 5ce58234 Michael Hanselmann
      # No changes
399 5ce58234 Michael Hanselmann
      return (title, target)
400 5ce58234 Michael Hanselmann
401 5ce58234 Michael Hanselmann
402 5ce58234 Michael Hanselmann
def _ManPageRole(typ, rawtext, text, lineno, inliner, # pylint: disable=W0102
403 5ce58234 Michael Hanselmann
                 options={}, content=[]):
404 5ce58234 Michael Hanselmann
  """Custom role for man page references.
405 5ce58234 Michael Hanselmann

406 5ce58234 Michael Hanselmann
  Converts man pages to links if enabled during the build.
407 5ce58234 Michael Hanselmann

408 5ce58234 Michael Hanselmann
  """
409 5ce58234 Michael Hanselmann
  xref = _ManPageXRefRole()
410 5ce58234 Michael Hanselmann
411 5ce58234 Michael Hanselmann
  assert ht.TNone(xref.converted)
412 5ce58234 Michael Hanselmann
413 5ce58234 Michael Hanselmann
  # Check if it's a known man page
414 5ce58234 Michael Hanselmann
  try:
415 5ce58234 Michael Hanselmann
    result = xref(typ, rawtext, text, lineno, inliner,
416 5ce58234 Michael Hanselmann
                  options=options, content=content)
417 5ce58234 Michael Hanselmann
  except ReSTError, err:
418 5ce58234 Michael Hanselmann
    msg = inliner.reporter.error(str(err), line=lineno)
419 5ce58234 Michael Hanselmann
    return ([inliner.problematic(rawtext, rawtext, msg)], [msg])
420 5ce58234 Michael Hanselmann
421 5ce58234 Michael Hanselmann
  assert ht.TBool(xref.converted)
422 5ce58234 Michael Hanselmann
423 5ce58234 Michael Hanselmann
  # Return if the conversion was successful (i.e. the man page was known and
424 5ce58234 Michael Hanselmann
  # conversion was enabled)
425 5ce58234 Michael Hanselmann
  if xref.converted:
426 5ce58234 Michael Hanselmann
    return result
427 5ce58234 Michael Hanselmann
428 5ce58234 Michael Hanselmann
  # Fallback if man page links are disabled or an unknown page is referenced
429 5ce58234 Michael Hanselmann
  return orig_manpage_role(typ, rawtext, text, lineno, inliner,
430 5ce58234 Michael Hanselmann
                           options=options, content=content)
431 95eb4188 Michael Hanselmann
432 95eb4188 Michael Hanselmann
433 daff2f81 Michael Hanselmann
def _EncodeRapiResourceLink(method, uri):
434 daff2f81 Michael Hanselmann
  """Encodes a RAPI resource URI for use as a link target.
435 daff2f81 Michael Hanselmann

436 daff2f81 Michael Hanselmann
  """
437 daff2f81 Michael Hanselmann
  parts = [RAPI_URI_ENCODE_RE.sub("-", uri.lower()).strip("-")]
438 daff2f81 Michael Hanselmann
439 daff2f81 Michael Hanselmann
  if method is not None:
440 daff2f81 Michael Hanselmann
    parts.append(method.lower())
441 daff2f81 Michael Hanselmann
442 daff2f81 Michael Hanselmann
  return "rapi-res-%s" % "+".join(filter(None, parts))
443 daff2f81 Michael Hanselmann
444 daff2f81 Michael Hanselmann
445 daff2f81 Michael Hanselmann
def _MakeRapiResourceLink(method, uri):
446 daff2f81 Michael Hanselmann
  """Generates link target name for RAPI resource.
447 daff2f81 Michael Hanselmann

448 daff2f81 Michael Hanselmann
  """
449 daff2f81 Michael Hanselmann
  if uri in ["/", "/2"]:
450 daff2f81 Michael Hanselmann
    # Don't link these
451 daff2f81 Michael Hanselmann
    return None
452 daff2f81 Michael Hanselmann
453 daff2f81 Michael Hanselmann
  elif uri == "/version":
454 daff2f81 Michael Hanselmann
    return _EncodeRapiResourceLink(method, uri)
455 daff2f81 Michael Hanselmann
456 daff2f81 Michael Hanselmann
  elif uri.startswith("/2/"):
457 daff2f81 Michael Hanselmann
    return _EncodeRapiResourceLink(method, uri[len("/2/"):])
458 daff2f81 Michael Hanselmann
459 daff2f81 Michael Hanselmann
  else:
460 daff2f81 Michael Hanselmann
    raise ReSTError("Unhandled URI '%s'" % uri)
461 daff2f81 Michael Hanselmann
462 daff2f81 Michael Hanselmann
463 60b47261 Michael Hanselmann
def _GetHandlerMethods(handler):
464 60b47261 Michael Hanselmann
  """Returns list of HTTP methods supported by handler class.
465 60b47261 Michael Hanselmann

466 60b47261 Michael Hanselmann
  @type handler: L{rapi.baserlib.ResourceBase}
467 60b47261 Michael Hanselmann
  @param handler: Handler class
468 60b47261 Michael Hanselmann
  @rtype: list of strings
469 60b47261 Michael Hanselmann

470 60b47261 Michael Hanselmann
  """
471 60b47261 Michael Hanselmann
  return sorted(method
472 60b47261 Michael Hanselmann
                for (method, op_attr, _, _) in rapi.baserlib.OPCODE_ATTRS
473 60b47261 Michael Hanselmann
                # Only if handler supports method
474 60b47261 Michael Hanselmann
                if hasattr(handler, method) or hasattr(handler, op_attr))
475 60b47261 Michael Hanselmann
476 60b47261 Michael Hanselmann
477 60b47261 Michael Hanselmann
def _DescribeHandlerAccess(handler, method):
478 60b47261 Michael Hanselmann
  """Returns textual description of required RAPI permissions.
479 60b47261 Michael Hanselmann

480 60b47261 Michael Hanselmann
  @type handler: L{rapi.baserlib.ResourceBase}
481 60b47261 Michael Hanselmann
  @param handler: Handler class
482 60b47261 Michael Hanselmann
  @type method: string
483 60b47261 Michael Hanselmann
  @param method: HTTP method (e.g. L{http.HTTP_GET})
484 60b47261 Michael Hanselmann
  @rtype: string
485 60b47261 Michael Hanselmann

486 60b47261 Michael Hanselmann
  """
487 60b47261 Michael Hanselmann
  access = rapi.baserlib.GetHandlerAccess(handler, method)
488 60b47261 Michael Hanselmann
489 60b47261 Michael Hanselmann
  if access:
490 60b47261 Michael Hanselmann
    return utils.CommaJoin(sorted(access))
491 60b47261 Michael Hanselmann
  else:
492 60b47261 Michael Hanselmann
    return "*(none)*"
493 60b47261 Michael Hanselmann
494 60b47261 Michael Hanselmann
495 aa82eb62 Michael Hanselmann
class _RapiHandlersForDocsHelper(object):
496 aa82eb62 Michael Hanselmann
  @classmethod
497 aa82eb62 Michael Hanselmann
  def Build(cls):
498 aa82eb62 Michael Hanselmann
    """Returns dictionary of resource handlers.
499 aa82eb62 Michael Hanselmann

500 aa82eb62 Michael Hanselmann
    """
501 aa82eb62 Michael Hanselmann
    resources = \
502 aa82eb62 Michael Hanselmann
      rapi.connector.GetHandlers("[node_name]", "[instance_name]",
503 aa82eb62 Michael Hanselmann
                                 "[group_name]", "[network_name]", "[job_id]",
504 aa82eb62 Michael Hanselmann
                                 "[disk_index]", "[resource]",
505 aa82eb62 Michael Hanselmann
                                 translate=cls._TranslateResourceUri)
506 aa82eb62 Michael Hanselmann
507 aa82eb62 Michael Hanselmann
    return resources
508 aa82eb62 Michael Hanselmann
509 aa82eb62 Michael Hanselmann
  @classmethod
510 aa82eb62 Michael Hanselmann
  def _TranslateResourceUri(cls, *args):
511 aa82eb62 Michael Hanselmann
    """Translates a resource URI for use in documentation.
512 aa82eb62 Michael Hanselmann

513 aa82eb62 Michael Hanselmann
    @see: L{rapi.connector.GetHandlers}
514 aa82eb62 Michael Hanselmann

515 aa82eb62 Michael Hanselmann
    """
516 aa82eb62 Michael Hanselmann
    return "".join(map(cls._UriPatternToString, args))
517 aa82eb62 Michael Hanselmann
518 aa82eb62 Michael Hanselmann
  @staticmethod
519 aa82eb62 Michael Hanselmann
  def _UriPatternToString(value):
520 aa82eb62 Michael Hanselmann
    """Converts L{rapi.connector.UriPattern} to strings.
521 aa82eb62 Michael Hanselmann

522 aa82eb62 Michael Hanselmann
    """
523 aa82eb62 Michael Hanselmann
    if isinstance(value, rapi.connector.UriPattern):
524 aa82eb62 Michael Hanselmann
      return value.content
525 aa82eb62 Michael Hanselmann
    else:
526 aa82eb62 Michael Hanselmann
      return value
527 aa82eb62 Michael Hanselmann
528 aa82eb62 Michael Hanselmann
529 aa82eb62 Michael Hanselmann
_RAPI_RESOURCES_FOR_DOCS = _RapiHandlersForDocsHelper.Build()
530 aa82eb62 Michael Hanselmann
531 aa82eb62 Michael Hanselmann
532 daff2f81 Michael Hanselmann
def _BuildRapiAccessTable(res):
533 daff2f81 Michael Hanselmann
  """Build a table with access permissions needed for all RAPI resources.
534 daff2f81 Michael Hanselmann

535 daff2f81 Michael Hanselmann
  """
536 daff2f81 Michael Hanselmann
  for (uri, handler) in utils.NiceSort(res.items(), key=compat.fst):
537 daff2f81 Michael Hanselmann
    reslink = _MakeRapiResourceLink(None, uri)
538 daff2f81 Michael Hanselmann
    if not reslink:
539 daff2f81 Michael Hanselmann
      # No link was generated
540 daff2f81 Michael Hanselmann
      continue
541 daff2f81 Michael Hanselmann
542 daff2f81 Michael Hanselmann
    yield ":ref:`%s <%s>`" % (uri, reslink)
543 daff2f81 Michael Hanselmann
544 60b47261 Michael Hanselmann
    for method in _GetHandlerMethods(handler):
545 daff2f81 Michael Hanselmann
      yield ("  | :ref:`%s <%s>`: %s" %
546 60b47261 Michael Hanselmann
             (method, _MakeRapiResourceLink(method, uri),
547 60b47261 Michael Hanselmann
              _DescribeHandlerAccess(handler, method)))
548 daff2f81 Michael Hanselmann
549 daff2f81 Michael Hanselmann
550 daff2f81 Michael Hanselmann
class RapiAccessTable(s_compat.Directive):
551 daff2f81 Michael Hanselmann
  """Custom directive to generate table of all RAPI resources.
552 daff2f81 Michael Hanselmann

553 daff2f81 Michael Hanselmann
  See also <http://docutils.sourceforge.net/docs/howto/rst-directives.html>.
554 daff2f81 Michael Hanselmann

555 daff2f81 Michael Hanselmann
  """
556 daff2f81 Michael Hanselmann
  has_content = False
557 daff2f81 Michael Hanselmann
  required_arguments = 0
558 daff2f81 Michael Hanselmann
  optional_arguments = 0
559 daff2f81 Michael Hanselmann
  final_argument_whitespace = False
560 daff2f81 Michael Hanselmann
  option_spec = {}
561 daff2f81 Michael Hanselmann
562 daff2f81 Michael Hanselmann
  def run(self):
563 aa82eb62 Michael Hanselmann
    include_text = "\n".join(_BuildRapiAccessTable(_RAPI_RESOURCES_FOR_DOCS))
564 daff2f81 Michael Hanselmann
565 daff2f81 Michael Hanselmann
    # Inject into state machine
566 daff2f81 Michael Hanselmann
    include_lines = docutils.statemachine.string2lines(include_text, _TAB_WIDTH,
567 daff2f81 Michael Hanselmann
                                                       convert_whitespace=1)
568 daff2f81 Michael Hanselmann
    self.state_machine.insert_input(include_lines, self.__class__.__name__)
569 daff2f81 Michael Hanselmann
570 daff2f81 Michael Hanselmann
    return []
571 daff2f81 Michael Hanselmann
572 daff2f81 Michael Hanselmann
573 d59633a6 Michael Hanselmann
class RapiResourceDetails(s_compat.Directive):
574 d59633a6 Michael Hanselmann
  """Custom directive for RAPI resource details.
575 d59633a6 Michael Hanselmann

576 d59633a6 Michael Hanselmann
  See also <http://docutils.sourceforge.net/docs/howto/rst-directives.html>.
577 d59633a6 Michael Hanselmann

578 d59633a6 Michael Hanselmann
  """
579 d59633a6 Michael Hanselmann
  has_content = False
580 d59633a6 Michael Hanselmann
  required_arguments = 1
581 d59633a6 Michael Hanselmann
  optional_arguments = 0
582 d59633a6 Michael Hanselmann
  final_argument_whitespace = False
583 d59633a6 Michael Hanselmann
584 d59633a6 Michael Hanselmann
  def run(self):
585 d59633a6 Michael Hanselmann
    uri = self.arguments[0]
586 d59633a6 Michael Hanselmann
587 d59633a6 Michael Hanselmann
    try:
588 d59633a6 Michael Hanselmann
      handler = _RAPI_RESOURCES_FOR_DOCS[uri]
589 d59633a6 Michael Hanselmann
    except KeyError:
590 d59633a6 Michael Hanselmann
      raise self.error("Unknown resource URI '%s'" % uri)
591 d59633a6 Michael Hanselmann
592 d59633a6 Michael Hanselmann
    lines = [
593 d59633a6 Michael Hanselmann
      ".. list-table::",
594 d59633a6 Michael Hanselmann
      "   :widths: 1 4",
595 d59633a6 Michael Hanselmann
      "   :header-rows: 1",
596 d59633a6 Michael Hanselmann
      "",
597 d59633a6 Michael Hanselmann
      "   * - Method",
598 d59633a6 Michael Hanselmann
      "     - :ref:`Required permissions <rapi-users>`",
599 d59633a6 Michael Hanselmann
      ]
600 d59633a6 Michael Hanselmann
601 d59633a6 Michael Hanselmann
    for method in _GetHandlerMethods(handler):
602 d59633a6 Michael Hanselmann
      lines.extend([
603 d59633a6 Michael Hanselmann
        "   * - :ref:`%s <%s>`" % (method, _MakeRapiResourceLink(method, uri)),
604 d59633a6 Michael Hanselmann
        "     - %s" % _DescribeHandlerAccess(handler, method),
605 d59633a6 Michael Hanselmann
        ])
606 d59633a6 Michael Hanselmann
607 d59633a6 Michael Hanselmann
    # Inject into state machine
608 d59633a6 Michael Hanselmann
    include_lines = \
609 d59633a6 Michael Hanselmann
      docutils.statemachine.string2lines("\n".join(lines), _TAB_WIDTH,
610 d59633a6 Michael Hanselmann
                                         convert_whitespace=1)
611 d59633a6 Michael Hanselmann
    self.state_machine.insert_input(include_lines, self.__class__.__name__)
612 d59633a6 Michael Hanselmann
613 d59633a6 Michael Hanselmann
    return []
614 d59633a6 Michael Hanselmann
615 d59633a6 Michael Hanselmann
616 5f43f393 Michael Hanselmann
def setup(app):
617 5f43f393 Michael Hanselmann
  """Sphinx extension callback.
618 5f43f393 Michael Hanselmann

619 5f43f393 Michael Hanselmann
  """
620 5ce58234 Michael Hanselmann
  # TODO: Implement Sphinx directive for query fields
621 5f43f393 Michael Hanselmann
  app.add_directive("opcode_params", OpcodeParams)
622 f96c51a0 Michael Hanselmann
  app.add_directive("opcode_result", OpcodeResult)
623 685d3b42 Michael Hanselmann
  app.add_directive("pyassert", PythonAssert)
624 685d3b42 Michael Hanselmann
  app.add_role("pyeval", PythonEvalRole)
625 daff2f81 Michael Hanselmann
  app.add_directive("rapi_access_table", RapiAccessTable)
626 d59633a6 Michael Hanselmann
  app.add_directive("rapi_resource_details", RapiResourceDetails)
627 5ce58234 Michael Hanselmann
628 5ce58234 Michael Hanselmann
  app.add_config_value("enable_manpages", False, True)
629 5ce58234 Michael Hanselmann
  app.add_role("manpage", _ManPageRole)