Source code for linuxdoc.manKernelDoc

#!/usr/bin/env python3
# SPDX-License-Identifier: AGPL-3.0-or-later
# pylint: disable=missing-docstring, invalid-name, unnecessary-pass
"""\
kernel-doc-man
~~~~~~~~~~~~~~

Implementation of the ``kernel-doc-man`` builder.

User documentation see :ref:`man-pages`.
"""

# ==============================================================================
# imports
# ==============================================================================

import collections
import re
from os import path

from docutils import nodes
from docutils.frontend import OptionParser
from docutils.io import FileOutput
from docutils.parsers.rst import Directive
from docutils.transforms import Transform
from docutils.utils import new_document
from sphinx import addnodes
from sphinx.builders.manpage import ManualPageBuilder
from sphinx.util import logging
from sphinx.util.console import bold, darkgreen  # pylint: disable=no-name-in-module
from sphinx.util.nodes import inline_all_toctrees
from sphinx.writers.manpage import ManualPageWriter

from .kernel_doc import Container

logger = logging.getLogger(__name__)

# ==============================================================================
# common globals
# ==============================================================================

DEFAULT_MAN_SECT = 9

# The version numbering follows numbering of the specification
# (Documentation/books/kernel-doc-HOWTO).
__version__ = "1.0"


# ==============================================================================
[docs] def setup(app): # ============================================================================== app.add_builder(KernelDocManBuilder) app.add_directive("kernel-doc-man", KernelDocMan) app.add_node( kernel_doc_man, html=(skip_kernel_doc_man, None), latex=(skip_kernel_doc_man, None), texinfo=(skip_kernel_doc_man, None), text=(skip_kernel_doc_man, None), man=(skip_kernel_doc_man, None), ) return dict(version=__version__, parallel_read_safe=True, parallel_write_safe=True)
# ==============================================================================
[docs] class kernel_doc_man(nodes.Invisible, nodes.Element): # pylint: disable=invalid-name # ============================================================================== """Node to mark a section as *manpage*"""
[docs] def skip_kernel_doc_man(self, node): # pylint: disable=unused-argument raise nodes.SkipNode
# ==============================================================================
[docs] class KernelDocMan(Directive): # ============================================================================== required_arguments = 1 optional_arguments = 0
[docs] def run(self): man_node = kernel_doc_man() man_node["manpage"] = self.arguments[0] return [man_node]
# ==============================================================================
[docs] class Section2Manpage(Transform): # ============================================================================== """Transforms a *section* tree into an *manpage* tree. The structural layout of a man-page differs from the one produced, by the kernel-doc parser. The kernel-doc parser produce reST which fits to *normal* documentation, e.g. the declaration of a function in reST is like. .. code-block:: rst user_function ============= .. c:function:: int user_function(int a) The *purpose* description. :param int a: Parameter a description Description =========== lorem ipsum .. Return ====== Returns first argument On the other side, in man-pages it is common (see ``man man-pages``) to print the *purpose* line in the "NAME" section, function's prototype in the "SYNOPSIS" section and the parameter description in the "OPTIONS" section:: NAME user_function -- The *purpose* description. SYNOPSIS int user_function(int a) OPTIONS a DESCRIPTION lorem ipsum RETURN VALUE Returns first argument """ # The common section order is: manTitles = [ (re.compile(r"^SYNOPSIS|^DEFINITION", flags=re.I), "SYNOPSIS"), (re.compile(r"^CONFIG", flags=re.I), "CONFIGURATION"), (re.compile(r"^DESCR", flags=re.I), "DESCRIPTION"), (re.compile(r"^OPTION", flags=re.I), "OPTIONS"), (re.compile(r"^EXIT", flags=re.I), "EXIT STATUS"), (re.compile(r"^RETURN", flags=re.I), "RETURN VALUE"), (re.compile(r"^ERROR", flags=re.I), "ERRORS"), (re.compile(r"^ENVIRON", flags=re.I), "ENVIRONMENT"), (re.compile(r"^FILE", flags=re.I), "FILES"), (re.compile(r"^VER", flags=re.I), "VERSIONS"), (re.compile(r"^ATTR", flags=re.I), "ATTRIBUTES"), (re.compile(r"^CONFOR", flags=re.I), "CONFORMING TO"), (re.compile(r"^NOTE", flags=re.I), "NOTES"), (re.compile(r"^BUG", flags=re.I), "BUGS"), (re.compile(r"^EXAMPLE", flags=re.I), "EXAMPLE"), (re.compile(r"^SEE", flags=re.I), "SEE ALSO"), ] manTitleOrder = [t for r, t in manTitles]
[docs] @classmethod def sec2man_get_first_child(cls, subtree, *classes): for _c in classes: if subtree is None: break idx = subtree.first_child_matching_class(_c) if idx is None: subtree = None break subtree = subtree[idx] return subtree
[docs] def strip_man_info(self): section = self.document[0] man_info = Container(authors=[]) man_node = self.sec2man_get_first_child(section, kernel_doc_man) name, sect = (man_node["manpage"].split(".", -1) + [DEFAULT_MAN_SECT])[:2] man_info["manpage"] = name man_info["mansect"] = sect # strip field list field_list = self.sec2man_get_first_child(section, nodes.field_list) if field_list: field_list.parent.remove(field_list) for field in field_list: name = field[0].astext().lower() value = field[1].astext() man_info[name] = man_info.get(name, []) + [ value, ] # normalize authors for auth, adr in zip( man_info.get("author", []), man_info.get("address", []) ): man_info["authors"].append("%s <%s>" % (auth, adr)) # strip *purpose* desc_content = self.sec2man_get_first_child( section, addnodes.desc, addnodes.desc_content ) if not desc_content or not desc_content: # missing initial short description in kernel-doc comment man_info.subtitle = "" else: man_info.subtitle = desc_content[0].astext() del desc_content[0] # remove section title old_title = self.sec2man_get_first_child(section, nodes.title) old_title.parent.remove(old_title) # gather type of the declaration decl_type = self.sec2man_get_first_child( section, addnodes.desc, addnodes.desc_signature, addnodes.desc_type ) if decl_type is not None: decl_type = decl_type.astext().strip() man_info.decl_type = decl_type # complete infos man_info.title = man_info["manpage"] man_info.section = man_info["mansect"] return man_info
[docs] def isolate_sections(self, sec_by_title): section = self.document[0] while True: sect = self.sec2man_get_first_child(section, nodes.section) if not sect: break sec_parent = sect.parent target_idx = sect.parent.index(sect) - 1 sect.parent.remove(sect) if isinstance(sec_parent[target_idx], nodes.target): # drop target / is useless in man-pages del sec_parent[target_idx] title = sect[0].astext().upper() for r, man_title in self.manTitles: # pylint: disable=invalid-name if r.search(title): title = man_title sect[0].replace_self(nodes.title(text=title)) break # we dont know if there are sections with the same title sec_by_title[title] = sec_by_title.get(title, []) + [sect] return sec_by_title
[docs] def isolate_synopsis(self, sec_by_title): synopsis = None c_desc = self.sec2man_get_first_child(self.document[0], addnodes.desc) if c_desc is not None: c_desc.parent.remove(c_desc) synopsis = nodes.section() synopsis += nodes.title(text="synopsis") synopsis += c_desc sec_by_title["SYNOPSIS"] = sec_by_title.get("SYNOPSIS", []) + [synopsis] return sec_by_title
[docs] def apply(self, **kwargs): self.document.man_info = self.strip_man_info() sec_by_title = collections.OrderedDict() self.isolate_sections(sec_by_title) # On struct, enum, union, typedef, the SYNOPSIS is taken from the # DEFINITION section. if self.document.man_info.decl_type not in [ "struct", "enum", "union", "typedef", ]: self.isolate_synopsis(sec_by_title) for sec_name in self.manTitleOrder: sec_list = sec_by_title.pop(sec_name, []) self.document[0] += sec_list for sec_list in sec_by_title.values(): self.document[0] += sec_list
# ==============================================================================
[docs] class KernelDocManBuilder(ManualPageBuilder): # ============================================================================== """ Builds groff output in manual page format. """ name = "kernel-doc-man" format = "man" supported_image_types = []
[docs] def init(self): pass
[docs] def is_manpage(self, node): if isinstance(node, nodes.section): return bool( Section2Manpage.sec2man_get_first_child(node, kernel_doc_man) is not None ) return False
[docs] def prepare_writing(self, docnames): """A place where you can add logic before :meth:`write_doc` is run""" pass
[docs] def write_doc(self, docname, doctree): """Where you actually write something to the filesystem.""" pass
[docs] def get_partial_document(self, children): doc_tree = new_document("<output>") doc_tree += children return doc_tree
[docs] def write(self, *ignored): # pylint: disable=overridden-final-method if self.config.man_pages: # build manpages from config.man_pages as usual ManualPageBuilder.write(self, *ignored) logger.info( bold("scan master tree for kernel-doc man-pages ... ") + darkgreen("{"), nonl=True, ) master_tree = self.env.get_doctree(self.config.master_doc) master_tree = inline_all_toctrees( self, set(), self.config.master_doc, master_tree, darkgreen, [self.config.master_doc], ) logger.info(darkgreen("}")) man_nodes = master_tree.traverse(condition=self.is_manpage) if not man_nodes and not self.config.man_pages: logger.warning( 'no "man_pages" config value nor manual section found; no manual pages ' "will be written" ) return logger.info(bold("START writing man pages ... "), nonl=True) for man_parent in man_nodes: doc_tree = self.get_partial_document(man_parent) Section2Manpage(doc_tree).apply() if not doc_tree.man_info["authors"] and self.config.author: doc_tree.man_info["authors"].append(self.config.author) doc_writer = ManualPageWriter(self) doc_settings = OptionParser( defaults=self.env.settings, components=(doc_writer,), read_config_files=True, ).get_default_values() doc_settings.__dict__.update(doc_tree.man_info) doc_tree.settings = doc_settings targetname = "%s.%s" % (doc_tree.man_info.title, doc_tree.man_info.section) if doc_tree.man_info.decl_type in ["struct", "enum", "union", "typedef"]: targetname = "%s_%s" % (doc_tree.man_info.decl_type, targetname) destination = FileOutput( destination_path=path.join(self.outdir, targetname), encoding="utf-8" ) logger.info(darkgreen(targetname) + " ", nonl=True) self.env.resolve_references(doc_tree, doc_tree.man_info.manpage, self) # remove pending_xref nodes for pendingnode in doc_tree.traverse(addnodes.pending_xref): pendingnode.replace_self(pendingnode.children) doc_writer.write(doc_tree, destination) logger.info("END writing man pages.")
[docs] def finish(self): pass