added examples

This commit is contained in:
Will McGugan 2019-12-18 16:46:24 +00:00
parent 47111185b9
commit 30acd491ae
12 changed files with 184 additions and 28 deletions

70
examples/log.py Normal file
View 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()

View file

@ -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

View file

@ -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(
{

View file

@ -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)

View file

@ -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(),
}

View file

@ -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*)",
]

View file

@ -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)

View file

@ -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)

View file

@ -185,6 +185,7 @@ class Style:
elif word == "nomerge":
merge = False
else:
try:
Color.parse(word)

View file

@ -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
View 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

View file

@ -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():