This commit is contained in:
Will McGugan 2020-08-08 17:38:57 +01:00
parent dba38be39e
commit 6169ab64df
5 changed files with 102 additions and 34 deletions

View file

@ -5,7 +5,7 @@ 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).
## [5.1.0] - Unreleased
## [5.1.0] - 2020-08-16
### Added
@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Implemented pretty printing, and removed pprintpp from dependancies
- Optimized Text.join
## [5.0.0] - 2020-08-02

View file

@ -64,4 +64,20 @@ If you would rather not shadow Python's builtin print, you can import ``rich.pri
from rich import print as rprint
Continue reading to learn about the more advanced features of Rich.
Continue reading to learn about the more advanced features of Rich.
Python in the REPL
------------------
Rich may be installed in the REPL so that Python data structures are pretty printed with syntax highlighting. Here's how::
>>> from rich import pretty
>>> pretty.install()
>>> ["Rich and pretty", True]
You can also use this feature to try out Rich *renderables*. Here's an example::
>>> from rich.panel import panel
>>> Panel.fit("[bold yellow]Hi, I'm a Panel", border_style="red")
Read on to learn more about Rich renderables.

View file

@ -13,7 +13,7 @@ from rich.highlighter import Highlighter
class RainbowHighlighter(Highlighter):
def highlight(self, text):
for index in range(len(text)):
text.stylize(str(randint(16, 255)), index, index + 1)
text.stylize(f"color({randint(16, 255)})", index, index + 1)
rainbow = RainbowHighlighter()

View file

@ -1,9 +1,10 @@
import sys
from collections import defaultdict
from dataclasses import dataclass, field
from rich.highlighter import ReprHighlighter
from typing import Any, Dict, List, Optional, Set, TYPE_CHECKING
from typing import Any, Dict, List, Optional, Set, Tuple, TYPE_CHECKING
from .cells import cell_len
from .highlighter import Highlighter, NullHighlighter, ReprHighlighter
@ -12,21 +13,32 @@ from .measure import Measurement
from .text import Text
if TYPE_CHECKING: # pragma: no cover
from .console import Console, ConsoleOptions, HighlighterType, RenderResult
from .console import (
Console,
ConsoleOptions,
HighlighterType,
OverflowMethod,
RenderResult,
)
def install(console: "Console" = None) -> None:
"""Install automatic pretty printing in the Python REPL."""
"""Install automatic pretty printing in the Python REPL.
Args:
console (Console, optional): Console instance or ``None`` to use global console. Defaults to None.
"""
from rich import get_console
console = console or get_console()
def display_hook(value: Any) -> None:
if value is not None:
assert console is not None
console.print(
value
if hasattr(value, "__rich_console__") or hasattr(value, "__rich__")
else pretty_repr(value)
else pretty_repr(value, no_wrap=True, overflow="ellipsis")
)
sys.displayhook = display_hook
@ -35,31 +47,51 @@ def install(console: "Console" = None) -> None:
class Pretty:
"""A rich renderable that pretty prints an object."""
def __init__(self, _object: Any, highlighter: "HighlighterType" = None) -> None:
def __init__(
self,
_object: Any,
highlighter: "HighlighterType" = None,
*,
indent_size: int = 4,
overflow: "OverflowMethod" = None,
no_wrap: bool = None,
) -> None:
self._object = _object
self.highlighter = highlighter or NullHighlighter()
self.indent_size = indent_size
self.overflow = overflow
self.no_wrap = no_wrap
def __rich_console__(
self, console: "Console", options: "ConsoleOptions"
) -> "RenderResult":
pretty_text = pretty_repr(self._object, max_width=options.max_width)
pretty_text = pretty_repr(
self._object,
max_width=options.max_width,
indent_size=self.indent_size,
overflow=self.overflow or options.overflow,
no_wrap=self.no_wrap if self.no_wrap is not None else options.no_wrap,
)
yield pretty_text
def __rich_measure__(self, console: "Console", max_width: int) -> "Measurement":
pretty_text = pretty_repr(self._object, max_width=max_width)
pretty_text = pretty_repr(
self._object, max_width=max_width, indent_size=self.indent_size
)
text_width = max(cell_len(line) for line in pretty_text.plain.splitlines())
return Measurement(text_width, text_width)
_BRACES = {
dict: (Text("{", "repr.brace"), Text("}", "repr.brace")),
_BRACES: Dict[type, Tuple[Text, Text, Text]] = {
dict: (Text("{", "repr.brace"), Text("}", "repr.brace"), Text("{}", "repr.brace")),
frozenset: (
Text.assemble("frozenset(", ("{", "repr.brace")),
Text.assemble(("}", "repr.brace"), ")"),
Text("frozenset()"),
),
list: (Text("[", "repr.brace"), Text("]", "repr.brace")),
set: (Text("{", "repr.brace"), Text("}", "repr.brace")),
tuple: (Text("(", "repr.brace"), Text(")", "repr.brace")),
list: (Text("[", "repr.brace"), Text("]", "repr.brace"), Text("[]", "repr.brace")),
set: (Text("{", "repr.brace"), Text("}", "repr.brace"), Text("set()")),
tuple: (Text("(", "repr.brace"), Text(")", "repr.brace"), Text("()", "repr.brace")),
}
_CONTAINERS = tuple(_BRACES.keys())
_REPR_STYLES = {
@ -103,6 +135,8 @@ def pretty_repr(
max_width: Optional[int] = 80,
indent_size: int = 4,
highlighter: Highlighter = None,
overflow: "OverflowMethod" = None,
no_wrap: bool = True,
) -> Text:
"""Return a 'pretty' repr.
@ -153,10 +187,10 @@ def pretty_repr(
except Exception as error:
text = Text(f"<error in repr: {error}>", "repr.error")
else:
if style is None:
if style is None and highlighter is not None:
text = highlighter(Text(repr_text))
else:
text = Text(repr_text, style)
text = Text(repr_text, style or "")
repr_cache[node_id] = text
return text
@ -187,12 +221,11 @@ def pretty_repr(
visited_set.add(node_id)
if type(node) in _CONTAINERS:
brace_open, brace_close = _BRACES[type(node)]
brace_open, brace_close, empty = _BRACES[type(node)]
expanded = level < expand_level
if not node:
append_text(brace_open)
append_text(brace_close)
append_text(empty)
else:
append_text(brace_open)
if isinstance(node, dict):
@ -234,10 +267,13 @@ def pretty_repr(
else:
break # pragma: no cover
return Text("\n").join(line.text for line in lines)
text = Text("\n", overflow=overflow, no_wrap=no_wrap).join(
line.text for line in lines
)
return text
if __name__ == "__main__":
if __name__ == "__main__": # pragma: no cover
from collections import defaultdict
class BrokenRepr:
@ -254,14 +290,13 @@ if __name__ == "__main__":
None: "This is None",
"Broken": BrokenRepr(),
}
data["foo"].append(data)
data["foo"].append(data) # type: ignore
from rich.console import Console
console = Console()
print("-" * console.width)
from rich import print
p = Pretty(data)
p = Pretty(data, overflow="ellipsis")
print(Measurement.get(console, p))
console.print(p)

View file

@ -565,16 +565,32 @@ class Text(JupyterMixin):
"""
new_text = self.blank_copy()
append = new_text.append_text
if self.plain:
for last, line in loop_last(lines):
append(line)
if not last:
append(self)
else:
for line in lines:
append(line)
def iter_text() -> Iterable["Text"]:
if self.plain:
for last, line in loop_last(lines):
yield line
if not last:
yield self
else:
yield from lines
extend_text = new_text._text.extend
append_span = new_text._spans.append
extend_spans = new_text._spans.extend
offset = 0
_Span = Span
for text in iter_text():
extend_text(text._text)
if text.style is not None:
append_span(_Span(offset, offset + len(text), text.style))
extend_spans(
_Span(offset + start, offset + end, style)
for start, end, style in text._spans
)
offset += len(text)
new_text._length = offset
return new_text
def tabs_to_spaces(self, tab_size: int = None) -> "Text":