@@ -71,27 +71,40 @@ def candidates(self):
7171 """Generate all possible representations"""
7272 actual_candidates = []
7373
74+ # Normal t-string candidates
75+ actual_candidates .extend (self ._generate_candidates_with_processor ('t' , self .str_for ))
76+
77+ # Raw t-string candidates (if we detect backslashes)
78+ if self ._contains_literal_backslashes ():
79+ actual_candidates .extend (self ._generate_candidates_with_processor ('rt' , self .raw_str_for ))
80+
81+ return filter (self .is_correct_ast , actual_candidates )
82+
83+ def _generate_candidates_with_processor (self , prefix , str_processor ):
84+ """Generate t-string candidates using the given prefix and string processor function."""
85+ candidates = []
86+
7487 for quote in self .allowed_quotes :
75- candidates = ['' ]
88+ quote_candidates = ['' ]
7689 debug_specifier_candidates = []
7790
7891 for v in self .node .values :
7992 if is_constant_node (v , ast .Constant ) and isinstance (v .value , str ):
8093 # String literal part - check for debug specifiers
8194
8295 # Could this be used as a debug specifier?
83- if len (candidates ) < 10 :
96+ if len (quote_candidates ) < 10 :
8497 import re
8598 debug_specifier = re .match (r'.*=\s*$' , v .value )
8699 if debug_specifier :
87100 # Maybe! Save for potential debug specifier completion
88101 try :
89- debug_specifier_candidates = [x + '{' + v .value for x in candidates ]
102+ debug_specifier_candidates = [x + '{' + v .value for x in quote_candidates ]
90103 except Exception :
91104 continue
92105
93106 try :
94- candidates = [x + self . str_for (v .value , quote ) for x in candidates ]
107+ quote_candidates = [x + str_processor (v .value , quote ) for x in quote_candidates ]
95108 except Exception :
96109 continue
97110
@@ -103,17 +116,17 @@ def candidates(self):
103116
104117 # Regular interpolation processing
105118 interpolation_candidates = InterpolationValue (v ).get_candidates ()
106- candidates = [x + y for x in candidates for y in interpolation_candidates ] + completed
119+ quote_candidates = [x + y for x in quote_candidates for y in interpolation_candidates ] + completed
107120
108121 debug_specifier_candidates = []
109122 except Exception :
110123 continue
111124 else :
112125 raise RuntimeError ('Unexpected TemplateStr value: %r' % v )
113126
114- actual_candidates .extend (['t' + quote + x + quote for x in candidates ])
127+ candidates .extend ([prefix + quote + x + quote for x in quote_candidates ])
115128
116- return filter ( self . is_correct_ast , actual_candidates )
129+ return candidates
117130
118131 def str_for (self , s , quote ):
119132 """Convert string literal to properly escaped form"""
@@ -125,6 +138,24 @@ def str_for(self, s, quote):
125138 return '\\ \n '
126139 return mini_s
127140
141+ def raw_str_for (self , s ):
142+ """
143+ Generate string representation for raw t-strings.
144+ Don't escape backslashes like MiniString does.
145+ """
146+ return s .replace ('{' , '{{' ).replace ('}' , '}}' )
147+
148+ def _contains_literal_backslashes (self ):
149+ """
150+ Check if this t-string contains literal backslashes in constant values.
151+ This indicates it may need to be a raw t-string.
152+ """
153+ for node in ast .walk (self .node ):
154+ if is_constant_node (node , ast .Str ):
155+ if '\\ ' in node .s :
156+ return True
157+ return False
158+
128159 def __str__ (self ):
129160 """Generate the shortest valid t-string representation"""
130161 if len (self .node .values ) == 0 :
@@ -195,20 +226,27 @@ def get_candidates(self):
195226 format_candidates = python_minifier .f_string .OuterFString (
196227 self .node .format_spec , pep701 = True
197228 ).candidates ()
198- # Remove the f prefix and quotes to get just the format part
229+ # Remove the f/rf prefix and quotes to get just the format part
199230 format_parts = []
200231 for fmt in format_candidates :
201- if fmt .startswith ('f' ):
232+ # Handle both f"..." and rf"..." patterns
233+ if fmt .startswith ('rf' ):
234+ # Remove rf prefix and outer quotes
235+ inner = fmt [2 :]
236+ elif fmt .startswith ('f' ):
202237 # Remove f prefix and outer quotes
203238 inner = fmt [1 :]
204- if (inner .startswith ('"' ) and inner .endswith ('"' )) or \
205- (inner .startswith ("'" ) and inner .endswith ("'" )):
206- format_parts .append (inner [1 :- 1 ])
207- elif (inner .startswith ('"""' ) and inner .endswith ('"""' )) or \
208- (inner .startswith ("'''" ) and inner .endswith ("'''" )):
209- format_parts .append (inner [3 :- 3 ])
210- else :
211- format_parts .append (inner )
239+ else :
240+ continue
241+
242+ if (inner .startswith ('"' ) and inner .endswith ('"' )) or \
243+ (inner .startswith ("'" ) and inner .endswith ("'" )):
244+ format_parts .append (inner [1 :- 1 ])
245+ elif (inner .startswith ('"""' ) and inner .endswith ('"""' )) or \
246+ (inner .startswith ("'''" ) and inner .endswith ("'''" )):
247+ format_parts .append (inner [3 :- 3 ])
248+ else :
249+ format_parts .append (inner )
212250
213251 if format_parts :
214252 self ._append (format_parts )
0 commit comments