From nobody Fri Dec 19 18:45:38 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DF91F267728; Tue, 8 Apr 2025 10:09:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744106997; cv=none; b=brTIRGU1ujm3IZga3dF7nbU+YsSmPGOFv9QC8gTvR84z/kPdXSxrEvDBQRnWvBL2q4yeCrtVAW3DqeuGMtJo9/bWB4ivOOkqUKHseM1q1yNuvpQprB39LJgmes/s0heMPFUWL4zqkp4cwviwyUF8Baa0dEJpnfsTF4K4d+QNg8k= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744106997; c=relaxed/simple; bh=WC+f6MF8GfFGr87diUnskmJPC7KBcMzgHUcQy1jHo0Y=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=TfER6g9h/HBF667pWSKEroHTmqedRx8xIFu108JlSUubVQ1K4OUPb5RKLjH3VAFZbKdkeRb7kWd3LYHwkUE7lMyNpC72OXHK/H5APzEGGQpLf1zSbkU7pCU6/cQJXHvkJJ2Ag6A42yrXLxZld7IBIur0P5pProd61s/+XubL9gk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=JLSz1ik1; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="JLSz1ik1" Received: by smtp.kernel.org (Postfix) with ESMTPSA id DDE73C4CEF8; Tue, 8 Apr 2025 10:09:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1744106996; bh=WC+f6MF8GfFGr87diUnskmJPC7KBcMzgHUcQy1jHo0Y=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=JLSz1ik1QaYVzyAQ9lMi3N82ZZgS2mb8I5/hWo0dcA4vTQiKV41iork2xAUjen4qF BCgXyrUvDK7SO7d/znIRxTAZjBFSdYoloCi71nUZJX1hy/9Mn5TWDVFA3MLqP7NiOm p5gUmhklTj3Wpmz8rZG+92h+h1ei4vFzAUmBwnlIpYYCxOxacOc+892BvrDBDC4hkp bdiI87FzfiVfPZSb1yBVcnNdQtDd3qVCfaAjjuJ5hIEfJyz3qOkTs3gPM1sEOydrVT yirH0LIbEIhtW7AnYvdaFVQpMC7eTvC+LV66enIT2hCHCHKk7mMCHmzkRUN1mk8T47 Mzs53J8mzCFTQ== Received: from mchehab by mail.kernel.org with local (Exim 4.98.2) (envelope-from ) id 1u25tt-00000008RVj-0nD6; Tue, 08 Apr 2025 18:09:49 +0800 From: Mauro Carvalho Chehab To: Linux Doc Mailing List , Jonathan Corbet Cc: Mauro Carvalho Chehab , linux-kernel@vger.kernel.org Subject: [PATCH v3 10/33] scripts/kernel-doc.py: move output classes to a separate file Date: Tue, 8 Apr 2025 18:09:13 +0800 Message-ID: <81087eff25d11c265019a8631f7fc8d3904795d0.1744106242.git.mchehab+huawei@kernel.org> X-Mailer: git-send-email 2.49.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Sender: Mauro Carvalho Chehab Content-Type: text/plain; charset="utf-8" In preparation for letting kerneldoc Sphinx extension to import Python libraries, move kernel-doc output logic to a separate file. Signed-off-by: Mauro Carvalho Chehab --- scripts/kernel-doc.py | 727 +------------------------------ scripts/lib/kdoc/kdoc_output.py | 736 ++++++++++++++++++++++++++++++++ 2 files changed, 739 insertions(+), 724 deletions(-) create mode 100755 scripts/lib/kdoc/kdoc_output.py diff --git a/scripts/kernel-doc.py b/scripts/kernel-doc.py index d09ada2d862a..abff78e9160f 100755 --- a/scripts/kernel-doc.py +++ b/scripts/kernel-doc.py @@ -2,9 +2,7 @@ # SPDX-License-Identifier: GPL-2.0 # Copyright(c) 2025: Mauro Carvalho Chehab . # -# pylint: disable=3DR0902,R0903,R0904,R0911,R0912,R0913,R0914,R0915,R0917,= R1702 -# pylint: disable=3DC0302,C0103,C0301 -# pylint: disable=3DC0116,C0115,W0511,W0613 +# pylint: disable=3DC0103 # # Converted from the kernel-doc script originally written in Perl # under GPLv2, copyrighted since 1998 by the following authors: @@ -102,14 +100,8 @@ documentation comment syntax. import argparse import logging import os -import re import sys =20 -from datetime import datetime -from pprint import pformat - -from dateutil import tz - # Import Python modules =20 LIB_DIR =3D "lib/kdoc" @@ -117,721 +109,8 @@ SRC_DIR =3D os.path.dirname(os.path.realpath(__file__= )) =20 sys.path.insert(0, os.path.join(SRC_DIR, LIB_DIR)) =20 -from kdoc_parser import KernelDoc, type_param -from kdoc_re import Re -from kdoc_files import KernelFiles - -function_pointer =3D Re(r"([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)", cache=3DFalse) - -# match expressions used to find embedded type information -type_constant =3D Re(r"\b``([^\`]+)``\b", cache=3DFalse) -type_constant2 =3D Re(r"\%([-_*\w]+)", cache=3DFalse) -type_func =3D Re(r"(\w+)\(\)", cache=3DFalse) -type_param_ref =3D Re(r"([\!~\*]?)\@(\w*((\.\w+)|(->\w+))*(\.\.\.)?)", cac= he=3DFalse) - -# Special RST handling for func ptr params -type_fp_param =3D Re(r"\@(\w+)\(\)", cache=3DFalse) - -# Special RST handling for structs with func ptr params -type_fp_param2 =3D Re(r"\@(\w+->\S+)\(\)", cache=3DFalse) - -type_env =3D Re(r"(\$\w+)", cache=3DFalse) -type_enum =3D Re(r"\&(enum\s*([_\w]+))", cache=3DFalse) -type_struct =3D Re(r"\&(struct\s*([_\w]+))", cache=3DFalse) -type_typedef =3D Re(r"\&(typedef\s*([_\w]+))", cache=3DFalse) -type_union =3D Re(r"\&(union\s*([_\w]+))", cache=3DFalse) -type_member =3D Re(r"\&([_\w]+)(\.|->)([_\w]+)", cache=3DFalse) -type_fallback =3D Re(r"\&([_\w]+)", cache=3DFalse) -type_member_func =3D type_member + Re(r"\(\)", cache=3DFalse) - - -class OutputFormat: - # output mode. - OUTPUT_ALL =3D 0 # output all symbols and doc sections - OUTPUT_INCLUDE =3D 1 # output only specified symbols - OUTPUT_EXPORTED =3D 2 # output exported symbols - OUTPUT_INTERNAL =3D 3 # output non-exported symbols - - # Virtual member to be overriden at the inherited classes - highlights =3D [] - - def __init__(self): - """Declare internal vars and set mode to OUTPUT_ALL""" - - self.out_mode =3D self.OUTPUT_ALL - self.enable_lineno =3D None - self.nosymbol =3D {} - self.symbol =3D None - self.function_table =3D set() - self.config =3D None - - def set_config(self, config): - self.config =3D config - - def set_filter(self, export, internal, symbol, nosymbol, function_tabl= e, - enable_lineno): - """ - Initialize filter variables according with 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 =3D enable_lineno - - if symbol: - self.out_mode =3D self.OUTPUT_INCLUDE - function_table =3D symbol - elif export: - self.out_mode =3D self.OUTPUT_EXPORTED - elif internal: - self.out_mode =3D self.OUTPUT_INTERNAL - else: - self.out_mode =3D self.OUTPUT_ALL - - if nosymbol: - self.nosymbol =3D set(nosymbol) - - if function_table: - self.function_table =3D function_table - - def highlight_block(self, block): - """ - Apply the RST highlights to a sub-block of text. - """ - - for r, sub in self.highlights: - block =3D r.sub(sub, block) - - return block - - def check_doc(self, name): - """Check if DOC should be output""" - - if self.out_mode =3D=3D self.OUTPUT_ALL: - return True - - if self.out_mode =3D=3D self.OUTPUT_INCLUDE: - if name in self.nosymbol: - return False - - if name in self.function_table: - return True - - return False - - def check_declaration(self, dtype, name): - if name in self.nosymbol: - return False - - if self.out_mode =3D=3D self.OUTPUT_ALL: - 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 =3D=3D self.OUTPUT_INTERNAL: - if dtype !=3D "function": - return True - - if name not in self.function_table: - return True - - return False - - def check_function(self, fname, name, args): - return True - - def check_enum(self, fname, name, args): - return True - - def check_typedef(self, fname, name, args): - return True - - def msg(self, fname, name, args): - - dtype =3D args.get('type', "") - - if dtype =3D=3D "doc": - self.out_doc(fname, name, args) - return False - - if not self.check_declaration(dtype, name): - return False - - if dtype =3D=3D "function": - self.out_function(fname, name, args) - return False - - if dtype =3D=3D "enum": - self.out_enum(fname, name, args) - return False - - if dtype =3D=3D "typedef": - self.out_typedef(fname, name, args) - return False - - if dtype in ["struct", "union"]: - self.out_struct(fname, name, args) - return False - - # Warn if some type requires an output logic - self.config.log.warning("doesn't now how to output '%s' block", - dtype) - - return True - - # Virtual methods to be overridden by inherited classes - def out_doc(self, fname, name, args): - pass - - def out_function(self, fname, name, args): - pass - - def out_enum(self, fname, name, args): - pass - - def out_typedef(self, fname, name, args): - pass - - def out_struct(self, fname, name, args): - pass - - -class RestFormat(OutputFormat): - # """Consts and functions used by ReST output""" - - highlights =3D [ - (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 =3D "\n" - - sphinx_literal =3D Re(r'^[^.].*::$', cache=3DFalse) - sphinx_cblock =3D Re(r'^\.\.\ +code-block::', cache=3DFalse) - - def __init__(self): - """ - Creates class variables. - - Not really mandatory, but it is a good coding style and makes - pylint happy. - """ - - super().__init__() - self.lineprefix =3D "" - - def print_lineno (self, ln): - """Outputs a line number""" - - if self.enable_lineno and ln: - print(f".. LINENO {ln}") - - def output_highlight(self, args): - input_text =3D args - output =3D "" - in_literal =3D False - litprefix =3D "" - block =3D "" - - 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 blo= ck, - # figure out the proper indent. - if not litprefix: - r =3D Re(r'^(\s*)') - if r.match(line): - litprefix =3D '^' + r.group(1) - else: - litprefix =3D "" - - output +=3D line + "\n" - elif not Re(litprefix).match(line): - in_literal =3D False - else: - output +=3D line + "\n" - else: - output +=3D line + "\n" - - # Not in a literal block (or just dropped out) - if not in_literal: - block +=3D line + "\n" - if self.sphinx_literal.match(line) or self.sphinx_cblock.m= atch(line): - in_literal =3D True - litprefix =3D "" - output +=3D self.highlight_block(block) - block =3D "" - - # Handle any remaining block - if block: - output +=3D self.highlight_block(block) - - # Print the output with the line prefix - for line in output.strip("\n").split("\n"): - print(self.lineprefix + line) - - def out_section(self, args, out_reference=3DFalse): - """ - Outputs a block section. - - This could use some work; it's used to output the DOC: sections, a= nd - starts by putting out the name of the doc section itself, but that - tends to duplicate a header already in the template file. - """ - - sectionlist =3D args.get('sectionlist', []) - sections =3D args.get('sections', {}) - section_start_lines =3D args.get('section_start_lines', {}) - - for section in sectionlist: - # Skip sections that are in the nosymbol_table - if section in self.nosymbol: - continue - - if not self.out_mode =3D=3D self.OUTPUT_INCLUDE: - if out_reference: - print(f".. _{section}:\n") - - if not self.symbol: - print(f'{self.lineprefix}**{section}**\n') - - self.print_lineno(section_start_lines.get(section, 0)) - self.output_highlight(sections[section]) - print() - print() - - def out_doc(self, fname, name, args): - if not self.check_doc(name): - return - - self.out_section(args, out_reference=3DTrue) - - def out_function(self, fname, name, args): - - oldprefix =3D self.lineprefix - signature =3D "" - - func_macro =3D args.get('func_macro', False) - if func_macro: - signature =3D args['function'] - else: - if args.get('functiontype'): - signature =3D args['functiontype'] + " " - signature +=3D args['function'] + " (" - - parameterlist =3D args.get('parameterlist', []) - parameterdescs =3D args.get('parameterdescs', {}) - parameterdesc_start_lines =3D args.get('parameterdesc_start_lines'= , {}) - - ln =3D args.get('ln', 0) - - count =3D 0 - for parameter in parameterlist: - if count !=3D 0: - signature +=3D ", " - count +=3D 1 - dtype =3D args['parametertypes'].get(parameter, "") - - if function_pointer.search(dtype): - signature +=3D function_pointer.group(1) + parameter + fun= ction_pointer.group(3) - else: - signature +=3D dtype - - if not func_macro: - signature +=3D ")" - - if args.get('typedef') or not args.get('functiontype'): - print(f".. c:macro:: {args['function']}\n") - - if args.get('typedef'): - self.print_lineno(ln) - print(" **Typedef**: ", end=3D"") - self.lineprefix =3D "" - self.output_highlight(args.get('purpose', "")) - print("\n\n**Syntax**\n") - print(f" ``{signature}``\n") - else: - print(f"``{signature}``\n") - else: - print(f".. c:function:: {signature}\n") - - if not args.get('typedef'): - self.print_lineno(ln) - self.lineprefix =3D " " - self.output_highlight(args.get('purpose', "")) - print() - - # Put descriptive text into a container (HTML
) to help set - # function prototypes apart - self.lineprefix =3D " " - - if parameterlist: - print(".. container:: kernelindent\n") - print(f"{self.lineprefix}**Parameters**\n") - - for parameter in parameterlist: - parameter_name =3D Re(r'\[.*').sub('', parameter) - dtype =3D args['parametertypes'].get(parameter, "") - - if dtype: - print(f"{self.lineprefix}``{dtype}``") - else: - print(f"{self.lineprefix}``{parameter}``") - - self.print_lineno(parameterdesc_start_lines.get(parameter_name= , 0)) - - self.lineprefix =3D " " - if parameter_name in parameterdescs and \ - parameterdescs[parameter_name] !=3D KernelDoc.undescribed: - - self.output_highlight(parameterdescs[parameter_name]) - print() - else: - print(f"{self.lineprefix}*undescribed*\n") - self.lineprefix =3D " " - - self.out_section(args) - self.lineprefix =3D oldprefix - - def out_enum(self, fname, name, args): - - oldprefix =3D self.lineprefix - name =3D args.get('enum', '') - parameterlist =3D args.get('parameterlist', []) - parameterdescs =3D args.get('parameterdescs', {}) - ln =3D args.get('ln', 0) - - print(f"\n\n.. c:enum:: {name}\n") - - self.print_lineno(ln) - self.lineprefix =3D " " - self.output_highlight(args.get('purpose', '')) - print() - - print(".. container:: kernelindent\n") - outer =3D self.lineprefix + " " - self.lineprefix =3D outer + " " - print(f"{outer}**Constants**\n") - - for parameter in parameterlist: - print(f"{outer}``{parameter}``") - - if parameterdescs.get(parameter, '') !=3D KernelDoc.undescribe= d: - self.output_highlight(parameterdescs[parameter]) - else: - print(f"{self.lineprefix}*undescribed*\n") - print() - - self.lineprefix =3D oldprefix - self.out_section(args) - - def out_typedef(self, fname, name, args): - - oldprefix =3D self.lineprefix - name =3D args.get('typedef', '') - ln =3D args.get('ln', 0) - - print(f"\n\n.. c:type:: {name}\n") - - self.print_lineno(ln) - self.lineprefix =3D " " - - self.output_highlight(args.get('purpose', '')) - - print() - - self.lineprefix =3D oldprefix - self.out_section(args) - - def out_struct(self, fname, name, args): - - name =3D args.get('struct', "") - purpose =3D args.get('purpose', "") - declaration =3D args.get('definition', "") - dtype =3D args.get('type', "struct") - ln =3D args.get('ln', 0) - - parameterlist =3D args.get('parameterlist', []) - parameterdescs =3D args.get('parameterdescs', {}) - parameterdesc_start_lines =3D args.get('parameterdesc_start_lines'= , {}) - - print(f"\n\n.. c:{dtype}:: {name}\n") - - self.print_lineno(ln) - - oldprefix =3D self.lineprefix - self.lineprefix +=3D " " - - self.output_highlight(purpose) - print() - - print(".. container:: kernelindent\n") - print(f"{self.lineprefix}**Definition**::\n") - - self.lineprefix =3D self.lineprefix + " " - - declaration =3D declaration.replace("\t", self.lineprefix) - - print(f"{self.lineprefix}{dtype} {name}" + ' {') - print(f"{declaration}{self.lineprefix}" + "};\n") - - self.lineprefix =3D " " - print(f"{self.lineprefix}**Members**\n") - for parameter in parameterlist: - if not parameter or parameter.startswith("#"): - continue - - parameter_name =3D parameter.split("[", maxsplit=3D1)[0] - - if parameterdescs.get(parameter_name) =3D=3D KernelDoc.undescr= ibed: - continue - - self.print_lineno(parameterdesc_start_lines.get(parameter_name= , 0)) - - print(f"{self.lineprefix}``{parameter}``") - - self.lineprefix =3D " " - self.output_highlight(parameterdescs[parameter_name]) - self.lineprefix =3D " " - - print() - - print() - - self.lineprefix =3D oldprefix - self.out_section(args) - - -class ManFormat(OutputFormat): - """Consts and functions used by man pages output""" - - highlights =3D ( - (type_constant, r"\1"), - (type_constant2, r"\1"), - (type_func, r"\\fB\1\\fP"), - (type_enum, r"\\fI\1\\fP"), - (type_struct, r"\\fI\1\\fP"), - (type_typedef, r"\\fI\1\\fP"), - (type_union, r"\\fI\1\\fP"), - (type_param, r"\\fI\1\\fP"), - (type_param_ref, r"\\fI\1\2\\fP"), - (type_member, r"\\fI\1\2\3\\fP"), - (type_fallback, r"\\fI\1\\fP") - ) - blankline =3D "" - - def __init__(self): - """ - Creates class variables. - - Not really mandatory, but it is a good coding style and makes - pylint happy. - """ - - super().__init__() - - dt =3D datetime.now() - if os.environ.get("KBUILD_BUILD_TIMESTAMP", None): - # use UTC TZ - to_zone =3D tz.gettz('UTC') - dt =3D dt.astimezone(to_zone) - - self.man_date =3D dt.strftime("%B %Y") - - def output_highlight(self, block): - - contents =3D self.highlight_block(block) - - if isinstance(contents, list): - contents =3D "\n".join(contents) - - for line in contents.strip("\n").split("\n"): - line =3D Re(r"^\s*").sub("", line) - - if line and line[0] =3D=3D ".": - print("\\&" + line) - else: - print(line) - - def out_doc(self, fname, name, args): - module =3D args.get('module') - sectionlist =3D args.get('sectionlist', []) - sections =3D args.get('sections', {}) - - print(f'.TH "{module}" 9 "{module}" "{self.man_date}" "API Manual"= LINUX') - - for section in sectionlist: - print(f'.SH "{section}"') - self.output_highlight(sections.get(section)) - - def out_function(self, fname, name, args): - """output function in man""" - - parameterlist =3D args.get('parameterlist', []) - parameterdescs =3D args.get('parameterdescs', {}) - sectionlist =3D args.get('sectionlist', []) - sections =3D args.get('sections', {}) - - print(f'.TH "{args['function']}" 9 "{args['function']}" "{self.man= _date}" "Kernel Hacker\'s Manual" LINUX') - - print(".SH NAME") - print(f"{args['function']} \\- {args['purpose']}") - - print(".SH SYNOPSIS") - if args.get('functiontype', ''): - print(f'.B "{args['functiontype']}" {args['function']}') - else: - print(f'.B "{args['function']}') - - count =3D 0 - parenth =3D "(" - post =3D "," - - for parameter in parameterlist: - if count =3D=3D len(parameterlist) - 1: - post =3D ");" - - dtype =3D args['parametertypes'].get(parameter, "") - if function_pointer.match(dtype): - # Pointer-to-function - print(f'".BI "{parenth}{function_pointer.group(1)}" " ") (= {function_pointer.group(2)}){post}"') - else: - dtype =3D Re(r'([^\*])$').sub(r'\1 ', dtype) - - print(f'.BI "{parenth}{dtype}" "{post}"') - count +=3D 1 - parenth =3D "" - - if parameterlist: - print(".SH ARGUMENTS") - - for parameter in parameterlist: - parameter_name =3D re.sub(r'\[.*', '', parameter) - - print(f'.IP "{parameter}" 12') - self.output_highlight(parameterdescs.get(parameter_name, "")) - - for section in sectionlist: - print(f'.SH "{section.upper()}"') - self.output_highlight(sections[section]) - - def out_enum(self, fname, name, args): - - name =3D args.get('enum', '') - parameterlist =3D args.get('parameterlist', []) - sectionlist =3D args.get('sectionlist', []) - sections =3D args.get('sections', {}) - - print(f'.TH "{args['module']}" 9 "enum {args['enum']}" "{self.man_= date}" "API Manual" LINUX') - - print(".SH NAME") - print(f"enum {args['enum']} \\- {args['purpose']}") - - print(".SH SYNOPSIS") - print(f"enum {args['enum']}" + " {") - - count =3D 0 - for parameter in parameterlist: - print(f'.br\n.BI " {parameter}"') - if count =3D=3D len(parameterlist) - 1: - print("\n};") - else: - print(", \n.br") - - count +=3D 1 - - print(".SH Constants") - - for parameter in parameterlist: - parameter_name =3D Re(r'\[.*').sub('', parameter) - print(f'.IP "{parameter}" 12') - self.output_highlight(args['parameterdescs'].get(parameter_nam= e, "")) - - for section in sectionlist: - print(f'.SH "{section}"') - self.output_highlight(sections[section]) - - def out_typedef(self, fname, name, args): - module =3D args.get('module') - typedef =3D args.get('typedef') - purpose =3D args.get('purpose') - sectionlist =3D args.get('sectionlist', []) - sections =3D args.get('sections', {}) - - print(f'.TH "{module}" 9 "{typedef}" "{self.man_date}" "API Manual= " LINUX') - - print(".SH NAME") - print(f"typedef {typedef} \\- {purpose}") - - for section in sectionlist: - print(f'.SH "{section}"') - self.output_highlight(sections.get(section)) - - def out_struct(self, fname, name, args): - module =3D args.get('module') - struct_type =3D args.get('type') - struct_name =3D args.get('struct') - purpose =3D args.get('purpose') - definition =3D args.get('definition') - sectionlist =3D args.get('sectionlist', []) - parameterlist =3D args.get('parameterlist', []) - sections =3D args.get('sections', {}) - parameterdescs =3D args.get('parameterdescs', {}) - - print(f'.TH "{module}" 9 "{struct_type} {struct_name}" "{self.man_= date}" "API Manual" LINUX') - - print(".SH NAME") - print(f"{struct_type} {struct_name} \\- {purpose}") - - # Replace tabs with two spaces and handle newlines - declaration =3D definition.replace("\t", " ") - declaration =3D Re(r"\n").sub('"\n.br\n.BI "', declaration) - - print(".SH SYNOPSIS") - print(f"{struct_type} {struct_name} " + "{" +"\n.br") - print(f'.BI "{declaration}\n' + "};\n.br\n") - - print(".SH Members") - for parameter in parameterlist: - if parameter.startswith("#"): - continue - - parameter_name =3D re.sub(r"\[.*", "", parameter) - - if parameterdescs.get(parameter_name) =3D=3D KernelDoc.undescr= ibed: - continue - - print(f'.IP "{parameter}" 12') - self.output_highlight(parameterdescs.get(parameter_name)) - - for section in sectionlist: - print(f'.SH "{section}"') - self.output_highlight(sections.get(section)) - - -# Command line interface - +from kdoc_files import KernelFiles # pylint: disable= =3DC0413 +from kdoc_output import RestFormat, ManFormat # pylint: disable= =3DC0413 =20 DESC =3D """ Read C language source or header FILEs, extract embedded documentation com= ments, diff --git a/scripts/lib/kdoc/kdoc_output.py b/scripts/lib/kdoc/kdoc_output= .py new file mode 100755 index 000000000000..24e40b3e7d1d --- /dev/null +++ b/scripts/lib/kdoc/kdoc_output.py @@ -0,0 +1,736 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +# Copyright(c) 2025: Mauro Carvalho Chehab . +# +# pylint: disable=3DC0301,R0911,R0912,R0913,R0914,R0915,R0917 + +# TODO: implement warning filtering + +""" +Implement output filters to print kernel-doc documentation. + +The implementation uses a virtual base class (OutputFormat) which +contains a 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. Currently, there are output classes for ReST and man/troff. +""" + +import os +import re +from datetime import datetime + +from dateutil import tz + +from kdoc_parser import KernelDoc, type_param +from kdoc_re import Re + + +function_pointer =3D Re(r"([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)", cache=3DFalse) + +# match expressions used to find embedded type information +type_constant =3D Re(r"\b``([^\`]+)``\b", cache=3DFalse) +type_constant2 =3D Re(r"\%([-_*\w]+)", cache=3DFalse) +type_func =3D Re(r"(\w+)\(\)", cache=3DFalse) +type_param_ref =3D Re(r"([\!~\*]?)\@(\w*((\.\w+)|(->\w+))*(\.\.\.)?)", cac= he=3DFalse) + +# Special RST handling for func ptr params +type_fp_param =3D Re(r"\@(\w+)\(\)", cache=3DFalse) + +# Special RST handling for structs with func ptr params +type_fp_param2 =3D Re(r"\@(\w+->\S+)\(\)", cache=3DFalse) + +type_env =3D Re(r"(\$\w+)", cache=3DFalse) +type_enum =3D Re(r"\&(enum\s*([_\w]+))", cache=3DFalse) +type_struct =3D Re(r"\&(struct\s*([_\w]+))", cache=3DFalse) +type_typedef =3D Re(r"\&(typedef\s*([_\w]+))", cache=3DFalse) +type_union =3D Re(r"\&(union\s*([_\w]+))", cache=3DFalse) +type_member =3D Re(r"\&([_\w]+)(\.|->)([_\w]+)", cache=3DFalse) +type_fallback =3D Re(r"\&([_\w]+)", cache=3DFalse) +type_member_func =3D type_member + Re(r"\(\)", cache=3DFalse) + + +class OutputFormat: + # output mode. + OUTPUT_ALL =3D 0 # output all symbols and doc sections + OUTPUT_INCLUDE =3D 1 # output only specified symbols + OUTPUT_EXPORTED =3D 2 # output exported symbols + OUTPUT_INTERNAL =3D 3 # output non-exported symbols + + # Virtual member to be overriden at the inherited classes + highlights =3D [] + + def __init__(self): + """Declare internal vars and set mode to OUTPUT_ALL""" + + self.out_mode =3D self.OUTPUT_ALL + self.enable_lineno =3D None + self.nosymbol =3D {} + self.symbol =3D None + self.function_table =3D set() + self.config =3D None + + def set_config(self, config): + self.config =3D config + + def set_filter(self, export, internal, symbol, nosymbol, function_tabl= e, + enable_lineno): + """ + Initialize filter variables according with 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 =3D enable_lineno + + if symbol: + self.out_mode =3D self.OUTPUT_INCLUDE + function_table =3D symbol + elif export: + self.out_mode =3D self.OUTPUT_EXPORTED + elif internal: + self.out_mode =3D self.OUTPUT_INTERNAL + else: + self.out_mode =3D self.OUTPUT_ALL + + if nosymbol: + self.nosymbol =3D set(nosymbol) + + if function_table: + self.function_table =3D function_table + + def highlight_block(self, block): + """ + Apply the RST highlights to a sub-block of text. + """ + + for r, sub in self.highlights: + block =3D r.sub(sub, block) + + return block + + def check_doc(self, name): + """Check if DOC should be output""" + + if self.out_mode =3D=3D self.OUTPUT_ALL: + return True + + if self.out_mode =3D=3D self.OUTPUT_INCLUDE: + if name in self.nosymbol: + return False + + if name in self.function_table: + return True + + return False + + def check_declaration(self, dtype, name): + if name in self.nosymbol: + return False + + if self.out_mode =3D=3D self.OUTPUT_ALL: + 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 =3D=3D self.OUTPUT_INTERNAL: + if dtype !=3D "function": + return True + + if name not in self.function_table: + return True + + return False + + def check_function(self, fname, name, args): + return True + + def check_enum(self, fname, name, args): + return True + + def check_typedef(self, fname, name, args): + return True + + def msg(self, fname, name, args): + + dtype =3D args.get('type', "") + + if dtype =3D=3D "doc": + self.out_doc(fname, name, args) + return False + + if not self.check_declaration(dtype, name): + return False + + if dtype =3D=3D "function": + self.out_function(fname, name, args) + return False + + if dtype =3D=3D "enum": + self.out_enum(fname, name, args) + return False + + if dtype =3D=3D "typedef": + self.out_typedef(fname, name, args) + return False + + if dtype in ["struct", "union"]: + self.out_struct(fname, name, args) + return False + + # Warn if some type requires an output logic + self.config.log.warning("doesn't now how to output '%s' block", + dtype) + + return True + + # Virtual methods to be overridden by inherited classes + def out_doc(self, fname, name, args): + pass + + def out_function(self, fname, name, args): + pass + + def out_enum(self, fname, name, args): + pass + + def out_typedef(self, fname, name, args): + pass + + def out_struct(self, fname, name, args): + pass + + +class RestFormat(OutputFormat): + # """Consts and functions used by ReST output""" + + highlights =3D [ + (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 =3D "\n" + + sphinx_literal =3D Re(r'^[^.].*::$', cache=3DFalse) + sphinx_cblock =3D Re(r'^\.\.\ +code-block::', cache=3DFalse) + + def __init__(self): + """ + Creates class variables. + + Not really mandatory, but it is a good coding style and makes + pylint happy. + """ + + super().__init__() + self.lineprefix =3D "" + + def print_lineno(self, ln): + """Outputs a line number""" + + if self.enable_lineno and ln: + print(f".. LINENO {ln}") + + def output_highlight(self, args): + input_text =3D args + output =3D "" + in_literal =3D False + litprefix =3D "" + block =3D "" + + 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 blo= ck, + # figure out the proper indent. + if not litprefix: + r =3D Re(r'^(\s*)') + if r.match(line): + litprefix =3D '^' + r.group(1) + else: + litprefix =3D "" + + output +=3D line + "\n" + elif not Re(litprefix).match(line): + in_literal =3D False + else: + output +=3D line + "\n" + else: + output +=3D line + "\n" + + # Not in a literal block (or just dropped out) + if not in_literal: + block +=3D line + "\n" + if self.sphinx_literal.match(line) or self.sphinx_cblock.m= atch(line): + in_literal =3D True + litprefix =3D "" + output +=3D self.highlight_block(block) + block =3D "" + + # Handle any remaining block + if block: + output +=3D self.highlight_block(block) + + # Print the output with the line prefix + for line in output.strip("\n").split("\n"): + print(self.lineprefix + line) + + def out_section(self, args, out_reference=3DFalse): + """ + Outputs a block section. + + This could use some work; it's used to output the DOC: sections, a= nd + starts by putting out the name of the doc section itself, but that + tends to duplicate a header already in the template file. + """ + + sectionlist =3D args.get('sectionlist', []) + sections =3D args.get('sections', {}) + section_start_lines =3D args.get('section_start_lines', {}) + + for section in sectionlist: + # Skip sections that are in the nosymbol_table + if section in self.nosymbol: + continue + + if not self.out_mode =3D=3D self.OUTPUT_INCLUDE: + if out_reference: + print(f".. _{section}:\n") + + if not self.symbol: + print(f'{self.lineprefix}**{section}**\n') + + self.print_lineno(section_start_lines.get(section, 0)) + self.output_highlight(sections[section]) + print() + print() + + def out_doc(self, fname, name, args): + if not self.check_doc(name): + return + + self.out_section(args, out_reference=3DTrue) + + def out_function(self, fname, name, args): + + oldprefix =3D self.lineprefix + signature =3D "" + + func_macro =3D args.get('func_macro', False) + if func_macro: + signature =3D args['function'] + else: + if args.get('functiontype'): + signature =3D args['functiontype'] + " " + signature +=3D args['function'] + " (" + + parameterlist =3D args.get('parameterlist', []) + parameterdescs =3D args.get('parameterdescs', {}) + parameterdesc_start_lines =3D args.get('parameterdesc_start_lines'= , {}) + + ln =3D args.get('ln', 0) + + count =3D 0 + for parameter in parameterlist: + if count !=3D 0: + signature +=3D ", " + count +=3D 1 + dtype =3D args['parametertypes'].get(parameter, "") + + if function_pointer.search(dtype): + signature +=3D function_pointer.group(1) + parameter + fun= ction_pointer.group(3) + else: + signature +=3D dtype + + if not func_macro: + signature +=3D ")" + + if args.get('typedef') or not args.get('functiontype'): + print(f".. c:macro:: {args['function']}\n") + + if args.get('typedef'): + self.print_lineno(ln) + print(" **Typedef**: ", end=3D"") + self.lineprefix =3D "" + self.output_highlight(args.get('purpose', "")) + print("\n\n**Syntax**\n") + print(f" ``{signature}``\n") + else: + print(f"``{signature}``\n") + else: + print(f".. c:function:: {signature}\n") + + if not args.get('typedef'): + self.print_lineno(ln) + self.lineprefix =3D " " + self.output_highlight(args.get('purpose', "")) + print() + + # Put descriptive text into a container (HTML
) to help set + # function prototypes apart + self.lineprefix =3D " " + + if parameterlist: + print(".. container:: kernelindent\n") + print(f"{self.lineprefix}**Parameters**\n") + + for parameter in parameterlist: + parameter_name =3D Re(r'\[.*').sub('', parameter) + dtype =3D args['parametertypes'].get(parameter, "") + + if dtype: + print(f"{self.lineprefix}``{dtype}``") + else: + print(f"{self.lineprefix}``{parameter}``") + + self.print_lineno(parameterdesc_start_lines.get(parameter_name= , 0)) + + self.lineprefix =3D " " + if parameter_name in parameterdescs and \ + parameterdescs[parameter_name] !=3D KernelDoc.undescribed: + + self.output_highlight(parameterdescs[parameter_name]) + print() + else: + print(f"{self.lineprefix}*undescribed*\n") + self.lineprefix =3D " " + + self.out_section(args) + self.lineprefix =3D oldprefix + + def out_enum(self, fname, name, args): + + oldprefix =3D self.lineprefix + name =3D args.get('enum', '') + parameterlist =3D args.get('parameterlist', []) + parameterdescs =3D args.get('parameterdescs', {}) + ln =3D args.get('ln', 0) + + print(f"\n\n.. c:enum:: {name}\n") + + self.print_lineno(ln) + self.lineprefix =3D " " + self.output_highlight(args.get('purpose', '')) + print() + + print(".. container:: kernelindent\n") + outer =3D self.lineprefix + " " + self.lineprefix =3D outer + " " + print(f"{outer}**Constants**\n") + + for parameter in parameterlist: + print(f"{outer}``{parameter}``") + + if parameterdescs.get(parameter, '') !=3D KernelDoc.undescribe= d: + self.output_highlight(parameterdescs[parameter]) + else: + print(f"{self.lineprefix}*undescribed*\n") + print() + + self.lineprefix =3D oldprefix + self.out_section(args) + + def out_typedef(self, fname, name, args): + + oldprefix =3D self.lineprefix + name =3D args.get('typedef', '') + ln =3D args.get('ln', 0) + + print(f"\n\n.. c:type:: {name}\n") + + self.print_lineno(ln) + self.lineprefix =3D " " + + self.output_highlight(args.get('purpose', '')) + + print() + + self.lineprefix =3D oldprefix + self.out_section(args) + + def out_struct(self, fname, name, args): + + name =3D args.get('struct', "") + purpose =3D args.get('purpose', "") + declaration =3D args.get('definition', "") + dtype =3D args.get('type', "struct") + ln =3D args.get('ln', 0) + + parameterlist =3D args.get('parameterlist', []) + parameterdescs =3D args.get('parameterdescs', {}) + parameterdesc_start_lines =3D args.get('parameterdesc_start_lines'= , {}) + + print(f"\n\n.. c:{dtype}:: {name}\n") + + self.print_lineno(ln) + + oldprefix =3D self.lineprefix + self.lineprefix +=3D " " + + self.output_highlight(purpose) + print() + + print(".. container:: kernelindent\n") + print(f"{self.lineprefix}**Definition**::\n") + + self.lineprefix =3D self.lineprefix + " " + + declaration =3D declaration.replace("\t", self.lineprefix) + + print(f"{self.lineprefix}{dtype} {name}" + ' {') + print(f"{declaration}{self.lineprefix}" + "};\n") + + self.lineprefix =3D " " + print(f"{self.lineprefix}**Members**\n") + for parameter in parameterlist: + if not parameter or parameter.startswith("#"): + continue + + parameter_name =3D parameter.split("[", maxsplit=3D1)[0] + + if parameterdescs.get(parameter_name) =3D=3D KernelDoc.undescr= ibed: + continue + + self.print_lineno(parameterdesc_start_lines.get(parameter_name= , 0)) + + print(f"{self.lineprefix}``{parameter}``") + + self.lineprefix =3D " " + self.output_highlight(parameterdescs[parameter_name]) + self.lineprefix =3D " " + + print() + + print() + + self.lineprefix =3D oldprefix + self.out_section(args) + + +class ManFormat(OutputFormat): + """Consts and functions used by man pages output""" + + highlights =3D ( + (type_constant, r"\1"), + (type_constant2, r"\1"), + (type_func, r"\\fB\1\\fP"), + (type_enum, r"\\fI\1\\fP"), + (type_struct, r"\\fI\1\\fP"), + (type_typedef, r"\\fI\1\\fP"), + (type_union, r"\\fI\1\\fP"), + (type_param, r"\\fI\1\\fP"), + (type_param_ref, r"\\fI\1\2\\fP"), + (type_member, r"\\fI\1\2\3\\fP"), + (type_fallback, r"\\fI\1\\fP") + ) + blankline =3D "" + + def __init__(self): + """ + Creates class variables. + + Not really mandatory, but it is a good coding style and makes + pylint happy. + """ + + super().__init__() + + dt =3D datetime.now() + if os.environ.get("KBUILD_BUILD_TIMESTAMP", None): + # use UTC TZ + to_zone =3D tz.gettz('UTC') + dt =3D dt.astimezone(to_zone) + + self.man_date =3D dt.strftime("%B %Y") + + def output_highlight(self, block): + + contents =3D self.highlight_block(block) + + if isinstance(contents, list): + contents =3D "\n".join(contents) + + for line in contents.strip("\n").split("\n"): + line =3D Re(r"^\s*").sub("", line) + + if line and line[0] =3D=3D ".": + print("\\&" + line) + else: + print(line) + + def out_doc(self, fname, name, args): + module =3D args.get('module') + sectionlist =3D args.get('sectionlist', []) + sections =3D args.get('sections', {}) + + print(f'.TH "{module}" 9 "{module}" "{self.man_date}" "API Manual"= LINUX') + + for section in sectionlist: + print(f'.SH "{section}"') + self.output_highlight(sections.get(section)) + + def out_function(self, fname, name, args): + """output function in man""" + + parameterlist =3D args.get('parameterlist', []) + parameterdescs =3D args.get('parameterdescs', {}) + sectionlist =3D args.get('sectionlist', []) + sections =3D args.get('sections', {}) + + print(f'.TH "{args['function']}" 9 "{args['function']}" "{self.man= _date}" "Kernel Hacker\'s Manual" LINUX') + + print(".SH NAME") + print(f"{args['function']} \\- {args['purpose']}") + + print(".SH SYNOPSIS") + if args.get('functiontype', ''): + print(f'.B "{args['functiontype']}" {args['function']}') + else: + print(f'.B "{args['function']}') + + count =3D 0 + parenth =3D "(" + post =3D "," + + for parameter in parameterlist: + if count =3D=3D len(parameterlist) - 1: + post =3D ");" + + dtype =3D args['parametertypes'].get(parameter, "") + if function_pointer.match(dtype): + # Pointer-to-function + print(f'".BI "{parenth}{function_pointer.group(1)}" " ") (= {function_pointer.group(2)}){post}"') + else: + dtype =3D Re(r'([^\*])$').sub(r'\1 ', dtype) + + print(f'.BI "{parenth}{dtype}" "{post}"') + count +=3D 1 + parenth =3D "" + + if parameterlist: + print(".SH ARGUMENTS") + + for parameter in parameterlist: + parameter_name =3D re.sub(r'\[.*', '', parameter) + + print(f'.IP "{parameter}" 12') + self.output_highlight(parameterdescs.get(parameter_name, "")) + + for section in sectionlist: + print(f'.SH "{section.upper()}"') + self.output_highlight(sections[section]) + + def out_enum(self, fname, name, args): + + name =3D args.get('enum', '') + parameterlist =3D args.get('parameterlist', []) + sectionlist =3D args.get('sectionlist', []) + sections =3D args.get('sections', {}) + + print(f'.TH "{args['module']}" 9 "enum {args['enum']}" "{self.man_= date}" "API Manual" LINUX') + + print(".SH NAME") + print(f"enum {args['enum']} \\- {args['purpose']}") + + print(".SH SYNOPSIS") + print(f"enum {args['enum']}" + " {") + + count =3D 0 + for parameter in parameterlist: + print(f'.br\n.BI " {parameter}"') + if count =3D=3D len(parameterlist) - 1: + print("\n};") + else: + print(", \n.br") + + count +=3D 1 + + print(".SH Constants") + + for parameter in parameterlist: + parameter_name =3D Re(r'\[.*').sub('', parameter) + print(f'.IP "{parameter}" 12') + self.output_highlight(args['parameterdescs'].get(parameter_nam= e, "")) + + for section in sectionlist: + print(f'.SH "{section}"') + self.output_highlight(sections[section]) + + def out_typedef(self, fname, name, args): + module =3D args.get('module') + typedef =3D args.get('typedef') + purpose =3D args.get('purpose') + sectionlist =3D args.get('sectionlist', []) + sections =3D args.get('sections', {}) + + print(f'.TH "{module}" 9 "{typedef}" "{self.man_date}" "API Manual= " LINUX') + + print(".SH NAME") + print(f"typedef {typedef} \\- {purpose}") + + for section in sectionlist: + print(f'.SH "{section}"') + self.output_highlight(sections.get(section)) + + def out_struct(self, fname, name, args): + module =3D args.get('module') + struct_type =3D args.get('type') + struct_name =3D args.get('struct') + purpose =3D args.get('purpose') + definition =3D args.get('definition') + sectionlist =3D args.get('sectionlist', []) + parameterlist =3D args.get('parameterlist', []) + sections =3D args.get('sections', {}) + parameterdescs =3D args.get('parameterdescs', {}) + + print(f'.TH "{module}" 9 "{struct_type} {struct_name}" "{self.man_= date}" "API Manual" LINUX') + + print(".SH NAME") + print(f"{struct_type} {struct_name} \\- {purpose}") + + # Replace tabs with two spaces and handle newlines + declaration =3D definition.replace("\t", " ") + declaration =3D Re(r"\n").sub('"\n.br\n.BI "', declaration) + + print(".SH SYNOPSIS") + print(f"{struct_type} {struct_name} " + "{" + "\n.br") + print(f'.BI "{declaration}\n' + "};\n.br\n") + + print(".SH Members") + for parameter in parameterlist: + if parameter.startswith("#"): + continue + + parameter_name =3D re.sub(r"\[.*", "", parameter) + + if parameterdescs.get(parameter_name) =3D=3D KernelDoc.undescr= ibed: + continue + + print(f'.IP "{parameter}" 12') + self.output_highlight(parameterdescs.get(parameter_name)) + + for section in sectionlist: + print(f'.SH "{section}"') + self.output_highlight(sections.get(section)) --=20 2.49.0