1
1
from __future__ import annotations
2
2
3
+ __all__ = ["MarkupError" , "escape" , "to_content" ]
4
+
3
5
import re
4
6
from ast import literal_eval
5
7
from operator import attrgetter
15
17
Union ,
16
18
)
17
19
20
+ from textual .css .tokenize import (
21
+ COLOR ,
22
+ PERCENT ,
23
+ TOKEN ,
24
+ VARIABLE_REF ,
25
+ Expect ,
26
+ TokenizerState ,
27
+ )
18
28
from textual .style import Style
19
29
20
30
if TYPE_CHECKING :
@@ -25,7 +35,63 @@ class MarkupError(Exception):
25
35
"""An error occurred parsing Textual markup."""
26
36
27
37
28
- __all__ = ["MarkupError" , "escape" , "to_content" ]
38
+ expect_markup_tag = Expect (
39
+ "style token" ,
40
+ end_tag = r"(?<!\\)\]" ,
41
+ key = r"[@a-zA-Z_-][a-zA-Z0-9_-]*=" ,
42
+ percent = PERCENT ,
43
+ color = COLOR ,
44
+ token = TOKEN ,
45
+ variable_ref = VARIABLE_REF ,
46
+ whitespace = r"\s+" ,
47
+ )
48
+
49
+ expect_markup = Expect (
50
+ "markup token" ,
51
+ open_closing_tag = r"(?<!\\)\[/" ,
52
+ open_tag = r"(?<!\\)\[" ,
53
+ end_tag = r"(?<!\\)\]" ,
54
+ ).extract_text ()
55
+
56
+ expect_markup_expression = Expect (
57
+ "markup" ,
58
+ end_tag = r"(?<!\\)\]" ,
59
+ word = r"\w+" ,
60
+ period = r"\." ,
61
+ round_start = r"\(" ,
62
+ round_end = r"\)" ,
63
+ square_start = r"\[" ,
64
+ square_end = r"\]" ,
65
+ curly_start = r"\{" ,
66
+ curly_end = r"\}" ,
67
+ comma = "," ,
68
+ whitespace = r"\s+" ,
69
+ double_string = r"\".*?\"" ,
70
+ single_string = r"'.*?'" ,
71
+ )
72
+
73
+
74
+ class MarkupTokenizer (TokenizerState ):
75
+ """Tokenizes Textual markup."""
76
+
77
+ EXPECT = expect_markup .expect_eof (True )
78
+ STATE_MAP = {
79
+ "open_tag" : expect_markup_tag ,
80
+ "open_closing_tag" : expect_markup_tag ,
81
+ "end_tag" : expect_markup ,
82
+ "key" : expect_markup_expression ,
83
+ }
84
+ STATE_PUSH = {
85
+ "round_start" : expect_markup_expression ,
86
+ "square_start" : expect_markup_expression ,
87
+ "curly_start" : expect_markup_expression ,
88
+ }
89
+ STATE_POP = {
90
+ "round_end" : "round_start" ,
91
+ "square_end" : "square_start" ,
92
+ "curly_end" : "curly_start" ,
93
+ }
94
+
29
95
30
96
RE_TAGS = re .compile (
31
97
r"""((\\*)\[([\$a-z#/@][^[]*?)])""" ,
@@ -248,6 +314,68 @@ def pop_style(style_name: str) -> Tuple[int, Tag]:
248
314
return content
249
315
250
316
317
+ def to_content (markup : str , style : str | Style = "" ) -> Content :
318
+
319
+ from textual .content import Content , Span
320
+
321
+ tokenizer = MarkupTokenizer ()
322
+ text : list [str ] = []
323
+ iter_tokens = iter (tokenizer (markup , ("inline" , "" )))
324
+
325
+ style_stack : list [tuple [int , str ]] = []
326
+
327
+ spans : list [Span ] = []
328
+
329
+ position = 0
330
+ tag_text : list [str ]
331
+ for token in iter_tokens :
332
+ print (repr (token ))
333
+ token_name = token .name
334
+ if token_name == "text" :
335
+ text .append (token .value )
336
+ position += len (token .value )
337
+ elif token_name == "open_tag" :
338
+ tag_text = []
339
+ print ("open" )
340
+ for token in iter_tokens :
341
+ print (" " , repr (token ))
342
+ if token .name == "end_tag" :
343
+ break
344
+ tag_text .append (token .value )
345
+ opening_tag = "" .join (tag_text )
346
+ style_stack .append ((position , opening_tag ))
347
+
348
+ elif token_name == "open_closing_tag" :
349
+ tag_text = []
350
+ print ("closing" )
351
+ for token in iter_tokens :
352
+ print (" " , repr (token ))
353
+ if token .name == "end_tag" :
354
+ break
355
+ tag_text .append (token .value )
356
+ closing_tag = "" .join (tag_text )
357
+ if closing_tag :
358
+ for index , (tag_position , tag_body ) in enumerate (reversed (style_stack )):
359
+ if tag_body == closing_tag :
360
+ style_stack .pop (- index )
361
+ spans .append (Span (tag_position , position , tag_body ))
362
+ break
363
+
364
+ else :
365
+ open_position , tag = style_stack .pop ()
366
+ spans .append (Span (open_position , position , tag ))
367
+
368
+ content_text = "" .join (text )
369
+ text_length = len (content_text )
370
+ while style_stack :
371
+ position , tag = style_stack .pop ()
372
+ spans .append (Span (position , text_length , tag ))
373
+
374
+ content = Content (content_text , spans )
375
+ print (repr (content ))
376
+ return content
377
+
378
+
251
379
if __name__ == "__main__" : # pragma: no cover
252
380
from rich .highlighter import ReprHighlighter
253
381
@@ -296,7 +424,10 @@ def on_markup_changed(self, event: TextArea.Changed) -> None:
296
424
results .update (event .text_area .text )
297
425
except Exception as error :
298
426
highlight = ReprHighlighter ()
299
- results .update (highlight (str (error )))
427
+ # results.update(highlight(str(error)))
428
+ from rich .traceback import Traceback
429
+
430
+ results .update (Traceback ())
300
431
self .query_one ("#results-container" ).add_class ("-error" )
301
432
else :
302
433
self .query_one ("#results-container" ).remove_class ("-error" )
0 commit comments