xref: /linux/tools/lib/python/abi/abi_parser.py (revision 72c395024dac5e215136cbff793455f065603b06)
1484e9aa6SMauro Carvalho Chehab#!/usr/bin/env python3
2484e9aa6SMauro Carvalho Chehab# pylint: disable=R0902,R0903,R0911,R0912,R0913,R0914,R0915,R0917,C0302
3484e9aa6SMauro Carvalho Chehab# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
4484e9aa6SMauro Carvalho Chehab# SPDX-License-Identifier: GPL-2.0
5484e9aa6SMauro Carvalho Chehab
6484e9aa6SMauro Carvalho Chehab"""
7484e9aa6SMauro Carvalho ChehabParse ABI documentation and produce results from it.
8484e9aa6SMauro Carvalho Chehab"""
9484e9aa6SMauro Carvalho Chehab
10484e9aa6SMauro Carvalho Chehabfrom argparse import Namespace
11484e9aa6SMauro Carvalho Chehabimport logging
12484e9aa6SMauro Carvalho Chehabimport os
13484e9aa6SMauro Carvalho Chehabimport re
14484e9aa6SMauro Carvalho Chehab
15484e9aa6SMauro Carvalho Chehabfrom pprint import pformat
16484e9aa6SMauro Carvalho Chehabfrom random import randrange, seed
17484e9aa6SMauro Carvalho Chehab
18484e9aa6SMauro Carvalho Chehab# Import Python modules
19484e9aa6SMauro Carvalho Chehab
20992a9df4SJonathan Corbetfrom abi.helpers import AbiDebug, ABI_DIR
21484e9aa6SMauro Carvalho Chehab
22484e9aa6SMauro Carvalho Chehab
23484e9aa6SMauro Carvalho Chehabclass AbiParser:
24*66c3bf97SMauro Carvalho Chehab    """Main class to parse ABI files."""
25484e9aa6SMauro Carvalho Chehab
26*66c3bf97SMauro Carvalho Chehab    #: Valid tags at Documentation/ABI.
27484e9aa6SMauro Carvalho Chehab    TAGS = r"(what|where|date|kernelversion|contact|description|users)"
28*66c3bf97SMauro Carvalho Chehab
29*66c3bf97SMauro Carvalho Chehab    #: ABI elements that will auto-generate cross-references.
30484e9aa6SMauro Carvalho Chehab    XREF = r"(?:^|\s|\()(\/(?:sys|config|proc|dev|kvd)\/[^,.:;\)\s]+)(?:[,.:;\)\s]|\Z)"
31484e9aa6SMauro Carvalho Chehab
32484e9aa6SMauro Carvalho Chehab    def __init__(self, directory, logger=None,
33484e9aa6SMauro Carvalho Chehab                 enable_lineno=False, show_warnings=True, debug=0):
34*66c3bf97SMauro Carvalho Chehab        """Stores arguments for the class and initialize class vars."""
35484e9aa6SMauro Carvalho Chehab
36484e9aa6SMauro Carvalho Chehab        self.directory = directory
37484e9aa6SMauro Carvalho Chehab        self.enable_lineno = enable_lineno
38484e9aa6SMauro Carvalho Chehab        self.show_warnings = show_warnings
39484e9aa6SMauro Carvalho Chehab        self.debug = debug
40484e9aa6SMauro Carvalho Chehab
41484e9aa6SMauro Carvalho Chehab        if not logger:
42484e9aa6SMauro Carvalho Chehab            self.log = logging.getLogger("get_abi")
43484e9aa6SMauro Carvalho Chehab        else:
44484e9aa6SMauro Carvalho Chehab            self.log = logger
45484e9aa6SMauro Carvalho Chehab
46484e9aa6SMauro Carvalho Chehab        self.data = {}
47484e9aa6SMauro Carvalho Chehab        self.what_symbols = {}
48484e9aa6SMauro Carvalho Chehab        self.file_refs = {}
49484e9aa6SMauro Carvalho Chehab        self.what_refs = {}
50484e9aa6SMauro Carvalho Chehab
51c67c3fbdSMauro Carvalho Chehab        # Ignore files that contain such suffixes
52c67c3fbdSMauro Carvalho Chehab        self.ignore_suffixes = (".rej", ".org", ".orig", ".bak", "~")
53c67c3fbdSMauro Carvalho Chehab
54484e9aa6SMauro Carvalho Chehab        # Regular expressions used on parser
55c67c3fbdSMauro Carvalho Chehab        self.re_abi_dir = re.compile(r"(.*)" + ABI_DIR)
56484e9aa6SMauro Carvalho Chehab        self.re_tag = re.compile(r"(\S+)(:\s*)(.*)", re.I)
57484e9aa6SMauro Carvalho Chehab        self.re_valid = re.compile(self.TAGS)
58484e9aa6SMauro Carvalho Chehab        self.re_start_spc = re.compile(r"(\s*)(\S.*)")
59484e9aa6SMauro Carvalho Chehab        self.re_whitespace = re.compile(r"^\s+")
60484e9aa6SMauro Carvalho Chehab
61484e9aa6SMauro Carvalho Chehab        # Regular used on print
62484e9aa6SMauro Carvalho Chehab        self.re_what = re.compile(r"(\/?(?:[\w\-]+\/?){1,2})")
63484e9aa6SMauro Carvalho Chehab        self.re_escape = re.compile(r"([\.\x01-\x08\x0e-\x1f\x21-\x2f\x3a-\x40\x7b-\xff])")
64484e9aa6SMauro Carvalho Chehab        self.re_unprintable = re.compile(r"([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xff]+)")
65484e9aa6SMauro Carvalho Chehab        self.re_title_mark = re.compile(r"\n[\-\*\=\^\~]+\n")
66484e9aa6SMauro Carvalho Chehab        self.re_doc = re.compile(r"Documentation/(?!devicetree)(\S+)\.rst")
67484e9aa6SMauro Carvalho Chehab        self.re_abi = re.compile(r"(Documentation/ABI/)([\w\/\-]+)")
68484e9aa6SMauro Carvalho Chehab        self.re_xref_node = re.compile(self.XREF)
69484e9aa6SMauro Carvalho Chehab
70484e9aa6SMauro Carvalho Chehab    def warn(self, fdata, msg, extra=None):
71*66c3bf97SMauro Carvalho Chehab        """Displays a parse error if warning is enabled."""
72484e9aa6SMauro Carvalho Chehab
73484e9aa6SMauro Carvalho Chehab        if not self.show_warnings:
74484e9aa6SMauro Carvalho Chehab            return
75484e9aa6SMauro Carvalho Chehab
76484e9aa6SMauro Carvalho Chehab        msg = f"{fdata.fname}:{fdata.ln}: {msg}"
77484e9aa6SMauro Carvalho Chehab        if extra:
78484e9aa6SMauro Carvalho Chehab            msg += "\n\t\t" + extra
79484e9aa6SMauro Carvalho Chehab
80484e9aa6SMauro Carvalho Chehab        self.log.warning(msg)
81484e9aa6SMauro Carvalho Chehab
82484e9aa6SMauro Carvalho Chehab    def add_symbol(self, what, fname, ln=None, xref=None):
83*66c3bf97SMauro Carvalho Chehab        """Create a reference table describing where each 'what' is located."""
84484e9aa6SMauro Carvalho Chehab
85484e9aa6SMauro Carvalho Chehab        if what not in self.what_symbols:
86484e9aa6SMauro Carvalho Chehab            self.what_symbols[what] = {"file": {}}
87484e9aa6SMauro Carvalho Chehab
88484e9aa6SMauro Carvalho Chehab        if fname not in self.what_symbols[what]["file"]:
89484e9aa6SMauro Carvalho Chehab            self.what_symbols[what]["file"][fname] = []
90484e9aa6SMauro Carvalho Chehab
91484e9aa6SMauro Carvalho Chehab        if ln and ln not in self.what_symbols[what]["file"][fname]:
92484e9aa6SMauro Carvalho Chehab            self.what_symbols[what]["file"][fname].append(ln)
93484e9aa6SMauro Carvalho Chehab
94484e9aa6SMauro Carvalho Chehab        if xref:
95484e9aa6SMauro Carvalho Chehab            self.what_symbols[what]["xref"] = xref
96484e9aa6SMauro Carvalho Chehab
97484e9aa6SMauro Carvalho Chehab    def _parse_line(self, fdata, line):
98*66c3bf97SMauro Carvalho Chehab        """Parse a single line of an ABI file."""
99484e9aa6SMauro Carvalho Chehab
100484e9aa6SMauro Carvalho Chehab        new_what = False
101484e9aa6SMauro Carvalho Chehab        new_tag = False
102484e9aa6SMauro Carvalho Chehab        content = None
103484e9aa6SMauro Carvalho Chehab
104484e9aa6SMauro Carvalho Chehab        match = self.re_tag.match(line)
105484e9aa6SMauro Carvalho Chehab        if match:
106484e9aa6SMauro Carvalho Chehab            new = match.group(1).lower()
107484e9aa6SMauro Carvalho Chehab            sep = match.group(2)
108484e9aa6SMauro Carvalho Chehab            content = match.group(3)
109484e9aa6SMauro Carvalho Chehab
110484e9aa6SMauro Carvalho Chehab            match = self.re_valid.search(new)
111484e9aa6SMauro Carvalho Chehab            if match:
112484e9aa6SMauro Carvalho Chehab                new_tag = match.group(1)
113484e9aa6SMauro Carvalho Chehab            else:
114484e9aa6SMauro Carvalho Chehab                if fdata.tag == "description":
115484e9aa6SMauro Carvalho Chehab                    # New "tag" is actually part of description.
116484e9aa6SMauro Carvalho Chehab                    # Don't consider it a tag
117484e9aa6SMauro Carvalho Chehab                    new_tag = False
118484e9aa6SMauro Carvalho Chehab                elif fdata.tag != "":
119484e9aa6SMauro Carvalho Chehab                    self.warn(fdata, f"tag '{fdata.tag}' is invalid", line)
120484e9aa6SMauro Carvalho Chehab
121484e9aa6SMauro Carvalho Chehab        if new_tag:
122484e9aa6SMauro Carvalho Chehab            # "where" is Invalid, but was a common mistake. Warn if found
123484e9aa6SMauro Carvalho Chehab            if new_tag == "where":
124484e9aa6SMauro Carvalho Chehab                self.warn(fdata, "tag 'Where' is invalid. Should be 'What:' instead")
125484e9aa6SMauro Carvalho Chehab                new_tag = "what"
126484e9aa6SMauro Carvalho Chehab
127484e9aa6SMauro Carvalho Chehab            if new_tag == "what":
128484e9aa6SMauro Carvalho Chehab                fdata.space = None
129484e9aa6SMauro Carvalho Chehab
130484e9aa6SMauro Carvalho Chehab                if content not in self.what_symbols:
131484e9aa6SMauro Carvalho Chehab                    self.add_symbol(what=content, fname=fdata.fname, ln=fdata.ln)
132484e9aa6SMauro Carvalho Chehab
133484e9aa6SMauro Carvalho Chehab                if fdata.tag == "what":
134484e9aa6SMauro Carvalho Chehab                    fdata.what.append(content.strip("\n"))
135484e9aa6SMauro Carvalho Chehab                else:
136484e9aa6SMauro Carvalho Chehab                    if fdata.key:
137484e9aa6SMauro Carvalho Chehab                        if "description" not in self.data.get(fdata.key, {}):
138484e9aa6SMauro Carvalho Chehab                            self.warn(fdata, f"{fdata.key} doesn't have a description")
139484e9aa6SMauro Carvalho Chehab
140484e9aa6SMauro Carvalho Chehab                        for w in fdata.what:
141484e9aa6SMauro Carvalho Chehab                            self.add_symbol(what=w, fname=fdata.fname,
142484e9aa6SMauro Carvalho Chehab                                            ln=fdata.what_ln, xref=fdata.key)
143484e9aa6SMauro Carvalho Chehab
144484e9aa6SMauro Carvalho Chehab                    fdata.label = content
145484e9aa6SMauro Carvalho Chehab                    new_what = True
146484e9aa6SMauro Carvalho Chehab
147484e9aa6SMauro Carvalho Chehab                    key = "abi_" + content.lower()
148484e9aa6SMauro Carvalho Chehab                    fdata.key = self.re_unprintable.sub("_", key).strip("_")
149484e9aa6SMauro Carvalho Chehab
150484e9aa6SMauro Carvalho Chehab                    # Avoid duplicated keys but using a defined seed, to make
151484e9aa6SMauro Carvalho Chehab                    # the namespace identical if there aren't changes at the
152484e9aa6SMauro Carvalho Chehab                    # ABI symbols
153484e9aa6SMauro Carvalho Chehab                    seed(42)
154484e9aa6SMauro Carvalho Chehab
155484e9aa6SMauro Carvalho Chehab                    while fdata.key in self.data:
156484e9aa6SMauro Carvalho Chehab                        char = randrange(0, 51) + ord("A")
157484e9aa6SMauro Carvalho Chehab                        if char > ord("Z"):
158484e9aa6SMauro Carvalho Chehab                            char += ord("a") - ord("Z") - 1
159484e9aa6SMauro Carvalho Chehab
160484e9aa6SMauro Carvalho Chehab                        fdata.key += chr(char)
161484e9aa6SMauro Carvalho Chehab
162484e9aa6SMauro Carvalho Chehab                    if fdata.key and fdata.key not in self.data:
163484e9aa6SMauro Carvalho Chehab                        self.data[fdata.key] = {
164484e9aa6SMauro Carvalho Chehab                            "what": [content],
165484e9aa6SMauro Carvalho Chehab                            "file": [fdata.file_ref],
1662a21d80dSMauro Carvalho Chehab                            "path": fdata.ftype,
167484e9aa6SMauro Carvalho Chehab                            "line_no": fdata.ln,
168484e9aa6SMauro Carvalho Chehab                        }
169484e9aa6SMauro Carvalho Chehab
170484e9aa6SMauro Carvalho Chehab                    fdata.what = self.data[fdata.key]["what"]
171484e9aa6SMauro Carvalho Chehab
172484e9aa6SMauro Carvalho Chehab                self.what_refs[content] = fdata.key
173484e9aa6SMauro Carvalho Chehab                fdata.tag = new_tag
174484e9aa6SMauro Carvalho Chehab                fdata.what_ln = fdata.ln
175484e9aa6SMauro Carvalho Chehab
176484e9aa6SMauro Carvalho Chehab                if fdata.nametag["what"]:
177484e9aa6SMauro Carvalho Chehab                    t = (content, fdata.key)
178484e9aa6SMauro Carvalho Chehab                    if t not in fdata.nametag["symbols"]:
179484e9aa6SMauro Carvalho Chehab                        fdata.nametag["symbols"].append(t)
180484e9aa6SMauro Carvalho Chehab
181484e9aa6SMauro Carvalho Chehab                return
182484e9aa6SMauro Carvalho Chehab
183484e9aa6SMauro Carvalho Chehab            if fdata.tag and new_tag:
184484e9aa6SMauro Carvalho Chehab                fdata.tag = new_tag
185484e9aa6SMauro Carvalho Chehab
186484e9aa6SMauro Carvalho Chehab                if new_what:
187484e9aa6SMauro Carvalho Chehab                    fdata.label = ""
188484e9aa6SMauro Carvalho Chehab
189484e9aa6SMauro Carvalho Chehab                    if "description" in self.data[fdata.key]:
190484e9aa6SMauro Carvalho Chehab                        self.data[fdata.key]["description"] += "\n\n"
191484e9aa6SMauro Carvalho Chehab
192484e9aa6SMauro Carvalho Chehab                    if fdata.file_ref not in self.data[fdata.key]["file"]:
193484e9aa6SMauro Carvalho Chehab                        self.data[fdata.key]["file"].append(fdata.file_ref)
194484e9aa6SMauro Carvalho Chehab
195484e9aa6SMauro Carvalho Chehab                    if self.debug == AbiDebug.WHAT_PARSING:
196484e9aa6SMauro Carvalho Chehab                        self.log.debug("what: %s", fdata.what)
197484e9aa6SMauro Carvalho Chehab
198484e9aa6SMauro Carvalho Chehab                if not fdata.what:
199484e9aa6SMauro Carvalho Chehab                    self.warn(fdata, "'What:' should come first:", line)
200484e9aa6SMauro Carvalho Chehab                    return
201484e9aa6SMauro Carvalho Chehab
202484e9aa6SMauro Carvalho Chehab                if new_tag == "description":
203484e9aa6SMauro Carvalho Chehab                    fdata.space = None
204484e9aa6SMauro Carvalho Chehab
205484e9aa6SMauro Carvalho Chehab                    if content:
206484e9aa6SMauro Carvalho Chehab                        sep = sep.replace(":", " ")
207484e9aa6SMauro Carvalho Chehab
208484e9aa6SMauro Carvalho Chehab                        c = " " * len(new_tag) + sep + content
209484e9aa6SMauro Carvalho Chehab                        c = c.expandtabs()
210484e9aa6SMauro Carvalho Chehab
211484e9aa6SMauro Carvalho Chehab                        match = self.re_start_spc.match(c)
212484e9aa6SMauro Carvalho Chehab                        if match:
213484e9aa6SMauro Carvalho Chehab                            # Preserve initial spaces for the first line
214484e9aa6SMauro Carvalho Chehab                            fdata.space = match.group(1)
215484e9aa6SMauro Carvalho Chehab                            content = match.group(2) + "\n"
216484e9aa6SMauro Carvalho Chehab
217484e9aa6SMauro Carvalho Chehab                self.data[fdata.key][fdata.tag] = content
218484e9aa6SMauro Carvalho Chehab
219484e9aa6SMauro Carvalho Chehab            return
220484e9aa6SMauro Carvalho Chehab
221484e9aa6SMauro Carvalho Chehab        # Store any contents before tags at the database
222484e9aa6SMauro Carvalho Chehab        if not fdata.tag and "what" in fdata.nametag:
223484e9aa6SMauro Carvalho Chehab            fdata.nametag["description"] += line
224484e9aa6SMauro Carvalho Chehab            return
225484e9aa6SMauro Carvalho Chehab
226484e9aa6SMauro Carvalho Chehab        if fdata.tag == "description":
227484e9aa6SMauro Carvalho Chehab            content = line.expandtabs()
228484e9aa6SMauro Carvalho Chehab
229484e9aa6SMauro Carvalho Chehab            if self.re_whitespace.sub("", content) == "":
230484e9aa6SMauro Carvalho Chehab                self.data[fdata.key][fdata.tag] += "\n"
231484e9aa6SMauro Carvalho Chehab                return
232484e9aa6SMauro Carvalho Chehab
233484e9aa6SMauro Carvalho Chehab            if fdata.space is None:
234484e9aa6SMauro Carvalho Chehab                match = self.re_start_spc.match(content)
235484e9aa6SMauro Carvalho Chehab                if match:
236484e9aa6SMauro Carvalho Chehab                    # Preserve initial spaces for the first line
237484e9aa6SMauro Carvalho Chehab                    fdata.space = match.group(1)
238484e9aa6SMauro Carvalho Chehab
239484e9aa6SMauro Carvalho Chehab                    content = match.group(2) + "\n"
240484e9aa6SMauro Carvalho Chehab            else:
241484e9aa6SMauro Carvalho Chehab                if content.startswith(fdata.space):
242484e9aa6SMauro Carvalho Chehab                    content = content[len(fdata.space):]
243484e9aa6SMauro Carvalho Chehab
244484e9aa6SMauro Carvalho Chehab                else:
245484e9aa6SMauro Carvalho Chehab                    fdata.space = ""
246484e9aa6SMauro Carvalho Chehab
247484e9aa6SMauro Carvalho Chehab            if fdata.tag == "what":
248484e9aa6SMauro Carvalho Chehab                w = content.strip("\n")
249484e9aa6SMauro Carvalho Chehab                if w:
250484e9aa6SMauro Carvalho Chehab                    self.data[fdata.key][fdata.tag].append(w)
251484e9aa6SMauro Carvalho Chehab            else:
252484e9aa6SMauro Carvalho Chehab                self.data[fdata.key][fdata.tag] += content
253484e9aa6SMauro Carvalho Chehab            return
254484e9aa6SMauro Carvalho Chehab
255484e9aa6SMauro Carvalho Chehab        content = line.strip()
256484e9aa6SMauro Carvalho Chehab        if fdata.tag:
257484e9aa6SMauro Carvalho Chehab            if fdata.tag == "what":
258484e9aa6SMauro Carvalho Chehab                w = content.strip("\n")
259484e9aa6SMauro Carvalho Chehab                if w:
260484e9aa6SMauro Carvalho Chehab                    self.data[fdata.key][fdata.tag].append(w)
261484e9aa6SMauro Carvalho Chehab            else:
262484e9aa6SMauro Carvalho Chehab                self.data[fdata.key][fdata.tag] += "\n" + content.rstrip("\n")
263484e9aa6SMauro Carvalho Chehab            return
264484e9aa6SMauro Carvalho Chehab
265484e9aa6SMauro Carvalho Chehab        # Everything else is error
266484e9aa6SMauro Carvalho Chehab        if content:
267484e9aa6SMauro Carvalho Chehab            self.warn(fdata, "Unexpected content", line)
268484e9aa6SMauro Carvalho Chehab
26998a4324aSMauro Carvalho Chehab    def parse_readme(self, nametag, fname):
270*66c3bf97SMauro Carvalho Chehab        """Parse ABI README file."""
27198a4324aSMauro Carvalho Chehab
272de61d651SMauro Carvalho Chehab        nametag["what"] = ["Introduction"]
2735d7871d7SMauro Carvalho Chehab        nametag["path"] = "README"
27498a4324aSMauro Carvalho Chehab        with open(fname, "r", encoding="utf8", errors="backslashreplace") as fp:
27598a4324aSMauro Carvalho Chehab            for line in fp:
2765d7871d7SMauro Carvalho Chehab                match = self.re_tag.match(line)
2775d7871d7SMauro Carvalho Chehab                if match:
2785d7871d7SMauro Carvalho Chehab                    new = match.group(1).lower()
27998a4324aSMauro Carvalho Chehab
2805d7871d7SMauro Carvalho Chehab                    match = self.re_valid.search(new)
2815d7871d7SMauro Carvalho Chehab                    if match:
2825d7871d7SMauro Carvalho Chehab                        nametag["description"] += "\n:" + line
2835d7871d7SMauro Carvalho Chehab                        continue
2845d7871d7SMauro Carvalho Chehab
2855d7871d7SMauro Carvalho Chehab                nametag["description"] += line
28698a4324aSMauro Carvalho Chehab
287484e9aa6SMauro Carvalho Chehab    def parse_file(self, fname, path, basename):
288*66c3bf97SMauro Carvalho Chehab        """Parse a single file."""
289484e9aa6SMauro Carvalho Chehab
290484e9aa6SMauro Carvalho Chehab        ref = f"abi_file_{path}_{basename}"
291484e9aa6SMauro Carvalho Chehab        ref = self.re_unprintable.sub("_", ref).strip("_")
292484e9aa6SMauro Carvalho Chehab
293484e9aa6SMauro Carvalho Chehab        # Store per-file state into a namespace variable. This will be used
294484e9aa6SMauro Carvalho Chehab        # by the per-line parser state machine and by the warning function.
295484e9aa6SMauro Carvalho Chehab        fdata = Namespace
296484e9aa6SMauro Carvalho Chehab
297484e9aa6SMauro Carvalho Chehab        fdata.fname = fname
298484e9aa6SMauro Carvalho Chehab        fdata.name = basename
299484e9aa6SMauro Carvalho Chehab
300484e9aa6SMauro Carvalho Chehab        pos = fname.find(ABI_DIR)
301484e9aa6SMauro Carvalho Chehab        if pos > 0:
302484e9aa6SMauro Carvalho Chehab            f = fname[pos:]
303484e9aa6SMauro Carvalho Chehab        else:
304484e9aa6SMauro Carvalho Chehab            f = fname
305484e9aa6SMauro Carvalho Chehab
306484e9aa6SMauro Carvalho Chehab        fdata.file_ref = (f, ref)
307484e9aa6SMauro Carvalho Chehab        self.file_refs[f] = ref
308484e9aa6SMauro Carvalho Chehab
309484e9aa6SMauro Carvalho Chehab        fdata.ln = 0
310484e9aa6SMauro Carvalho Chehab        fdata.what_ln = 0
311484e9aa6SMauro Carvalho Chehab        fdata.tag = ""
312484e9aa6SMauro Carvalho Chehab        fdata.label = ""
313484e9aa6SMauro Carvalho Chehab        fdata.what = []
314484e9aa6SMauro Carvalho Chehab        fdata.key = None
315484e9aa6SMauro Carvalho Chehab        fdata.xrefs = None
316484e9aa6SMauro Carvalho Chehab        fdata.space = None
317484e9aa6SMauro Carvalho Chehab        fdata.ftype = path.split("/")[0]
318484e9aa6SMauro Carvalho Chehab
319484e9aa6SMauro Carvalho Chehab        fdata.nametag = {}
320dc525a76SMauro Carvalho Chehab        fdata.nametag["what"] = [f"ABI file {path}/{basename}"]
321484e9aa6SMauro Carvalho Chehab        fdata.nametag["type"] = "File"
3222a21d80dSMauro Carvalho Chehab        fdata.nametag["path"] = fdata.ftype
323484e9aa6SMauro Carvalho Chehab        fdata.nametag["file"] = [fdata.file_ref]
324484e9aa6SMauro Carvalho Chehab        fdata.nametag["line_no"] = 1
325484e9aa6SMauro Carvalho Chehab        fdata.nametag["description"] = ""
326484e9aa6SMauro Carvalho Chehab        fdata.nametag["symbols"] = []
327484e9aa6SMauro Carvalho Chehab
328484e9aa6SMauro Carvalho Chehab        self.data[ref] = fdata.nametag
329484e9aa6SMauro Carvalho Chehab
330484e9aa6SMauro Carvalho Chehab        if self.debug & AbiDebug.WHAT_OPEN:
331484e9aa6SMauro Carvalho Chehab            self.log.debug("Opening file %s", fname)
332484e9aa6SMauro Carvalho Chehab
33398a4324aSMauro Carvalho Chehab        if basename == "README":
33498a4324aSMauro Carvalho Chehab            self.parse_readme(fdata.nametag, fname)
33598a4324aSMauro Carvalho Chehab            return
33698a4324aSMauro Carvalho Chehab
337484e9aa6SMauro Carvalho Chehab        with open(fname, "r", encoding="utf8", errors="backslashreplace") as fp:
338484e9aa6SMauro Carvalho Chehab            for line in fp:
339484e9aa6SMauro Carvalho Chehab                fdata.ln += 1
340484e9aa6SMauro Carvalho Chehab
341484e9aa6SMauro Carvalho Chehab                self._parse_line(fdata, line)
342484e9aa6SMauro Carvalho Chehab
343484e9aa6SMauro Carvalho Chehab            if "description" in fdata.nametag:
344484e9aa6SMauro Carvalho Chehab                fdata.nametag["description"] = fdata.nametag["description"].lstrip("\n")
345484e9aa6SMauro Carvalho Chehab
346484e9aa6SMauro Carvalho Chehab            if fdata.key:
347484e9aa6SMauro Carvalho Chehab                if "description" not in self.data.get(fdata.key, {}):
348484e9aa6SMauro Carvalho Chehab                    self.warn(fdata, f"{fdata.key} doesn't have a description")
349484e9aa6SMauro Carvalho Chehab
350484e9aa6SMauro Carvalho Chehab                for w in fdata.what:
351484e9aa6SMauro Carvalho Chehab                    self.add_symbol(what=w, fname=fname, xref=fdata.key)
352484e9aa6SMauro Carvalho Chehab
353c67c3fbdSMauro Carvalho Chehab    def _parse_abi(self, root=None):
354*66c3bf97SMauro Carvalho Chehab        """Internal function to parse documentation ABI recursively."""
355484e9aa6SMauro Carvalho Chehab
356c67c3fbdSMauro Carvalho Chehab        if not root:
357c67c3fbdSMauro Carvalho Chehab            root = self.directory
358484e9aa6SMauro Carvalho Chehab
359c67c3fbdSMauro Carvalho Chehab        with os.scandir(root) as obj:
360c67c3fbdSMauro Carvalho Chehab            for entry in obj:
361c67c3fbdSMauro Carvalho Chehab                name = os.path.join(root, entry.name)
362c67c3fbdSMauro Carvalho Chehab
363c67c3fbdSMauro Carvalho Chehab                if entry.is_dir():
3649bec7870SMauro Carvalho Chehab                    self._parse_abi(name)
365484e9aa6SMauro Carvalho Chehab                    continue
366484e9aa6SMauro Carvalho Chehab
367c67c3fbdSMauro Carvalho Chehab                if not entry.is_file():
368c67c3fbdSMauro Carvalho Chehab                    continue
369c67c3fbdSMauro Carvalho Chehab
370c67c3fbdSMauro Carvalho Chehab                basename = os.path.basename(name)
371484e9aa6SMauro Carvalho Chehab
372c67c3fbdSMauro Carvalho Chehab                if basename.startswith("."):
373484e9aa6SMauro Carvalho Chehab                    continue
374484e9aa6SMauro Carvalho Chehab
375c67c3fbdSMauro Carvalho Chehab                if basename.endswith(self.ignore_suffixes):
376c67c3fbdSMauro Carvalho Chehab                    continue
377484e9aa6SMauro Carvalho Chehab
378c67c3fbdSMauro Carvalho Chehab                path = self.re_abi_dir.sub("", os.path.dirname(name))
379c67c3fbdSMauro Carvalho Chehab
380c67c3fbdSMauro Carvalho Chehab                self.parse_file(name, path, basename)
381c67c3fbdSMauro Carvalho Chehab
382c67c3fbdSMauro Carvalho Chehab    def parse_abi(self, root=None):
383*66c3bf97SMauro Carvalho Chehab        """Parse documentation ABI."""
384c67c3fbdSMauro Carvalho Chehab
385c67c3fbdSMauro Carvalho Chehab        self._parse_abi(root)
386484e9aa6SMauro Carvalho Chehab
387484e9aa6SMauro Carvalho Chehab        if self.debug & AbiDebug.DUMP_ABI_STRUCTS:
388484e9aa6SMauro Carvalho Chehab            self.log.debug(pformat(self.data))
389484e9aa6SMauro Carvalho Chehab
3909bec7870SMauro Carvalho Chehab    def desc_txt(self, desc):
391*66c3bf97SMauro Carvalho Chehab        """Print description as found inside ABI files."""
392484e9aa6SMauro Carvalho Chehab
393484e9aa6SMauro Carvalho Chehab        desc = desc.strip(" \t\n")
394484e9aa6SMauro Carvalho Chehab
3959bec7870SMauro Carvalho Chehab        return desc + "\n\n"
396484e9aa6SMauro Carvalho Chehab
397c9408169SMauro Carvalho Chehab    def xref(self, fname):
398c9408169SMauro Carvalho Chehab        """
399*66c3bf97SMauro Carvalho Chehab        Converts a Documentation/ABI + basename into a ReST cross-reference.
400c9408169SMauro Carvalho Chehab        """
401c9408169SMauro Carvalho Chehab
402c9408169SMauro Carvalho Chehab        xref = self.file_refs.get(fname)
403c9408169SMauro Carvalho Chehab        if not xref:
404c9408169SMauro Carvalho Chehab            return None
405c9408169SMauro Carvalho Chehab        else:
406c9408169SMauro Carvalho Chehab            return xref
407c9408169SMauro Carvalho Chehab
4089bec7870SMauro Carvalho Chehab    def desc_rst(self, desc):
409*66c3bf97SMauro Carvalho Chehab        """Enrich ReST output by creating cross-references."""
410484e9aa6SMauro Carvalho Chehab
411484e9aa6SMauro Carvalho Chehab        # Remove title markups from the description
412484e9aa6SMauro Carvalho Chehab        # Having titles inside ABI files will only work if extra
413484e9aa6SMauro Carvalho Chehab        # care would be taken in order to strictly follow the same
414484e9aa6SMauro Carvalho Chehab        # level order for each markup.
415484e9aa6SMauro Carvalho Chehab        desc = self.re_title_mark.sub("\n\n", "\n" + desc)
416484e9aa6SMauro Carvalho Chehab        desc = desc.rstrip(" \t\n").lstrip("\n")
417484e9aa6SMauro Carvalho Chehab
418484e9aa6SMauro Carvalho Chehab        # Python's regex performance for non-compiled expressions is a lot
419484e9aa6SMauro Carvalho Chehab        # than Perl, as Perl automatically caches them at their
420484e9aa6SMauro Carvalho Chehab        # first usage. Here, we'll need to do the same, as otherwise the
421484e9aa6SMauro Carvalho Chehab        # performance penalty is be high
422484e9aa6SMauro Carvalho Chehab
423484e9aa6SMauro Carvalho Chehab        new_desc = ""
424484e9aa6SMauro Carvalho Chehab        for d in desc.split("\n"):
425484e9aa6SMauro Carvalho Chehab            if d == "":
426484e9aa6SMauro Carvalho Chehab                new_desc += "\n"
427484e9aa6SMauro Carvalho Chehab                continue
428484e9aa6SMauro Carvalho Chehab
429484e9aa6SMauro Carvalho Chehab            # Use cross-references for doc files where needed
430484e9aa6SMauro Carvalho Chehab            d = self.re_doc.sub(r":doc:`/\1`", d)
431484e9aa6SMauro Carvalho Chehab
432484e9aa6SMauro Carvalho Chehab            # Use cross-references for ABI generated docs where needed
433484e9aa6SMauro Carvalho Chehab            matches = self.re_abi.findall(d)
434484e9aa6SMauro Carvalho Chehab            for m in matches:
435484e9aa6SMauro Carvalho Chehab                abi = m[0] + m[1]
436484e9aa6SMauro Carvalho Chehab
437484e9aa6SMauro Carvalho Chehab                xref = self.file_refs.get(abi)
438484e9aa6SMauro Carvalho Chehab                if not xref:
439484e9aa6SMauro Carvalho Chehab                    # This may happen if ABI is on a separate directory,
440484e9aa6SMauro Carvalho Chehab                    # like parsing ABI testing and symbol is at stable.
441484e9aa6SMauro Carvalho Chehab                    # The proper solution is to move this part of the code
442484e9aa6SMauro Carvalho Chehab                    # for it to be inside sphinx/kernel_abi.py
443484e9aa6SMauro Carvalho Chehab                    self.log.info("Didn't find ABI reference for '%s'", abi)
444484e9aa6SMauro Carvalho Chehab                else:
445484e9aa6SMauro Carvalho Chehab                    new = self.re_escape.sub(r"\\\1", m[1])
446484e9aa6SMauro Carvalho Chehab                    d = re.sub(fr"\b{abi}\b", f":ref:`{new} <{xref}>`", d)
447484e9aa6SMauro Carvalho Chehab
448484e9aa6SMauro Carvalho Chehab            # Seek for cross reference symbols like /sys/...
449484e9aa6SMauro Carvalho Chehab            # Need to be careful to avoid doing it on a code block
450484e9aa6SMauro Carvalho Chehab            if d[0] not in [" ", "\t"]:
451484e9aa6SMauro Carvalho Chehab                matches = self.re_xref_node.findall(d)
452484e9aa6SMauro Carvalho Chehab                for m in matches:
453484e9aa6SMauro Carvalho Chehab                    # Finding ABI here is more complex due to wildcards
454484e9aa6SMauro Carvalho Chehab                    xref = self.what_refs.get(m)
455484e9aa6SMauro Carvalho Chehab                    if xref:
456484e9aa6SMauro Carvalho Chehab                        new = self.re_escape.sub(r"\\\1", m)
457484e9aa6SMauro Carvalho Chehab                        d = re.sub(fr"\b{m}\b", f":ref:`{new} <{xref}>`", d)
458484e9aa6SMauro Carvalho Chehab
459484e9aa6SMauro Carvalho Chehab            new_desc += d + "\n"
460484e9aa6SMauro Carvalho Chehab
4619bec7870SMauro Carvalho Chehab        return new_desc + "\n\n"
462484e9aa6SMauro Carvalho Chehab
4632a21d80dSMauro Carvalho Chehab    def doc(self, output_in_txt=False, show_symbols=True, show_file=True,
4642a21d80dSMauro Carvalho Chehab            filter_path=None):
465*66c3bf97SMauro Carvalho Chehab        """Print ABI at stdout."""
466484e9aa6SMauro Carvalho Chehab
467484e9aa6SMauro Carvalho Chehab        part = None
468484e9aa6SMauro Carvalho Chehab        for key, v in sorted(self.data.items(),
469484e9aa6SMauro Carvalho Chehab                             key=lambda x: (x[1].get("type", ""),
470484e9aa6SMauro Carvalho Chehab                                            x[1].get("what"))):
471484e9aa6SMauro Carvalho Chehab
4722a21d80dSMauro Carvalho Chehab            wtype = v.get("type", "Symbol")
473484e9aa6SMauro Carvalho Chehab            file_ref = v.get("file")
474484e9aa6SMauro Carvalho Chehab            names = v.get("what", [""])
475484e9aa6SMauro Carvalho Chehab
4762a21d80dSMauro Carvalho Chehab            if wtype == "File":
4772a21d80dSMauro Carvalho Chehab                if not show_file:
4782a21d80dSMauro Carvalho Chehab                    continue
4792a21d80dSMauro Carvalho Chehab            else:
4802a21d80dSMauro Carvalho Chehab                if not show_symbols:
4812a21d80dSMauro Carvalho Chehab                    continue
4822a21d80dSMauro Carvalho Chehab
4832a21d80dSMauro Carvalho Chehab            if filter_path:
4842a21d80dSMauro Carvalho Chehab                if v.get("path") != filter_path:
485484e9aa6SMauro Carvalho Chehab                    continue
486484e9aa6SMauro Carvalho Chehab
4879bec7870SMauro Carvalho Chehab            msg = ""
4889bec7870SMauro Carvalho Chehab
489484e9aa6SMauro Carvalho Chehab            if wtype != "File":
490484e9aa6SMauro Carvalho Chehab                cur_part = names[0]
491484e9aa6SMauro Carvalho Chehab                if cur_part.find("/") >= 0:
492484e9aa6SMauro Carvalho Chehab                    match = self.re_what.match(cur_part)
493484e9aa6SMauro Carvalho Chehab                    if match:
494484e9aa6SMauro Carvalho Chehab                        symbol = match.group(1).rstrip("/")
495484e9aa6SMauro Carvalho Chehab                        cur_part = "Symbols under " + symbol
496484e9aa6SMauro Carvalho Chehab
497484e9aa6SMauro Carvalho Chehab                if cur_part and cur_part != part:
498484e9aa6SMauro Carvalho Chehab                    part = cur_part
4996649b421SMauro Carvalho Chehab                    msg += part + "\n"+ "-" * len(part) +"\n\n"
500484e9aa6SMauro Carvalho Chehab
5019bec7870SMauro Carvalho Chehab                msg += f".. _{key}:\n\n"
502484e9aa6SMauro Carvalho Chehab
503484e9aa6SMauro Carvalho Chehab                max_len = 0
504484e9aa6SMauro Carvalho Chehab                for i in range(0, len(names)):           # pylint: disable=C0200
505484e9aa6SMauro Carvalho Chehab                    names[i] = "**" + self.re_escape.sub(r"\\\1", names[i]) + "**"
506484e9aa6SMauro Carvalho Chehab
507484e9aa6SMauro Carvalho Chehab                    max_len = max(max_len, len(names[i]))
508484e9aa6SMauro Carvalho Chehab
5099bec7870SMauro Carvalho Chehab                msg += "+-" + "-" * max_len + "-+\n"
510484e9aa6SMauro Carvalho Chehab                for name in names:
5119bec7870SMauro Carvalho Chehab                    msg += f"| {name}" + " " * (max_len - len(name)) + " |\n"
5129bec7870SMauro Carvalho Chehab                    msg += "+-" + "-" * max_len + "-+\n"
5139bec7870SMauro Carvalho Chehab                msg += "\n"
514484e9aa6SMauro Carvalho Chehab
515484e9aa6SMauro Carvalho Chehab            for ref in file_ref:
516484e9aa6SMauro Carvalho Chehab                if wtype == "File":
5179bec7870SMauro Carvalho Chehab                    msg += f".. _{ref[1]}:\n\n"
518484e9aa6SMauro Carvalho Chehab                else:
519484e9aa6SMauro Carvalho Chehab                    base = os.path.basename(ref[0])
5209bec7870SMauro Carvalho Chehab                    msg += f"Defined on file :ref:`{base} <{ref[1]}>`\n\n"
521484e9aa6SMauro Carvalho Chehab
522484e9aa6SMauro Carvalho Chehab            if wtype == "File":
5236649b421SMauro Carvalho Chehab                msg += names[0] +"\n" + "-" * len(names[0]) +"\n\n"
524484e9aa6SMauro Carvalho Chehab
525484e9aa6SMauro Carvalho Chehab            desc = v.get("description")
526484e9aa6SMauro Carvalho Chehab            if not desc and wtype != "File":
5279bec7870SMauro Carvalho Chehab                msg += f"DESCRIPTION MISSING for {names[0]}\n\n"
528484e9aa6SMauro Carvalho Chehab
529484e9aa6SMauro Carvalho Chehab            if desc:
530484e9aa6SMauro Carvalho Chehab                if output_in_txt:
5319bec7870SMauro Carvalho Chehab                    msg += self.desc_txt(desc)
532484e9aa6SMauro Carvalho Chehab                else:
5339bec7870SMauro Carvalho Chehab                    msg += self.desc_rst(desc)
534484e9aa6SMauro Carvalho Chehab
535484e9aa6SMauro Carvalho Chehab            symbols = v.get("symbols")
536484e9aa6SMauro Carvalho Chehab            if symbols:
5379bec7870SMauro Carvalho Chehab                msg += "Has the following ABI:\n\n"
538484e9aa6SMauro Carvalho Chehab
539484e9aa6SMauro Carvalho Chehab                for w, label in symbols:
540484e9aa6SMauro Carvalho Chehab                    # Escape special chars from content
541484e9aa6SMauro Carvalho Chehab                    content = self.re_escape.sub(r"\\\1", w)
542484e9aa6SMauro Carvalho Chehab
5439bec7870SMauro Carvalho Chehab                    msg += f"- :ref:`{content} <{label}>`\n\n"
544484e9aa6SMauro Carvalho Chehab
545484e9aa6SMauro Carvalho Chehab            users = v.get("users")
546484e9aa6SMauro Carvalho Chehab            if users and users.strip(" \t\n"):
5476649b421SMauro Carvalho Chehab                users = users.strip("\n").replace('\n', '\n\t')
5486649b421SMauro Carvalho Chehab                msg += f"Users:\n\t{users}\n\n"
5499bec7870SMauro Carvalho Chehab
550aea5e52dSMauro Carvalho Chehab            ln = v.get("line_no", 1)
551aea5e52dSMauro Carvalho Chehab
552aea5e52dSMauro Carvalho Chehab            yield (msg, file_ref[0][0], ln)
553484e9aa6SMauro Carvalho Chehab
554484e9aa6SMauro Carvalho Chehab    def check_issues(self):
555*66c3bf97SMauro Carvalho Chehab        """Warn about duplicated ABI entries."""
556484e9aa6SMauro Carvalho Chehab
557484e9aa6SMauro Carvalho Chehab        for what, v in self.what_symbols.items():
558484e9aa6SMauro Carvalho Chehab            files = v.get("file")
559484e9aa6SMauro Carvalho Chehab            if not files:
560484e9aa6SMauro Carvalho Chehab                # Should never happen if the parser works properly
561484e9aa6SMauro Carvalho Chehab                self.log.warning("%s doesn't have a file associated", what)
562484e9aa6SMauro Carvalho Chehab                continue
563484e9aa6SMauro Carvalho Chehab
564484e9aa6SMauro Carvalho Chehab            if len(files) == 1:
565484e9aa6SMauro Carvalho Chehab                continue
566484e9aa6SMauro Carvalho Chehab
567484e9aa6SMauro Carvalho Chehab            f = []
568484e9aa6SMauro Carvalho Chehab            for fname, lines in sorted(files.items()):
569484e9aa6SMauro Carvalho Chehab                if not lines:
570484e9aa6SMauro Carvalho Chehab                    f.append(f"{fname}")
571484e9aa6SMauro Carvalho Chehab                elif len(lines) == 1:
572484e9aa6SMauro Carvalho Chehab                    f.append(f"{fname}:{lines[0]}")
573484e9aa6SMauro Carvalho Chehab                else:
5746649b421SMauro Carvalho Chehab                    m = fname + "lines "
5756649b421SMauro Carvalho Chehab                    m += ", ".join(str(x) for x in lines)
5766649b421SMauro Carvalho Chehab                    f.append(m)
577484e9aa6SMauro Carvalho Chehab
578484e9aa6SMauro Carvalho Chehab            self.log.warning("%s is defined %d times: %s", what, len(f), "; ".join(f))
5796b48bea1SMauro Carvalho Chehab
5806b48bea1SMauro Carvalho Chehab    def search_symbols(self, expr):
581*66c3bf97SMauro Carvalho Chehab        """ Searches for ABI symbols."""
5826b48bea1SMauro Carvalho Chehab
5836b48bea1SMauro Carvalho Chehab        regex = re.compile(expr, re.I)
5846b48bea1SMauro Carvalho Chehab
5856b48bea1SMauro Carvalho Chehab        found_keys = 0
5866b48bea1SMauro Carvalho Chehab        for t in sorted(self.data.items(), key=lambda x: [0]):
5876b48bea1SMauro Carvalho Chehab            v = t[1]
5886b48bea1SMauro Carvalho Chehab
5896b48bea1SMauro Carvalho Chehab            wtype = v.get("type", "")
5906b48bea1SMauro Carvalho Chehab            if wtype == "File":
5916b48bea1SMauro Carvalho Chehab                continue
5926b48bea1SMauro Carvalho Chehab
5936b48bea1SMauro Carvalho Chehab            for what in v.get("what", [""]):
5946b48bea1SMauro Carvalho Chehab                if regex.search(what):
5956b48bea1SMauro Carvalho Chehab                    found_keys += 1
5966b48bea1SMauro Carvalho Chehab
5976b48bea1SMauro Carvalho Chehab                    kernelversion = v.get("kernelversion", "").strip(" \t\n")
5986b48bea1SMauro Carvalho Chehab                    date = v.get("date", "").strip(" \t\n")
5996b48bea1SMauro Carvalho Chehab                    contact = v.get("contact", "").strip(" \t\n")
6006b48bea1SMauro Carvalho Chehab                    users = v.get("users", "").strip(" \t\n")
6016b48bea1SMauro Carvalho Chehab                    desc = v.get("description", "").strip(" \t\n")
6026b48bea1SMauro Carvalho Chehab
6036b48bea1SMauro Carvalho Chehab                    files = []
6046b48bea1SMauro Carvalho Chehab                    for f in v.get("file", ()):
6056b48bea1SMauro Carvalho Chehab                        files.append(f[0])
6066b48bea1SMauro Carvalho Chehab
6076b48bea1SMauro Carvalho Chehab                    what = str(found_keys) + ". " + what
6086b48bea1SMauro Carvalho Chehab                    title_tag = "-" * len(what)
6096b48bea1SMauro Carvalho Chehab
6106b48bea1SMauro Carvalho Chehab                    print(f"\n{what}\n{title_tag}\n")
6116b48bea1SMauro Carvalho Chehab
6126b48bea1SMauro Carvalho Chehab                    if kernelversion:
6136b48bea1SMauro Carvalho Chehab                        print(f"Kernel version:\t\t{kernelversion}")
6146b48bea1SMauro Carvalho Chehab
6156b48bea1SMauro Carvalho Chehab                    if date:
6166b48bea1SMauro Carvalho Chehab                        print(f"Date:\t\t\t{date}")
6176b48bea1SMauro Carvalho Chehab
6186b48bea1SMauro Carvalho Chehab                    if contact:
6196b48bea1SMauro Carvalho Chehab                        print(f"Contact:\t\t{contact}")
6206b48bea1SMauro Carvalho Chehab
6216b48bea1SMauro Carvalho Chehab                    if users:
6226b48bea1SMauro Carvalho Chehab                        print(f"Users:\t\t\t{users}")
6236b48bea1SMauro Carvalho Chehab
6246649b421SMauro Carvalho Chehab                    print("Defined on file(s):\t" + ", ".join(files))
6256b48bea1SMauro Carvalho Chehab
6266b48bea1SMauro Carvalho Chehab                    if desc:
6276649b421SMauro Carvalho Chehab                        desc = desc.strip("\n")
6286649b421SMauro Carvalho Chehab                        print(f"\n{desc}\n")
6296b48bea1SMauro Carvalho Chehab
6306b48bea1SMauro Carvalho Chehab        if not found_keys:
6316b48bea1SMauro Carvalho Chehab            print(f"Regular expression /{expr}/ not found.")
632