File: generate_ui_types.py

package info (click to toggle)
gajim 2.4.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 17,320 kB
  • sloc: python: 79,114; sh: 387; xml: 67; makefile: 6
file content (135 lines) | stat: -rwxr-xr-x 3,304 bytes parent folder | download
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")