#
# replcode.py
# By Joel Gould
# joelg@alum.mit.edu
# http://www.gouldhome.com/
#
# This subroutine takes a string, which may have embedded Python code, and
# executes that embedded code, returning the resulting string. It is useful
# for things like dynamically producing webpages.
#
# We first execute any code in [!! ... !!]. Then we evaluate any code in
# [?? ... ??]. We do allow nested block of executed code. To use a nested
# block, include an integer in the block delimiters, ex: [1!! ... !!1]
#
# You can pass an errorLogger function to runPythonCode. This function
# will be called to print additional error messages. Default is
# sys.stdout.write().
#
# Use the special function "OUTPUT" inside the embeded Python code to add text
# to the output.
#
# Here is a sample of using replcode:
#
# >>> import replcode
# >>> input_text = """
# ... Normal line.
# ... Expression [?? 1+2 ??].
# ... Global variable [?? variable ??].
# ... [!!
# ... def foo(x):
# ... return x+x !!].
# ... Function [?? foo('abc') ??].
# ... [!!
# ... OUTPUT('Nested call [?? variable ??]') !!].
# ... [!!
# ... OUTPUT('''Double nested [1!!
# ... myVariable = '456' !!1][?? myVariable ??]''') !!].
# ... """
# >>> global_dict = { 'variable': '123' }
# >>> output_text = replcode.runPythonCode(input_text,global_dict)
# >>> print output_text
#
# Normal line.
# Expression 3.
# Global variable 123.
# .
# Function abcabc.
# Nested call 123.
# Double nested 456.
#
import re
import sys
import string
#---------------------------------------------------------------------------
def runPythonCode(data, global_dict={}, local_dict=None, errorLogger=None):
eval_state = EvalState(global_dict, local_dict, errorLogger)
data = re.sub(r'(?s)\[(?P<num>\d?)!!(?P<code>.+?)!!(?P=num)\]', eval_state.exec_python, data)
data = re.sub(r'(?s)\[\?\?(?P<code>.+?)\?\?\]', eval_state.eval_python, data)
return data
#---------------------------------------------------------------------------
# This class is used to encapuslate the global and local dictionaries with
# the exec_python and eval_python functions.
class EvalState:
def __init__(self, global_dict, local_dict, errorLogger):
self.global_dict = global_dict
self.local_dict = local_dict
if errorLogger:
self.errorLogger = errorLogger
else:
self.errorLogger = sys.stdout.write
# copy these things into the global dictionary
self.global_dict['OUTPUT'] = OUTPUT
self.global_dict['sys'] = sys
self.global_dict['string'] = string
self.global_dict['__builtins__'] = __builtins__
# Subroutine called from re module for every block of code to be
# executed. Executed code can not return anything but it is allowed to
# call the OUTPUT subroutine. Any string produced from OUTPUT will
# added to the OUTPUT_TEXT global variable and returned.
def exec_python(self, result):
# Condition the code. Replace all tabs with four spaces. Then make
# sure that we unindent every line by the indentation level of the
# first line.
code = result.group('code')
code = string.replace(code, '\t', ' ')
result2 = re.search(r'(?P<prefix>\n[ ]*)[#a-zA-Z0-9''"]', code)
if not result2:
raise ParsingError,'Invalid template code expression: ' + code
code = string.replace(code, result2.group('prefix'), '\n')
code = code + '\n'
try:
self.global_dict['OUTPUT_TEXT'] = ''
if self.local_dict:
exec code in self.global_dict, self.local_dict
else:
exec code in self.global_dict
return self.global_dict['OUTPUT_TEXT']
except:
self.errorLogger('\n---- Error parsing: ----\n')
self.errorLogger(code)
self.errorLogger('\n------------------------\n')
raise
# Subroutine called from re module for every block of code to be
# evaluated. Returned the result of the evaluation (should be a string).
def eval_python(self, result):
code = result.group('code')
code = string.replace(code, '\t', ' ')
try:
if self.local_dict:
result = eval(code, self.global_dict, self.local_dict)
else:
result = eval(code, self.global_dict)
return str(result)
except:
self.errorLogger('\n---- Error parsing: ----\n')
self.errorLogger(code)
self.errorLogger('\n------------------------\n')
raise
#---------------------------------------------------------------------------
# This routine is only called when OUTPUT() is included in executed Python
# code from the templates. It evaluates its parameter as if it was a
# template and appends the result to the OUTPUT_TEXT variable in the global
# dictionary.
def OUTPUT(data):
# This magic python code extracts the local and global dictionaries in
# the stack frame which was in effect when OUTPUT was called.
try:
raise ZeroDivisionError
except ZeroDivisionError:
local_dict = sys.exc_info()[2].tb_frame.f_back.f_locals
global_dict = sys.exc_info()[2].tb_frame.f_back.f_globals
global_dict['OUTPUT_TEXT'] = global_dict['OUTPUT_TEXT'] + runPythonCode(data, global_dict, local_dict)