qmk_firmware

QMK firmware for my keyboards (Corne, Sweep Ferris) and trackball (Ploopy Adept)
Log | Files | Refs | Submodules | LICENSE

keycodes.py (3048B)


      1 from pathlib import Path
      2 
      3 from qmk.json_schema import merge_ordered_dicts, deep_update, json_load, validate
      4 
      5 CONSTANTS_PATH = Path('data/constants/')
      6 KEYCODES_PATH = CONSTANTS_PATH / 'keycodes'
      7 EXTRAS_PATH = KEYCODES_PATH / 'extras'
      8 
      9 
     10 def _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 
     19 def _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 
     28 def _search_path(lang=None):
     29     return EXTRAS_PATH if lang else KEYCODES_PATH
     30 
     31 
     32 def _search_prefix(lang=None):
     33     return f'keycodes_{lang}' if lang else 'keycodes'
     34 
     35 
     36 def _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 
     51 def _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 
     64 def _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 
     78 def 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 
    102 def 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 
    111 def 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