keyboard stuff
1from pathlib import Path
2
3from qmk.json_schema import merge_ordered_dicts, deep_update, json_load, validate
4
5CONSTANTS_PATH = Path('data/constants/')
6KEYCODES_PATH = CONSTANTS_PATH / 'keycodes'
7EXTRAS_PATH = KEYCODES_PATH / 'extras'
8
9
10def _find_versions(path, prefix):
11 ret = []
12 for file in path.glob(f'{prefix}_[0-9].[0-9].[0-9].hjson'):
13 ret.append(file.stem.split('_')[-1])
14
15 ret.sort(reverse=True)
16 return ret
17
18
19def _potential_search_versions(version, lang=None):
20 versions = list_versions(lang)
21 versions.reverse()
22
23 loc = versions.index(version) + 1
24
25 return versions[:loc]
26
27
28def _search_path(lang=None):
29 return EXTRAS_PATH if lang else KEYCODES_PATH
30
31
32def _search_prefix(lang=None):
33 return f'keycodes_{lang}' if lang else 'keycodes'
34
35
36def _locate_files(path, prefix, versions):
37 # collate files by fragment "type"
38 files = {'_': []}
39 for version in versions:
40 files['_'].append(path / f'{prefix}_{version}.hjson')
41
42 for file in path.glob(f'{prefix}_{version}_*.hjson'):
43 fragment = file.stem.replace(f'{prefix}_{version}_', '')
44 if fragment not in files:
45 files[fragment] = []
46 files[fragment].append(file)
47
48 return files
49
50
51def _process_files(files):
52 # allow override within types of fragments - but not globally
53 spec = {}
54 for category in files.values():
55 specs = []
56 for file in category:
57 specs.append(json_load(file))
58
59 deep_update(spec, merge_ordered_dicts(specs))
60
61 return spec
62
63
64def _validate(spec):
65 # first throw it to the jsonschema
66 validate(spec, 'qmk.keycodes.v1')
67
68 # no duplicate keycodes
69 keycodes = []
70 for value in spec['keycodes'].values():
71 keycodes.append(value['key'])
72 keycodes.extend(value.get('aliases', []))
73 duplicates = set([x for x in keycodes if keycodes.count(x) > 1])
74 if duplicates:
75 raise ValueError(f'Keycode spec contains duplicate keycodes! ({",".join(duplicates)})')
76
77
78def load_spec(version, lang=None):
79 """Build keycode data from the requested spec file
80 """
81 if version == 'latest':
82 version = list_versions(lang)[0]
83
84 path = _search_path(lang)
85 prefix = _search_prefix(lang)
86 versions = _potential_search_versions(version, lang)
87
88 # Load bases + any fragments
89 spec = _process_files(_locate_files(path, prefix, versions))
90
91 # Sort?
92 spec['version'] = version
93 spec['keycodes'] = dict(sorted(spec.get('keycodes', {}).items()))
94 spec['ranges'] = dict(sorted(spec.get('ranges', {}).items()))
95
96 # Validate?
97 _validate(spec)
98
99 return spec
100
101
102def list_versions(lang=None):
103 """Return available versions - sorted newest first
104 """
105 path = _search_path(lang)
106 prefix = _search_prefix(lang)
107
108 return _find_versions(path, prefix)
109
110
111def list_languages():
112 """Return available languages
113 """
114 ret = set()
115 for file in EXTRAS_PATH.glob('keycodes_*_[0-9].[0-9].[0-9].hjson'):
116 ret.add(file.stem.split('_')[1])
117
118 return ret