lint-keymaps.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. #!/usr/bin/env python3
  2. import json
  3. import os
  4. import sys
  5. PERMITTED_MAPS = ['map', 'shift_map', 'alt_map', 'altgr_map', 'shift_altgr_map']
  6. REQUIRED_MAPS = ['map', 'shift_map', 'alt_map']
  7. # See Userland/Libraries/LibKeyboard/CharacterMapFile.cpp
  8. # and Userland/Libraries/LibKeyboard/CharacterMap.cpp.
  9. GOOD_MAP_LENGTHS = {90, 128}
  10. def report(filename, problem):
  11. """Print a lint problem to stdout.
  12. Args:
  13. filename (str): keymap filename
  14. problem (str): problem message
  15. """
  16. print('{}: {}'.format(filename, problem))
  17. def validate_single_map(filename, mapname, values):
  18. """Validate a key map.
  19. Args:
  20. filename (str): keymap filename
  21. mapname (str): map name (altgr_map, alt_map, shift_altgr_map)
  22. values (list): key values
  23. Returns:
  24. bool: key map is valid
  25. """
  26. all_good = True
  27. if not isinstance(values, list):
  28. report(filename, '"{}" is not an array'.format(mapname))
  29. return False # Cannot continue other checks
  30. if not any(values):
  31. report(filename, 'no values set in {}'.format(mapname))
  32. all_good = False
  33. for i, c in enumerate(values):
  34. if len(c) > 1:
  35. report(filename, 'more than one character ("{}") for charmap index {} of {}'.format(c, i, mapname))
  36. all_good = False
  37. if len(values) == 0:
  38. report(filename, 'map {} is empty.'.format(mapname))
  39. all_good = False
  40. if len(values) not in GOOD_MAP_LENGTHS:
  41. report(filename, 'length {} of map {} is suspicious. Off-by-one?'.format(len(values), mapname))
  42. all_good = False
  43. return all_good
  44. def validate_fullmap(filename, fullmap):
  45. """Validate a full key map for all map names (including maps for key modifiers).
  46. Args:
  47. filename (str): keymap filename
  48. fullmap (dict): key mappings
  49. Returns:
  50. bool: keymap file contains valid key mappings
  51. """
  52. all_good = True
  53. if not isinstance(fullmap, dict):
  54. report(filename, 'is not an object')
  55. return False # Cannot continue other checks
  56. for name, map_ in fullmap.items():
  57. if name not in PERMITTED_MAPS:
  58. report(filename, 'contains unknown entry {}'.format(name))
  59. all_good = False
  60. all_good &= validate_single_map(filename, name, map_)
  61. for name in REQUIRED_MAPS:
  62. if name not in fullmap:
  63. report(filename, 'map {} is missing'.format(name))
  64. all_good = False
  65. if 'altgr_map' in fullmap and 'alt_map' in fullmap and fullmap['altgr_map'] == fullmap['alt_map']:
  66. report(filename, 'altgr_map is identical to alt_map. Remove altgr_map for the same effect.')
  67. report(filename, '(Or add new characters!)')
  68. all_good = False
  69. if 'shift_altgr_map' in fullmap and 'alt_map' in fullmap and fullmap['shift_altgr_map'] == fullmap['alt_map']:
  70. report(filename, 'shift_altgr_map is identical to alt_map. Remove shift_altgr_map for the same effect.')
  71. report(filename, '(Or add new characters!)')
  72. all_good = False
  73. return all_good
  74. def run_with(filenames):
  75. """Check list of keymap files for errors.
  76. Args:
  77. filenames (list): keymap files to check
  78. Returns:
  79. bool: All keymap files are valid
  80. """
  81. passed = 0
  82. for filename in filenames:
  83. with open(filename, 'r') as fp:
  84. fullmap = json.load(fp)
  85. if validate_fullmap(filename, fullmap):
  86. passed += 1
  87. print('{} out of {} keymaps passed.'.format(passed, len(filenames)))
  88. return passed == len(filenames)
  89. def list_files_here():
  90. """Retrieve a list of all '.json' files in the working directory.
  91. Returns:
  92. list: JSON filenames
  93. """
  94. filelist = []
  95. for filename in os.listdir():
  96. if filename.endswith('.json'):
  97. filelist.append(filename)
  98. else:
  99. report(filename, 'weird filename (ignored)')
  100. # Files are in "filesystem" order. Sort them for slightly more
  101. # aesthetically pleasing output.
  102. filelist.sort()
  103. return filelist
  104. def run_here():
  105. """Check all keymap files in the working directory for errors.
  106. Returns:
  107. bool: All keymap files are valid
  108. """
  109. return run_with(list_files_here())
  110. if __name__ == '__main__':
  111. os.chdir(os.path.dirname(__file__) + "/../Base/res/keymaps/")
  112. if not run_here():
  113. sys.exit(1)