879 lines
25 KiB
Python
879 lines
25 KiB
Python
|
"""
|
||
|
simple, elegant templating
|
||
|
(part of web.py)
|
||
|
"""
|
||
|
|
||
|
import re, glob, os, os.path
|
||
|
from types import FunctionType as function
|
||
|
from utils import storage, group, utf8
|
||
|
from net import websafe
|
||
|
|
||
|
# differences from python:
|
||
|
# - for: has an optional else: that gets called if the loop never runs
|
||
|
# differences to add:
|
||
|
# - you can use the expression inside if, while blocks
|
||
|
# - special for loop attributes, like django?
|
||
|
# - you can check to see if a variable is defined (perhaps w/ get func?)
|
||
|
# all these are probably good ideas for python...
|
||
|
|
||
|
# todo:
|
||
|
# inline tuple
|
||
|
# relax constraints on spacing
|
||
|
# continue, break, etc.
|
||
|
# tracebacks
|
||
|
|
||
|
global_globals = {'None':None, 'False':False, 'True': True}
|
||
|
MAX_ITERS = 100000
|
||
|
|
||
|
WHAT = 0
|
||
|
ARGS = 4
|
||
|
KWARGS = 6
|
||
|
NAME = 2
|
||
|
BODY = 4
|
||
|
CLAUSE = 2
|
||
|
ELIF = 6
|
||
|
ELSE = 8
|
||
|
IN = 6
|
||
|
NAME = 2
|
||
|
EXPR = 4
|
||
|
FILTER = 4
|
||
|
THING = 2
|
||
|
ATTR = 4
|
||
|
ITEM = 4
|
||
|
NEGATE = 4
|
||
|
X = 2
|
||
|
OP = 4
|
||
|
Y = 6
|
||
|
LINENO = -1
|
||
|
|
||
|
# http://docs.python.org/ref/identifiers.html
|
||
|
r_var = '[a-zA-Z_][a-zA-Z0-9_]*'
|
||
|
|
||
|
class ParseError(Exception): pass
|
||
|
class Parser:
|
||
|
def __init__(self, text, name=""):
|
||
|
self.t = text
|
||
|
self.p = 0
|
||
|
self._lock = [False]
|
||
|
self.name = name
|
||
|
|
||
|
def lock(self):
|
||
|
self._lock[-1] = True
|
||
|
|
||
|
def curline(self):
|
||
|
return self.t[:self.p].count('\n')+1
|
||
|
|
||
|
def csome(self):
|
||
|
return repr(self.t[self.p:self.p+5]+'...')
|
||
|
|
||
|
def Error(self, x, y=None):
|
||
|
if y is None: y = self.csome()
|
||
|
raise ParseError, "%s: expected %s, got %s (line %s)" % (self.name, x, y, self.curline())
|
||
|
|
||
|
def q(self, f):
|
||
|
def internal(*a, **kw):
|
||
|
checkp = self.p
|
||
|
self._lock.append(False)
|
||
|
try:
|
||
|
q = f(*a, **kw)
|
||
|
except ParseError:
|
||
|
if self._lock[-1]:
|
||
|
raise
|
||
|
self.p = checkp
|
||
|
self._lock.pop()
|
||
|
return False
|
||
|
self._lock.pop()
|
||
|
return q or True
|
||
|
return internal
|
||
|
|
||
|
def tokr(self, t):
|
||
|
text = self.c(len(t))
|
||
|
if text != t:
|
||
|
self.Error(repr(t), repr(text))
|
||
|
return t
|
||
|
|
||
|
def ltokr(self, *l):
|
||
|
for x in l:
|
||
|
o = self.tokq(x)
|
||
|
if o: return o
|
||
|
self.Error('one of '+repr(l))
|
||
|
|
||
|
def rer(self, r):
|
||
|
x = re.match(r, self.t[self.p:]) #@@re_compile
|
||
|
if not x:
|
||
|
self.Error('r'+repr(r))
|
||
|
return self.tokr(x.group())
|
||
|
|
||
|
def endr(self):
|
||
|
if self.p != len(self.t):
|
||
|
self.Error('EOF')
|
||
|
|
||
|
def c(self, n=1):
|
||
|
out = self.t[self.p:self.p+n]
|
||
|
if out == '' and n != 0:
|
||
|
self.Error('character', 'EOF')
|
||
|
self.p += n
|
||
|
return out
|
||
|
|
||
|
def lookbehind(self, t):
|
||
|
return self.t[self.p-len(t):self.p] == t
|
||
|
|
||
|
def __getattr__(self, a):
|
||
|
if a.endswith('q'):
|
||
|
return self.q(getattr(self, a[:-1]+'r'))
|
||
|
raise AttributeError, a
|
||
|
|
||
|
class TemplateParser(Parser):
|
||
|
def __init__(self, *a, **kw):
|
||
|
Parser.__init__(self, *a, **kw)
|
||
|
self.curws = ''
|
||
|
self.curind = ''
|
||
|
|
||
|
def o(self, *a):
|
||
|
return a+('lineno', self.curline())
|
||
|
|
||
|
def go(self):
|
||
|
# maybe try to do some traceback parsing/hacking
|
||
|
return self.gor()
|
||
|
|
||
|
def gor(self):
|
||
|
header = self.defwithq()
|
||
|
results = self.lines(start=True)
|
||
|
self.endr()
|
||
|
return header, results
|
||
|
|
||
|
def ws(self):
|
||
|
n = 0
|
||
|
while self.tokq(" "): n += 1
|
||
|
return " " * n
|
||
|
|
||
|
def defwithr(self):
|
||
|
self.tokr('$def with ')
|
||
|
self.lock()
|
||
|
self.tokr('(')
|
||
|
args = []
|
||
|
kw = []
|
||
|
x = self.req(r_var)
|
||
|
while x:
|
||
|
if self.tokq('='):
|
||
|
v = self.exprr()
|
||
|
kw.append((x, v))
|
||
|
else:
|
||
|
args.append(x)
|
||
|
x = self.tokq(', ') and self.req(r_var)
|
||
|
self.tokr(')\n')
|
||
|
return self.o('defwith', 'null', None, 'args', args, 'kwargs', kw)
|
||
|
|
||
|
def literalr(self):
|
||
|
o = (
|
||
|
self.req('"[^"]*"') or #@@ no support for escapes
|
||
|
self.req("'[^']*'")
|
||
|
)
|
||
|
if o is False:
|
||
|
o = self.req('\-?[0-9]+(\.[0-9]*)?')
|
||
|
if o is not False:
|
||
|
if '.' in o: o = float(o)
|
||
|
else: o = int(o)
|
||
|
|
||
|
if o is False: self.Error('literal')
|
||
|
return self.o('literal', 'thing', o)
|
||
|
|
||
|
def listr(self):
|
||
|
self.tokr('[')
|
||
|
self.lock()
|
||
|
x = []
|
||
|
if not self.tokq(']'):
|
||
|
while True:
|
||
|
t = self.exprr()
|
||
|
x.append(t)
|
||
|
if not self.tokq(', '): break
|
||
|
self.tokr(']')
|
||
|
return self.o('list', 'thing', x)
|
||
|
|
||
|
def dictr(self):
|
||
|
self.tokr('{')
|
||
|
self.lock()
|
||
|
x = {}
|
||
|
if not self.tokq('}'):
|
||
|
while True:
|
||
|
k = self.exprr()
|
||
|
self.tokr(': ')
|
||
|
v = self.exprr()
|
||
|
x[k] = v
|
||
|
if not self.tokq(', '): break
|
||
|
self.tokr('}')
|
||
|
return self.o('dict', 'thing', x)
|
||
|
|
||
|
def parenr(self):
|
||
|
self.tokr('(')
|
||
|
self.lock()
|
||
|
o = self.exprr() # todo: allow list
|
||
|
self.tokr(')')
|
||
|
return self.o('paren', 'thing', o)
|
||
|
|
||
|
def atomr(self):
|
||
|
"""returns var, literal, paren, dict, or list"""
|
||
|
o = (
|
||
|
self.varq() or
|
||
|
self.parenq() or
|
||
|
self.dictq() or
|
||
|
self.listq() or
|
||
|
self.literalq()
|
||
|
)
|
||
|
if o is False: self.Error('atom')
|
||
|
return o
|
||
|
|
||
|
def primaryr(self):
|
||
|
"""returns getattr, call, or getitem"""
|
||
|
n = self.atomr()
|
||
|
while 1:
|
||
|
if self.tokq('.'):
|
||
|
v = self.req(r_var)
|
||
|
if not v:
|
||
|
self.p -= 1 # get rid of the '.'
|
||
|
break
|
||
|
else:
|
||
|
n = self.o('getattr', 'thing', n, 'attr', v)
|
||
|
elif self.tokq('('):
|
||
|
args = []
|
||
|
kw = []
|
||
|
|
||
|
while 1:
|
||
|
# need to see if we're doing a keyword argument
|
||
|
checkp = self.p
|
||
|
k = self.req(r_var)
|
||
|
if k and self.tokq('='): # yup
|
||
|
v = self.exprr()
|
||
|
kw.append((k, v))
|
||
|
else:
|
||
|
self.p = checkp
|
||
|
x = self.exprq()
|
||
|
if x: # at least it's something
|
||
|
args.append(x)
|
||
|
else:
|
||
|
break
|
||
|
|
||
|
if not self.tokq(', '): break
|
||
|
self.tokr(')')
|
||
|
n = self.o('call', 'thing', n, 'args', args, 'kwargs', kw)
|
||
|
elif self.tokq('['):
|
||
|
v = self.exprr()
|
||
|
self.tokr(']')
|
||
|
n = self.o('getitem', 'thing', n, 'item', v)
|
||
|
else:
|
||
|
break
|
||
|
|
||
|
return n
|
||
|
|
||
|
def exprr(self):
|
||
|
negate = self.tokq('not ')
|
||
|
x = self.primaryr()
|
||
|
if self.tokq(' '):
|
||
|
operator = self.ltokr('not in', 'in', 'is not', 'is', '==', '!=', '>=', '<=', '<', '>', 'and', 'or', '*', '+', '-', '/', '%')
|
||
|
self.tokr(' ')
|
||
|
y = self.exprr()
|
||
|
x = self.o('test', 'x', x, 'op', operator, 'y', y)
|
||
|
|
||
|
return self.o('expr', 'thing', x, 'negate', negate)
|
||
|
|
||
|
def varr(self):
|
||
|
return self.o('var', 'name', self.rer(r_var))
|
||
|
|
||
|
def liner(self):
|
||
|
out = []
|
||
|
o = self.curws
|
||
|
while 1:
|
||
|
c = self.c()
|
||
|
self.lock()
|
||
|
if c == '\n':
|
||
|
self.p -= 1
|
||
|
break
|
||
|
if c == '$':
|
||
|
if self.lookbehind('\\$'):
|
||
|
o = o[:-1] + c
|
||
|
else:
|
||
|
filter = not bool(self.tokq(':'))
|
||
|
|
||
|
if self.tokq('{'):
|
||
|
out.append(o)
|
||
|
out.append(self.o('itpl', 'name', self.exprr(), 'filter', filter))
|
||
|
self.tokr('}')
|
||
|
o = ''
|
||
|
else:
|
||
|
g = self.primaryq()
|
||
|
if g:
|
||
|
out.append(o)
|
||
|
out.append(self.o('itpl', 'name', g, 'filter', filter))
|
||
|
o = ''
|
||
|
else:
|
||
|
o += c
|
||
|
else:
|
||
|
o += c
|
||
|
self.tokr('\n')
|
||
|
if not self.lookbehind('\\\n'):
|
||
|
o += '\n'
|
||
|
else:
|
||
|
o = o[:-1]
|
||
|
out.append(o)
|
||
|
return self.o('line', 'thing', out)
|
||
|
|
||
|
def varsetr(self):
|
||
|
self.tokr('$var ')
|
||
|
self.lock()
|
||
|
what = self.rer(r_var)
|
||
|
self.tokr(':')
|
||
|
body = self.lines()
|
||
|
return self.o('varset', 'name', what, 'body', body)
|
||
|
|
||
|
def ifr(self):
|
||
|
self.tokr("$if ")
|
||
|
self.lock()
|
||
|
expr = self.exprr()
|
||
|
self.tokr(":")
|
||
|
ifc = self.lines()
|
||
|
|
||
|
elifs = []
|
||
|
while self.tokq(self.curws + self.curind + '$elif '):
|
||
|
v = self.exprr()
|
||
|
self.tokr(':')
|
||
|
c = self.lines()
|
||
|
elifs.append(self.o('elif', 'clause', v, 'body', c))
|
||
|
|
||
|
if self.tokq(self.curws + self.curind + "$else:"):
|
||
|
elsec = self.lines()
|
||
|
else:
|
||
|
elsec = None
|
||
|
|
||
|
return self.o('if', 'clause', expr, 'then', ifc, 'elif', elifs, 'else', elsec)
|
||
|
|
||
|
def forr(self):
|
||
|
self.tokr("$for ")
|
||
|
self.lock()
|
||
|
v = self.setabler()
|
||
|
self.tokr(" in ")
|
||
|
g = self.exprr()
|
||
|
self.tokr(":")
|
||
|
l = self.lines()
|
||
|
|
||
|
if self.tokq(self.curws + self.curind + '$else:'):
|
||
|
elsec = self.lines()
|
||
|
else:
|
||
|
elsec = None
|
||
|
|
||
|
return self.o('for', 'name', v, 'body', l, 'in', g, 'else', elsec)
|
||
|
|
||
|
def whiler(self):
|
||
|
self.tokr('$while ')
|
||
|
self.lock()
|
||
|
v = self.exprr()
|
||
|
self.tokr(":")
|
||
|
l = self.lines()
|
||
|
|
||
|
if self.tokq(self.curws + self.curind + '$else:'):
|
||
|
elsec = self.lines()
|
||
|
else:
|
||
|
elsec = None
|
||
|
|
||
|
return self.o('while', 'clause', v, 'body', l, 'null', None, 'else', elsec)
|
||
|
|
||
|
def assignr(self):
|
||
|
self.tokr('$ ')
|
||
|
assign = self.rer(r_var) # NOTE: setable
|
||
|
self.tokr(' = ')
|
||
|
expr = self.exprr()
|
||
|
self.tokr('\n')
|
||
|
|
||
|
return self.o('assign', 'name', assign, 'expr', expr)
|
||
|
|
||
|
def commentr(self):
|
||
|
self.tokr('$#')
|
||
|
self.lock()
|
||
|
while self.c() != '\n': pass
|
||
|
return self.o('comment')
|
||
|
|
||
|
def setabler(self):
|
||
|
out = [self.varr()] #@@ not quite right
|
||
|
while self.tokq(', '):
|
||
|
out.append(self.varr())
|
||
|
return out
|
||
|
|
||
|
def lines(self, start=False):
|
||
|
"""
|
||
|
This function gets called from two places:
|
||
|
1. at the start, where it's matching the document itself
|
||
|
2. after any command, where it matches one line or an indented block
|
||
|
"""
|
||
|
o = []
|
||
|
if not start: # try to match just one line
|
||
|
singleline = self.tokq(' ') and self.lineq()
|
||
|
if singleline:
|
||
|
return [singleline]
|
||
|
else:
|
||
|
self.rer(' *') #@@slurp space?
|
||
|
self.tokr('\n')
|
||
|
oldind = self.curind
|
||
|
self.curind += ' '
|
||
|
while 1:
|
||
|
oldws = self.curws
|
||
|
t = self.tokq(oldws + self.curind)
|
||
|
if not t: break
|
||
|
|
||
|
self.curws += self.ws()
|
||
|
x = t and (
|
||
|
self.varsetq() or
|
||
|
self.ifq() or
|
||
|
self.forq() or
|
||
|
self.whileq() or
|
||
|
self.assignq() or
|
||
|
self.commentq() or
|
||
|
self.lineq())
|
||
|
self.curws = oldws
|
||
|
if not x:
|
||
|
break
|
||
|
elif x[WHAT] == 'comment':
|
||
|
pass
|
||
|
else:
|
||
|
o.append(x)
|
||
|
|
||
|
if not start: self.curind = oldind
|
||
|
return o
|
||
|
|
||
|
class Stowage(storage):
|
||
|
def __str__(self): return self.get('_str')
|
||
|
#@@ edits in place
|
||
|
def __add__(self, other):
|
||
|
if isinstance(other, (unicode, str)):
|
||
|
self._str += other
|
||
|
return self
|
||
|
else:
|
||
|
raise TypeError, 'cannot add'
|
||
|
def __radd__(self, other):
|
||
|
if isinstance(other, (unicode, str)):
|
||
|
self._str = other + self._str
|
||
|
return self
|
||
|
else:
|
||
|
raise TypeError, 'cannot add'
|
||
|
|
||
|
class WTF(AssertionError): pass
|
||
|
class SecurityError(Exception):
|
||
|
"""The template seems to be trying to do something naughty."""
|
||
|
pass
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
Required = object()
|
||
|
class Template:
|
||
|
globals = {}
|
||
|
content_types = {
|
||
|
'.html' : 'text/html; charset=utf-8',
|
||
|
'.txt' : 'text/plain',
|
||
|
}
|
||
|
|
||
|
def __init__(self, text, filter=None, filename=""):
|
||
|
self.filter = filter
|
||
|
self.filename = filename
|
||
|
# universal newlines:
|
||
|
text = text.replace('\r\n', '\n').replace('\r', '\n').expandtabs()
|
||
|
if not text.endswith('\n'): text += '\n'
|
||
|
header, tree = TemplateParser(text, filename).go()
|
||
|
self.tree = tree
|
||
|
if header:
|
||
|
self.h_defwith(header)
|
||
|
else:
|
||
|
self.args, self.kwargs = (), {}
|
||
|
|
||
|
def __call__(self, *a, **kw):
|
||
|
d = self.globals.copy()
|
||
|
d.update(self._parseargs(a, kw))
|
||
|
f = Fill(self.tree, d=d)
|
||
|
if self.filter: f.filter = self.filter
|
||
|
|
||
|
import webapi as web
|
||
|
if 'headers' in web.ctx and self.filename:
|
||
|
content_type = self.find_content_type()
|
||
|
if content_type:
|
||
|
web.header('Content-Type', content_type, unique=True)
|
||
|
|
||
|
return f.go()
|
||
|
|
||
|
def find_content_type(self):
|
||
|
for ext, content_type in self.content_types.iteritems():
|
||
|
if self.filename.endswith(ext):
|
||
|
return content_type
|
||
|
|
||
|
def _parseargs(self, inargs, inkwargs):
|
||
|
# difference from Python:
|
||
|
# no error on setting a keyword arg twice
|
||
|
d = {}
|
||
|
for arg in self.args:
|
||
|
d[arg] = Required
|
||
|
for kw, val in self.kwargs:
|
||
|
d[kw] = val
|
||
|
|
||
|
for n, val in enumerate(inargs):
|
||
|
if n < len(self.args):
|
||
|
d[self.args[n]] = val
|
||
|
elif n < len(self.args)+len(self.kwargs):
|
||
|
kw = self.kwargs[n - len(self.args)][0]
|
||
|
d[kw] = val
|
||
|
|
||
|
for kw, val in inkwargs.iteritems():
|
||
|
d[kw] = val
|
||
|
|
||
|
unset = []
|
||
|
for k, v in d.iteritems():
|
||
|
if v is Required:
|
||
|
unset.append(k)
|
||
|
if unset:
|
||
|
raise TypeError, 'values for %s are required' % unset
|
||
|
|
||
|
return d
|
||
|
|
||
|
def h_defwith(self, header):
|
||
|
assert header[WHAT] == 'defwith'
|
||
|
f = Fill(self.tree, d={})
|
||
|
|
||
|
self.args = header[ARGS]
|
||
|
self.kwargs = []
|
||
|
for var, valexpr in header[KWARGS]:
|
||
|
self.kwargs.append((var, f.h(valexpr)))
|
||
|
|
||
|
def __repr__(self):
|
||
|
return "<Template: %s>" % self.filename
|
||
|
|
||
|
class Handle:
|
||
|
def __init__(self, parsetree, **kw):
|
||
|
self._funccache = {}
|
||
|
self.parsetree = parsetree
|
||
|
for (k, v) in kw.iteritems(): setattr(self, k, v)
|
||
|
|
||
|
def h(self, item):
|
||
|
return getattr(self, 'h_' + item[WHAT])(item)
|
||
|
|
||
|
class Fill(Handle):
|
||
|
builtins = global_globals
|
||
|
def filter(self, text):
|
||
|
if text is None: return ''
|
||
|
else: return utf8(text)
|
||
|
# often replaced with stuff like net.websafe
|
||
|
|
||
|
def h_literal(self, i):
|
||
|
item = i[THING]
|
||
|
if isinstance(item, (unicode, str)) and item[0] in ['"', "'"]:
|
||
|
item = item[1:-1]
|
||
|
elif isinstance(item, (float, int)):
|
||
|
pass
|
||
|
return item
|
||
|
|
||
|
def h_list(self, i):
|
||
|
x = i[THING]
|
||
|
out = []
|
||
|
for item in x:
|
||
|
out.append(self.h(item))
|
||
|
return out
|
||
|
|
||
|
def h_dict(self, i):
|
||
|
x = i[THING]
|
||
|
out = {}
|
||
|
for k, v in x.iteritems():
|
||
|
out[self.h(k)] = self.h(v)
|
||
|
return out
|
||
|
|
||
|
def h_paren(self, i):
|
||
|
item = i[THING]
|
||
|
if isinstance(item, list):
|
||
|
raise NotImplementedError, 'tuples'
|
||
|
return self.h(item)
|
||
|
|
||
|
def h_getattr(self, i):
|
||
|
thing, attr = i[THING], i[ATTR]
|
||
|
thing = self.h(thing)
|
||
|
if attr.startswith('_') or attr.startswith('func_') or attr.startswith('im_'):
|
||
|
raise SecurityError, 'tried to get ' + attr
|
||
|
try:
|
||
|
if thing in self.builtins:
|
||
|
raise SecurityError, 'tried to getattr on ' + repr(thing)
|
||
|
except TypeError:
|
||
|
pass # raised when testing an unhashable object
|
||
|
try:
|
||
|
return getattr(thing, attr)
|
||
|
except AttributeError:
|
||
|
if isinstance(thing, list) and attr == 'join':
|
||
|
return lambda s: s.join(thing)
|
||
|
else:
|
||
|
raise
|
||
|
|
||
|
def h_call(self, i):
|
||
|
call = self.h(i[THING])
|
||
|
args = [self.h(x) for x in i[ARGS]]
|
||
|
kw = dict([(x, self.h(y)) for (x, y) in i[KWARGS]])
|
||
|
return call(*args, **kw)
|
||
|
|
||
|
def h_getitem(self, i):
|
||
|
thing, item = i[THING], i[ITEM]
|
||
|
thing = self.h(thing)
|
||
|
item = self.h(item)
|
||
|
return thing[item]
|
||
|
|
||
|
def h_expr(self, i):
|
||
|
item = self.h(i[THING])
|
||
|
if i[NEGATE]:
|
||
|
item = not item
|
||
|
return item
|
||
|
|
||
|
def h_test(self, item):
|
||
|
ox, op, oy = item[X], item[OP], item[Y]
|
||
|
# for short-circuiting to work, we can't eval these here
|
||
|
e = self.h
|
||
|
if op == 'is':
|
||
|
return e(ox) is e(oy)
|
||
|
elif op == 'is not':
|
||
|
return e(ox) is not e(oy)
|
||
|
elif op == 'in':
|
||
|
return e(ox) in e(oy)
|
||
|
elif op == 'not in':
|
||
|
return e(ox) not in e(oy)
|
||
|
elif op == '==':
|
||
|
return e(ox) == e(oy)
|
||
|
elif op == '!=':
|
||
|
return e(ox) != e(oy)
|
||
|
elif op == '>':
|
||
|
return e(ox) > e(oy)
|
||
|
elif op == '<':
|
||
|
return e(ox) < e(oy)
|
||
|
elif op == '<=':
|
||
|
return e(ox) <= e(oy)
|
||
|
elif op == '>=':
|
||
|
return e(ox) >= e(oy)
|
||
|
elif op == 'and':
|
||
|
return e(ox) and e(oy)
|
||
|
elif op == 'or':
|
||
|
return e(ox) or e(oy)
|
||
|
elif op == '+':
|
||
|
return e(ox) + e(oy)
|
||
|
elif op == '-':
|
||
|
return e(ox) - e(oy)
|
||
|
elif op == '*':
|
||
|
return e(ox) * e(oy)
|
||
|
elif op == '/':
|
||
|
return e(ox) / e(oy)
|
||
|
elif op == '%':
|
||
|
return e(ox) % e(oy)
|
||
|
else:
|
||
|
raise WTF, 'op ' + op
|
||
|
|
||
|
def h_var(self, i):
|
||
|
v = i[NAME]
|
||
|
if v in self.d:
|
||
|
return self.d[v]
|
||
|
elif v in self.builtins:
|
||
|
return self.builtins[v]
|
||
|
elif v == 'self':
|
||
|
return self.output
|
||
|
else:
|
||
|
raise NameError, 'could not find %s (line %s)' % (repr(i[NAME]), i[LINENO])
|
||
|
|
||
|
def h_line(self, i):
|
||
|
out = []
|
||
|
for x in i[THING]:
|
||
|
#@@ what if x is unicode
|
||
|
if isinstance(x, str):
|
||
|
out.append(x)
|
||
|
elif x[WHAT] == 'itpl':
|
||
|
o = self.h(x[NAME])
|
||
|
if x[FILTER]:
|
||
|
o = self.filter(o)
|
||
|
else:
|
||
|
o = (o is not None and utf8(o)) or ""
|
||
|
out.append(o)
|
||
|
else:
|
||
|
raise WTF, x
|
||
|
return ''.join(out)
|
||
|
|
||
|
def h_varset(self, i):
|
||
|
self.output[i[NAME]] = ''.join(self.h_lines(i[BODY]))
|
||
|
return ''
|
||
|
|
||
|
def h_if(self, i):
|
||
|
expr = self.h(i[CLAUSE])
|
||
|
if expr:
|
||
|
do = i[BODY]
|
||
|
else:
|
||
|
for e in i[ELIF]:
|
||
|
expr = self.h(e[CLAUSE])
|
||
|
if expr:
|
||
|
do = e[BODY]
|
||
|
break
|
||
|
else:
|
||
|
do = i[ELSE]
|
||
|
return ''.join(self.h_lines(do))
|
||
|
|
||
|
def h_for(self, i):
|
||
|
out = []
|
||
|
assert i[IN][WHAT] == 'expr'
|
||
|
invar = self.h(i[IN])
|
||
|
forvar = i[NAME]
|
||
|
if invar:
|
||
|
for nv in invar:
|
||
|
if len(forvar) == 1:
|
||
|
fv = forvar[0]
|
||
|
assert fv[WHAT] == 'var'
|
||
|
self.d[fv[NAME]] = nv # same (lack of) scoping as Python
|
||
|
else:
|
||
|
for x, y in zip(forvar, nv):
|
||
|
assert x[WHAT] == 'var'
|
||
|
self.d[x[NAME]] = y
|
||
|
|
||
|
out.extend(self.h_lines(i[BODY]))
|
||
|
else:
|
||
|
if i[ELSE]:
|
||
|
out.extend(self.h_lines(i[ELSE]))
|
||
|
return ''.join(out)
|
||
|
|
||
|
def h_while(self, i):
|
||
|
out = []
|
||
|
expr = self.h(i[CLAUSE])
|
||
|
if not expr:
|
||
|
return ''.join(self.h_lines(i[ELSE]))
|
||
|
c = 0
|
||
|
while expr:
|
||
|
c += 1
|
||
|
if c >= MAX_ITERS:
|
||
|
raise RuntimeError, 'too many while-loop iterations (line %s)' % i[LINENO]
|
||
|
out.extend(self.h_lines(i[BODY]))
|
||
|
expr = self.h(i[CLAUSE])
|
||
|
return ''.join(out)
|
||
|
|
||
|
def h_assign(self, i):
|
||
|
self.d[i[NAME]] = self.h(i[EXPR])
|
||
|
return ''
|
||
|
|
||
|
def h_comment(self, i): pass
|
||
|
|
||
|
def h_lines(self, lines):
|
||
|
if lines is None: return []
|
||
|
return map(self.h, lines)
|
||
|
|
||
|
def go(self):
|
||
|
self.output = Stowage()
|
||
|
self.output._str = ''.join(map(self.h, self.parsetree))
|
||
|
if self.output.keys() == ['_str']:
|
||
|
self.output = self.output['_str']
|
||
|
return self.output
|
||
|
|
||
|
class render:
|
||
|
def __init__(self, loc='templates/', cache=True):
|
||
|
self.loc = loc
|
||
|
if cache:
|
||
|
self.cache = {}
|
||
|
else:
|
||
|
self.cache = False
|
||
|
|
||
|
def _do(self, name, filter=None):
|
||
|
if self.cache is False or name not in self.cache:
|
||
|
|
||
|
tmplpath = os.path.join(self.loc, name)
|
||
|
p = [f for f in glob.glob(tmplpath + '.*') if not f.endswith('~')] # skip backup files
|
||
|
if not p and os.path.isdir(tmplpath):
|
||
|
return render(tmplpath, cache=self.cache)
|
||
|
elif not p:
|
||
|
raise AttributeError, 'no template named ' + name
|
||
|
|
||
|
p = p[0]
|
||
|
c = Template(open(p).read(), filename=p)
|
||
|
if self.cache is not False: self.cache[name] = (p, c)
|
||
|
|
||
|
if self.cache is not False: p, c = self.cache[name]
|
||
|
|
||
|
if p.endswith('.html') or p.endswith('.xml'):
|
||
|
if not filter: c.filter = websafe
|
||
|
return c
|
||
|
|
||
|
def __getattr__(self, p):
|
||
|
return self._do(p)
|
||
|
|
||
|
def frender(fn, *a, **kw):
|
||
|
return Template(open(fn).read(), *a, **kw)
|
||
|
|
||
|
def test():
|
||
|
import sys
|
||
|
verbose = '-v' in sys.argv
|
||
|
def assertEqual(a, b):
|
||
|
if a == b:
|
||
|
if verbose:
|
||
|
sys.stderr.write('.')
|
||
|
sys.stderr.flush()
|
||
|
else:
|
||
|
assert a == b, "\nexpected: %s\ngot: %s" % (repr(b), repr(a))
|
||
|
|
||
|
from utils import storage, group
|
||
|
|
||
|
class t:
|
||
|
def __init__(self, text):
|
||
|
self.text = text
|
||
|
|
||
|
def __call__(self, *a, **kw):
|
||
|
return TestResult(self.text, Template(self.text)(*a, **kw))
|
||
|
|
||
|
class TestResult:
|
||
|
def __init__(self, source, value):
|
||
|
self.source = source
|
||
|
self.value = value
|
||
|
|
||
|
def __eq__(self, other):
|
||
|
if self.value == other:
|
||
|
if verbose:
|
||
|
sys.stderr.write('.')
|
||
|
else:
|
||
|
print >> sys.stderr, 'FAIL:', repr(self.source), 'expected', repr(other), ', got', repr(self.value)
|
||
|
sys.stderr.flush()
|
||
|
|
||
|
t('1')() == '1\n'
|
||
|
t('$def with ()\n1')() == '1\n'
|
||
|
t('$def with (a)\n$a')(1) == '1\n'
|
||
|
t('$def with (a=0)\n$a')(1) == '1\n'
|
||
|
t('$def with (a=0)\n$a')(a=1) == '1\n'
|
||
|
t('$if 1: 1')() == '1\n'
|
||
|
t('$if 1:\n 1')() == '1\n'
|
||
|
t('$if 0: 0\n$elif 1: 1')() == '1\n'
|
||
|
t('$if 0: 0\n$elif None: 0\n$else: 1')() == '1\n'
|
||
|
t('$if (0 < 1) and (1 < 2): 1')() == '1\n'
|
||
|
t('$for x in [1, 2, 3]: $x')() == '1\n2\n3\n'
|
||
|
t('$for x in []: 0\n$else: 1')() == '1\n'
|
||
|
t('$def with (a)\n$while a and a.pop(): 1')([1, 2, 3]) == '1\n1\n1\n'
|
||
|
t('$while 0: 0\n$else: 1')() == '1\n'
|
||
|
t('$ a = 1\n$a')() == '1\n'
|
||
|
t('$# 0')() == ''
|
||
|
t('$def with (d)\n$for k, v in d.iteritems(): $k')({1: 1}) == '1\n'
|
||
|
t('$def with (a)\n$(a)')(1) == '1\n'
|
||
|
t('$def with (a)\n$a')(1) == '1\n'
|
||
|
t('$def with (a)\n$a.b')(storage(b=1)) == '1\n'
|
||
|
t('$def with (a)\n$a[0]')([1]) == '1\n'
|
||
|
t('${0 or 1}')() == '1\n'
|
||
|
t('$ a = [1]\n$a[0]')() == '1\n'
|
||
|
t('$ a = {1: 1}\n$a.keys()[0]')() == '1\n'
|
||
|
t('$ a = []\n$if not a: 1')() == '1\n'
|
||
|
t('$ a = {}\n$if not a: 1')() == '1\n'
|
||
|
t('$ a = -1\n$a')() == '-1\n'
|
||
|
t('$ a = "1"\n$a')() == '1\n'
|
||
|
t('$if 1 is 1: 1')() == '1\n'
|
||
|
t('$if not 0: 1')() == '1\n'
|
||
|
t('$if 1:\n $if 1: 1')() == '1\n'
|
||
|
t('$ a = 1\n$a')() == '1\n'
|
||
|
t('$ a = 1.\n$a')() == '1.0\n'
|
||
|
t('$({1: 1}.keys()[0])')() == '1\n'
|
||
|
t('$for x in [1, 2, 3]:\n\t$x')() == ' 1\n 2\n 3\n'
|
||
|
t('$def with (a)\n$:a')(1) == '1\n'
|
||
|
t('$def with (a)\n$a')(u'\u203d') == '\xe2\x80\xbd\n'
|
||
|
t(u'$def with (f)\n$:f("x")')(lambda x: x) == 'x\n'
|
||
|
|
||
|
j = Template("$var foo: bar")()
|
||
|
assertEqual(str(j), '')
|
||
|
assertEqual(j.foo, 'bar\n')
|
||
|
if verbose: sys.stderr.write('\n')
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
test()
|