summaryrefslogtreecommitdiff
path: root/Meta/lint-keymaps.py
blob: a22258c0ce7cb3c05ab1c578c16f8ff0a057d9cd (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
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#!/usr/bin/env python3

import json
import os
import sys

PERMITTED_MAPS = ['map', 'shift_map', 'alt_map', 'altgr_map', 'shift_altgr_map']
REQUIRED_MAPS = ['map', 'shift_map', 'alt_map']
# See Userland/Libraries/LibKeyboard/CharacterMapFile.cpp
# and Userland/Libraries/LibKeyboard/CharacterMap.cpp.
GOOD_MAP_LENGTHS = {90, 128}


def report(filename, problem):
    """Print a lint problem to stdout.

    Args:
        filename (str): keymap file name
        problem (str): problem message
    """
    print('{}: {}'.format(filename, problem))


def validate_single_map(filename, mapname, values):
    """Validate a key map.

    Args:
        filename (str): keymap file name
        mapname (str): map name (altgr_map, alt_map, shift_altgr_map)
        values (list): key values

    Returns:
        bool: key map is valid
    """

    all_good = True

    if not isinstance(values, list):
        report(filename, '"{}" is not an array'.format(mapname))
        return False  # Cannot continue other checks

    if not any(values):
        report(filename, 'no values set in {}'.format(mapname))
        all_good = False

    for i, c in enumerate(values):
        if len(c) > 1:
            report(filename, 'more than one character ("{}") for charmap index {} of {}'.format(c, i, mapname))
            all_good = False

    if len(values) == 0:
        report(filename, 'map {} is empty.'.format(mapname))
        all_good = False

    if len(values) not in GOOD_MAP_LENGTHS:
        report(filename, 'length {} of map {} is suspicious. Off-by-one?'.format(len(values), mapname))
        all_good = False

    return all_good


def validate_fullmap(filename, fullmap):
    """Validate a full key map for all map names (including maps for key modifiers).

    Args:
        filename (str): keymap file name
        fullmap (dict): key mappings

    Returns:
        bool: keymap file contains valid key mappings
    """

    all_good = True

    if not isinstance(fullmap, dict):
        report(filename, 'is not an object')
        return False  # Cannot continue other checks

    for name, map_ in fullmap.items():
        if name not in PERMITTED_MAPS:
            report(filename, 'contains unknown entry {}'.format(name))
            all_good = False

        all_good &= validate_single_map(filename, name, map_)

    for name in REQUIRED_MAPS:
        if name not in fullmap:
            report(filename, 'map {} is missing'.format(name))
            all_good = False

    if 'altgr_map' in fullmap and 'alt_map' in fullmap and fullmap['altgr_map'] == fullmap['alt_map']:
        report(filename, 'altgr_map is identical to alt_map. Remove altgr_map for the same effect.')
        report(filename, '(Or add new characters!)')
        all_good = False

    if 'shift_altgr_map' in fullmap and 'alt_map' in fullmap and fullmap['shift_altgr_map'] == fullmap['alt_map']:
        report(filename, 'shift_altgr_map is identical to alt_map. Remove shift_altgr_map for the same effect.')
        report(filename, '(Or add new characters!)')
        all_good = False

    return all_good


def run_with(filenames):
    """Check list of keymap files for errors.

    Args:
        filenames (list): keymap files to check

    Returns:
        bool: All keymap files are valid
    """

    passed = 0
    for filename in filenames:
        with open(filename, 'r') as fp:
            fullmap = json.load(fp)
        if validate_fullmap(filename, fullmap):
            passed += 1

    print('{} out of {} keymaps passed.'.format(passed, len(filenames)))
    return passed == len(filenames)


def list_files_here():
    """Retrieve a list of all '.json' files in the working directory.

    Returns:
        list: JSON file names
    """

    filelist = []
    for filename in os.listdir():
        if filename.endswith('.json'):
            filelist.append(filename)
        else:
            report(filename, 'weird filename (ignored)')
    # Files are in "filesystem" order. Sort them for slightly more
    # aesthetically pleasing output.
    filelist.sort()
    return filelist


def run_here():
    """Check all keymap files in the working directory for errors.

    Returns:
        bool: All keymap files are valid
    """

    return run_with(list_files_here())


if __name__ == '__main__':
    os.chdir(os.path.dirname(__file__) + "/../Base/res/keymaps/")
    if not run_here():
        sys.exit(1)