Skip to content

Commit

Permalink
Merge pull request #1444 from willmcgugan/fixes-10.8.0
Browse files Browse the repository at this point in the history
added print_json
  • Loading branch information
willmcgugan committed Aug 28, 2021
2 parents 69ea180 + 6910b7e commit 4cbfd37
Show file tree
Hide file tree
Showing 11 changed files with 155 additions and 2 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +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).

## [Unreleased]
## [10.8.0] - 2020-08-28

### Added

- Added Panel.subtitle
- Added Panel.subtitle_align

### Fixed

- Fixed a bug where calling `rich.reconfigure` within a `pytest_configure` hook would lead to a crash
- Fixed highlight not being passed through options https://github.com/willmcgugan/rich/issues/1404

## [10.7.0] - 2021-08-05

Expand Down
23 changes: 23 additions & 0 deletions docs/source/console.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,28 @@ The :meth:`~rich.console.Console.log` methods offers the same capabilities as pr

To help with debugging, the log() method has a ``log_locals`` parameter. If you set this to ``True``, Rich will display a table of local variables where the method was called.


Printing JSON
-------------

The :meth:`~rich.console.Console.print_json` method will pretty print (format and style) a string containing JSON. Here's a short example::

console.print_json('[false, true, null, "foo"]')

You can also *log* json by logging a :class:`~rich.json.JSON` object::

from rich.json import JSON
console.log(JSON('["foo", "bar"]'))

Because printing JSON is a common requirement, you may import ``print_json`` from the main namespace::

from rich import print_json

You can also pretty print JSON via the command line with the following::

python -m rich.json cats.json


Low level output
----------------

Expand All @@ -78,6 +100,7 @@ Here's an example::

>>> console.out("Locals", locals())


Rules
-----

Expand Down
7 changes: 7 additions & 0 deletions docs/source/reference/json.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
rich.json
=========

.. automodule:: rich.json
:members:


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.7.0"
version = "10.8.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
12 changes: 12 additions & 0 deletions rich/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,18 @@ def print(
return write_console.print(*objects, sep=sep, end=end)


def print_json(json: str, *, indent: int = 4, highlight: bool = True) -> None:
"""Pretty prints JSON. Output will be valid JSON.
Args:
json (str): A string containing JSON.
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)


def inspect(
obj: Any,
*,
Expand Down
14 changes: 14 additions & 0 deletions rich/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -1583,6 +1583,7 @@ def print(
height=height,
no_wrap=no_wrap,
markup=markup,
highlight=highlight,
)

new_segments: List[Segment] = []
Expand Down Expand Up @@ -1613,6 +1614,19 @@ def print(
else:
self._buffer.extend(new_segments)

def print_json(self, json: str, *, indent: int = 4, highlight: bool = True) -> None:
"""Pretty prints JSON. Output will be valid JSON.
Args:
json (str): A string containing JSON.
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)
self.print(json_renderable)

def update_screen(
self,
renderable: RenderableType,
Expand Down
6 changes: 6 additions & 0 deletions rich/default_styles.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@
"repr.filename": Style(color="bright_magenta"),
"rule.line": Style(color="bright_green"),
"rule.text": Style.null(),
"json.brace": Style(bold=True),
"json.bool_true": Style(color="bright_green", italic=True),
"json.bool_false": Style(color="bright_red", italic=True),
"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),
"prompt": Style.null(),
"prompt.choices": Style(color="magenta", bold=True),
"prompt.default": Style(color="cyan", bold=True),
Expand Down
14 changes: 14 additions & 0 deletions rich/highlighter.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,20 @@ class ReprHighlighter(RegexHighlighter):
]


class JSONHighlighter(RegexHighlighter):
"""Highlights JSON"""

base_style = "json."
highlights = [
_combine_regex(
r"(?P<brace>[\{\[\(\)\]\}])",
r"\b(?P<bool_true>true)\b|\b(?P<bool_false>false)\b|\b(?P<null>null)\b",
r"(?P<number>(?<!\w)\-?[0-9]+\.?[0-9]*(e[\-\+]?\d+?)?\b|0x[0-9a-fA-F]*)",
r"(?<![\\\w])(?P<str>b?\".*?(?<!\\)\")",
),
]


if __name__ == "__main__": # pragma: no cover
from .console import Console

Expand Down
53 changes: 53 additions & 0 deletions rich/json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from json import loads, dumps

from .text import Text
from .highlighter import JSONHighlighter, NullHighlighter


class JSON:
"""A rebderable which pretty prints JSON.
Args:
json (str): JSON encoded data.
indent (int, optional): Number of characters to indent by. Defaults to True.
highlight (bool, optional): Enable highlighting. Defaults to True.
"""

def __init__(self, json: str, indent: int = 4, 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

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


if __name__ == "__main__":

import argparse
import sys

parser = argparse.ArgumentParser(description="Pretty print json")
parser.add_argument(
"path",
metavar="PATH",
help="path to file, or - for stdin",
)
args = parser.parse_args()

from rich.console import Console

console = Console()
error_console = Console(stderr=True)

try:
with open(args.path, "rt") as json_file:
json_data = json_file.read()
except Exception as error:
error_console.print(f"Unable to read {args.path!r}; {error}")
sys.exit(-1)

console.print(JSON(json_data), soft_wrap=True)
9 changes: 9 additions & 0 deletions tests/test_console.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,15 @@ def test_print():
assert console.file.getvalue() == "foo\n"


def test_print_json():
console = Console(file=io.StringIO(), color_system="truecolor")
console.print_json('[false, true, null, "foo"]')
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_log():
console = Console(
file=io.StringIO(),
Expand Down
10 changes: 10 additions & 0 deletions tests/test_rich_print.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ def test_rich_print():
console.file = backup_file


def test_rich_print_json():
console = rich.get_console()
with console.capture() as capture:
rich.print_json('[false, true, null, "foo"]')
result = capture.get()
print(repr(result))
expected = '[\n false,\n true,\n null,\n "foo"\n]\n'
assert result == expected


def test_rich_print_X():
console = rich.get_console()
output = io.StringIO()
Expand Down

0 comments on commit 4cbfd37

Please sign in to comment.