coulomb-social/scripts/audit-bubble-rendering.py

86 lines
3.9 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""Offline private rendering cards and lossiness report; never grants acceptance."""
import argparse
import hashlib
import json
import os
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
# The standalone tool uses an integration-disabled, in-memory configuration.
os.environ["DJANGO_SETTINGS_MODULE"] = "coulomb_social.settings.test"
import django # noqa: E402
django.setup()
from coulomb_social.apps.spaces.bubble_transfer import _records # noqa: E402
from coulomb_social.apps.spaces.rendering_audit import ( # noqa: E402
audit_body,
review_card,
review_index,
)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("articles", type=Path, help="Private captured articles.json")
parser.add_argument("--output", type=Path, required=True, help="New private directory")
args = parser.parse_args()
try:
output = args.output.expanduser().resolve()
source = args.articles.expanduser().resolve()
repo = Path(__file__).resolve().parents[1]
if output.is_relative_to(repo) or source.is_relative_to(output):
raise ValueError("Unsafe output location")
captured = source.read_bytes()
records = _records(json.loads(captured))
report = {"schema": "coulomb.bubble-rendering-audit.v1",
"source_sha256": hashlib.sha256(captured).hexdigest(),
"original_bubble_render_compared": False,
"productive_fidelity_accepted": False, "records": []}
cards = {}
for identity, article in sorted(records.items()):
body = article.get("content_text")
title = str(article.get("nomer_text") or identity)
if not isinstance(body, str):
report["records"].append({"id": identity, "title": title, "status": "body_missing"})
continue
audit, rendered = audit_body(body)
card = hashlib.sha256(identity.encode()).hexdigest() + ".html"
cards[card] = review_card(title, body, rendered, audit, source_id=identity)
report["records"].append({"id": identity, "title": title,
"status": "rendered", "card": card,
"card_sha256": hashlib.sha256(cards[card]).hexdigest(),
**audit})
output.mkdir(mode=0o700)
for name, data in sorted({**cards, "index.html": review_index(report["records"])}.items()):
fd = os.open(output / name, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
with os.fdopen(fd, "wb") as stream:
stream.write(data)
report["status"] = "completed_offline_rendering_audit"
fd = os.open(output / "audit.json", os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
with os.fdopen(fd, "w") as stream:
json.dump(report, stream, indent=2, sort_keys=True)
stream.write("\n")
print(json.dumps({
"records": len(records), "rendered_bodies": len(cards),
"sanitization_changed_html": sum(r.get("sanitization_changed_html", False)
for r in report["records"]),
"markup_changed_by_sanitization": sum(r.get("markup_changed_by_sanitization", False)
for r in report["records"]),
"serialization_only_changes": sum(r.get("serialization_only_change", False)
for r in report["records"]),
"productive_fidelity_accepted": False,
}))
except (ValueError, OSError, TypeError):
print("Rendering audit refused; private files retained. "
"Without audit.json output is incomplete.", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())