# ICE Revision: $Id: TemplateFile.py,v de6dbd122d11 2020-02-25 11:02:08Z bgschaid $
import re
from math import *
import sys
from PyFoam.Error import error,warning
from PyFoam.ThirdParty.pyratemp import Template as PyratempTemplate
from PyFoam.ThirdParty.pyratemp import EvalPseudoSandbox,TemplateRenderError
from PyFoam.ThirdParty.pyratemp import Renderer as PyratempRenderer
from PyFoam.ThirdParty.six import iteritems,exec_,print_,PY3
[docs]class RendererWithFilename(PyratempRenderer):
"""Usual renderer but report a filename"""
def __init__(self, evalfunc, escapefunc,filename=None):
PyratempRenderer.__init__(self, evalfunc, escapefunc)
self.fileName = filename
[docs] def reportString(self,expr, err):
result="Cannot eval expression '%s'. (%s: %s)" %(expr, err.__class__.__name__, err)
if self.fileName:
result+=" in file "+self.fileName
return result
def _eval(self, expr, data):
"""evalfunc with error-messages"""
try:
return self.evalfunc(expr, data)
except (TypeError,NameError,IndexError,KeyError,AttributeError, SyntaxError):
err = sys.exc_info()[1] # Needed because python 2.5 does not support 'as e'
raise TemplateRenderError(self.reportString(expr,err))
[docs]class TolerantRenderer(RendererWithFilename):
"""Variant of the renderer that doesn't choke on problems with evaluations"""
def __init__(self, evalfunc, escapefunc,filename=None):
RendererWithFilename.__init__(self, evalfunc, escapefunc,filename=filename)
def _eval(self, expr, data):
"""evalfunc with error-messages"""
try:
return self.evalfunc(expr, data)
except (TypeError,NameError,IndexError,KeyError,AttributeError, SyntaxError):
err = sys.exc_info()[1] # Needed because python 2.5 does not support 'as e'
warning(self.reportString(expr,err))
return "Template evaluation ERROR: "+self.reportString(expr,err)
execIdString="this is meant to be executed:"
substituteIdString="substitute current values into this string:"
[docs]class PyratempPreprocessor(object):
"""This class preprocesses the input that is give to it in such a
way that the old format (using $$ at the line beginnings and $
.. $ for expressions) is reworked into something that pyratemp understands
"""
def __init__(self,
dovarline=True,
doexpr=True,
expressionDelimiter="$",
assignmentLineStart="$$",
allowExec=False,
assignmentDebug=None,
specials=[]):
"""Create the regexp once for performance reasons
:param dovarline: look for variable lines that start with $$
:param doexpr: substitute expressions that are between $
:param expressionDelimiter: character/string that is used before and after an
expression. After the expression the reverse of the string is used
:param assignmentLineStart: character sequence that signals an assignment line
:param assignmentDebug: Add a commented line to debug assignments. Prefix used is this parameter
:param allowExec: allows execution of code. This is potentially unsafe
:param specials: a list. If any expression starts with one of these values then
the full expression (including delimiters) is left verbatim in the template"""
self.clip=len(expressionDelimiter)
self.specials=specials
tmp=list(expressionDelimiter)
tmp.reverse()
self.expressionDelimiter=re.escape(expressionDelimiter)
self.expressionDelimiterEnd=re.escape("".join(tmp))
self.expressionDelimiterRaw=expressionDelimiter
self.expressionDelimiterEndRaw="".join(tmp)
# print self.expressionDelimiter,self.expressionDelimiterEnd
self.assignmentLineStart=assignmentLineStart
self.assignmentDebug=assignmentDebug
self.expr=re.compile("%s[^$!\n]+?%s" % (self.expressionDelimiter,self.expressionDelimiterEnd))
self.dovarline=dovarline
self.doexpr=doexpr
self.allowExec=allowExec
def __call__(self,original):
"""This does the actual work"""
if len(original)==0:
return original
lines=original.split("\n")
if lines[-1]=="":
lines=lines[:-1]
result=""
def isVarname(name):
return re.match("[_A-Za-z][_A-Za-z0-9]*$",name.strip())!=None
for l in lines:
skipLine=False
if l[:len(self.assignmentLineStart)]==self.assignmentLineStart and self.dovarline:
tmp=l[len(self.assignmentLineStart):].split("=")
if len(tmp)!=2 or not isVarname(tmp[0]):
if self.allowExec:
execString=l[len(self.assignmentLineStart):].replace("\\","\\\\").replace("\"","\\\"")
result+='$!setvar("%s", "%s")!$#!' % (
"dummyVarForExecution",
execIdString+execString.strip()
)
result+="\n"
skipLine=True
else:
error("Each definition must be of the form: <name>=<value>",
"The string",l,"is not. Try running the utility with the option --allow-exec-instead-of-assignment")
else:
# if tmp[1].find('"')>=0:
# error("There is a \" in",tmp[1],"\npyratemp can't cope with that'")
exprStr=tmp[1].replace("\\","\\\\").replace("\"","\\\"")
result+='$!setvar("%s", "%s")!$#!' % (tmp[0].strip(),exprStr.strip())
result+="\n"
if self.assignmentDebug and self.doexpr:
l=self.assignmentDebug+" "+tmp[0].strip()+" "+self.expressionDelimiterRaw+tmp[0].strip()+self.expressionDelimiterEndRaw
else:
continue
elif self.doexpr:
nl=""
iStart=0
for m in self.expr.finditer(l):
inner=l[m.start()+self.clip:m.end()-self.clip]
hasSpecial=False
nl+=l[iStart:m.start()]
for k in self.specials:
if len(k)<=len(inner):
if inner[:len(k)]==k:
hasSpecial=True
substVarName="dummyVarForSubstitution"
# nl+=l[m.start():m.end()]
nl+='$!setvar("%s", "%s")!$#!\n' % (
substVarName,
substituteIdString+l[m.start():m.end()]
)
nl+='$!'+substVarName+'!$'
if not hasSpecial:
nl+="$!"+inner+"!$"
iStart=m.end()
result+=nl+l[iStart:]+"\n"
else:
if not skipLine:
result+=l+"\n"
# remove trailing newline if the original had none
if original[-1]!='\n' and result[-1]=='\n':
result=result[:-1]
return result
[docs]class EvalPseudoSandboxWithMath(EvalPseudoSandbox):
"""Add mathematical functions to the valid functons"""
def __init__(self,allowExec=False):
EvalPseudoSandbox.__init__(self)
import math
for o in dir(math):
if o[0]!="_":
self.register(o,getattr(math,o))
from PyFoam.ThirdParty.six.moves import builtins as __builtin__
self.register("set",__builtin__.set)
if allowExec:
del self.eval_allowed_globals["__import__"]
self.register("__import__",__builtins__["__import__"])
[docs] def compile(self, expr,mode="eval"):
"""Compile a python-eval-expression. Overrides the default implementation
to allow '_[1]' as a valid name
"""
if expr not in self._compile_cache:
c = compile(expr, "", mode)
for i in c.co_names: #prevent breakout via new-style-classes
if i[0] == '_':
if i[1]!='[' or i[-1]!=']':
raise NameError("Name '%s' is not allowed." %(i))
self._compile_cache[expr] = c
return self._compile_cache[expr]
[docs] def eval(self, expr, locals):
"""Eval a python-eval-expression.
Sets ``self.locals_ptr`` to ``locales`` and compiles the code
before evaluating.
"""
if expr[:len(substituteIdString)]==substituteIdString:
goOn=True
replacement=expr[len(substituteIdString):]
while goOn:
try:
value=replacement % locals
goOn=False
except KeyError:
e = sys.exc_info()[1] # Needed because python 2.5 does not support 'as e'
kExpr="%("+e.args[0]+")"
replacement=replacement.replace(kExpr,"%"+kExpr)
return value
# print value
sav = self.locals_ptr
self.locals_ptr = locals
doEval=True
if expr[:len(execIdString)]==execIdString:
doEval=False
if doEval:
globals= {"__builtins__":self.eval_allowed_globals}
if PY3:
globals.update(locals)
try:
x = eval(self.compile(expr),globals, locals)
except:
e = sys.exc_info()[1] # Needed because python 2.5 does not support 'as e'
print_("Problem avaluating",expr,":",e)
raise e
else:
# globals= {"__builtins__":self.eval_allowed_globals}
globals= {"__builtins__":__builtins__}
expr=expr[len(execIdString):]
exec_(self.compile(expr,mode="exec"),globals,locals)
x = None
self.locals_ptr = sav
return x
[docs]class EvalPseudoSandboxWithMathWithImport(EvalPseudoSandboxWithMath):
"""Class that allows the import of packages"""
def __init__(self):
EvalPseudoSandboxWithMath.__init__(self,allowExec=True)
[docs]class TemplateFile(TemplateFileOldFormat):
"""Works on template files. Does calculations between $$.
Lines that start with $$ contain definitions"""
def __init__(self,
name=None,
content=None,
encoding="utf-8",
expressionDelimiter="|",
assignmentLineStart="$$",
assignmentDebug=None,
specials=[],
renderer_class=None,
tolerantRender=False,
allowExec=False
):
"""Exactly one of the parameters must be specified
:param name: name of the template file.
:param content: Content of the template
:param expressionDelimiter: character/string that delimits expression strings.
:param assignmentLineStart: Start of a line that holds an assignment operation
:param assignmentDebug: Add a commented line to debug assignments. Prefix used is this parameter
:param allowExec: allow execution (and import). This is potentially unsafe
:param special: list with strings that leave expression untreated"""
self.expressionDelimiter=expressionDelimiter
self.assignmentLineStart=assignmentLineStart
self.assignmentDebug=assignmentDebug
self.specials=specials
self.allowExec=allowExec
super(TemplateFile,self).__init__(name=name,
content=content,
)
if renderer_class==None:
if tolerantRender:
class ConcreteTolerantRenderer(TolerantRenderer):
def __init__(self,evalfunc, escapefunc):
TolerantRenderer.__init__(self,
evalfunc,
escapefunc,filename=name)
renderer_class=ConcreteTolerantRenderer
else:
class ConcreteRenderWithFileName(RendererWithFilename):
def __init__(self,evalfunc, escapefunc):
RendererWithFilename.__init__(self,
evalfunc,
escapefunc,filename=name)
renderer_class=ConcreteRenderWithFileName
if allowExec:
sandbox=EvalPseudoSandboxWithMathWithImport
else:
sandbox=EvalPseudoSandboxWithMath
self.ptemplate=PyratempTemplate(string=self.template,
eval_class=sandbox,
renderer_class=renderer_class,
encoding=encoding,
escape=None
)
[docs] def buildTemplate(self,template):
self.template=PyratempPreprocessor(assignmentLineStart=self.assignmentLineStart,
expressionDelimiter=self.expressionDelimiter,
assignmentDebug=self.assignmentDebug,
specials=self.specials,
allowExec=self.allowExec
)(template)
[docs] def getString(self,vals):
"""In the template, replaces all the strings between $$
with the evaluation of the expressions
:param vals: dictionary with the values
:returns: The string with the replaced expressions"""
return self.ptemplate(**vals)
# Should work with Python3 and Python2