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