json.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. from pathlib import Path
  2. from json import loads, dumps
  3. from typing import Any, Callable, Optional, Union
  4. from .text import Text
  5. from .highlighter import JSONHighlighter, NullHighlighter
  6. class JSON:
  7. """A renderable which pretty prints JSON.
  8. Args:
  9. json (str): JSON encoded data.
  10. indent (Union[None, int, str], optional): Number of characters to indent by. Defaults to 2.
  11. highlight (bool, optional): Enable highlighting. Defaults to True.
  12. skip_keys (bool, optional): Skip keys not of a basic type. Defaults to False.
  13. ensure_ascii (bool, optional): Escape all non-ascii characters. Defaults to False.
  14. check_circular (bool, optional): Check for circular references. Defaults to True.
  15. allow_nan (bool, optional): Allow NaN and Infinity values. Defaults to True.
  16. default (Callable, optional): A callable that converts values that can not be encoded
  17. in to something that can be JSON encoded. Defaults to None.
  18. sort_keys (bool, optional): Sort dictionary keys. Defaults to False.
  19. """
  20. def __init__(
  21. self,
  22. json: str,
  23. indent: Union[None, int, str] = 2,
  24. highlight: bool = True,
  25. skip_keys: bool = False,
  26. ensure_ascii: bool = False,
  27. check_circular: bool = True,
  28. allow_nan: bool = True,
  29. default: Optional[Callable[[Any], Any]] = None,
  30. sort_keys: bool = False,
  31. ) -> None:
  32. data = loads(json)
  33. json = dumps(
  34. data,
  35. indent=indent,
  36. skipkeys=skip_keys,
  37. ensure_ascii=ensure_ascii,
  38. check_circular=check_circular,
  39. allow_nan=allow_nan,
  40. default=default,
  41. sort_keys=sort_keys,
  42. )
  43. highlighter = JSONHighlighter() if highlight else NullHighlighter()
  44. self.text = highlighter(json)
  45. self.text.no_wrap = True
  46. self.text.overflow = None
  47. @classmethod
  48. def from_data(
  49. cls,
  50. data: Any,
  51. indent: Union[None, int, str] = 2,
  52. highlight: bool = True,
  53. skip_keys: bool = False,
  54. ensure_ascii: bool = False,
  55. check_circular: bool = True,
  56. allow_nan: bool = True,
  57. default: Optional[Callable[[Any], Any]] = None,
  58. sort_keys: bool = False,
  59. ) -> "JSON":
  60. """Encodes a JSON object from arbitrary data.
  61. Args:
  62. data (Any): An object that may be encoded in to JSON
  63. indent (Union[None, int, str], optional): Number of characters to indent by. Defaults to 2.
  64. highlight (bool, optional): Enable highlighting. Defaults to True.
  65. default (Callable, optional): Optional callable which will be called for objects that cannot be serialized. Defaults to None.
  66. skip_keys (bool, optional): Skip keys not of a basic type. Defaults to False.
  67. ensure_ascii (bool, optional): Escape all non-ascii characters. Defaults to False.
  68. check_circular (bool, optional): Check for circular references. Defaults to True.
  69. allow_nan (bool, optional): Allow NaN and Infinity values. Defaults to True.
  70. default (Callable, optional): A callable that converts values that can not be encoded
  71. in to something that can be JSON encoded. Defaults to None.
  72. sort_keys (bool, optional): Sort dictionary keys. Defaults to False.
  73. Returns:
  74. JSON: New JSON object from the given data.
  75. """
  76. json_instance: "JSON" = cls.__new__(cls)
  77. json = dumps(
  78. data,
  79. indent=indent,
  80. skipkeys=skip_keys,
  81. ensure_ascii=ensure_ascii,
  82. check_circular=check_circular,
  83. allow_nan=allow_nan,
  84. default=default,
  85. sort_keys=sort_keys,
  86. )
  87. highlighter = JSONHighlighter() if highlight else NullHighlighter()
  88. json_instance.text = highlighter(json)
  89. json_instance.text.no_wrap = True
  90. json_instance.text.overflow = None
  91. return json_instance
  92. def __rich__(self) -> Text:
  93. return self.text
  94. if __name__ == "__main__":
  95. import argparse
  96. import sys
  97. parser = argparse.ArgumentParser(description="Pretty print json")
  98. parser.add_argument(
  99. "path",
  100. metavar="PATH",
  101. help="path to file, or - for stdin",
  102. )
  103. parser.add_argument(
  104. "-i",
  105. "--indent",
  106. metavar="SPACES",
  107. type=int,
  108. help="Number of spaces in an indent",
  109. default=2,
  110. )
  111. args = parser.parse_args()
  112. from pip._vendor.rich.console import Console
  113. console = Console()
  114. error_console = Console(stderr=True)
  115. try:
  116. if args.path == "-":
  117. json_data = sys.stdin.read()
  118. else:
  119. json_data = Path(args.path).read_text()
  120. except Exception as error:
  121. error_console.print(f"Unable to read {args.path!r}; {error}")
  122. sys.exit(-1)
  123. console.print(JSON(json_data, indent=args.indent), soft_wrap=True)