linux-doc.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 0/3] Convert get_feat to Python
@ 2025-11-18 19:09 Mauro Carvalho Chehab
  2025-11-18 19:09 ` [PATCH v2 1/3] tools/docs/get_feat.py: convert get_feat.pl " Mauro Carvalho Chehab
                   ` (3 more replies)
  0 siblings, 4 replies; 6+ messages in thread
From: Mauro Carvalho Chehab @ 2025-11-18 19:09 UTC (permalink / raw)
  To: Jonathan Corbet, Linux Doc Mailing List
  Cc: Mauro Carvalho Chehab, Mauro Carvalho Chehab, linux-kernel

Hi Jon,

That's the final series to complete the migration of documentation
build: it converts get_feat from Perl to Python.

V2 is technically identical to v1: the only difference is that it
now uses tools/lib/python/feat to store the library logic.

With that, no Sphinx in-kernel extensions use fork anymore to call
ancillary scripts: everything is now importing Python methods
directly from the libraries.

As we don't have a definition yet for the location of the Python
libraries yet, I opted to place the parse features library inside
tools/docs/lib. It shouldn't be hard to move it elsewhere once we
define a better place for them.

There's nothing special on this conversion: it is a direct translation,
almost bug-compatible with the original version (*).

(*) I did solve two or three caveats on patch 1.

Most of the complexity of the script relies at the logic to produce
ReST tables. I do have here on my internal scripts a (somewhat) generic
formatter for ReST tables in Python. I was tempted to convert the logic
to use it, but, as this could cause regressions, I opted to not do it
right now, mainly because the matrix table logic is complex. Also,
I'm tempted to modify a little bit the output there, but extra tests
are required to see if PDF output would work with complex tables (I
remember I had a problem with that in the past). So, I'm postponing
such extra cleanup.

Mauro Carvalho Chehab (3):
  tools/docs/get_feat.py: convert get_feat.pl to Python
  Documentation/sphinx/kernel_feat.py: use class directly
  get_feat.pl: remove it, as it got replaced by get_feat.py

 Documentation/sphinx/kernel_feat.py     |  24 +-
 tools/docs/get_feat.pl                  | 641 ------------------------
 tools/docs/get_feat.py                  | 225 +++++++++
 tools/lib/python/feat/parse_features.py | 494 ++++++++++++++++++
 4 files changed, 732 insertions(+), 652 deletions(-)
 delete mode 100755 tools/docs/get_feat.pl
 create mode 100755 tools/docs/get_feat.py
 create mode 100755 tools/lib/python/feat/parse_features.py

-- 
2.51.1



^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH v2 1/3] tools/docs/get_feat.py: convert get_feat.pl to Python
  2025-11-18 19:09 [PATCH v2 0/3] Convert get_feat to Python Mauro Carvalho Chehab
@ 2025-11-18 19:09 ` Mauro Carvalho Chehab
  2025-11-18 19:09 ` [PATCH v2 2/3] Documentation/sphinx/kernel_feat.py: use class directly Mauro Carvalho Chehab
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 6+ messages in thread
From: Mauro Carvalho Chehab @ 2025-11-18 19:09 UTC (permalink / raw)
  To: Jonathan Corbet, Linux Doc Mailing List
  Cc: Mauro Carvalho Chehab, Mauro Carvalho Chehab, linux-kernel

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 <mchehab+huawei@kernel.org>
---
 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/kernel_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
 
+srctree = os.path.abspath(os.environ["srctree"])
+sys.path.insert(0, os.path.join(srctree, "tools/docs/lib"))
+
+from parse_features import ParseFeature                # pylint: disable=C0413
+
 def ErrorString(exc):  # Shamelessly stolen from docutils
     return f'{exc.__class__.__name}: {exc}'
 
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=R0902,R0911,R0912,R0914,R0915
+# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
+# 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 = "../../tools/lib/python"
+SRC_DIR = 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: disable=C0413
+
+SRCTREE = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../..")
+DEFAULT_DIR = "Documentation/features"
+
+
+class GetFeature:
+    """Helper class to parse feature parsing parameters"""
+
+    @staticmethod
+    def get_current_arch():
+        """Detects the current architecture"""
+
+        proc = subprocess.run(["uname", "-m"], check=True,
+                              capture_output=True, text=True)
+
+        arch = proc.stdout.strip()
+        if arch in ["x86_64", "i386"]:
+            arch = "x86"
+        elif arch == "s390x":
+            arch = "s390"
+
+        return arch
+
+    def run_parser(self, args):
+        """Execute the feature parser"""
+
+        feat = ParseFeature(args.directory, args.debug, args.enable_fname)
+        data = 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 = self.run_parser(args)
+
+        if args.arch:
+            rst = feat.output_arch_table(args.arch, args.feat)
+        elif args.feat:
+            rst = feat.output_feature(args.feat)
+        else:
+            rst = feat.output_matrix()
+
+        print(rst)
+
+    def run_current(self, args):
+        """
+        Instead of using a --arch parameter, get feature for the current
+        architecture.
+        """
+
+        args.arch = 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 = self.get_current_arch()
+
+        feat = self.run_parser(args)
+        msg = 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="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="Output features for a single specific "
+                                  "feature.")
+
+
+    def current_args(self, subparsers):
+        """Implementscurrent argparse subparser"""
+
+        parser = subparsers.add_parser("current",
+                                       formatter_class=argparse.RawTextHelpFormatter,
+                                       description="Output table in ReST "
+                                                   "compatible ASCII format "
+                                                   "with features for this "
+                                                   "machine's architecture")
+
+        self.parse_feat(parser)
+        parser.set_defaults(func=self.run_current)
+
+    def rest_args(self, subparsers):
+        """Implement rest argparse subparser"""
+
+        parser = subparsers.add_parser("rest",
+                                       formatter_class=argparse.RawTextHelpFormatter,
+                                       description="Output table(s) in ReST "
+                                                   "compatible ASCII format "
+                                                   "with features in ReST "
+                                                   "markup language. The "
+                                                   "output is affected by "
+                                                   "--arch or --feat/--feature"
+                                                   " flags.")
+
+        self.parse_arch(parser)
+        self.parse_feat(parser)
+        parser.set_defaults(func=self.run_rest)
+
+    def list_args(self, subparsers):
+        """Implement list argparse subparser"""
+
+        parser = subparsers.add_parser("list",
+                                       formatter_class=argparse.RawTextHelpFormatter,
+                                       description="List features for this "
+                                                   "machine's architecture, "
+                                                   "using an easier to parse "
+                                                   "format. The output is "
+                                                   "affected by --arch flag.")
+
+        self.parse_arch(parser)
+        self.parse_feat(parser)
+        parser.set_defaults(func=self.run_list)
+
+    def validate_args(self, subparsers):
+        """Implement validate argparse subparser"""
+
+        parser = subparsers.add_parser("validate",
+                                       formatter_class=argparse.RawTextHelpFormatter,
+                                       description="Validate the contents of "
+                                                   "the files under "
+                                                   f"{DEFAULT_DIR}.")
+
+        parser.set_defaults(func=self.run_parser)
+
+    def parser(self):
+        """
+        Create an arparse with common options and several subparsers
+        """
+        parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
+
+        parser.add_argument("-d", "--debug", action="count", default=0,
+                            help="Put the script in verbose mode, useful for "
+                                 "debugging. Can be called multiple times, to "
+                                 "increase verbosity.")
+
+        parser.add_argument("--directory", "--dir", default=DEFAULT_DIR,
+                            help="Changes the location of the Feature files. "
+                                 f"By default, it uses the {DEFAULT_DIR} "
+                                 "directory.")
+
+        parser.add_argument("--enable-fname", action="store_true",
+                            help="Prints the file name of the feature files. "
+                                 "This can be used in order to track "
+                                 "dependencies during documentation build.")
+
+        subparsers = parser.add_subparsers()
+
+        self.current_args(subparsers)
+        self.rest_args(subparsers)
+        self.list_args(subparsers)
+        self.validate_args(subparsers)
+
+        args = parser.parse_args()
+
+        return args
+
+
+def main():
+    """Main program"""
+
+    feat = GetFeature()
+
+    args = 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__ == "__main__":
+    main()
diff --git a/tools/lib/python/feat/parse_features.py b/tools/lib/python/feat/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=R0902,R0911,R0912,R0914,R0915
+# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
+# 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 = "Feature"
+    h_kconfig = "Kconfig"
+    h_description = "Description"
+    h_subsys = "Subsystem"
+    h_status = "Status"
+    h_arch = "Architecture"
+
+    # Sort order for status. Others will be mapped at the end.
+    status_map = {
+        "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=0, enable_fname=False):
+        """
+        Sets internal variables
+        """
+
+        self.prefix = prefix
+        self.debug = debug
+        self.enable_fname = enable_fname
+
+        self.data = {}
+
+        # Initial maximum values use just the headers
+        self.max_size_name = len(self.h_name)
+        self.max_size_kconfig = len(self.h_kconfig)
+        self.max_size_description = len(self.h_description)
+        self.max_size_desc_word = 0
+        self.max_size_subsys = len(self.h_subsys)
+        self.max_size_status = len(self.h_status)
+        self.max_size_arch = len(self.h_arch)
+        self.max_size_arch_with_header = self.max_size_arch + self.max_size_arch
+        self.description_size = 1
+
+        self.msg = ""
+
+    def emit(self, msg="", end="\n"):
+        self.msg += msg + end
+
+    def parse_error(self, fname, ln, msg, data=None):
+        """
+        Displays an error message, printing file name and line
+        """
+
+        if ln:
+            fname += f"#{ln}"
+
+        print(f"Warning: file {fname}: {msg}", file=sys.stderr, end="")
+
+        if data:
+            data = data.rstrip()
+            print(f":\n\t{data}", file=sys.stderr)
+        else:
+            print("", file=sys.stderr)
+
+    def parse_feat_file(self, fname):
+        """Parses a single arch-support.txt feature file"""
+
+        if os.path.isdir(fname):
+            return
+
+        base = os.path.basename(fname)
+
+        if base != "arch-support.txt":
+            if self.debug:
+                print(f"ignoring {fname}", file=sys.stderr)
+            return
+
+        subsys = os.path.dirname(fname).split("/")[-2]
+        self.max_size_subsys = max(self.max_size_subsys, len(subsys))
+
+        feature_name = ""
+        kconfig = ""
+        description = ""
+        comments = ""
+        arch_table = {}
+
+        if self.debug > 1:
+            print(f"Opening {fname}", file=sys.stderr)
+
+        if self.enable_fname:
+            full_fname = os.path.abspath(fname)
+            self.emit(f".. FILE {full_fname}")
+
+        with open(fname, encoding="utf-8") as f:
+            for ln, line in enumerate(f, start=1):
+                line = line.strip()
+
+                match = re.match(r"^\#\s+Feature\s+name:\s*(.*\S)", line)
+                if match:
+                    feature_name = match.group(1)
+
+                    self.max_size_name = max(self.max_size_name,
+                                             len(feature_name))
+                    continue
+
+                match = re.match(r"^\#\s+Kconfig:\s*(.*\S)", line)
+                if match:
+                    kconfig = match.group(1)
+
+                    self.max_size_kconfig = max(self.max_size_kconfig,
+                                                len(kconfig))
+                    continue
+
+                match = re.match(r"^\#\s+description:\s*(.*\S)", line)
+                if match:
+                    description = match.group(1)
+
+                    self.max_size_description = max(self.max_size_description,
+                                                    len(description))
+
+                    words = re.split(r"\s+", line)[1:]
+                    for word in words:
+                        self.max_size_desc_word = 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 = re.match(r"^\#\s*(.*)$", line)
+                if match:
+                    comments += match.group(1)
+                    continue
+
+                match = re.match(r"^\s*\|\s*(\S+):\s*\|\s*(\S+)\s*\|\s*$", line)
+                if match:
+                    arch = match.group(1)
+                    status = match.group(2)
+
+                    self.max_size_status = max(self.max_size_status,
+                                               len(status))
+                    self.max_size_arch = max(self.max_size_arch, len(arch))
+
+                    if status == "..":
+                        status = "---"
+
+                    arch_table[arch] = 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] = {
+            "where": fname,
+            "subsys": subsys,
+            "kconfig": kconfig,
+            "description": description,
+            "comments": comments,
+            "table": arch_table,
+        }
+
+        self.max_size_arch_with_header = self.max_size_arch + len(self.h_arch)
+
+    def parse(self):
+        """Parses all arch-support.txt feature files inside self.prefix"""
+
+        path = os.path.expanduser(self.prefix)
+
+        if self.debug > 2:
+            print(f"Running parser for {path}")
+
+        example_path = os.path.join(path, "arch-support.txt")
+
+        for fname in iglob(os.path.join(path, "**"), recursive=True):
+            if fname != example_path:
+                self.parse_feat_file(fname)
+
+        return self.data
+
+    def output_arch_table(self, arch, feat=None):
+        """
+        Output feature(s) for a given architecture.
+        """
+
+        title = f"Feature status on {arch} architecture"
+
+        self.emit("=" * len(title))
+        self.emit(title)
+        self.emit("=" * len(title))
+        self.emit()
+
+        self.emit("=" * self.max_size_subsys + "  ", end="")
+        self.emit("=" * self.max_size_name + "  ", end="")
+        self.emit("=" * self.max_size_kconfig + "  ", end="")
+        self.emit("=" * self.max_size_status + "  ", end="")
+        self.emit("=" * self.max_size_description)
+
+        self.emit(f"{self.h_subsys:<{self.max_size_subsys}}  ", end="")
+        self.emit(f"{self.h_name:<{self.max_size_name}}  ", end="")
+        self.emit(f"{self.h_kconfig:<{self.max_size_kconfig}}  ", end="")
+        self.emit(f"{self.h_status:<{self.max_size_status}}  ", end="")
+        self.emit(f"{self.h_description:<{self.max_size_description}}")
+
+        self.emit("=" * self.max_size_subsys + "  ", end="")
+        self.emit("=" * self.max_size_name + "  ", end="")
+        self.emit("=" * self.max_size_kconfig + "  ", end="")
+        self.emit("=" * self.max_size_status + "  ", end="")
+        self.emit("=" * self.max_size_description)
+
+        sorted_features = sorted(self.data.keys(),
+                                 key=lambda x: (self.data[x]["subsys"],
+                                                x.lower()))
+
+        for name in sorted_features:
+            if feat and name != feat:
+                continue
+
+            arch_table = self.data[name]["table"]
+
+            if not arch in arch_table:
+                continue
+
+            self.emit(f"{self.data[name]['subsys']:<{self.max_size_subsys}}  ",
+                  end="")
+            self.emit(f"{name:<{self.max_size_name}}  ", end="")
+            self.emit(f"{self.data[name]['kconfig']:<{self.max_size_kconfig}}  ",
+                  end="")
+            self.emit(f"{arch_table[arch]:<{self.max_size_status}}  ",
+                  end="")
+            self.emit(f"{self.data[name]['description']}")
+
+        self.emit("=" * self.max_size_subsys + "  ", end="")
+        self.emit("=" * self.max_size_name + "  ", end="")
+        self.emit("=" * self.max_size_kconfig + "  ", end="")
+        self.emit("=" * self.max_size_status + "  ", end="")
+        self.emit("=" * self.max_size_description)
+
+        return self.msg
+
+    def output_feature(self, feat):
+        """
+        Output a feature on all architectures
+        """
+
+        title = f"Feature {feat}"
+
+        self.emit("=" * len(title))
+        self.emit(title)
+        self.emit("=" * 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 = self.data[feat]["description"]
+        desc = desc[0].upper() + desc[1:]
+        desc = desc.rstrip(". \t")
+        self.emit(f"\n{desc}.\n")
+
+        com = self.data[feat]["comments"].strip()
+        if com:
+            self.emit("Comments")
+            self.emit("--------")
+            self.emit(f"\n{com}\n")
+
+        self.emit("=" * self.max_size_arch + "  ", end="")
+        self.emit("=" * self.max_size_status)
+
+        self.emit(f"{self.h_arch:<{self.max_size_arch}}  ", end="")
+        self.emit(f"{self.h_status:<{self.max_size_status}}")
+
+        self.emit("=" * self.max_size_arch + "  ", end="")
+        self.emit("=" * self.max_size_status)
+
+        arch_table = self.data[feat]["table"]
+        for arch in sorted(arch_table.keys()):
+            self.emit(f"{arch:<{self.max_size_arch}}  ", end="")
+            self.emit(f"{arch_table[arch]:<{self.max_size_status}}")
+
+        self.emit("=" * self.max_size_arch + "  ", end="")
+        self.emit("=" * 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 = "="
+        else:
+            ln_marker = "-"
+
+        self.emit("+" + ln_marker * self.max_size_name + "+", end="")
+        self.emit(ln_marker * desc_size, end="")
+        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 = "Feature status on all architectures"
+
+        self.emit("=" * len(title))
+        self.emit(title)
+        self.emit("=" * len(title))
+        self.emit()
+
+        desc_title = f"{self.h_kconfig} / {self.h_description}"
+
+        desc_size = self.max_size_kconfig + 4
+        if not self.description_size:
+            desc_size = max(self.max_size_description, desc_size)
+        else:
+            desc_size = max(self.description_size, desc_size)
+
+        desc_size = max(self.max_size_desc_word, desc_size, len(desc_title))
+
+        notcompat = "Not compatible"
+        self.max_size_status = max(self.max_size_status, len(notcompat))
+
+        min_status_size = self.max_size_status + self.max_size_arch + 4
+        max_size_status = max(min_status_size, self.max_size_status)
+
+        h_status_per_arch = "Status per architecture"
+        max_size_status = max(max_size_status, len(h_status_per_arch))
+
+        cur_subsys = None
+        for name in sorted(self.data.keys(),
+                           key=lambda x: (self.data[x]["subsys"], x.lower())):
+            if not cur_subsys or cur_subsys != self.data[name]["subsys"]:
+                if cur_subsys:
+                    self.emit()
+
+                cur_subsys = self.data[name]["subsys"]
+
+                title = f"Subsystem: {cur_subsys}"
+                self.emit(title)
+                self.emit("=" * len(title))
+                self.emit()
+
+                self.matrix_lines(desc_size, max_size_status, 0)
+
+                self.emit(f"|{self.h_name:<{self.max_size_name}}", end="")
+                self.emit(f"|{desc_title:<{desc_size}}", end="")
+                self.emit(f"|{h_status_per_arch:<{max_size_status}}|")
+
+                self.matrix_lines(desc_size, max_size_status, 1)
+
+            lines = []
+            descs = []
+            cur_status = ""
+            line = ""
+
+            arch_table = sorted(self.data[name]["table"].items(),
+                                key=lambda x: (self.status_map.get(x[1], 99),
+                                               x[0].lower()))
+
+            for arch, status in arch_table:
+                if status == "---":
+                    status = notcompat
+
+                if status != cur_status:
+                    if line != "":
+                        lines.append(line)
+                        line = ""
+                    line = f"- **{status}**: {arch}"
+                elif len(line) + len(arch) + 2 < max_size_status:
+                    line += f", {arch}"
+                else:
+                    lines.append(line)
+                    line = f"  {arch}"
+                cur_status = status
+
+            if line != "":
+                lines.append(line)
+
+            description = self.data[name]["description"]
+            while len(description) > desc_size:
+                desc_line = description[:desc_size]
+
+                last_space = desc_line.rfind(" ")
+                if last_space != -1:
+                    desc_line = desc_line[:last_space]
+                    descs.append(desc_line)
+                    description = description[last_space + 1:]
+                else:
+                    desc_line = desc_line[:-1]
+                    descs.append(desc_line + "\\")
+                    description = description[len(desc_line):]
+
+            if description:
+                descs.append(description)
+
+            while len(lines) < 2 + len(descs):
+                lines.append("")
+
+            for ln, line in enumerate(lines):
+                col = ["", ""]
+
+                if not ln:
+                    col[0] = name
+                    col[1] = f"``{self.data[name]['kconfig']}``"
+                else:
+                    if ln >= 2 and descs:
+                        col[1] = descs.pop(0)
+
+                self.emit(f"|{col[0]:<{self.max_size_name}}", end="")
+                self.emit(f"|{col[1]:<{desc_size}}", end="")
+                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 architecture.
+        """
+        self.emit("#")
+        self.emit(f"# Kernel feature support matrix of the '{arch}' architecture:")
+        self.emit("#")
+
+        # Sort by subsystem, then by feature name (case‑insensitive)
+        for name in sorted(self.data.keys(),
+                           key=lambda n: (self.data[n]["subsys"].lower(),
+                                          n.lower())):
+            if feat and name != feat:
+                continue
+
+            feature = self.data[name]
+            arch_table = feature["table"]
+            status = arch_table.get(arch, "")
+            status = " " * ((4 - len(status)) // 2) + status
+
+            self.emit(f"{feature['subsys']:>{self.max_size_subsys + 1}}/ ",
+                      end="")
+            self.emit(f"{name:<{self.max_size_name}}: ", end="")
+            self.emit(f"{status:<5}|   ", end="")
+            self.emit(f"{feature['kconfig']:>{self.max_size_kconfig}} ",
+                      end="")
+            self.emit(f"#  {feature['description']}")
+
+        return self.msg
-- 
2.51.1


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH v2 2/3] Documentation/sphinx/kernel_feat.py: use class directly
  2025-11-18 19:09 [PATCH v2 0/3] Convert get_feat to Python Mauro Carvalho Chehab
  2025-11-18 19:09 ` [PATCH v2 1/3] tools/docs/get_feat.py: convert get_feat.pl " Mauro Carvalho Chehab
@ 2025-11-18 19:09 ` Mauro Carvalho Chehab
  2025-11-18 19:09 ` [PATCH v2 3/3] get_feat.pl: remove it, as it got replaced by get_feat.py Mauro Carvalho Chehab
  2025-11-21 17:46 ` [PATCH v2 0/3] Convert get_feat to Python Jonathan Corbet
  3 siblings, 0 replies; 6+ messages in thread
From: Mauro Carvalho Chehab @ 2025-11-18 19:09 UTC (permalink / raw)
  To: Jonathan Corbet, Linux Doc Mailing List
  Cc: Mauro Carvalho Chehab, Mauro Carvalho Chehab, linux-kernel

Now that get_feat is in Python, we don't need to use subprocess
to fork an executable file: we can use the feature classes
directly.

Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
 Documentation/sphinx/kernel_feat.py | 23 ++++++++++-------------
 1 file changed, 10 insertions(+), 13 deletions(-)

diff --git a/Documentation/sphinx/kernel_feat.py b/Documentation/sphinx/kernel_feat.py
index 1dcbfe335a65..bdc0fef5c87f 100644
--- a/Documentation/sphinx/kernel_feat.py
+++ b/Documentation/sphinx/kernel_feat.py
@@ -34,7 +34,6 @@
 import codecs
 import os
 import re
-import subprocess
 import sys
 
 from docutils import nodes, statemachine
@@ -43,9 +42,9 @@ from docutils.parsers.rst import directives, Directive
 from sphinx.util.docutils import switch_source_input
 
 srctree = os.path.abspath(os.environ["srctree"])
-sys.path.insert(0, os.path.join(srctree, "tools/docs/lib"))
+sys.path.insert(0, os.path.join(srctree, "tools/lib/python"))
 
-from parse_features import ParseFeature                # pylint: disable=C0413
+from feat.parse_features import ParseFeature                # pylint: disable=C0413
 
 def ErrorString(exc):  # Shamelessly stolen from docutils
     return f'{exc.__class__.__name}: {exc}'
@@ -89,18 +88,16 @@ class KernelFeat(Directive):
 
         srctree = os.path.abspath(os.environ["srctree"])
 
-        args = [
-            os.path.join(srctree, 'tools/docs/get_feat.pl'),
-            'rest',
-            '--enable-fname',
-            '--dir',
-            os.path.join(srctree, 'Documentation', self.arguments[0]),
-        ]
+        feature_dir = os.path.join(srctree, 'Documentation', self.arguments[0])
+
+        feat = ParseFeature(feature_dir, False, True)
+        feat.parse()
 
         if len(self.arguments) > 1:
-            args.extend(['--arch', self.arguments[1]])
-
-        lines = subprocess.check_output(args, cwd=os.path.dirname(doc.current_source)).decode('utf-8')
+            arch = self.arguments[1]
+            lines = feat.output_arch_table(arch)
+        else:
+            lines = feat.output_matrix()
 
         line_regex = re.compile(r"^\.\. FILE (\S+)$")
 
-- 
2.51.1


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH v2 3/3] get_feat.pl: remove it, as it got replaced by get_feat.py
  2025-11-18 19:09 [PATCH v2 0/3] Convert get_feat to Python Mauro Carvalho Chehab
  2025-11-18 19:09 ` [PATCH v2 1/3] tools/docs/get_feat.py: convert get_feat.pl " Mauro Carvalho Chehab
  2025-11-18 19:09 ` [PATCH v2 2/3] Documentation/sphinx/kernel_feat.py: use class directly Mauro Carvalho Chehab
@ 2025-11-18 19:09 ` Mauro Carvalho Chehab
  2025-11-21 17:46 ` [PATCH v2 0/3] Convert get_feat to Python Jonathan Corbet
  3 siblings, 0 replies; 6+ messages in thread
From: Mauro Carvalho Chehab @ 2025-11-18 19:09 UTC (permalink / raw)
  To: Jonathan Corbet, Linux Doc Mailing List
  Cc: Mauro Carvalho Chehab, Mauro Carvalho Chehab, linux-kernel

Now that this was rewritten in Python, we can remove the old
tool.

Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
 tools/docs/get_feat.pl | 641 -----------------------------------------
 1 file changed, 641 deletions(-)
 delete mode 100755 tools/docs/get_feat.pl

diff --git a/tools/docs/get_feat.pl b/tools/docs/get_feat.pl
deleted file mode 100755
index d75e7c85dc85..000000000000
--- a/tools/docs/get_feat.pl
+++ /dev/null
@@ -1,641 +0,0 @@
-#!/usr/bin/env perl
-# SPDX-License-Identifier: GPL-2.0
-
-use strict;
-use Pod::Usage;
-use Getopt::Long;
-use File::Find;
-use Fcntl ':mode';
-use Cwd 'abs_path';
-
-my $help;
-my $man;
-my $debug;
-my $arch;
-my $feat;
-my $enable_fname;
-
-my $basename = abs_path($0);
-$basename =~ s,/[^/]+$,/,;
-
-my $prefix=$basename . "../../Documentation/features";
-
-# Used only at for full features output. The script will auto-adjust
-# such values for the minimal possible values
-my $status_size = 1;
-my $description_size = 1;
-
-GetOptions(
-	"debug|d+" => \$debug,
-	"dir=s" => \$prefix,
-	'help|?' => \$help,
-	'arch=s' => \$arch,
-	'feat=s' => \$feat,
-	'feature=s' => \$feat,
-	"enable-fname" => \$enable_fname,
-	man => \$man
-) or pod2usage(2);
-
-pod2usage(1) if $help;
-pod2usage(-exitstatus => 0, -verbose => 2) if $man;
-
-pod2usage(1) if (scalar @ARGV < 1 || @ARGV > 2);
-
-my ($cmd, $arg) = @ARGV;
-
-pod2usage(2) if ($cmd ne "current" && $cmd ne "rest" && $cmd ne "validate"
-		&& $cmd ne "ls" && $cmd ne "list");
-
-require Data::Dumper if ($debug);
-
-my %data;
-my %archs;
-
-#
-# Displays an error message, printing file name and line
-#
-sub parse_error($$$$) {
-	my ($file, $ln, $msg, $data) = @_;
-
-	$data =~ s/\s+$/\n/;
-
-	print STDERR "Warning: file $file#$ln:\n\t$msg";
-
-	if ($data ne "") {
-		print STDERR ". Line\n\t\t$data";
-	} else {
-	    print STDERR "\n";
-	}
-}
-
-#
-# Parse a features file, storing its contents at %data
-#
-
-my $h_name = "Feature";
-my $h_kconfig = "Kconfig";
-my $h_description = "Description";
-my $h_subsys = "Subsystem";
-my $h_status = "Status";
-my $h_arch = "Architecture";
-
-my $max_size_name = length($h_name);
-my $max_size_kconfig = length($h_kconfig);
-my $max_size_description = length($h_description);
-my $max_size_subsys = length($h_subsys);
-my $max_size_status = length($h_status);
-
-my $max_size_arch = 0;
-my $max_size_arch_with_header;
-my $max_description_word = 0;
-
-sub parse_feat {
-	my $file = $File::Find::name;
-
-	my $mode = (stat($file))[2];
-	return if ($mode & S_IFDIR);
-	return if ($file =~ m,($prefix)/arch-support.txt,);
-	return if (!($file =~ m,arch-support.txt$,));
-
-	if ($enable_fname) {
-		printf ".. FILE %s\n", abs_path($file);
-	}
-
-	my $subsys = "";
-	$subsys = $2 if ( m,.*($prefix)/([^/]+).*,);
-
-	if (length($subsys) > $max_size_subsys) {
-		$max_size_subsys = length($subsys);
-	}
-
-	my $name;
-	my $kconfig;
-	my $description;
-	my $comments = "";
-	my $last_status;
-	my $ln;
-	my %arch_table;
-
-	print STDERR "Opening $file\n" if ($debug > 1);
-	open IN, $file;
-
-	while(<IN>) {
-		$ln++;
-
-		if (m/^\#\s+Feature\s+name:\s*(.*\S)/) {
-			$name = $1;
-			if (length($name) > $max_size_name) {
-				$max_size_name = length($name);
-			}
-			next;
-		}
-		if (m/^\#\s+Kconfig:\s*(.*\S)/) {
-			$kconfig = $1;
-			if (length($kconfig) > $max_size_kconfig) {
-				$max_size_kconfig = length($kconfig);
-			}
-			next;
-		}
-		if (m/^\#\s+description:\s*(.*\S)/) {
-			$description = $1;
-			if (length($description) > $max_size_description) {
-				$max_size_description = length($description);
-			}
-
-			foreach my $word (split /\s+/, $description) {
-				if (length($word) > $max_description_word) {
-					$max_description_word = length($word);
-				}
-			}
-
-			next;
-		}
-		next if (m/^\\s*$/);
-		next if (m/^\s*\-+\s*$/);
-		next if (m/^\s*\|\s*arch\s*\|\s*status\s*\|\s*$/);
-
-		if (m/^\#\s*(.*)/) {
-			$comments .= "$1\n";
-			next;
-		}
-		if (m/^\s*\|\s*(\S+):\s*\|\s*(\S+)\s*\|\s*$/) {
-			my $a = $1;
-			my $status = $2;
-
-			if (length($status) > $max_size_status) {
-				$max_size_status = length($status);
-			}
-			if (length($a) > $max_size_arch) {
-				$max_size_arch = length($a);
-			}
-
-			$status = "---" if ($status =~ m/^\.\.$/);
-
-			$archs{$a} = 1;
-			$arch_table{$a} = $status;
-			next;
-		}
-
-		#Everything else is an error
-		parse_error($file, $ln, "line is invalid", $_);
-	}
-	close IN;
-
-	if (!$name) {
-		parse_error($file, $ln, "Feature name not found", "");
-		return;
-	}
-
-	parse_error($file, $ln, "Subsystem not found", "") if (!$subsys);
-	parse_error($file, $ln, "Kconfig not found", "") if (!$kconfig);
-	parse_error($file, $ln, "Description not found", "") if (!$description);
-
-	if (!%arch_table) {
-		parse_error($file, $ln, "Architecture table not found", "");
-		return;
-	}
-
-	$data{$name}->{where} = $file;
-	$data{$name}->{subsys} = $subsys;
-	$data{$name}->{kconfig} = $kconfig;
-	$data{$name}->{description} = $description;
-	$data{$name}->{comments} = $comments;
-	$data{$name}->{table} = \%arch_table;
-
-	$max_size_arch_with_header = $max_size_arch + length($h_arch);
-}
-
-#
-# Output feature(s) for a given architecture
-#
-sub output_arch_table {
-	my $title = "Feature status on $arch architecture";
-
-	print "=" x length($title) . "\n";
-	print "$title\n";
-	print "=" x length($title) . "\n\n";
-
-	print "=" x $max_size_subsys;
-	print "  ";
-	print "=" x $max_size_name;
-	print "  ";
-	print "=" x $max_size_kconfig;
-	print "  ";
-	print "=" x $max_size_status;
-	print "  ";
-	print "=" x $max_size_description;
-	print "\n";
-	printf "%-${max_size_subsys}s  ", $h_subsys;
-	printf "%-${max_size_name}s  ", $h_name;
-	printf "%-${max_size_kconfig}s  ", $h_kconfig;
-	printf "%-${max_size_status}s  ", $h_status;
-	printf "%-${max_size_description}s\n", $h_description;
-	print "=" x $max_size_subsys;
-	print "  ";
-	print "=" x $max_size_name;
-	print "  ";
-	print "=" x $max_size_kconfig;
-	print "  ";
-	print "=" x $max_size_status;
-	print "  ";
-	print "=" x $max_size_description;
-	print "\n";
-
-	foreach my $name (sort {
-				($data{$a}->{subsys} cmp $data{$b}->{subsys}) ||
-				("\L$a" cmp "\L$b")
-			       } keys %data) {
-		next if ($feat && $name ne $feat);
-
-		my %arch_table = %{$data{$name}->{table}};
-		printf "%-${max_size_subsys}s  ", $data{$name}->{subsys};
-		printf "%-${max_size_name}s  ", $name;
-		printf "%-${max_size_kconfig}s  ", $data{$name}->{kconfig};
-		printf "%-${max_size_status}s  ", $arch_table{$arch};
-		printf "%-s\n", $data{$name}->{description};
-	}
-
-	print "=" x $max_size_subsys;
-	print "  ";
-	print "=" x $max_size_name;
-	print "  ";
-	print "=" x $max_size_kconfig;
-	print "  ";
-	print "=" x $max_size_status;
-	print "  ";
-	print "=" x $max_size_description;
-	print "\n";
-}
-
-#
-# list feature(s) for a given architecture
-#
-sub list_arch_features {
-	print "#\n# Kernel feature support matrix of the '$arch' architecture:\n#\n";
-
-	foreach my $name (sort {
-				($data{$a}->{subsys} cmp $data{$b}->{subsys}) ||
-				("\L$a" cmp "\L$b")
-			       } keys %data) {
-		next if ($feat && $name ne $feat);
-
-		my %arch_table = %{$data{$name}->{table}};
-
-		my $status = $arch_table{$arch};
-		$status = " " x ((4 - length($status)) / 2) . $status;
-
-		printf " %${max_size_subsys}s/ ", $data{$name}->{subsys};
-		printf "%-${max_size_name}s: ", $name;
-		printf "%-5s|   ", $status;
-		printf "%${max_size_kconfig}s # ", $data{$name}->{kconfig};
-		printf " %s\n", $data{$name}->{description};
-	}
-}
-
-#
-# Output a feature on all architectures
-#
-sub output_feature {
-	my $title = "Feature $feat";
-
-	print "=" x length($title) . "\n";
-	print "$title\n";
-	print "=" x length($title) . "\n\n";
-
-	print ":Subsystem: $data{$feat}->{subsys} \n" if ($data{$feat}->{subsys});
-	print ":Kconfig: $data{$feat}->{kconfig} \n" if ($data{$feat}->{kconfig});
-
-	my $desc = $data{$feat}->{description};
-	$desc =~ s/^([a-z])/\U$1/;
-	$desc =~ s/\.?\s*//;
-	print "\n$desc.\n\n";
-
-	my $com = $data{$feat}->{comments};
-	$com =~ s/^\s+//;
-	$com =~ s/\s+$//;
-	if ($com) {
-		print "Comments\n";
-		print "--------\n\n";
-		print "$com\n\n";
-	}
-
-	print "=" x $max_size_arch_with_header;
-	print "  ";
-	print "=" x $max_size_status;
-	print "\n";
-
-	printf "%-${max_size_arch}s  ", $h_arch;
-	printf "%-${max_size_status}s", $h_status . "\n";
-
-	print "=" x $max_size_arch_with_header;
-	print "  ";
-	print "=" x $max_size_status;
-	print "\n";
-
-	my %arch_table = %{$data{$feat}->{table}};
-	foreach my $arch (sort keys %arch_table) {
-		printf "%-${max_size_arch}s  ", $arch;
-		printf "%-${max_size_status}s\n", $arch_table{$arch};
-	}
-
-	print "=" x $max_size_arch_with_header;
-	print "  ";
-	print "=" x $max_size_status;
-	print "\n";
-}
-
-#
-# Output all features for all architectures
-#
-
-sub matrix_lines($$$) {
-	my $desc_size = shift;
-	my $status_size = shift;
-	my $header = shift;
-	my $fill;
-	my $ln_marker;
-
-	if ($header) {
-		$ln_marker = "=";
-	} else {
-		$ln_marker = "-";
-	}
-
-	$fill = $ln_marker;
-
-	print "+";
-	print $fill x $max_size_name;
-	print "+";
-	print $fill x $desc_size;
-	print "+";
-	print $ln_marker x $status_size;
-	print "+\n";
-}
-
-sub output_matrix {
-	my $title = "Feature status on all architectures";
-	my $notcompat = "Not compatible";
-
-	print "=" x length($title) . "\n";
-	print "$title\n";
-	print "=" x length($title) . "\n\n";
-
-	my $desc_title = "$h_kconfig / $h_description";
-
-	my $desc_size = $max_size_kconfig + 4;
-	if (!$description_size) {
-		$desc_size = $max_size_description if ($max_size_description > $desc_size);
-	} else {
-		$desc_size = $description_size if ($description_size > $desc_size);
-	}
-	$desc_size = $max_description_word if ($max_description_word > $desc_size);
-
-	$desc_size = length($desc_title) if (length($desc_title) > $desc_size);
-
-	$max_size_status = length($notcompat) if (length($notcompat) > $max_size_status);
-
-	# Ensure that the status will fit
-	my $min_status_size = $max_size_status + $max_size_arch + 6;
-	$status_size = $min_status_size if ($status_size < $min_status_size);
-
-
-	my $cur_subsys = "";
-	foreach my $name (sort {
-				($data{$a}->{subsys} cmp $data{$b}->{subsys}) or
-				("\L$a" cmp "\L$b")
-			       } keys %data) {
-
-		if ($cur_subsys ne $data{$name}->{subsys}) {
-			if ($cur_subsys ne "") {
-				printf "\n";
-			}
-
-			$cur_subsys = $data{$name}->{subsys};
-
-			my $title = "Subsystem: $cur_subsys";
-			print "$title\n";
-			print "=" x length($title) . "\n\n";
-
-
-			matrix_lines($desc_size, $status_size, 0);
-
-			printf "|%-${max_size_name}s", $h_name;
-			printf "|%-${desc_size}s", $desc_title;
-
-			printf "|%-${status_size}s|\n", "Status per architecture";
-			matrix_lines($desc_size, $status_size, 1);
-		}
-
-		my %arch_table = %{$data{$name}->{table}};
-		my $cur_status = "";
-
-		my (@lines, @descs);
-		my $line = "";
-		foreach my $arch (sort {
-					($arch_table{$b} cmp $arch_table{$a}) or
-					("\L$a" cmp "\L$b")
-				       } keys %arch_table) {
-
-			my $status = $arch_table{$arch};
-
-			if ($status eq "---") {
-				$status = $notcompat;
-			}
-
-			if ($status ne $cur_status) {
-				if ($line ne "") {
-					push @lines, $line;
-					$line = "";
-				}
-				$line = "- **" . $status . "**: " . $arch;
-			} elsif (length($line) + length ($arch) + 2 < $status_size) {
-				$line .= ", " . $arch;
-			} else {
-				push @lines, $line;
-				$line = "  " . $arch;
-			}
-			$cur_status = $status;
-		}
-		push @lines, $line if ($line ne "");
-
-		my $description = $data{$name}->{description};
-		while (length($description) > $desc_size) {
-			my $d = substr $description, 0, $desc_size;
-
-			# Ensure that it will end on a space
-			# if it can't, it means that the size is too small
-			# Instead of aborting it, let's print what we have
-			if (!($d =~ s/^(.*)\s+.*/$1/)) {
-				$d = substr $d, 0, -1;
-				push @descs, "$d\\";
-				$description =~ s/^\Q$d\E//;
-			} else {
-				push @descs, $d;
-				$description =~ s/^\Q$d\E\s+//;
-			}
-		}
-		push @descs, $description;
-
-		# Ensure that the full description will be printed
-		push @lines, "" while (scalar(@lines) < 2 + scalar(@descs));
-
-		my $ln = 0;
-		for my $line(@lines) {
-			if (!$ln) {
-				printf "|%-${max_size_name}s", $name;
-				printf "|%-${desc_size}s", "``" . $data{$name}->{kconfig} . "``";
-			} elsif ($ln >= 2 && scalar(@descs)) {
-				printf "|%-${max_size_name}s", "";
-				printf "|%-${desc_size}s", shift @descs;
-			} else {
-				printf "|%-${max_size_name}s", "";
-				printf "|%-${desc_size}s", "";
-			}
-
-			printf "|%-${status_size}s|\n", $line;
-
-			$ln++;
-		}
-		matrix_lines($desc_size, $status_size, 0);
-	}
-}
-
-
-#
-# Parses all feature files located at $prefix dir
-#
-find({wanted =>\&parse_feat, no_chdir => 1}, $prefix);
-
-print STDERR Data::Dumper->Dump([\%data], [qw(*data)]) if ($debug);
-
-#
-# Handles the command
-#
-if ($cmd eq "current") {
-	$arch = qx(uname -m | sed 's/x86_64/x86/' | sed 's/i386/x86/' | sed 's/s390x/s390/');
-	$arch =~s/\s+$//;
-}
-
-if ($cmd eq "ls" or $cmd eq "list") {
-	if (!$arch) {
-		$arch = qx(uname -m | sed 's/x86_64/x86/' | sed 's/i386/x86/' | sed 's/s390x/s390/');
-		$arch =~s/\s+$//;
-	}
-
-	list_arch_features;
-
-	exit;
-}
-
-if ($cmd ne "validate") {
-	if ($arch) {
-		output_arch_table;
-	} elsif ($feat) {
-		output_feature;
-	} else {
-		output_matrix;
-	}
-}
-
-__END__
-
-=head1 NAME
-
-get_feat.pl - parse the Linux Feature files and produce a ReST book.
-
-=head1 SYNOPSIS
-
-B<get_feat.pl> [--debug] [--man] [--help] [--dir=<dir>] [--arch=<arch>]
-	       [--feature=<feature>|--feat=<feature>] <COMAND> [<ARGUMENT>]
-
-Where <COMMAND> can be:
-
-=over 8
-
-B<current>               - output table in ReST compatible ASCII format
-			   with features for this machine's architecture
-
-B<rest>                  - output table(s)  in ReST compatible ASCII format
-			   with features in ReST markup language. The output
-			   is affected by --arch or --feat/--feature flags.
-
-B<validate>              - validate the contents of the files under
-			   Documentation/features.
-
-B<ls> or B<list>         - list features for this machine's architecture,
-			   using an easier to parse format.
-			   The output is affected by --arch flag.
-
-=back
-
-=head1 OPTIONS
-
-=over 8
-
-=item B<--arch>
-
-Output features for an specific architecture, optionally filtering for
-a single specific feature.
-
-=item B<--feat> or B<--feature>
-
-Output features for a single specific feature.
-
-=item B<--dir>
-
-Changes the location of the Feature files. By default, it uses
-the Documentation/features directory.
-
-=item B<--enable-fname>
-
-Prints the file name of the feature files. This can be used in order to
-track dependencies during documentation build.
-
-=item B<--debug>
-
-Put the script in verbose mode, useful for debugging. Can be called multiple
-times, to increase verbosity.
-
-=item B<--help>
-
-Prints a brief help message and exits.
-
-=item B<--man>
-
-Prints the manual page and exits.
-
-=back
-
-=head1 DESCRIPTION
-
-Parse the Linux feature files from Documentation/features (by default),
-optionally producing results at ReST format.
-
-It supports output data per architecture, per feature or a
-feature x arch matrix.
-
-When used with B<rest> command, it will use either one of the tree formats:
-
-If neither B<--arch> or B<--feature> arguments are used, it will output a
-matrix with features per architecture.
-
-If B<--arch> argument is used, it will output the features availability for
-a given architecture.
-
-If B<--feat> argument is used, it will output the content of the feature
-file using ReStructured Text markup.
-
-=head1 BUGS
-
-Report bugs to Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
-
-=head1 COPYRIGHT
-
-Copyright (c) 2019 by Mauro Carvalho Chehab <mchehab+samsung@kernel.org>.
-
-License GPLv2: GNU GPL version 2 <http://gnu.org/licenses/gpl.html>.
-
-This is free software: you are free to change and redistribute it.
-There is NO WARRANTY, to the extent permitted by law.
-
-=cut
-- 
2.51.1


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH v2 0/3] Convert get_feat to Python
  2025-11-18 19:09 [PATCH v2 0/3] Convert get_feat to Python Mauro Carvalho Chehab
                   ` (2 preceding siblings ...)
  2025-11-18 19:09 ` [PATCH v2 3/3] get_feat.pl: remove it, as it got replaced by get_feat.py Mauro Carvalho Chehab
@ 2025-11-21 17:46 ` Jonathan Corbet
  2025-11-21 21:27   ` Mauro Carvalho Chehab
  3 siblings, 1 reply; 6+ messages in thread
From: Jonathan Corbet @ 2025-11-21 17:46 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Linux Doc Mailing List
  Cc: Mauro Carvalho Chehab, Mauro Carvalho Chehab, linux-kernel

Mauro Carvalho Chehab <mchehab+huawei@kernel.org> writes:

So you had me worried with this:

> As we don't have a definition yet for the location of the Python
> libraries yet, I opted to place the parse features library inside
> tools/docs/lib. It shouldn't be hard to move it elsewhere once we
> define a better place for them.

But it seems that you did, in fact, update the library location.  I've
applied these, thanks.

jon

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH v2 0/3] Convert get_feat to Python
  2025-11-21 17:46 ` [PATCH v2 0/3] Convert get_feat to Python Jonathan Corbet
@ 2025-11-21 21:27   ` Mauro Carvalho Chehab
  0 siblings, 0 replies; 6+ messages in thread
From: Mauro Carvalho Chehab @ 2025-11-21 21:27 UTC (permalink / raw)
  To: Jonathan Corbet
  Cc: Linux Doc Mailing List, Mauro Carvalho Chehab, linux-kernel

Em Fri, 21 Nov 2025 10:46:32 -0700
Jonathan Corbet <corbet@lwn.net> escreveu:

> Mauro Carvalho Chehab <mchehab+huawei@kernel.org> writes:
> 
> So you had me worried with this:
> 
> > As we don't have a definition yet for the location of the Python
> > libraries yet, I opted to place the parse features library inside
> > tools/docs/lib. It shouldn't be hard to move it elsewhere once we
> > define a better place for them.  
> 
> But it seems that you did, in fact, update the library location. 

Heh, forgot to update the description ;-)

> I've
> applied these, thanks.

Thanks!

Mauro

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2025-11-21 21:27 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-11-18 19:09 [PATCH v2 0/3] Convert get_feat to Python Mauro Carvalho Chehab
2025-11-18 19:09 ` [PATCH v2 1/3] tools/docs/get_feat.py: convert get_feat.pl " Mauro Carvalho Chehab
2025-11-18 19:09 ` [PATCH v2 2/3] Documentation/sphinx/kernel_feat.py: use class directly Mauro Carvalho Chehab
2025-11-18 19:09 ` [PATCH v2 3/3] get_feat.pl: remove it, as it got replaced by get_feat.py Mauro Carvalho Chehab
2025-11-21 17:46 ` [PATCH v2 0/3] Convert get_feat to Python Jonathan Corbet
2025-11-21 21:27   ` Mauro Carvalho Chehab

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).