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 re
import collections
from os import path

from docutils.io import FileOutput
from docutils.frontend import OptionParser
from docutils import nodes
from docutils.utils import new_document
from docutils.parsers.rst import Directive
from docutils.transforms import Transform

from sphinx import addnodes
from sphinx.util import logging
from sphinx.util.nodes import inline_all_toctrees
from sphinx.util.console import bold, darkgreen  # pylint: disable=no-name-in-module
from sphinx.writers.manpage import ManualPageWriter

from sphinx.builders.manpage import ManualPageBuilder

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( # pylint: disable=invalid-name nodes.Invisible, nodes.Element): # ============================================================================== """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): # ============================================================================== u"""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 len(desc_content): # pylint: disable=len-as-condition # 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): 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