info_json_encoder.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  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 default(self, obj):
  16. """Fix certain objects that don't encode.
  17. """
  18. if isinstance(obj, Decimal):
  19. if obj == int(obj):
  20. return int(obj)
  21. return float(obj)
  22. return json.JSONEncoder.default(self, obj)
  23. def encode(self, obj):
  24. """Encode JSON objects for QMK.
  25. """
  26. if isinstance(obj, Decimal):
  27. if obj == int(obj): # I can't believe Decimal objects don't have .is_integer()
  28. return int(obj)
  29. return float(obj)
  30. elif isinstance(obj, (list, tuple)):
  31. if self._primitives_only(obj):
  32. return "[" + ", ".join(self.encode(element) for element in obj) + "]"
  33. else:
  34. self.indentation_level += 1
  35. output = [self.indent_str + self.encode(element) for element in obj]
  36. self.indentation_level -= 1
  37. return "[\n" + ",\n".join(output) + "\n" + self.indent_str + "]"
  38. elif isinstance(obj, dict):
  39. if obj:
  40. if self.indentation_level == 4:
  41. # These are part of a layout, put them on a single line.
  42. return "{ " + ", ".join(f"{self.encode(key)}: {self.encode(element)}" for key, element in sorted(obj.items())) + " }"
  43. else:
  44. self.indentation_level += 1
  45. output = [self.indent_str + f"{json.dumps(key)}: {self.encode(value)}" for key, value in sorted(obj.items(), key=self.sort_root_dict)]
  46. self.indentation_level -= 1
  47. return "{\n" + ",\n".join(output) + "\n" + self.indent_str + "}"
  48. else:
  49. return "{}"
  50. else:
  51. return super().encode(obj)
  52. def _primitives_only(self, obj):
  53. """Returns true if the object doesn't have any container type objects (list, tuple, dict).
  54. """
  55. if isinstance(obj, dict):
  56. obj = obj.values()
  57. return not any(isinstance(element, self.container_types) for element in obj)
  58. def sort_root_dict(self, key):
  59. """Forces layout to the back of the sort order.
  60. """
  61. key = key[0]
  62. if self.indentation_level == 1:
  63. if key == 'manufacturer':
  64. return '10keyboard_name'
  65. elif key == 'keyboard_name':
  66. return '11keyboard_name'
  67. elif key == 'maintainer':
  68. return '12maintainer'
  69. elif key in ('height', 'width'):
  70. return '40' + str(key)
  71. elif key == 'community_layouts':
  72. return '97community_layouts'
  73. elif key == 'layout_aliases':
  74. return '98layout_aliases'
  75. elif key == 'layouts':
  76. return '99layouts'
  77. else:
  78. return '50' + str(key)
  79. return key
  80. @property
  81. def indent_str(self):
  82. return self.indentation_char * (self.indentation_level * self.indent)