tbox: v1.6 ...
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate the Excel rulebook from rules.yaml (EV-008).
|
||||
|
||||
USAGE
|
||||
python3 generate_rulebook_xlsx.py [output.xlsx]
|
||||
|
||||
rules.yaml is the single source of truth. This script never edits it.
|
||||
The output must never be edited by hand (EV-007).
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
import yaml
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
|
||||
from openpyxl.utils import get_column_letter
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
SRC = os.path.join(HERE, "rules.yaml")
|
||||
OUT = sys.argv[1] if len(sys.argv) > 1 else os.path.join(HERE, "PR_TBox_Rulebook.xlsx")
|
||||
|
||||
FONT = "Arial"
|
||||
INK = "1F2933"
|
||||
HEAD_FILL = PatternFill("solid", fgColor="1F2933")
|
||||
BAND = {
|
||||
"Identifier": "E8F0F7",
|
||||
"Label": "EAF3EC",
|
||||
"Declaration": "FBF0E4",
|
||||
"Evolution": "F2EAF5",
|
||||
}
|
||||
MAJOR_FILL = PatternFill("solid", fgColor="FFF4CE")
|
||||
THIN = Side(style="thin", color="C9CFD6")
|
||||
BORDER = Border(left=THIN, right=THIN, top=THIN, bottom=THIN)
|
||||
|
||||
|
||||
def selfcheck(doc):
|
||||
"""EV-011 and EV-012 applied to the rulebook itself."""
|
||||
problems = []
|
||||
for r in doc["rules"]:
|
||||
ctl = r.get("control") or {}
|
||||
if not ctl.get("tier"):
|
||||
problems.append("%s: no control tier (EV-012)" % r["id"])
|
||||
if r.get("severity") == "BLOCKING" and not ctl.get("executor"):
|
||||
problems.append("%s: BLOCKING without an executor (EV-011)" % r["id"])
|
||||
if r.get("category") not in doc["categories"]:
|
||||
problems.append("%s: unknown category" % r["id"])
|
||||
return problems
|
||||
|
||||
|
||||
def flow(t):
|
||||
return " ".join((t or "").split())
|
||||
|
||||
|
||||
def joined(v):
|
||||
return ", ".join(v) if isinstance(v, list) else (v if v else "")
|
||||
|
||||
|
||||
def header(ws, cols):
|
||||
for i, (title, width) in enumerate(cols, start=1):
|
||||
c = ws.cell(row=1, column=i, value=title)
|
||||
c.font = Font(name=FONT, size=10, bold=True, color="FFFFFF")
|
||||
c.fill = HEAD_FILL
|
||||
c.alignment = Alignment(vertical="center", horizontal="left", wrap_text=True)
|
||||
c.border = BORDER
|
||||
ws.column_dimensions[get_column_letter(i)].width = width
|
||||
ws.row_dimensions[1].height = 28
|
||||
ws.freeze_panes = ws.cell(row=2, column=1)
|
||||
ws.auto_filter.ref = "A1:%s1" % get_column_letter(len(cols))
|
||||
|
||||
|
||||
def put(ws, r, c, value, wrap=False, bold=False, fill=None, size=10):
|
||||
cell = ws.cell(row=r, column=c, value=value)
|
||||
cell.font = Font(name=FONT, size=size, bold=bold, color=INK)
|
||||
cell.alignment = Alignment(vertical="top", wrap_text=wrap)
|
||||
cell.border = BORDER
|
||||
if fill:
|
||||
cell.fill = fill
|
||||
return cell
|
||||
|
||||
|
||||
def sheet_cover(wb, doc):
|
||||
m = doc["meta"]
|
||||
ws = wb.create_sheet("Cover")
|
||||
ws.column_dimensions["A"].width = 24
|
||||
ws.column_dimensions["B"].width = 100
|
||||
put(ws, 1, 1, m["title"], bold=True, size=14)
|
||||
|
||||
rows = [
|
||||
("Version", "%s — %s" % (m["version"], m["status"])),
|
||||
("Generated", "%s from rules.yaml" % m["date"]),
|
||||
("Scope", flow(m["scope"])),
|
||||
("Audience", flow(m["audience"])),
|
||||
("Generation", "Generated from rules.yaml by generate_rulebook_xlsx.py. "
|
||||
"Do not edit this workbook: edit the source and regenerate "
|
||||
"(EV-007, EV-008)."),
|
||||
]
|
||||
r = 3
|
||||
for k, v in rows:
|
||||
put(ws, r, 1, k, bold=True)
|
||||
put(ws, r, 2, v, wrap=True)
|
||||
ws.row_dimensions[r].height = 15 * (len(v) // 95 + 1)
|
||||
r += 1
|
||||
|
||||
r += 1
|
||||
put(ws, r, 1, "Categories", bold=True, size=12)
|
||||
r += 1
|
||||
for k, v in doc["categories"].items():
|
||||
put(ws, r, 1, k, bold=True, fill=PatternFill("solid", fgColor=BAND[k]))
|
||||
put(ws, r, 2, "%s — %s" % (v["title"], flow(v["intent"])), wrap=True)
|
||||
ws.row_dimensions[r].height = 30
|
||||
r += 1
|
||||
|
||||
r += 1
|
||||
put(ws, r, 1, "Counts", bold=True, size=12)
|
||||
r += 1
|
||||
for label, formula in [
|
||||
("Rules", "=COUNTA(Rules!A2:A200)"),
|
||||
("of which blocking", '=COUNTIF(Rules!D2:D200,"BLOCKING")'),
|
||||
("Examples", "=COUNTA(Examples!A2:A400)"),
|
||||
("Classes", "=COUNTA(Abstractness!A2:A200)"),
|
||||
("of which abstract", "=COUNTIF(Abstractness!B2:B200,TRUE)"),
|
||||
("Display entries", "=COUNTA(Display!A2:A200)"),
|
||||
]:
|
||||
put(ws, r, 1, label)
|
||||
put(ws, r, 2, formula)
|
||||
r += 1
|
||||
|
||||
|
||||
def sheet_rules(wb, doc):
|
||||
ws = wb.create_sheet("Rules")
|
||||
header(ws, [("Rule", 10), ("Category", 14), ("Title", 40), ("Severity", 11),
|
||||
("Statement", 70), ("Applies to", 24), ("Enforced at", 14),
|
||||
("Executor", 26), ("Procedure", 16), ("Related", 16),
|
||||
("Why", 90)])
|
||||
r = 2
|
||||
for x in doc["rules"]:
|
||||
ctl = x.get("control") or {}
|
||||
band = PatternFill("solid", fgColor=BAND[x["category"]])
|
||||
put(ws, r, 1, x["id"], bold=True, fill=band)
|
||||
put(ws, r, 2, x["category"], fill=band)
|
||||
put(ws, r, 3, x["title"], wrap=True, bold=True)
|
||||
put(ws, r, 4, x["severity"],
|
||||
fill=MAJOR_FILL if x["severity"] != "BLOCKING" else None)
|
||||
put(ws, r, 5, flow(x["statement"]), wrap=True)
|
||||
put(ws, r, 6, joined(x.get("scope")), wrap=True)
|
||||
put(ws, r, 7, joined(ctl.get("tier")))
|
||||
put(ws, r, 8, ctl.get("executor") or "")
|
||||
put(ws, r, 9, ctl.get("procedure") or "pending")
|
||||
put(ws, r, 10, x.get("filiation") or "")
|
||||
put(ws, r, 11, flow(x.get("rationale")), wrap=True)
|
||||
ws.row_dimensions[r].height = 78
|
||||
r += 1
|
||||
|
||||
|
||||
def sheet_examples(wb, doc):
|
||||
ws = wb.create_sheet("Examples")
|
||||
header(ws, [("Rule", 10), ("Category", 14), ("From", 48), ("To", 48), ("Note", 62)])
|
||||
r = 2
|
||||
for x in doc["rules"]:
|
||||
for e in x.get("examples") or []:
|
||||
band = PatternFill("solid", fgColor=BAND[x["category"]])
|
||||
put(ws, r, 1, x["id"], bold=True, fill=band)
|
||||
put(ws, r, 2, x["category"], fill=band)
|
||||
put(ws, r, 3, str(e["from"]), wrap=True)
|
||||
put(ws, r, 4, str(e["to"]), wrap=True)
|
||||
put(ws, r, 5, e.get("note") or "", wrap=True)
|
||||
r += 1
|
||||
|
||||
|
||||
def sheet_abstractness(wb, doc):
|
||||
ws = wb.create_sheet("Abstractness")
|
||||
header(ws, [("Class", 36), ("isAbstract", 12), ("Note", 96)])
|
||||
r = 2
|
||||
for a in doc["abstractness"]:
|
||||
put(ws, r, 1, a["term"], bold=a["is_abstract"])
|
||||
put(ws, r, 2, a["is_abstract"])
|
||||
put(ws, r, 3, a.get("note") or "", wrap=True)
|
||||
r += 1
|
||||
|
||||
|
||||
def sheet_display(wb, doc):
|
||||
ws = wb.create_sheet("Display")
|
||||
header(ws, [("IRI", 36), ("rdfs:label", 36), ("shortLabel", 22),
|
||||
("acronym", 10), ("Note", 80)])
|
||||
r = 2
|
||||
for d in doc["display"]:
|
||||
put(ws, r, 1, d["iri"], bold=True)
|
||||
put(ws, r, 2, d["label"])
|
||||
put(ws, r, 3, d.get("short_label") or "")
|
||||
put(ws, r, 4, d.get("acronym") or "")
|
||||
put(ws, r, 5, d.get("note") or "", wrap=True)
|
||||
r += 1
|
||||
|
||||
|
||||
def main():
|
||||
doc = yaml.safe_load(open(SRC, encoding="utf-8"))
|
||||
problems = selfcheck(doc)
|
||||
if problems:
|
||||
print("SELF-CHECK FAILED")
|
||||
for p in problems:
|
||||
print(" " + p)
|
||||
sys.exit(1)
|
||||
|
||||
wb = Workbook()
|
||||
wb.remove(wb.active)
|
||||
sheet_cover(wb, doc)
|
||||
sheet_rules(wb, doc)
|
||||
sheet_examples(wb, doc)
|
||||
sheet_abstractness(wb, doc)
|
||||
sheet_display(wb, doc)
|
||||
wb.save(OUT)
|
||||
print("self-check passed: %d rules" % len(doc["rules"]))
|
||||
print("written: %s (%d sheets)" % (OUT, len(wb.sheetnames)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user