#!/usr/bin/env python3 """Rebuild data/misc/loglevel.tsv from RFC 5424 and POSIX . data-import/loglevel.py [--source URL_OR_FILE] [--posix URL_OR_FILE] [--cache DIR] [--out FILE] Every severity in Table 2 ships, paired to POSIX's keywords by the order each source lists them, which holds only because a keyword prefixes the severity RFC 5424 numbers alike. """ import argparse import html import re import sys from pathlib import Path import source import tsv SOURCE = "https://www.rfc-editor.org/rfc/rfc5424.txt" POSIX = "https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/syslog.h.html" OUT = Path(__file__).resolve().parent.parent / "data" / "misc" / "loglevel.tsv" CACHE = Path(__file__).resolve().parent / "cache" COLUMNS = ["code", "keyword", "severity"] TABLE = re.compile(r"Table 1\.\s+Syslog Message Facilities(.*?)Table 2\.\s+Syslog Message Severities", re.S) ROW = re.compile(r"^ +(\d) +([A-Z][a-z]+): ", re.M) SECTION = re.compile(r"severity level portion of the.*?macros:(.*?)The following shall be declared as functions", re.S) MACRO = re.compile(r"\bLOG_([A-Z]+)\b") EXPECTED = 8 def severities(text): table = TABLE.search(text) if not table: sys.exit("the RFC holds no text between the facility and the severity caption; its tables have moved") return ROW.findall(table.group(1)) def keywords(page): section = SECTION.search(html.unescape(re.sub(r"<[^>]+>", "", page))) if not section: sys.exit("POSIX spells no severity list between its own caption and the function declarations; the page has moved") return [m.lower() for m in MACRO.findall(section.group(1))] def main(): p = argparse.ArgumentParser(description=__doc__.splitlines()[0]) p.add_argument("--cache", default=str(CACHE)) p.add_argument("--source", default=SOURCE) p.add_argument("--posix", default=POSIX) p.add_argument("--out", default=str(OUT)) a = p.parse_args() table = sorted(severities(source.fetch(a.source, a.cache, "rfc5424.txt").decode("utf-8"))) codes = [code for code, _ in table] if codes != [str(i) for i in range(EXPECTED)]: sys.exit(f"the severity table numbers {codes}, not 0 through {EXPECTED - 1}") words = keywords(source.fetch(a.posix, a.cache, "posix-syslog.h.html").decode("utf-8")) if len(words) != EXPECTED: sys.exit(f"POSIX lists {len(words)} severity macros, not {EXPECTED}: {words}") rows = [] for (code, severity), keyword in zip(table, words): if not severity.lower().startswith(keyword): sys.exit(f"severity {code} is {severity} in the RFC and {keyword} in POSIX, which is no prefix of it") rows.append({"code": code, "keyword": keyword, "severity": severity}) tsv.write(a.out, COLUMNS, rows) if __name__ == "__main__": main()