Воркед

This commit is contained in:
kromskii2 committed 2025-03-15 09:36:28 +07:00
1 parent ac32c55ab9
commit ee94d8e529
1138 files changed
+55771 -77901

No files matched your search

@@ -1,7 +1,5 @@
import inspect
import io
import os
import platform
import sys
import threading
import zlib
@@ -34,6 +32,8 @@ from typing import (
cast,
)
from pip._vendor.rich._null_file import NULL_FILE
if sys.version_info >= (3, 8):
from typing import Literal, Protocol, runtime_checkable
else:
@@ -46,6 +46,7 @@ else:
from . import errors, themes
from ._emoji_replace import _emoji_replace
from ._export_format import CONSOLE_HTML_FORMAT, CONSOLE_SVG_FORMAT
from ._fileno import get_fileno
from ._log_render import FormatTimeCallable, LogRender
from .align import Align, AlignMethod
from .color import ColorSystem, blend_rgb
@@ -74,7 +75,7 @@ if TYPE_CHECKING:
JUPYTER_DEFAULT_COLUMNS = 115
JUPYTER_DEFAULT_LINES = 100
WINDOWS = platform.system() == "Windows"
WINDOWS = sys.platform == "win32"
HighlighterType = Callable[[Union[str, "Text"]], "Text"]
JustifyMethod = Literal["default", "left", "center", "right", "full"]
@@ -88,15 +89,15 @@ class NoChange:
NO_CHANGE = NoChange()
try:
_STDIN_FILENO = sys.__stdin__.fileno()
_STDIN_FILENO = sys.__stdin__.fileno() # type: ignore[union-attr]
except Exception:
_STDIN_FILENO = 0
try:
_STDOUT_FILENO = sys.__stdout__.fileno()
_STDOUT_FILENO = sys.__stdout__.fileno() # type: ignore[union-attr]
except Exception:
_STDOUT_FILENO = 1
try:
_STDERR_FILENO = sys.__stderr__.fileno()
_STDERR_FILENO = sys.__stderr__.fileno() # type: ignore[union-attr]
except Exception:
_STDERR_FILENO = 2
@@ -104,7 +105,11 @@ _STD_STREAMS = (_STDIN_FILENO, _STDOUT_FILENO, _STDERR_FILENO)
_STD_STREAMS_OUTPUT = (_STDOUT_FILENO, _STDERR_FILENO)
_TERM_COLORS = {"256color": ColorSystem.EIGHT_BIT, "16color": ColorSystem.STANDARD}
_TERM_COLORS = {
"kitty": ColorSystem.EIGHT_BIT,
"256color": ColorSystem.EIGHT_BIT,
"16color": ColorSystem.STANDARD,
}
class ConsoleDimensions(NamedTuple):
@@ -272,6 +277,7 @@ class ConsoleRenderable(Protocol):
# A type that may be rendered by Console.
RenderableType = Union[ConsoleRenderable, RichCast, str]
"""A string or any object that may be rendered by Rich."""
# The result of calling a __rich_console__ method.
RenderResult = Iterable[Union[RenderableType, Segment]]
@@ -516,7 +522,11 @@ def _is_jupyter() -> bool: # pragma: no cover
return False
ipython = get_ipython() # type: ignore[name-defined]
shell = ipython.__class__.__name__
if "google.colab" in str(ipython.__class__) or shell == "ZMQInteractiveShell":
if (
"google.colab" in str(ipython.__class__)
or os.getenv("DATABRICKS_RUNTIME_VERSION")
or shell == "ZMQInteractiveShell"
):
return True # Jupyter notebook or qtconsole
elif shell == "TerminalInteractiveShell":
return False # Terminal running IPython
@@ -697,7 +707,11 @@ class Console:
self._height = height
self._color_system: Optional[ColorSystem]
self._force_terminal = force_terminal
self._force_terminal = None
if force_terminal is not None:
self._force_terminal = force_terminal
self._file = file
self.quiet = quiet
self.stderr = stderr
@@ -739,13 +753,15 @@ class Console:
self._is_alt_screen = False
def __repr__(self) -> str:
return f"<console width={self.width} {str(self._color_system)}>"
return f"<console width={self.width} {self._color_system!s}>"
@property
def file(self) -> IO[str]:
"""Get the file object to write to."""
file = self._file or (sys.stderr if self.stderr else sys.stdout)
file = getattr(file, "rich_proxied_file", file)
if file is None:
file = NULL_FILE
return file
@file.setter
@@ -928,6 +944,16 @@ class Console:
# Return False for Idle which claims to be a tty but can't handle ansi codes
return False
if self.is_jupyter:
# return False for Jupyter, which may have FORCE_COLOR set
return False
# If FORCE_COLOR env var has any value at all, we assume a terminal.
force_color = self._environ.get("FORCE_COLOR")
if force_color is not None:
self._force_terminal = True
return True
isatty: Optional[Callable[[], bool]] = getattr(self.file, "isatty", None)
try:
return False if isatty is None else isatty()
@@ -979,19 +1005,14 @@ class Console:
width: Optional[int] = None
height: Optional[int] = None
if WINDOWS: # pragma: no cover
streams = _STD_STREAMS_OUTPUT if WINDOWS else _STD_STREAMS
for file_descriptor in streams:
try:
width, height = os.get_terminal_size()
width, height = os.get_terminal_size(file_descriptor)
except (AttributeError, ValueError, OSError): # Probably not a terminal
pass
else:
for file_descriptor in _STD_STREAMS:
try:
width, height = os.get_terminal_size(file_descriptor)
except (AttributeError, ValueError, OSError):
pass
else:
break
else:
break
columns = self._environ.get("COLUMNS")
if columns is not None and columns.isdigit():
@@ -1125,7 +1146,7 @@ class Console:
status: RenderableType,
*,
spinner: str = "dots",
spinner_style: str = "status.spinner",
spinner_style: StyleType = "status.spinner",
speed: float = 1.0,
refresh_per_second: float = 12.5,
) -> "Status":
@@ -1282,7 +1303,7 @@ class Console:
renderable = rich_cast(renderable)
if hasattr(renderable, "__rich_console__") and not isclass(renderable):
render_iterable = renderable.__rich_console__(self, _options) # type: ignore[union-attr]
render_iterable = renderable.__rich_console__(self, _options)
elif isinstance(renderable, str):
text_renderable = self.render_str(
renderable, highlight=_options.highlight, markup=_options.markup
@@ -1359,9 +1380,14 @@ class Console:
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)
@@ -1410,9 +1436,11 @@ class Console:
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,
@@ -1502,14 +1530,18 @@ class Console:
if text:
sep_text = Text(sep, justify=justify, end=end)
append(sep_text.join(text))
del text[:]
text.clear()
for renderable in objects:
renderable = rich_cast(renderable)
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):
@@ -1701,7 +1733,7 @@ class Console:
indent: Union[None, int, str] = 2,
highlight: bool = True,
skip_keys: bool = False,
ensure_ascii: bool = True,
ensure_ascii: bool = False,
check_circular: bool = True,
allow_nan: bool = True,
default: Optional[Callable[[Any], Any]] = None,
@@ -1899,7 +1931,6 @@ class Console:
end (str, optional): String to write at end of print data. Defaults to "\\\\n".
style (Union[str, Style], optional): A style to apply to output. Defaults to None.
justify (str, optional): One of "left", "right", "center", or "full". Defaults to ``None``.
overflow (str, optional): Overflow method: "crop", "fold", or "ellipsis". Defaults to None.
emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. Defaults to None.
markup (Optional[bool], optional): Enable markup, or ``None`` to use console default. Defaults to None.
highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default. Defaults to None.
@@ -1960,6 +1991,20 @@ class Console:
):
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
@@ -1969,13 +2014,21 @@ class Console:
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[:])
if self._buffer_index == 0:
if self.is_jupyter: # pragma: no cover
from .jupyter import display
@@ -1985,31 +2038,50 @@ class Console:
if WINDOWS:
use_legacy_windows_render = False
if self.legacy_windows:
try:
fileno = get_fileno(self.file)
if fileno is not None:
use_legacy_windows_render = (
self.file.fileno() in _STD_STREAMS_OUTPUT
fileno in _STD_STREAMS_OUTPUT
)
except (ValueError, io.UnsupportedOperation):
pass
if use_legacy_windows_render:
from pip._vendor.rich._win32_console import LegacyWindowsTerm
from pip._vendor.rich._windows_renderer import legacy_windows_render
legacy_windows_render(
self._buffer[:], LegacyWindowsTerm(self.file)
)
buffer = self._buffer[:]
if self.no_color and self._color_system:
buffer = list(Segment.remove_color(buffer))
legacy_windows_render(buffer, LegacyWindowsTerm(self.file))
else:
# Either a non-std stream on legacy Windows, or modern Windows.
text = self._render_buffer(self._buffer[:])
# https://bugs.python.org/issue37871
# https://github.com/python/cpython/issues/82052
# We need to avoid writing more than 32Kb in a single write, due to the above bug
write = self.file.write
for line in text.splitlines(True):
try:
write(line)
except UnicodeEncodeError as error:
error.reason = f"{error.reason}\n*** You may need to add PYTHONIOENCODING=utf-8 to your environment ***"
raise
# Worse case scenario, every character is 4 bytes of utf-8
MAX_WRITE = 32 * 1024 // 4
try:
if len(text) <= MAX_WRITE:
write(text)
else:
batch: List[str] = []
batch_append = batch.append
size = 0
for line in text.splitlines(True):
if size + len(line) > MAX_WRITE and batch:
write("".join(batch))
batch.clear()
size = 0
batch_append(line)
size += len(line)
if batch:
write("".join(batch))
batch.clear()
except UnicodeEncodeError as error:
error.reason = f"{error.reason}\n*** You may need to add PYTHONIOENCODING=utf-8 to your environment ***"
raise
else:
text = self._render_buffer(self._buffer[:])
try:
@@ -2122,7 +2194,7 @@ class Console:
"""
text = self.export_text(clear=clear, styles=styles)
with open(path, "wt", encoding="utf-8") as write_file:
with open(path, "w", encoding="utf-8") as write_file:
write_file.write(text)
def export_html(
@@ -2228,7 +2300,7 @@ class Console:
code_format=code_format,
inline_styles=inline_styles,
)
with open(path, "wt", encoding="utf-8") as write_file:
with open(path, "w", encoding="utf-8") as write_file:
write_file.write(html)
def export_svg(
@@ -2238,18 +2310,24 @@ class Console:
theme: Optional[TerminalTheme] = None,
clear: bool = True,
code_format: str = CONSOLE_SVG_FORMAT,
font_aspect_ratio: float = 0.61,
unique_id: Optional[str] = None,
) -> str:
"""
Generate an SVG from the console contents (requires record=True in Console constructor).
Args:
path (str): The path to write the SVG to.
title (str): The title of the tab in the output image
title (str, optional): The title of the tab in the output image
theme (TerminalTheme, optional): The ``TerminalTheme`` object to use to style the terminal
clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``
code_format (str): Format string used to generate the SVG. Rich will inject a number of variables
code_format (str, optional): Format string used to generate the SVG. Rich will inject a number of variables
into the string in order to form the final SVG output. The default template used and the variables
injected by Rich can be found by inspecting the ``console.CONSOLE_SVG_FORMAT`` variable.
font_aspect_ratio (float, optional): The width to height ratio of the font used in the ``code_format``
string. Defaults to 0.61, which is the width to height ratio of Fira Code (the default font).
If you aren't specifying a different font inside ``code_format``, you probably don't need this.
unique_id (str, optional): unique id that is used as the prefix for various elements (CSS styles, node
ids). If not set, this defaults to a computed value based on the recorded content.
"""
from pip._vendor.rich.cells import cell_len
@@ -2293,7 +2371,7 @@ class Console:
width = self.width
char_height = 20
char_width = char_height * 0.61
char_width = char_height * font_aspect_ratio
line_height = char_height * 1.22
margin_top = 1
@@ -2345,14 +2423,16 @@ class Console:
if clear:
self._record_buffer.clear()
unique_id = "terminal-" + str(
zlib.adler32(
("".join(segment.text for segment in segments)).encode(
"utf-8", "ignore"
if unique_id is None:
unique_id = "terminal-" + str(
zlib.adler32(
("".join(repr(segment) for segment in segments)).encode(
"utf-8",
"ignore",
)
+ title.encode("utf-8", "ignore")
)
+ title.encode("utf-8", "ignore")
)
)
y = 0
for y, line in enumerate(Segment.split_and_crop_lines(segments, length=width)):
x = 0
@@ -2482,25 +2562,34 @@ class Console:
theme: Optional[TerminalTheme] = None,
clear: bool = True,
code_format: str = CONSOLE_SVG_FORMAT,
font_aspect_ratio: float = 0.61,
unique_id: Optional[str] = None,
) -> None:
"""Generate an SVG file from the console contents (requires record=True in Console constructor).
Args:
path (str): The path to write the SVG to.
title (str): The title of the tab in the output image
title (str, optional): The title of the tab in the output image
theme (TerminalTheme, optional): The ``TerminalTheme`` object to use to style the terminal
clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``
code_format (str): Format string used to generate the SVG. Rich will inject a number of variables
code_format (str, optional): Format string used to generate the SVG. Rich will inject a number of variables
into the string in order to form the final SVG output. The default template used and the variables
injected by Rich can be found by inspecting the ``console.CONSOLE_SVG_FORMAT`` variable.
font_aspect_ratio (float, optional): The width to height ratio of the font used in the ``code_format``
string. Defaults to 0.61, which is the width to height ratio of Fira Code (the default font).
If you aren't specifying a different font inside ``code_format``, you probably don't need this.
unique_id (str, optional): unique id that is used as the prefix for various elements (CSS styles, node
ids). If not set, this defaults to a computed value based on the recorded content.
"""
svg = self.export_svg(
title=title,
theme=theme,
clear=clear,
code_format=code_format,
font_aspect_ratio=font_aspect_ratio,
unique_id=unique_id,
)
with open(path, "wt", encoding="utf-8") as write_file:
with open(path, "w", encoding="utf-8") as write_file:
write_file.write(svg)