def __init__(self, directory, logger=None,
enable_lineno=False, show_warnings=True, debug=0): """Stores arguments for the class and initialize class vars"""
def warn(self, fdata, msg, extra=None): """Displays a parse error if warning is enabled"""
ifnot self.show_warnings: return
msg = f"{fdata.fname}:{fdata.ln}: {msg}" if extra:
msg += "\n\t\t" + extra
self.log.warning(msg)
def add_symbol(self, what, fname, ln=None, xref=None): """Create a reference table describing where each 'what' is located"""
if what notin self.what_symbols:
self.what_symbols[what] = {"file": {}}
if fname notin self.what_symbols[what]["file"]:
self.what_symbols[what]["file"][fname] = []
if ln and ln notin self.what_symbols[what]["file"][fname]:
self.what_symbols[what]["file"][fname].append(ln)
if xref:
self.what_symbols[what]["xref"] = xref
def _parse_line(self, fdata, line): """Parse a single line of an ABI file"""
new_what = False
new_tag = False
content = None
match = self.re_tag.match(line) if match:
new = match.group(1).lower()
sep = match.group(2)
content = match.group(3)
match = self.re_valid.search(new) if match:
new_tag = match.group(1) else: if fdata.tag == "description": # New "tag" is actually part of description. # Don't consider it a tag
new_tag = False elif fdata.tag != "":
self.warn(fdata, f"tag '{fdata.tag}' is invalid", line)
if new_tag: # "where" is Invalid, but was a common mistake. Warn if found if new_tag == "where":
self.warn(fdata, "tag 'Where' is invalid. Should be 'What:' instead")
new_tag = "what"
if new_tag == "what":
fdata.space = None
if content notin self.what_symbols:
self.add_symbol(what=content, fname=fdata.fname, ln=fdata.ln)
if fdata.tag == "what":
fdata.what.append(content.strip("\n")) else: if fdata.key: if"description"notin self.data.get(fdata.key, {}):
self.warn(fdata, f"{fdata.key} doesn't have a description")
for w in fdata.what:
self.add_symbol(what=w, fname=fdata.fname,
ln=fdata.what_ln, xref=fdata.key)
if fdata.tag == "what":
w = content.strip("\n") if w:
self.data[fdata.key][fdata.tag].append(w) else:
self.data[fdata.key][fdata.tag] += content return
content = line.strip() if fdata.tag: if fdata.tag == "what":
w = content.strip("\n") if w:
self.data[fdata.key][fdata.tag].append(w) else:
self.data[fdata.key][fdata.tag] += "\n" + content.rstrip("\n") return
# Everything else is error if content:
self.warn(fdata, "Unexpected content", line)
def parse_readme(self, nametag, fname): """Parse ABI README file"""
nametag["what"] = ["Introduction"]
nametag["path"] = "README" with open(fname, "r", encoding="utf8", errors="backslashreplace") as fp: for line in fp:
match = self.re_tag.match(line) if match:
new = match.group(1).lower()
match = self.re_valid.search(new) if match:
nametag["description"] += "\n:" + line continue
nametag["description"] += line
def parse_file(self, fname, path, basename): """Parse a single file"""
# Store per-file state into a namespace variable. This will be used # by the per-line parser state machine and by the warning function.
fdata = Namespace
fdata.fname = fname
fdata.name = basename
pos = fname.find(ABI_DIR) if pos > 0:
f = fname[pos:] else:
f = fname
def desc_rst(self, desc): """Enrich ReST output by creating cross-references"""
# Remove title markups from the description # Having titles inside ABI files will only work if extra # care would be taken in order to strictly follow the same # level order for each markup.
desc = self.re_title_mark.sub("\n\n", "\n" + desc)
desc = desc.rstrip(" \t\n").lstrip("\n")
# Python's regex performance for non-compiled expressions is a lot # than Perl, as Perl automatically caches them at their # first usage. Here, we'll need to do the same, as otherwise the # performance penalty is be high
new_desc = "" for d in desc.split("\n"): if d == "":
new_desc += "\n" continue
# Use cross-references for doc files where needed
d = self.re_doc.sub(r":doc:`/\1`", d)
# Use cross-references for ABI generated docs where needed
matches = self.re_abi.findall(d) for m in matches:
abi = m[0] + m[1]
xref = self.file_refs.get(abi) ifnot xref: # This may happen if ABI is on a separate directory, # like parsing ABI testing and symbol is at stable. # The proper solution is to move this part of the code # for it to be inside sphinx/kernel_abi.py
self.log.info("Didn't find ABI reference for '%s'", abi) else:
new = self.re_escape.sub(r"\\\1", m[1])
d = re.sub(fr"\b{abi}\b", f":ref:`{new} <{xref}>`", d)
# Seek for cross reference symbols like /sys/... # Need to be careful to avoid doing it on a code block if d[0] notin [" ", "\t"]:
matches = self.re_xref_node.findall(d) for m in matches: # Finding ABI here is more complex due to wildcards
xref = self.what_refs.get(m) if xref:
new = self.re_escape.sub(r"\\\1", m)
d = re.sub(fr"\b{m}\b", f":ref:`{new} <{xref}>`", d)
new_desc += d + "\n"
return new_desc + "\n\n"
def doc(self, output_in_txt=False, show_symbols=True, show_file=True,
filter_path=None): """Print ABI at stdout"""
part = None for key, v in sorted(self.data.items(),
key=lambda x: (x[1].get("type", ""),
x[1].get("what"))):
if filter_path: if v.get("path") != filter_path: continue
msg = ""
if wtype != "File":
cur_part = names[0] if cur_part.find("/") >= 0:
match = self.re_what.match(cur_part) if match:
symbol = match.group(1).rstrip("/")
cur_part = "Symbols under " + symbol
if cur_part and cur_part != part:
part = cur_part
msg += part + "\n"+ "-" * len(part) +"\n\n"
msg += f".. _{key}:\n\n"
max_len = 0 for i in range(0, len(names)): # pylint: disable=C0200
names[i] = "**" + self.re_escape.sub(r"\\\1", names[i]) + "**"
for ref in file_ref: if wtype == "File":
msg += f".. _{ref[1]}:\n\n" else:
base = os.path.basename(ref[0])
msg += f"Defined on file :ref:`{base} <{ref[1]}>`\n\n"
desc = v.get("description") ifnot desc and wtype != "File":
msg += f"DESCRIPTION MISSING for {names[0]}\n\n"
if desc: if output_in_txt:
msg += self.desc_txt(desc) else:
msg += self.desc_rst(desc)
symbols = v.get("symbols") if symbols:
msg += "Has the following ABI:\n\n"
for w, label in symbols: # Escape special chars from content
content = self.re_escape.sub(r"\\\1", w)
msg += f"- :ref:`{content} <{label}>`\n\n"
users = v.get("users") if users and users.strip(" \t\n"):
users = users.strip("\n").replace('\n', '\n\t')
msg += f"Users:\n\t{users}\n\n"
ln = v.get("line_no", 1)
yield (msg, file_ref[0][0], ln)
def check_issues(self): """Warn about duplicated ABI entries"""
for what, v in self.what_symbols.items():
files = v.get("file") ifnot files: # Should never happen if the parser works properly
self.log.warning("%s doesn't have a file associated", what) continue
if len(files) == 1: continue
f = [] for fname, lines in sorted(files.items()): ifnot lines:
f.append(f"{fname}") elif len(lines) == 1:
f.append(f"{fname}:{lines[0]}") else:
m = fname + "lines "
m += ", ".join(str(x) for x in lines)
f.append(m)
self.log.warning("%s is defined %d times: %s", what, len(f), "; ".join(f))
def search_symbols(self, expr): """ Searches for ABI symbols """
regex = re.compile(expr, re.I)
found_keys = 0 for t in sorted(self.data.items(), key=lambda x: [0]):
v = t[1]
wtype = v.get("type", "") if wtype == "File": continue
for what in v.get("what", [""]): if regex.search(what):
found_keys += 1
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.