mirror of
https://github.com/BrianPugh/game-and-watch-patch.git
synced 2025-12-16 07:16:26 +01:00
109 lines
3.7 KiB
Python
109 lines
3.7 KiB
Python
#!/usr/bin/env python3
|
|
"""From https://gist.github.com/jannismain/e96666ca4f059c3e5bc28abb711b5c92"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
|
|
class CompactJSONEncoder(json.JSONEncoder):
|
|
"""A JSON Encoder that puts small containers on single lines."""
|
|
|
|
CONTAINER_TYPES = (list, tuple, dict)
|
|
"""Container datatypes include primitives or other containers."""
|
|
|
|
MAX_WIDTH = 80
|
|
"""Maximum width of a container that might be put on a single line."""
|
|
|
|
MAX_ITEMS = 10
|
|
"""Maximum number of items in container that might be put on single line."""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
# using this class without indentation is pointless
|
|
if kwargs.get("indent") is None:
|
|
kwargs["indent"] = 4
|
|
super().__init__(*args, **kwargs)
|
|
self.indentation_level = 0
|
|
|
|
def encode(self, o):
|
|
"""Encode JSON object *o* with respect to single line lists."""
|
|
if isinstance(o, (list, tuple)):
|
|
return self._encode_list(o)
|
|
if isinstance(o, dict):
|
|
return self._encode_object(o)
|
|
if isinstance(o, float): # Use scientific notation for floats
|
|
return format(o, "g")
|
|
return json.dumps(
|
|
o,
|
|
skipkeys=self.skipkeys,
|
|
ensure_ascii=self.ensure_ascii,
|
|
check_circular=self.check_circular,
|
|
allow_nan=self.allow_nan,
|
|
sort_keys=self.sort_keys,
|
|
indent=self.indent,
|
|
separators=(self.item_separator, self.key_separator),
|
|
default=self.default if hasattr(self, "default") else None,
|
|
)
|
|
|
|
def _encode_list(self, o):
|
|
if self._put_on_single_line(o):
|
|
return "[" + ", ".join(self.encode(el) for el in o) + "]"
|
|
self.indentation_level += 1
|
|
output = [self.indent_str + self.encode(el) for el in o]
|
|
self.indentation_level -= 1
|
|
return "[\n" + ",\n".join(output) + "\n" + self.indent_str + "]"
|
|
|
|
def _encode_object(self, o):
|
|
if not o:
|
|
return "{}"
|
|
|
|
# ensure keys are converted to strings
|
|
o = {str(k) if k is not None else "null": v for k, v in o.items()}
|
|
|
|
if self.sort_keys:
|
|
o = dict(sorted(o.items(), key=lambda x: x[0]))
|
|
|
|
if self._put_on_single_line(o):
|
|
return (
|
|
"{ "
|
|
+ ", ".join(
|
|
f"{json.dumps(k)}: {self.encode(el)}" for k, el in o.items()
|
|
)
|
|
+ " }"
|
|
)
|
|
|
|
self.indentation_level += 1
|
|
output = [
|
|
f"{self.indent_str}{json.dumps(k)}: {self.encode(v)}" for k, v in o.items()
|
|
]
|
|
self.indentation_level -= 1
|
|
|
|
return "{\n" + ",\n".join(output) + "\n" + self.indent_str + "}"
|
|
|
|
def iterencode(self, o, **kwargs):
|
|
"""Required to also work with `json.dump`."""
|
|
return self.encode(o)
|
|
|
|
def _put_on_single_line(self, o):
|
|
return (
|
|
self._primitives_only(o)
|
|
and len(o) <= self.MAX_ITEMS
|
|
and len(str(o)) - 2 <= self.MAX_WIDTH
|
|
)
|
|
|
|
def _primitives_only(self, o: list | tuple | dict):
|
|
if isinstance(o, (list, tuple)):
|
|
return not any(isinstance(el, self.CONTAINER_TYPES) for el in o)
|
|
elif isinstance(o, dict):
|
|
return not any(isinstance(el, self.CONTAINER_TYPES) for el in o.values())
|
|
|
|
@property
|
|
def indent_str(self) -> str:
|
|
if isinstance(self.indent, int):
|
|
return " " * (self.indentation_level * self.indent)
|
|
elif isinstance(self.indent, str):
|
|
return self.indentation_level * self.indent
|
|
else:
|
|
raise ValueError(
|
|
f"indent must either be of type int or str (is: {type(self.indent)})"
|
|
)
|