1717from jinja2 import Environment , Template , meta
1818
1919# 3rd party imports
20+ from rich .markup import escape
2021from rich .text import Text
2122from rich .tree import Tree
2223from ruamel .yaml import YAML
@@ -43,6 +44,31 @@ class Maniphest(Phabfive):
4344 def __init__ (self ):
4445 super (Maniphest , self ).__init__ ()
4546
47+ def _escape (self , content ):
48+ """
49+ Safely escape user-provided content for Rich markup display.
50+
51+ Rich library treats square brackets [...] as markup syntax. This method
52+ escapes user-provided content so literal brackets are displayed correctly
53+ without being interpreted as Rich formatting tags.
54+
55+ Parameters
56+ ----------
57+ content : any
58+ User-provided content to escape (task names, descriptions, comments, etc.)
59+
60+ Returns
61+ -------
62+ str or Text
63+ Escaped string safe for Rich display, or original Text object if already safe
64+ """
65+ if content is None :
66+ return ""
67+ if isinstance (content , Text ):
68+ # Text objects are already safe Rich objects, don't escape
69+ return content
70+ return escape (str (content ))
71+
4672 def _resolve_project_phids (self , project : str ) -> list [str ]:
4773 """
4874 Resolve project name, hashtag, or wildcard pattern to list of project PHIDs.
@@ -2102,12 +2128,13 @@ def _display_task_yaml(self, console, task_dict):
21022128 # Multi-line value
21032129 console .print (f" { key } : |-" )
21042130 for line in str (value ).splitlines ():
2105- console .print (f" { line } " )
2131+ console .print (f" { self . _escape ( line ) } " )
21062132 elif self ._needs_yaml_quoting (value ):
2107- escaped = value .replace ("'" , "''" )
2108- console .print (f" { key } : '{ escaped } '" )
2133+ # YAML quote escaping ('' for single quotes) + Rich markup escaping
2134+ yaml_escaped = str (value ).replace ("'" , "''" )
2135+ console .print (f" { key } : '{ self ._escape (yaml_escaped )} '" )
21092136 else :
2110- console .print (f" { key } : { value } " )
2137+ console .print (f" { key } : { self . _escape ( value ) } " )
21112138
21122139 # Print Assignee
21132140 if assignee :
@@ -2145,18 +2172,19 @@ def _display_task_yaml(self, console, task_dict):
21452172 )
21462173 )
21472174 else :
2148- escaped = column_link .replace ("'" , "''" )
2149- console .print (f" Column: '{ escaped } '" )
2175+ # column_link is a string, needs both YAML and Rich escaping
2176+ yaml_escaped = column_link .replace ("'" , "''" )
2177+ console .print (f" Column: '{ self ._escape (yaml_escaped )} '" )
21502178 else :
21512179 console .print (
21522180 Text .assemble (" Column: " , column_link )
21532181 )
21542182 continue
21552183 if self ._needs_yaml_quoting (value ):
2156- escaped = value .replace ("'" , "''" )
2157- console .print (f" { key } : '{ escaped } '" )
2184+ yaml_escaped = str ( value ) .replace ("'" , "''" )
2185+ console .print (f" { key } : '{ self . _escape ( yaml_escaped ) } '" )
21582186 else :
2159- console .print (f" { key } : { value } " )
2187+ console .print (f" { key } : { self . _escape ( value ) } " )
21602188
21612189 # Print History section
21622190 if history :
@@ -2165,13 +2193,13 @@ def _display_task_yaml(self, console, task_dict):
21652193 if hist_key == "Boards" and isinstance (hist_value , dict ):
21662194 console .print (" Boards:" )
21672195 for board_name , transitions in hist_value .items ():
2168- console .print (f" { board_name } :" )
2196+ console .print (f" { self . _escape ( board_name ) } :" )
21692197 for trans in transitions :
2170- console .print (f" - { trans } " )
2198+ console .print (f" - { self . _escape ( trans ) } " )
21712199 elif isinstance (hist_value , list ):
21722200 console .print (f" { hist_key } :" )
21732201 for trans in hist_value :
2174- console .print (f" - { trans } " )
2202+ console .print (f" - { self . _escape ( trans ) } " )
21752203
21762204 # Print Comments section
21772205 comments = task_dict .get ("Comments" , [])
@@ -2181,11 +2209,11 @@ def _display_task_yaml(self, console, task_dict):
21812209 if isinstance (comment , PreservedScalarString ) or "\n " in str (comment ):
21822210 # Multi-line comment
21832211 lines = str (comment ).splitlines ()
2184- console .print (f" - { lines [0 ]} " )
2212+ console .print (f" - { self . _escape ( lines [0 ]) } " )
21852213 for line in lines [1 :]:
2186- console .print (f" { line } " )
2214+ console .print (f" { self . _escape ( line ) } " )
21872215 else :
2188- console .print (f" - { comment } " )
2216+ console .print (f" - { self . _escape ( comment ) } " )
21892217
21902218 # Print Metadata section
21912219 if metadata :
@@ -2195,11 +2223,11 @@ def _display_task_yaml(self, console, task_dict):
21952223 if meta_value :
21962224 console .print (f" { meta_key } :" )
21972225 for item in meta_value :
2198- console .print (f" - { item } " )
2226+ console .print (f" - { self . _escape ( item ) } " )
21992227 else :
22002228 console .print (f" { meta_key } : []" )
22012229 else :
2202- console .print (f" { meta_key } : { meta_value } " )
2230+ console .print (f" { meta_key } : { self . _escape ( meta_value ) } " )
22032231
22042232 def _display_task_tree (self , console , task_dict ):
22052233 """Display a single task in tree format using Rich Tree.
@@ -2230,9 +2258,9 @@ def _display_task_tree(self, console, task_dict):
22302258 first_line = str (value ).split ("\n " )[0 ]
22312259 if len (first_line ) > 60 :
22322260 first_line = first_line [:57 ] + "..."
2233- task_branch .add (f"{ key } : { first_line } " )
2261+ task_branch .add (f"{ key } : { self . _escape ( first_line ) } " )
22342262 else :
2235- task_branch .add (f"{ key } : { value } " )
2263+ task_branch .add (f"{ key } : { self . _escape ( value ) } " )
22362264
22372265 # Add Assignee
22382266 if assignee :
@@ -2262,7 +2290,7 @@ def _display_task_tree(self, console, task_dict):
22622290 )
22632291 board_branch .add (Text .assemble ("Column: " , column_link ))
22642292 continue
2265- board_branch .add (f"{ key } : { value } " )
2293+ board_branch .add (f"{ key } : { self . _escape ( value ) } " )
22662294
22672295 # Add History section
22682296 if history :
@@ -2271,13 +2299,13 @@ def _display_task_tree(self, console, task_dict):
22712299 if hist_key == "Boards" and isinstance (hist_value , dict ):
22722300 boards_hist = history_branch .add ("Boards" )
22732301 for board_name , transitions in hist_value .items ():
2274- board_hist = boards_hist .add (board_name )
2302+ board_hist = boards_hist .add (self . _escape ( board_name ) )
22752303 for trans in transitions :
2276- board_hist .add (trans )
2304+ board_hist .add (self . _escape ( trans ) )
22772305 elif isinstance (hist_value , list ):
22782306 hist_type_branch = history_branch .add (hist_key )
22792307 for trans in hist_value :
2280- hist_type_branch .add (trans )
2308+ hist_type_branch .add (self . _escape ( trans ) )
22812309
22822310 # Add Comments section
22832311 comments = task_dict .get ("Comments" , [])
@@ -2289,9 +2317,9 @@ def _display_task_tree(self, console, task_dict):
22892317 first_line = str (comment ).split ("\n " )[0 ]
22902318 if len (first_line ) > 60 :
22912319 first_line = first_line [:57 ] + "..."
2292- comments_branch .add (first_line )
2320+ comments_branch .add (self . _escape ( first_line ) )
22932321 else :
2294- comments_branch .add (str (comment ))
2322+ comments_branch .add (self . _escape ( str (comment ) ))
22952323
22962324 # Add Metadata section
22972325 if metadata :
@@ -2301,11 +2329,11 @@ def _display_task_tree(self, console, task_dict):
23012329 if meta_value :
23022330 list_branch = meta_branch .add (meta_key )
23032331 for item in meta_value :
2304- list_branch .add (str (item ))
2332+ list_branch .add (self . _escape ( str (item ) ))
23052333 else :
23062334 meta_branch .add (f"{ meta_key } : []" )
23072335 else :
2308- meta_branch .add (f"{ meta_key } : { meta_value } " )
2336+ meta_branch .add (f"{ meta_key } : { self . _escape ( meta_value ) } " )
23092337
23102338 console .print (tree )
23112339
0 commit comments