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 <[email protected]>. 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 20484e9aa6SMauro Carvalho Chehabfrom helpers import AbiDebug, ABI_DIR 21484e9aa6SMauro Carvalho Chehab 22484e9aa6SMauro Carvalho Chehab 23484e9aa6SMauro Carvalho Chehabclass AbiParser: 24484e9aa6SMauro Carvalho Chehab """Main class to parse ABI files""" 25484e9aa6SMauro Carvalho Chehab 26484e9aa6SMauro Carvalho Chehab TAGS = r"(what|where|date|kernelversion|contact|description|users)" 27484e9aa6SMauro Carvalho Chehab XREF = r"(?:^|\s|\()(\/(?:sys|config|proc|dev|kvd)\/[^,.:;\)\s]+)(?:[,.:;\)\s]|\Z)" 28484e9aa6SMauro Carvalho Chehab 29484e9aa6SMauro Carvalho Chehab def __init__(self, directory, logger=None, 30484e9aa6SMauro Carvalho Chehab enable_lineno=False, show_warnings=True, debug=0): 31484e9aa6SMauro Carvalho Chehab """Stores arguments for the class and initialize class vars""" 32484e9aa6SMauro Carvalho Chehab 33484e9aa6SMauro Carvalho Chehab self.directory = directory 34484e9aa6SMauro Carvalho Chehab self.enable_lineno = enable_lineno 35484e9aa6SMauro Carvalho Chehab self.show_warnings = show_warnings 36484e9aa6SMauro Carvalho Chehab self.debug = debug 37484e9aa6SMauro Carvalho Chehab 38484e9aa6SMauro Carvalho Chehab if not logger: 39484e9aa6SMauro Carvalho Chehab self.log = logging.getLogger("get_abi") 40484e9aa6SMauro Carvalho Chehab else: 41484e9aa6SMauro Carvalho Chehab self.log = logger 42484e9aa6SMauro Carvalho Chehab 43484e9aa6SMauro Carvalho Chehab self.data = {} 44484e9aa6SMauro Carvalho Chehab self.what_symbols = {} 45484e9aa6SMauro Carvalho Chehab self.file_refs = {} 46484e9aa6SMauro Carvalho Chehab self.what_refs = {} 47484e9aa6SMauro Carvalho Chehab 48c67c3fbdSMauro Carvalho Chehab # Ignore files that contain such suffixes 49c67c3fbdSMauro Carvalho Chehab self.ignore_suffixes = (".rej", ".org", ".orig", ".bak", "~") 50c67c3fbdSMauro Carvalho Chehab 51484e9aa6SMauro Carvalho Chehab # Regular expressions used on parser 52c67c3fbdSMauro Carvalho Chehab self.re_abi_dir = re.compile(r"(.*)" + ABI_DIR) 53484e9aa6SMauro Carvalho Chehab self.re_tag = re.compile(r"(\S+)(:\s*)(.*)", re.I) 54484e9aa6SMauro Carvalho Chehab self.re_valid = re.compile(self.TAGS) 55484e9aa6SMauro Carvalho Chehab self.re_start_spc = re.compile(r"(\s*)(\S.*)") 56484e9aa6SMauro Carvalho Chehab self.re_whitespace = re.compile(r"^\s+") 57484e9aa6SMauro Carvalho Chehab 58484e9aa6SMauro Carvalho Chehab # Regular used on print 59484e9aa6SMauro Carvalho Chehab self.re_what = re.compile(r"(\/?(?:[\w\-]+\/?){1,2})") 60484e9aa6SMauro Carvalho Chehab self.re_escape = re.compile(r"([\.\x01-\x08\x0e-\x1f\x21-\x2f\x3a-\x40\x7b-\xff])") 61484e9aa6SMauro Carvalho Chehab self.re_unprintable = re.compile(r"([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xff]+)") 62484e9aa6SMauro Carvalho Chehab self.re_title_mark = re.compile(r"\n[\-\*\=\^\~]+\n") 63484e9aa6SMauro Carvalho Chehab self.re_doc = re.compile(r"Documentation/(?!devicetree)(\S+)\.rst") 64484e9aa6SMauro Carvalho Chehab self.re_abi = re.compile(r"(Documentation/ABI/)([\w\/\-]+)") 65484e9aa6SMauro Carvalho Chehab self.re_xref_node = re.compile(self.XREF) 66484e9aa6SMauro Carvalho Chehab 67484e9aa6SMauro Carvalho Chehab def warn(self, fdata, msg, extra=None): 68484e9aa6SMauro Carvalho Chehab """Displays a parse error if warning is enabled""" 69484e9aa6SMauro Carvalho Chehab 70484e9aa6SMauro Carvalho Chehab if not self.show_warnings: 71484e9aa6SMauro Carvalho Chehab return 72484e9aa6SMauro Carvalho Chehab 73484e9aa6SMauro Carvalho Chehab msg = f"{fdata.fname}:{fdata.ln}: {msg}" 74484e9aa6SMauro Carvalho Chehab if extra: 75484e9aa6SMauro Carvalho Chehab msg += "\n\t\t" + extra 76484e9aa6SMauro Carvalho Chehab 77484e9aa6SMauro Carvalho Chehab self.log.warning(msg) 78484e9aa6SMauro Carvalho Chehab 79484e9aa6SMauro Carvalho Chehab def add_symbol(self, what, fname, ln=None, xref=None): 80484e9aa6SMauro Carvalho Chehab """Create a reference table describing where each 'what' is located""" 81484e9aa6SMauro Carvalho Chehab 82484e9aa6SMauro Carvalho Chehab if what not in self.what_symbols: 83484e9aa6SMauro Carvalho Chehab self.what_symbols[what] = {"file": {}} 84484e9aa6SMauro Carvalho Chehab 85484e9aa6SMauro Carvalho Chehab if fname not in self.what_symbols[what]["file"]: 86484e9aa6SMauro Carvalho Chehab self.what_symbols[what]["file"][fname] = [] 87484e9aa6SMauro Carvalho Chehab 88484e9aa6SMauro Carvalho Chehab if ln and ln not in self.what_symbols[what]["file"][fname]: 89484e9aa6SMauro Carvalho Chehab self.what_symbols[what]["file"][fname].append(ln) 90484e9aa6SMauro Carvalho Chehab 91484e9aa6SMauro Carvalho Chehab if xref: 92484e9aa6SMauro Carvalho Chehab self.what_symbols[what]["xref"] = xref 93484e9aa6SMauro Carvalho Chehab 94484e9aa6SMauro Carvalho Chehab def _parse_line(self, fdata, line): 95484e9aa6SMauro Carvalho Chehab """Parse a single line of an ABI file""" 96484e9aa6SMauro Carvalho Chehab 97484e9aa6SMauro Carvalho Chehab new_what = False 98484e9aa6SMauro Carvalho Chehab new_tag = False 99484e9aa6SMauro Carvalho Chehab content = None 100484e9aa6SMauro Carvalho Chehab 101484e9aa6SMauro Carvalho Chehab match = self.re_tag.match(line) 102484e9aa6SMauro Carvalho Chehab if match: 103484e9aa6SMauro Carvalho Chehab new = match.group(1).lower() 104484e9aa6SMauro Carvalho Chehab sep = match.group(2) 105484e9aa6SMauro Carvalho Chehab content = match.group(3) 106484e9aa6SMauro Carvalho Chehab 107484e9aa6SMauro Carvalho Chehab match = self.re_valid.search(new) 108484e9aa6SMauro Carvalho Chehab if match: 109484e9aa6SMauro Carvalho Chehab new_tag = match.group(1) 110484e9aa6SMauro Carvalho Chehab else: 111484e9aa6SMauro Carvalho Chehab if fdata.tag == "description": 112484e9aa6SMauro Carvalho Chehab # New "tag" is actually part of description. 113484e9aa6SMauro Carvalho Chehab # Don't consider it a tag 114484e9aa6SMauro Carvalho Chehab new_tag = False 115484e9aa6SMauro Carvalho Chehab elif fdata.tag != "": 116484e9aa6SMauro Carvalho Chehab self.warn(fdata, f"tag '{fdata.tag}' is invalid", line) 117484e9aa6SMauro Carvalho Chehab 118484e9aa6SMauro Carvalho Chehab if new_tag: 119484e9aa6SMauro Carvalho Chehab # "where" is Invalid, but was a common mistake. Warn if found 120484e9aa6SMauro Carvalho Chehab if new_tag == "where": 121484e9aa6SMauro Carvalho Chehab self.warn(fdata, "tag 'Where' is invalid. Should be 'What:' instead") 122484e9aa6SMauro Carvalho Chehab new_tag = "what" 123484e9aa6SMauro Carvalho Chehab 124484e9aa6SMauro Carvalho Chehab if new_tag == "what": 125484e9aa6SMauro Carvalho Chehab fdata.space = None 126484e9aa6SMauro Carvalho Chehab 127484e9aa6SMauro Carvalho Chehab if content not in self.what_symbols: 128484e9aa6SMauro Carvalho Chehab self.add_symbol(what=content, fname=fdata.fname, ln=fdata.ln) 129484e9aa6SMauro Carvalho Chehab 130484e9aa6SMauro Carvalho Chehab if fdata.tag == "what": 131484e9aa6SMauro Carvalho Chehab fdata.what.append(content.strip("\n")) 132484e9aa6SMauro Carvalho Chehab else: 133484e9aa6SMauro Carvalho Chehab if fdata.key: 134484e9aa6SMauro Carvalho Chehab if "description" not in self.data.get(fdata.key, {}): 135484e9aa6SMauro Carvalho Chehab self.warn(fdata, f"{fdata.key} doesn't have a description") 136484e9aa6SMauro Carvalho Chehab 137484e9aa6SMauro Carvalho Chehab for w in fdata.what: 138484e9aa6SMauro Carvalho Chehab self.add_symbol(what=w, fname=fdata.fname, 139484e9aa6SMauro Carvalho Chehab ln=fdata.what_ln, xref=fdata.key) 140484e9aa6SMauro Carvalho Chehab 141484e9aa6SMauro Carvalho Chehab fdata.label = content 142484e9aa6SMauro Carvalho Chehab new_what = True 143484e9aa6SMauro Carvalho Chehab 144484e9aa6SMauro Carvalho Chehab key = "abi_" + content.lower() 145484e9aa6SMauro Carvalho Chehab fdata.key = self.re_unprintable.sub("_", key).strip("_") 146484e9aa6SMauro Carvalho Chehab 147484e9aa6SMauro Carvalho Chehab # Avoid duplicated keys but using a defined seed, to make 148484e9aa6SMauro Carvalho Chehab # the namespace identical if there aren't changes at the 149484e9aa6SMauro Carvalho Chehab # ABI symbols 150484e9aa6SMauro Carvalho Chehab seed(42) 151484e9aa6SMauro Carvalho Chehab 152484e9aa6SMauro Carvalho Chehab while fdata.key in self.data: 153484e9aa6SMauro Carvalho Chehab char = randrange(0, 51) + ord("A") 154484e9aa6SMauro Carvalho Chehab if char > ord("Z"): 155484e9aa6SMauro Carvalho Chehab char += ord("a") - ord("Z") - 1 156484e9aa6SMauro Carvalho Chehab 157484e9aa6SMauro Carvalho Chehab fdata.key += chr(char) 158484e9aa6SMauro Carvalho Chehab 159484e9aa6SMauro Carvalho Chehab if fdata.key and fdata.key not in self.data: 160484e9aa6SMauro Carvalho Chehab self.data[fdata.key] = { 161484e9aa6SMauro Carvalho Chehab "what": [content], 162484e9aa6SMauro Carvalho Chehab "file": [fdata.file_ref], 1632a21d80dSMauro Carvalho Chehab "path": fdata.ftype, 164484e9aa6SMauro Carvalho Chehab "line_no": fdata.ln, 165484e9aa6SMauro Carvalho Chehab } 166484e9aa6SMauro Carvalho Chehab 167484e9aa6SMauro Carvalho Chehab fdata.what = self.data[fdata.key]["what"] 168484e9aa6SMauro Carvalho Chehab 169484e9aa6SMauro Carvalho Chehab self.what_refs[content] = fdata.key 170484e9aa6SMauro Carvalho Chehab fdata.tag = new_tag 171484e9aa6SMauro Carvalho Chehab fdata.what_ln = fdata.ln 172484e9aa6SMauro Carvalho Chehab 173484e9aa6SMauro Carvalho Chehab if fdata.nametag["what"]: 174484e9aa6SMauro Carvalho Chehab t = (content, fdata.key) 175484e9aa6SMauro Carvalho Chehab if t not in fdata.nametag["symbols"]: 176484e9aa6SMauro Carvalho Chehab fdata.nametag["symbols"].append(t) 177484e9aa6SMauro Carvalho Chehab 178484e9aa6SMauro Carvalho Chehab return 179484e9aa6SMauro Carvalho Chehab 180484e9aa6SMauro Carvalho Chehab if fdata.tag and new_tag: 181484e9aa6SMauro Carvalho Chehab fdata.tag = new_tag 182484e9aa6SMauro Carvalho Chehab 183484e9aa6SMauro Carvalho Chehab if new_what: 184484e9aa6SMauro Carvalho Chehab fdata.label = "" 185484e9aa6SMauro Carvalho Chehab 186484e9aa6SMauro Carvalho Chehab if "description" in self.data[fdata.key]: 187484e9aa6SMauro Carvalho Chehab self.data[fdata.key]["description"] += "\n\n" 188484e9aa6SMauro Carvalho Chehab 189484e9aa6SMauro Carvalho Chehab if fdata.file_ref not in self.data[fdata.key]["file"]: 190484e9aa6SMauro Carvalho Chehab self.data[fdata.key]["file"].append(fdata.file_ref) 191484e9aa6SMauro Carvalho Chehab 192484e9aa6SMauro Carvalho Chehab if self.debug == AbiDebug.WHAT_PARSING: 193484e9aa6SMauro Carvalho Chehab self.log.debug("what: %s", fdata.what) 194484e9aa6SMauro Carvalho Chehab 195484e9aa6SMauro Carvalho Chehab if not fdata.what: 196484e9aa6SMauro Carvalho Chehab self.warn(fdata, "'What:' should come first:", line) 197484e9aa6SMauro Carvalho Chehab return 198484e9aa6SMauro Carvalho Chehab 199484e9aa6SMauro Carvalho Chehab if new_tag == "description": 200484e9aa6SMauro Carvalho Chehab fdata.space = None 201484e9aa6SMauro Carvalho Chehab 202484e9aa6SMauro Carvalho Chehab if content: 203484e9aa6SMauro Carvalho Chehab sep = sep.replace(":", " ") 204484e9aa6SMauro Carvalho Chehab 205484e9aa6SMauro Carvalho Chehab c = " " * len(new_tag) + sep + content 206484e9aa6SMauro Carvalho Chehab c = c.expandtabs() 207484e9aa6SMauro Carvalho Chehab 208484e9aa6SMauro Carvalho Chehab match = self.re_start_spc.match(c) 209484e9aa6SMauro Carvalho Chehab if match: 210484e9aa6SMauro Carvalho Chehab # Preserve initial spaces for the first line 211484e9aa6SMauro Carvalho Chehab fdata.space = match.group(1) 212484e9aa6SMauro Carvalho Chehab content = match.group(2) + "\n" 213484e9aa6SMauro Carvalho Chehab 214484e9aa6SMauro Carvalho Chehab self.data[fdata.key][fdata.tag] = content 215484e9aa6SMauro Carvalho Chehab 216484e9aa6SMauro Carvalho Chehab return 217484e9aa6SMauro Carvalho Chehab 218484e9aa6SMauro Carvalho Chehab # Store any contents before tags at the database 219484e9aa6SMauro Carvalho Chehab if not fdata.tag and "what" in fdata.nametag: 220484e9aa6SMauro Carvalho Chehab fdata.nametag["description"] += line 221484e9aa6SMauro Carvalho Chehab return 222484e9aa6SMauro Carvalho Chehab 223484e9aa6SMauro Carvalho Chehab if fdata.tag == "description": 224484e9aa6SMauro Carvalho Chehab content = line.expandtabs() 225484e9aa6SMauro Carvalho Chehab 226484e9aa6SMauro Carvalho Chehab if self.re_whitespace.sub("", content) == "": 227484e9aa6SMauro Carvalho Chehab self.data[fdata.key][fdata.tag] += "\n" 228484e9aa6SMauro Carvalho Chehab return 229484e9aa6SMauro Carvalho Chehab 230484e9aa6SMauro Carvalho Chehab if fdata.space is None: 231484e9aa6SMauro Carvalho Chehab match = self.re_start_spc.match(content) 232484e9aa6SMauro Carvalho Chehab if match: 233484e9aa6SMauro Carvalho Chehab # Preserve initial spaces for the first line 234484e9aa6SMauro Carvalho Chehab fdata.space = match.group(1) 235484e9aa6SMauro Carvalho Chehab 236484e9aa6SMauro Carvalho Chehab content = match.group(2) + "\n" 237484e9aa6SMauro Carvalho Chehab else: 238484e9aa6SMauro Carvalho Chehab if content.startswith(fdata.space): 239484e9aa6SMauro Carvalho Chehab content = content[len(fdata.space):] 240484e9aa6SMauro Carvalho Chehab 241484e9aa6SMauro Carvalho Chehab else: 242484e9aa6SMauro Carvalho Chehab fdata.space = "" 243484e9aa6SMauro Carvalho Chehab 244484e9aa6SMauro Carvalho Chehab if fdata.tag == "what": 245484e9aa6SMauro Carvalho Chehab w = content.strip("\n") 246484e9aa6SMauro Carvalho Chehab if w: 247484e9aa6SMauro Carvalho Chehab self.data[fdata.key][fdata.tag].append(w) 248484e9aa6SMauro Carvalho Chehab else: 249484e9aa6SMauro Carvalho Chehab self.data[fdata.key][fdata.tag] += content 250484e9aa6SMauro Carvalho Chehab return 251484e9aa6SMauro Carvalho Chehab 252484e9aa6SMauro Carvalho Chehab content = line.strip() 253484e9aa6SMauro Carvalho Chehab if fdata.tag: 254484e9aa6SMauro Carvalho Chehab if fdata.tag == "what": 255484e9aa6SMauro Carvalho Chehab w = content.strip("\n") 256484e9aa6SMauro Carvalho Chehab if w: 257484e9aa6SMauro Carvalho Chehab self.data[fdata.key][fdata.tag].append(w) 258484e9aa6SMauro Carvalho Chehab else: 259484e9aa6SMauro Carvalho Chehab self.data[fdata.key][fdata.tag] += "\n" + content.rstrip("\n") 260484e9aa6SMauro Carvalho Chehab return 261484e9aa6SMauro Carvalho Chehab 262484e9aa6SMauro Carvalho Chehab # Everything else is error 263484e9aa6SMauro Carvalho Chehab if content: 264484e9aa6SMauro Carvalho Chehab self.warn(fdata, "Unexpected content", line) 265484e9aa6SMauro Carvalho Chehab 26698a4324aSMauro Carvalho Chehab def parse_readme(self, nametag, fname): 26798a4324aSMauro Carvalho Chehab """Parse ABI README file""" 26898a4324aSMauro Carvalho Chehab 269*de61d651SMauro Carvalho Chehab nametag["what"] = ["Introduction"] 2705d7871d7SMauro Carvalho Chehab nametag["path"] = "README" 27198a4324aSMauro Carvalho Chehab with open(fname, "r", encoding="utf8", errors="backslashreplace") as fp: 27298a4324aSMauro Carvalho Chehab for line in fp: 2735d7871d7SMauro Carvalho Chehab match = self.re_tag.match(line) 2745d7871d7SMauro Carvalho Chehab if match: 2755d7871d7SMauro Carvalho Chehab new = match.group(1).lower() 27698a4324aSMauro Carvalho Chehab 2775d7871d7SMauro Carvalho Chehab match = self.re_valid.search(new) 2785d7871d7SMauro Carvalho Chehab if match: 2795d7871d7SMauro Carvalho Chehab nametag["description"] += "\n:" + line 2805d7871d7SMauro Carvalho Chehab continue 2815d7871d7SMauro Carvalho Chehab 2825d7871d7SMauro Carvalho Chehab nametag["description"] += line 28398a4324aSMauro Carvalho Chehab 284484e9aa6SMauro Carvalho Chehab def parse_file(self, fname, path, basename): 285484e9aa6SMauro Carvalho Chehab """Parse a single file""" 286484e9aa6SMauro Carvalho Chehab 287484e9aa6SMauro Carvalho Chehab ref = f"abi_file_{path}_{basename}" 288484e9aa6SMauro Carvalho Chehab ref = self.re_unprintable.sub("_", ref).strip("_") 289484e9aa6SMauro Carvalho Chehab 290484e9aa6SMauro Carvalho Chehab # Store per-file state into a namespace variable. This will be used 291484e9aa6SMauro Carvalho Chehab # by the per-line parser state machine and by the warning function. 292484e9aa6SMauro Carvalho Chehab fdata = Namespace 293484e9aa6SMauro Carvalho Chehab 294484e9aa6SMauro Carvalho Chehab fdata.fname = fname 295484e9aa6SMauro Carvalho Chehab fdata.name = basename 296484e9aa6SMauro Carvalho Chehab 297484e9aa6SMauro Carvalho Chehab pos = fname.find(ABI_DIR) 298484e9aa6SMauro Carvalho Chehab if pos > 0: 299484e9aa6SMauro Carvalho Chehab f = fname[pos:] 300484e9aa6SMauro Carvalho Chehab else: 301484e9aa6SMauro Carvalho Chehab f = fname 302484e9aa6SMauro Carvalho Chehab 303484e9aa6SMauro Carvalho Chehab fdata.file_ref = (f, ref) 304484e9aa6SMauro Carvalho Chehab self.file_refs[f] = ref 305484e9aa6SMauro Carvalho Chehab 306484e9aa6SMauro Carvalho Chehab fdata.ln = 0 307484e9aa6SMauro Carvalho Chehab fdata.what_ln = 0 308484e9aa6SMauro Carvalho Chehab fdata.tag = "" 309484e9aa6SMauro Carvalho Chehab fdata.label = "" 310484e9aa6SMauro Carvalho Chehab fdata.what = [] 311484e9aa6SMauro Carvalho Chehab fdata.key = None 312484e9aa6SMauro Carvalho Chehab fdata.xrefs = None 313484e9aa6SMauro Carvalho Chehab fdata.space = None 314484e9aa6SMauro Carvalho Chehab fdata.ftype = path.split("/")[0] 315484e9aa6SMauro Carvalho Chehab 316484e9aa6SMauro Carvalho Chehab fdata.nametag = {} 317dc525a76SMauro Carvalho Chehab fdata.nametag["what"] = [f"ABI file {path}/{basename}"] 318484e9aa6SMauro Carvalho Chehab fdata.nametag["type"] = "File" 3192a21d80dSMauro Carvalho Chehab fdata.nametag["path"] = fdata.ftype 320484e9aa6SMauro Carvalho Chehab fdata.nametag["file"] = [fdata.file_ref] 321484e9aa6SMauro Carvalho Chehab fdata.nametag["line_no"] = 1 322484e9aa6SMauro Carvalho Chehab fdata.nametag["description"] = "" 323484e9aa6SMauro Carvalho Chehab fdata.nametag["symbols"] = [] 324484e9aa6SMauro Carvalho Chehab 325484e9aa6SMauro Carvalho Chehab self.data[ref] = fdata.nametag 326484e9aa6SMauro Carvalho Chehab 327484e9aa6SMauro Carvalho Chehab if self.debug & AbiDebug.WHAT_OPEN: 328484e9aa6SMauro Carvalho Chehab self.log.debug("Opening file %s", fname) 329484e9aa6SMauro Carvalho Chehab 33098a4324aSMauro Carvalho Chehab if basename == "README": 33198a4324aSMauro Carvalho Chehab self.parse_readme(fdata.nametag, fname) 33298a4324aSMauro Carvalho Chehab return 33398a4324aSMauro Carvalho Chehab 334484e9aa6SMauro Carvalho Chehab with open(fname, "r", encoding="utf8", errors="backslashreplace") as fp: 335484e9aa6SMauro Carvalho Chehab for line in fp: 336484e9aa6SMauro Carvalho Chehab fdata.ln += 1 337484e9aa6SMauro Carvalho Chehab 338484e9aa6SMauro Carvalho Chehab self._parse_line(fdata, line) 339484e9aa6SMauro Carvalho Chehab 340484e9aa6SMauro Carvalho Chehab if "description" in fdata.nametag: 341484e9aa6SMauro Carvalho Chehab fdata.nametag["description"] = fdata.nametag["description"].lstrip("\n") 342484e9aa6SMauro Carvalho Chehab 343484e9aa6SMauro Carvalho Chehab if fdata.key: 344484e9aa6SMauro Carvalho Chehab if "description" not in self.data.get(fdata.key, {}): 345484e9aa6SMauro Carvalho Chehab self.warn(fdata, f"{fdata.key} doesn't have a description") 346484e9aa6SMauro Carvalho Chehab 347484e9aa6SMauro Carvalho Chehab for w in fdata.what: 348484e9aa6SMauro Carvalho Chehab self.add_symbol(what=w, fname=fname, xref=fdata.key) 349484e9aa6SMauro Carvalho Chehab 350c67c3fbdSMauro Carvalho Chehab def _parse_abi(self, root=None): 351c67c3fbdSMauro Carvalho Chehab """Internal function to parse documentation ABI recursively""" 352484e9aa6SMauro Carvalho Chehab 353c67c3fbdSMauro Carvalho Chehab if not root: 354c67c3fbdSMauro Carvalho Chehab root = self.directory 355484e9aa6SMauro Carvalho Chehab 356c67c3fbdSMauro Carvalho Chehab with os.scandir(root) as obj: 357c67c3fbdSMauro Carvalho Chehab for entry in obj: 358c67c3fbdSMauro Carvalho Chehab name = os.path.join(root, entry.name) 359c67c3fbdSMauro Carvalho Chehab 360c67c3fbdSMauro Carvalho Chehab if entry.is_dir(): 3619bec7870SMauro Carvalho Chehab self._parse_abi(name) 362484e9aa6SMauro Carvalho Chehab continue 363484e9aa6SMauro Carvalho Chehab 364c67c3fbdSMauro Carvalho Chehab if not entry.is_file(): 365c67c3fbdSMauro Carvalho Chehab continue 366c67c3fbdSMauro Carvalho Chehab 367c67c3fbdSMauro Carvalho Chehab basename = os.path.basename(name) 368484e9aa6SMauro Carvalho Chehab 369c67c3fbdSMauro Carvalho Chehab if basename.startswith("."): 370484e9aa6SMauro Carvalho Chehab continue 371484e9aa6SMauro Carvalho Chehab 372c67c3fbdSMauro Carvalho Chehab if basename.endswith(self.ignore_suffixes): 373c67c3fbdSMauro Carvalho Chehab continue 374484e9aa6SMauro Carvalho Chehab 375c67c3fbdSMauro Carvalho Chehab path = self.re_abi_dir.sub("", os.path.dirname(name)) 376c67c3fbdSMauro Carvalho Chehab 377c67c3fbdSMauro Carvalho Chehab self.parse_file(name, path, basename) 378c67c3fbdSMauro Carvalho Chehab 379c67c3fbdSMauro Carvalho Chehab def parse_abi(self, root=None): 380c67c3fbdSMauro Carvalho Chehab """Parse documentation ABI""" 381c67c3fbdSMauro Carvalho Chehab 382c67c3fbdSMauro Carvalho Chehab self._parse_abi(root) 383484e9aa6SMauro Carvalho Chehab 384484e9aa6SMauro Carvalho Chehab if self.debug & AbiDebug.DUMP_ABI_STRUCTS: 385484e9aa6SMauro Carvalho Chehab self.log.debug(pformat(self.data)) 386484e9aa6SMauro Carvalho Chehab 3879bec7870SMauro Carvalho Chehab def desc_txt(self, desc): 388484e9aa6SMauro Carvalho Chehab """Print description as found inside ABI files""" 389484e9aa6SMauro Carvalho Chehab 390484e9aa6SMauro Carvalho Chehab desc = desc.strip(" \t\n") 391484e9aa6SMauro Carvalho Chehab 3929bec7870SMauro Carvalho Chehab return desc + "\n\n" 393484e9aa6SMauro Carvalho Chehab 394c9408169SMauro Carvalho Chehab def xref(self, fname): 395c9408169SMauro Carvalho Chehab """ 396c9408169SMauro Carvalho Chehab Converts a Documentation/ABI + basename into a ReST cross-reference 397c9408169SMauro Carvalho Chehab """ 398c9408169SMauro Carvalho Chehab 399c9408169SMauro Carvalho Chehab xref = self.file_refs.get(fname) 400c9408169SMauro Carvalho Chehab if not xref: 401c9408169SMauro Carvalho Chehab return None 402c9408169SMauro Carvalho Chehab else: 403c9408169SMauro Carvalho Chehab return xref 404c9408169SMauro Carvalho Chehab 4059bec7870SMauro Carvalho Chehab def desc_rst(self, desc): 406484e9aa6SMauro Carvalho Chehab """Enrich ReST output by creating cross-references""" 407484e9aa6SMauro Carvalho Chehab 408484e9aa6SMauro Carvalho Chehab # Remove title markups from the description 409484e9aa6SMauro Carvalho Chehab # Having titles inside ABI files will only work if extra 410484e9aa6SMauro Carvalho Chehab # care would be taken in order to strictly follow the same 411484e9aa6SMauro Carvalho Chehab # level order for each markup. 412484e9aa6SMauro Carvalho Chehab desc = self.re_title_mark.sub("\n\n", "\n" + desc) 413484e9aa6SMauro Carvalho Chehab desc = desc.rstrip(" \t\n").lstrip("\n") 414484e9aa6SMauro Carvalho Chehab 415484e9aa6SMauro Carvalho Chehab # Python's regex performance for non-compiled expressions is a lot 416484e9aa6SMauro Carvalho Chehab # than Perl, as Perl automatically caches them at their 417484e9aa6SMauro Carvalho Chehab # first usage. Here, we'll need to do the same, as otherwise the 418484e9aa6SMauro Carvalho Chehab # performance penalty is be high 419484e9aa6SMauro Carvalho Chehab 420484e9aa6SMauro Carvalho Chehab new_desc = "" 421484e9aa6SMauro Carvalho Chehab for d in desc.split("\n"): 422484e9aa6SMauro Carvalho Chehab if d == "": 423484e9aa6SMauro Carvalho Chehab new_desc += "\n" 424484e9aa6SMauro Carvalho Chehab continue 425484e9aa6SMauro Carvalho Chehab 426484e9aa6SMauro Carvalho Chehab # Use cross-references for doc files where needed 427484e9aa6SMauro Carvalho Chehab d = self.re_doc.sub(r":doc:`/\1`", d) 428484e9aa6SMauro Carvalho Chehab 429484e9aa6SMauro Carvalho Chehab # Use cross-references for ABI generated docs where needed 430484e9aa6SMauro Carvalho Chehab matches = self.re_abi.findall(d) 431484e9aa6SMauro Carvalho Chehab for m in matches: 432484e9aa6SMauro Carvalho Chehab abi = m[0] + m[1] 433484e9aa6SMauro Carvalho Chehab 434484e9aa6SMauro Carvalho Chehab xref = self.file_refs.get(abi) 435484e9aa6SMauro Carvalho Chehab if not xref: 436484e9aa6SMauro Carvalho Chehab # This may happen if ABI is on a separate directory, 437484e9aa6SMauro Carvalho Chehab # like parsing ABI testing and symbol is at stable. 438484e9aa6SMauro Carvalho Chehab # The proper solution is to move this part of the code 439484e9aa6SMauro Carvalho Chehab # for it to be inside sphinx/kernel_abi.py 440484e9aa6SMauro Carvalho Chehab self.log.info("Didn't find ABI reference for '%s'", abi) 441484e9aa6SMauro Carvalho Chehab else: 442484e9aa6SMauro Carvalho Chehab new = self.re_escape.sub(r"\\\1", m[1]) 443484e9aa6SMauro Carvalho Chehab d = re.sub(fr"\b{abi}\b", f":ref:`{new} <{xref}>`", d) 444484e9aa6SMauro Carvalho Chehab 445484e9aa6SMauro Carvalho Chehab # Seek for cross reference symbols like /sys/... 446484e9aa6SMauro Carvalho Chehab # Need to be careful to avoid doing it on a code block 447484e9aa6SMauro Carvalho Chehab if d[0] not in [" ", "\t"]: 448484e9aa6SMauro Carvalho Chehab matches = self.re_xref_node.findall(d) 449484e9aa6SMauro Carvalho Chehab for m in matches: 450484e9aa6SMauro Carvalho Chehab # Finding ABI here is more complex due to wildcards 451484e9aa6SMauro Carvalho Chehab xref = self.what_refs.get(m) 452484e9aa6SMauro Carvalho Chehab if xref: 453484e9aa6SMauro Carvalho Chehab new = self.re_escape.sub(r"\\\1", m) 454484e9aa6SMauro Carvalho Chehab d = re.sub(fr"\b{m}\b", f":ref:`{new} <{xref}>`", d) 455484e9aa6SMauro Carvalho Chehab 456484e9aa6SMauro Carvalho Chehab new_desc += d + "\n" 457484e9aa6SMauro Carvalho Chehab 4589bec7870SMauro Carvalho Chehab return new_desc + "\n\n" 459484e9aa6SMauro Carvalho Chehab 4602a21d80dSMauro Carvalho Chehab def doc(self, output_in_txt=False, show_symbols=True, show_file=True, 4612a21d80dSMauro Carvalho Chehab filter_path=None): 462484e9aa6SMauro Carvalho Chehab """Print ABI at stdout""" 463484e9aa6SMauro Carvalho Chehab 464484e9aa6SMauro Carvalho Chehab part = None 465484e9aa6SMauro Carvalho Chehab for key, v in sorted(self.data.items(), 466484e9aa6SMauro Carvalho Chehab key=lambda x: (x[1].get("type", ""), 467484e9aa6SMauro Carvalho Chehab x[1].get("what"))): 468484e9aa6SMauro Carvalho Chehab 4692a21d80dSMauro Carvalho Chehab wtype = v.get("type", "Symbol") 470484e9aa6SMauro Carvalho Chehab file_ref = v.get("file") 471484e9aa6SMauro Carvalho Chehab names = v.get("what", [""]) 472484e9aa6SMauro Carvalho Chehab 4732a21d80dSMauro Carvalho Chehab if wtype == "File": 4742a21d80dSMauro Carvalho Chehab if not show_file: 4752a21d80dSMauro Carvalho Chehab continue 4762a21d80dSMauro Carvalho Chehab else: 4772a21d80dSMauro Carvalho Chehab if not show_symbols: 4782a21d80dSMauro Carvalho Chehab continue 4792a21d80dSMauro Carvalho Chehab 4802a21d80dSMauro Carvalho Chehab if filter_path: 4812a21d80dSMauro Carvalho Chehab if v.get("path") != filter_path: 482484e9aa6SMauro Carvalho Chehab continue 483484e9aa6SMauro Carvalho Chehab 4849bec7870SMauro Carvalho Chehab msg = "" 4859bec7870SMauro Carvalho Chehab 486484e9aa6SMauro Carvalho Chehab if wtype != "File": 487484e9aa6SMauro Carvalho Chehab cur_part = names[0] 488484e9aa6SMauro Carvalho Chehab if cur_part.find("/") >= 0: 489484e9aa6SMauro Carvalho Chehab match = self.re_what.match(cur_part) 490484e9aa6SMauro Carvalho Chehab if match: 491484e9aa6SMauro Carvalho Chehab symbol = match.group(1).rstrip("/") 492484e9aa6SMauro Carvalho Chehab cur_part = "Symbols under " + symbol 493484e9aa6SMauro Carvalho Chehab 494484e9aa6SMauro Carvalho Chehab if cur_part and cur_part != part: 495484e9aa6SMauro Carvalho Chehab part = cur_part 4966649b421SMauro Carvalho Chehab msg += part + "\n"+ "-" * len(part) +"\n\n" 497484e9aa6SMauro Carvalho Chehab 4989bec7870SMauro Carvalho Chehab msg += f".. _{key}:\n\n" 499484e9aa6SMauro Carvalho Chehab 500484e9aa6SMauro Carvalho Chehab max_len = 0 501484e9aa6SMauro Carvalho Chehab for i in range(0, len(names)): # pylint: disable=C0200 502484e9aa6SMauro Carvalho Chehab names[i] = "**" + self.re_escape.sub(r"\\\1", names[i]) + "**" 503484e9aa6SMauro Carvalho Chehab 504484e9aa6SMauro Carvalho Chehab max_len = max(max_len, len(names[i])) 505484e9aa6SMauro Carvalho Chehab 5069bec7870SMauro Carvalho Chehab msg += "+-" + "-" * max_len + "-+\n" 507484e9aa6SMauro Carvalho Chehab for name in names: 5089bec7870SMauro Carvalho Chehab msg += f"| {name}" + " " * (max_len - len(name)) + " |\n" 5099bec7870SMauro Carvalho Chehab msg += "+-" + "-" * max_len + "-+\n" 5109bec7870SMauro Carvalho Chehab msg += "\n" 511484e9aa6SMauro Carvalho Chehab 512484e9aa6SMauro Carvalho Chehab for ref in file_ref: 513484e9aa6SMauro Carvalho Chehab if wtype == "File": 5149bec7870SMauro Carvalho Chehab msg += f".. _{ref[1]}:\n\n" 515484e9aa6SMauro Carvalho Chehab else: 516484e9aa6SMauro Carvalho Chehab base = os.path.basename(ref[0]) 5179bec7870SMauro Carvalho Chehab msg += f"Defined on file :ref:`{base} <{ref[1]}>`\n\n" 518484e9aa6SMauro Carvalho Chehab 519484e9aa6SMauro Carvalho Chehab if wtype == "File": 5206649b421SMauro Carvalho Chehab msg += names[0] +"\n" + "-" * len(names[0]) +"\n\n" 521484e9aa6SMauro Carvalho Chehab 522484e9aa6SMauro Carvalho Chehab desc = v.get("description") 523484e9aa6SMauro Carvalho Chehab if not desc and wtype != "File": 5249bec7870SMauro Carvalho Chehab msg += f"DESCRIPTION MISSING for {names[0]}\n\n" 525484e9aa6SMauro Carvalho Chehab 526484e9aa6SMauro Carvalho Chehab if desc: 527484e9aa6SMauro Carvalho Chehab if output_in_txt: 5289bec7870SMauro Carvalho Chehab msg += self.desc_txt(desc) 529484e9aa6SMauro Carvalho Chehab else: 5309bec7870SMauro Carvalho Chehab msg += self.desc_rst(desc) 531484e9aa6SMauro Carvalho Chehab 532484e9aa6SMauro Carvalho Chehab symbols = v.get("symbols") 533484e9aa6SMauro Carvalho Chehab if symbols: 5349bec7870SMauro Carvalho Chehab msg += "Has the following ABI:\n\n" 535484e9aa6SMauro Carvalho Chehab 536484e9aa6SMauro Carvalho Chehab for w, label in symbols: 537484e9aa6SMauro Carvalho Chehab # Escape special chars from content 538484e9aa6SMauro Carvalho Chehab content = self.re_escape.sub(r"\\\1", w) 539484e9aa6SMauro Carvalho Chehab 5409bec7870SMauro Carvalho Chehab msg += f"- :ref:`{content} <{label}>`\n\n" 541484e9aa6SMauro Carvalho Chehab 542484e9aa6SMauro Carvalho Chehab users = v.get("users") 543484e9aa6SMauro Carvalho Chehab if users and users.strip(" \t\n"): 5446649b421SMauro Carvalho Chehab users = users.strip("\n").replace('\n', '\n\t') 5456649b421SMauro Carvalho Chehab msg += f"Users:\n\t{users}\n\n" 5469bec7870SMauro Carvalho Chehab 547aea5e52dSMauro Carvalho Chehab ln = v.get("line_no", 1) 548aea5e52dSMauro Carvalho Chehab 549aea5e52dSMauro Carvalho Chehab yield (msg, file_ref[0][0], ln) 550484e9aa6SMauro Carvalho Chehab 551484e9aa6SMauro Carvalho Chehab def check_issues(self): 552484e9aa6SMauro Carvalho Chehab """Warn about duplicated ABI entries""" 553484e9aa6SMauro Carvalho Chehab 554484e9aa6SMauro Carvalho Chehab for what, v in self.what_symbols.items(): 555484e9aa6SMauro Carvalho Chehab files = v.get("file") 556484e9aa6SMauro Carvalho Chehab if not files: 557484e9aa6SMauro Carvalho Chehab # Should never happen if the parser works properly 558484e9aa6SMauro Carvalho Chehab self.log.warning("%s doesn't have a file associated", what) 559484e9aa6SMauro Carvalho Chehab continue 560484e9aa6SMauro Carvalho Chehab 561484e9aa6SMauro Carvalho Chehab if len(files) == 1: 562484e9aa6SMauro Carvalho Chehab continue 563484e9aa6SMauro Carvalho Chehab 564484e9aa6SMauro Carvalho Chehab f = [] 565484e9aa6SMauro Carvalho Chehab for fname, lines in sorted(files.items()): 566484e9aa6SMauro Carvalho Chehab if not lines: 567484e9aa6SMauro Carvalho Chehab f.append(f"{fname}") 568484e9aa6SMauro Carvalho Chehab elif len(lines) == 1: 569484e9aa6SMauro Carvalho Chehab f.append(f"{fname}:{lines[0]}") 570484e9aa6SMauro Carvalho Chehab else: 5716649b421SMauro Carvalho Chehab m = fname + "lines " 5726649b421SMauro Carvalho Chehab m += ", ".join(str(x) for x in lines) 5736649b421SMauro Carvalho Chehab f.append(m) 574484e9aa6SMauro Carvalho Chehab 575484e9aa6SMauro Carvalho Chehab self.log.warning("%s is defined %d times: %s", what, len(f), "; ".join(f)) 5766b48bea1SMauro Carvalho Chehab 5776b48bea1SMauro Carvalho Chehab def search_symbols(self, expr): 5786b48bea1SMauro Carvalho Chehab """ Searches for ABI symbols """ 5796b48bea1SMauro Carvalho Chehab 5806b48bea1SMauro Carvalho Chehab regex = re.compile(expr, re.I) 5816b48bea1SMauro Carvalho Chehab 5826b48bea1SMauro Carvalho Chehab found_keys = 0 5836b48bea1SMauro Carvalho Chehab for t in sorted(self.data.items(), key=lambda x: [0]): 5846b48bea1SMauro Carvalho Chehab v = t[1] 5856b48bea1SMauro Carvalho Chehab 5866b48bea1SMauro Carvalho Chehab wtype = v.get("type", "") 5876b48bea1SMauro Carvalho Chehab if wtype == "File": 5886b48bea1SMauro Carvalho Chehab continue 5896b48bea1SMauro Carvalho Chehab 5906b48bea1SMauro Carvalho Chehab for what in v.get("what", [""]): 5916b48bea1SMauro Carvalho Chehab if regex.search(what): 5926b48bea1SMauro Carvalho Chehab found_keys += 1 5936b48bea1SMauro Carvalho Chehab 5946b48bea1SMauro Carvalho Chehab kernelversion = v.get("kernelversion", "").strip(" \t\n") 5956b48bea1SMauro Carvalho Chehab date = v.get("date", "").strip(" \t\n") 5966b48bea1SMauro Carvalho Chehab contact = v.get("contact", "").strip(" \t\n") 5976b48bea1SMauro Carvalho Chehab users = v.get("users", "").strip(" \t\n") 5986b48bea1SMauro Carvalho Chehab desc = v.get("description", "").strip(" \t\n") 5996b48bea1SMauro Carvalho Chehab 6006b48bea1SMauro Carvalho Chehab files = [] 6016b48bea1SMauro Carvalho Chehab for f in v.get("file", ()): 6026b48bea1SMauro Carvalho Chehab files.append(f[0]) 6036b48bea1SMauro Carvalho Chehab 6046b48bea1SMauro Carvalho Chehab what = str(found_keys) + ". " + what 6056b48bea1SMauro Carvalho Chehab title_tag = "-" * len(what) 6066b48bea1SMauro Carvalho Chehab 6076b48bea1SMauro Carvalho Chehab print(f"\n{what}\n{title_tag}\n") 6086b48bea1SMauro Carvalho Chehab 6096b48bea1SMauro Carvalho Chehab if kernelversion: 6106b48bea1SMauro Carvalho Chehab print(f"Kernel version:\t\t{kernelversion}") 6116b48bea1SMauro Carvalho Chehab 6126b48bea1SMauro Carvalho Chehab if date: 6136b48bea1SMauro Carvalho Chehab print(f"Date:\t\t\t{date}") 6146b48bea1SMauro Carvalho Chehab 6156b48bea1SMauro Carvalho Chehab if contact: 6166b48bea1SMauro Carvalho Chehab print(f"Contact:\t\t{contact}") 6176b48bea1SMauro Carvalho Chehab 6186b48bea1SMauro Carvalho Chehab if users: 6196b48bea1SMauro Carvalho Chehab print(f"Users:\t\t\t{users}") 6206b48bea1SMauro Carvalho Chehab 6216649b421SMauro Carvalho Chehab print("Defined on file(s):\t" + ", ".join(files)) 6226b48bea1SMauro Carvalho Chehab 6236b48bea1SMauro Carvalho Chehab if desc: 6246649b421SMauro Carvalho Chehab desc = desc.strip("\n") 6256649b421SMauro Carvalho Chehab print(f"\n{desc}\n") 6266b48bea1SMauro Carvalho Chehab 6276b48bea1SMauro Carvalho Chehab if not found_keys: 6286b48bea1SMauro Carvalho Chehab print(f"Regular expression /{expr}/ not found.") 629