+def BuildQueryFields(fields):
+ """Build query fields documentation.
+
+ @type fields: dict (field name as key, field details as value)
+
+ """
+ defs = [(fdef.name, fdef.doc)
+ for (_, (fdef, _, _, _)) in utils.NiceSort(fields.items(),
+ key=compat.fst)]
+ return BuildValuesDoc(defs)
+
+
+def BuildValuesDoc(values):
+ """Builds documentation for a list of values
+
+ @type values: list of tuples in the form (value, documentation)
+
+ """
+ for name, doc in values:
+ assert len(doc.splitlines()) == 1
+ yield "``%s``" % (name,)
+ yield " %s" % (doc,)
+
+
+def _ManPageNodeClass(*args, **kwargs):
+ """Generates a pending XRef like a ":doc:`...`" reference.
+
+ """
+ # Type for sphinx/environment.py:BuildEnvironment.resolve_references
+ kwargs["reftype"] = "doc"
+
+ # Force custom title
+ kwargs["refexplicit"] = True
+
+ return sphinx.addnodes.pending_xref(*args, **kwargs)
+
+
+class _ManPageXRefRole(sphinx.roles.XRefRole):
+ def __init__(self):
+ """Initializes this class.
+
+ """
+ sphinx.roles.XRefRole.__init__(self, nodeclass=_ManPageNodeClass,
+ warn_dangling=True)
+
+ assert not hasattr(self, "converted"), \
+ "Sphinx base class gained an attribute named 'converted'"
+
+ self.converted = None
+
+ def process_link(self, env, refnode, has_explicit_title, title, target):
+ """Specialization for man page links.
+
+ """
+ if has_explicit_title:
+ raise ReSTError("Setting explicit title is not allowed for man pages")
+
+ # Check format and extract name and section
+ m = _MAN_RE.match(title)
+ if not m:
+ raise ReSTError("Man page reference '%s' does not match regular"
+ " expression '%s'" % (title, _MAN_RE.pattern))
+
+ name = m.group("name")
+ section = int(m.group("section"))
+
+ wanted_section = _autoconf.MAN_PAGES.get(name, None)
+
+ if not (wanted_section is None or wanted_section == section):
+ raise ReSTError("Referenced man page '%s' has section number %s, but the"
+ " reference uses section %s" %
+ (name, wanted_section, section))
+
+ self.converted = bool(wanted_section is not None and
+ env.app.config.enable_manpages)
+
+ if self.converted:
+ # Create link to known man page
+ return (title, "man-%s" % name)
+ else:
+ # No changes
+ return (title, target)
+
+
+def _ManPageRole(typ, rawtext, text, lineno, inliner, # pylint: disable=W0102
+ options={}, content=[]):
+ """Custom role for man page references.
+
+ Converts man pages to links if enabled during the build.
+
+ """
+ xref = _ManPageXRefRole()
+
+ assert ht.TNone(xref.converted)
+
+ # Check if it's a known man page
+ try:
+ result = xref(typ, rawtext, text, lineno, inliner,
+ options=options, content=content)
+ except ReSTError, err:
+ msg = inliner.reporter.error(str(err), line=lineno)
+ return ([inliner.problematic(rawtext, rawtext, msg)], [msg])
+
+ assert ht.TBool(xref.converted)
+
+ # Return if the conversion was successful (i.e. the man page was known and
+ # conversion was enabled)
+ if xref.converted:
+ return result
+
+ # Fallback if man page links are disabled or an unknown page is referenced
+ return orig_manpage_role(typ, rawtext, text, lineno, inliner,
+ options=options, content=content)
+
+
+def _EncodeRapiResourceLink(method, uri):
+ """Encodes a RAPI resource URI for use as a link target.
+
+ """
+ parts = [RAPI_URI_ENCODE_RE.sub("-", uri.lower()).strip("-")]
+
+ if method is not None:
+ parts.append(method.lower())
+
+ return "rapi-res-%s" % "+".join(filter(None, parts))
+
+
+def _MakeRapiResourceLink(method, uri):
+ """Generates link target name for RAPI resource.
+
+ """
+ if uri in ["/", "/2"]:
+ # Don't link these
+ return None
+
+ elif uri == "/version":
+ return _EncodeRapiResourceLink(method, uri)
+
+ elif uri.startswith("/2/"):
+ return _EncodeRapiResourceLink(method, uri[len("/2/"):])
+
+ else:
+ raise ReSTError("Unhandled URI '%s'" % uri)
+
+
+def _GetHandlerMethods(handler):
+ """Returns list of HTTP methods supported by handler class.
+
+ @type handler: L{rapi.baserlib.ResourceBase}
+ @param handler: Handler class
+ @rtype: list of strings
+
+ """
+ return sorted(method
+ for (method, op_attr, _, _) in rapi.baserlib.OPCODE_ATTRS
+ # Only if handler supports method
+ if hasattr(handler, method) or hasattr(handler, op_attr))
+
+
+def _DescribeHandlerAccess(handler, method):
+ """Returns textual description of required RAPI permissions.
+
+ @type handler: L{rapi.baserlib.ResourceBase}
+ @param handler: Handler class
+ @type method: string
+ @param method: HTTP method (e.g. L{http.HTTP_GET})
+ @rtype: string
+
+ """
+ access = rapi.baserlib.GetHandlerAccess(handler, method)
+
+ if access:
+ return utils.CommaJoin(sorted(access))
+ else:
+ return "*(none)*"
+
+
+class _RapiHandlersForDocsHelper(object):
+ @classmethod
+ def Build(cls):
+ """Returns dictionary of resource handlers.
+
+ """
+ resources = \
+ rapi.connector.GetHandlers("[node_name]", "[instance_name]",
+ "[group_name]", "[network_name]", "[job_id]",
+ "[disk_index]", "[resource]",
+ translate=cls._TranslateResourceUri)
+
+ return resources
+
+ @classmethod
+ def _TranslateResourceUri(cls, *args):
+ """Translates a resource URI for use in documentation.
+
+ @see: L{rapi.connector.GetHandlers}
+
+ """
+ return "".join(map(cls._UriPatternToString, args))
+
+ @staticmethod
+ def _UriPatternToString(value):
+ """Converts L{rapi.connector.UriPattern} to strings.
+
+ """
+ if isinstance(value, rapi.connector.UriPattern):
+ return value.content
+ else:
+ return value
+
+
+_RAPI_RESOURCES_FOR_DOCS = _RapiHandlersForDocsHelper.Build()
+
+
+def _BuildRapiAccessTable(res):
+ """Build a table with access permissions needed for all RAPI resources.
+
+ """
+ for (uri, handler) in utils.NiceSort(res.items(), key=compat.fst):
+ reslink = _MakeRapiResourceLink(None, uri)
+ if not reslink:
+ # No link was generated
+ continue
+
+ yield ":ref:`%s <%s>`" % (uri, reslink)
+
+ for method in _GetHandlerMethods(handler):
+ yield (" | :ref:`%s <%s>`: %s" %
+ (method, _MakeRapiResourceLink(method, uri),
+ _DescribeHandlerAccess(handler, method)))
+
+
+class RapiAccessTable(s_compat.Directive):
+ """Custom directive to generate table of all RAPI resources.
+
+ See also <http://docutils.sourceforge.net/docs/howto/rst-directives.html>.
+
+ """
+ has_content = False
+ required_arguments = 0
+ optional_arguments = 0
+ final_argument_whitespace = False
+ option_spec = {}
+
+ def run(self):
+ include_text = "\n".join(_BuildRapiAccessTable(_RAPI_RESOURCES_FOR_DOCS))
+
+ # Inject into state machine
+ include_lines = docutils.statemachine.string2lines(include_text, _TAB_WIDTH,
+ convert_whitespace=1)
+ self.state_machine.insert_input(include_lines, self.__class__.__name__)
+
+ return []
+
+
+class RapiResourceDetails(s_compat.Directive):
+ """Custom directive for RAPI resource details.
+
+ See also <http://docutils.sourceforge.net/docs/howto/rst-directives.html>.
+
+ """
+ has_content = False
+ required_arguments = 1
+ optional_arguments = 0
+ final_argument_whitespace = False
+
+ def run(self):
+ uri = self.arguments[0]
+
+ try:
+ handler = _RAPI_RESOURCES_FOR_DOCS[uri]
+ except KeyError:
+ raise self.error("Unknown resource URI '%s'" % uri)
+
+ lines = [
+ ".. list-table::",
+ " :widths: 1 4",
+ " :header-rows: 1",
+ "",
+ " * - Method",
+ " - :ref:`Required permissions <rapi-users>`",
+ ]
+
+ for method in _GetHandlerMethods(handler):
+ lines.extend([
+ " * - :ref:`%s <%s>`" % (method, _MakeRapiResourceLink(method, uri)),
+ " - %s" % _DescribeHandlerAccess(handler, method),
+ ])
+
+ # Inject into state machine
+ include_lines = \
+ docutils.statemachine.string2lines("\n".join(lines), _TAB_WIDTH,
+ convert_whitespace=1)
+ self.state_machine.insert_input(include_lines, self.__class__.__name__)
+
+ return []
+
+