mirror of
https://github.com/Textualize/rich.git
synced 2025-08-04 18:18:22 +00:00
added examples
This commit is contained in:
parent
47111185b9
commit
30acd491ae
12 changed files with 184 additions and 28 deletions
70
examples/log.py
Normal file
70
examples/log.py
Normal file
|
@ -0,0 +1,70 @@
|
|||
import time
|
||||
from rich.console import Console
|
||||
from rich.style import Style
|
||||
from rich.highlighter import RegexHighlighter
|
||||
|
||||
|
||||
class RequestHighlighter(RegexHighlighter):
|
||||
base_style = "req."
|
||||
highlights = [
|
||||
r"^(?P<protocol>\w+) (?P<method>\w+) (?P<path>\S+) (?P<result>\w+) (?P<stats>\[.+\])$",
|
||||
r"\/(?P<filename>\w+\..{3,4})",
|
||||
]
|
||||
|
||||
|
||||
console = Console()
|
||||
console.push_styles(
|
||||
{
|
||||
"req.protocol": Style.parse("dim bold green"),
|
||||
"req.method": Style.parse("bold cyan"),
|
||||
"req.path": Style.parse("magenta"),
|
||||
"req.filename": Style.parse("bright_magenta"),
|
||||
"req.result": Style.parse("yellow"),
|
||||
"req.stats": Style.parse("dim"),
|
||||
}
|
||||
)
|
||||
|
||||
console.log("Server starting...")
|
||||
console.log("Serving on http://127.0.0.1:8000", highlight=None)
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
console.log(
|
||||
"HTTP GET /foo/bar/baz/egg.html 200 [0.57, 127.0.0.1:59076]",
|
||||
highlight=RequestHighlighter(),
|
||||
)
|
||||
|
||||
console.log(
|
||||
"HTTP GET /foo/bar/baz/background.jpg 200 [0.57, 127.0.0.1:59076]",
|
||||
highlight=RequestHighlighter(),
|
||||
)
|
||||
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
def test_locals():
|
||||
foo = (1, 2, 3)
|
||||
movies = ["Deadpool", "Rise of the Skywalker"]
|
||||
console = Console()
|
||||
|
||||
console.log(
|
||||
"JSON RPC batch",
|
||||
[
|
||||
{"jsonrpc": "2.0", "method": "sum", "params": [1, 2, 4], "id": "1"},
|
||||
{"jsonrpc": "2.0", "method": "notify_hello", "params": [7]},
|
||||
{"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": "2"},
|
||||
{"foo": "boo"},
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "foo.get",
|
||||
"params": {"name": "myself", "enable": False, "grommits": None},
|
||||
"id": "5",
|
||||
},
|
||||
{"jsonrpc": "2.0", "method": "get_data", "id": "9"},
|
||||
],
|
||||
log_locals=True,
|
||||
)
|
||||
|
||||
|
||||
test_locals()
|
|
@ -7,6 +7,7 @@ from .text import Text
|
|||
|
||||
if TYPE_CHECKING:
|
||||
from .console import Console, ConsoleRenderable, RenderableType
|
||||
from table import Table
|
||||
|
||||
|
||||
class LogRender:
|
||||
|
@ -57,8 +58,6 @@ class LogRender:
|
|||
output.add_row(*row)
|
||||
return output
|
||||
|
||||
self.console.print(output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from .console import Console
|
||||
|
|
|
@ -14,6 +14,7 @@ import shutil
|
|||
import sys
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
IO,
|
||||
Iterable,
|
||||
|
@ -39,6 +40,7 @@ from .color import ColorSystem
|
|||
from .highlighter import ReprHighlighter
|
||||
from .pretty import Pretty
|
||||
from .style import Style
|
||||
from .tabulate import tabulate_mapping
|
||||
from . import highlighter
|
||||
from . import themes
|
||||
from .pretty import Pretty
|
||||
|
@ -48,6 +50,7 @@ from .segment import Segment
|
|||
if TYPE_CHECKING:
|
||||
from .text import Text
|
||||
|
||||
HighlighterType = Callable[[Union[str, Text]], Text]
|
||||
|
||||
JustifyValues = Optional[Literal["left", "center", "right", "full"]]
|
||||
|
||||
|
@ -81,9 +84,6 @@ def console_str(render_object: Any) -> Text:
|
|||
return Text(str(render_object))
|
||||
|
||||
|
||||
_repr_highlight = ReprHighlighter()
|
||||
|
||||
|
||||
@dataclass
|
||||
class ConsoleOptions:
|
||||
"""Options for __console__ method."""
|
||||
|
@ -244,6 +244,9 @@ class Console:
|
|||
"""Exit buffer context."""
|
||||
self._exit_buffer()
|
||||
|
||||
def push_styles(self, styles: Dict[str, Style]) -> None:
|
||||
self._styles.maps.append(styles)
|
||||
|
||||
@property
|
||||
def encoding(self) -> str:
|
||||
"""Get the encoding of the console file.
|
||||
|
@ -530,7 +533,12 @@ class Console:
|
|||
return StyleContext(self, _style)
|
||||
|
||||
def _collect_renderables(
|
||||
self, objects: Iterable[Any], sep: str, end: str, emoji=True,
|
||||
self,
|
||||
objects: Iterable[Any],
|
||||
sep: str,
|
||||
end: str,
|
||||
emoji=True,
|
||||
highlight: HighlighterType = None,
|
||||
) -> List[ConsoleRenderable]:
|
||||
"""Combined a number of renderables and text in to one renderable.
|
||||
|
||||
|
@ -570,11 +578,11 @@ class Console:
|
|||
render_str = renderable
|
||||
if emoji:
|
||||
render_str = _emoji_replace(render_str)
|
||||
append_text(_repr_highlight(render_str))
|
||||
append_text(highlight(render_str) if highlight else render_str)
|
||||
elif isinstance(renderable, Text):
|
||||
append_text(renderable)
|
||||
elif isinstance(renderable, (int, float, bool, bytes, type(None))):
|
||||
append_text(_repr_highlight(repr(renderable)))
|
||||
append_text(highlight(repr(renderable)) if highlight else renderable)
|
||||
else:
|
||||
check_text()
|
||||
append(Pretty(renderable))
|
||||
|
@ -589,6 +597,7 @@ class Console:
|
|||
end="\n",
|
||||
style: Union[str, Style] = None,
|
||||
emoji=True,
|
||||
highlight: HighlighterType = None,
|
||||
) -> None:
|
||||
"""Print to the console.
|
||||
|
||||
|
@ -603,7 +612,9 @@ class Console:
|
|||
self.line()
|
||||
return
|
||||
|
||||
renderables = self._collect_renderables(objects, sep=sep, end=end, emoji=emoji)
|
||||
renderables = self._collect_renderables(
|
||||
objects, sep=sep, end=end, emoji=emoji, highlight=highlighter
|
||||
)
|
||||
|
||||
render_options = self.options
|
||||
extend = self.buffer.extend
|
||||
|
@ -612,11 +623,20 @@ class Console:
|
|||
for renderable in renderables:
|
||||
extend(render(renderable, render_options))
|
||||
|
||||
def log(self, *objects: Any, debug=Ellipsis) -> None:
|
||||
def log(
|
||||
self,
|
||||
*objects: Any,
|
||||
debug=Ellipsis,
|
||||
highlight: HighlighterType = None,
|
||||
log_locals: bool = False,
|
||||
) -> None:
|
||||
if not objects and not debug:
|
||||
self.line()
|
||||
return
|
||||
renderables = self._collect_renderables(objects, sep=" ", end="\n")
|
||||
highlighter = highlight or ReprHighlighter()
|
||||
renderables = self._collect_renderables(
|
||||
objects, sep=" ", end="\n", highlight=highlighter
|
||||
)
|
||||
|
||||
if debug != Ellipsis:
|
||||
renderables.append(Pretty(debug))
|
||||
|
@ -624,6 +644,13 @@ class Console:
|
|||
caller = inspect.stack()[1]
|
||||
path = caller.filename.rpartition(os.sep)[-1]
|
||||
line_no = caller.lineno
|
||||
if log_locals:
|
||||
locals_map = {
|
||||
key: value
|
||||
for key, value in caller.frame.f_locals.items()
|
||||
if not key.startswith("__")
|
||||
}
|
||||
renderables.append(tabulate_mapping(locals_map, title="Locals"))
|
||||
|
||||
with self:
|
||||
self.buffer.extend(
|
||||
|
@ -817,7 +844,7 @@ if __name__ == "__main__":
|
|||
)
|
||||
|
||||
console.log("# Hello, **World**!")
|
||||
console.log("Hello, World!")
|
||||
console.log("Hello, World!", "{'a': 1}", repr(console))
|
||||
|
||||
console.log(
|
||||
{
|
||||
|
|
|
@ -71,7 +71,6 @@ class Lines:
|
|||
"""Console render method to insert line-breaks."""
|
||||
for line in self._lines:
|
||||
yield line
|
||||
yield Segment("\n")
|
||||
|
||||
def append(self, line: Text) -> None:
|
||||
self._lines.append(line)
|
||||
|
|
|
@ -37,22 +37,23 @@ DEFAULT_STYLES: Dict[str, Style] = {
|
|||
"magenta": Style(color="magenta"),
|
||||
"cyan": Style(color="cyan"),
|
||||
"white": Style(color="white"),
|
||||
"log.time": Style(color="cyan"),
|
||||
"log.time": Style(color="cyan", dim=True),
|
||||
"log.message": Style(),
|
||||
"log.path": Style(dim=True),
|
||||
"repr.str": Style(color="green", merge=False),
|
||||
"repr.brace": Style(bold=True),
|
||||
"repr.tag_start": Style(bold=True),
|
||||
"repr.tag_name": Style(color="red", bold=True),
|
||||
"repr.tag_contents": Style(color="cyan"),
|
||||
"repr.tag_name": Style(color="bright_magenta"),
|
||||
"repr.tag_contents": Style(color="default"),
|
||||
"repr.tag_end": Style(bold=True),
|
||||
"repr.attrib_name": Style(color="green", italic=True),
|
||||
"repr.attrib_name": Style(color="yellow", italic=True),
|
||||
"repr.attrib_equal": Style(bold=True),
|
||||
"repr.attrib_value": Style(color="magenta"),
|
||||
"repr.number": Style(color="blue"),
|
||||
"repr.number": Style(color="blue", bold=True),
|
||||
"repr.bool_true": Style(color="bright_green", italic=True),
|
||||
"repr.bool_false": Style(color="bright_red", italic=True),
|
||||
"repr.none": Style(color="magenta", italic=True),
|
||||
"repr.url": Style(underline=True),
|
||||
"inspect.key": Style(italic=True),
|
||||
"inspect.value": Style(),
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from re import finditer
|
||||
from typing import List, Union
|
||||
|
||||
|
@ -7,8 +8,19 @@ from typing import List, Union
|
|||
from .text import Text
|
||||
|
||||
|
||||
class Highlighter:
|
||||
class Highlighter(ABC):
|
||||
def __call__(self, text: Union[str, Text]) -> Text:
|
||||
"""Highlight a str or Text instance.
|
||||
|
||||
Args:
|
||||
text (Union[str, Text]): Text to highlight.
|
||||
|
||||
Raises:
|
||||
TypeError: If not called with text or str.
|
||||
|
||||
Returns:
|
||||
Text: A test instance with highlighting applied.
|
||||
"""
|
||||
if isinstance(text, str):
|
||||
highlight_text = Text(text)
|
||||
elif isinstance(text, Text):
|
||||
|
@ -17,11 +29,14 @@ class Highlighter:
|
|||
raise TypeError(f"str or Text instance required, not {text!r}")
|
||||
return self.highlight(highlight_text)
|
||||
|
||||
@abstractmethod
|
||||
def highlight(self, text: Text) -> Text:
|
||||
pass
|
||||
...
|
||||
|
||||
|
||||
class RegexHighlighter(Highlighter):
|
||||
"""Applies highlighting from a list of regular expressions."""
|
||||
|
||||
highlights: List[str] = []
|
||||
base_style: str = ""
|
||||
|
||||
|
@ -31,8 +46,9 @@ class RegexHighlighter(Highlighter):
|
|||
stylize = text.stylize
|
||||
for highlight in self.highlights:
|
||||
for match in finditer(highlight, str_text):
|
||||
_span = match.span
|
||||
for name, _ in match.groupdict().items():
|
||||
start, end = match.span(name)
|
||||
start, end = _span(name)
|
||||
if start != -1:
|
||||
stylize(start, end, f"{base_style}{name}")
|
||||
return text
|
||||
|
@ -41,12 +57,13 @@ class RegexHighlighter(Highlighter):
|
|||
class ReprHighlighter(RegexHighlighter):
|
||||
base_style = "repr."
|
||||
highlights = [
|
||||
r"(?P<brace>[\{\[\]\}])",
|
||||
r"(?P<brace>[\{\[\(\)\]\}])",
|
||||
r"(?P<tag_start>\<)(?P<tag_name>\w*)(?P<tag_contents>.*?)(?P<tag_end>\>)",
|
||||
r"(?P<attrib_name>\w+?)=(?P<attrib_value>\"?\w+\"?)",
|
||||
r"(?P<bool_true>True)|(?P<bool_false>False)|(?P<none>None)",
|
||||
r"(?P<number>\-?[0-9]+\.?[0-9]*)",
|
||||
r"(?P<str>b?\'\'\'.*?\'\'\'|b?\'.*?\'|b?\"\"\".*?\"\"\"|b?\".*?\")",
|
||||
r"(?P<url>https?:\/\/\S*)",
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -486,10 +486,10 @@ This is a [link](https://www.willmcgugan.com)
|
|||
if __name__ == "__main__": # pragma: no cover
|
||||
from .console import Console
|
||||
|
||||
console = Console(record=True)
|
||||
console = Console(record=True, width=60)
|
||||
# print(console.size)
|
||||
|
||||
markup = "<foo>"
|
||||
# markup = "<foo>"
|
||||
md = Markdown(markup)
|
||||
|
||||
console.print(md)
|
||||
|
|
|
@ -4,8 +4,9 @@ from typing import Any, TYPE_CHECKING
|
|||
|
||||
from pprintpp import pformat
|
||||
|
||||
|
||||
from ._render_width import RenderWidth
|
||||
from .highlighter import ReprHighlighter
|
||||
from .text import Text
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .console import Console, ConsoleOptions, RenderResult
|
||||
|
@ -20,3 +21,7 @@ class Pretty:
|
|||
pretty_str = pformat(self._object, width=options.max_width)
|
||||
pretty_text = highlighter(pretty_str)
|
||||
yield pretty_text
|
||||
|
||||
def __console_width__(self, max_width: int) -> RenderWidth:
|
||||
text = Text(pformat(self._object, width=max_width))
|
||||
return text.__console_width__(max_width)
|
||||
|
|
|
@ -185,6 +185,7 @@ class Style:
|
|||
|
||||
elif word == "nomerge":
|
||||
merge = False
|
||||
|
||||
else:
|
||||
try:
|
||||
Color.parse(word)
|
||||
|
|
|
@ -87,6 +87,7 @@ class Table:
|
|||
def __init__(
|
||||
self,
|
||||
*headers: Union[Column, str],
|
||||
title: str = None,
|
||||
width: int = None,
|
||||
box: Optional[box.Box] = box.MINIMAL_DOUBLE_HEAD,
|
||||
padding: PaddingDimensions = (0, 1),
|
||||
|
@ -98,22 +99,25 @@ class Table:
|
|||
style: Union[str, Style] = "none",
|
||||
header_style: Union[str, Style] = "bold",
|
||||
border_style: Union[str, Style] = "",
|
||||
title_style: Union[str, Style] = "bold",
|
||||
) -> None:
|
||||
self.columns = [
|
||||
(Column(header) if isinstance(header, str) else header)
|
||||
for header in headers
|
||||
]
|
||||
self.title = title
|
||||
self.width = width
|
||||
self.box = box
|
||||
self._padding = Padding.unpack(padding)
|
||||
self.pad_edge = pad_edge
|
||||
self.expand = expand
|
||||
self.show_header = show_header
|
||||
self.show_header = show_header and headers
|
||||
self.show_footer = show_footer
|
||||
self.show_edge = show_edge
|
||||
self.style = style
|
||||
self.header_style = header_style
|
||||
self.border_style = border_style
|
||||
self.title_style = title_style
|
||||
self._row_count = 0
|
||||
|
||||
@property
|
||||
|
@ -163,6 +167,8 @@ class Table:
|
|||
self.columns.append(column)
|
||||
|
||||
def add_row(self, *renderables: Optional[Union[str, ConsoleRenderable]]) -> None:
|
||||
from .console import ConsoleRenderable
|
||||
|
||||
def add_cell(column: Column, renderable: ConsoleRenderable):
|
||||
column._cells.append(renderable)
|
||||
|
||||
|
@ -184,7 +190,7 @@ class Table:
|
|||
self.columns.append(column)
|
||||
else:
|
||||
column = columns[index]
|
||||
if hasattr(renderable, "__console__"):
|
||||
if isinstance(renderable, ConsoleRenderable):
|
||||
add_cell(column, renderable)
|
||||
elif renderable is None:
|
||||
add_cell(column, Text(""))
|
||||
|
@ -201,11 +207,17 @@ class Table:
|
|||
max_width = options.max_width
|
||||
if self.width is not None:
|
||||
max_width = min(self.width, max_width)
|
||||
|
||||
if self.box:
|
||||
max_width -= len(self.columns)
|
||||
if self.show_edge:
|
||||
max_width -= 2
|
||||
widths = self._calculate_column_widths(max_width)
|
||||
table_width = sum(widths) + len(self.columns) + (2 if self.box else 0)
|
||||
if self.title:
|
||||
title_text = Text(self.title, self.title_style)
|
||||
wrapped_title = title_text.wrap(table_width, "center")
|
||||
yield wrapped_title
|
||||
yield from self._render(console, options, widths)
|
||||
|
||||
def _calculate_column_widths(self, max_width: int) -> List[int]:
|
||||
|
|
25
rich/tabulate.py
Normal file
25
rich/tabulate.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
|
||||
|
||||
from . import box
|
||||
from .pretty import Pretty
|
||||
from .table import Table
|
||||
from .text import Text
|
||||
|
||||
|
||||
def tabulate_mapping(mapping: Mapping, title: str = None) -> Table:
|
||||
"""Generate a simple table from a mapping.
|
||||
|
||||
Args:
|
||||
mapping (Mapping): A mapping object (e.g. a dict);
|
||||
title (str, optional): [description]. Optional title to be displayed over the table.
|
||||
|
||||
Returns:
|
||||
Table: A table instance which may be rendered by the Console.
|
||||
"""
|
||||
table = Table(title=title, box=box.ROUNDED, border_style="blue", padding=0)
|
||||
for key, value in mapping.items():
|
||||
table.add_row(Pretty(key), Pretty(value))
|
||||
return table
|
|
@ -22,7 +22,7 @@ def test_apply_style():
|
|||
def test_split_and_crop_lines():
|
||||
assert list(
|
||||
Segment.split_and_crop_lines([Segment("Hello\nWorld!\n"), Segment("foo")], 4)
|
||||
) == [[Segment("Hell")], [Segment("Worl")], [Segment("foo")]]
|
||||
) == [[Segment("Hell")], [Segment("Worl")], [Segment("foo"), Segment(" ")]]
|
||||
|
||||
|
||||
def test_get_line_length():
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue