at master 3.0 kB view raw
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