abouttreesummaryrefslogcommitdiff
path: root/check_layout.py
blob: 23481ad2f3a85acfd3cc7d60951c89a4b4234ee0 (plain)
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
import xml.etree.ElementTree as ET
import sys, os

warning_count = 0

KNOWN_NOT_LAYOUT = set([
    "number_row", "numpad", "pin",
    "bottom_row", "settings", "method",
    "greekmath", "numeric", "emoji_bottom_row" ])

def warn(msg):
    global warning_count
    print(msg)
    warning_count += 1

def key_list_str(keys):
    return ", ".join(sorted(list(keys)))

def missing_some_of(keys, symbols, class_name=None):
    if class_name is None:
        class_name = "of [" + ", ".join(symbols) + "]"
    missing = set(symbols).difference(keys)
    if len(missing) > 0 and len(missing) != len(symbols):
        warn("Layout includes some %s but not all, missing: %s" % (
            class_name, key_list_str(missing)))

def missing_required(keys, symbols, msg):
    missing = set(symbols).difference(keys)
    if len(missing) > 0:
        warn("%s, missing: %s" % (msg, key_list_str(missing)))

def unexpected_keys(keys, symbols, msg):
    unexpected = set(symbols).intersection(keys)
    if len(unexpected) > 0:
        warn("%s, unexpected: %s" % (msg, key_list_str(unexpected)))

def parse_layout(fname):
    keys = set()
    dup = set()
    root = ET.parse(fname).getroot()
    if root.tag != "keyboard":
        return None
    for row in root:
        for key in row:
            for attr in key.keys():
                if attr.startswith("key"):
                    k = key.get(attr).removeprefix("\\")
                    if k in keys: dup.add(k)
                    keys.add(k)
    return root, keys, dup

def check_layout(layout):
    root, keys, dup = layout
    if len(dup) > 0: warn("Duplicate keys: " + key_list_str(dup))
    missing_some_of(keys, "~!@#$%^&*(){}`[]=\\-_;:/.,?<>'\"+|", "ASCII punctuation")
    missing_some_of(keys, "0123456789", "digits")
    missing_required(keys,
                     ["esc", "tab", "backspace", "delete",
                      "f11_placeholder", "f12_placeholder"],
                     "Layout doesn't define some important keys")
    unexpected_keys(keys,
                    ["copy", "paste", "cut", "selectAll", "shareText",
                     "pasteAsPlainText", "undo", "redo" ],
                    "Layout contains editing keys")
    unexpected_keys(keys,
                    [ "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9",
                     "f10", "f11", "f12" ],
                    "Layout contains function keys")
    unexpected_keys(keys, [""], "Layout contains empty strings")

    bottom_row_keys = [
            "ctrl", "fn", "switch_numeric", "change_method", "switch_emoji",
            "config", "switch_forward", "switch_backward", "enter", "action",
            "left", "up", "right", "down", "space"
            ]

    if root.get("bottom_row") == "false":
        missing_required(keys, bottom_row_keys,
                         "Layout redefines the bottom row but some important keys are missing")
    else:
        unexpected_keys(keys, bottom_row_keys,
                        "Layout contains keys present in the bottom row")

    if root.get("script") == None:
        warn("Layout doesn't specify a script.")

for fname in sorted(sys.argv[1:]):
    layout_id, _ = os.path.splitext(os.path.basename(fname))
    if layout_id in KNOWN_NOT_LAYOUT:
        continue
    layout = parse_layout(fname)
    if layout == None:
        print("Not a layout file: %s" % layout_id)
    else:
        print("# %s" % layout_id)
        warning_count = 0
        check_layout(layout)
        print("%d warnings" % warning_count)