Skip to content

Commit

Permalink
Merge pull request #1445 from willmcgugan/json-color
Browse files Browse the repository at this point in the history
Json color
  • Loading branch information
willmcgugan committed Aug 29, 2021
2 parents 6dea513 + 4c34483 commit 1b12bb6
Show file tree
Hide file tree
Showing 9 changed files with 89 additions and 12 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [10.9.0] - 2020-08-29

### Added

- Added data parameter to print_json method / function

### Changed

- Changed default indent of JSON to 2 (down from 4)
- Changed highlighting of JSON keys to new style (bold blue)

## [10.8.0] - 2020-08-28

### Added
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "rich"
homepage = "https://github.com/willmcgugan/rich"
documentation = "https://rich.readthedocs.io/en/latest/"
version = "10.8.0"
version = "10.9.0"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
authors = ["Will McGugan <willmcgugan@gmail.com>"]
license = "MIT"
Expand Down
15 changes: 11 additions & 4 deletions rich/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def print(
sep: str = " ",
end: str = "\n",
file: Optional[IO[str]] = None,
flush: bool = False
flush: bool = False,
) -> None:
r"""Print object(s) supplied via positional arguments.
This function has an identical signature to the built-in print.
Expand All @@ -69,16 +69,23 @@ def print(
return write_console.print(*objects, sep=sep, end=end)


def print_json(json: str, *, indent: int = 4, highlight: bool = True) -> None:
def print_json(
json: Optional[str] = None,
*,
data: Any = None,
indent: int = 2,
highlight: bool = True,
) -> None:
"""Pretty prints JSON. Output will be valid JSON.
Args:
json (str): A string containing JSON.
data (Any): If not json is supplied, then encode this data.
indent (int, optional): Number of spaces to indent. Defaults to 4.
highlight (bool, optional): Enable highlighting of output: Defaults to True.
"""

get_console().print_json(json, indent=indent, highlight=highlight)
get_console().print_json(json, data=data, indent=indent, highlight=highlight)


def inspect(
Expand All @@ -93,7 +100,7 @@ def inspect(
dunder: bool = False,
sort: bool = True,
all: bool = False,
value: bool = True
value: bool = True,
) -> None:
"""Inspect any Python object.
Expand Down
21 changes: 18 additions & 3 deletions rich/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -1614,17 +1614,32 @@ def print(
else:
self._buffer.extend(new_segments)

def print_json(self, json: str, *, indent: int = 4, highlight: bool = True) -> None:
def print_json(
self,
json: Optional[str] = None,
*,
data: Any = None,
indent: int = 2,
highlight: bool = True,
) -> None:
"""Pretty prints JSON. Output will be valid JSON.
Args:
json (str): A string containing JSON.
json (Optional[str]): A string containing JSON.
data (Any): If not json is supplied, then encode this data.
indent (int, optional): Number of spaces to indent. Defaults to 4.
highlight (bool, optional): Enable highlighting of output: Defaults to True.
"""
from rich.json import JSON

json_renderable = JSON(json, indent=indent, highlight=highlight)
if json is None:
json_renderable = JSON.from_data(data, indent=indent, highlight=highlight)
else:
if not isinstance(json, str):
raise TypeError(
f"json must be str. Did you mean print_json(data={json!r}) ?"
)
json_renderable = JSON(json, indent=indent, highlight=highlight)
self.print(json_renderable)

def update_screen(
Expand Down
1 change: 1 addition & 0 deletions rich/default_styles.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
"json.null": Style(color="magenta", italic=True),
"json.number": Style(color="cyan", bold=True, italic=False),
"json.str": Style(color="green", italic=False, bold=False),
"json.key": Style(color="blue", bold=True),
"prompt": Style.null(),
"prompt.choices": Style(color="magenta", bold=True),
"prompt.default": Style(color="cyan", bold=True),
Expand Down
1 change: 1 addition & 0 deletions rich/highlighter.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ class JSONHighlighter(RegexHighlighter):
r"(?P<number>(?<!\w)\-?[0-9]+\.?[0-9]*(e[\-\+]?\d+?)?\b|0x[0-9a-fA-F]*)",
r"(?<![\\\w])(?P<str>b?\".*?(?<!\\)\")",
),
r"(?<![\\\w])(?P<key>b?\".*?(?<!\\)\")\:",
]


Expand Down
31 changes: 29 additions & 2 deletions rich/json.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from json import loads, dumps
from typing import Any

from .text import Text
from .highlighter import JSONHighlighter, NullHighlighter
Expand All @@ -13,14 +14,32 @@ class JSON:
highlight (bool, optional): Enable highlighting. Defaults to True.
"""

def __init__(self, json: str, indent: int = 4, highlight: bool = True) -> None:
def __init__(self, json: str, indent: int = 2, highlight: bool = True) -> None:
data = loads(json)
json = dumps(data, indent=indent)
highlighter = JSONHighlighter() if highlight else NullHighlighter()
self.text = highlighter(json)
self.text.no_wrap = True
self.text.overflow = None

@classmethod
def from_data(cls, data: Any, indent: int = 2, highlight: bool = True) -> "JSON":
"""Encodes a JSON object from arbitrary data.
Returns:
Args:
data (Any): An object that may be encoded in to JSON
indent (int, optional): Number of characters to indent by. Defaults to True.
highlight (bool, optional): Enable highlighting. Defaults to True.
"""
json_instance: "JSON" = cls.__new__(cls)
json = dumps(data, indent=indent)
highlighter = JSONHighlighter() if highlight else NullHighlighter()
json_instance.text = highlighter(json)
json_instance.text.no_wrap = True
json_instance.text.overflow = None
return json_instance

def __rich__(self) -> Text:
return self.text

Expand All @@ -36,6 +55,14 @@ def __rich__(self) -> Text:
metavar="PATH",
help="path to file, or - for stdin",
)
parser.add_argument(
"-i",
"--indent",
metavar="SPACES",
type=int,
help="Number of spaces in an indent",
default=2,
)
args = parser.parse_args()

from rich.console import Console
Expand All @@ -50,4 +77,4 @@ def __rich__(self) -> Text:
error_console.print(f"Unable to read {args.path!r}; {error}")
sys.exit(-1)

console.print(JSON(json_data), soft_wrap=True)
console.print(JSON(json_data, indent=args.indent), soft_wrap=True)
17 changes: 16 additions & 1 deletion tests/test_console.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,22 @@ def test_print():

def test_print_json():
console = Console(file=io.StringIO(), color_system="truecolor")
console.print_json('[false, true, null, "foo"]')
console.print_json('[false, true, null, "foo"]', indent=4)
result = console.file.getvalue()
print(repr(result))
expected = '\x1b[1m[\x1b[0m\n \x1b[3;91mfalse\x1b[0m,\n \x1b[3;92mtrue\x1b[0m,\n \x1b[3;35mnull\x1b[0m,\n \x1b[32m"foo"\x1b[0m\n\x1b[1m]\x1b[0m\n'
assert result == expected


def test_print_json_error():
console = Console(file=io.StringIO(), color_system="truecolor")
with pytest.raises(TypeError):
console.print_json(["foo"], indent=4)


def test_print_json_data():
console = Console(file=io.StringIO(), color_system="truecolor")
console.print_json(data=[False, True, None, "foo"], indent=4)
result = console.file.getvalue()
print(repr(result))
expected = '\x1b[1m[\x1b[0m\n \x1b[3;91mfalse\x1b[0m,\n \x1b[3;92mtrue\x1b[0m,\n \x1b[3;35mnull\x1b[0m,\n \x1b[32m"foo"\x1b[0m\n\x1b[1m]\x1b[0m\n'
Expand Down
2 changes: 1 addition & 1 deletion tests/test_rich_print.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def test_rich_print():
def test_rich_print_json():
console = rich.get_console()
with console.capture() as capture:
rich.print_json('[false, true, null, "foo"]')
rich.print_json('[false, true, null, "foo"]', indent=4)
result = capture.get()
print(repr(result))
expected = '[\n false,\n true,\n null,\n "foo"\n]\n'
Expand Down

0 comments on commit 1b12bb6

Please sign in to comment.