Skip to content
2 changes: 2 additions & 0 deletions rich/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -2209,3 +2209,5 @@ def save_html(
}
)
console.log("foo")

console.print_json(data={"name": "apple", "count": 1}, indent=None)
32 changes: 29 additions & 3 deletions rich/highlighter.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import re
import string
from abc import ABC, abstractmethod
from typing import List, Union

from .text import Text
from .text import Span, Text


def _combine_regex(*regexes: str) -> str:
Expand Down Expand Up @@ -104,17 +106,38 @@ class ReprHighlighter(RegexHighlighter):
class JSONHighlighter(RegexHighlighter):
"""Highlights JSON"""

# Captures the start and end of JSON strings, handling escaped quotes
JSON_STR = r"(?<![\\\w])(?P<str>b?\".*?(?<!\\)\")"

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?\".*?(?<!\\)\")",
JSON_STR,
),
r"(?<![\\\w])(?P<key>b?\".*?(?<!\\)\")\:",
]

def highlight(self, text: Text) -> None:
super().highlight(text)

# Additional work to handle highlighting JSON keys
plain = text.plain
append = text.spans.append
for match in re.finditer(self.JSON_STR, plain):
start, end = match.span()
cursor = end
while cursor < len(plain):
char = plain[cursor]
cursor += 1
if char in string.whitespace:
continue
elif char == ":":
append(Span(start, end, "json.key"))
else:
break


if __name__ == "__main__": # pragma: no cover
from .console import Console
Expand Down Expand Up @@ -145,3 +168,6 @@ class JSONHighlighter(RegexHighlighter):
console.print(
"127.0.1.1 bar 192.168.1.4 2001:0db8:85a3:0000:0000:8a2e:0370:7334 foo"
)
import json

console.print_json(json.dumps(obj={"name": "apple", "count": 1}), indent=None)
11 changes: 10 additions & 1 deletion tests/test_console.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
Console,
ConsoleDimensions,
ConsoleOptions,
group,
ScreenUpdate,
group,
)
from rich.control import Control
from rich.measure import measure_renderables
Expand Down Expand Up @@ -185,6 +185,15 @@ def test_print_json_ensure_ascii():
assert result == expected


def test_print_json_indent_none():
console = Console(file=io.StringIO(), color_system="truecolor")
data = {"name": "apple", "count": 1}
console.print_json(data=data, indent=None)
result = console.file.getvalue()
expected = '\x1b[1m{\x1b[0m\x1b[1;34m"name"\x1b[0m: \x1b[32m"apple"\x1b[0m, \x1b[1;34m"count"\x1b[0m: \x1b[1;36m1\x1b[0m\x1b[1m}\x1b[0m\n'
assert result == expected


def test_log():
console = Console(
file=io.StringIO(),
Expand Down
40 changes: 38 additions & 2 deletions tests/test_highlighter.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""Tests for the highlighter classes."""
import pytest
import json
from typing import List

from rich.highlighter import NullHighlighter, ReprHighlighter
import pytest

from rich.highlighter import JSONHighlighter, NullHighlighter, ReprHighlighter
from rich.text import Span, Text


Expand Down Expand Up @@ -92,3 +94,37 @@ def test_highlight_regex(test: str, spans: List[Span]):
highlighter.highlight(text)
print(text.spans)
assert text.spans == spans


def test_highlight_json_with_indent():
json_string = json.dumps({"name": "apple", "count": 1}, indent=4)
text = Text(json_string)
highlighter = JSONHighlighter()
highlighter.highlight(text)
assert text.spans == [
Span(0, 1, "json.brace"),
Span(6, 12, "json.str"),
Span(14, 21, "json.str"),
Span(27, 34, "json.str"),
Span(36, 37, "json.number"),
Span(38, 39, "json.brace"),
Span(6, 12, "json.key"),
Span(27, 34, "json.key"),
]


def test_highlight_json_no_indent():
json_string = json.dumps({"name": "apple", "count": 1}, indent=None)
text = Text(json_string)
highlighter = JSONHighlighter()
highlighter.highlight(text)
assert text.spans == [
Span(0, 1, "json.brace"),
Span(1, 7, "json.str"),
Span(9, 16, "json.str"),
Span(18, 25, "json.str"),
Span(27, 28, "json.number"),
Span(28, 29, "json.brace"),
Span(1, 7, "json.key"),
Span(18, 25, "json.key"),
]