880 lines · 26.8 KB · Python
RawDownload
#!/usr/bin/env python3# SPDX-License-Identifier: GPL-2.0# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.## pylint: disable=C0301,R0902,R0911,R0912,R0913,R0914,R0915,R0917 """Classes to implement output filters to print kernel-doc documentation. The implementation uses a virtual base class ``OutputFormat``. Itcontains dispatches to virtual methods, and some code to filterout output messages. The actual implementation is done on one separate class per each typeof output, e.g. ``RestFormat`` and ``ManFormat`` classes. Currently, there are output classes for ReST and man/troff.""" import osimport refrom datetime import datetime from kdoc.kdoc_parser import KernelDoc, type_paramfrom kdoc.kdoc_re import KernRe  function_pointer = KernRe(r"([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)", cache=False) # match expressions used to find embedded type informationtype_constant = KernRe(r"\b``([^\`]+)``\b", cache=False)type_constant2 = KernRe(r"\%([-_*\w]+)", cache=False)type_func = KernRe(r"(\w+)\(\)", cache=False)type_param_ref = KernRe(r"([\!~\*]?)\@(\w*((\.\w+)|(->\w+))*(\.\.\.)?)", cache=False) # Special RST handling for func ptr paramstype_fp_param = KernRe(r"\@(\w+)\(\)", cache=False) # Special RST handling for structs with func ptr paramstype_fp_param2 = KernRe(r"\@(\w+->\S+)\(\)", cache=False) type_env = KernRe(r"(\$\w+)", cache=False)type_enum = KernRe(r"\&(enum\s*([_\w]+))", cache=False)type_struct = KernRe(r"\&(struct\s*([_\w]+))", cache=False)type_typedef = KernRe(r"\&(typedef\s*([_\w]+))", cache=False)type_union = KernRe(r"\&(union\s*([_\w]+))", cache=False)type_member = KernRe(r"\&([_\w]+)(\.|->)([_\w]+)", cache=False)type_fallback = KernRe(r"\&([_\w]+)", cache=False)type_member_func = type_member + KernRe(r"\(\)", cache=False)  class OutputFormat:    """    Base class for OutputFormat. If used as-is, it means that only    warnings will be displayed.    """     # output mode.    OUTPUT_ALL          = 0 #: Output all symbols and doc sections.    OUTPUT_INCLUDE      = 1 #: Output only specified symbols.    OUTPUT_EXPORTED     = 2 #: Output exported symbols.    OUTPUT_INTERNAL     = 3 #: Output non-exported symbols.     #: Highlights to be used in ReST format.    highlights = []     #: Blank line character.    blankline = ""     def __init__(self):        """Declare internal vars and set mode to ``OUTPUT_ALL``."""         self.out_mode = self.OUTPUT_ALL        self.enable_lineno = None        self.nosymbol = {}        self.symbol = None        self.function_table = None        self.config = None        self.no_doc_sections = False         self.data = ""     def set_config(self, config):        """        Setup global config variables used by both parser and output.        """         self.config = config     def set_filter(self, export, internal, symbol, nosymbol, function_table,                   enable_lineno, no_doc_sections):        """        Initialize filter variables according to the requested mode.         Only one choice is valid between export, internal and symbol.         The nosymbol filter can be used on all modes.        """         self.enable_lineno = enable_lineno        self.no_doc_sections = no_doc_sections        self.function_table = function_table         if symbol:            self.out_mode = self.OUTPUT_INCLUDE        elif export:            self.out_mode = self.OUTPUT_EXPORTED        elif internal:            self.out_mode = self.OUTPUT_INTERNAL        else:            self.out_mode = self.OUTPUT_ALL         if nosymbol:            self.nosymbol = set(nosymbol)      def highlight_block(self, block):        """        Apply the RST highlights to a sub-block of text.        """         for r, sub in self.highlights:            block = r.sub(sub, block)         return block     def out_warnings(self, args):        """        Output warnings for identifiers that will be displayed.        """         for log_msg in args.warnings:            self.config.warning(log_msg)     def check_doc(self, name, args):        """Check if DOC should be output."""         if self.no_doc_sections:            return False         if name in self.nosymbol:            return False         if self.out_mode == self.OUTPUT_ALL:            self.out_warnings(args)            return True         if self.out_mode == self.OUTPUT_INCLUDE:            if name in self.function_table:                self.out_warnings(args)                return True         return False     def check_declaration(self, dtype, name, args):        """        Checks if a declaration should be output or not based on the        filtering criteria.        """         if name in self.nosymbol:            return False         if self.out_mode == self.OUTPUT_ALL:            self.out_warnings(args)            return True         if self.out_mode in [self.OUTPUT_INCLUDE, self.OUTPUT_EXPORTED]:            if name in self.function_table:                return True         if self.out_mode == self.OUTPUT_INTERNAL:            if dtype != "function":                self.out_warnings(args)                return True             if name not in self.function_table:                self.out_warnings(args)                return True         return False     def msg(self, fname, name, args):        """        Handles a single entry from kernel-doc parser.        """         self.data = ""         dtype = args.type         if dtype == "doc":            self.out_doc(fname, name, args)            return self.data         if not self.check_declaration(dtype, name, args):            return self.data         if dtype == "function":            self.out_function(fname, name, args)            return self.data         if dtype == "enum":            self.out_enum(fname, name, args)            return self.data         if dtype == "var":            self.out_var(fname, name, args)            return self.data         if dtype == "typedef":            self.out_typedef(fname, name, args)            return self.data         if dtype in ["struct", "union"]:            self.out_struct(fname, name, args)            return self.data         # Warn if some type requires an output logic        self.config.log.warning("doesn't know how to output '%s' block",                                dtype)         return None     # Virtual methods to be overridden by inherited classes    # At the base class, those do nothing.    def set_symbols(self, symbols):        """Get a list of all symbols from kernel_doc."""     def out_doc(self, fname, name, args):        """Outputs a DOC block."""     def out_function(self, fname, name, args):        """Outputs a function."""     def out_enum(self, fname, name, args):        """Outputs an enum."""     def out_var(self, fname, name, args):        """Outputs a variable."""     def out_typedef(self, fname, name, args):        """Outputs a typedef."""     def out_struct(self, fname, name, args):        """Outputs a struct."""  class RestFormat(OutputFormat):    """Consts and functions used by ReST output."""     #: Highlights to be used in ReST format    highlights = [        (type_constant, r"``\1``"),        (type_constant2, r"``\1``"),         # Note: need to escape () to avoid func matching later        (type_member_func, r":c:type:`\1\2\3\\(\\) <\1>`"),        (type_member, r":c:type:`\1\2\3 <\1>`"),        (type_fp_param, r"**\1\\(\\)**"),        (type_fp_param2, r"**\1\\(\\)**"),        (type_func, r"\1()"),        (type_enum, r":c:type:`\1 <\2>`"),        (type_struct, r":c:type:`\1 <\2>`"),        (type_typedef, r":c:type:`\1 <\2>`"),        (type_union, r":c:type:`\1 <\2>`"),         # in rst this can refer to any type        (type_fallback, r":c:type:`\1`"),        (type_param_ref, r"**\1\2**")    ]     blankline = "\n"     #: Sphinx literal block regex.    sphinx_literal = KernRe(r'^[^.].*::$', cache=False)     #: Sphinx code block regex.    sphinx_cblock = KernRe(r'^\.\.\ +code-block::', cache=False)     def __init__(self):        """        Creates class variables.         Not really mandatory, but it is a good coding style and makes        pylint happy.        """         super().__init__()        self.lineprefix = ""     def print_lineno(self, ln):        """Outputs a line number."""         if self.enable_lineno and ln is not None:            ln += 1            self.data += f".. LINENO {ln}\n"     def output_highlight(self, args):        """        Outputs a C symbol that may require being converted to ReST using        the self.highlights variable.        """         input_text = args        output = ""        in_literal = False        litprefix = ""        block = ""         for line in input_text.strip("\n").split("\n"):             # If we're in a literal block, see if we should drop out of it.            # Otherwise, pass the line straight through unmunged.            if in_literal:                if line.strip():  # If the line is not blank                    # If this is the first non-blank line in a literal block,                    # figure out the proper indent.                    if not litprefix:                        r = KernRe(r'^(\s*)')                        if r.match(line):                            litprefix = '^' + r.group(1)                        else:                            litprefix = ""                         output += line + "\n"                    elif not KernRe(litprefix).match(line):                        in_literal = False                    else:                        output += line + "\n"                else:                    output += line + "\n"             # Not in a literal block (or just dropped out)            if not in_literal:                block += line + "\n"                if self.sphinx_literal.match(line) or self.sphinx_cblock.match(line):                    in_literal = True                    litprefix = ""                    output += self.highlight_block(block)                    block = ""         # Handle any remaining block        if block:            output += self.highlight_block(block)         # Print the output with the line prefix        for line in output.strip("\n").split("\n"):            self.data += self.lineprefix + line + "\n"     def out_section(self, args, out_docblock=False):        """        Outputs a block section.         This could use some work; it's used to output the DOC: sections, and        starts by putting out the name of the doc section itself, but that        tends to duplicate a header already in the template file.        """        for section, text in args.sections.items():            # Skip sections that are in the nosymbol_table            if section in self.nosymbol:                continue             if out_docblock:                if not self.out_mode == self.OUTPUT_INCLUDE:                    self.data += f".. _{section}:\n\n"                    self.data += f'{self.lineprefix}**{section}**\n\n'            else:                self.data += f'{self.lineprefix}**{section}**\n\n'             self.print_lineno(args.section_start_lines.get(section, 0))            self.output_highlight(text)            self.data += "\n"        self.data += "\n"     def out_doc(self, fname, name, args):        if not self.check_doc(name, args):            return        self.out_section(args, out_docblock=True)     def out_function(self, fname, name, args):         oldprefix = self.lineprefix        signature = ""         func_macro = args.get('func_macro', False)        if func_macro:            signature = name        else:            if args.get('functiontype'):                signature = args['functiontype'] + " "            signature += name + " ("         ln = args.declaration_start_line        count = 0        for parameter in args.parameterlist:            if count != 0:                signature += ", "            count += 1            dtype = args.parametertypes.get(parameter, "")             if function_pointer.search(dtype):                signature += function_pointer.group(1) + parameter + function_pointer.group(3)            else:                signature += dtype         if not func_macro:            signature += ")"         self.print_lineno(ln)        if args.get('typedef') or not args.get('functiontype'):            self.data += f".. c:macro:: {name}\n\n"             if args.get('typedef'):                self.data += "   **Typedef**: "                self.lineprefix = ""                self.output_highlight(args.get('purpose', ""))                self.data += "\n\n**Syntax**\n\n"                self.data += f"  ``{signature}``\n\n"            else:                self.data += f"``{signature}``\n\n"        else:            self.data += f".. c:function:: {signature}\n\n"         if not args.get('typedef'):            self.print_lineno(ln)            self.lineprefix = "   "            self.output_highlight(args.get('purpose', ""))            self.data += "\n"         # Put descriptive text into a container (HTML <div>) to help set        # function prototypes apart        self.lineprefix = "  "         if args.parameterlist:            self.data += ".. container:: kernelindent\n\n"            self.data += f"{self.lineprefix}**Parameters**\n\n"         for parameter in args.parameterlist:            parameter_name = KernRe(r'\[.*').sub('', parameter)            dtype = args.parametertypes.get(parameter, "")             if dtype:                self.data += f"{self.lineprefix}``{dtype}``\n"            else:                self.data += f"{self.lineprefix}``{parameter}``\n"             self.print_lineno(args.parameterdesc_start_lines.get(parameter_name, 0))             self.lineprefix = "    "            if parameter_name in args.parameterdescs and \               args.parameterdescs[parameter_name] != KernelDoc.undescribed:                 self.output_highlight(args.parameterdescs[parameter_name])                self.data += "\n"            else:                self.data += f"{self.lineprefix}*undescribed*\n\n"            self.lineprefix = "  "         self.out_section(args)        self.lineprefix = oldprefix     def out_enum(self, fname, name, args):         oldprefix = self.lineprefix        ln = args.declaration_start_line         self.data += f"\n\n.. c:enum:: {name}\n\n"         self.print_lineno(ln)        self.lineprefix = "  "        self.output_highlight(args.get('purpose', ''))        self.data += "\n"         self.data += ".. container:: kernelindent\n\n"        outer = self.lineprefix + "  "        self.lineprefix = outer + "  "        self.data += f"{outer}**Constants**\n\n"         for parameter in args.parameterlist:            self.data += f"{outer}``{parameter}``\n"             if args.parameterdescs.get(parameter, '') != KernelDoc.undescribed:                self.output_highlight(args.parameterdescs[parameter])            else:                self.data += f"{self.lineprefix}*undescribed*\n\n"            self.data += "\n"         self.lineprefix = oldprefix        self.out_section(args)     def out_var(self, fname, name, args):        oldprefix = self.lineprefix        ln = args.declaration_start_line        full_proto = args.other_stuff["full_proto"]         self.lineprefix = "  "         self.data += f"\n\n.. c:macro:: {name}\n\n{self.lineprefix}``{full_proto}``\n\n" 

Lines 1–500 of 880.