xref: /linux/tools/lib/python/feat/parse_features.py (revision 72c395024dac5e215136cbff793455f065603b06)
1caa642bfSMauro Carvalho Chehab#!/usr/bin/env python3
2caa642bfSMauro Carvalho Chehab# pylint: disable=R0902,R0911,R0912,R0914,R0915
3caa642bfSMauro Carvalho Chehab# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
4caa642bfSMauro Carvalho Chehab# SPDX-License-Identifier: GPL-2.0
5caa642bfSMauro Carvalho Chehab
6caa642bfSMauro Carvalho Chehab
7caa642bfSMauro Carvalho Chehab"""
8caa642bfSMauro Carvalho ChehabLibrary to parse the Linux Feature files and produce a ReST book.
9caa642bfSMauro Carvalho Chehab"""
10caa642bfSMauro Carvalho Chehab
11caa642bfSMauro Carvalho Chehabimport os
12caa642bfSMauro Carvalho Chehabimport re
13caa642bfSMauro Carvalho Chehabimport sys
14caa642bfSMauro Carvalho Chehab
15caa642bfSMauro Carvalho Chehabfrom glob import iglob
16caa642bfSMauro Carvalho Chehab
17caa642bfSMauro Carvalho Chehab
18caa642bfSMauro Carvalho Chehabclass ParseFeature:
19caa642bfSMauro Carvalho Chehab    """
20caa642bfSMauro Carvalho Chehab    Parses Documentation/features, allowing to generate ReST documentation
21caa642bfSMauro Carvalho Chehab    from it.
22caa642bfSMauro Carvalho Chehab    """
23caa642bfSMauro Carvalho Chehab
24*ef6aa110SMauro Carvalho Chehab    #: feature header string.
25caa642bfSMauro Carvalho Chehab    h_name = "Feature"
26*ef6aa110SMauro Carvalho Chehab
27*ef6aa110SMauro Carvalho Chehab    #: Kernel config header string.
28caa642bfSMauro Carvalho Chehab    h_kconfig = "Kconfig"
29*ef6aa110SMauro Carvalho Chehab
30*ef6aa110SMauro Carvalho Chehab    #: description header string.
31caa642bfSMauro Carvalho Chehab    h_description = "Description"
32*ef6aa110SMauro Carvalho Chehab
33*ef6aa110SMauro Carvalho Chehab    #: subsystem header string.
34caa642bfSMauro Carvalho Chehab    h_subsys = "Subsystem"
35*ef6aa110SMauro Carvalho Chehab
36*ef6aa110SMauro Carvalho Chehab    #: status header string.
37caa642bfSMauro Carvalho Chehab    h_status = "Status"
38*ef6aa110SMauro Carvalho Chehab
39*ef6aa110SMauro Carvalho Chehab    #: architecture header string.
40caa642bfSMauro Carvalho Chehab    h_arch = "Architecture"
41caa642bfSMauro Carvalho Chehab
42*ef6aa110SMauro Carvalho Chehab    #: Sort order for status. Others will be mapped at the end.
43caa642bfSMauro Carvalho Chehab    status_map = {
44caa642bfSMauro Carvalho Chehab        "ok":   0,
45caa642bfSMauro Carvalho Chehab        "TODO": 1,
46caa642bfSMauro Carvalho Chehab        "N/A":  2,
47caa642bfSMauro Carvalho Chehab        # The only missing status is "..", which was mapped as "---",
48caa642bfSMauro Carvalho Chehab        # as this is an special ReST cell value. Let it get the
49caa642bfSMauro Carvalho Chehab        # default order (99).
50caa642bfSMauro Carvalho Chehab    }
51caa642bfSMauro Carvalho Chehab
52caa642bfSMauro Carvalho Chehab    def __init__(self, prefix, debug=0, enable_fname=False):
53caa642bfSMauro Carvalho Chehab        """
54*ef6aa110SMauro Carvalho Chehab        Sets internal variables.
55caa642bfSMauro Carvalho Chehab        """
56caa642bfSMauro Carvalho Chehab
57caa642bfSMauro Carvalho Chehab        self.prefix = prefix
58caa642bfSMauro Carvalho Chehab        self.debug = debug
59caa642bfSMauro Carvalho Chehab        self.enable_fname = enable_fname
60caa642bfSMauro Carvalho Chehab
61caa642bfSMauro Carvalho Chehab        self.data = {}
62caa642bfSMauro Carvalho Chehab
63caa642bfSMauro Carvalho Chehab        # Initial maximum values use just the headers
64caa642bfSMauro Carvalho Chehab        self.max_size_name = len(self.h_name)
65caa642bfSMauro Carvalho Chehab        self.max_size_kconfig = len(self.h_kconfig)
66caa642bfSMauro Carvalho Chehab        self.max_size_description = len(self.h_description)
67caa642bfSMauro Carvalho Chehab        self.max_size_desc_word = 0
68caa642bfSMauro Carvalho Chehab        self.max_size_subsys = len(self.h_subsys)
69caa642bfSMauro Carvalho Chehab        self.max_size_status = len(self.h_status)
70caa642bfSMauro Carvalho Chehab        self.max_size_arch = len(self.h_arch)
71caa642bfSMauro Carvalho Chehab        self.max_size_arch_with_header = self.max_size_arch + self.max_size_arch
72caa642bfSMauro Carvalho Chehab        self.description_size = 1
73caa642bfSMauro Carvalho Chehab
74caa642bfSMauro Carvalho Chehab        self.msg = ""
75caa642bfSMauro Carvalho Chehab
76caa642bfSMauro Carvalho Chehab    def emit(self, msg="", end="\n"):
77*ef6aa110SMauro Carvalho Chehab        """Helper function to append a new message for feature output."""
78*ef6aa110SMauro Carvalho Chehab
79caa642bfSMauro Carvalho Chehab        self.msg += msg + end
80caa642bfSMauro Carvalho Chehab
81caa642bfSMauro Carvalho Chehab    def parse_error(self, fname, ln, msg, data=None):
82caa642bfSMauro Carvalho Chehab        """
83*ef6aa110SMauro Carvalho Chehab        Displays an error message, printing file name and line.
84caa642bfSMauro Carvalho Chehab        """
85caa642bfSMauro Carvalho Chehab
86caa642bfSMauro Carvalho Chehab        if ln:
87caa642bfSMauro Carvalho Chehab            fname += f"#{ln}"
88caa642bfSMauro Carvalho Chehab
89caa642bfSMauro Carvalho Chehab        print(f"Warning: file {fname}: {msg}", file=sys.stderr, end="")
90caa642bfSMauro Carvalho Chehab
91caa642bfSMauro Carvalho Chehab        if data:
92caa642bfSMauro Carvalho Chehab            data = data.rstrip()
93caa642bfSMauro Carvalho Chehab            print(f":\n\t{data}", file=sys.stderr)
94caa642bfSMauro Carvalho Chehab        else:
95caa642bfSMauro Carvalho Chehab            print("", file=sys.stderr)
96caa642bfSMauro Carvalho Chehab
97caa642bfSMauro Carvalho Chehab    def parse_feat_file(self, fname):
98*ef6aa110SMauro Carvalho Chehab        """Parses a single arch-support.txt feature file."""
99caa642bfSMauro Carvalho Chehab
100caa642bfSMauro Carvalho Chehab        if os.path.isdir(fname):
101caa642bfSMauro Carvalho Chehab            return
102caa642bfSMauro Carvalho Chehab
103caa642bfSMauro Carvalho Chehab        base = os.path.basename(fname)
104caa642bfSMauro Carvalho Chehab
105caa642bfSMauro Carvalho Chehab        if base != "arch-support.txt":
106caa642bfSMauro Carvalho Chehab            if self.debug:
107caa642bfSMauro Carvalho Chehab                print(f"ignoring {fname}", file=sys.stderr)
108caa642bfSMauro Carvalho Chehab            return
109caa642bfSMauro Carvalho Chehab
110caa642bfSMauro Carvalho Chehab        subsys = os.path.dirname(fname).split("/")[-2]
111caa642bfSMauro Carvalho Chehab        self.max_size_subsys = max(self.max_size_subsys, len(subsys))
112caa642bfSMauro Carvalho Chehab
113caa642bfSMauro Carvalho Chehab        feature_name = ""
114caa642bfSMauro Carvalho Chehab        kconfig = ""
115caa642bfSMauro Carvalho Chehab        description = ""
116caa642bfSMauro Carvalho Chehab        comments = ""
117caa642bfSMauro Carvalho Chehab        arch_table = {}
118caa642bfSMauro Carvalho Chehab
119caa642bfSMauro Carvalho Chehab        if self.debug > 1:
120caa642bfSMauro Carvalho Chehab            print(f"Opening {fname}", file=sys.stderr)
121caa642bfSMauro Carvalho Chehab
122caa642bfSMauro Carvalho Chehab        if self.enable_fname:
123caa642bfSMauro Carvalho Chehab            full_fname = os.path.abspath(fname)
124caa642bfSMauro Carvalho Chehab            self.emit(f".. FILE {full_fname}")
125caa642bfSMauro Carvalho Chehab
126caa642bfSMauro Carvalho Chehab        with open(fname, encoding="utf-8") as f:
127caa642bfSMauro Carvalho Chehab            for ln, line in enumerate(f, start=1):
128caa642bfSMauro Carvalho Chehab                line = line.strip()
129caa642bfSMauro Carvalho Chehab
130caa642bfSMauro Carvalho Chehab                match = re.match(r"^\#\s+Feature\s+name:\s*(.*\S)", line)
131caa642bfSMauro Carvalho Chehab                if match:
132caa642bfSMauro Carvalho Chehab                    feature_name = match.group(1)
133caa642bfSMauro Carvalho Chehab
134caa642bfSMauro Carvalho Chehab                    self.max_size_name = max(self.max_size_name,
135caa642bfSMauro Carvalho Chehab                                             len(feature_name))
136caa642bfSMauro Carvalho Chehab                    continue
137caa642bfSMauro Carvalho Chehab
138caa642bfSMauro Carvalho Chehab                match = re.match(r"^\#\s+Kconfig:\s*(.*\S)", line)
139caa642bfSMauro Carvalho Chehab                if match:
140caa642bfSMauro Carvalho Chehab                    kconfig = match.group(1)
141caa642bfSMauro Carvalho Chehab
142caa642bfSMauro Carvalho Chehab                    self.max_size_kconfig = max(self.max_size_kconfig,
143caa642bfSMauro Carvalho Chehab                                                len(kconfig))
144caa642bfSMauro Carvalho Chehab                    continue
145caa642bfSMauro Carvalho Chehab
146caa642bfSMauro Carvalho Chehab                match = re.match(r"^\#\s+description:\s*(.*\S)", line)
147caa642bfSMauro Carvalho Chehab                if match:
148caa642bfSMauro Carvalho Chehab                    description = match.group(1)
149caa642bfSMauro Carvalho Chehab
150caa642bfSMauro Carvalho Chehab                    self.max_size_description = max(self.max_size_description,
151caa642bfSMauro Carvalho Chehab                                                    len(description))
152caa642bfSMauro Carvalho Chehab
153caa642bfSMauro Carvalho Chehab                    words = re.split(r"\s+", line)[1:]
154caa642bfSMauro Carvalho Chehab                    for word in words:
155caa642bfSMauro Carvalho Chehab                        self.max_size_desc_word = max(self.max_size_desc_word,
156caa642bfSMauro Carvalho Chehab                                                        len(word))
157caa642bfSMauro Carvalho Chehab
158caa642bfSMauro Carvalho Chehab                    continue
159caa642bfSMauro Carvalho Chehab
160caa642bfSMauro Carvalho Chehab                if re.search(r"^\\s*$", line):
161caa642bfSMauro Carvalho Chehab                    continue
162caa642bfSMauro Carvalho Chehab
163caa642bfSMauro Carvalho Chehab                if re.match(r"^\s*\-+\s*$", line):
164caa642bfSMauro Carvalho Chehab                    continue
165caa642bfSMauro Carvalho Chehab
166caa642bfSMauro Carvalho Chehab                if re.search(r"^\s*\|\s*arch\s*\|\s*status\s*\|\s*$", line):
167caa642bfSMauro Carvalho Chehab                    continue
168caa642bfSMauro Carvalho Chehab
169caa642bfSMauro Carvalho Chehab                match = re.match(r"^\#\s*(.*)$", line)
170caa642bfSMauro Carvalho Chehab                if match:
171caa642bfSMauro Carvalho Chehab                    comments += match.group(1)
172caa642bfSMauro Carvalho Chehab                    continue
173caa642bfSMauro Carvalho Chehab
174caa642bfSMauro Carvalho Chehab                match = re.match(r"^\s*\|\s*(\S+):\s*\|\s*(\S+)\s*\|\s*$", line)
175caa642bfSMauro Carvalho Chehab                if match:
176caa642bfSMauro Carvalho Chehab                    arch = match.group(1)
177caa642bfSMauro Carvalho Chehab                    status = match.group(2)
178caa642bfSMauro Carvalho Chehab
179caa642bfSMauro Carvalho Chehab                    self.max_size_status = max(self.max_size_status,
180caa642bfSMauro Carvalho Chehab                                               len(status))
181caa642bfSMauro Carvalho Chehab                    self.max_size_arch = max(self.max_size_arch, len(arch))
182caa642bfSMauro Carvalho Chehab
183caa642bfSMauro Carvalho Chehab                    if status == "..":
184caa642bfSMauro Carvalho Chehab                        status = "---"
185caa642bfSMauro Carvalho Chehab
186caa642bfSMauro Carvalho Chehab                    arch_table[arch] = status
187caa642bfSMauro Carvalho Chehab
188caa642bfSMauro Carvalho Chehab                    continue
189caa642bfSMauro Carvalho Chehab
190caa642bfSMauro Carvalho Chehab                self.parse_error(fname, ln, "Line is invalid", line)
191caa642bfSMauro Carvalho Chehab
192caa642bfSMauro Carvalho Chehab        if not feature_name:
193caa642bfSMauro Carvalho Chehab            self.parse_error(fname, 0, "Feature name not found")
194caa642bfSMauro Carvalho Chehab            return
195caa642bfSMauro Carvalho Chehab        if not subsys:
196caa642bfSMauro Carvalho Chehab            self.parse_error(fname, 0, "Subsystem not found")
197caa642bfSMauro Carvalho Chehab            return
198caa642bfSMauro Carvalho Chehab        if not kconfig:
199caa642bfSMauro Carvalho Chehab            self.parse_error(fname, 0, "Kconfig not found")
200caa642bfSMauro Carvalho Chehab            return
201caa642bfSMauro Carvalho Chehab        if not description:
202caa642bfSMauro Carvalho Chehab            self.parse_error(fname, 0, "Description not found")
203caa642bfSMauro Carvalho Chehab            return
204caa642bfSMauro Carvalho Chehab        if not arch_table:
205caa642bfSMauro Carvalho Chehab            self.parse_error(fname, 0, "Architecture table not found")
206caa642bfSMauro Carvalho Chehab            return
207caa642bfSMauro Carvalho Chehab
208caa642bfSMauro Carvalho Chehab        self.data[feature_name] = {
209caa642bfSMauro Carvalho Chehab            "where": fname,
210caa642bfSMauro Carvalho Chehab            "subsys": subsys,
211caa642bfSMauro Carvalho Chehab            "kconfig": kconfig,
212caa642bfSMauro Carvalho Chehab            "description": description,
213caa642bfSMauro Carvalho Chehab            "comments": comments,
214caa642bfSMauro Carvalho Chehab            "table": arch_table,
215caa642bfSMauro Carvalho Chehab        }
216caa642bfSMauro Carvalho Chehab
217caa642bfSMauro Carvalho Chehab        self.max_size_arch_with_header = self.max_size_arch + len(self.h_arch)
218caa642bfSMauro Carvalho Chehab
219caa642bfSMauro Carvalho Chehab    def parse(self):
220*ef6aa110SMauro Carvalho Chehab        """Parses all arch-support.txt feature files inside self.prefix."""
221caa642bfSMauro Carvalho Chehab
222caa642bfSMauro Carvalho Chehab        path = os.path.expanduser(self.prefix)
223caa642bfSMauro Carvalho Chehab
224caa642bfSMauro Carvalho Chehab        if self.debug > 2:
225caa642bfSMauro Carvalho Chehab            print(f"Running parser for {path}")
226caa642bfSMauro Carvalho Chehab
227caa642bfSMauro Carvalho Chehab        example_path = os.path.join(path, "arch-support.txt")
228caa642bfSMauro Carvalho Chehab
229caa642bfSMauro Carvalho Chehab        for fname in iglob(os.path.join(path, "**"), recursive=True):
230caa642bfSMauro Carvalho Chehab            if fname != example_path:
231caa642bfSMauro Carvalho Chehab                self.parse_feat_file(fname)
232caa642bfSMauro Carvalho Chehab
233caa642bfSMauro Carvalho Chehab        return self.data
234caa642bfSMauro Carvalho Chehab
235caa642bfSMauro Carvalho Chehab    def output_arch_table(self, arch, feat=None):
236caa642bfSMauro Carvalho Chehab        """
237caa642bfSMauro Carvalho Chehab        Output feature(s) for a given architecture.
238caa642bfSMauro Carvalho Chehab        """
239caa642bfSMauro Carvalho Chehab
240caa642bfSMauro Carvalho Chehab        title = f"Feature status on {arch} architecture"
241caa642bfSMauro Carvalho Chehab
242caa642bfSMauro Carvalho Chehab        self.emit("=" * len(title))
243caa642bfSMauro Carvalho Chehab        self.emit(title)
244caa642bfSMauro Carvalho Chehab        self.emit("=" * len(title))
245caa642bfSMauro Carvalho Chehab        self.emit()
246caa642bfSMauro Carvalho Chehab
247caa642bfSMauro Carvalho Chehab        self.emit("=" * self.max_size_subsys + "  ", end="")
248caa642bfSMauro Carvalho Chehab        self.emit("=" * self.max_size_name + "  ", end="")
249caa642bfSMauro Carvalho Chehab        self.emit("=" * self.max_size_kconfig + "  ", end="")
250caa642bfSMauro Carvalho Chehab        self.emit("=" * self.max_size_status + "  ", end="")
251caa642bfSMauro Carvalho Chehab        self.emit("=" * self.max_size_description)
252caa642bfSMauro Carvalho Chehab
253caa642bfSMauro Carvalho Chehab        self.emit(f"{self.h_subsys:<{self.max_size_subsys}}  ", end="")
254caa642bfSMauro Carvalho Chehab        self.emit(f"{self.h_name:<{self.max_size_name}}  ", end="")
255caa642bfSMauro Carvalho Chehab        self.emit(f"{self.h_kconfig:<{self.max_size_kconfig}}  ", end="")
256caa642bfSMauro Carvalho Chehab        self.emit(f"{self.h_status:<{self.max_size_status}}  ", end="")
257caa642bfSMauro Carvalho Chehab        self.emit(f"{self.h_description:<{self.max_size_description}}")
258caa642bfSMauro Carvalho Chehab
259caa642bfSMauro Carvalho Chehab        self.emit("=" * self.max_size_subsys + "  ", end="")
260caa642bfSMauro Carvalho Chehab        self.emit("=" * self.max_size_name + "  ", end="")
261caa642bfSMauro Carvalho Chehab        self.emit("=" * self.max_size_kconfig + "  ", end="")
262caa642bfSMauro Carvalho Chehab        self.emit("=" * self.max_size_status + "  ", end="")
263caa642bfSMauro Carvalho Chehab        self.emit("=" * self.max_size_description)
264caa642bfSMauro Carvalho Chehab
265caa642bfSMauro Carvalho Chehab        sorted_features = sorted(self.data.keys(),
266caa642bfSMauro Carvalho Chehab                                 key=lambda x: (self.data[x]["subsys"],
267caa642bfSMauro Carvalho Chehab                                                x.lower()))
268caa642bfSMauro Carvalho Chehab
269caa642bfSMauro Carvalho Chehab        for name in sorted_features:
270caa642bfSMauro Carvalho Chehab            if feat and name != feat:
271caa642bfSMauro Carvalho Chehab                continue
272caa642bfSMauro Carvalho Chehab
273caa642bfSMauro Carvalho Chehab            arch_table = self.data[name]["table"]
274caa642bfSMauro Carvalho Chehab
275caa642bfSMauro Carvalho Chehab            if not arch in arch_table:
276caa642bfSMauro Carvalho Chehab                continue
277caa642bfSMauro Carvalho Chehab
278caa642bfSMauro Carvalho Chehab            self.emit(f"{self.data[name]['subsys']:<{self.max_size_subsys}}  ",
279caa642bfSMauro Carvalho Chehab                  end="")
280caa642bfSMauro Carvalho Chehab            self.emit(f"{name:<{self.max_size_name}}  ", end="")
281caa642bfSMauro Carvalho Chehab            self.emit(f"{self.data[name]['kconfig']:<{self.max_size_kconfig}}  ",
282caa642bfSMauro Carvalho Chehab                  end="")
283caa642bfSMauro Carvalho Chehab            self.emit(f"{arch_table[arch]:<{self.max_size_status}}  ",
284caa642bfSMauro Carvalho Chehab                  end="")
285caa642bfSMauro Carvalho Chehab            self.emit(f"{self.data[name]['description']}")
286caa642bfSMauro Carvalho Chehab
287caa642bfSMauro Carvalho Chehab        self.emit("=" * self.max_size_subsys + "  ", end="")
288caa642bfSMauro Carvalho Chehab        self.emit("=" * self.max_size_name + "  ", end="")
289caa642bfSMauro Carvalho Chehab        self.emit("=" * self.max_size_kconfig + "  ", end="")
290caa642bfSMauro Carvalho Chehab        self.emit("=" * self.max_size_status + "  ", end="")
291caa642bfSMauro Carvalho Chehab        self.emit("=" * self.max_size_description)
292caa642bfSMauro Carvalho Chehab
293caa642bfSMauro Carvalho Chehab        return self.msg
294caa642bfSMauro Carvalho Chehab
295caa642bfSMauro Carvalho Chehab    def output_feature(self, feat):
296caa642bfSMauro Carvalho Chehab        """
297*ef6aa110SMauro Carvalho Chehab        Output a feature on all architectures.
298caa642bfSMauro Carvalho Chehab        """
299caa642bfSMauro Carvalho Chehab
300caa642bfSMauro Carvalho Chehab        title = f"Feature {feat}"
301caa642bfSMauro Carvalho Chehab
302caa642bfSMauro Carvalho Chehab        self.emit("=" * len(title))
303caa642bfSMauro Carvalho Chehab        self.emit(title)
304caa642bfSMauro Carvalho Chehab        self.emit("=" * len(title))
305caa642bfSMauro Carvalho Chehab        self.emit()
306caa642bfSMauro Carvalho Chehab
307caa642bfSMauro Carvalho Chehab        if not feat in self.data:
308caa642bfSMauro Carvalho Chehab            return
309caa642bfSMauro Carvalho Chehab
310caa642bfSMauro Carvalho Chehab        if self.data[feat]["subsys"]:
311caa642bfSMauro Carvalho Chehab            self.emit(f":Subsystem: {self.data[feat]['subsys']}")
312caa642bfSMauro Carvalho Chehab        if self.data[feat]["kconfig"]:
313caa642bfSMauro Carvalho Chehab            self.emit(f":Kconfig: {self.data[feat]['kconfig']}")
314caa642bfSMauro Carvalho Chehab
315caa642bfSMauro Carvalho Chehab        desc = self.data[feat]["description"]
316caa642bfSMauro Carvalho Chehab        desc = desc[0].upper() + desc[1:]
317caa642bfSMauro Carvalho Chehab        desc = desc.rstrip(". \t")
318caa642bfSMauro Carvalho Chehab        self.emit(f"\n{desc}.\n")
319caa642bfSMauro Carvalho Chehab
320caa642bfSMauro Carvalho Chehab        com = self.data[feat]["comments"].strip()
321caa642bfSMauro Carvalho Chehab        if com:
322caa642bfSMauro Carvalho Chehab            self.emit("Comments")
323caa642bfSMauro Carvalho Chehab            self.emit("--------")
324caa642bfSMauro Carvalho Chehab            self.emit(f"\n{com}\n")
325caa642bfSMauro Carvalho Chehab
326caa642bfSMauro Carvalho Chehab        self.emit("=" * self.max_size_arch + "  ", end="")
327caa642bfSMauro Carvalho Chehab        self.emit("=" * self.max_size_status)
328caa642bfSMauro Carvalho Chehab
329caa642bfSMauro Carvalho Chehab        self.emit(f"{self.h_arch:<{self.max_size_arch}}  ", end="")
330caa642bfSMauro Carvalho Chehab        self.emit(f"{self.h_status:<{self.max_size_status}}")
331caa642bfSMauro Carvalho Chehab
332caa642bfSMauro Carvalho Chehab        self.emit("=" * self.max_size_arch + "  ", end="")
333caa642bfSMauro Carvalho Chehab        self.emit("=" * self.max_size_status)
334caa642bfSMauro Carvalho Chehab
335caa642bfSMauro Carvalho Chehab        arch_table = self.data[feat]["table"]
336caa642bfSMauro Carvalho Chehab        for arch in sorted(arch_table.keys()):
337caa642bfSMauro Carvalho Chehab            self.emit(f"{arch:<{self.max_size_arch}}  ", end="")
338caa642bfSMauro Carvalho Chehab            self.emit(f"{arch_table[arch]:<{self.max_size_status}}")
339caa642bfSMauro Carvalho Chehab
340caa642bfSMauro Carvalho Chehab        self.emit("=" * self.max_size_arch + "  ", end="")
341caa642bfSMauro Carvalho Chehab        self.emit("=" * self.max_size_status)
342caa642bfSMauro Carvalho Chehab
343caa642bfSMauro Carvalho Chehab        return self.msg
344caa642bfSMauro Carvalho Chehab
345caa642bfSMauro Carvalho Chehab    def matrix_lines(self, desc_size, max_size_status, header):
346caa642bfSMauro Carvalho Chehab        """
347*ef6aa110SMauro Carvalho Chehab        Helper function to split element tables at the output matrix.
348caa642bfSMauro Carvalho Chehab        """
349caa642bfSMauro Carvalho Chehab
350caa642bfSMauro Carvalho Chehab        if header:
351caa642bfSMauro Carvalho Chehab            ln_marker = "="
352caa642bfSMauro Carvalho Chehab        else:
353caa642bfSMauro Carvalho Chehab            ln_marker = "-"
354caa642bfSMauro Carvalho Chehab
355caa642bfSMauro Carvalho Chehab        self.emit("+" + ln_marker * self.max_size_name + "+", end="")
356caa642bfSMauro Carvalho Chehab        self.emit(ln_marker * desc_size, end="")
357caa642bfSMauro Carvalho Chehab        self.emit("+" + ln_marker * max_size_status + "+")
358caa642bfSMauro Carvalho Chehab
359caa642bfSMauro Carvalho Chehab    def output_matrix(self):
360caa642bfSMauro Carvalho Chehab        """
361caa642bfSMauro Carvalho Chehab        Generates a set of tables, groped by subsystem, containing
362caa642bfSMauro Carvalho Chehab        what's the feature state on each architecture.
363caa642bfSMauro Carvalho Chehab        """
364caa642bfSMauro Carvalho Chehab
365caa642bfSMauro Carvalho Chehab        title = "Feature status on all architectures"
366caa642bfSMauro Carvalho Chehab
367caa642bfSMauro Carvalho Chehab        self.emit("=" * len(title))
368caa642bfSMauro Carvalho Chehab        self.emit(title)
369caa642bfSMauro Carvalho Chehab        self.emit("=" * len(title))
370caa642bfSMauro Carvalho Chehab        self.emit()
371caa642bfSMauro Carvalho Chehab
372caa642bfSMauro Carvalho Chehab        desc_title = f"{self.h_kconfig} / {self.h_description}"
373caa642bfSMauro Carvalho Chehab
374caa642bfSMauro Carvalho Chehab        desc_size = self.max_size_kconfig + 4
375caa642bfSMauro Carvalho Chehab        if not self.description_size:
376caa642bfSMauro Carvalho Chehab            desc_size = max(self.max_size_description, desc_size)
377caa642bfSMauro Carvalho Chehab        else:
378caa642bfSMauro Carvalho Chehab            desc_size = max(self.description_size, desc_size)
379caa642bfSMauro Carvalho Chehab
380caa642bfSMauro Carvalho Chehab        desc_size = max(self.max_size_desc_word, desc_size, len(desc_title))
381caa642bfSMauro Carvalho Chehab
382caa642bfSMauro Carvalho Chehab        notcompat = "Not compatible"
383caa642bfSMauro Carvalho Chehab        self.max_size_status = max(self.max_size_status, len(notcompat))
384caa642bfSMauro Carvalho Chehab
385caa642bfSMauro Carvalho Chehab        min_status_size = self.max_size_status + self.max_size_arch + 4
386caa642bfSMauro Carvalho Chehab        max_size_status = max(min_status_size, self.max_size_status)
387caa642bfSMauro Carvalho Chehab
388caa642bfSMauro Carvalho Chehab        h_status_per_arch = "Status per architecture"
389caa642bfSMauro Carvalho Chehab        max_size_status = max(max_size_status, len(h_status_per_arch))
390caa642bfSMauro Carvalho Chehab
391caa642bfSMauro Carvalho Chehab        cur_subsys = None
392caa642bfSMauro Carvalho Chehab        for name in sorted(self.data.keys(),
393caa642bfSMauro Carvalho Chehab                           key=lambda x: (self.data[x]["subsys"], x.lower())):
394caa642bfSMauro Carvalho Chehab            if not cur_subsys or cur_subsys != self.data[name]["subsys"]:
395caa642bfSMauro Carvalho Chehab                if cur_subsys:
396caa642bfSMauro Carvalho Chehab                    self.emit()
397caa642bfSMauro Carvalho Chehab
398caa642bfSMauro Carvalho Chehab                cur_subsys = self.data[name]["subsys"]
399caa642bfSMauro Carvalho Chehab
400caa642bfSMauro Carvalho Chehab                title = f"Subsystem: {cur_subsys}"
401caa642bfSMauro Carvalho Chehab                self.emit(title)
402caa642bfSMauro Carvalho Chehab                self.emit("=" * len(title))
403caa642bfSMauro Carvalho Chehab                self.emit()
404caa642bfSMauro Carvalho Chehab
405caa642bfSMauro Carvalho Chehab                self.matrix_lines(desc_size, max_size_status, 0)
406caa642bfSMauro Carvalho Chehab
407caa642bfSMauro Carvalho Chehab                self.emit(f"|{self.h_name:<{self.max_size_name}}", end="")
408caa642bfSMauro Carvalho Chehab                self.emit(f"|{desc_title:<{desc_size}}", end="")
409caa642bfSMauro Carvalho Chehab                self.emit(f"|{h_status_per_arch:<{max_size_status}}|")
410caa642bfSMauro Carvalho Chehab
411caa642bfSMauro Carvalho Chehab                self.matrix_lines(desc_size, max_size_status, 1)
412caa642bfSMauro Carvalho Chehab
413caa642bfSMauro Carvalho Chehab            lines = []
414caa642bfSMauro Carvalho Chehab            descs = []
415caa642bfSMauro Carvalho Chehab            cur_status = ""
416caa642bfSMauro Carvalho Chehab            line = ""
417caa642bfSMauro Carvalho Chehab
418caa642bfSMauro Carvalho Chehab            arch_table = sorted(self.data[name]["table"].items(),
419caa642bfSMauro Carvalho Chehab                                key=lambda x: (self.status_map.get(x[1], 99),
420caa642bfSMauro Carvalho Chehab                                               x[0].lower()))
421caa642bfSMauro Carvalho Chehab
422caa642bfSMauro Carvalho Chehab            for arch, status in arch_table:
423caa642bfSMauro Carvalho Chehab                if status == "---":
424caa642bfSMauro Carvalho Chehab                    status = notcompat
425caa642bfSMauro Carvalho Chehab
426caa642bfSMauro Carvalho Chehab                if status != cur_status:
427caa642bfSMauro Carvalho Chehab                    if line != "":
428caa642bfSMauro Carvalho Chehab                        lines.append(line)
429caa642bfSMauro Carvalho Chehab                        line = ""
430caa642bfSMauro Carvalho Chehab                    line = f"- **{status}**: {arch}"
431caa642bfSMauro Carvalho Chehab                elif len(line) + len(arch) + 2 < max_size_status:
432caa642bfSMauro Carvalho Chehab                    line += f", {arch}"
433caa642bfSMauro Carvalho Chehab                else:
434caa642bfSMauro Carvalho Chehab                    lines.append(line)
435caa642bfSMauro Carvalho Chehab                    line = f"  {arch}"
436caa642bfSMauro Carvalho Chehab                cur_status = status
437caa642bfSMauro Carvalho Chehab
438caa642bfSMauro Carvalho Chehab            if line != "":
439caa642bfSMauro Carvalho Chehab                lines.append(line)
440caa642bfSMauro Carvalho Chehab
441caa642bfSMauro Carvalho Chehab            description = self.data[name]["description"]
442caa642bfSMauro Carvalho Chehab            while len(description) > desc_size:
443caa642bfSMauro Carvalho Chehab                desc_line = description[:desc_size]
444caa642bfSMauro Carvalho Chehab
445caa642bfSMauro Carvalho Chehab                last_space = desc_line.rfind(" ")
446caa642bfSMauro Carvalho Chehab                if last_space != -1:
447caa642bfSMauro Carvalho Chehab                    desc_line = desc_line[:last_space]
448caa642bfSMauro Carvalho Chehab                    descs.append(desc_line)
449caa642bfSMauro Carvalho Chehab                    description = description[last_space + 1:]
450caa642bfSMauro Carvalho Chehab                else:
451caa642bfSMauro Carvalho Chehab                    desc_line = desc_line[:-1]
452caa642bfSMauro Carvalho Chehab                    descs.append(desc_line + "\\")
453caa642bfSMauro Carvalho Chehab                    description = description[len(desc_line):]
454caa642bfSMauro Carvalho Chehab
455caa642bfSMauro Carvalho Chehab            if description:
456caa642bfSMauro Carvalho Chehab                descs.append(description)
457caa642bfSMauro Carvalho Chehab
458caa642bfSMauro Carvalho Chehab            while len(lines) < 2 + len(descs):
459caa642bfSMauro Carvalho Chehab                lines.append("")
460caa642bfSMauro Carvalho Chehab
461caa642bfSMauro Carvalho Chehab            for ln, line in enumerate(lines):
462caa642bfSMauro Carvalho Chehab                col = ["", ""]
463caa642bfSMauro Carvalho Chehab
464caa642bfSMauro Carvalho Chehab                if not ln:
465caa642bfSMauro Carvalho Chehab                    col[0] = name
466caa642bfSMauro Carvalho Chehab                    col[1] = f"``{self.data[name]['kconfig']}``"
467caa642bfSMauro Carvalho Chehab                else:
468caa642bfSMauro Carvalho Chehab                    if ln >= 2 and descs:
469caa642bfSMauro Carvalho Chehab                        col[1] = descs.pop(0)
470caa642bfSMauro Carvalho Chehab
471caa642bfSMauro Carvalho Chehab                self.emit(f"|{col[0]:<{self.max_size_name}}", end="")
472caa642bfSMauro Carvalho Chehab                self.emit(f"|{col[1]:<{desc_size}}", end="")
473caa642bfSMauro Carvalho Chehab                self.emit(f"|{line:<{max_size_status}}|")
474caa642bfSMauro Carvalho Chehab
475caa642bfSMauro Carvalho Chehab            self.matrix_lines(desc_size, max_size_status, 0)
476caa642bfSMauro Carvalho Chehab
477caa642bfSMauro Carvalho Chehab        return self.msg
478caa642bfSMauro Carvalho Chehab
479caa642bfSMauro Carvalho Chehab    def list_arch_features(self, arch, feat):
480caa642bfSMauro Carvalho Chehab        """
481caa642bfSMauro Carvalho Chehab        Print a matrix of kernel feature support for the chosen architecture.
482caa642bfSMauro Carvalho Chehab        """
483caa642bfSMauro Carvalho Chehab        self.emit("#")
484caa642bfSMauro Carvalho Chehab        self.emit(f"# Kernel feature support matrix of the '{arch}' architecture:")
485caa642bfSMauro Carvalho Chehab        self.emit("#")
486caa642bfSMauro Carvalho Chehab
487caa642bfSMauro Carvalho Chehab        # Sort by subsystem, then by feature name (case‑insensitive)
488caa642bfSMauro Carvalho Chehab        for name in sorted(self.data.keys(),
489caa642bfSMauro Carvalho Chehab                           key=lambda n: (self.data[n]["subsys"].lower(),
490caa642bfSMauro Carvalho Chehab                                          n.lower())):
491caa642bfSMauro Carvalho Chehab            if feat and name != feat:
492caa642bfSMauro Carvalho Chehab                continue
493caa642bfSMauro Carvalho Chehab
494caa642bfSMauro Carvalho Chehab            feature = self.data[name]
495caa642bfSMauro Carvalho Chehab            arch_table = feature["table"]
496caa642bfSMauro Carvalho Chehab            status = arch_table.get(arch, "")
497caa642bfSMauro Carvalho Chehab            status = " " * ((4 - len(status)) // 2) + status
498caa642bfSMauro Carvalho Chehab
499caa642bfSMauro Carvalho Chehab            self.emit(f"{feature['subsys']:>{self.max_size_subsys + 1}}/ ",
500caa642bfSMauro Carvalho Chehab                      end="")
501caa642bfSMauro Carvalho Chehab            self.emit(f"{name:<{self.max_size_name}}: ", end="")
502caa642bfSMauro Carvalho Chehab            self.emit(f"{status:<5}|   ", end="")
503caa642bfSMauro Carvalho Chehab            self.emit(f"{feature['kconfig']:>{self.max_size_kconfig}} ",
504caa642bfSMauro Carvalho Chehab                      end="")
505caa642bfSMauro Carvalho Chehab            self.emit(f"#  {feature['description']}")
506caa642bfSMauro Carvalho Chehab
507caa642bfSMauro Carvalho Chehab        return self.msg
508