Statistics
| Branch: | Tag: | Revision:

root / lib / build / sphinx_ext.py @ 56c934da

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 b4fcee5b Michael Hanselmann
62 b459a848 Andrea Spadaccini
import ganeti.rapi.rlib2 # pylint: disable=W0611
63 daff2f81 Michael Hanselmann
import ganeti.rapi.connector # pylint: disable=W0611
64 5f43f393 Michael Hanselmann
65 5f43f393 Michael Hanselmann
66 5ce58234 Michael Hanselmann
#: Regular expression for man page names
67 5ce58234 Michael Hanselmann
_MAN_RE = re.compile(r"^(?P<name>[-\w_]+)\((?P<section>\d+)\)$")
68 5ce58234 Michael Hanselmann
69 c6793656 Michael Hanselmann
_TAB_WIDTH = 2
70 c6793656 Michael Hanselmann
71 daff2f81 Michael Hanselmann
RAPI_URI_ENCODE_RE = re.compile("[^_a-z0-9]+", re.I)
72 daff2f81 Michael Hanselmann
73 5ce58234 Michael Hanselmann
74 5ce58234 Michael Hanselmann
class ReSTError(Exception):
75 5ce58234 Michael Hanselmann
  """Custom class for generating errors in Sphinx.
76 5ce58234 Michael Hanselmann

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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