markup syntax

This commit is contained in:
Will McGugan 2020-08-01 21:23:51 +01:00
parent 1f09278776
commit 7da3ee6299
4 changed files with 43 additions and 19 deletions

View file

@ -38,17 +38,14 @@ If your terminal software supports hyperlinks, you will be able to click the wor
Escaping
~~~~~~~~
Occasionally you may want to print something that Rich would interpret as markup. You can *escape* square brackets by doubling them up. Here's an example::
Occasionally you may want to print something that Rich would interpret as markup. You can *escape* a tab by preceding it with backslash. Here's an example::
>>> from rich import print
>>> print("foo[[bar]]")
>>> print("foo\[bar]")
foo[bar]
The function :func:`~rich.markup.escape` will handle escape of text for you.
.. warning::
Be careful when using f-strings with console markup. You will need to escape any variables if they could contain square brackets.
Rendering Markup
----------------

View file

@ -116,6 +116,10 @@ You can also use these custom styles via markup. For example::
console.print("[warning]The pod bay doors are locked[/warning]")
.. note::
style names must be lower case, start with a letter, and only contain letters or the characters ``"."``, ``"-"``, ``"_"``.
If you prefer you can write your styles in an external config file rather than in Python. Here's an example of the format::
[styles]

View file

@ -7,7 +7,13 @@ from .text import Span, Text
from ._emoji_replace import _emoji_replace
re_tags = re.compile(r"(\[\[)|(\]\])|\[([a-zA-Z\-_#\/].*?)\]")
RE_TAGS = re.compile(
r"""
(\\\[)|
\[([a-z#\/].*?)\]
""",
re.VERBOSE,
)
class Tag(NamedTuple):
@ -41,7 +47,7 @@ def escape(markup: str) -> str:
Returns:
str: Markup with square brackets escaped.
"""
return markup.replace("[", "[[").replace("]", "]]")
return markup.replace("[", "\[")
def _parse(markup: str) -> Iterable[Tuple[int, Optional[str], Optional[Tag]]]:
@ -52,19 +58,19 @@ def _parse(markup: str) -> Iterable[Tuple[int, Optional[str], Optional[Tag]]]:
"""
position = 0
for match in re_tags.finditer(markup):
escape_open, escape_close, tag_text = match.groups()
for match in RE_TAGS.finditer(markup):
(escape_open, tag_text) = match.groups()
start, end = match.span()
if start > position:
yield start, markup[position:start], None
if tag_text:
if escape_open:
yield start, "[", None
else:
text, equals, parameters = tag_text.partition("=")
if equals:
yield start, None, Tag(text, parameters)
else:
yield start, None, Tag(tag_text.strip(), None)
else:
yield start, (escape_open and "[") or (escape_close and "]"), None # type: ignore
position = end
if position < len(markup):
yield position, markup[position:], None

View file

@ -1,15 +1,33 @@
import pytest
from rich.markup import escape, MarkupError, _parse, render, Tag
from rich.markup import escape, MarkupError, _parse, render, Tag, RE_TAGS
from rich.text import Span
def test_re_no_match():
assert RE_TAGS.match("[True]") == None
assert RE_TAGS.match("[False]") == None
assert RE_TAGS.match("[None]") == None
assert RE_TAGS.match("[1]") == None
assert RE_TAGS.match("[2]") == None
assert RE_TAGS.match("[]") == None
def test_re_match():
assert RE_TAGS.match("[true]")
assert RE_TAGS.match("[false]")
assert RE_TAGS.match("[none]")
assert RE_TAGS.match("[color(1)]")
assert RE_TAGS.match("[#ff00ff]")
assert RE_TAGS.match("[/]")
def test_escape():
assert escape("foo[bar]") == "foo[[bar]]"
assert escape("foo[bar]") == "foo\[bar]"
def test_parse():
result = list(_parse("[foo]hello[/foo][bar]world[/][[escaped]]"))
result = list(_parse("[foo]hello[/foo][bar]world[/]\[escaped]"))
expected = [
(0, None, Tag(name="foo", parameters=None)),
(10, "hello", None),
@ -18,8 +36,7 @@ def test_parse():
(26, "world", None),
(26, None, Tag(name="/", parameters=None)),
(29, "[", None),
(38, "escaped", None),
(38, "]", None),
(31, "escaped]", None),
]
assert result == expected
@ -41,8 +58,8 @@ def test_render():
def test_render_not_tags():
result = render('[1], [1,2,3,4], ["hello"]')
assert str(result) == '[1], [1,2,3,4], ["hello"]'
result = render('[[1], [1,2,3,4], ["hello"], [None], [False], [True]]')
assert str(result) == '[[1], [1,2,3,4], ["hello"], [None], [False], [True]]'
assert result.spans == []