Path: blob/master/tools/lib/python/kdoc/kdoc_output.py
122941 views
#!/usr/bin/env python31# SPDX-License-Identifier: GPL-2.02# Copyright(c) 2025: Mauro Carvalho Chehab <[email protected]>.3#4# pylint: disable=C0301,R0902,R0911,R0912,R0913,R0914,R0915,R091756"""7Classes to implement output filters to print kernel-doc documentation.89The implementation uses a virtual base class ``OutputFormat``. It10contains dispatches to virtual methods, and some code to filter11out output messages.1213The actual implementation is done on one separate class per each type14of output, e.g. ``RestFormat`` and ``ManFormat`` classes.1516Currently, there are output classes for ReST and man/troff.17"""1819import os20import re21from datetime import datetime2223from kdoc.kdoc_parser import KernelDoc, type_param24from kdoc.kdoc_re import KernRe252627function_pointer = KernRe(r"([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)", cache=False)2829# match expressions used to find embedded type information30type_constant = KernRe(r"\b``([^\`]+)``\b", cache=False)31type_constant2 = KernRe(r"\%([-_*\w]+)", cache=False)32type_func = KernRe(r"(\w+)\(\)", cache=False)33type_param_ref = KernRe(r"([\!~\*]?)\@(\w*((\.\w+)|(->\w+))*(\.\.\.)?)", cache=False)3435# Special RST handling for func ptr params36type_fp_param = KernRe(r"\@(\w+)\(\)", cache=False)3738# Special RST handling for structs with func ptr params39type_fp_param2 = KernRe(r"\@(\w+->\S+)\(\)", cache=False)4041type_env = KernRe(r"(\$\w+)", cache=False)42type_enum = KernRe(r"\&(enum\s*([_\w]+))", cache=False)43type_struct = KernRe(r"\&(struct\s*([_\w]+))", cache=False)44type_typedef = KernRe(r"\&(typedef\s*([_\w]+))", cache=False)45type_union = KernRe(r"\&(union\s*([_\w]+))", cache=False)46type_member = KernRe(r"\&([_\w]+)(\.|->)([_\w]+)", cache=False)47type_fallback = KernRe(r"\&([_\w]+)", cache=False)48type_member_func = type_member + KernRe(r"\(\)", cache=False)495051class OutputFormat:52"""53Base class for OutputFormat. If used as-is, it means that only54warnings will be displayed.55"""5657# output mode.58OUTPUT_ALL = 0 #: Output all symbols and doc sections.59OUTPUT_INCLUDE = 1 #: Output only specified symbols.60OUTPUT_EXPORTED = 2 #: Output exported symbols.61OUTPUT_INTERNAL = 3 #: Output non-exported symbols.6263#: Highlights to be used in ReST format.64highlights = []6566#: Blank line character.67blankline = ""6869def __init__(self):70"""Declare internal vars and set mode to ``OUTPUT_ALL``."""7172self.out_mode = self.OUTPUT_ALL73self.enable_lineno = None74self.nosymbol = {}75self.symbol = None76self.function_table = None77self.config = None78self.no_doc_sections = False7980self.data = ""8182def set_config(self, config):83"""84Setup global config variables used by both parser and output.85"""8687self.config = config8889def set_filter(self, export, internal, symbol, nosymbol, function_table,90enable_lineno, no_doc_sections):91"""92Initialize filter variables according to the requested mode.9394Only one choice is valid between export, internal and symbol.9596The nosymbol filter can be used on all modes.97"""9899self.enable_lineno = enable_lineno100self.no_doc_sections = no_doc_sections101self.function_table = function_table102103if symbol:104self.out_mode = self.OUTPUT_INCLUDE105elif export:106self.out_mode = self.OUTPUT_EXPORTED107elif internal:108self.out_mode = self.OUTPUT_INTERNAL109else:110self.out_mode = self.OUTPUT_ALL111112if nosymbol:113self.nosymbol = set(nosymbol)114115116def highlight_block(self, block):117"""118Apply the RST highlights to a sub-block of text.119"""120121for r, sub in self.highlights:122block = r.sub(sub, block)123124return block125126def out_warnings(self, args):127"""128Output warnings for identifiers that will be displayed.129"""130131for log_msg in args.warnings:132self.config.warning(log_msg)133134def check_doc(self, name, args):135"""Check if DOC should be output."""136137if self.no_doc_sections:138return False139140if name in self.nosymbol:141return False142143if self.out_mode == self.OUTPUT_ALL:144self.out_warnings(args)145return True146147if self.out_mode == self.OUTPUT_INCLUDE:148if name in self.function_table:149self.out_warnings(args)150return True151152return False153154def check_declaration(self, dtype, name, args):155"""156Checks if a declaration should be output or not based on the157filtering criteria.158"""159160if name in self.nosymbol:161return False162163if self.out_mode == self.OUTPUT_ALL:164self.out_warnings(args)165return True166167if self.out_mode in [self.OUTPUT_INCLUDE, self.OUTPUT_EXPORTED]:168if name in self.function_table:169return True170171if self.out_mode == self.OUTPUT_INTERNAL:172if dtype != "function":173self.out_warnings(args)174return True175176if name not in self.function_table:177self.out_warnings(args)178return True179180return False181182def msg(self, fname, name, args):183"""184Handles a single entry from kernel-doc parser.185"""186187self.data = ""188189dtype = args.type190191if dtype == "doc":192self.out_doc(fname, name, args)193return self.data194195if not self.check_declaration(dtype, name, args):196return self.data197198if dtype == "function":199self.out_function(fname, name, args)200return self.data201202if dtype == "enum":203self.out_enum(fname, name, args)204return self.data205206if dtype == "var":207self.out_var(fname, name, args)208return self.data209210if dtype == "typedef":211self.out_typedef(fname, name, args)212return self.data213214if dtype in ["struct", "union"]:215self.out_struct(fname, name, args)216return self.data217218# Warn if some type requires an output logic219self.config.log.warning("doesn't know how to output '%s' block",220dtype)221222return None223224# Virtual methods to be overridden by inherited classes225# At the base class, those do nothing.226def set_symbols(self, symbols):227"""Get a list of all symbols from kernel_doc."""228229def out_doc(self, fname, name, args):230"""Outputs a DOC block."""231232def out_function(self, fname, name, args):233"""Outputs a function."""234235def out_enum(self, fname, name, args):236"""Outputs an enum."""237238def out_var(self, fname, name, args):239"""Outputs a variable."""240241def out_typedef(self, fname, name, args):242"""Outputs a typedef."""243244def out_struct(self, fname, name, args):245"""Outputs a struct."""246247248class RestFormat(OutputFormat):249"""Consts and functions used by ReST output."""250251#: Highlights to be used in ReST format252highlights = [253(type_constant, r"``\1``"),254(type_constant2, r"``\1``"),255256# Note: need to escape () to avoid func matching later257(type_member_func, r":c:type:`\1\2\3\\(\\) <\1>`"),258(type_member, r":c:type:`\1\2\3 <\1>`"),259(type_fp_param, r"**\1\\(\\)**"),260(type_fp_param2, r"**\1\\(\\)**"),261(type_func, r"\1()"),262(type_enum, r":c:type:`\1 <\2>`"),263(type_struct, r":c:type:`\1 <\2>`"),264(type_typedef, r":c:type:`\1 <\2>`"),265(type_union, r":c:type:`\1 <\2>`"),266267# in rst this can refer to any type268(type_fallback, r":c:type:`\1`"),269(type_param_ref, r"**\1\2**")270]271272blankline = "\n"273274#: Sphinx literal block regex.275sphinx_literal = KernRe(r'^[^.].*::$', cache=False)276277#: Sphinx code block regex.278sphinx_cblock = KernRe(r'^\.\.\ +code-block::', cache=False)279280def __init__(self):281"""282Creates class variables.283284Not really mandatory, but it is a good coding style and makes285pylint happy.286"""287288super().__init__()289self.lineprefix = ""290291def print_lineno(self, ln):292"""Outputs a line number."""293294if self.enable_lineno and ln is not None:295ln += 1296self.data += f".. LINENO {ln}\n"297298def output_highlight(self, args):299"""300Outputs a C symbol that may require being converted to ReST using301the self.highlights variable.302"""303304input_text = args305output = ""306in_literal = False307litprefix = ""308block = ""309310for line in input_text.strip("\n").split("\n"):311312# If we're in a literal block, see if we should drop out of it.313# Otherwise, pass the line straight through unmunged.314if in_literal:315if line.strip(): # If the line is not blank316# If this is the first non-blank line in a literal block,317# figure out the proper indent.318if not litprefix:319r = KernRe(r'^(\s*)')320if r.match(line):321litprefix = '^' + r.group(1)322else:323litprefix = ""324325output += line + "\n"326elif not KernRe(litprefix).match(line):327in_literal = False328else:329output += line + "\n"330else:331output += line + "\n"332333# Not in a literal block (or just dropped out)334if not in_literal:335block += line + "\n"336if self.sphinx_literal.match(line) or self.sphinx_cblock.match(line):337in_literal = True338litprefix = ""339output += self.highlight_block(block)340block = ""341342# Handle any remaining block343if block:344output += self.highlight_block(block)345346# Print the output with the line prefix347for line in output.strip("\n").split("\n"):348self.data += self.lineprefix + line + "\n"349350def out_section(self, args, out_docblock=False):351"""352Outputs a block section.353354This could use some work; it's used to output the DOC: sections, and355starts by putting out the name of the doc section itself, but that356tends to duplicate a header already in the template file.357"""358for section, text in args.sections.items():359# Skip sections that are in the nosymbol_table360if section in self.nosymbol:361continue362363if out_docblock:364if not self.out_mode == self.OUTPUT_INCLUDE:365self.data += f".. _{section}:\n\n"366self.data += f'{self.lineprefix}**{section}**\n\n'367else:368self.data += f'{self.lineprefix}**{section}**\n\n'369370self.print_lineno(args.section_start_lines.get(section, 0))371self.output_highlight(text)372self.data += "\n"373self.data += "\n"374375def out_doc(self, fname, name, args):376if not self.check_doc(name, args):377return378self.out_section(args, out_docblock=True)379380def out_function(self, fname, name, args):381382oldprefix = self.lineprefix383signature = ""384385func_macro = args.get('func_macro', False)386if func_macro:387signature = name388else:389if args.get('functiontype'):390signature = args['functiontype'] + " "391signature += name + " ("392393ln = args.declaration_start_line394count = 0395for parameter in args.parameterlist:396if count != 0:397signature += ", "398count += 1399dtype = args.parametertypes.get(parameter, "")400401if function_pointer.search(dtype):402signature += function_pointer.group(1) + parameter + function_pointer.group(3)403else:404signature += dtype405406if not func_macro:407signature += ")"408409self.print_lineno(ln)410if args.get('typedef') or not args.get('functiontype'):411self.data += f".. c:macro:: {name}\n\n"412413if args.get('typedef'):414self.data += " **Typedef**: "415self.lineprefix = ""416self.output_highlight(args.get('purpose', ""))417self.data += "\n\n**Syntax**\n\n"418self.data += f" ``{signature}``\n\n"419else:420self.data += f"``{signature}``\n\n"421else:422self.data += f".. c:function:: {signature}\n\n"423424if not args.get('typedef'):425self.print_lineno(ln)426self.lineprefix = " "427self.output_highlight(args.get('purpose', ""))428self.data += "\n"429430# Put descriptive text into a container (HTML <div>) to help set431# function prototypes apart432self.lineprefix = " "433434if args.parameterlist:435self.data += ".. container:: kernelindent\n\n"436self.data += f"{self.lineprefix}**Parameters**\n\n"437438for parameter in args.parameterlist:439parameter_name = KernRe(r'\[.*').sub('', parameter)440dtype = args.parametertypes.get(parameter, "")441442if dtype:443self.data += f"{self.lineprefix}``{dtype}``\n"444else:445self.data += f"{self.lineprefix}``{parameter}``\n"446447self.print_lineno(args.parameterdesc_start_lines.get(parameter_name, 0))448449self.lineprefix = " "450if parameter_name in args.parameterdescs and \451args.parameterdescs[parameter_name] != KernelDoc.undescribed:452453self.output_highlight(args.parameterdescs[parameter_name])454self.data += "\n"455else:456self.data += f"{self.lineprefix}*undescribed*\n\n"457self.lineprefix = " "458459self.out_section(args)460self.lineprefix = oldprefix461462def out_enum(self, fname, name, args):463464oldprefix = self.lineprefix465ln = args.declaration_start_line466467self.data += f"\n\n.. c:enum:: {name}\n\n"468469self.print_lineno(ln)470self.lineprefix = " "471self.output_highlight(args.get('purpose', ''))472self.data += "\n"473474self.data += ".. container:: kernelindent\n\n"475outer = self.lineprefix + " "476self.lineprefix = outer + " "477self.data += f"{outer}**Constants**\n\n"478479for parameter in args.parameterlist:480self.data += f"{outer}``{parameter}``\n"481482if args.parameterdescs.get(parameter, '') != KernelDoc.undescribed:483self.output_highlight(args.parameterdescs[parameter])484else:485self.data += f"{self.lineprefix}*undescribed*\n\n"486self.data += "\n"487488self.lineprefix = oldprefix489self.out_section(args)490491def out_var(self, fname, name, args):492oldprefix = self.lineprefix493ln = args.declaration_start_line494full_proto = args.other_stuff["full_proto"]495496self.lineprefix = " "497498self.data += f"\n\n.. c:macro:: {name}\n\n{self.lineprefix}``{full_proto}``\n\n"499500self.print_lineno(ln)501self.output_highlight(args.get('purpose', ''))502self.data += "\n"503504if args.other_stuff["default_val"]:505self.data += f'{self.lineprefix}**Initialization**\n\n'506self.output_highlight(f'default: ``{args.other_stuff["default_val"]}``')507508self.out_section(args)509510def out_typedef(self, fname, name, args):511512oldprefix = self.lineprefix513ln = args.declaration_start_line514515self.data += f"\n\n.. c:type:: {name}\n\n"516517self.print_lineno(ln)518self.lineprefix = " "519520self.output_highlight(args.get('purpose', ''))521522self.data += "\n"523524self.lineprefix = oldprefix525self.out_section(args)526527def out_struct(self, fname, name, args):528529purpose = args.get('purpose', "")530declaration = args.get('definition', "")531dtype = args.type532ln = args.declaration_start_line533534self.data += f"\n\n.. c:{dtype}:: {name}\n\n"535536self.print_lineno(ln)537538oldprefix = self.lineprefix539self.lineprefix += " "540541self.output_highlight(purpose)542self.data += "\n"543544self.data += ".. container:: kernelindent\n\n"545self.data += f"{self.lineprefix}**Definition**::\n\n"546547self.lineprefix = self.lineprefix + " "548549declaration = declaration.replace("\t", self.lineprefix)550551self.data += f"{self.lineprefix}{dtype} {name}" + ' {' + "\n"552self.data += f"{declaration}{self.lineprefix}" + "};\n\n"553554self.lineprefix = " "555self.data += f"{self.lineprefix}**Members**\n\n"556for parameter in args.parameterlist:557if not parameter or parameter.startswith("#"):558continue559560parameter_name = parameter.split("[", maxsplit=1)[0]561562if args.parameterdescs.get(parameter_name) == KernelDoc.undescribed:563continue564565self.print_lineno(args.parameterdesc_start_lines.get(parameter_name, 0))566567self.data += f"{self.lineprefix}``{parameter}``\n"568569self.lineprefix = " "570self.output_highlight(args.parameterdescs[parameter_name])571self.lineprefix = " "572573self.data += "\n"574575self.data += "\n"576577self.lineprefix = oldprefix578self.out_section(args)579580581class ManFormat(OutputFormat):582"""Consts and functions used by man pages output."""583584highlights = (585(type_constant, r"\1"),586(type_constant2, r"\1"),587(type_func, r"\\fB\1\\fP"),588(type_enum, r"\\fI\1\\fP"),589(type_struct, r"\\fI\1\\fP"),590(type_typedef, r"\\fI\1\\fP"),591(type_union, r"\\fI\1\\fP"),592(type_param, r"\\fI\1\\fP"),593(type_param_ref, r"\\fI\1\2\\fP"),594(type_member, r"\\fI\1\2\3\\fP"),595(type_fallback, r"\\fI\1\\fP")596)597blankline = ""598599#: Allowed timestamp formats.600date_formats = [601"%a %b %d %H:%M:%S %Z %Y",602"%a %b %d %H:%M:%S %Y",603"%Y-%m-%d",604"%b %d %Y",605"%B %d %Y",606"%m %d %Y",607]608609def __init__(self, modulename):610"""611Creates class variables.612613Not really mandatory, but it is a good coding style and makes614pylint happy.615"""616617super().__init__()618self.modulename = modulename619self.symbols = []620621dt = None622tstamp = os.environ.get("KBUILD_BUILD_TIMESTAMP")623if tstamp:624for fmt in self.date_formats:625try:626dt = datetime.strptime(tstamp, fmt)627break628except ValueError:629pass630631if not dt:632dt = datetime.now()633634self.man_date = dt.strftime("%B %Y")635636def arg_name(self, args, name):637"""638Return the name that will be used for the man page.639640As we may have the same name on different namespaces,641prepend the data type for all types except functions and typedefs.642643The doc section is special: it uses the modulename.644"""645646dtype = args.type647648if dtype == "doc":649return self.modulename650651if dtype in ["function", "typedef"]:652return name653654return f"{dtype} {name}"655656def set_symbols(self, symbols):657"""658Get a list of all symbols from kernel_doc.659660Man pages will uses it to add a SEE ALSO section with other661symbols at the same file.662"""663self.symbols = symbols664665def out_tail(self, fname, name, args):666"""Adds a tail for all man pages."""667668# SEE ALSO section669self.data += f'.SH "SEE ALSO"' + "\n.PP\n"670self.data += (f"Kernel file \\fB{args.fname}\\fR\n")671if len(self.symbols) >= 2:672cur_name = self.arg_name(args, name)673674related = []675for arg in self.symbols:676out_name = self.arg_name(arg, arg.name)677678if cur_name == out_name:679continue680681related.append(f"\\fB{out_name}\\fR(9)")682683self.data += ",\n".join(related) + "\n"684685# TODO: does it make sense to add other sections? Maybe686# REPORTING ISSUES? LICENSE?687688def msg(self, fname, name, args):689"""690Handles a single entry from kernel-doc parser.691692Add a tail at the end of man pages output.693"""694super().msg(fname, name, args)695self.out_tail(fname, name, args)696697return self.data698699def output_highlight(self, block):700"""701Outputs a C symbol that may require being highlighted with702self.highlights variable using troff syntax.703"""704705contents = self.highlight_block(block)706707if isinstance(contents, list):708contents = "\n".join(contents)709710for line in contents.strip("\n").split("\n"):711line = KernRe(r"^\s*").sub("", line)712if not line:713continue714715if line[0] == ".":716self.data += "\\&" + line + "\n"717else:718self.data += line + "\n"719720def out_doc(self, fname, name, args):721if not self.check_doc(name, args):722return723724out_name = self.arg_name(args, name)725726self.data += f'.TH "{self.modulename}" 9 "{out_name}" "{self.man_date}" "API Manual" LINUX' + "\n"727728for section, text in args.sections.items():729self.data += f'.SH "{section}"' + "\n"730self.output_highlight(text)731732def out_function(self, fname, name, args):733734out_name = self.arg_name(args, name)735736self.data += f'.TH "{name}" 9 "{out_name}" "{self.man_date}" "Kernel Hacker\'s Manual" LINUX' + "\n"737738self.data += ".SH NAME\n"739self.data += f"{name} \\- {args['purpose']}\n"740741self.data += ".SH SYNOPSIS\n"742if args.get('functiontype', ''):743self.data += f'.B "{args["functiontype"]}" {name}' + "\n"744else:745self.data += f'.B "{name}' + "\n"746747count = 0748parenth = "("749post = ","750751for parameter in args.parameterlist:752if count == len(args.parameterlist) - 1:753post = ");"754755dtype = args.parametertypes.get(parameter, "")756if function_pointer.match(dtype):757# Pointer-to-function758self.data += f'".BI "{parenth}{function_pointer.group(1)}" " ") ({function_pointer.group(2)}){post}"' + "\n"759else:760dtype = KernRe(r'([^\*])$').sub(r'\1 ', dtype)761762self.data += f'.BI "{parenth}{dtype}" "{post}"' + "\n"763count += 1764parenth = ""765766if args.parameterlist:767self.data += ".SH ARGUMENTS\n"768769for parameter in args.parameterlist:770parameter_name = re.sub(r'\[.*', '', parameter)771772self.data += f'.IP "{parameter}" 12' + "\n"773self.output_highlight(args.parameterdescs.get(parameter_name, ""))774775for section, text in args.sections.items():776self.data += f'.SH "{section.upper()}"' + "\n"777self.output_highlight(text)778779def out_enum(self, fname, name, args):780out_name = self.arg_name(args, name)781782self.data += f'.TH "{self.modulename}" 9 "{out_name}" "{self.man_date}" "API Manual" LINUX' + "\n"783784self.data += ".SH NAME\n"785self.data += f"enum {name} \\- {args['purpose']}\n"786787self.data += ".SH SYNOPSIS\n"788self.data += f"enum {name}" + " {\n"789790count = 0791for parameter in args.parameterlist:792self.data += f'.br\n.BI " {parameter}"' + "\n"793if count == len(args.parameterlist) - 1:794self.data += "\n};\n"795else:796self.data += ", \n.br\n"797798count += 1799800self.data += ".SH Constants\n"801802for parameter in args.parameterlist:803parameter_name = KernRe(r'\[.*').sub('', parameter)804self.data += f'.IP "{parameter}" 12' + "\n"805self.output_highlight(args.parameterdescs.get(parameter_name, ""))806807for section, text in args.sections.items():808self.data += f'.SH "{section}"' + "\n"809self.output_highlight(text)810811def out_var(self, fname, name, args):812out_name = self.arg_name(args, name)813full_proto = args.other_stuff["full_proto"]814815self.data += f'.TH "{self.modulename}" 9 "{out_name}" "{self.man_date}" "API Manual" LINUX' + "\n"816817self.data += ".SH NAME\n"818self.data += f"{name} \\- {args['purpose']}\n"819820self.data += ".SH SYNOPSIS\n"821self.data += f"{full_proto}\n"822823if args.other_stuff["default_val"]:824self.data += f'.SH "Initialization"' + "\n"825self.output_highlight(f'default: {args.other_stuff["default_val"]}')826827for section, text in args.sections.items():828self.data += f'.SH "{section}"' + "\n"829self.output_highlight(text)830831def out_typedef(self, fname, name, args):832module = self.modulename833purpose = args.get('purpose')834out_name = self.arg_name(args, name)835836self.data += f'.TH "{module}" 9 "{out_name}" "{self.man_date}" "API Manual" LINUX' + "\n"837838self.data += ".SH NAME\n"839self.data += f"typedef {name} \\- {purpose}\n"840841for section, text in args.sections.items():842self.data += f'.SH "{section}"' + "\n"843self.output_highlight(text)844845def out_struct(self, fname, name, args):846module = self.modulename847purpose = args.get('purpose')848definition = args.get('definition')849out_name = self.arg_name(args, name)850851self.data += f'.TH "{module}" 9 "{out_name}" "{self.man_date}" "API Manual" LINUX' + "\n"852853self.data += ".SH NAME\n"854self.data += f"{args.type} {name} \\- {purpose}\n"855856# Replace tabs with two spaces and handle newlines857declaration = definition.replace("\t", " ")858declaration = KernRe(r"\n").sub('"\n.br\n.BI "', declaration)859860self.data += ".SH SYNOPSIS\n"861self.data += f"{args.type} {name} " + "{" + "\n.br\n"862self.data += f'.BI "{declaration}\n' + "};\n.br\n\n"863864self.data += ".SH Members\n"865for parameter in args.parameterlist:866if parameter.startswith("#"):867continue868869parameter_name = re.sub(r"\[.*", "", parameter)870871if args.parameterdescs.get(parameter_name) == KernelDoc.undescribed:872continue873874self.data += f'.IP "{parameter}" 12' + "\n"875self.output_highlight(args.parameterdescs.get(parameter_name))876877for section, text in args.sections.items():878self.data += f'.SH "{section}"' + "\n"879self.output_highlight(text)880881882