You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

117 lines
2.9 KiB

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