Statistics
| Branch: | Tag: | Revision:

root / lib / build / sphinx_ext.py @ c8fca479

History | View | Annotate | Download (17 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 b8669a69 Jose A. Lopes
from ganeti import _constants
50 685d3b42 Michael Hanselmann
from ganeti import constants
51 685d3b42 Michael Hanselmann
from ganeti import compat
52 685d3b42 Michael Hanselmann
from ganeti import errors
53 5f43f393 Michael Hanselmann
from ganeti import utils
54 5f43f393 Michael Hanselmann
from ganeti import opcodes
55 580b1fdd Jose A. Lopes
from ganeti import opcodes_base
56 5f43f393 Michael Hanselmann
from ganeti import ht
57 b4fcee5b Michael Hanselmann
from ganeti import rapi
58 12e0ee0d Michael Hanselmann
from ganeti import luxi
59 46ab58d4 Michael Hanselmann
from ganeti import objects
60 1302ce18 Michael Hanselmann
from ganeti import http
61 c8fca479 Santi Raffa
from ganeti import pathutils
62 b4fcee5b Michael Hanselmann
63 b459a848 Andrea Spadaccini
import ganeti.rapi.rlib2 # pylint: disable=W0611
64 daff2f81 Michael Hanselmann
import ganeti.rapi.connector # pylint: disable=W0611
65 5f43f393 Michael Hanselmann
66 5f43f393 Michael Hanselmann
67 5ce58234 Michael Hanselmann
#: Regular expression for man page names
68 5ce58234 Michael Hanselmann
_MAN_RE = re.compile(r"^(?P<name>[-\w_]+)\((?P<section>\d+)\)$")
69 5ce58234 Michael Hanselmann
70 c6793656 Michael Hanselmann
_TAB_WIDTH = 2
71 c6793656 Michael Hanselmann
72 daff2f81 Michael Hanselmann
RAPI_URI_ENCODE_RE = re.compile("[^_a-z0-9]+", re.I)
73 daff2f81 Michael Hanselmann
74 5ce58234 Michael Hanselmann
75 5ce58234 Michael Hanselmann
class ReSTError(Exception):
76 5ce58234 Michael Hanselmann
  """Custom class for generating errors in Sphinx.
77 5ce58234 Michael Hanselmann

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

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

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

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

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

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

148 5f43f393 Michael Hanselmann
  @type op_id: string
149 5f43f393 Michael Hanselmann
  @param op_id: Opcode ID
150 5f43f393 Michael Hanselmann

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

193 f96c51a0 Michael Hanselmann
  @type op_id: string
194 f96c51a0 Michael Hanselmann
  @param op_id: Opcode ID
195 f96c51a0 Michael Hanselmann

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

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

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

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

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

269 685d3b42 Michael Hanselmann
  The expression's result is included as a literal.
270 685d3b42 Michael Hanselmann

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

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

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

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

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

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

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

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

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

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

411 5ce58234 Michael Hanselmann
  Converts man pages to links if enabled during the build.
412 5ce58234 Michael Hanselmann

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

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

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

471 60b47261 Michael Hanselmann
  @type handler: L{rapi.baserlib.ResourceBase}
472 60b47261 Michael Hanselmann
  @param handler: Handler class
473 60b47261 Michael Hanselmann
  @rtype: list of strings
474 60b47261 Michael Hanselmann

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

485 60b47261 Michael Hanselmann
  @type handler: L{rapi.baserlib.ResourceBase}
486 60b47261 Michael Hanselmann
  @param handler: Handler class
487 60b47261 Michael Hanselmann
  @type method: string
488 60b47261 Michael Hanselmann
  @param method: HTTP method (e.g. L{http.HTTP_GET})
489 60b47261 Michael Hanselmann
  @rtype: string
490 60b47261 Michael Hanselmann

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

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

518 aa82eb62 Michael Hanselmann
    @see: L{rapi.connector.GetHandlers}
519 aa82eb62 Michael Hanselmann

520 aa82eb62 Michael Hanselmann
    """
521 aa82eb62 Michael Hanselmann
    return "".join(map(cls._UriPatternToString, args))
522 aa82eb62 Michael Hanselmann
523 aa82eb62 Michael Hanselmann
  @staticmethod
524 aa82eb62 Michael Hanselmann
  def _UriPatternToString(value):
525 aa82eb62 Michael Hanselmann
    """Converts L{rapi.connector.UriPattern} to strings.
526 aa82eb62 Michael Hanselmann

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

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

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

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

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

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

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