Ian Bicking: the old part of his blog

Re: String Interpolation

In the early days of Aquarium, before I started using Cheetah, I had a decent interpolation library that fit in a single function, but was still quite handy:

def evalstr(s, dollar="$"):

    """Evaluate a string with embedded python expressions.

    This is a Python version of the function evalstr, which is used for
    variable interpolation.

    For instance,

        a = 5
        evalStr("I have $a$ dogs.")

    will return

        "I have 5 dogs."

    The code between the $'s can be any valid Python expression.  I'll
    just basically do

        str(eval(expression), yourGlobals, yourLocals)

    Since eval is being used, the expression must return some value.
    To escape $'s in s, just use $$ (even within expressions).  To use
    a different character than $, pass the desired character in the
    second (optional) argument.  If there are an odd number of $
    symbols, a ValueError exception will be raised.  Here are some
    additional examples (assuming a = 5):

    "My dog is $a$ years old." -> "My dog is 5 years old.",
    "I have $$5.00" -> "I have $5.00",
    "My $a$ year old dog has $$5.00" -> "My 5 year old dog has $5.00",
    "My dog has $'$$%s.' % a$" -> "My dog has $5.",
    "My dog has $'$$' + str(a) + '.'" -> ValueError exception,
    "My dog is $a$.$" -> ValueError exception,
    "$$ is a char." -> "$ is a char.",
    "$a$ is a number." -> "5 is a number.",
    "$a$" -> "5",
    "$a" -> ValueError exception,
    "I have a $$" -> "I have a $",
    "" -> "",
    "Spam" -> "Spam",
    "$" -> ValueError exception,
    "$ evalstr('I am #a#.', '#') $" -> "I am 5."

    """
    
    # By the way, I'm sorry if this stuff is hard to read.  If you
    # can come up with something more elegant, I'd love to see it, 
    # but this is the best that I could come up with.  

    import string
    import sys
    inEv = 0     # Are we in eval mode (i.e. inside a single $)?
    inDollar = 0 # We just got a dollar.  We might be in an escape.
    ret = ""     # This is the string we're returning.
    tok = ""     # The token we're currently working with.
    stack = []   # All good languages are stack based ;)
    i=j=0        # These are indexes into s.

    # Grab the calling function's namespace.  I can't find a nice 
    # way to do this other than manually generating an exception 
    # and surfing the traceback.  As for performance, you can do 
    # this about 10000 times in a bit less than a second on my 
    # PIII 600 mhz system.

    try:
        1/0
    except:
        traceback = sys.exc_info()[2]
        currFrame = traceback.tb_frame
        prevFrame = currFrame.f_back
        prevGlobals = prevFrame.f_globals
        prevLocals = prevFrame.f_locals
    
    # If they forget to close the $, raise a ValueError exception.

    if string.count(s, dollar) % 2: 
        raise ValueError, "unmatched dollar symbol"

    sLen = len(s)
    while 1:

        # Get next token.

        j = i
        if j==sLen:
            tok = ""
        elif s[j] == dollar:
            tok = dollar
            i = j+1
        else:
            while j<sLen and s[j]!=dollar: j=j+1
            if j==sLen:
                tok = s[i:]
                i = sLen
            else:
                tok = s[i:j]
                i = j

        # Deal with receiving $'s (may call continue).

        if tok == dollar:
            if inDollar: inDollar = 0
            else: 
                inDollar = 1
                continue

        # Deal with appending stuff to the stack or to ret.

        if inDollar:
            inEv = not inEv
            inDollar = 0
        if inEv: stack = stack + [tok]
        else:
            if len(stack) > 0:
                ret = ret + \
                    str(eval(string.join(stack, ""),
                         prevGlobals, 
                         prevLocals))
            ret = ret + tok
            stack = []

        # Deal with end of string.

        if tok == "": break
    return ret
Comment on String Interpolation
by Shannon -jj Behrens