diff options
Diffstat (limited to 'lib/python/qmk/json_encoders.py')
| -rwxr-xr-x | lib/python/qmk/json_encoders.py | 192 | 
1 files changed, 192 insertions, 0 deletions
| diff --git a/lib/python/qmk/json_encoders.py b/lib/python/qmk/json_encoders.py new file mode 100755 index 0000000000..9f3da022b4 --- /dev/null +++ b/lib/python/qmk/json_encoders.py @@ -0,0 +1,192 @@ +"""Class that pretty-prints QMK info.json files. +""" +import json +from decimal import Decimal + +newline = '\n' + + +class QMKJSONEncoder(json.JSONEncoder): +    """Base class for all QMK JSON encoders. +    """ +    container_types = (list, tuple, dict) +    indentation_char = " " + +    def __init__(self, *args, **kwargs): +        super().__init__(*args, **kwargs) +        self.indentation_level = 0 + +        if not self.indent: +            self.indent = 4 + +    def encode_decimal(self, obj): +        """Encode a decimal object. +        """ +        if obj == int(obj):  # I can't believe Decimal objects don't have .is_integer() +            return int(obj) + +        return float(obj) + +    def encode_list(self, obj): +        """Encode a list-like object. +        """ +        if self.primitives_only(obj): +            return "[" + ", ".join(self.encode(element) for element in obj) + "]" + +        else: +            self.indentation_level += 1 +            output = [self.indent_str + self.encode(element) for element in obj] +            self.indentation_level -= 1 + +            return "[\n" + ",\n".join(output) + "\n" + self.indent_str + "]" + +    def encode(self, obj): +        """Encode keymap.json objects for QMK. +        """ +        if isinstance(obj, Decimal): +            return self.encode_decimal(obj) + +        elif isinstance(obj, (list, tuple)): +            return self.encode_list(obj) + +        elif isinstance(obj, dict): +            return self.encode_dict(obj) + +        else: +            return super().encode(obj) + +    def primitives_only(self, obj): +        """Returns true if the object doesn't have any container type objects (list, tuple, dict). +        """ +        if isinstance(obj, dict): +            obj = obj.values() + +        return not any(isinstance(element, self.container_types) for element in obj) + +    @property +    def indent_str(self): +        return self.indentation_char * (self.indentation_level * self.indent) + + +class InfoJSONEncoder(QMKJSONEncoder): +    """Custom encoder to make info.json's a little nicer to work with. +    """ +    def encode_dict(self, obj): +        """Encode info.json dictionaries. +        """ +        if obj: +            if self.indentation_level == 4: +                # These are part of a layout, put them on a single line. +                return "{ " + ", ".join(f"{self.encode(key)}: {self.encode(element)}" for key, element in sorted(obj.items())) + " }" + +            else: +                self.indentation_level += 1 +                output = [self.indent_str + f"{json.dumps(key)}: {self.encode(value)}" for key, value in sorted(obj.items(), key=self.sort_dict)] +                self.indentation_level -= 1 +                return "{\n" + ",\n".join(output) + "\n" + self.indent_str + "}" +        else: +            return "{}" + +    def sort_dict(self, key): +        """Forces layout to the back of the sort order. +        """ +        key = key[0] + +        if self.indentation_level == 1: +            if key == 'manufacturer': +                return '10keyboard_name' + +            elif key == 'keyboard_name': +                return '11keyboard_name' + +            elif key == 'maintainer': +                return '12maintainer' + +            elif key in ('height', 'width'): +                return '40' + str(key) + +            elif key == 'community_layouts': +                return '97community_layouts' + +            elif key == 'layout_aliases': +                return '98layout_aliases' + +            elif key == 'layouts': +                return '99layouts' + +            else: +                return '50' + str(key) + +        return key + + +class KeymapJSONEncoder(QMKJSONEncoder): +    """Custom encoder to make keymap.json's a little nicer to work with. +    """ +    def encode_dict(self, obj): +        """Encode dictionary objects for keymap.json. +        """ +        if obj: +            self.indentation_level += 1 +            output_lines = [f"{self.indent_str}{json.dumps(key)}: {self.encode(value)}" for key, value in sorted(obj.items(), key=self.sort_dict)] +            output = ',\n'.join(output_lines) +            self.indentation_level -= 1 + +            return f"{{\n{output}\n{self.indent_str}}}" + +        else: +            return "{}" + +    def encode_list(self, obj): +        """Encode a list-like object. +        """ +        if self.indentation_level == 2: +            indent_level = self.indentation_level + 1 +            # We have a list of keycodes +            layer = [[]] + +            for key in obj: +                if key == 'JSON_NEWLINE': +                    layer.append([]) +                else: +                    layer[-1].append(f'"{key}"') + +            layer = [f"{self.indent_str*indent_level}{', '.join(row)}" for row in layer] + +            return f"{self.indent_str}[\n{newline.join(layer)}\n{self.indent_str*self.indentation_level}]" + +        elif self.primitives_only(obj): +            return "[" + ", ".join(self.encode(element) for element in obj) + "]" + +        else: +            self.indentation_level += 1 +            output = [self.indent_str + self.encode(element) for element in obj] +            self.indentation_level -= 1 + +            return "[\n" + ",\n".join(output) + "\n" + self.indent_str + "]" + +    def sort_dict(self, key): +        """Sorts the hashes in a nice way. +        """ +        key = key[0] + +        if self.indentation_level == 1: +            if key == 'version': +                return '00version' + +            elif key == 'author': +                return '01author' + +            elif key == 'notes': +                return '02notes' + +            elif key == 'layers': +                return '98layers' + +            elif key == 'documentation': +                return '99documentation' + +            else: +                return '50' + str(key) + +        return key | 
