Skip to content

Commit

Permalink
Merge branch 'master' into dataclasses_3.13
Browse files Browse the repository at this point in the history
  • Loading branch information
willmcgugan authored Aug 26, 2024
2 parents 848fdde + 1b2dada commit d6abebd
Show file tree
Hide file tree
Showing 9 changed files with 226 additions and 101 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed cached hash preservation upon clearing meta and links https://github.com/Textualize/rich/issues/2942
- Fixed overriding the `background_color` of `Syntax` not including padding https://github.com/Textualize/rich/issues/3295
- Fixed pretty printing of dataclasses with a default repr in Python 3.13 https://github.com/Textualize/rich/pull/3455
- Fixed selective enabling of highlighting when disabled in the `Console` https://github.com/Textualize/rich/issues/3419
- Fixed BrokenPipeError writing an error message https://github.com/Textualize/rich/pull/3468
- Fixed superfluous space above Markdown tables https://github.com/Textualize/rich/pull/3469
- Fixed issue with record and capture interaction https://github.com/Textualize/rich/pull/3470
- Fixed control codes breaking in `append_tokens` https://github.com/Textualize/rich/pull/3471

### Changed

Expand All @@ -31,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Adds a `case_sensitive` parameter to `prompt.Prompt`. This determines if the
response is treated as case-sensitive. Defaults to `True`.
- Added `Console.on_broken_pipe` https://github.com/Textualize/rich/pull/3468

## [13.7.1] - 2024-02-28

Expand Down
131 changes: 59 additions & 72 deletions poetry.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ show_error_codes = true
strict = true
enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"]


[[tool.mypy.overrides]]
module = ["pygments.*", "IPython.*", "ipywidgets.*"]
ignore_missing_imports = true
Expand Down
50 changes: 42 additions & 8 deletions rich/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -1385,9 +1385,14 @@ def render_lines(
extra_lines = render_options.height - len(lines)
if extra_lines > 0:
pad_line = [
[Segment(" " * render_options.max_width, style), Segment("\n")]
if new_lines
else [Segment(" " * render_options.max_width, style)]
(
[
Segment(" " * render_options.max_width, style),
Segment("\n"),
]
if new_lines
else [Segment(" " * render_options.max_width, style)]
)
]
lines.extend(pad_line * extra_lines)

Expand Down Expand Up @@ -1436,9 +1441,11 @@ def render_str(
rich_text.overflow = overflow
else:
rich_text = Text(
_emoji_replace(text, default_variant=self._emoji_variant)
if emoji_enabled
else text,
(
_emoji_replace(text, default_variant=self._emoji_variant)
if emoji_enabled
else text
),
justify=justify,
overflow=overflow,
style=style,
Expand Down Expand Up @@ -1535,7 +1542,11 @@ def check_text() -> None:
if isinstance(renderable, str):
append_text(
self.render_str(
renderable, emoji=emoji, markup=markup, highlighter=_highlighter
renderable,
emoji=emoji,
markup=markup,
highlight=highlight,
highlighter=_highlighter,
)
)
elif isinstance(renderable, Text):
Expand Down Expand Up @@ -1985,6 +1996,20 @@ def log(
):
buffer_extend(line)

def on_broken_pipe(self) -> None:
"""This function is called when a `BrokenPipeError` is raised.
This can occur when piping Textual output in Linux and macOS.
The default implementation is to exit the app, but you could implement
this method in a subclass to change the behavior.
See https://docs.python.org/3/library/signal.html#note-on-sigpipe for details.
"""
self.quiet = True
devnull = os.open(os.devnull, os.O_WRONLY)
os.dup2(devnull, sys.stdout.fileno())
raise SystemExit(1)

def _check_buffer(self) -> None:
"""Check if the buffer may be rendered. Render it if it can (e.g. Console.quiet is False)
Rendering is supported on Windows, Unix and Jupyter environments. For
Expand All @@ -1994,8 +2019,17 @@ def _check_buffer(self) -> None:
if self.quiet:
del self._buffer[:]
return

try:
self._write_buffer()
except BrokenPipeError:
self.on_broken_pipe()

def _write_buffer(self) -> None:
"""Write the buffer to the output file."""

with self._lock:
if self.record:
if self.record and not self._buffer_index:
with self._record_buffer_lock:
self._record_buffer.extend(self._buffer[:])

Expand Down
2 changes: 1 addition & 1 deletion rich/markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,7 @@ def __rich_console__(
and context.stack.top.on_child_close(context, element)
)
if should_render:
if new_line:
if new_line and node_type != "inline":
yield _new_line_segment
yield from console.render(element, context.options)

Expand Down
1 change: 1 addition & 0 deletions rich/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -1041,6 +1041,7 @@ def append_tokens(
_Span = Span
offset = len(self)
for content, style in tokens:
content = strip_control_codes(content)
append_text(content)
if style:
append_span(_Span(offset, offset + len(content), style))
Expand Down
85 changes: 68 additions & 17 deletions tests/test_console.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import datetime
import io
import os
import subprocess
import sys
import tempfile
from typing import Optional, Tuple, Type, Union
Expand Down Expand Up @@ -380,23 +381,6 @@ def test_capture():
assert capture.get() == "Hello\n"


def test_capture_and_record(capsys):
recorder = Console(record=True)
recorder.print("ABC")

with recorder.capture() as capture:
recorder.print("Hello")

assert capture.get() == "Hello\n"

recorded_text = recorder.export_text()
out, err = capsys.readouterr()

assert recorded_text == "ABC\nHello\n"
assert capture.get() == "Hello\n"
assert out == "ABC\n"


def test_input(monkeypatch, capsys):
def fake_input(prompt=""):
console.file.write(prompt)
Expand Down Expand Up @@ -991,3 +975,70 @@ def test_force_color():
},
)
assert console.color_system in ("truecolor", "windows")


def test_reenable_highlighting() -> None:
"""Check that when highlighting is disabled, it can be reenabled in print()"""
console = Console(
file=io.StringIO(),
_environ={
"FORCE_COLOR": "1",
"TERM": "xterm-256color",
"COLORTERM": "truecolor",
},
highlight=False,
)
console.print("[1, 2, 3]")
console.print("[1, 2, 3]", highlight=True)
output = console.file.getvalue()
lines = output.splitlines()
print(repr(lines))
# First line not highlighted
assert lines[0] == "[1, 2, 3]"
# Second line highlighted

assert (
lines[1]
== "\x1b[1m[\x1b[0m\x1b[1;36m1\x1b[0m, \x1b[1;36m2\x1b[0m, \x1b[1;36m3\x1b[0m\x1b[1m]\x1b[0m"
)


@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
def test_brokenpipeerror() -> None:
"""Test BrokenPipe works as expected."""
which_py, which_head = (["which", cmd] for cmd in ("python", "head"))
rich_cmd = "python -m rich".split()
for cmd in [which_py, which_head, rich_cmd]:
check = subprocess.run(cmd).returncode
if check != 0:
return # Only test on suitable Unix platforms
head_cmd = "head -1".split()
proc1 = subprocess.Popen(rich_cmd, stdout=subprocess.PIPE)
proc2 = subprocess.Popen(head_cmd, stdin=proc1.stdout, stdout=subprocess.PIPE)
proc1.stdout.close()
output, _ = proc2.communicate()
proc1.wait()
proc2.wait()
assert proc1.returncode == 1
assert proc2.returncode == 0


def test_capture_and_record() -> None:
"""Regression test for https://github.com/Textualize/rich/issues/2563"""

console = Console(record=True)
print("Before Capture started:")
console.print("[blue underline]Print 0")
with console.capture() as capture:
console.print("[blue underline]Print 1")
console.print("[blue underline]Print 2")
console.print("[blue underline]Print 3")
console.print("[blue underline]Print 4")

capture_content = capture.get()
print(repr(capture_content))
assert capture_content == "Print 1\nPrint 2\nPrint 3\nPrint 4\n"

recorded_content = console.export_text()
print(repr(recorded_content))
assert recorded_content == "Print 0\n"
Loading

0 comments on commit d6abebd

Please sign in to comment.