1010
1111# 3rd party imports
1212from docopt import DocoptExit , Option , docopt , extras
13+ from rich .markup import escape
1314from rich .text import Text
1415from rich .tree import Tree
1516from ruamel .yaml import YAML
2627# =============================================================================
2728
2829
30+ def _escape_for_rich (content ):
31+ """Escape user content for safe Rich printing.
32+
33+ Rich interprets square brackets [...] as markup syntax.
34+ This causes issues with user content containing:
35+ - [/path] patterns -> MarkupError crash (interpreted as closing tag)
36+ - [bug] tags -> Silently stripped (interpreted as invalid style)
37+
38+ Parameters
39+ ----------
40+ content : any
41+ User-provided content to escape
42+
43+ Returns
44+ -------
45+ str or Text
46+ Empty string for None, Text objects unchanged, escaped string otherwise
47+ """
48+ if content is None :
49+ return ""
50+ if isinstance (content , Text ):
51+ # Text objects are already safe Rich objects (e.g., hyperlinks)
52+ return content
53+ return escape (str (content ))
54+
55+
2956def _needs_yaml_quoting (value ):
3057 """Check if a string value needs YAML quoting.
3158
@@ -72,12 +99,12 @@ def display_task_rich(console, task_dict, phabfive_instance):
7299 # Multi-line value
73100 console .print (f" { key } : |-" )
74101 for line in str (value ).splitlines ():
75- console .print (f" { line } " )
102+ console .print (f" { _escape_for_rich ( line ) } " )
76103 elif _needs_yaml_quoting (value ):
77- escaped = value .replace ("'" , "''" )
78- console .print (f" { key } : '{ escaped } '" )
104+ escaped = str ( value ) .replace ("'" , "''" )
105+ console .print (f" { key } : '{ _escape_for_rich ( escaped ) } '" )
79106 else :
80- console .print (f" { key } : { value } " )
107+ console .print (f" { key } : { _escape_for_rich ( value ) } " )
81108
82109 # Print Assignee
83110 if assignee :
@@ -127,10 +154,10 @@ def display_task_rich(console, task_dict, phabfive_instance):
127154 )
128155 continue
129156 if _needs_yaml_quoting (value ):
130- escaped = value .replace ("'" , "''" )
131- console .print (f" { key } : '{ escaped } '" )
157+ escaped = str ( value ) .replace ("'" , "''" )
158+ console .print (f" { key } : '{ _escape_for_rich ( escaped ) } '" )
132159 else :
133- console .print (f" { key } : { value } " )
160+ console .print (f" { key } : { _escape_for_rich ( value ) } " )
134161
135162 # Print History section
136163 if history :
@@ -139,13 +166,13 @@ def display_task_rich(console, task_dict, phabfive_instance):
139166 if hist_key == "Boards" and isinstance (hist_value , dict ):
140167 console .print (" Boards:" )
141168 for board_name , transitions in hist_value .items ():
142- console .print (f" { board_name } :" )
169+ console .print (f" { _escape_for_rich ( board_name ) } :" )
143170 for trans in transitions :
144- console .print (f" - { trans } " )
171+ console .print (f" - { _escape_for_rich ( trans ) } " )
145172 elif isinstance (hist_value , list ):
146173 console .print (f" { hist_key } :" )
147174 for trans in hist_value :
148- console .print (f" - { trans } " )
175+ console .print (f" - { _escape_for_rich ( trans ) } " )
149176
150177 # Print Comments section
151178 comments = task_dict .get ("Comments" , [])
@@ -155,11 +182,11 @@ def display_task_rich(console, task_dict, phabfive_instance):
155182 if isinstance (comment , PreservedScalarString ) or "\n " in str (comment ):
156183 # Multi-line comment
157184 lines = str (comment ).splitlines ()
158- console .print (f" - { lines [0 ]} " )
185+ console .print (f" - { _escape_for_rich ( lines [0 ]) } " )
159186 for line in lines [1 :]:
160- console .print (f" { line } " )
187+ console .print (f" { _escape_for_rich ( line ) } " )
161188 else :
162- console .print (f" - { comment } " )
189+ console .print (f" - { _escape_for_rich ( comment ) } " )
163190
164191 # Print Metadata section
165192 if metadata :
@@ -169,11 +196,11 @@ def display_task_rich(console, task_dict, phabfive_instance):
169196 if meta_value :
170197 console .print (f" { meta_key } :" )
171198 for item in meta_value :
172- console .print (f" - { item } " )
199+ console .print (f" - { _escape_for_rich ( item ) } " )
173200 else :
174201 console .print (f" { meta_key } : []" )
175202 else :
176- console .print (f" { meta_key } : { meta_value } " )
203+ console .print (f" { meta_key } : { _escape_for_rich ( meta_value ) } " )
177204
178205
179206def display_task_tree (console , task_dict , phabfive_instance ):
@@ -208,9 +235,9 @@ def display_task_tree(console, task_dict, phabfive_instance):
208235 first_line = str (value ).split ("\n " )[0 ]
209236 if len (first_line ) > 60 :
210237 first_line = first_line [:57 ] + "..."
211- task_branch .add (f"{ key } : { first_line } " )
238+ task_branch .add (f"{ key } : { _escape_for_rich ( first_line ) } " )
212239 else :
213- task_branch .add (f"{ key } : { value } " )
240+ task_branch .add (f"{ key } : { _escape_for_rich ( value ) } " )
214241
215242 # Add Assignee
216243 if assignee :
@@ -244,7 +271,7 @@ def display_task_tree(console, task_dict, phabfive_instance):
244271 )
245272 board_branch .add (Text .assemble ("Column: " , column_link ))
246273 continue
247- board_branch .add (f"{ key } : { value } " )
274+ board_branch .add (f"{ key } : { _escape_for_rich ( value ) } " )
248275
249276 # Add History section
250277 if history :
@@ -253,13 +280,13 @@ def display_task_tree(console, task_dict, phabfive_instance):
253280 if hist_key == "Boards" and isinstance (hist_value , dict ):
254281 boards_hist = history_branch .add ("Boards" )
255282 for board_name , transitions in hist_value .items ():
256- board_hist = boards_hist .add (board_name )
283+ board_hist = boards_hist .add (_escape_for_rich ( board_name ) )
257284 for trans in transitions :
258- board_hist .add (trans )
285+ board_hist .add (_escape_for_rich ( trans ) )
259286 elif isinstance (hist_value , list ):
260287 hist_type_branch = history_branch .add (hist_key )
261288 for trans in hist_value :
262- hist_type_branch .add (trans )
289+ hist_type_branch .add (_escape_for_rich ( trans ) )
263290
264291 # Add Comments section
265292 comments = task_dict .get ("Comments" , [])
@@ -271,9 +298,9 @@ def display_task_tree(console, task_dict, phabfive_instance):
271298 first_line = str (comment ).split ("\n " )[0 ]
272299 if len (first_line ) > 60 :
273300 first_line = first_line [:57 ] + "..."
274- comments_branch .add (first_line )
301+ comments_branch .add (_escape_for_rich ( first_line ) )
275302 else :
276- comments_branch .add (str (comment ))
303+ comments_branch .add (_escape_for_rich ( str (comment ) ))
277304
278305 # Add Metadata section
279306 if metadata :
@@ -283,11 +310,11 @@ def display_task_tree(console, task_dict, phabfive_instance):
283310 if meta_value :
284311 list_branch = meta_branch .add (meta_key )
285312 for item in meta_value :
286- list_branch .add (str (item ))
313+ list_branch .add (_escape_for_rich ( str (item ) ))
287314 else :
288315 meta_branch .add (f"{ meta_key } : []" )
289316 else :
290- meta_branch .add (f"{ meta_key } : { meta_value } " )
317+ meta_branch .add (f"{ meta_key } : { _escape_for_rich ( meta_value ) } " )
291318
292319 console .print (tree )
293320
0 commit comments