ZaStoGram/Tools/check_plugin_alert_contract.py

75 lines
2.9 KiB
Python

#!/usr/bin/env python3
"""Static guard for exteraGram-facing ui.alert.AlertDialogBuilder compatibility.
Plugins build dialogs from their button handlers; a constructor argument, constant or
method missing here makes that handler raise, so the button plays its ripple and does
nothing.
"""
from __future__ import annotations
import ast
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
ALERT = ROOT / "TMessagesProj/src/main/python/ui/alert.py"
CONSTANTS = {
"ALERT_TYPE_MESSAGE", "ALERT_TYPE_LOADING", "ALERT_TYPE_SPINNER",
"BUTTON_POSITIVE", "BUTTON_NEGATIVE", "BUTTON_NEUTRAL",
}
METHODS = {
"set_title", "set_message", "set_message_text_view_clickable", "set_view", "set_items",
"set_positive_button", "set_negative_button", "set_neutral_button", "make_button_red",
"set_on_back_button_listener", "set_on_dismiss_listener", "set_on_cancel_listener",
"set_top_image", "set_top_drawable", "set_top_animation", "set_dim_enabled",
"set_dialog_button_color_key", "set_blurred_background", "set_cancelable",
"set_canceled_on_touch_outside", "create", "show", "dismiss", "get_dialog", "get_button",
"set_progress",
}
INIT_ARGS = ["self", "context", "progress_style", "resources_provider"]
def fail(errors: list[str]) -> int:
print("Plugin ui.alert contract check failed:", file=sys.stderr)
for error in errors:
print(f"- {error}", file=sys.stderr)
return 1
def main() -> int:
try:
source = ALERT.read_text(encoding="utf-8")
except FileNotFoundError:
return fail([f"Missing {ALERT.relative_to(ROOT)}"])
tree = ast.parse(source, filename=str(ALERT))
builder = next((n for n in tree.body if isinstance(n, ast.ClassDef) and n.name == "AlertDialogBuilder"), None)
if builder is None:
return fail(["ui.alert must define AlertDialogBuilder"])
methods = {n.name: n for n in builder.body if isinstance(n, ast.FunctionDef)}
constants = {t.id for n in builder.body if isinstance(n, ast.Assign)
for t in n.targets if isinstance(t, ast.Name)}
errors: list[str] = []
init = methods.get("__init__")
if init is None or [a.arg for a in init.args.args] != INIT_ARGS:
errors.append("AlertDialogBuilder.__init__ must accept (context, progress_style, resources_provider)")
for name in sorted(CONSTANTS - constants):
errors.append(f"AlertDialogBuilder.{name} is missing")
for name in sorted(METHODS - methods.keys()):
errors.append(f"AlertDialogBuilder.{name}() is missing")
for name in ("create", "show"):
fn = methods.get(name)
returns = [n for n in ast.walk(fn) if isinstance(n, ast.Return)] if fn else []
if fn and not any(isinstance(r.value, ast.Name) and r.value.id == "self" for r in returns):
errors.append(f"AlertDialogBuilder.{name}() must return the builder, as in exteraGram")
return fail(errors) if errors else 0
if __name__ == "__main__":
sys.exit(main())