Skip to content

Commit d4d4f71

Browse files
authored
Merge pull request #5851 from Textualize/lenient-markup
Lenient markup
2 parents 29297c4 + 2f8f5b9 commit d4d4f71

File tree

4 files changed

+42
-18
lines changed

4 files changed

+42
-18
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1717

1818
- Added experimental opt-in support for https://github.com/willmcgugan/textual-speedups
1919

20+
### Changed
21+
22+
- Content markup is now more lenient; if a 'tag' doesn't contain a valid style it will be included verbatim. https://github.com/Textualize/textual/pull/5851
23+
2024
## [3.3.0] - 2025-06-01
2125

2226
### Fixed

src/textual/markup.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,9 @@ class MarkupError(Exception):
4848
variable_ref=VARIABLE_REF,
4949
whitespace=r"\s+",
5050
)
51-
.expect_eof(False)
51+
.expect_eof(True)
5252
.expect_semicolon(False)
53+
.extract_text(True)
5354
)
5455

5556
expect_markup = Expect(
@@ -374,14 +375,36 @@ def process_text(template_text: str, /) -> str:
374375
elif token_name == "open_tag":
375376
tag_text = []
376377

378+
eof = False
379+
contains_text = False
377380
for token in iter_tokens:
378381
if token.name == "end_tag":
379382
break
383+
elif token.name == "text":
384+
contains_text = True
385+
elif token.name == "eof":
386+
eof = True
380387
tag_text.append(token.value)
381-
opening_tag = "".join(tag_text).strip()
382-
style_stack.append(
383-
(position, opening_tag, normalize_markup_tag(opening_tag))
384-
)
388+
if contains_text or eof:
389+
# "tag" was unparsable
390+
text_content = f"[{''.join(tag_text)}" + ("" if eof else "]")
391+
text_append(text_content)
392+
position += len(text_content)
393+
else:
394+
opening_tag = "".join(tag_text)
395+
396+
if not opening_tag.strip():
397+
blank_tag = f"[{opening_tag}]"
398+
text_append(blank_tag)
399+
position += len(blank_tag)
400+
else:
401+
style_stack.append(
402+
(
403+
position,
404+
opening_tag,
405+
normalize_markup_tag(opening_tag.strip()),
406+
)
407+
)
385408

386409
elif token_name == "open_closing_tag":
387410
tag_text = []

tests/test_content.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ def test_assemble():
229229
("\\[/foo", "[/foo"),
230230
("\\[/foo]", "[/foo]"),
231231
("\\[]", "[]"),
232+
("\\[0]", "[0]"),
232233
],
233234
)
234235
def test_escape(markup: str, plain: str) -> None:
@@ -273,14 +274,3 @@ def test_first_line():
273274
first_line = content.first_line
274275
assert first_line.plain == "foo"
275276
assert first_line.spans == [Span(0, 3, "red")]
276-
277-
278-
def test_errors():
279-
with pytest.raises(Exception):
280-
Content.from_markup("[")
281-
282-
with pytest.raises(Exception):
283-
Content.from_markup("[:")
284-
285-
with pytest.raises(Exception):
286-
Content.from_markup("[foo")

tests/test_markup.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@
1010
["markup", "content"],
1111
[
1212
("", Content("")),
13+
("[", Content("[")),
14+
("[]", Content("[]")),
15+
("[ ", Content("[ ")),
16+
("[ ", Content("[ ")),
17+
("[ ]", Content("[ ]")),
18+
("[0", Content("[0")),
19+
("[0]", Content("[0]")),
20+
("[red", Content("[red")),
21+
("[red]", Content("")),
1322
("foo", Content("foo")),
1423
("foo\n", Content("foo\n")),
1524
("foo\nbar", Content("foo\nbar")),
@@ -152,8 +161,6 @@ def test_to_content(markup: str, content: Content):
152161

153162

154163
def test_content_parse_fail() -> None:
155-
with pytest.raises(MarkupError):
156-
to_content("[rgb(1,2,3,4)]foo")
157164
with pytest.raises(MarkupError):
158165
to_content("[foo]foo[/bar]")
159166
with pytest.raises(MarkupError):

0 commit comments

Comments
 (0)