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.

96 lines
3.1 KiB

  1. """Class that pretty-prints QMK info.json files.
  2. """
  3. import json
  4. from decimal import Decimal
  5. class InfoJSONEncoder(json.JSONEncoder):
  6. """Custom encoder to make info.json's a little nicer to work with.
  7. """
  8. container_types = (list, tuple, dict)
  9. indentation_char = " "
  10. def __init__(self, *args, **kwargs):
  11. super().__init__(*args, **kwargs)
  12. self.indentation_level = 0
  13. if not self.indent:
  14. self.indent = 4
  15. def encode(self, obj):
  16. """Encode JSON objects for QMK.
  17. """
  18. if isinstance(obj, Decimal):
  19. if obj == int(obj): # I can't believe Decimal objects don't have .is_integer()
  20. return int(obj)
  21. return float(obj)
  22. elif isinstance(obj, (list, tuple)):
  23. if self._primitives_only(obj):
  24. return "[" + ", ".join(self.encode(element) for element in obj) + "]"
  25. else:
  26. self.indentation_level += 1
  27. output = [self.indent_str + self.encode(element) for element in obj]
  28. self.indentation_level -= 1
  29. return "[\n" + ",\n".join(output) + "\n" + self.indent_str + "]"
  30. elif isinstance(obj, dict):
  31. if obj:
  32. if self.indentation_level == 4:
  33. # These are part of a layout, put them on a single line.
  34. return "{ " + ", ".join(f"{self.encode(key)}: {self.encode(element)}" for key, element in sorted(obj.items())) + " }"
  35. else:
  36. self.indentation_level += 1
  37. output = [self.indent_str + f"{json.dumps(key)}: {self.encode(value)}" for key, value in sorted(obj.items(), key=self.sort_root_dict)]
  38. self.indentation_level -= 1
  39. return "{\n" + ",\n".join(output) + "\n" + self.indent_str + "}"
  40. else:
  41. return "{}"
  42. else:
  43. return super().encode(obj)
  44. def _primitives_only(self, obj):
  45. """Returns true if the object doesn't have any container type objects (list, tuple, dict).
  46. """
  47. if isinstance(obj, dict):
  48. obj = obj.values()
  49. return not any(isinstance(element, self.container_types) for element in obj)
  50. def sort_root_dict(self, key):
  51. """Forces layout to the back of the sort order.
  52. """
  53. key = key[0]
  54. if self.indentation_level == 1:
  55. if key == 'manufacturer':
  56. return '10keyboard_name'
  57. elif key == 'keyboard_name':
  58. return '11keyboard_name'
  59. elif key == 'maintainer':
  60. return '12maintainer'
  61. elif key in ('height', 'width'):
  62. return '40' + str(key)
  63. elif key == 'community_layouts':
  64. return '97community_layouts'
  65. elif key == 'layout_aliases':
  66. return '98layout_aliases'
  67. elif key == 'layouts':
  68. return '99layouts'
  69. else:
  70. return '50' + str(key)
  71. return key
  72. @property
  73. def indent_str(self):
  74. return self.indentation_char * (self.indentation_level * self.indent)