mirror of https://github.com/Textualize/rich.git
Merge branch 'master' of github.com:Textualize/rich into capture-echo
This commit is contained in:
commit
6e7074b4cb
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -5,7 +5,15 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
## [12.4.5] - Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- Environment variables `JUPYTER_COLUMNS` and `JUPYTER_LINES` to control width and height of console in Jupyter
|
||||
|
||||
### Changed
|
||||
|
||||
- Default width of Jupyter console size is increased to 115
|
||||
|
||||
### Fixed
|
||||
|
||||
|
@ -15,6 +23,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Fix crashes that can happen with `inspect` when docstrings contain some special control codes https://github.com/Textualize/rich/pull/2294
|
||||
- Fix edges used in first row of tables when `show_header=False` https://github.com/Textualize/rich/pull/2330
|
||||
- Fix interaction between `Capture` contexts and `Console(record=True)` https://github.com/Textualize/rich/pull/2343
|
||||
- Fixed hash issue in Styles class https://github.com/Textualize/rich/pull/2346
|
||||
|
||||
### Changed
|
||||
|
||||
- `Style.__add__` will no longer return `NotImplemented`
|
||||
- Remove rich.\_lru_cache
|
||||
|
||||
### Added
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ The following people have contributed to the development of Rich:
|
|||
- [Pete Davison](https://github.com/pd93)
|
||||
- [James Estevez](https://github.com/jstvz)
|
||||
- [Oleksis Fraga](https://github.com/oleksis)
|
||||
- [Andy Gimblett](https://github.com/gimbo)
|
||||
- [Michał Górny](https://github.com/mgorny)
|
||||
- [Leron Gray](https://github.com/daddycocoaman)
|
||||
- [Kenneth Hoste](https://github.com/boegel)
|
||||
|
@ -24,6 +25,7 @@ The following people have contributed to the development of Rich:
|
|||
- [Alexander Mancevice](https://github.com/amancevice)
|
||||
- [Will McGugan](https://github.com/willmcgugan)
|
||||
- [Paul McGuire](https://github.com/ptmcg)
|
||||
- [Antony Milne](https://github.com/AntonyMilneQB)
|
||||
- [Nathan Page](https://github.com/nathanrpage97)
|
||||
- [Avi Perl](https://github.com/avi-perl)
|
||||
- [Laurent Peuch](https://github.com/psycojoker)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
alabaster==0.7.12
|
||||
Sphinx==4.5.0
|
||||
Sphinx==5.0.2
|
||||
sphinx-rtd-theme==1.0.0
|
||||
sphinx-copybutton==0.5.0
|
||||
|
|
|
@ -429,3 +429,5 @@ Rich respects some standard environment variables.
|
|||
Setting the environment variable ``TERM`` to ``"dumb"`` or ``"unknown"`` will disable color/style and some features that require moving the cursor, such as progress bars.
|
||||
|
||||
If the environment variable ``NO_COLOR`` is set, Rich will disable all color in the output.
|
||||
|
||||
If ``width``/``height`` arguments are not explicitly provided as arguments to ``Console`` then the environment variables ``COLUMNS``/``LINES`` can be used to set the console width/height. ``JUPYTER_COLUMNS``/``JUPYTER_LINES`` behave similarly and are used in Jupyter.
|
||||
|
|
|
@ -490,7 +490,7 @@ python-versions = "*"
|
|||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "0.950"
|
||||
version = "0.961"
|
||||
description = "Optional static typing for Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -1065,7 +1065,7 @@ jupyter = ["ipywidgets"]
|
|||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.6.3"
|
||||
content-hash = "db93dc88a30c445999c422ad140ff15c2d28d0017efe262ce0f750cb42b45baf"
|
||||
content-hash = "b91f46d585ba68100ec1683af412ef096290262d34257fbce5c7b8353e9dc227"
|
||||
|
||||
[metadata.files]
|
||||
appnope = [
|
||||
|
@ -1389,29 +1389,29 @@ mistune = [
|
|||
{file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"},
|
||||
]
|
||||
mypy = [
|
||||
{file = "mypy-0.950-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cf9c261958a769a3bd38c3e133801ebcd284ffb734ea12d01457cb09eacf7d7b"},
|
||||
{file = "mypy-0.950-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5b5bd0ffb11b4aba2bb6d31b8643902c48f990cc92fda4e21afac658044f0c0"},
|
||||
{file = "mypy-0.950-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5e7647df0f8fc947388e6251d728189cfadb3b1e558407f93254e35abc026e22"},
|
||||
{file = "mypy-0.950-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:eaff8156016487c1af5ffa5304c3e3fd183edcb412f3e9c72db349faf3f6e0eb"},
|
||||
{file = "mypy-0.950-cp310-cp310-win_amd64.whl", hash = "sha256:563514c7dc504698fb66bb1cf897657a173a496406f1866afae73ab5b3cdb334"},
|
||||
{file = "mypy-0.950-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:dd4d670eee9610bf61c25c940e9ade2d0ed05eb44227275cce88701fee014b1f"},
|
||||
{file = "mypy-0.950-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ca75ecf2783395ca3016a5e455cb322ba26b6d33b4b413fcdedfc632e67941dc"},
|
||||
{file = "mypy-0.950-cp36-cp36m-win_amd64.whl", hash = "sha256:6003de687c13196e8a1243a5e4bcce617d79b88f83ee6625437e335d89dfebe2"},
|
||||
{file = "mypy-0.950-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4c653e4846f287051599ed8f4b3c044b80e540e88feec76b11044ddc5612ffed"},
|
||||
{file = "mypy-0.950-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e19736af56947addedce4674c0971e5dceef1b5ec7d667fe86bcd2b07f8f9075"},
|
||||
{file = "mypy-0.950-cp37-cp37m-win_amd64.whl", hash = "sha256:ef7beb2a3582eb7a9f37beaf38a28acfd801988cde688760aea9e6cc4832b10b"},
|
||||
{file = "mypy-0.950-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0112752a6ff07230f9ec2f71b0d3d4e088a910fdce454fdb6553e83ed0eced7d"},
|
||||
{file = "mypy-0.950-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ee0a36edd332ed2c5208565ae6e3a7afc0eabb53f5327e281f2ef03a6bc7687a"},
|
||||
{file = "mypy-0.950-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:77423570c04aca807508a492037abbd72b12a1fb25a385847d191cd50b2c9605"},
|
||||
{file = "mypy-0.950-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5ce6a09042b6da16d773d2110e44f169683d8cc8687e79ec6d1181a72cb028d2"},
|
||||
{file = "mypy-0.950-cp38-cp38-win_amd64.whl", hash = "sha256:5b231afd6a6e951381b9ef09a1223b1feabe13625388db48a8690f8daa9b71ff"},
|
||||
{file = "mypy-0.950-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0384d9f3af49837baa92f559d3fa673e6d2652a16550a9ee07fc08c736f5e6f8"},
|
||||
{file = "mypy-0.950-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1fdeb0a0f64f2a874a4c1f5271f06e40e1e9779bf55f9567f149466fc7a55038"},
|
||||
{file = "mypy-0.950-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:61504b9a5ae166ba5ecfed9e93357fd51aa693d3d434b582a925338a2ff57fd2"},
|
||||
{file = "mypy-0.950-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a952b8bc0ae278fc6316e6384f67bb9a396eb30aced6ad034d3a76120ebcc519"},
|
||||
{file = "mypy-0.950-cp39-cp39-win_amd64.whl", hash = "sha256:eaea21d150fb26d7b4856766e7addcf929119dd19fc832b22e71d942835201ef"},
|
||||
{file = "mypy-0.950-py3-none-any.whl", hash = "sha256:a4d9898f46446bfb6405383b57b96737dcfd0a7f25b748e78ef3e8c576bba3cb"},
|
||||
{file = "mypy-0.950.tar.gz", hash = "sha256:1b333cfbca1762ff15808a0ef4f71b5d3eed8528b23ea1c3fb50543c867d68de"},
|
||||
{file = "mypy-0.961-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:697540876638ce349b01b6786bc6094ccdaba88af446a9abb967293ce6eaa2b0"},
|
||||
{file = "mypy-0.961-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b117650592e1782819829605a193360a08aa99f1fc23d1d71e1a75a142dc7e15"},
|
||||
{file = "mypy-0.961-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bdd5ca340beffb8c44cb9dc26697628d1b88c6bddf5c2f6eb308c46f269bb6f3"},
|
||||
{file = "mypy-0.961-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3e09f1f983a71d0672bbc97ae33ee3709d10c779beb613febc36805a6e28bb4e"},
|
||||
{file = "mypy-0.961-cp310-cp310-win_amd64.whl", hash = "sha256:e999229b9f3198c0c880d5e269f9f8129c8862451ce53a011326cad38b9ccd24"},
|
||||
{file = "mypy-0.961-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b24be97351084b11582fef18d79004b3e4db572219deee0212078f7cf6352723"},
|
||||
{file = "mypy-0.961-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f4a21d01fc0ba4e31d82f0fff195682e29f9401a8bdb7173891070eb260aeb3b"},
|
||||
{file = "mypy-0.961-cp36-cp36m-win_amd64.whl", hash = "sha256:439c726a3b3da7ca84a0199a8ab444cd8896d95012c4a6c4a0d808e3147abf5d"},
|
||||
{file = "mypy-0.961-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5a0b53747f713f490affdceef835d8f0cb7285187a6a44c33821b6d1f46ed813"},
|
||||
{file = "mypy-0.961-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0e9f70df36405c25cc530a86eeda1e0867863d9471fe76d1273c783df3d35c2e"},
|
||||
{file = "mypy-0.961-cp37-cp37m-win_amd64.whl", hash = "sha256:b88f784e9e35dcaa075519096dc947a388319cb86811b6af621e3523980f1c8a"},
|
||||
{file = "mypy-0.961-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d5aaf1edaa7692490f72bdb9fbd941fbf2e201713523bdb3f4038be0af8846c6"},
|
||||
{file = "mypy-0.961-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9f5f5a74085d9a81a1f9c78081d60a0040c3efb3f28e5c9912b900adf59a16e6"},
|
||||
{file = "mypy-0.961-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f4b794db44168a4fc886e3450201365c9526a522c46ba089b55e1f11c163750d"},
|
||||
{file = "mypy-0.961-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:64759a273d590040a592e0f4186539858c948302c653c2eac840c7a3cd29e51b"},
|
||||
{file = "mypy-0.961-cp38-cp38-win_amd64.whl", hash = "sha256:63e85a03770ebf403291ec50097954cc5caf2a9205c888ce3a61bd3f82e17569"},
|
||||
{file = "mypy-0.961-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f1332964963d4832a94bebc10f13d3279be3ce8f6c64da563d6ee6e2eeda932"},
|
||||
{file = "mypy-0.961-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:006be38474216b833eca29ff6b73e143386f352e10e9c2fbe76aa8549e5554f5"},
|
||||
{file = "mypy-0.961-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9940e6916ed9371809b35b2154baf1f684acba935cd09928952310fbddaba648"},
|
||||
{file = "mypy-0.961-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a5ea0875a049de1b63b972456542f04643daf320d27dc592d7c3d9cd5d9bf950"},
|
||||
{file = "mypy-0.961-cp39-cp39-win_amd64.whl", hash = "sha256:1ece702f29270ec6af25db8cf6185c04c02311c6bb21a69f423d40e527b75c56"},
|
||||
{file = "mypy-0.961-py3-none-any.whl", hash = "sha256:03c6cc893e7563e7b2949b969e63f02c000b32502a1b4d1314cabe391aa87d66"},
|
||||
{file = "mypy-0.961.tar.gz", hash = "sha256:f730d56cb924d371c26b8eaddeea3cc07d78ff51c521c6d04899ac6904b75492"},
|
||||
]
|
||||
mypy-extensions = [
|
||||
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
|
||||
|
|
|
@ -40,7 +40,7 @@ jupyter = ["ipywidgets"]
|
|||
[tool.poetry.dev-dependencies]
|
||||
pytest = "^7.0.0"
|
||||
black = "^22.3"
|
||||
mypy = "^0.950"
|
||||
mypy = "^0.961"
|
||||
pytest-cov = "^3.0.0"
|
||||
attrs = "^21.4.0"
|
||||
types-dataclasses = "^0.6.4"
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
from typing import Dict, Generic, TypeVar, TYPE_CHECKING
|
||||
import sys
|
||||
|
||||
CacheKey = TypeVar("CacheKey")
|
||||
CacheValue = TypeVar("CacheValue")
|
||||
|
||||
if sys.version_info < (3, 9):
|
||||
from typing_extensions import OrderedDict
|
||||
else:
|
||||
from collections import OrderedDict
|
||||
|
||||
|
||||
class LRUCache(OrderedDict[CacheKey, CacheValue]):
|
||||
"""
|
||||
A dictionary-like container that stores a given maximum items.
|
||||
|
||||
If an additional item is added when the LRUCache is full, the least
|
||||
recently used key is discarded to make room for the new item.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, cache_size: int) -> None:
|
||||
self.cache_size = cache_size
|
||||
super().__init__()
|
||||
|
||||
def __setitem__(self, key: CacheKey, value: CacheValue) -> None:
|
||||
"""Store a new views, potentially discarding an old value."""
|
||||
if key not in self:
|
||||
if len(self) >= self.cache_size:
|
||||
self.popitem(last=False)
|
||||
super().__setitem__(key, value)
|
||||
|
||||
def __getitem__(self, key: CacheKey) -> CacheValue:
|
||||
"""Gets the item, but also makes it most recent."""
|
||||
value: CacheValue = super().__getitem__(key)
|
||||
super().__delitem__(key)
|
||||
super().__setitem__(key, value)
|
||||
return value
|
|
@ -1,15 +1,15 @@
|
|||
import re
|
||||
from functools import lru_cache
|
||||
from typing import Dict, List
|
||||
from typing import Callable, List
|
||||
|
||||
from ._cell_widths import CELL_WIDTHS
|
||||
from ._lru_cache import LRUCache
|
||||
|
||||
# Regex to match sequence of the most common character ranges
|
||||
_is_single_cell_widths = re.compile("^[\u0020-\u006f\u00a0\u02ff\u0370-\u0482]*$").match
|
||||
|
||||
|
||||
def cell_len(text: str, _cache: Dict[str, int] = LRUCache(1024 * 4)) -> int:
|
||||
@lru_cache(4096)
|
||||
def _cached_cell_len(text: str) -> int:
|
||||
"""Get the number of cells required to display text.
|
||||
|
||||
Args:
|
||||
|
@ -18,14 +18,24 @@ def cell_len(text: str, _cache: Dict[str, int] = LRUCache(1024 * 4)) -> int:
|
|||
Returns:
|
||||
int: Get the number of cells required to display text.
|
||||
"""
|
||||
cached_result = _cache.get(text, None)
|
||||
if cached_result is not None:
|
||||
return cached_result
|
||||
|
||||
_get_size = get_character_cell_size
|
||||
total_size = sum(_get_size(character) for character in text)
|
||||
if len(text) <= 512:
|
||||
_cache[text] = total_size
|
||||
return total_size
|
||||
|
||||
|
||||
def cell_len(text: str, _cell_len: Callable[[str], int] = _cached_cell_len) -> int:
|
||||
"""Get the number of cells required to display text.
|
||||
|
||||
Args:
|
||||
text (str): Text to display.
|
||||
|
||||
Returns:
|
||||
int: Get the number of cells required to display text.
|
||||
"""
|
||||
if len(text) < 512:
|
||||
return _cell_len(text)
|
||||
_get_size = get_character_cell_size
|
||||
total_size = sum(_get_size(character) for character in text)
|
||||
return total_size
|
||||
|
||||
|
||||
|
@ -80,7 +90,7 @@ def set_cell_size(text: str, total: int) -> str:
|
|||
return text + " " * (total - size)
|
||||
return text[:total]
|
||||
|
||||
if not total:
|
||||
if total <= 0:
|
||||
return ""
|
||||
cell_size = cell_len(text)
|
||||
if cell_size == total:
|
||||
|
|
|
@ -73,6 +73,8 @@ if TYPE_CHECKING:
|
|||
from .live import Live
|
||||
from .status import Status
|
||||
|
||||
JUPYTER_DEFAULT_COLUMNS = 115
|
||||
JUPYTER_DEFAULT_LINES = 100
|
||||
WINDOWS = platform.system() == "Windows"
|
||||
|
||||
HighlighterType = Callable[[Union[str, "Text"]], "Text"]
|
||||
|
@ -661,8 +663,18 @@ class Console:
|
|||
|
||||
self.is_jupyter = _is_jupyter() if force_jupyter is None else force_jupyter
|
||||
if self.is_jupyter:
|
||||
width = width or 93
|
||||
height = height or 100
|
||||
if width is None:
|
||||
jupyter_columns = self._environ.get("JUPYTER_COLUMNS")
|
||||
if jupyter_columns is not None and jupyter_columns.isdigit():
|
||||
width = int(jupyter_columns)
|
||||
else:
|
||||
width = JUPYTER_DEFAULT_COLUMNS
|
||||
if height is None:
|
||||
jupyter_lines = self._environ.get("JUPYTER_LINES")
|
||||
if jupyter_lines is not None and jupyter_lines.isdigit():
|
||||
height = int(jupyter_lines)
|
||||
else:
|
||||
height = JUPYTER_DEFAULT_LINES
|
||||
|
||||
self.soft_wrap = soft_wrap
|
||||
self._width = width
|
||||
|
|
|
@ -22,6 +22,8 @@ def report() -> None: # pragma: no cover
|
|||
"TERM_PROGRAM",
|
||||
"COLUMNS",
|
||||
"LINES",
|
||||
"JUPYTER_COLUMNS",
|
||||
"JUPYTER_LINES",
|
||||
"JPY_PARENT_PID",
|
||||
"VSCODE_VERBOSE_LOGGING",
|
||||
)
|
||||
|
|
37
rich/rule.py
37
rich/rule.py
|
@ -4,6 +4,7 @@ from .align import AlignMethod
|
|||
from .cells import cell_len, set_cell_size
|
||||
from .console import Console, ConsoleOptions, RenderResult
|
||||
from .jupyter import JupyterMixin
|
||||
from .measure import Measurement
|
||||
from .style import Style
|
||||
from .text import Text
|
||||
|
||||
|
@ -62,10 +63,7 @@ class Rule(JupyterMixin):
|
|||
|
||||
chars_len = cell_len(characters)
|
||||
if not self.title:
|
||||
rule_text = Text(characters * ((width // chars_len) + 1), self.style)
|
||||
rule_text.truncate(width)
|
||||
rule_text.plain = set_cell_size(rule_text.plain, width)
|
||||
yield rule_text
|
||||
yield self._rule_line(chars_len, width)
|
||||
return
|
||||
|
||||
if isinstance(self.title, Text):
|
||||
|
@ -75,10 +73,16 @@ class Rule(JupyterMixin):
|
|||
|
||||
title_text.plain = title_text.plain.replace("\n", " ")
|
||||
title_text.expand_tabs()
|
||||
rule_text = Text(end=self.end)
|
||||
|
||||
required_space = 4 if self.align == "center" else 2
|
||||
truncate_width = max(0, width - required_space)
|
||||
if not truncate_width:
|
||||
yield self._rule_line(chars_len, width)
|
||||
return
|
||||
|
||||
rule_text = Text(end=self.end)
|
||||
if self.align == "center":
|
||||
title_text.truncate(width - 4, overflow="ellipsis")
|
||||
title_text.truncate(truncate_width, overflow="ellipsis")
|
||||
side_width = (width - cell_len(title_text.plain)) // 2
|
||||
left = Text(characters * (side_width // chars_len + 1))
|
||||
left.truncate(side_width - 1)
|
||||
|
@ -89,12 +93,12 @@ class Rule(JupyterMixin):
|
|||
rule_text.append(title_text)
|
||||
rule_text.append(" " + right.plain, self.style)
|
||||
elif self.align == "left":
|
||||
title_text.truncate(width - 2, overflow="ellipsis")
|
||||
title_text.truncate(truncate_width, overflow="ellipsis")
|
||||
rule_text.append(title_text)
|
||||
rule_text.append(" ")
|
||||
rule_text.append(characters * (width - rule_text.cell_len), self.style)
|
||||
elif self.align == "right":
|
||||
title_text.truncate(width - 2, overflow="ellipsis")
|
||||
title_text.truncate(truncate_width, overflow="ellipsis")
|
||||
rule_text.append(characters * (width - title_text.cell_len - 1), self.style)
|
||||
rule_text.append(" ")
|
||||
rule_text.append(title_text)
|
||||
|
@ -102,14 +106,29 @@ class Rule(JupyterMixin):
|
|||
rule_text.plain = set_cell_size(rule_text.plain, width)
|
||||
yield rule_text
|
||||
|
||||
def _rule_line(self, chars_len: int, width: int) -> Text:
|
||||
rule_text = Text(self.characters * ((width // chars_len) + 1), self.style)
|
||||
rule_text.truncate(width)
|
||||
rule_text.plain = set_cell_size(rule_text.plain, width)
|
||||
return rule_text
|
||||
|
||||
def __rich_measure__(
|
||||
self, console: Console, options: ConsoleOptions
|
||||
) -> Measurement:
|
||||
return Measurement(1, 1)
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
from rich.console import Console
|
||||
import sys
|
||||
|
||||
from rich.console import Console
|
||||
|
||||
try:
|
||||
text = sys.argv[1]
|
||||
except IndexError:
|
||||
text = "Hello, World"
|
||||
console = Console()
|
||||
console.print(Rule(title=text))
|
||||
|
||||
console = Console()
|
||||
console.print(Rule("foo"), width=4)
|
||||
|
|
|
@ -59,7 +59,7 @@ class Style:
|
|||
_bgcolor: Optional[Color]
|
||||
_attributes: int
|
||||
_set_attributes: int
|
||||
_hash: int
|
||||
_hash: Optional[int]
|
||||
_null: bool
|
||||
_meta: Optional[bytes]
|
||||
|
||||
|
@ -190,16 +190,7 @@ class Style:
|
|||
self._link = link
|
||||
self._link_id = f"{randint(0, 999999)}" if link else ""
|
||||
self._meta = None if meta is None else dumps(meta)
|
||||
self._hash = hash(
|
||||
(
|
||||
self._color,
|
||||
self._bgcolor,
|
||||
self._attributes,
|
||||
self._set_attributes,
|
||||
link,
|
||||
self._meta,
|
||||
)
|
||||
)
|
||||
self._hash: Optional[int] = None
|
||||
self._null = not (self._set_attributes or color or bgcolor or link or meta)
|
||||
|
||||
@classmethod
|
||||
|
@ -227,17 +218,8 @@ class Style:
|
|||
style._link = None
|
||||
style._link_id = ""
|
||||
style._meta = None
|
||||
style._hash = hash(
|
||||
(
|
||||
color,
|
||||
bgcolor,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
)
|
||||
style._null = not (color or bgcolor)
|
||||
style._hash = None
|
||||
return style
|
||||
|
||||
@classmethod
|
||||
|
@ -257,16 +239,7 @@ class Style:
|
|||
style._link = None
|
||||
style._link_id = ""
|
||||
style._meta = dumps(meta)
|
||||
style._hash = hash(
|
||||
(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
style._meta,
|
||||
)
|
||||
)
|
||||
style._hash = None
|
||||
style._null = not (meta)
|
||||
return style
|
||||
|
||||
|
@ -366,6 +339,7 @@ class Style:
|
|||
Returns:
|
||||
str: String containing codes.
|
||||
"""
|
||||
|
||||
if self._ansi is None:
|
||||
sgr: List[str] = []
|
||||
append = sgr.append
|
||||
|
@ -446,16 +420,26 @@ class Style:
|
|||
def __eq__(self, other: Any) -> bool:
|
||||
if not isinstance(other, Style):
|
||||
return NotImplemented
|
||||
return (
|
||||
self._color == other._color
|
||||
and self._bgcolor == other._bgcolor
|
||||
and self._set_attributes == other._set_attributes
|
||||
and self._attributes == other._attributes
|
||||
and self._link == other._link
|
||||
and self._meta == other._meta
|
||||
)
|
||||
return self.__hash__() == other.__hash__()
|
||||
|
||||
def __ne__(self, other: Any) -> bool:
|
||||
if not isinstance(other, Style):
|
||||
return NotImplemented
|
||||
return self.__hash__() != other.__hash__()
|
||||
|
||||
def __hash__(self) -> int:
|
||||
if self._hash is not None:
|
||||
return self._hash
|
||||
self._hash = hash(
|
||||
(
|
||||
self._color,
|
||||
self._bgcolor,
|
||||
self._attributes,
|
||||
self._set_attributes,
|
||||
self._link,
|
||||
self._meta,
|
||||
)
|
||||
)
|
||||
return self._hash
|
||||
|
||||
@property
|
||||
|
@ -502,9 +486,9 @@ class Style:
|
|||
style._set_attributes = self._set_attributes
|
||||
style._link = self._link
|
||||
style._link_id = f"{randint(0, 999999)}" if self._link else ""
|
||||
style._hash = self._hash
|
||||
style._null = False
|
||||
style._meta = None
|
||||
style._hash = None
|
||||
return style
|
||||
|
||||
@classmethod
|
||||
|
@ -677,7 +661,7 @@ class Style:
|
|||
style._set_attributes = self._set_attributes
|
||||
style._link = link
|
||||
style._link_id = f"{randint(0, 999999)}" if link else ""
|
||||
style._hash = self._hash
|
||||
style._hash = None
|
||||
style._null = False
|
||||
style._meta = self._meta
|
||||
return style
|
||||
|
@ -700,7 +684,7 @@ class Style:
|
|||
"""
|
||||
if not text or color_system is None:
|
||||
return text
|
||||
attrs = self._make_ansi_codes(color_system)
|
||||
attrs = self._ansi or self._make_ansi_codes(color_system)
|
||||
rendered = f"\x1b[{attrs}m{text}\x1b[0m" if attrs else text
|
||||
if self._link and not legacy_windows:
|
||||
rendered = (
|
||||
|
@ -720,9 +704,8 @@ class Style:
|
|||
text = text or str(self)
|
||||
sys.stdout.write(f"{self.render(text)}\n")
|
||||
|
||||
def __add__(self, style: Optional["Style"]) -> "Style":
|
||||
if not (isinstance(style, Style) or style is None):
|
||||
return NotImplemented
|
||||
@lru_cache(maxsize=1024)
|
||||
def _add(self, style: Optional["Style"]) -> "Style":
|
||||
if style is None or style._null:
|
||||
return self
|
||||
if self._null:
|
||||
|
@ -738,14 +721,18 @@ class Style:
|
|||
new_style._set_attributes = self._set_attributes | style._set_attributes
|
||||
new_style._link = style._link or self._link
|
||||
new_style._link_id = style._link_id or self._link_id
|
||||
new_style._hash = style._hash
|
||||
new_style._null = style._null
|
||||
if self._meta and style._meta:
|
||||
new_style._meta = dumps({**self.meta, **style.meta})
|
||||
else:
|
||||
new_style._meta = self._meta or style._meta
|
||||
new_style._hash = None
|
||||
return new_style
|
||||
|
||||
def __add__(self, style: Optional["Style"]) -> "Style":
|
||||
combined_style = self._add(style)
|
||||
return combined_style.copy() if combined_style.link else combined_style
|
||||
|
||||
|
||||
NULL_STYLE = Style()
|
||||
|
||||
|
|
|
@ -3,5 +3,30 @@ from rich.console import Console
|
|||
|
||||
def test_jupyter():
|
||||
console = Console(force_jupyter=True)
|
||||
assert console.width == 93
|
||||
assert console.width == 115
|
||||
assert console.height == 100
|
||||
assert console.color_system == "truecolor"
|
||||
|
||||
|
||||
def test_jupyter_columns_env():
|
||||
console = Console(_environ={"JUPYTER_COLUMNS": "314"}, force_jupyter=True)
|
||||
assert console.width == 314
|
||||
# width take precedence
|
||||
console = Console(width=40, _environ={"JUPYTER_COLUMNS": "314"}, force_jupyter=True)
|
||||
assert console.width == 40
|
||||
# Should not fail
|
||||
console = Console(
|
||||
width=40, _environ={"JUPYTER_COLUMNS": "broken"}, force_jupyter=True
|
||||
)
|
||||
|
||||
|
||||
def test_jupyter_lines_env():
|
||||
console = Console(_environ={"JUPYTER_LINES": "220"}, force_jupyter=True)
|
||||
assert console.height == 220
|
||||
# height take precedence
|
||||
console = Console(height=40, _environ={"JUPYTER_LINES": "220"}, force_jupyter=True)
|
||||
assert console.height == 40
|
||||
# Should not fail
|
||||
console = Console(
|
||||
width=40, _environ={"JUPYTER_LINES": "broken"}, force_jupyter=True
|
||||
)
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from rich._lru_cache import LRUCache
|
||||
|
||||
|
||||
def test_lru_cache():
|
||||
cache = LRUCache(3)
|
||||
|
||||
# insert some values
|
||||
cache["foo"] = 1
|
||||
cache["bar"] = 2
|
||||
cache["baz"] = 3
|
||||
assert "foo" in cache
|
||||
|
||||
# Cache size is 3, so the following should kick oldest one out
|
||||
cache["egg"] = 4
|
||||
assert "foo" not in cache
|
||||
assert "egg" in "egg" in cache
|
||||
|
||||
# cache is now full
|
||||
# look up two keys
|
||||
cache["bar"]
|
||||
cache["baz"]
|
||||
|
||||
# Insert a new value
|
||||
cache["eggegg"] = 5
|
||||
# Check it kicked out the 'oldest' key
|
||||
assert "egg" not in cache
|
|
@ -61,6 +61,47 @@ def test_rule_cjk():
|
|||
assert console.file.getvalue() == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"align,outcome",
|
||||
[
|
||||
("center", "───\n"),
|
||||
("left", "… ─\n"),
|
||||
("right", "─ …\n"),
|
||||
],
|
||||
)
|
||||
def test_rule_not_enough_space_for_title_text(align, outcome):
|
||||
console = Console(width=3, file=io.StringIO(), record=True)
|
||||
console.rule("Hello!", align=align)
|
||||
assert console.file.getvalue() == outcome
|
||||
|
||||
|
||||
def test_rule_center_aligned_title_not_enough_space_for_rule():
|
||||
console = Console(width=4, file=io.StringIO(), record=True)
|
||||
console.rule("ABCD")
|
||||
assert console.file.getvalue() == "────\n"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("align", ["left", "right"])
|
||||
def test_rule_side_aligned_not_enough_space_for_rule(align):
|
||||
console = Console(width=2, file=io.StringIO(), record=True)
|
||||
console.rule("ABCD", align=align)
|
||||
assert console.file.getvalue() == "──\n"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"align,outcome",
|
||||
[
|
||||
("center", "─ … ─\n"),
|
||||
("left", "AB… ─\n"),
|
||||
("right", "─ AB…\n"),
|
||||
],
|
||||
)
|
||||
def test_rule_just_enough_width_available_for_title(align, outcome):
|
||||
console = Console(width=5, file=io.StringIO(), record=True)
|
||||
console.rule("ABCD", align=align)
|
||||
assert console.file.getvalue() == outcome
|
||||
|
||||
|
||||
def test_characters():
|
||||
console = Console(
|
||||
width=16,
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
import io
|
||||
from textwrap import dedent
|
||||
|
||||
import pytest
|
||||
|
||||
from rich import box
|
||||
from rich.console import Console
|
||||
from rich.rule import Rule
|
||||
from rich.table import Table
|
||||
|
||||
|
||||
@pytest.mark.parametrize("expand_kwarg", ({}, {"expand": False}))
|
||||
def test_rule_in_unexpanded_table(expand_kwarg):
|
||||
console = Console(width=32, file=io.StringIO(), legacy_windows=False, _environ={})
|
||||
table = Table(box=box.ASCII, show_header=False, **expand_kwarg)
|
||||
table.add_column()
|
||||
table.add_column()
|
||||
table.add_row("COL1", "COL2")
|
||||
table.add_row("COL1", Rule())
|
||||
table.add_row("COL1", "COL2")
|
||||
console.print(table)
|
||||
expected = dedent(
|
||||
"""\
|
||||
+-------------+
|
||||
| COL1 | COL2 |
|
||||
| COL1 | ──── |
|
||||
| COL1 | COL2 |
|
||||
+-------------+
|
||||
"""
|
||||
)
|
||||
result = console.file.getvalue()
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_rule_in_expanded_table():
|
||||
console = Console(width=32, file=io.StringIO(), legacy_windows=False, _environ={})
|
||||
table = Table(box=box.ASCII, expand=True, show_header=False)
|
||||
table.add_column()
|
||||
table.add_column()
|
||||
table.add_row("COL1", "COL2")
|
||||
table.add_row("COL1", Rule(style=None))
|
||||
table.add_row("COL1", "COL2")
|
||||
console.print(table)
|
||||
expected = dedent(
|
||||
"""\
|
||||
+------------------------------+
|
||||
| COL1 | COL2 |
|
||||
| COL1 | ──────────── |
|
||||
| COL1 | COL2 |
|
||||
+------------------------------+
|
||||
"""
|
||||
)
|
||||
result = console.file.getvalue()
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_rule_in_ratio_table():
|
||||
console = Console(width=32, file=io.StringIO(), legacy_windows=False, _environ={})
|
||||
table = Table(box=box.ASCII, expand=True, show_header=False)
|
||||
table.add_column(ratio=1)
|
||||
table.add_column()
|
||||
table.add_row("COL1", "COL2")
|
||||
table.add_row("COL1", Rule(style=None))
|
||||
table.add_row("COL1", "COL2")
|
||||
console.print(table)
|
||||
expected = dedent(
|
||||
"""\
|
||||
+------------------------------+
|
||||
| COL1 | COL2 |
|
||||
| COL1 | ──── |
|
||||
| COL1 | COL2 |
|
||||
+------------------------------+
|
||||
"""
|
||||
)
|
||||
result = console.file.getvalue()
|
||||
assert result == expected
|
|
@ -46,17 +46,15 @@ def test_spinner_update():
|
|||
spinner = Spinner("dots")
|
||||
console.print(spinner)
|
||||
|
||||
spinner.update(text="Bar", style="green", speed=2)
|
||||
time += 80 / 1000
|
||||
console.print(spinner)
|
||||
rule = Rule("Bar")
|
||||
|
||||
spinner.update(text=Rule("Bar"))
|
||||
spinner.update(text=rule)
|
||||
time += 80 / 1000
|
||||
console.print(spinner)
|
||||
|
||||
result = console.end_capture()
|
||||
print(repr(result))
|
||||
expected = f"⠋\n\x1b[32m⠙\x1b[0m Bar\n\x1b[32m⠸\x1b[0m \x1b[92m────── \x1b[0mBar\x1b[92m ───────\x1b[0m\n"
|
||||
expected = "⠋\n⠙ \x1b[92m─\x1b[0m\n"
|
||||
assert result == expected
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import pytest
|
||||
|
||||
from rich.color import Color, ColorSystem, ColorType
|
||||
from rich import errors
|
||||
from rich.color import Color, ColorSystem, ColorType
|
||||
from rich.style import Style, StyleStack
|
||||
|
||||
|
||||
|
@ -168,7 +168,6 @@ def test_test():
|
|||
|
||||
def test_add():
|
||||
assert Style(color="red") + None == Style(color="red")
|
||||
assert Style().__add__("foo") == NotImplemented
|
||||
|
||||
|
||||
def test_iadd():
|
||||
|
|
Loading…
Reference in New Issue