tbox: v1.6 ...
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate the Markdown rulebook from rules.yaml (EV-008).
|
||||
|
||||
USAGE
|
||||
python3 generate_rulebook_md.py [output.md]
|
||||
|
||||
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
|
||||
|
||||
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.md")
|
||||
|
||||
|
||||
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(text):
|
||||
"""Collapse a YAML folded scalar into a single paragraph."""
|
||||
return " ".join((text or "").split())
|
||||
|
||||
|
||||
def joined(v):
|
||||
return ", ".join(v) if isinstance(v, list) else (v or "-")
|
||||
|
||||
|
||||
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)
|
||||
|
||||
m, out = doc["meta"], []
|
||||
w = out.append
|
||||
|
||||
w("# %s" % m["title"])
|
||||
w("")
|
||||
w("**Version %s** — %s — generated %s" % (m["version"], m["status"], m["date"]))
|
||||
w("")
|
||||
w("> Generated from `rules.yaml`. Do not edit this document: edit the source "
|
||||
"and regenerate (EV-007, EV-008).")
|
||||
w("")
|
||||
w("## Scope")
|
||||
w("")
|
||||
w(flow(m["scope"]))
|
||||
w("")
|
||||
w(flow(m["audience"]))
|
||||
w("")
|
||||
w("## Categories")
|
||||
w("")
|
||||
w("| Category | Subject | Rules |")
|
||||
w("|---|---|---|")
|
||||
for k, v in doc["categories"].items():
|
||||
n = sum(1 for r in doc["rules"] if r["category"] == k)
|
||||
w("| **%s** | %s | %d |" % (k, v["title"], n))
|
||||
w("")
|
||||
w("## Severity")
|
||||
w("")
|
||||
for k, v in doc["severity_model"].items():
|
||||
w("- **%s** — %s" % (k, flow(v)))
|
||||
w("")
|
||||
|
||||
for cat, info in doc["categories"].items():
|
||||
w("---")
|
||||
w("")
|
||||
w("# %s — %s" % (cat, info["title"]))
|
||||
w("")
|
||||
w(flow(info["intent"]))
|
||||
w("")
|
||||
for r in [x for x in doc["rules"] if x["category"] == cat]:
|
||||
ctl = r.get("control") or {}
|
||||
w("## %s — %s" % (r["id"], r["title"]))
|
||||
w("")
|
||||
w("**Statement.** %s" % flow(r["statement"]))
|
||||
w("")
|
||||
w("| | |")
|
||||
w("|---|---|")
|
||||
w("| Severity | **%s** |" % r["severity"])
|
||||
w("| Applies to | %s |" % joined(r.get("scope")))
|
||||
w("| Enforced at | %s |" % joined(ctl.get("tier")))
|
||||
w("| Executor | `%s` |" % (ctl.get("executor") or "-"))
|
||||
w("| Procedure | %s |" % (ctl.get("procedure") or "_pending_"))
|
||||
if r.get("filiation"):
|
||||
w("| Related | %s |" % r["filiation"])
|
||||
w("")
|
||||
if r.get("rationale"):
|
||||
w("**Why.** %s" % flow(r["rationale"]))
|
||||
w("")
|
||||
if r.get("examples"):
|
||||
w("| From | To | Note |")
|
||||
w("|---|---|---|")
|
||||
for e in r["examples"]:
|
||||
w("| %s | %s | %s |" % (e["from"], e["to"], e.get("note") or ""))
|
||||
w("")
|
||||
|
||||
ab = doc["abstractness"]
|
||||
w("---")
|
||||
w("")
|
||||
w("# Annex A — Abstractness")
|
||||
w("")
|
||||
w("Applies TN-006 and TN-007 to the model. %d classes, of which **%d abstract** "
|
||||
"and **%d concrete**. Maintained with the model, not after it."
|
||||
% (len(ab), sum(1 for a in ab if a["is_abstract"]),
|
||||
sum(1 for a in ab if not a["is_abstract"])))
|
||||
w("")
|
||||
w("| Class | isAbstract | Note |")
|
||||
w("|---|---|---|")
|
||||
for a in ab:
|
||||
w("| `%s` | %s | %s |"
|
||||
% (a["term"], "**true**" if a["is_abstract"] else "false", a.get("note") or ""))
|
||||
w("")
|
||||
w("---")
|
||||
w("")
|
||||
w("# Annex B — Display")
|
||||
w("")
|
||||
w("Applies TN-011 and TN-022. The short label is what screens display and what "
|
||||
"relation names reuse; the acronym is a search key, never an identity.")
|
||||
w("")
|
||||
w("| IRI | rdfs:label | shortLabel | acronym | Note |")
|
||||
w("|---|---|---|---|---|")
|
||||
for d in doc["display"]:
|
||||
w("| `%s` | %s | %s | %s | %s |"
|
||||
% (d["iri"], d["label"], d.get("short_label") or "-",
|
||||
d.get("acronym") or "-", d.get("note") or ""))
|
||||
w("")
|
||||
|
||||
open(OUT, "w", encoding="utf-8").write("\n".join(out))
|
||||
print("self-check passed: %d rules, every tier assigned, no blocking rule "
|
||||
"without an executor" % len(doc["rules"]))
|
||||
print("written: %s (%d lines)" % (OUT, len(out)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user