def _patch_special_syntax(self): """Takes an fstring (and its prefix, ie "f") that may contain xonsh expressions as its field values and substitues them for a call to __xonsh__.eval_fstring_field as needed. """ prelen = len(self.prefix) quote = self.fstring[prelen] if self.fstring[prelen + 1] == quote: quote *= 3 template = self.fstring[prelen + len(quote) : -len(quote)] while True: repl = self.prefix + quote + template + quote try: res = pyparse(repl) break except SyntaxError as e: # The e.text attribute is expected to contain the failing # expression, e.g. "($HOME)" for f"{$HOME}" string. if e.text is None or e.text[0] != "(": raise error_expr = e.text[1:-1] epos = template.find(error_expr) if epos < 0: raise # We can olny get here in the case of handled SyntaxError. # Patch the last error and start over. xonsh_field = (error_expr, self.filename if self.filename else None) field_id = id(xonsh_field) self.fields[field_id] = xonsh_field eval_field = f"__xonsh__.eval_fstring_field({field_id})" template = template[:epos] + eval_field + template[epos + len(error_expr) :] self.repl = repl self.res = res.body[0].value
def _unpatch_strings(self): """Reverts false-positive field matches within strings.""" reparse = False for node in ast.walk(self.res): if isinstance(node, ast.Constant) and isinstance(node.value, str): value = node.value elif isinstance(node, ast.Str): value = node.s else: continue match = RE_FSTR_FIELD_WRAPPER.search(value) if match is None: continue field = self.fields.pop(int(match.group(2)), None) if field is None: continue self.repl = self.repl.replace(match.group(1), field[0], 1) reparse = True if reparse: self.res = pyparse(self.repl).body[0].value