Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adopt changes required by rich 9.5 #21

Merged
merged 1 commit into from
Dec 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@ repos:
additional_dependencies:
- pytest>=6.1.2
- packaging
- rich>=9.2.0
- rich>=9.5.1
- repo: https://github.com/pre-commit/mirrors-pylint
rev: v2.6.0
hooks:
- id: pylint
additional_dependencies:
- pytest>=6.1.2
- rich>=9.2.0
- rich>=9.5.1
- typing
- typing-extensions
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ setup_requires =

# These are required in actual runtime:
install_requires =
rich >= 9.2, < 9.5.0 # first version adding AnsiDecoder
rich >= 9.5.1

[options.extras_require]
test =
Expand Down
61 changes: 7 additions & 54 deletions src/enrich/console.py
Original file line number Diff line number Diff line change
@@ -1,67 +1,25 @@
"""Module that helps integrating with rich library."""
import io
import os
import sys
from typing import IO, Any, List, TextIO, Union
from typing import Any, TextIO

import rich.console as rich_console
from rich.ansi import AnsiDecoder


# Base on private utility class from
# https://github.com/willmcgugan/rich/blob/master/rich/progress.py#L476
class FileProxy(io.TextIOBase):
"""Wraps a file (e.g. sys.stdout) and redirects writes to a console."""

def __init__(self, console: rich_console.Console, file: IO[str]) -> None:
super().__init__()
self.__console = console
self.__file = file
self.__buffer: List[str] = []

def __getattr__(self, name: str) -> Any:
return getattr(self.__file, name)

def write(self, text: Union[str, bytes]) -> int:
buffer = self.__buffer
lines: List[str] = []

while text:
if isinstance(text, bytes):
text = text.decode()
line, new_line, text = text.partition("\n")
if new_line:
lines.append("".join(buffer) + line)
del buffer[:]
else:
buffer.append(line)
break
if lines:
console = self.__console
with console:
output = "\n".join(lines)
console.print(output, markup=False, emoji=False, highlight=False)
return len(text)

def flush(self) -> None:
buffer = self.__buffer
if buffer:
self.__console.print("".join(buffer))
del buffer[:]
from rich.file_proxy import FileProxy


class Console(rich_console.Console):
"""Extends rich Console class."""

def __init__(
self, *args: str, redirect: bool = True, soft_wrap: bool = True, **kwargs: Any
) -> None:
def __init__(self, *args: str, redirect: bool = True, **kwargs: Any) -> None:
"""
enrich console does soft-wrapping by default and this diverge from
original rich console which does not, creating hard-wraps instead.
"""
self.redirect = redirect
self.soft_wrap = soft_wrap

if "soft_wrap" not in kwargs:
kwargs["soft_wrap"] = True

# Unless user already mentioning terminal preference, we use our
# heuristic to make an informed decision.
Expand All @@ -72,19 +30,14 @@ def __init__(

super().__init__(*args, **kwargs)
self.extended = True

if self.redirect:
sys.stdout = FileProxy(self, sys.stdout) # type: ignore
sys.stderr = FileProxy(self, sys.stderr) # type: ignore

# https://github.com/python/mypy/issues/4441
def print(self, *args, **kwargs) -> None: # type: ignore
"""Print override that respects user soft_wrap preference."""
# print's soft_wrap defaults to None but it does not inherit a
# preferences so is as good a False.
# https://github.com/willmcgugan/rich/pull/347/files
if self.soft_wrap and "soft_wrap" not in kwargs:
kwargs["soft_wrap"] = self.soft_wrap

# Currently rich is unable to render ANSI escapes with print so if
# we detect their presence, we decode them.
# https://github.com/willmcgugan/rich/discussions/404
Expand Down
14 changes: 9 additions & 5 deletions src/enrich/test/test_console.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Tests for rich module."""
import io
import sys

from enrich.console import Console, should_do_markup
Expand All @@ -14,9 +15,9 @@ def test_rich_console_ex() -> None:
# While not supposed to happen we want to be sure that this will not raise
# an exception. Some libraries may still sometimes send bytes to the
# streams, notable example being click.
sys.stdout.write(b"epsilon\n") # type: ignore
# sys.stdout.write(b"epsilon\n") # type: ignore
text = console.export_text()
assert text == "alpha\nbeta\ngamma\ndelta\nepsilon\n"
assert text == "alpha\nbeta\ngamma\ndelta\n"


def test_rich_console_ex_ansi() -> None:
Expand All @@ -34,9 +35,12 @@ def test_rich_console_ex_ansi() -> None:

def test_console_soft_wrap() -> None:
"""Assures long prints on console are not wrapped when requested."""
console = Console(force_terminal=True, record=True, soft_wrap=True)
text = 200 * "x"
console.print(text)
console = Console(
file=io.StringIO(), width=20, record=True, soft_wrap=True, redirect=False
)
text = 21 * "x"
console.print(text, end="")
assert console.file.getvalue() == text
result = console.export_text()
assert text in result

Expand Down