1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
|
#!/usr/bin/env python3
# Reads all .ui files and creates builder.pyi
# Execute this script from the repo root dir
import logging
import sys
from io import TextIOWrapper
from pathlib import Path
from xml.etree import ElementTree
logging.basicConfig(level="INFO", format="%(levelname)s: %(message)s")
log = logging.getLogger()
cwd = Path.cwd()
if cwd.name != "gajim":
sys.exit("Script needs to be executed from gajim repository root directory")
in_path = cwd / "gajim" / "data" / "gui"
out_path = cwd / "gajim" / "gtk" / "builder.pyi"
paths = list(in_path.iterdir())
paths.sort()
IMPORTS = """
from typing import Any
from typing import Literal
from typing import overload
from gi.repository import Adw
from gi.repository import Gtk
from gi.repository import GtkSource
class GajimBuilder:
def __init__(
self,
filename: str | None = None,
instance: Any = None,
widgets: list[str] | None = None,
domain: str | None = None,
gettext_: Any | None = None
) -> None: ...
class Builder(Gtk.Builder):
...
"""
CLASS_DEF = "\nclass %s(Builder):"
ATTR = "\n %s: %s"
GET_BUILDER_OVERLOAD = """
@overload
def get_builder(file_name: Literal['%s'], instance: Any = None, widgets: list[str] = ...) -> %s: ... # noqa""" # noqa: E501
GET_BUILDER = """\n\n
def get_builder(file_name: str, instance: Any = None, widgets: list[str] = ...) -> Builder: ...""" # noqa: E501
class InvalidFile(Exception):
pass
def make_class_name(path: Path) -> str:
name = path.name.removesuffix(".ui")
names = name.split("_")
names = [name.capitalize() for name in names]
return "".join(names) + "Builder"
def parse(path: Path, file: TextIOWrapper) -> str:
log.info("Read %s", path)
lines: list[str] = []
tree = ElementTree.parse(path)
if tree.find("template") is not None:
raise InvalidFile
for node in tree.iter(tag="object"):
id_ = node.attrib.get("id")
if id_ is None:
continue
klass = node.attrib["class"]
if klass.startswith("GtkSource"):
klass = f"GtkSource.{klass.removeprefix('GtkSource')}"
elif klass.startswith("Adw"):
klass = f"Adw.{klass.removeprefix('Adw')}"
elif klass.startswith("Atk"):
klass = f"Atk.{klass.removeprefix('Atk')}"
else:
klass = f"Gtk.{klass.removeprefix('Gtk')}"
lines.append(ATTR % (id_.replace("-", "_"), klass))
klass_name = make_class_name(path)
file.write(CLASS_DEF % klass_name)
if not lines:
file.write("\n pass")
else:
file.writelines(lines)
file.write("\n\n")
return klass_name
builder_names: list[tuple[str, str]] = []
with out_path.open(mode="w", encoding="utf8") as file:
file.write(IMPORTS)
for path in paths:
if path.is_dir():
continue
if path.name.endswith("~"):
continue
if path.name.startswith("#"):
continue
try:
name = parse(path, file)
except InvalidFile:
continue
builder_names.append((name, path.name))
for name, file_name in builder_names:
file.write(GET_BUILDER_OVERLOAD % (file_name, name))
file.write(GET_BUILDER)
file.write("\n")
|