Skip to content

Instantly share code, notes, and snippets.

@GreyElaina
Created January 14, 2025 07:24
Show Gist options
  • Select an option

  • Save GreyElaina/d22fd18355b8397e4bc8a1640f18d74f to your computer and use it in GitHub Desktop.

Select an option

Save GreyElaina/d22fd18355b8397e4bc8a1640f18d74f to your computer and use it in GitHub Desktop.
# /// script
# dependencies = [
# "executing>=2.1.0"
# ]
# ///
import ast
import sys
from dataclasses import dataclass
from executing import Source
from typing import Callable, Literal
_CONVERSIONS = {-1: None, 97: "a", 114: "r", 115: "s"}
@dataclass
class Interpolation:
value: object
expr: str
conv: Literal["a", "r", "s"] | None
format_spec: str
@dataclass
class Template:
args: tuple[str | Interpolation, ...]
def t(cb: Callable[[], str]):
"""
A function that converts an f-string lambda into a Template object with parsed interpolations.
This function takes a lambda expression that returns an f-string and converts it into a Template
object, parsing both literal strings and interpolated values while preserving formatting information.
Args:
cb (Callable[[], str]): A lambda function that returns an f-string. Must be in the form
lambda: f"string with {interpolations}"; Won't be called but parsed for its AST.
Returns:
Template: A Template object containing a tuple of string literals and Interpolation objects.
Each Interpolation object contains:
- value: The evaluated value of the expression
- expr: The string representation of the expression
- conv: The conversion flag (s, r, or a) if specified
- format_spec: The format specification if specified
Raises:
ValueError: If any of these conditions are met:
- t() is not called as a function (executing's edge case)
- t() is not called with a lambda
- The lambda does not return an f-string
- The f-string contains unsupported elements
Example:
>>> x = 42
>>> t(lambda: f"Value is {x:d}")
Template(("Value is ", Interpolation(value=42, expr="x", conv=None, format_spec="d")))
"""
frame = sys._getframe(1)
node = Source.executing(frame).node
if not isinstance(node, ast.Call):
raise ValueError("t() must be called as a function")
arg0 = node.args[0]
if not isinstance(arg0, ast.Lambda):
raise ValueError("t() must be called with a lambda")
if not isinstance(arg0.body, ast.JoinedStr):
raise ValueError("t() lambda must return a f-string")
values = []
for v in arg0.body.values:
if isinstance(v, ast.Constant):
values.append(v.value)
elif isinstance(v, ast.FormattedValue):
conv = _CONVERSIONS[v.conversion]
expr = ast.unparse(v.value)
val = eval(expr, frame.f_locals, frame.f_globals)
spec = v.format_spec.values[0].value if v.format_spec is not None else "" # type: ignore
values.append(
Interpolation(value=val, expr=expr, conv=conv, format_spec=spec)
)
else:
raise ValueError("Unsupported f-string element")
return Template(tuple(values))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment