#!/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 def output_symbols(self, fname, symbols): """ Handles a set of KdocItem symbols. """ self.set_symbols(symbols) msg = "" for arg in symbols: m = self.msg(fname, arg.name, arg) if m is None: ln = arg.get("ln", 0) dtype = arg.get('type', "") self.config.log.warning("%s:%d Can't handle %s", fname, ln, dtype) else: msg += m return msg # 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.sections_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" Lines 1–500 of 1160. Load more
#!/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``. It contains dispatches to virtual methods, and some code to filter out output messages. The actual implementation is done on one separate class per each type of output, e.g. ``RestFormat`` and ``ManFormat`` classes. Currently, there are output classes for ReST and man/troff. """ import os import re from datetime import datetime from kdoc.kdoc_parser import KernelDoc, type_param from kdoc.kdoc_re import KernRe function_pointer = KernRe(r"([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)", cache=False) # match expressions used to find embedded type information type_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 params type_fp_param = KernRe(r"\@(\w+)\(\)", cache=False) # Special RST handling for structs with func ptr params type_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 def output_symbols(self, fname, symbols): """ Handles a set of KdocItem symbols. """ self.set_symbols(symbols) msg = "" for arg in symbols: m = self.msg(fname, arg.name, arg) if m is None: ln = arg.get("ln", 0) dtype = arg.get('type', "") self.config.log.warning("%s:%d Can't handle %s", fname, ln, dtype) else: msg += m return msg # 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.sections_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"