From nobody Tue Dec 2 02:44:19 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 E3021366DB0; Tue, 18 Nov 2025 19:09:41 +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=1763492982; cv=none; b=O3wVUMGiZdOuzPSh4xilnY2FGLtOuHHqNNt+ZjFhOWmXe0uv9CoX2Ct8koXo4z34DnfU6T/SGkwbAzmhwMt4CoxFwcq0jemM1UWpEGo5otZQcFMzghqnXiLu5k4uoehOoHwsDYjjbKuax5+trsZFD6pqm0a3UnPXubkwWwCC8IA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763492982; c=relaxed/simple; bh=JSYeCHfzPfIGscZTpH7Zd15FwY4Vw9eJSwNML06KvQs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=pWXRiQd3UelE7bR44F60eY90teWC+qU9C+kR4TWkIqVPPXZV+Vie74nPn/CHiWm7wrgto3IdrcosmUGJDStBrAglAdDXfFSdo8CMGcnOn97h/N+lPLtsbk8DqMDsd6Dz4a2dEEAu775N4Geinz3FZRxQSSy4kNC36y8vIkWXpMI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=XUjWugWA; 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="XUjWugWA" Received: by smtp.kernel.org (Postfix) with ESMTPSA id E10AAC4AF0B; Tue, 18 Nov 2025 19:09:40 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1763492981; bh=JSYeCHfzPfIGscZTpH7Zd15FwY4Vw9eJSwNML06KvQs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=XUjWugWARpB4BtzYNGI9/tEebbYxlPCrxs7RAKDV13/F3OqquAT+JcVGcKEP0Wkv+ fGWygwjdt9yQWumbM5eV8wVSj8dgquAU2rDNU1iR08NbMd7F7QR9IqolVbiUSpnRPv +joroYO66/Z0fihsT44W1EI9ZSTBlvtyJhy0cXNsPu6z9vqwzCzK+pDQSo70MXdCdU k44r+5mGkzpLUHW0qq8qgj34mu5QCwitffTA4XRvUhef/oIPNvoEnmY/CKyFXrlpNV 5XPl4PgvtIDBvmfjAimnssFvC9IwK+IGWq4DwDp2Fcgl44GFQ3z3TURiQmzm3FefK6 ljZeCUYKA/aJg== Received: from mchehab by mail.kernel.org with local (Exim 4.99) (envelope-from ) id 1vLR58-000000042hQ-18if; Tue, 18 Nov 2025 20:09:38 +0100 From: Mauro Carvalho Chehab To: "Jonathan Corbet" , Linux Doc Mailing List Cc: Mauro Carvalho Chehab , "Mauro Carvalho Chehab" , linux-kernel@vger.kernel.org Subject: [PATCH v2 1/3] tools/docs/get_feat.py: convert get_feat.pl to Python Date: Tue, 18 Nov 2025 20:09:26 +0100 Message-ID: <03c26cee1ec567804735a33047e625ef5ab7bfa8.1763492868.git.mchehab+huawei@kernel.org> X-Mailer: git-send-email 2.51.1 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-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Sender: Mauro Carvalho Chehab As we want to call Python code directly at the Sphinx extension, convert get_feat.pl to Python. The code was made to be (almost) bug-compatible with the Perl version, with two exceptions: 1. Currently, Perl script outputs a wrong table if arch is set to a non-existing value; 2. the ReST table output when --feat is used without --arch has an invalid format, as the number of characters for the table delimiters are wrong. Those two bugs were fixed while testing the conversion. Additionally, another caveat was solved: the output when --feat is used without arch and the feature doesn't exist doesn't contain an empty table anymore. Signed-off-by: Mauro Carvalho Chehab --- Documentation/sphinx/kernel_feat.py | 5 + tools/docs/get_feat.py | 225 +++++++++++ tools/lib/python/feat/parse_features.py | 494 ++++++++++++++++++++++++ 3 files changed, 724 insertions(+) create mode 100755 tools/docs/get_feat.py create mode 100755 tools/lib/python/feat/parse_features.py diff --git a/Documentation/sphinx/kernel_feat.py b/Documentation/sphinx/ker= nel_feat.py index 81c67ef23d8d..1dcbfe335a65 100644 --- a/Documentation/sphinx/kernel_feat.py +++ b/Documentation/sphinx/kernel_feat.py @@ -42,6 +42,11 @@ from docutils.statemachine import ViewList from docutils.parsers.rst import directives, Directive from sphinx.util.docutils import switch_source_input =20 +srctree =3D os.path.abspath(os.environ["srctree"]) +sys.path.insert(0, os.path.join(srctree, "tools/docs/lib")) + +from parse_features import ParseFeature # pylint: disable= =3DC0413 + def ErrorString(exc): # Shamelessly stolen from docutils return f'{exc.__class__.__name}: {exc}' =20 diff --git a/tools/docs/get_feat.py b/tools/docs/get_feat.py new file mode 100755 index 000000000000..2b5155a1f134 --- /dev/null +++ b/tools/docs/get_feat.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python3 +# pylint: disable=3DR0902,R0911,R0912,R0914,R0915 +# Copyright(c) 2025: Mauro Carvalho Chehab . +# SPDX-License-Identifier: GPL-2.0 + + +""" +Parse the Linux Feature files and produce a ReST book. +""" + +import argparse +import os +import subprocess +import sys + +from pprint import pprint + +LIB_DIR =3D "../../tools/lib/python" +SRC_DIR =3D os.path.dirname(os.path.realpath(__file__)) + +sys.path.insert(0, os.path.join(SRC_DIR, LIB_DIR)) + +from feat.parse_features import ParseFeature # pylint: disa= ble=3DC0413 + +SRCTREE =3D os.path.join(os.path.dirname(os.path.realpath(__file__)), "../= ..") +DEFAULT_DIR =3D "Documentation/features" + + +class GetFeature: + """Helper class to parse feature parsing parameters""" + + @staticmethod + def get_current_arch(): + """Detects the current architecture""" + + proc =3D subprocess.run(["uname", "-m"], check=3DTrue, + capture_output=3DTrue, text=3DTrue) + + arch =3D proc.stdout.strip() + if arch in ["x86_64", "i386"]: + arch =3D "x86" + elif arch =3D=3D "s390x": + arch =3D "s390" + + return arch + + def run_parser(self, args): + """Execute the feature parser""" + + feat =3D ParseFeature(args.directory, args.debug, args.enable_fnam= e) + data =3D feat.parse() + + if args.debug > 2: + pprint(data) + + return feat + + def run_rest(self, args): + """ + Generate tables in ReST format. Three types of tables are + supported, depending on the calling arguments: + + - neither feature nor arch is passed: generates a full matrix; + - arch provided: generates a table of supported tables for the + guiven architecture, eventually filtered by feature; + - only feature provided: generates a table with feature details, + showing what architectures it is implemented. + """ + + feat =3D self.run_parser(args) + + if args.arch: + rst =3D feat.output_arch_table(args.arch, args.feat) + elif args.feat: + rst =3D feat.output_feature(args.feat) + else: + rst =3D feat.output_matrix() + + print(rst) + + def run_current(self, args): + """ + Instead of using a --arch parameter, get feature for the current + architecture. + """ + + args.arch =3D self.get_current_arch() + + self.run_rest(args) + + def run_list(self, args): + """ + Generate a list of features for a given architecture, in a format + parseable by other scripts. The output format is not ReST. + """ + + if not args.arch: + args.arch =3D self.get_current_arch() + + feat =3D self.run_parser(args) + msg =3D feat.list_arch_features(args.arch, args.feat) + + print(msg) + + def parse_arch(self, parser): + """Add a --arch parsing argument""" + + parser.add_argument("--arch", + help=3D"Output features for an specific" + " architecture, optionally filtering for = a " + "single specific feature.") + + def parse_feat(self, parser): + """Add a --feat parsing argument""" + + parser.add_argument("--feat", "--feature", + help=3D"Output features for a single specific " + "feature.") + + + def current_args(self, subparsers): + """Implementscurrent argparse subparser""" + + parser =3D subparsers.add_parser("current", + formatter_class=3Dargparse.RawTextH= elpFormatter, + description=3D"Output table in ReST= " + "compatible ASCII forma= t " + "with features for this= " + "machine's architecture= ") + + self.parse_feat(parser) + parser.set_defaults(func=3Dself.run_current) + + def rest_args(self, subparsers): + """Implement rest argparse subparser""" + + parser =3D subparsers.add_parser("rest", + formatter_class=3Dargparse.RawTextH= elpFormatter, + description=3D"Output table(s) in R= eST " + "compatible ASCII forma= t " + "with features in ReST " + "markup language. The " + "output is affected by " + "--arch or --feat/--fea= ture" + " flags.") + + self.parse_arch(parser) + self.parse_feat(parser) + parser.set_defaults(func=3Dself.run_rest) + + def list_args(self, subparsers): + """Implement list argparse subparser""" + + parser =3D subparsers.add_parser("list", + formatter_class=3Dargparse.RawTextH= elpFormatter, + description=3D"List features for th= is " + "machine's architecture= , " + "using an easier to par= se " + "format. The output is " + "affected by --arch fla= g.") + + self.parse_arch(parser) + self.parse_feat(parser) + parser.set_defaults(func=3Dself.run_list) + + def validate_args(self, subparsers): + """Implement validate argparse subparser""" + + parser =3D subparsers.add_parser("validate", + formatter_class=3Dargparse.RawTextH= elpFormatter, + description=3D"Validate the content= s of " + "the files under " + f"{DEFAULT_DIR}.") + + parser.set_defaults(func=3Dself.run_parser) + + def parser(self): + """ + Create an arparse with common options and several subparsers + """ + parser =3D argparse.ArgumentParser(formatter_class=3Dargparse.RawT= extHelpFormatter) + + parser.add_argument("-d", "--debug", action=3D"count", default=3D0, + help=3D"Put the script in verbose mode, useful= for " + "debugging. Can be called multiple times,= to " + "increase verbosity.") + + parser.add_argument("--directory", "--dir", default=3DDEFAULT_DIR, + help=3D"Changes the location of the Feature fi= les. " + f"By default, it uses the {DEFAULT_DIR} " + "directory.") + + parser.add_argument("--enable-fname", action=3D"store_true", + help=3D"Prints the file name of the feature fi= les. " + "This can be used in order to track " + "dependencies during documentation build.= ") + + subparsers =3D parser.add_subparsers() + + self.current_args(subparsers) + self.rest_args(subparsers) + self.list_args(subparsers) + self.validate_args(subparsers) + + args =3D parser.parse_args() + + return args + + +def main(): + """Main program""" + + feat =3D GetFeature() + + args =3D feat.parser() + + if "func" in args: + args.func(args) + else: + sys.exit(f"Please specify a valid command for {sys.argv[0]}") + + +# Call main method +if __name__ =3D=3D "__main__": + main() diff --git a/tools/lib/python/feat/parse_features.py b/tools/lib/python/fea= t/parse_features.py new file mode 100755 index 000000000000..b88c04d3e2fe --- /dev/null +++ b/tools/lib/python/feat/parse_features.py @@ -0,0 +1,494 @@ +#!/usr/bin/env python3 +# pylint: disable=3DR0902,R0911,R0912,R0914,R0915 +# Copyright(c) 2025: Mauro Carvalho Chehab . +# SPDX-License-Identifier: GPL-2.0 + + +""" +Library to parse the Linux Feature files and produce a ReST book. +""" + +import os +import re +import sys + +from glob import iglob + + +class ParseFeature: + """ + Parses Documentation/features, allowing to generate ReST documentation + from it. + """ + + h_name =3D "Feature" + h_kconfig =3D "Kconfig" + h_description =3D "Description" + h_subsys =3D "Subsystem" + h_status =3D "Status" + h_arch =3D "Architecture" + + # Sort order for status. Others will be mapped at the end. + status_map =3D { + "ok": 0, + "TODO": 1, + "N/A": 2, + # The only missing status is "..", which was mapped as "---", + # as this is an special ReST cell value. Let it get the + # default order (99). + } + + def __init__(self, prefix, debug=3D0, enable_fname=3DFalse): + """ + Sets internal variables + """ + + self.prefix =3D prefix + self.debug =3D debug + self.enable_fname =3D enable_fname + + self.data =3D {} + + # Initial maximum values use just the headers + self.max_size_name =3D len(self.h_name) + self.max_size_kconfig =3D len(self.h_kconfig) + self.max_size_description =3D len(self.h_description) + self.max_size_desc_word =3D 0 + self.max_size_subsys =3D len(self.h_subsys) + self.max_size_status =3D len(self.h_status) + self.max_size_arch =3D len(self.h_arch) + self.max_size_arch_with_header =3D self.max_size_arch + self.max_s= ize_arch + self.description_size =3D 1 + + self.msg =3D "" + + def emit(self, msg=3D"", end=3D"\n"): + self.msg +=3D msg + end + + def parse_error(self, fname, ln, msg, data=3DNone): + """ + Displays an error message, printing file name and line + """ + + if ln: + fname +=3D f"#{ln}" + + print(f"Warning: file {fname}: {msg}", file=3Dsys.stderr, end=3D"") + + if data: + data =3D data.rstrip() + print(f":\n\t{data}", file=3Dsys.stderr) + else: + print("", file=3Dsys.stderr) + + def parse_feat_file(self, fname): + """Parses a single arch-support.txt feature file""" + + if os.path.isdir(fname): + return + + base =3D os.path.basename(fname) + + if base !=3D "arch-support.txt": + if self.debug: + print(f"ignoring {fname}", file=3Dsys.stderr) + return + + subsys =3D os.path.dirname(fname).split("/")[-2] + self.max_size_subsys =3D max(self.max_size_subsys, len(subsys)) + + feature_name =3D "" + kconfig =3D "" + description =3D "" + comments =3D "" + arch_table =3D {} + + if self.debug > 1: + print(f"Opening {fname}", file=3Dsys.stderr) + + if self.enable_fname: + full_fname =3D os.path.abspath(fname) + self.emit(f".. FILE {full_fname}") + + with open(fname, encoding=3D"utf-8") as f: + for ln, line in enumerate(f, start=3D1): + line =3D line.strip() + + match =3D re.match(r"^\#\s+Feature\s+name:\s*(.*\S)", line) + if match: + feature_name =3D match.group(1) + + self.max_size_name =3D max(self.max_size_name, + len(feature_name)) + continue + + match =3D re.match(r"^\#\s+Kconfig:\s*(.*\S)", line) + if match: + kconfig =3D match.group(1) + + self.max_size_kconfig =3D max(self.max_size_kconfig, + len(kconfig)) + continue + + match =3D re.match(r"^\#\s+description:\s*(.*\S)", line) + if match: + description =3D match.group(1) + + self.max_size_description =3D max(self.max_size_descri= ption, + len(description)) + + words =3D re.split(r"\s+", line)[1:] + for word in words: + self.max_size_desc_word =3D max(self.max_size_desc= _word, + len(word)) + + continue + + if re.search(r"^\\s*$", line): + continue + + if re.match(r"^\s*\-+\s*$", line): + continue + + if re.search(r"^\s*\|\s*arch\s*\|\s*status\s*\|\s*$", line= ): + continue + + match =3D re.match(r"^\#\s*(.*)$", line) + if match: + comments +=3D match.group(1) + continue + + match =3D re.match(r"^\s*\|\s*(\S+):\s*\|\s*(\S+)\s*\|\s*$= ", line) + if match: + arch =3D match.group(1) + status =3D match.group(2) + + self.max_size_status =3D max(self.max_size_status, + len(status)) + self.max_size_arch =3D max(self.max_size_arch, len(arc= h)) + + if status =3D=3D "..": + status =3D "---" + + arch_table[arch] =3D status + + continue + + self.parse_error(fname, ln, "Line is invalid", line) + + if not feature_name: + self.parse_error(fname, 0, "Feature name not found") + return + if not subsys: + self.parse_error(fname, 0, "Subsystem not found") + return + if not kconfig: + self.parse_error(fname, 0, "Kconfig not found") + return + if not description: + self.parse_error(fname, 0, "Description not found") + return + if not arch_table: + self.parse_error(fname, 0, "Architecture table not found") + return + + self.data[feature_name] =3D { + "where": fname, + "subsys": subsys, + "kconfig": kconfig, + "description": description, + "comments": comments, + "table": arch_table, + } + + self.max_size_arch_with_header =3D self.max_size_arch + len(self.h= _arch) + + def parse(self): + """Parses all arch-support.txt feature files inside self.prefix""" + + path =3D os.path.expanduser(self.prefix) + + if self.debug > 2: + print(f"Running parser for {path}") + + example_path =3D os.path.join(path, "arch-support.txt") + + for fname in iglob(os.path.join(path, "**"), recursive=3DTrue): + if fname !=3D example_path: + self.parse_feat_file(fname) + + return self.data + + def output_arch_table(self, arch, feat=3DNone): + """ + Output feature(s) for a given architecture. + """ + + title =3D f"Feature status on {arch} architecture" + + self.emit("=3D" * len(title)) + self.emit(title) + self.emit("=3D" * len(title)) + self.emit() + + self.emit("=3D" * self.max_size_subsys + " ", end=3D"") + self.emit("=3D" * self.max_size_name + " ", end=3D"") + self.emit("=3D" * self.max_size_kconfig + " ", end=3D"") + self.emit("=3D" * self.max_size_status + " ", end=3D"") + self.emit("=3D" * self.max_size_description) + + self.emit(f"{self.h_subsys:<{self.max_size_subsys}} ", end=3D"") + self.emit(f"{self.h_name:<{self.max_size_name}} ", end=3D"") + self.emit(f"{self.h_kconfig:<{self.max_size_kconfig}} ", end=3D"") + self.emit(f"{self.h_status:<{self.max_size_status}} ", end=3D"") + self.emit(f"{self.h_description:<{self.max_size_description}}") + + self.emit("=3D" * self.max_size_subsys + " ", end=3D"") + self.emit("=3D" * self.max_size_name + " ", end=3D"") + self.emit("=3D" * self.max_size_kconfig + " ", end=3D"") + self.emit("=3D" * self.max_size_status + " ", end=3D"") + self.emit("=3D" * self.max_size_description) + + sorted_features =3D sorted(self.data.keys(), + key=3Dlambda x: (self.data[x]["subsys"], + x.lower())) + + for name in sorted_features: + if feat and name !=3D feat: + continue + + arch_table =3D self.data[name]["table"] + + if not arch in arch_table: + continue + + self.emit(f"{self.data[name]['subsys']:<{self.max_size_subsys}= } ", + end=3D"") + self.emit(f"{name:<{self.max_size_name}} ", end=3D"") + self.emit(f"{self.data[name]['kconfig']:<{self.max_size_kconfi= g}} ", + end=3D"") + self.emit(f"{arch_table[arch]:<{self.max_size_status}} ", + end=3D"") + self.emit(f"{self.data[name]['description']}") + + self.emit("=3D" * self.max_size_subsys + " ", end=3D"") + self.emit("=3D" * self.max_size_name + " ", end=3D"") + self.emit("=3D" * self.max_size_kconfig + " ", end=3D"") + self.emit("=3D" * self.max_size_status + " ", end=3D"") + self.emit("=3D" * self.max_size_description) + + return self.msg + + def output_feature(self, feat): + """ + Output a feature on all architectures + """ + + title =3D f"Feature {feat}" + + self.emit("=3D" * len(title)) + self.emit(title) + self.emit("=3D" * len(title)) + self.emit() + + if not feat in self.data: + return + + if self.data[feat]["subsys"]: + self.emit(f":Subsystem: {self.data[feat]['subsys']}") + if self.data[feat]["kconfig"]: + self.emit(f":Kconfig: {self.data[feat]['kconfig']}") + + desc =3D self.data[feat]["description"] + desc =3D desc[0].upper() + desc[1:] + desc =3D desc.rstrip(". \t") + self.emit(f"\n{desc}.\n") + + com =3D self.data[feat]["comments"].strip() + if com: + self.emit("Comments") + self.emit("--------") + self.emit(f"\n{com}\n") + + self.emit("=3D" * self.max_size_arch + " ", end=3D"") + self.emit("=3D" * self.max_size_status) + + self.emit(f"{self.h_arch:<{self.max_size_arch}} ", end=3D"") + self.emit(f"{self.h_status:<{self.max_size_status}}") + + self.emit("=3D" * self.max_size_arch + " ", end=3D"") + self.emit("=3D" * self.max_size_status) + + arch_table =3D self.data[feat]["table"] + for arch in sorted(arch_table.keys()): + self.emit(f"{arch:<{self.max_size_arch}} ", end=3D"") + self.emit(f"{arch_table[arch]:<{self.max_size_status}}") + + self.emit("=3D" * self.max_size_arch + " ", end=3D"") + self.emit("=3D" * self.max_size_status) + + return self.msg + + def matrix_lines(self, desc_size, max_size_status, header): + """ + Helper function to split element tables at the output matrix + """ + + if header: + ln_marker =3D "=3D" + else: + ln_marker =3D "-" + + self.emit("+" + ln_marker * self.max_size_name + "+", end=3D"") + self.emit(ln_marker * desc_size, end=3D"") + self.emit("+" + ln_marker * max_size_status + "+") + + def output_matrix(self): + """ + Generates a set of tables, groped by subsystem, containing + what's the feature state on each architecture. + """ + + title =3D "Feature status on all architectures" + + self.emit("=3D" * len(title)) + self.emit(title) + self.emit("=3D" * len(title)) + self.emit() + + desc_title =3D f"{self.h_kconfig} / {self.h_description}" + + desc_size =3D self.max_size_kconfig + 4 + if not self.description_size: + desc_size =3D max(self.max_size_description, desc_size) + else: + desc_size =3D max(self.description_size, desc_size) + + desc_size =3D max(self.max_size_desc_word, desc_size, len(desc_tit= le)) + + notcompat =3D "Not compatible" + self.max_size_status =3D max(self.max_size_status, len(notcompat)) + + min_status_size =3D self.max_size_status + self.max_size_arch + 4 + max_size_status =3D max(min_status_size, self.max_size_status) + + h_status_per_arch =3D "Status per architecture" + max_size_status =3D max(max_size_status, len(h_status_per_arch)) + + cur_subsys =3D None + for name in sorted(self.data.keys(), + key=3Dlambda x: (self.data[x]["subsys"], x.lowe= r())): + if not cur_subsys or cur_subsys !=3D self.data[name]["subsys"]: + if cur_subsys: + self.emit() + + cur_subsys =3D self.data[name]["subsys"] + + title =3D f"Subsystem: {cur_subsys}" + self.emit(title) + self.emit("=3D" * len(title)) + self.emit() + + self.matrix_lines(desc_size, max_size_status, 0) + + self.emit(f"|{self.h_name:<{self.max_size_name}}", end=3D"= ") + self.emit(f"|{desc_title:<{desc_size}}", end=3D"") + self.emit(f"|{h_status_per_arch:<{max_size_status}}|") + + self.matrix_lines(desc_size, max_size_status, 1) + + lines =3D [] + descs =3D [] + cur_status =3D "" + line =3D "" + + arch_table =3D sorted(self.data[name]["table"].items(), + key=3Dlambda x: (self.status_map.get(x[1],= 99), + x[0].lower())) + + for arch, status in arch_table: + if status =3D=3D "---": + status =3D notcompat + + if status !=3D cur_status: + if line !=3D "": + lines.append(line) + line =3D "" + line =3D f"- **{status}**: {arch}" + elif len(line) + len(arch) + 2 < max_size_status: + line +=3D f", {arch}" + else: + lines.append(line) + line =3D f" {arch}" + cur_status =3D status + + if line !=3D "": + lines.append(line) + + description =3D self.data[name]["description"] + while len(description) > desc_size: + desc_line =3D description[:desc_size] + + last_space =3D desc_line.rfind(" ") + if last_space !=3D -1: + desc_line =3D desc_line[:last_space] + descs.append(desc_line) + description =3D description[last_space + 1:] + else: + desc_line =3D desc_line[:-1] + descs.append(desc_line + "\\") + description =3D description[len(desc_line):] + + if description: + descs.append(description) + + while len(lines) < 2 + len(descs): + lines.append("") + + for ln, line in enumerate(lines): + col =3D ["", ""] + + if not ln: + col[0] =3D name + col[1] =3D f"``{self.data[name]['kconfig']}``" + else: + if ln >=3D 2 and descs: + col[1] =3D descs.pop(0) + + self.emit(f"|{col[0]:<{self.max_size_name}}", end=3D"") + self.emit(f"|{col[1]:<{desc_size}}", end=3D"") + self.emit(f"|{line:<{max_size_status}}|") + + self.matrix_lines(desc_size, max_size_status, 0) + + return self.msg + + def list_arch_features(self, arch, feat): + """ + Print a matrix of kernel feature support for the chosen architectu= re. + """ + self.emit("#") + self.emit(f"# Kernel feature support matrix of the '{arch}' archit= ecture:") + self.emit("#") + + # Sort by subsystem, then by feature name (case=E2=80=91insensitiv= e) + for name in sorted(self.data.keys(), + key=3Dlambda n: (self.data[n]["subsys"].lower(), + n.lower())): + if feat and name !=3D feat: + continue + + feature =3D self.data[name] + arch_table =3D feature["table"] + status =3D arch_table.get(arch, "") + status =3D " " * ((4 - len(status)) // 2) + status + + self.emit(f"{feature['subsys']:>{self.max_size_subsys + 1}}/ ", + end=3D"") + self.emit(f"{name:<{self.max_size_name}}: ", end=3D"") + self.emit(f"{status:<5}| ", end=3D"") + self.emit(f"{feature['kconfig']:>{self.max_size_kconfig}} ", + end=3D"") + self.emit(f"# {feature['description']}") + + return self.msg --=20 2.51.1