86 lines
3.9 KiB
Python
86 lines
3.9 KiB
Python
|
|
#!/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())
|