mirror of
https://github.com/Textualize/rich.git
synced 2025-08-04 10:08:40 +00:00
color parsing
This commit is contained in:
parent
977dbcfebc
commit
1f09278776
9 changed files with 74 additions and 37 deletions
|
@ -5,6 +5,13 @@ 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).
|
||||
|
||||
## [5.0.0] - 2020-08-01
|
||||
|
||||
### Changed
|
||||
|
||||
- Change to console markup syntax to not parse Python structures as markup, i.e. `[1,2,3]` is not treated as a literal, not a tag.
|
||||
- Standard color numbers syntax has changed to `"color(<number>)"` so that `[5]` (for example) is considered a literal.
|
||||
|
||||
## [4.2.2] - 2020-07-30
|
||||
|
||||
### Changed
|
||||
|
|
|
@ -16,9 +16,9 @@ To specify a foreground color use one of the 256 :ref:`appendix-colors`. For exa
|
|||
|
||||
console.print("Hello", style="magenta")
|
||||
|
||||
You may also use the color's number (an integer between 0 and 255). The following will give the equivalent output::
|
||||
You may also use the color's number (an integer between 0 and 255) with the syntax `"color(<number>)"`. The following will give the equivalent output::
|
||||
|
||||
console.print("Hello", style="5")
|
||||
console.print("Hello", style="color(5)")
|
||||
|
||||
Alteratively you can use a CSS-like syntax to specify a color with a "#" followed by three pairs of hex characters, or in RGB form with three decimal integers. The following two lines both print "Hello" in the same color (purple)::
|
||||
|
||||
|
|
|
@ -27,3 +27,24 @@ Since building Text instances from parts is a common requirement, Rich offers :m
|
|||
console.print(text)
|
||||
|
||||
You can apply a style to given words in the text with :meth:`~rich.text.Text.highlight_words` or for ultimate control call :meth:`~rich.text.Text.highlight_regex` to highlight text matching a *regular expression*.
|
||||
|
||||
|
||||
Text attributes
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
The Text class has a number of parameters you can set on the constructor to modify how the text is display.
|
||||
|
||||
- ``justify`` should be "left", "center", "right", or "full", and will override default justify behaviour if set.
|
||||
- ``overflow`` should be "fold", "crop", or "ellipsis" and will override default overflow if set.
|
||||
- ``no_wrap`` prevents wrapping if the text is longer then the available width.
|
||||
- ``tab_size`` Sets the number of characters in a tab.
|
||||
|
||||
A Text instance may be used in place of a plain string virtually everywhere in the Rich API, which gives you a lot of control in how text is displays within other Rich renderables. For instance, the following right aligns text within a :class:`rich.panel.Panel`::
|
||||
|
||||
from rich import print
|
||||
from rich.panel import Panel
|
||||
from rich.text import Text
|
||||
panel = Panel(Text("Hello", justify="right"))
|
||||
print(panel)
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
name = "rich"
|
||||
homepage = "https://github.com/willmcgugan/rich"
|
||||
documentation = "https://rich.readthedocs.io/en/latest/"
|
||||
version = "4.2.2"
|
||||
version = "5.0.0"
|
||||
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
||||
authors = ["Will McGugan <willmcgugan@gmail.com>"]
|
||||
license = "MIT"
|
||||
|
|
|
@ -24,7 +24,7 @@ class ColorBox:
|
|||
for color_start in range(start, start + 36, 6):
|
||||
text = Text()
|
||||
for color_no in range(color_start, color_start + 6):
|
||||
text.append(" ", Style(bgcolor=str(color_no)))
|
||||
text.append(" ", Style(bgcolor=f"color({color_no})"))
|
||||
yield text
|
||||
|
||||
def __rich_measure__(self, console: "Console", max_width: int) -> Measurement:
|
||||
|
|
|
@ -249,7 +249,7 @@ class ColorParseError(Exception):
|
|||
RE_COLOR = re.compile(
|
||||
r"""^
|
||||
\#([0-9a-f]{6})$|
|
||||
([0-9]{1,3})$|
|
||||
color\(([0-9]{1,3})\)$|
|
||||
rgb\(([\d\s,]+)\)$
|
||||
""",
|
||||
re.VERBOSE,
|
||||
|
@ -338,16 +338,12 @@ class Color(NamedTuple):
|
|||
if color == "default":
|
||||
return cls(color, type=ColorType.DEFAULT)
|
||||
|
||||
named_color_number = ANSI_COLOR_NAMES.get(color)
|
||||
if named_color_number is not None:
|
||||
color_number = ANSI_COLOR_NAMES.get(color)
|
||||
if color_number is not None:
|
||||
return cls(
|
||||
color,
|
||||
type=(
|
||||
ColorType.STANDARD
|
||||
if named_color_number < 16
|
||||
else ColorType.EIGHT_BIT
|
||||
),
|
||||
number=named_color_number,
|
||||
type=(ColorType.STANDARD if color_number < 16 else ColorType.EIGHT_BIT),
|
||||
number=color_number,
|
||||
)
|
||||
|
||||
color_match = RE_COLOR.match(color)
|
||||
|
@ -355,22 +351,22 @@ class Color(NamedTuple):
|
|||
raise ColorParseError(f"{color!r} is not a valid color")
|
||||
|
||||
color_24, color_8, color_rgb = color_match.groups()
|
||||
if color_8:
|
||||
number = int(color_8)
|
||||
if number > 255:
|
||||
raise ColorParseError(f"8bit colors must be <= 255 in {color!r}")
|
||||
return cls(
|
||||
color,
|
||||
ColorType.STANDARD if number < 16 else ColorType.EIGHT_BIT,
|
||||
number=number,
|
||||
)
|
||||
|
||||
elif color_24:
|
||||
if color_24:
|
||||
triplet = ColorTriplet(
|
||||
int(color_24[0:2], 16), int(color_24[2:4], 16), int(color_24[4:6], 16)
|
||||
)
|
||||
return cls(color, ColorType.TRUECOLOR, triplet=triplet)
|
||||
|
||||
elif color_8:
|
||||
number = int(color_8)
|
||||
if number > 255:
|
||||
raise ColorParseError(f"color number must be <= 255 in {color!r}")
|
||||
return cls(
|
||||
color,
|
||||
type=(ColorType.STANDARD if number < 16 else ColorType.EIGHT_BIT),
|
||||
number=number,
|
||||
)
|
||||
|
||||
else: # color_rgb:
|
||||
components = color_rgb.split(",")
|
||||
if len(components) != 3:
|
||||
|
|
|
@ -7,7 +7,7 @@ from .text import Span, Text
|
|||
from ._emoji_replace import _emoji_replace
|
||||
|
||||
|
||||
re_tags = re.compile(r"(\[\[)|(\]\])|\[(.*?)\]")
|
||||
re_tags = re.compile(r"(\[\[)|(\]\])|\[([a-zA-Z\-_#\/].*?)\]")
|
||||
|
||||
|
||||
class Tag(NamedTuple):
|
||||
|
@ -151,6 +151,7 @@ if __name__ == "__main__": # pragma: no cover
|
|||
# t = Text.from_markup('Hello [link="https://www.willmcgugan.com"]W[b]o[/b]rld[/]!')
|
||||
# print(repr(t._spans))
|
||||
|
||||
console.print("Hello [1], [1,2,3] ['hello']")
|
||||
console.print("foo")
|
||||
console.print("Hello [link=https://www.willmcgugan.com]W[b]o[/b]rld[/]!")
|
||||
|
||||
|
|
|
@ -34,8 +34,8 @@ def test_windows() -> None:
|
|||
def test_truecolor() -> None:
|
||||
assert Color.parse("#ff0000").get_truecolor() == ColorTriplet(255, 0, 0)
|
||||
assert Color.parse("red").get_truecolor() == ColorTriplet(128, 0, 0)
|
||||
assert Color.parse("1").get_truecolor() == ColorTriplet(128, 0, 0)
|
||||
assert Color.parse("17").get_truecolor() == ColorTriplet(0, 0, 95)
|
||||
assert Color.parse("color(1)").get_truecolor() == ColorTriplet(128, 0, 0)
|
||||
assert Color.parse("color(17)").get_truecolor() == ColorTriplet(0, 0, 95)
|
||||
assert Color.parse("default").get_truecolor() == ColorTriplet(0, 0, 0)
|
||||
assert Color.parse("default").get_truecolor(foreground=False) == ColorTriplet(
|
||||
255, 255, 255
|
||||
|
@ -50,7 +50,9 @@ def test_parse_success() -> None:
|
|||
assert Color.parse("red") == Color("red", ColorType.STANDARD, 1, None)
|
||||
assert Color.parse("bright_red") == Color("bright_red", ColorType.STANDARD, 9, None)
|
||||
assert Color.parse("yellow4") == Color("yellow4", ColorType.EIGHT_BIT, 106, None)
|
||||
assert Color.parse("100") == Color("100", ColorType.EIGHT_BIT, 100, None)
|
||||
assert Color.parse("color(100)") == Color(
|
||||
"color(100)", ColorType.EIGHT_BIT, 100, None
|
||||
)
|
||||
assert Color.parse("#112233") == Color(
|
||||
"#112233", ColorType.TRUECOLOR, None, ColorTriplet(0x11, 0x22, 0x33)
|
||||
)
|
||||
|
@ -72,6 +74,8 @@ def test_default() -> None:
|
|||
def test_parse_error() -> None:
|
||||
with pytest.raises(ColorParseError):
|
||||
Color.parse("256")
|
||||
with pytest.raises(ColorParseError):
|
||||
Color.parse("color(256)")
|
||||
with pytest.raises(ColorParseError):
|
||||
Color.parse("rgb(999,0,0)")
|
||||
with pytest.raises(ColorParseError):
|
||||
|
@ -89,15 +93,17 @@ def test_get_ansi_codes() -> None:
|
|||
assert Color.parse("default").get_ansi_codes(False) == ("49",)
|
||||
assert Color.parse("red").get_ansi_codes() == ("31",)
|
||||
assert Color.parse("red").get_ansi_codes(False) == ("41",)
|
||||
assert Color.parse("1").get_ansi_codes() == ("31",)
|
||||
assert Color.parse("1").get_ansi_codes(False) == ("41",)
|
||||
assert Color.parse("color(1)").get_ansi_codes() == ("31",)
|
||||
assert Color.parse("color(1)").get_ansi_codes(False) == ("41",)
|
||||
assert Color.parse("#ff0000").get_ansi_codes() == ("38", "2", "255", "0", "0")
|
||||
assert Color.parse("#ff0000").get_ansi_codes(False) == ("48", "2", "255", "0", "0")
|
||||
|
||||
|
||||
def test_downgrade() -> None:
|
||||
|
||||
assert Color.parse("9").downgrade(0) == Color("9", ColorType.STANDARD, 9, None)
|
||||
assert Color.parse("color(9)").downgrade(0) == Color(
|
||||
"color(9)", ColorType.STANDARD, 9, None
|
||||
)
|
||||
|
||||
assert Color.parse("#000000").downgrade(ColorSystem.EIGHT_BIT) == Color(
|
||||
"#000000", ColorType.EIGHT_BIT, 16, None
|
||||
|
@ -119,12 +125,12 @@ def test_downgrade() -> None:
|
|||
"#ff0000", ColorType.STANDARD, 1, None
|
||||
)
|
||||
|
||||
assert Color.parse("9").downgrade(ColorSystem.STANDARD) == Color(
|
||||
"9", ColorType.STANDARD, 9, None
|
||||
assert Color.parse("color(9)").downgrade(ColorSystem.STANDARD) == Color(
|
||||
"color(9)", ColorType.STANDARD, 9, None
|
||||
)
|
||||
|
||||
assert Color.parse("20").downgrade(ColorSystem.STANDARD) == Color(
|
||||
"20", ColorType.STANDARD, 4, None
|
||||
assert Color.parse("color(20)").downgrade(ColorSystem.STANDARD) == Color(
|
||||
"color(20)", ColorType.STANDARD, 4, None
|
||||
)
|
||||
|
||||
assert Color.parse("red").downgrade(ColorSystem.WINDOWS) == Color(
|
||||
|
@ -139,8 +145,8 @@ def test_downgrade() -> None:
|
|||
"#ff0000", ColorType.WINDOWS, 1, None
|
||||
)
|
||||
|
||||
assert Color.parse("255").downgrade(ColorSystem.WINDOWS) == Color(
|
||||
"255", ColorType.WINDOWS, 7, None
|
||||
assert Color.parse("color(255)").downgrade(ColorSystem.WINDOWS) == Color(
|
||||
"color(255)", ColorType.WINDOWS, 7, None
|
||||
)
|
||||
|
||||
assert Color.parse("#00ff00").downgrade(ColorSystem.STANDARD) == Color(
|
||||
|
|
|
@ -40,6 +40,12 @@ def test_render():
|
|||
assert result.spans == [Span(0, 3, "bold")]
|
||||
|
||||
|
||||
def test_render_not_tags():
|
||||
result = render('[1], [1,2,3,4], ["hello"]')
|
||||
assert str(result) == '[1], [1,2,3,4], ["hello"]'
|
||||
assert result.spans == []
|
||||
|
||||
|
||||
def test_render_link():
|
||||
result = render("[link=foo]FOO[/link]")
|
||||
assert str(result) == "FOO"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue