##
# Contemplate
# Light-weight Object-Oriented Template Engine for PHP, Python, JavaScript
#
# @version 1.6.0
# https://github.com/foo123/Contemplate
#
# @inspired by : Simple JavaScript Templating, John Resig - http://ejohn.org/ - MIT Licensed
# http://ejohn.org/blog/javascript-micro-templating/
#
##
# needed imports
import os, sys, re, time, datetime, calendar, math, codecs, json
try:
# Python 3.x
import urllib.parse
# http://www.php2python.com/wiki/function.urlencode/
def rawurlencode(s):
return urllib.parse.quote(s)
def rawurldecode(s):
return urllib.parse.unquote(s)
def urlencode(s):
return urllib.parse.quote_plus(s)
def urldecode(s):
return urllib.parse.unquote_plus(s)
except ImportError:
# Python 2.x
import urllib
# http://www.php2python.com/wiki/function.urlencode/
def rawurlencode(s):
return urllib.quote(s)
def rawurldecode(s):
return urllib.unquote(s)
def urlencode(s):
return urllib.quote_plus(s)
def urldecode(s):
return urllib.unquote_plus(s)
try:
from importlib import reload
except ImportError:
pass
# (protected) global properties
class _G:
isInited = False
leftTplSep = "<%"
rightTplSep = "%>"
tplStart = ''
tplEnd = ''
preserveLinesDefault = "' + \"\\n\" + '"
preserveLines = ''
EOL = "\n"
TEOL = "\n" #os.linesep
pad = " "
escape = True
level = 0
loops = 0
ifs = 0
loopifs = 0
allblocks = None
allblockscnt = None
openblocks = None
startblock = None
endblock = None
blockptr = -1
locals = None
variables = None
strings = None
currentblock = None
extends = None
uses = None
id = 0
funcId = 0
uuid = 0
ctx = None
glob = None
context = None
NEWLINE = re.compile(r'\n\r|\r\n|\r|\n')
SQUOTE = re.compile(r"'")
NL = re.compile(r'\n')
DS_RE = re.compile(r'[/\\]')
TAG_RE = re.compile(r'</?[a-zA-Z0-9:_\-]+[^<>]*>',re.S|re.M)
AMP_RE = re.compile(r'&+')
UNDERL = re.compile(r'[\W]+')
ALPHA = re.compile(r'^[a-zA-Z_]')
NUM = re.compile(r'^[0-9]')
ALPHANUM = re.compile(r'^[a-zA-Z0-9_]')
SPACE = re.compile(r'^\s')
ALL_SPACE = re.compile(r'^\s+$')
INDENT = re.compile(r'^(postdent|predent)\((-?\d+)\):')
T_OR = re.compile(r'(.)(\|\|)(.)')
T_AND = re.compile(r'(.)(&&)(.)')
T_NOT = re.compile(r'(.)(!)([^=])')
TT_ClassCode = None
TT_BlockCode = None
TT_BLOCK = None
TT_FUNC = None
TT_RCODE = None
re_controls = re.compile(r'(\t|\s?)\s*((#ID_(continue|endblock|elsefor|endfor|endif|break|else|fi)#(\s*\(\s*\))?)|(#ID_([^#]+)#\s*(\()))(.*)$')
reserved_var_names = [
'Contemplate', 'self', 'self_', 'data', '__p__', '__i__', '__ctx'
]
directives = [
'set', 'unset', 'isset',
'if', 'elseif', 'else', 'endif',
'for', 'elsefor', 'endfor',
'extends', 'block', 'endblock',
'include', 'super', 'getblock', 'iif', 'empty', 'continue', 'break', 'local_set', 'get', 'local'
]
directive_aliases = {
'elif' : 'elseif'
,'fi' : 'endif'
}
aliases = {
'l' : 'locale'
,'xl' : 'xlocale'
,'nl' : 'nlocale'
,'nxl' : 'nxlocale'
,'cc' : 'concat'
,'j' : 'join'
,'dq' : 'qq'
,'now' : 'time'
,'template' : 'tpl'
}
T_REGEXP = type(_G.NEWLINE)
#
# Auxilliary methods
# (mostly methods to simulate php-like functionality needed by the engine)
#
def array_keys(o):
if isinstance(o, (list,tuple)): return list(map(str, range(0, len(o))))
if isinstance(o, dict): return list(o.keys())
return []
def array_values(o):
if isinstance(o, list): return o
if isinstance(o, tuple): return list(o)
if isinstance(o, dict):
if is_numeric_array(o):
# get values in list-order by ascending index
v = []
l = len(o)
i = 0
while i < l:
v.append(o[str(i)])
i += 1
return v
else:
return list(o.values())
return []
def is_numeric_array(o):
if isinstance(o, (list,tuple)): return True
if isinstance(o, dict):
k = array_keys(o)
i = 0
l = len(k)
while i < l:
if str(i) not in k: return False
i += 1
return True
return False
default_date_locale = {
'meridian': {'am':'am', 'pm':'pm', 'AM':'AM', 'PM':'PM'}
,'ordinal': {'ord':{1:'st',2:'nd',3:'rd'}, 'nth':'th'}
,'timezone': ['UTC','EST','MDT']
,'timezone_short': ['UTC','EST','MDT']
,'day': ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']
,'day_short': ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']
,'month': ['January','February','March','April','May','June','July','August','September','October','November','December']
,'month_short': ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']
}
def wait_ms(ms):
time.sleep(ms / 1000)
def open_file(file, op, encoding):
return open(file, op, -1, encoding)
def read_file(file, encoding):
buffer = ''
f = open_file(file, 'r', encoding)
buffer = f.read()
f.close()
return buffer
# https://stackoverflow.com/questions/186202/what-is-the-best-way-to-open-a-file-for-exclusive-access-in-python
# https://github.com/drandreaskrueger/lockbydir
def write_file(file, text, encoding):
f = open_file(file, 'w', encoding)
f.write(text)
f.close()
def parse_str(s):
# http://www.php2python.com/wiki/function.parse-str/
global _G
strArr = s.strip('&').split('&')
array = {}
possibleLists = []
for tmp in strArr:
tmp = tmp.split('=')
key = rawurldecode(tmp[0].strip())
if len(tmp) < 2: value = ''
else: value = rawurldecode(tmp[1].strip())
j = key.find('\x00')
if j > -1: key = key[0:j]
if key and '[' != key[0]:
keys = []
postLeftBracketPos = 0
lk = len(key)
for j in range(lk):
if '[' == key[j] and 0 == postLeftBracketPos:
postLeftBracketPos = j + 1
elif ']' == key[j]:
if postLeftBracketPos:
if 0 == len(keys):
keys.append(key[0:postLeftBracketPos - 1])
keys.append(key[postLeftBracketPos:j])
postLeftBracketPos = 0
if j < lk-1 and '[' != key[j + 1]: break
if 0 == len(keys): keys = [key]
for j in range(len(key[0])):
chr = keys[0][j]
if ' ' == chr or '.' == chr or '[' == chr:
keys[0] = keys[0][0:j] + '_' + keys[0][j + 1:]
if '[' == chr: break
obj = array
key = None
lastObj = obj
lastkey = keys[len(keys)-1].strip("'\"").strip() if len(keys) else None
for j in range(len(keys)):
prevkey = key
key = keys[j].strip("'\"")
prevobj = lastObj
lastObj = obj
if '' != key.strip() or 0 == j:
if key not in obj: obj[key] = [] if (j+1 == len(keys)-1) and (''==lastkey) else {}
obj = obj[key]
else:
# To insert new dimension
#ct = -1
#for p in obj:
# if _G.digit.match(p) and int(p) > ct: ct = int(p)
#key = str(ct + 1)
key = True
if key is True:
lastObj.append(value)
else:
try:
ikey = int(key, 10)
except BaseException as exc:
ikey = -1
if 0 <= ikey:
possibleLists.append({'key':prevkey,'obj':prevobj})
lastObj[ key ] = value
i = len(possibleLists)-1
while i >= 0:
# safe to pass multiple times same obj, it is possible
obj = possibleLists[i]['obj'][possibleLists[i]['key']] if possibleLists[i]['key'] else possibleLists[i]['obj']
if is_numeric_array(obj):
obj = array_values(obj)
if possibleLists[i]['key']:
possibleLists[i]['obj'][possibleLists[i]['key']] = obj
else:
array = obj
i -= 1
return array
def http_build_query_helper(key, val, arg_separator, PHP_QUERY_RFC3986):
encode = rawurlencode if PHP_QUERY_RFC3986 else urlencode
if True == val: val = "1"
elif False == val: val = "0"
if val is not None:
key = str(key)
data = None
if isinstance(val, dict): data = val.items()
elif isinstance(val, (list, tuple)): data = enumerate(val)
if data:
tmp = []
for k,v in data:
if v is not None:
tmp.append(http_build_query_helper(key + "[" + str(k) + "]", v, arg_separator, PHP_QUERY_RFC3986))
return arg_separator.join(tmp)
else:
return encode(key) + "=" + encode(str(val))
else:
return ''
def http_build_query(data, arg_separator = '&', PHP_QUERY_RFC3986 = False):
tmp = []
for key,value in data.items():
query = http_build_query_helper(key, value, arg_separator, PHP_QUERY_RFC3986)
if '' != query: tmp.append(query)
return arg_separator.join(tmp)
def php_time():
return int(time.time())
def php_date(format, timestamp = None):
global default_date_locale
locale = default_date_locale
# http://php.net/manual/en/datetime.formats.date.php
# http://strftime.org/
# https://docs.python.org/2/library/time.html
# adapted from http://brandonwamboldt.ca/python-php-date-class-335/
if timestamp is None: timestamp = php_time()
utime = timestamp
dtime = datetime.datetime.fromtimestamp(timestamp)
D = {}
w = dtime.weekday()
W = dtime.isocalendar()[1]
d = dtime.day
dmod10 = d % 10
n = dtime.month
Y = dtime.year
g = int(dtime.strftime("%I"))
G = int(dtime.strftime("%H"))
meridian = dtime.strftime("%p")
tzo = int(time.timezone / 60)
atzo = abs(tzo)
# Calculate and return Swatch Internet Time
# http://code.activestate.com/recipes/578473-calculating-swatch-internet-time-or-beats/
lh, lm, ls = time.localtime()[3:6]
beats = ((lh * 3600) + (lm * 60) + ls + time.timezone) / 86.4
if beats > 1000: beats -= 1000
elif beats < 0: beats += 1000
# Day --
D['d'] = str(d).zfill(2)
D['D'] = locale['day_short'][0 if 6 == w else w+1]
D['j'] = str(d)
D['l'] = locale['day'][0 if 6 == w else w+1]
D['N'] = str(w+1 if 6 > w else 7)
D['S'] = locale['ordinal']['ord'][d] if d in locale['ordinal']['ord'] else (locale['ordinal']['ord'][dmod10] if dmod10 in locale['ordinal']['ord'] else locale['ordinal']['nth'])
D['w'] = str(1 if 6 == w else w+2)
D['z'] = str(dtime.timetuple().tm_yday)
# Week --
D['W'] = str(W)
# Month --
D['F'] = locale['month'][n-1]
D['m'] = str(n).zfill(2)
D['M'] = locale['month_short'][n-1]
D['n'] = str(n)
D['t'] = str(calendar.monthrange(Y, n)[1])
# Year --
D['L'] = str(int(calendar.isleap(Y)))
D['o'] = str(Y + (1 if n == 12 and W < 9 else (-1 if n == 1 and W > 9 else 0)))
D['Y'] = str(Y)
D['y'] = str(Y)[2:]
# Time --
D['a'] = locale['meridian'][meridian.lower()] if meridian.lower() in locale['meridian'] else meridian.lower()
D['A'] = locale['meridian'][meridian] if meridian in locale['meridian'] else meridian
D['B'] = str(int(beats)).zfill(3)
D['g'] = str(g)
D['G'] = str(G)
D['h'] = str(g).zfill(2)
D['H'] = str(G).zfill(2)
D['i'] = str(dtime.minute).zfill(2)
D['s'] = str(dtime.second).zfill(2)
D['u'] = str(dtime.microsecond).zfill(6)
# Timezone --
D['e'] = '' # TODO, missing
D['I'] = str(dtime.dst())
D['O'] = ('-' if tzo > 0 else '+')+str(int(atzo / 60) * 100 + atzo % 60).zfill(4)
D['P'] = D['O'][:3]+':'+D['O'][3:]
D['T'] = 'UTC'
D['Z'] = str(-tzo*60)
# Full Date/Time --
D['c'] = D['Y']+'-'+D['m']+'-'+D['d']+'\\'+D['T']+D['H']+':'+D['i']+':'+D['s']+D['P']
D['r'] = D['D']+', '+D['d']+' '+D['M']+' '+D['Y']+' '+D['H']+':'+D['i']+':'+D['s']+' '+D['O']
D['U'] = str(utime)
formatted_datetime = ''
for f in format: formatted_datetime += D[f] if f in D else f
return formatted_datetime
sprintf_format_re = re.compile(r'%%|%(\d+\$)?([-+\'#0 ]*)(\*\d+\$|\*|\d+)?(\.(\*\d+\$|\*|\d+))?([scboxXuideEfFgG])', re.S)
def sprintf_(fmt, args):
global sprintf_format_re
class nonlocal_:
index = 0
def do_format(m):
match = m.group(0)
if '%%' == match: return match
valueIndex = m.group(1)
flags = m.group(2)
minWidth = m.group(3)
precision = m.group(4)
#precision = m.group(5)
type = m.group(6)
if valueIndex:
repl = '%(arg_'+str(int(valueIndex[0:-1])-1)+')'
else:
repl = '%(arg_'+str(nonlocal_.index)+')'
nonlocal_.index += 1
if flags:
repl += flags
if minWidth:
repl += minWidth
if precision:
repl += precision
if type:
repl += type
return repl
fmt = re.sub(sprintf_format_re, do_format, fmt)
arguments = {}
index = 0
for arg in args:
arguments['arg_'+str(index)] = arg
index += 1
return fmt % arguments
def sprintf(format, *args):
#if len(args) and isinstance(args[0],(list,tuple)): args = args[0]
#return format % tuple(args)
return sprintf_(format, args)
def vsprintf(format, args):
#return format % tuple(args)
return sprintf_(format, args)
# static
def import_tpl(filename, classname, cacheDir, doReload = False):
# http://www.php2python.com/wiki/function.import_tpl/
# http://docs.python.org/dev/3.0/whatsnew/3.0.html
# http://stackoverflow.com/questions/4821104/python-dynamic-instantiation-from-string-name-of-a-class-in-dynamically-imported
#_locals_ = {'Contemplate': Contemplate}
#_globals_ = {'Contemplate': Contemplate}
#if 'execfile' in globals():
# # Python 2.x
# execfile(filename, _globals_, _locals_)
# return _locals_[classname]
#else:
# # Python 3.x
# exec(read_file(filename), _globals_, _locals_)
# return _locals_[classname]
# http://docs.python.org/2/library/imp.html
# http://docs.python.org/2/library/functions.html#__import__
# http://docs.python.org/3/library/functions.html#__import__
# http://stackoverflow.com/questions/301134/dynamic-module-import-in-python
# http://stackoverflow.com/questions/11108628/python-dynamic-from-import
# also: http://code.activestate.com/recipes/473888-lazy-module-imports/
# using import instead of execfile, usually takes advantage of Python cached compiled code
global _G
getTplClass = None
# add the dynamic import path to sys
basename = os.path.basename(filename)
directory = os.path.dirname(filename)
os.sys.path.append(cacheDir)
os.sys.path.append(directory)
#currentcwd = os.getcwd()
#os.chdir(directory) # change working directory so we know import will work
if os.path.exists(filename):
modname = basename[:-3] # remove .py extension
max_tries = 3
tries = 0
found = False
# try to recover from module not found if created just before importing
# retry a specified amount of times untill succeeded
# TO FIND and FIX: still fails sometimes even with delay added and multiple retries
while tries < max_tries and not found:
tries += 1
try:
mod = __import__(modname)
found = True
except ModuleNotFoundError:
found = False
if not found: wait_ms(100) # delay 100 ms
if found:
if doReload: reload(mod) # Might be out of date
# a trick in-order to pass the Contemplate super-class in a cross-module way
getTplClass = getattr(mod, '__getTplClass__')
# restore current dir
#os.chdir(currentcwd)
# remove the dynamic import path from sys
del os.sys.path[-1]
del os.sys.path[-1]
# return the tplClass if found
return getTplClass(Contemplate) if getTplClass else None
# static
def create_function(funcName, args, sourceCode, additional_symbols = dict()):
# http://code.activestate.com/recipes/550804-create-a-restricted-python-function-from-a-string/
# The list of symbols that are included by default in the generated
# function's environment
SAFE_SYMBOLS = [
"list", "dict", "enumerate", "tuple", "set", "long", "float", "object",
"bool", "callable", "True", "False", "dir",
"frozenset", "getattr", "hasattr", "abs", "cmp", "complex",
"divmod", "id", "pow", "round", "slice", "vars",
"hash", "hex", "int", "isinstance", "issubclass", "len",
"map", "filter", "max", "min", "oct", "chr", "ord", "range",
"reduce", "repr", "str", "type", "zip", "xrange", "None",
"Exception", "KeyboardInterrupt"
]
# Also add the standard exceptions
__bi = __builtins__
if type(__bi) is not dict:
__bi = __bi.__dict__
for k in __bi:
if k.endswith("Error") or k.endswith("Warning"):
SAFE_SYMBOLS.append(k)
del __bi
# Include the sourcecode as the code of a function funcName:
s = "def " + funcName + "(%s):\n" % args
s += sourceCode # this should be already properly padded
# Byte-compilation (optional)
byteCode = compile(s, "<string>", 'exec')
# Setup the local and global dictionaries of the execution
# environment for __TheFunction__
bis = dict() # builtins
globs = dict()
locs = dict()
# Setup a standard-compatible python environment
bis["locals"] = lambda: locs
bis["globals"] = lambda: globs
globs["__builtins__"] = bis
globs["__name__"] = "SUBENV"
globs["__doc__"] = sourceCode
# Determine how the __builtins__ dictionary should be accessed
if type(__builtins__) is dict:
bi_dict = __builtins__
else:
bi_dict = __builtins__.__dict__
# Include the safe symbols
for k in SAFE_SYMBOLS:
# try from current locals
try:
locs[k] = locals()[k]
continue
except KeyError:
pass
# Try from globals
try:
globs[k] = globals()[k]
continue
except KeyError:
pass
# Try from builtins
try:
bis[k] = bi_dict[k]
except KeyError:
# Symbol not available anywhere: silently ignored
pass
# Include the symbols added by the caller, in the globals dictionary
globs.update(additional_symbols)
# Finally execute the Function statement:
eval(byteCode, globs, locs)
# As a result, the function is defined as the item funcName
# in the locals dictionary
fct = locs[funcName]
# Attach the function to the globals so that it can be recursive
del locs[funcName]
globs[funcName] = fct
# Attach the actual source code to the docstring
fct.__doc__ = sourceCode
# return the compiled function object
return fct
def reset_state():
global _G
_G.loops = 0
_G.ifs = 0
_G.loopifs = 0
_G.allblocks = []
_G.allblockscnt = {}
_G.openblocks = [[None, -1]]
_G.extends = None
_G.uses = []
_G.level = 0
_G.id = 0
_G.locals = {}
_G.variables = {}
_G.currentblock = '_'
if _G.currentblock not in _G.locals: _G.locals[_G.currentblock] = {}
if _G.currentblock not in _G.variables: _G.variables[_G.currentblock] = {}
#_G.funcId = 0
def clear_state():
global _G
_G.loops = 0
_G.ifs = 0
_G.loopifs = 0
_G.allblocks = []
_G.allblockscnt = {}
_G.openblocks = [[None, -1]]
#_G.extends = None
#_G.uses = []
_G.level = 0
_G.locals = None
_G.variables = None
_G.currentblock = None
_G.id = 0
_G.strings = None
#_G.funcId = 0
def push_state():
global _G
return [_G.loops, _G.ifs, _G.loopifs, _G.level,
_G.allblocks, _G.allblockscnt, _G.openblocks, _G.extends, _G.locals, _G.variables, _G.currentblock, _G.uses]
def pop_state(state):
global _G
_G.loops = state[0]
_G.ifs = state[1]
_G.loopifs = state[2]
_G.level = state[3]
_G.allblocks = state[4]
_G.allblockscnt = state[5]
_G.openblocks = state[6]
_G.extends = state[7]
_G.locals = state[8]
_G.variables = state[9]
_G.currentblock = state[10]
_G.uses = state[11]
def remove_initial_space(s):
l = len(s)
if l:
initial_space = ''
for c in s:
if ' ' == c or "\t" == c: initial_space += c
else: break
sl = len(initial_space)
if sl:
s = s[sl:]
pos = s.find("\n", 0);
while -1 < pos:
if initial_space == s[pos+1:pos+1+sl]:
s = s[0:pos+1] + s[pos+1+sl:]
pos = s.find("\n", pos + 1)
return s
def remove_blank_lines(s):
global _G
lines = s.split("\n")
n = len(lines)
start = 0
end = n-1
for i in range(n):
l = lines[i]
if len(l) and not _G.ALL_SPACE.match(l):
start = i
break
for i in range(n-1, start-1, -1):
l = lines[i]
if len(l) and not _G.ALL_SPACE.match(l):
end = i
break
return "\n".join(lines[start:end+1])
def align(s, level = None):
global _G
if level is None: level = _G.level
s = remove_initial_space(s)
l = len(s)
if l and (0 < level):
alignment = _G.pad * level
aligned = alignment
is_line_start = True
for c in s:
if "\n" == c:
aligned += "\n" + alignment
is_line_start = True
elif is_line_start:
# consistently replace tabs with our tabbed spaces
if _G.SPACE.match(c):
aligned += _G.pad if "\t" == c else c
else:
aligned += c
is_line_start = False
else:
aligned += c
else:
aligned = s
return aligned
def get_separators(text, separators = None):
global _G
if separators:
seps = separators.strip().split(" ")
_G.leftTplSep = seps[0].strip()
_G.rightTplSep = seps[1].strip()
else:
# tpl separators are defined on 1st (non-empty) line of tpl content
l = len(text)
i = 0
pos = 0
line = ""
while i < l and -1 < pos and not len(line):
pos = text.find("\n", i)
line = text[i:pos+1].strip() if -1 < pos else ""
i = pos+1
if len(line):
seps = line.split(" ")
_G.leftTplSep = seps[0].strip()
_G.rightTplSep = seps[1].strip()
text = text[pos+1:]
return text
def split_arguments(args, delim = ','):
args = args.strip()
l = len(args)
if not l: return ['']
i = 0
a = []
paren = []
s = ''
while i < l:
c = args[i]
i += 1
if delim == c and not len(paren):
a.append(s.strip())
s = ''
continue
s += c
if '(' == c:
paren.insert(0, ')')
elif '{' == c:
paren.insert(0, '}')
elif '[' == c:
paren.insert(0, ']')
elif ')' == c or '}' == c or ']' == c:
if (not len(paren)) or (paren[0] != c): break
paren.pop(0)
if len(s): a.append(s.strip())
if i < l: a.append(args[i:].strip())
return a
def local_variable(variable = None, block = None, literal = False):
global _G
if variable is None:
_G.id += 1
return '_loc_' + str(_G.id)
else:
if block is None: block = _G.currentblock
if not (_G.variables[block][variable] in _G.locals[block]):
_G.locals[block][_G.variables[block][variable]] = 2 if literal else 1
return variable
def is_local_variable(variable, block = None):
if block is None: block = _G.currentblock
#if variable.startswith('_loc_'): return 1
return _G.locals[block][_G.variables[block][variable]] if _G.variables[block][variable] in _G.locals[block] else 0
#
# Control structures
#
def t_include(id):
global _G
contx = _G.context
id = id.strip()
if _G.strings and (id in _G.strings): id = _G.strings[id]
ch = id[0]
if ('"' == ch or "'" == ch) and (ch == id[-1]): id = id[1:-1] # quoted id
# cache it
if id not in contx.partials: #and (id not in _G.glob.partials):
tpl = get_template_contents(id, contx)
tpl = get_separators(tpl)
state = push_state()
reset_state()
contx.partials[id] = ["" + parse(tpl, _G.leftTplSep, _G.rightTplSep, False) + "'" + _G.TEOL, _G.uses[:] if _G.uses else []]
pop_state(state)
# add usedTpls used inside include tpl to current usedTpls
for usedTpl in contx.partials[id][1]:
if usedTpl not in _G.uses:
_G.uses.append(usedTpl)
return align(contx.partials[id][0]) # if id in contx.partials else _G.glob.partials[id][0]
def t_block(block):
global _G
block = block.split(',')
echoed = not(("False"==block[1].strip()) if len(block)>1 else False)
block = block[0].strip()
if _G.strings and (block in _G.strings): block = _G.strings[block]
ch = block[0]
if ('"' == ch or "'" == ch) and (ch == block[-1]): block = block[1:-1] # quoted block
_G.allblocks.append([block, -1, -1, 0, _G.openblocks[0][1], echoed])
if block in _G.allblockscnt: _G.allblockscnt[block] += 1
else: _G.allblockscnt[block] = 1
_G.blockptr = len(_G.allblocks)
_G.openblocks[:0] = [[block, _G.blockptr-1]]
_G.startblock = block
_G.endblock = None
return "' + #BLOCK_" + block + "#"
def t_endblock(args = ''):
global _G
if 1 < len(_G.openblocks):
block = _G.openblocks.pop(0)
_G.endblock = block[0]
_G.blockptr = block[1]+1
_G.startblock = None
return "#/BLOCK_" + block[0] + "#"
return ''
#
# auxilliary parsing methods
#
def merge(m, *args):
numargs = len(args)
if numargs < 1: return m
merged = m
for arg in args:
# http://www.php2python.com/wiki/function.array-merge/
merged = dict(merged)
merged.update(arg)
return merged
def parse_constructs(match):
global _G
re_controls = _G.re_controls
prefix = match.group(1) if match.group(1) else ''
ctrl = match.group(4) if match.group(4) else (match.group(7) if match.group(7) else '')
rest = match.group(9) if match.group(9) else ''
startParen = match.group(8) if match.group(8) else False
args = ''
out = ''
# parse parentheses and arguments, accurately
if startParen and len(startParen):
paren = 1
l = len(rest)
i = 0
while i < l and paren > 0:
ch = rest[i]
i += 1
if '(' == ch: paren += 1
elif ')' == ch: paren -= 1
if paren > 0: args += ch
rest = rest[len(args)+1:]
args = args.strip()
if ctrl in _G.directive_aliases: ctrl = _G.directive_aliases[ctrl]
try:
m = _G.directives.index(ctrl)
except:
m = -1
if m > -1:
if 22==m: # local
varname = args.strip()
tplvarname = _G.variables[_G.currentblock][varname]
if tplvarname in _G.reserved_var_names:
# should be different from 'self', 'data', .. as these are used internally
raise Contemplate.Exception('Contemplate Parse: Use of reserved name as local variable name "'+tplvarname+'"')
local_variable(varname, None, True) # make it a literal local variable variable
out = "'" + _G.TEOL
elif 0==m or 20==m: # set, local_set
args = re.sub(re_controls, parse_constructs, args)
args = split_arguments(args, ',')
varname = args.pop(0).strip()
expr = ','.join(args).strip()
if 20 == m and not is_local_variable(varname): local_variable(varname) # make it a local variable
out = "'" + _G.TEOL + align(varname + ' = ('+ expr +')') + _G.TEOL
elif 21==m: # get
args = re.sub(re_controls, parse_constructs, args)
out = prefix + 'Contemplate.get(' + args + ')'
elif 1==m: # unset
args = re.sub(re_controls, parse_constructs, args)
varname = args
if varname:
varname = str(varname).strip()
out = "'" + _G.TEOL + align('if ("'+varname+'__RAW__" in data): del ' + varname) + _G.TEOL
else:
out = "'" + _G.TEOL
elif 2==m: # isset
args = re.sub(re_controls, parse_constructs, args)
varname = args
is_local_var = is_local_variable(varname)
out = '(("'+('' if 2 == is_local_var else '_loc_') + varname + '__RAW__" in locals()) and (' + varname + ' is not None))' if is_local_var else '(("' + varname + '__RAW__" in data) and (' + varname + ' is not None))'
elif 3==m: # if
args = re.sub(re_controls, parse_constructs, args)
out = "'" + align(_G.TEOL.join([
""
,"if ("+args+"):"
,""
]))
_G.ifs += 1
_G.level += 1
elif 4==m: # elseif
args = re.sub(re_controls, parse_constructs, args)
_G.level -= 1
out = "'" + align(_G.TEOL.join([
""
,"elif ("+args+"):"
,""
]))
_G.level += 1
elif 5==m: # else
_G.level -= 1
out = "'" + align(_G.TEOL.join([
""
,"else:"
,""
]))
_G.level += 1
elif 6==m: # endif
_G.ifs -= 1
_G.level -= 1
out = "'" + align(_G.TEOL.join([
"",""
]))
elif 7==m: # for
args = re.sub(re_controls, parse_constructs, args)
for_expr = args
is_php_style = for_expr.find(' as ')
is_python_style = for_expr.find(' in ')
if -1 < is_python_style:
for_expr = [for_expr[0:is_python_style], for_expr[is_python_style+4:]]
o = for_expr[1].strip()
kv = for_expr[0].split(',')
else: #if -1 < is_php_style
for_expr = [for_expr[0:is_php_style], for_expr[is_php_style+4:]]
o = for_expr[0].strip()
kv = for_expr[1].split('=>')
_o = local_variable()
isAssoc = (len(kv) >= 2)
if isAssoc:
k = kv[0].strip()
v = kv[1].strip()
_oI = local_variable()
if not is_local_variable(k):
local_variable(k)
if not is_local_variable(v):
local_variable(v)
# a = [51,27,13,56] dict(enumerate(a))
out = "'" + align(_G.TEOL.join([
""
,""+_o+" = "+o+""
,""+_oI+" = (enumerate("+_o+") if isinstance("+_o+",(list,tuple)) else "+_o+".items()) if "+_o+" else None"
,"if ("+_oI+"):"
," for "+k+","+v+" in "+_oI+":"
,""
]))
_G.level += 2
else:
v = kv[0].strip()
_oV = local_variable()
if not is_local_variable(v):
local_variable(v)
out = "'" + align(_G.TEOL.join([
""
,""+_o+" = "+o+""
,""+_oV+" = ("+_o+" if isinstance("+_o+",(list,tuple)) else "+_o+".values()) if "+_o+" else None"
,"if ("+_oV+"):"
," for "+v+" in "+_oV+":"
,""
]))
_G.level += 2
_G.loops += 1
_G.loopifs += 1
elif 8==m: # elsefor
_G.loopifs -= 1
_G.level += -2
out = "'" + align(_G.TEOL.join([
""
,"else:"
,""
]))
_G.level += 1
elif 9==m: # endfor
if _G.loopifs == _G.loops:
_G.loops -= 1
_G.loopifs -= 1
_G.level += -2
out = "'" + align(_G.TEOL.join([
"",""
]))
else:
_G.loops -= 1
_G.level += -1
out = "'" + align(_G.TEOL.join([
"",""
]))
elif 10==m: # extends
id = args.strip()
if _G.strings and (id in _G.strings): id = _G.strings[id]
ch = id[0]
if ('"' == ch or "'" == ch) and (ch == id[-1]): id = id[1:-1] # quoted id
_G.extends = id
out = "'" + _G.TEOL
elif 11==m: # block
out = t_block(args)
elif 12==m: # endblock
out = t_endblock()
elif 13==m: # import_tpl
out = t_include(args)
elif 14==m: # super
args = re.sub(re_controls, parse_constructs, args)
out = prefix + 'self_.sprblock(' + args + ', data)'
elif 15==m: # getblock
args = re.sub(re_controls, parse_constructs, args)
out = prefix + '__i__.block(' + args + ', data)'
elif 16==m: # iif
args = split_arguments(re.sub(re_controls, parse_constructs, args),',')
out = prefix + "(("+args[1]+") if ("+args[0]+") else ("+args[2]+"))"
elif 17==m: #empty
args = re.sub(re_controls, parse_constructs, args)
varname = args
is_local_var = is_local_variable(varname)
out = prefix + ('(("' + ('' if 2 == is_local_var else '_loc_') + varname + '__RAW__" not in locals()) or ('+varname+' is None) or Contemplate.empty('+varname+'))' if is_local_var else '(("' + varname + '__RAW__" not in data) or ('+varname+' is None) or Contemplate.empty('+varname+'))')
elif 18==m or 19==m: #'continue','break'
out = "'" + _G.TEOL + align('continue' if 18==m else 'break') + _G.TEOL
return out + re.sub(re_controls, parse_constructs, rest)
if (ctrl in _G.context.plugins) or (ctrl in _G.glob.plugins):
pl = _G.context.plugins[ctrl] if ctrl in _G.context.plugins else _G.glob.plugins[ctrl]
args = re.sub(re_controls, parse_constructs, args)
out = pl.render([args]+split_arguments(args,',')) if isinstance(pl,Contemplate.InlineTemplate) else 'Contemplate.plg_("' + ctrl + '"' + ('' if not len(args) else ','+args) + ')'
return prefix + out + re.sub(re_controls, parse_constructs, rest)
if ctrl in _G.aliases: ctrl = _G.aliases[ctrl]
args = re.sub(re_controls, parse_constructs, args)
# aliases and builtin functions
if 's'==ctrl:
out = 'str(' + args + ')'
elif 'n'==ctrl:
out = 'int(' + args + ')'
elif 'f'==ctrl:
out = 'float(' + args + ')'
elif 'q'==ctrl:
out = '"\'"+str(' + args + ')+"\'"'
elif 'qq'==ctrl:
out = '\'"\'+str(' + args + ')+\'"\''
elif 'concat'==ctrl:
out = 'str('+')+str('.join(split_arguments(args, ','))+')'
elif 'is_array'==ctrl:
args = split_arguments(args, ',')
if len(args) > 1:
out = "(isinstance("+args[0]+",list) if ("+args[1]+") else isinstance("+args[0]+",(list,tuple,dict)))"
else:
out = "isinstance("+args[0]+",(list,tuple,dict))"
elif 'in_array'==ctrl:
args = split_arguments(args, ',')
out = "(("+args[0]+") in ("+args[1]+"))"
else:
if 'tpl'==ctrl:
args2 = split_arguments(args, ',')
usedTpl = args2[0]
if usedTpl.startswith('#STR_') and usedTpl in _G.strings:
# only literal string support here
usedTpl = _G.strings[usedTpl][1:-1] # without quotes
if usedTpl not in _G.uses:
_G.uses.append(usedTpl)
if hasattr(Contemplate, ctrl) and callable(getattr(Contemplate, ctrl)):
out = 'Contemplate.' + ctrl + '(' + args + ')'
else:
out = ctrl + ('('+args+')' if startParen else '')
return prefix + out + re.sub(re_controls, parse_constructs, rest)
def parse_blocks(s):
global _G
blocks = []
bl = len(_G.allblocks)
EOL = _G.TEOL
while bl:
bl -= 1
delims = _G.allblocks[bl]
block = delims[0]
pos1 = delims[1]
pos2 = delims[2]
off = delims[3]
containerblock = delims[4]
echoed = delims[5]
tag = "#BLOCK_" + block + "#"
rep = "__i__.block('" + block + "', data)" if echoed else "''"
tl = len(tag)
rl = len(rep)
if -1 < containerblock:
# adjust the ending position of the container block (if nested)
# to compensate for the replacements in this (nested) block
_G.allblocks[containerblock][3] += rl - (pos2-pos1+1)
# adjust the ending position of this block (if nested)
# to compensate for the replacements of any (nested) block(s)
pos2 += off
if 1 == _G.allblockscnt[block]:
# 1st occurance, block definition
blocks.append([block, _G.TT_BLOCK.render({
'BLOCKCODE' : s[pos1+tl:pos2-tl-1] + "'"
})])
s = s[0:pos1] + rep + s[pos2+1:]
if 1 <= _G.allblockscnt[block]: _G.allblockscnt[block] -= 1
#_G.allblocks = None
#_G.allblockscnt = None
#_G.openblocks = None
return [s, blocks]
def parse_variable(s, i, l):
global _G
if _G.ALPHA.match(s[i]):
strings = {}
variables = []
space = 0
hasStrings = False
# main variable
variable = s[i]
i += 1
while i < l and _G.ALPHANUM.match(s[i]):
variable += s[i]
i += 1
variable_raw = variable
# transform into tpl variable
variable_main = "data['" + variable_raw + "']"
variable_rest = ""
_G.id += 1
id = "#VAR_"+str(_G.id)+"#"
_len = len(variable_raw)
_G.variables[_G.currentblock][id] = variable_raw
# extra space
space = 0
while i < l and _G.SPACE.match(s[i]):
space += 1
i += 1
# optional properties
while i < l and ('.' == s[i] or '[' == s[i] or '->' == s[i:i+2]):
delim = s[i]
i += 1
# -> (php) object notation property
if '-' == delim:
delim += s[i]
i += 1
# extra space
while i < l and _G.SPACE.match(s[i]):
space += 1
i += 1
# alpha-numeric dot property
if '.' == delim:
# property
property = ''
while i < l and _G.ALPHANUM.match(s[i]):
property += s[i]
i += 1
lp = len(property)
if lp:
# transform into tpl variable bracketed property
variable_rest += "['" + property + "']"
_len += space + 1 + lp
space = 0
else:
break
# alpha-numeric (php) object notation property
elif '->' == delim:
# property
property = ''
while i < l and _G.ALPHANUM.match(s[i]):
property += s[i]
i += 1
lp = len(property)
if lp:
# transform into tpl variable object property
variable_rest += "." + property + ""
_len += space + 2 + lp
space = 0
else:
break
# bracketed property
elif '[' == delim:
bracket = ''
while i < l:
ch = s[i]
# spaces
if _G.SPACE.match(ch):
space += 1
i += 1
# literal string property
elif '"' == ch or "'" == ch:
#property = parse_string( s, ch, i+1, l )
q = ch
str_ = q
escaped = False
si = i+1
while si < l:
ch = s[si]
si += 1
str_ += ch
if ( q == ch and not escaped ): break
escaped = (not escaped and '\\' == ch)
property = str_
_G.id += 1
strid = "#STR_"+str(_G.id)+"#"
strings[strid] = property
lp = len(property)
i += lp
_len += space + lp
space = 0
hasStrings = True
bracket += strid
# numeric array property
elif _G.NUM.match(ch):
property = s[i]
i += 1
while i < l and _G.NUM.match(s[i]):
property += s[i]
i += 1
lp = len(property)
_len += space + lp
space = 0
bracket += property
# sub-variable as property
elif '$' == ch:
sub = s[i+1:]
subvariables = parse_variable(sub, 0, len(sub))
if subvariables:
# transform into tpl variable property
property = subvariables[-1]
lp = property[4]
i += lp + 1
_len += space + 1 + lp
space = 0
variables = variables + subvariables
hasStrings = hasStrings or property[5]
bracket += property[0]
else:
bracket += ch
_len += 1
i += 1
# identifiers
elif _G.ALPHA.match(ch):
_len += space + 1
i += 1
if space > 0:
bracket += " "
space = 0
is_prop_access = (2<i and '-'==s[i-3] and '>'==s[i-2])
tok = ch
while i < l:
ch = s[i]
if _G.ALPHANUM.match(ch):
i += 1
_len += 1
tok += ch
else: break
if 'null' == tok: tok = 'None'
elif 'false' == tok: tok = 'False'
elif 'true' == tok: tok = 'True'
elif 'as' != tok and 'in' != tok and not is_prop_access: tok = '#ID_'+tok+'#'
bracket += tok
# close bracket
elif ']' == ch:
variable_rest += delim + re.sub(_G.re_controls, parse_constructs, bracket) + ch
_len += space + 2
space = 0
i += 1
break
# rest
else:
bracket += ch
_len += 1
i += 1
# extra space
while i < l and _G.SPACE.match(s[i]):
space += 1
i += 1
variables.append([id, variable_raw, variable_main, variable_rest, _len, hasStrings, strings])
return variables
return None
str_re = re.compile(r'#STR_\d+#', re.M|re.S)
def parse(tpl, leftTplSep, rightTplSep, withblocks = True):
global _G
global str_re
re_controls = _G.re_controls
ALPHA = _G.ALPHA
ALPHANUM = _G.ALPHANUM
compatibility_mode = False
non_compatibility_mode = True
t1 = leftTplSep
l1 = len(t1)
t2 = rightTplSep
l2 = len(t2)
parsed = ''
while tpl and len(tpl):
p1 = tpl.find(t1)
if -1 == p1:
s = tpl
if _G.escape: s = s.replace("\\", "\\\\") # escape escapes
s = s.replace("'", "\\'") # escape single quotes accurately (used by parse function)
s = s.replace("\n", _G.preserveLines) # preserve lines
#s = re.sub(_G.NL, _G.preserveLines, s) # preserve lines
parsed += s
break
p2 = tpl.find(t2, p1+l1)
if -1 == p2: p2 = len(tpl)
if p1 > 0:
s = tpl[0:p1]
if _G.escape: s = s.replace("\\", "\\\\") # escape escapes
s = s.replace("'", "\\'") # escape single quotes accurately (used by parse function)
s = s.replace("\n", _G.preserveLines) # preserve lines
#s = re.sub(_G.NL, _G.preserveLines, s) # preserve lines
parsed += s
# php literal code block
isphp = 'php:' == tpl[p1+l1:p1+l1+4]
# js literal code block
isjs = 'js:' == tpl[p1+l1:p1+l1+3]
# py literal code block
ispy = 'py:' == tpl[p1+l1:p1+l1+3]
if isphp or isjs or ispy:
# include if in same language else ignore
if ispy:
if '=' == tpl[p1+l1+3:p1+l1+4]:
parsed += "'" + align("\n# py code start") + align("\n__p__ += str(" + tpl[p1+l1+4:p2].strip() + ")") + align("\n# py code end\n__p__ += '")
else:
indent = 0
l3 = 0
indenttype = 'none'
m = _G.INDENT.match(tpl[p1+l1+3:])
if m:
indenttype = m.group(1)
indent = int(m.group(2))
l3 = len(m.group(0))
code = remove_blank_lines(tpl[p1+l1+3+l3:p2])
if 'predent' == indenttype:
_G.level = max(0, _G.level + indent)
parsed += "' " + align("\n# py code start")
if len(code.strip()):
parsed += "\n" + align(code)
if 'postdent' == indenttype:
_G.level = max(0, _G.level + indent)
parsed += align("\n# py code end\n__p__ += '")
tpl = tpl[p2+l2:]
continue
# template TAG
s = tpl[p1+l1:p2]
tpl = tpl[p2+l2:]
# parse each template tag section accurately
# refined parsing
count = len(s)
index = 0
ch = ''
out = ''
variables = []
strings = {}
hasVariables = False
hasStrings = False
hasBlock = False
space = 0
while index < count:
ch = s[index]
index += 1
# variable
if '$' == ch:
if space > 0:
out += " "
space = 0
tok = parse_variable(s, index, count)
if tok:
for tokv in tok:
id = tokv[0]
#_G.variables[_G.currentblock][id] = tokv[1]
if tokv[6]: strings.update(tokv[6])
out += id
index += tokv[4]
variables = variables + tok
hasVariables = True
hasStrings = hasStrings or tokv[6]
else:
out += '$'
# literal string
elif '"' == ch or "'" == ch:
if space > 0:
out += " "
space = 0
#tok = parse_string(s, ch, index, count)
q = ch
str_ = q
escaped = False
si = index
while si < count:
ch = s[si]
si += 1
str_ += ch
if q == ch and not escaped: break
escaped = (not escaped and '\\' == ch)
tok = str_
_G.id += 1
id = "#STR_"+str(_G.id)+"#"
strings[id] = tok
out += id
index += len(tok)-1
hasStrings = True
# spaces
elif "\n" == ch or "\r" == ch or "\t" == ch or "\v" == ch or "\0" == ch:
space += 1
# directive or identifier or atom in compatibility mode
elif '%' == ch:
if space > 0:
out += " "
space = 0
q = ch
if non_compatibility_mode or index >= count:
out += q
continue
ch = s[index]
if ALPHA.match(ch):
index += 1
tok = ch
while index < count:
ch = s[index]
if ALPHANUM.match(ch):
index += 1
tok += ch
else: break
tok = '#ID_'+tok+'#'
out += tok
else:
out += q
# directive or identifier or atom
elif non_compatibility_mode and ALPHA.match(ch):
if space > 0:
out += " "
space = 0
is_prop_access = (2<index and '-'==s[index-3] and '>'==s[index-2])
tok = ch
while index < count:
ch = s[index]
if ALPHANUM.match(ch):
index += 1
tok += ch
else: break
if 'null' == tok: tok = 'None'
elif 'false' == tok: tok = 'False'
elif 'true' == tok: tok = 'True'
elif 'as' != tok and 'in' != tok and not is_prop_access: tok = '#ID_'+tok+'#'
out += tok
# rest, bypass
else:
if space > 0:
out += " "
space = 0
out += ch
# fix literal data notation python-style
if compatibility_mode: out = out.replace('true', 'True').replace('false', 'False').replace('null', 'None')
# fix literal data notation, not needed here
#out = str_replace(array('{', '}', '[', ']', ':'), array('array(', ')','array(', ')', '=>'), out);
# fix pending "->" arrow notation for variable object
out = out.replace('->', '.')
out = re.sub(_G.T_NOT, r'\1 not \3', re.sub(_G.T_OR, r'\1 or \3', re.sub(_G.T_AND, r'\1 and \3', out)))
tag = "\t" + out + "\v"
_G.startblock = None
_G.endblock = None
_G.blockptr = -1
_G.strings = strings
# replace constructs, functions, etc..
tag = re.sub(re_controls, parse_constructs, tag)
# check for blocks
if _G.startblock:
_G.startblock = "#BLOCK_"+_G.startblock+"#"
hasBlock = True
elif _G.endblock:
_G.endblock = "#/BLOCK_"+_G.endblock+"#"
hasBlock = True
notFoundBlock = hasBlock
# replacements
if "\t" == tag[0] and "\v" == tag[-1]:
tag = "' + str("+tag[1:-1].strip()+") + '"
if hasVariables:
# replace variables
for v in reversed(variables):
id = v[0]
varname = v[1]
tag = tag.replace(id+'__RAW__', varname)
if varname in _G.locals[_G.currentblock]: # local (loop) variable
tag = tag.replace(id, ('' if 2 == _G.locals[_G.currentblock][varname] else '_loc_')+varname+v[3])
else: # default (data) variable
tag = tag.replace(id, v[2]+v[3])
if hasStrings:
# replace strings (accurately)
tagTpl = InlineTemplate.multisplit_re(tag, str_re)
tag = ''
for v in tagTpl:
if v[0]:
# and replace blocks (accurately)
if notFoundBlock:
if _G.startblock:
blockTag = v[1].find(_G.startblock)
if -1 != blockTag:
_G.allblocks[_G.blockptr-1][1] = blockTag + len(parsed) + len(tag)
notFoundBlock = False
else: #if _G.endblock:
blockTag = v[1].find(_G.endblock)
if -1 != blockTag:
_G.allblocks[_G.blockptr-1][2] = blockTag + len(parsed) + len(tag) + len(_G.endblock)
notFoundBlock = False
tag += v[1]
else:
tag += strings[v[1]]
elif hasBlock:
# replace blocks (accurately)
if _G.startblock:
_G.allblocks[_G.blockptr-1][1] = len(parsed) + tag.find(_G.startblock)
else: #if _G.endblock:
_G.allblocks[_G.blockptr-1][2] = len(parsed) + tag.find(_G.endblock) + len(_G.endblock)
# replace tpl separators
if "\v" == tag[-1]:
tag = tag[0:-1] + align(_G.tplEnd)
if "\t" == tag[0]:
tag = _G.tplStart + tag[1:]
if hasBlock:
# update blocks (accurately)
blockTag = len(_G.tplStart)-1
if _G.startblock:
_G.allblocks[_G.blockptr-1][1] += blockTag
else: #if _G.endblock:
_G.allblocks[_G.blockptr-1][2] += blockTag
parsed += tag
return (parse_blocks(parsed) if len(_G.allblocks)>0 else [parsed, []]) if False != withblocks else parsed
def get_cached_template_name(id, ctx, cacheDir):
global _G
if -1 != id.find('/') or -1 != id.find('\\'):
filename = os.path.basename(id)
path = os.path.dirname(id.rstrip(os.pathsep).rstrip('/\\')).strip('/\\')
if len(path): path += '/'
else:
filename = id
path = ''
return os.path.join(cacheDir, path + re.sub(_G.UNDERL, '_', filename) + '_tpl__' + re.sub(_G.UNDERL, '_', ctx) + '.py')
def get_cached_template_class(id, ctx):
global _G
if -1 != id.find('/') or -1 != id.find('\\'):
filename = os.path.basename(id)
else:
filename = id
return 'Contemplate_' + re.sub(_G.UNDERL, '_', filename) + '__' + re.sub(_G.UNDERL, '_', ctx)
def get_template_contents(id, contx):
global _G
if not Contemplate.hasTpl(id, contx.id):
found = Contemplate.findTpl(id, contx.id)
if not found: return ''
tpldef = {}
tpldef[id] = found
Contemplate.add(tpldef, contx.id)
if id in contx.templates: template = contx.templates[id]
elif id in _G.glob.templates: template = _G.glob.templates[id]
else: return ''
if template[1]: return template[0] # inline tpl
elif os.path.exists(template[0]): return read_file(template[0], contx.encoding)
return ''
def create_template_render_function(id, contx, seps = None):
global _G
tpl = get_template_contents(id, contx)
tpl = get_separators(tpl, seps)
reset_state()
blocks = parse(tpl, _G.leftTplSep, _G.rightTplSep)
clear_state()
renderf = blocks[0]
blocks = blocks[1]
EOL = _G.TEOL
func = _G.TT_FUNC.render({
'FCODE' : "" if _G.extends else "__p__ += '" + renderf + "'"
})
_G.funcId += 1
funcName = '_contemplateFn' + str(_G.funcId)
fn = create_function(funcName, 'data,self_,__i__', align(func, 1), {'Contemplate': Contemplate})
blockfns = {}
for b in blocks:
funcName = '_contemplateBlockFn_' + b[0] + '_' + str(_G.funcId)
blockfns[b] = create_function(funcName, 'data,self_,__i__', align(b[1], 1), {'Contemplate': Contemplate})
return [fn, blockfns]
def create_cached_template(id, contx, filename, classname, seps = None):
global _G
tpl = get_template_contents(id, contx)
tpl = get_separators(tpl, seps)
reset_state()
blocks = parse(tpl, _G.leftTplSep, _G.rightTplSep)
clear_state()
renderf = blocks[0]
blocks = blocks[1]
EOL = _G.TEOL
# tpl-defined blocks
sblocks = ''
for b in blocks:
sblocks += EOL + _G.TT_BlockCode.render({
'BLOCKNAME' : b[0]
,'BLOCKMETHODNAME' : "_blockfn_"+b[0]
,'BLOCKMETHODCODE' : align(b[1], 1)
})
renderCode = _G.TT_RCODE.render({
'RCODE' : "__p__ = ''" if _G.extends else "__p__ += '" + renderf + "'"
})
extendCode = "self_.extend('"+_G.extends+"')" if _G.extends else ''
extendCode += EOL + "self_._usesTpl = ["+("'"+"','".join(_G.uses)+"'" if len(_G.uses) else '')+"]"
prefixCode = contx.prefix if contx.prefix else ''
# generate tpl class
classCode = _G.TT_ClassCode.render({
'PREFIXCODE' : prefixCode
,'TPLID' : id
,'CLASSNAME' : classname
,'EXTENDCODE' : align(extendCode, 3)
,'BLOCKS' : align(sblocks, 2)
,'RENDERCODE' : align(renderCode, 4)
})
return write_file(filename, classCode, contx.encoding)
def get_cached_template(id, contx, options = dict()):
global _G
# inline templates saved only in-memory
if id in contx.templates: template = contx.templates[id]
elif id in _G.glob.templates: template = _G.glob.templates[id]
else: template = None
if not options: options = {'context':contx.id,'autoUpdate':False}
parsed = options['parsed'] if 'parsed' in options else None
if 'parsed' in options: del options['parsed']
if template:
# inline templates saved only in-memory
if template[1]:
# dynamic in-memory caching during page-request
tpl = Contemplate.Template()
tpl.setId(id).ctx(contx)
if parsed:
_G.funcId += 1
tpl.setRenderFunction(create_function('_contemplateFn' + str(_G.funcId), 'data,self_,__i__', align(parsed, 1), {'Contemplate': Contemplate}))
else:
fns = create_template_render_function(id, contx, options['separators'])
tpl.setRenderFunction(fns[0]).setBlocks(fns[1]).usesTpl(_G.uses)
sprTpl = _G.extends
if sprTpl: tpl.extend(Contemplate.tpl(sprTpl, None, options))
return tpl
CM = contx.cacheMode
if True != options['autoUpdate'] and CM == Contemplate.CACHE_TO_DISK_NOUPDATE:
cachedTplFile = get_cached_template_name(id, contx.id, contx.cacheDir)
cachedTplClass = get_cached_template_class(id, contx.id)
exists = os.path.isfile(cachedTplFile)
if not exists:
# if not exist, create it
if -1 != id.find('/') or -1 != id.find('\\'):
fname = os.path.basename(id)
fpath = os.path.dirname(id.rstrip(os.pathsep).rstrip('/\\')).strip('/\\')
else:
fname = id
fpath = ''
if len(fpath): create_path(fpath, contx.cacheDir)
create_cached_template(id, contx, cachedTplFile, cachedTplClass, options['separators'])
if os.path.isfile(cachedTplFile):
tpl = import_tpl(cachedTplFile, cachedTplClass, contx.cacheDir)()
tpl.setId(id).ctx(contx)
return tpl
return None
elif True == options['autoUpdate'] or CM == Contemplate.CACHE_TO_DISK_AUTOUPDATE:
cachedTplFile = get_cached_template_name(id, contx.id, contx.cacheDir)
cachedTplClass = get_cached_template_class(id, contx.id)
exists = os.path.isfile(cachedTplFile)
if not exists or (os.path.getmtime( cachedTplFile ) <= os.path.getmtime(template[0])):
# if tpl not exist or is out-of-sync (re-)create it
if not exists:
if -1 != id.find('/') or -1 != id.find('\\'):
fname = os.path.basename(id)
fpath = os.path.dirname(id.rstrip(os.pathsep).rstrip('/\\')).strip('/\\')
else:
fname = id
fpath = ''
if len(fpath): create_path(fpath, contx.cacheDir)
create_cached_template(id, contx, cachedTplFile, cachedTplClass, options['separators'])
if os.path.isfile(cachedTplFile):
tpl = import_tpl(cachedTplFile, cachedTplClass, contx.cacheDir)()
tpl.setId(id).ctx(contx)
return tpl
return None
else:
# dynamic in-memory caching during page-request
fns = create_template_render_function(id, contx, options['separators'])
tpl = Contemplate.Template(id)
tpl.ctx(contx).setRenderFunction(fns[0]).setBlocks(fns[1]).usesTpl(_G.uses)
sprTpl = _G.extends
if sprTpl: tpl.extend(Contemplate.tpl(sprTpl, None, options))
return tpl
return None
def split_and_filter(r, s, regex = True):
return list(filter(lambda x: 0<len(x), map(lambda x: x.strip(), re.split(r, s) if regex else s.split(r))))
def create_path(path, root = '', mode = 0o755):
global _G
path = path.strip()
if not len(path): return
parts = split_and_filter(_G.DS_RE, path)
current = root.rstrip('/\\')
for part in parts:
current += '/' + part
if not os.path.exists(current):
os.mkdir(current, mode)
class ContemplateException(Exception):
pass
class InlineTemplate:
def multisplit(tpl, reps = dict(), as_array = False):
#as_array = isinstance(reps, (list,tuple))
a = [[1, tpl]]
items = enumerate(reps) if as_array else reps.items()
for r,s in items:
c = []
sr = s if as_array else r
s = [0, s]
for ai in a:
if 1 == ai[0]:
b = ai[1].split(sr)
bl = len(b)
c.append([1, b[0]])
if bl > 1:
for j in range(bl-1):
c.append(s)
c.append([1, b[j+1]])
else:
c.append(ai)
a = c
return a
def multisplit_re(tpl, rex):
a = [ ]
i = 0
m = rex.search(tpl, i)
while m:
a.append([1, tpl[i:m.start()]])
try:
mg = m.group(1)
except:
mg = m.group(0)
is_numeric = False
try:
mn = int(mg,10)
is_numeric = False if math.isnan(mn) else True
except ValueError:
is_numeric = False
a.append([0, mn if is_numeric else mg])
i = m.end()
m = rex.search(tpl, i)
a.append([1, tpl[i:]])
return a
def compile(tpl):
global _G
l = len(tpl)
out = 'return ('
for s in tpl:
notIsSub = s[0]
s = s[ 1 ]
if notIsSub: out += "'" + re.sub(_G.NEWLINE, "' + \"\\\\n\" + '", re.sub(_G.SQUOTE, "\\'", s)) + "'"
else: out += " + str(args['" + s + "']) + "
out += ')'
_G.funcId += 1
funcName = '_contemplateInlineFn' + str(_G.funcId)
return create_function(funcName, 'args', ' ' + out, {})
def __init__(self, tpl = '', replacements = None, compiled = False):
if not replacements: replacements = {}
self.id = None
self._renderer = None
self._parsed = False # lazy init, only if needed, as and when needed
self._args = [tpl, replacements, compiled]
self.tpl = None
def __del__(self):
self.dispose()
def dispose(self):
self.id = None
self.tpl = None
self._renderer = None
self._parsed = None
self._args = None
return self
def render(self, args = None):
if not args: args = []
if not self._parsed: # lazy init, only if needed, as and when needed
tpl = self._args[0]
replacements = self._args[1]
compiled = self._args[2]
self.tpl = InlineTemplate.multisplit_re(tpl, replacements) if isinstance(replacements, T_REGEXP) else InlineTemplate.multisplit(tpl, replacements)
if compiled is True: self._renderer = InlineTemplate.compile(self.tpl)
self._args = None
self._parsed = True
if callable(self._renderer): return self._renderer(args)
tpl = self.tpl
out = ''
for s in tpl:
notIsSub = s[0]
s = s[1]
out += str(s) if notIsSub else str(args[s])
return out
class Template:
def __init__(self, id = None):
self._renderer = None
self._blocks = None
self._extends = None
self._usesTpl = None
self._ctx = None
self._autonomus = False
self.id = None
if id is not None: self.id = id
def __del__(self):
self.dispose()
def dispose(self):
self._renderer = None
self._blocks = None
self._extends = None
self._usesTpl = None
self._ctx = None
self._autonomus = None
self.id = None
return self
def setId(self, id = None):
if id is not None: self.id = id
return self
def ctx(self, ctx):
self._ctx = ctx
return self
def autonomus(self, enable = True):
self._autonomus = bool(enable)
return self
def extend(self, tpl):
self._extends = Contemplate.tpl(tpl) if tpl and isinstance(tpl, str) else (tpl if isinstance(tpl, Template) else None)
return self
def usesTpl(self, usesTpls):
self._usesTpl = [usesTpls] if isinstance(usesTpls,str) else usesTpls
return self
def setBlocks(self, blocks):
if not self._blocks: self._blocks = {}
self._blocks = Contemplate.merge(self._blocks, blocks)
return self
def setRenderFunction(self, renderFunc = None):
self._renderer = renderFunc if callable(renderFunc) else None
return self
def sprblock(self, block, data):
#if not __i__: __i__ = self
if self._extends:
return self._extends.block(block, data, self._extends)
return ''
def block(self, block, data, __i__ = None):
__ctx = False
r = ''
if not __i__:
__i__ = self
if not self._autonomus: __ctx = Contemplate._set_ctx(self._ctx)
if (self._blocks) and (block in self._blocks) and callable(self._blocks[block]):
blockfunc = self._blocks[block]
r = blockfunc(data, self, __i__)
elif self._extends:
r = self._extends.block(block, data, __i__)
if __ctx: Contemplate._set_ctx(__ctx)
return r
def render(self, data, __i__ = None):
__ctx = False
__p__ = ''
if not __i__:
__i__ = self
if not self._autonomus: __ctx = Contemplate._set_ctx(self._ctx)
if self._extends:
__p__ = self._extends.render(data, __i__)
elif callable(self._renderer):
# dynamic function
__p__ = self._renderer(data, self, __i__)
if __ctx: Contemplate._set_ctx(__ctx)
return __p__
# aliases
def renderBlock(self, block, data, __i__ = None):
return self.block(self, block, data, __i__)
def renderSuperBlock(self, block, data):
return self.sprblock(self, block, data)
class Ctx:
def __init__(self, id):
self.id = id
self.cacheDir = './'
self.cacheMode = 0
self.cache = {}
self.templateDirs = []
self.templateFinder = None
self.templates = {}
self.partials = {}
self.plugins = {}
self.prefix = ''
self.encoding = 'utf-8'
def __del__(self):
self.dispose()
def dispose(self):
self.id = None
self.cacheDir = None
self.cacheMode = None
self.templateDirs = None
self.templateFinder = None
self.templates = None
self.partials = None
self.plugins = None
self.prefix = None
self.encoding = None
if self.cache:
for tpl in self.cache: self.cache[tpl].dispose()
self.cache = None
#
# The Contemplate Engine Main Python Class
#
class Contemplate:
"""
Contemplate Template Engine for Python,
https://github.com/foo123/Contemplate
"""
# constants (not real constants in Python)
VERSION = "1.6.0"
CACHE_TO_DISK_NONE = 0
CACHE_TO_DISK_AUTOUPDATE = 2
CACHE_TO_DISK_NOUPDATE = 4
Exception = ContemplateException
InlineTemplate = InlineTemplate
Template = Template
Ctx = Ctx
#
#
def init():
global _G
if _G.isInited: return
# a default global context
_G.glob = Ctx('global')
_G.ctx = {
'global' : _G.glob
}
_G.context = _G.glob
# pre-compute the needed regular expressions
_G.preserveLines = _G.preserveLinesDefault
_G.tplStart = "'" + _G.TEOL
_G.tplEnd = _G.TEOL + "__p__ += '"
# make compilation templates
_G.TT_ClassCode = InlineTemplate(_G.TEOL.join([
"# -*- coding: UTF-8 -*-"
,"#PREFIXCODE#"
,"# Contemplate cached template '#TPLID#'"
,"def __getTplClass__(Contemplate):"
," # extends the main Contemplate.Template class"
," class #CLASSNAME#(Contemplate.Template):"
," 'Contemplate cached template #TPLID#'"
," # constructor"
," def __init__(self, id = None):"
," self_ = self"
," super(#CLASSNAME#, self).__init__(id)"
," # extend tpl assign code starts here"
,"#EXTENDCODE#"
," # extend tpl assign code ends here"
," # tpl-defined blocks render code starts here"
,"#BLOCKS#"
," # tpl-defined blocks render code ends here"
," # render a tpl block method"
," def block(self, block, data, __i__ = None):"
," self_ = self"
," __ctx = False"
," r = ''"
," if not __i__:"
," __i__ = self_"
," if not self_._autonomus: __ctx = Contemplate._set_ctx(self_._ctx)"
," method = '_blockfn_' + block"
," if (hasattr(self_, method) and callable(getattr(self_, method))):"
," r = getattr(self_, method)(data, self_, __i__)"
," elif self_._extends:"
," r = self_._extends.block(block, data, __i__)"
," if __ctx: Contemplate._set_ctx(__ctx)"
," return r"
," # render method"
," def render(self, data, __i__ = None):"
," self_ = self"
," __ctx = False"
," __p__ = ''"
," if not __i__:"
," __i__ = self_"
," if not self._autonomus: __ctx = Contemplate._set_ctx(self_._ctx)"
," if self_._extends:"
," __p__ = self_._extends.render(data, __i__)"
,""
," else:"
," # tpl main render code starts here"
,"#RENDERCODE#"
," # tpl main render code ends here"
,""
," if __ctx: Contemplate._set_ctx(__ctx)"
," return __p__"
," return #CLASSNAME#"
,"# allow to 'import *' from this file as a module"
,"__all__ = ['__getTplClass__']"
,""
]), {
"#PREFIXCODE#" : "PREFIXCODE"
,"#CLASSNAME#" : "CLASSNAME"
,"#TPLID#" : "TPLID"
,"#BLOCKS#" : "BLOCKS"
,"#EXTENDCODE#" : "EXTENDCODE"
,"#RENDERCODE#" : "RENDERCODE"
}, False)
_G.TT_BlockCode = InlineTemplate(_G.TEOL.join([
""
,"# tpl block render method for block '#BLOCKNAME#'"
,"def #BLOCKMETHODNAME#(self, data, self_, __i__):"
,"#BLOCKMETHODCODE#"
,""
]), {
"#BLOCKNAME#" : "BLOCKNAME"
,"#BLOCKMETHODNAME#" : "BLOCKMETHODNAME"
,"#BLOCKMETHODCODE#" : "BLOCKMETHODCODE"
}, False)
_G.TT_BLOCK = InlineTemplate(_G.TEOL.join([
""
,"__p__ = ''"
,"#BLOCKCODE#"
,"return __p__"
,""
]), {
"#BLOCKCODE#" : "BLOCKCODE"
}, False)
_G.TT_FUNC = InlineTemplate(_G.TEOL.join([
""
,"__p__ = ''"
,"#FCODE#"
,"return __p__"
,""
]), {
"#FCODE#" : "FCODE"
}, False)
_G.TT_RCODE = InlineTemplate(_G.TEOL.join([
""
,"#RCODE#"
,""
]), {
"#RCODE#" : "RCODE"
}, False)
clear_state()
_G.isInited = True
def _set_ctx(ctx):
global _G
contx = _G.context
#if isinstance(ctx, Ctx): _G.context = ctx
#elif ctx and (ctx in _G.ctx): _G.context = _G.ctx[ctx]
#else: _G.context = _G.glob
_G.context = ctx if ctx else _G.glob
return contx
#
# Main API methods
#
def createCtx(ctx):
global _G
if ctx and ('global' != ctx) and (ctx not in _G.ctx): _G.ctx[ctx] = Ctx(ctx)
def disposeCtx(ctx):
global _G
if ctx and ('global' != ctx) and (ctx in _G.ctx):
_G.ctx[ctx].dispose()
del _G.ctx[ctx]
def setTemplateSeparators(seps = None):
global _G
if seps:
if 'left' in seps: _G.leftTplSep = str(seps['left'])
if 'right' in seps: _G.rightTplSep = str(seps['right'])
def setPreserveLines(enable = True):
global _G
_G.preserveLines = _G.preserveLinesDefault if enable else ''
def hasPlugin(name, ctx = 'global'):
global _G
contx = _G.ctx[ctx] if ctx and (ctx in _G.ctx) else _G.context
return name and ((name in contx.plugins) or (name in _G.glob.plugins))
def addPlugin(name, pluginCode, ctx = 'global'):
global _G
if name and pluginCode:
contx = _G.ctx[ctx] if ctx and (ctx in _G.ctx) else _G.context
contx.plugins[str(name)] = pluginCode
def plg_(plg, *args):
global _G
if plg in _G.context.plugins and callable(_G.context.plugins[plg]):
return _G.context.plugins[plg](*args)
elif plg in _G.glob.plugins and callable(_G.glob.plugins[plg]):
return _G.glob.plugins[plg](*args)
return ''
def setPrefixCode(preCode = None, ctx = 'global'):
global _G
contx = _G.ctx[ctx] if ctx and (ctx in _G.ctx) else _G.context
if preCode: contx.prefix = str(preCode)
def setEncoding(encoding, ctx = 'global'):
global _G
contx = _G.ctx[ctx] if ctx and (ctx in _G.ctx) else _G.context
contx.encoding = encoding
def setCacheDir(dir, ctx = 'global'):
global _G
contx = _G.ctx[ctx] if ctx and (ctx in _G.ctx) else _G.context
_self = Contemplate
_dir = contx.cacheDir = os.path.abspath(dir)
initPyFile = os.path.join(_dir, '__init__.py')
if not os.path.exists(initPyFile):
_initPy_ = """\
# added by Contemplate.py Engine
# dummy Python __init__.py file
# used with Contemplate 'import'
# to import_tpl cached templates as modules, for optimization
"""
write_file(initPyFile, _initPy_, contx.encoding)
#if _dir not in os.sys.path:
# # allow to use 'import' in order to import_tpl cached templates
# os.sys.path.append(_dir)
def setCacheMode(mode, ctx = 'global'):
global _G
contx = _G.ctx[ctx] if ctx and (ctx in _G.ctx) else _G.context
contx.cacheMode = mode
def setTemplateDirs(dirs, ctx = 'global'):
global _G
contx = _G.ctx[ctx] if ctx and (ctx in _G.ctx) else _G.context
contx.templateDirs = [dirs] if isinstance(dirs,str) else dirs
def getTemplateDirs(ctx = 'global'):
global _G
contx = _G.ctx[ctx] if ctx and (ctx in _G.ctx) else _G.context
return contx.templateDirs
def setTemplateFinder(finder, ctx = 'global'):
global _G
contx = _G.ctx[ctx] if ctx and (ctx in _G.ctx) else _G.context
contx.templateFinder = finder if callable(finder) else None
def clearCache(all = False, ctx = 'global'):
global _G
contx = _G.ctx[ctx] if ctx and (ctx in _G.ctx) else _G.context
contx.cache = {}
if all: contx.partials = {}
def hasTpl(tpl, ctx = 'global'):
global _G
contx = _G.ctx[ctx] if ctx and (ctx in _G.ctx) else _G.context
return tpl and ((tpl in contx.templates) or (tpl in _G.glob.templates))
def add(tpls, ctx = 'global'):
global _G
if tpls and isinstance(tpls, dict):
contx = _G.ctx[ctx] if ctx and (ctx in _G.ctx) else _G.context
for tplID in tpls:
if isinstance(tpls[tplID], (list, tuple)):
# unified way to add tpls both as reference and inline
# inline tpl, passed as array
if len(tpls[tplID][0]):
contx.templates[tplID] = [tpls[tplID][0], True]
else:
contx.templates[tplID] = [tpls[tplID], False]
def getTemplateContents(id, ctx = 'global'):
global _G
contx = _G.ctx[ctx] if ctx and (ctx in _G.ctx) else _G.context
return get_template_contents(id, contx)
def findTpl(tpl, ctx = 'global'):
global _G
contx = _G.ctx[ctx] if ctx and (ctx in _G.ctx) else _G.context
if callable(contx.templateFinder):
return contx.templateFinder(tpl)
if len(contx.templateDirs):
filename = tpl.lstrip('/\\')
for dir in contx.templateDirs:
path = dir.rstrip('/\\') + '/' + filename
if os.path.exists(path): return path
return None
if contx != _G.glob:
contx = _G.glob
if callable(contx.templateFinder):
return contx.templateFinder(tpl)
if len(contx.templateDirs):
filename = tpl.lstrip('/\\')
for dir in contx.templateDirs:
path = dir.rstrip('/\\') + '/' + filename
if os.path.exists(path): return path
return None
return None
def parseTpl(tpl, options = dict()):
global _G
# see what context this template may use
contx = None
if isinstance(options, str):
if options in _G.ctx:
contx = _G.ctx[options] # preset context
else:
contx = _G.glob # global context
options = {}
options = merge({
'separators': None
}, {} if not options else options)
if 'context' in options:
if options['context'] in _G.ctx:
contx = _G.ctx[options['context']] # preset context
elif not contx:
contx = _G.glob # global context
del options['context']
if not contx: contx = _G.glob # global context
leftSep = _G.leftTplSep
rightSep = _G.rightTplSep
separators = options['separators'] if options and ('separators' in options) else None
if separators:
leftSep = separators[0]
rightSep = separators[1]
_ctx = _G.context
_G.context = contx
reset_state()
parsed = parse(tpl, leftSep, rightSep)
clear_state()
_G.context = _ctx
return parsed
#
# Main Template functions
#
def tpl(tpl, data = None, options = None):
global _G
if isinstance(tpl, Contemplate.Template):
tmpl = tpl
else:
if options is None: options = {}
# see what context this template may use
contx = None
if isinstance(options, str):
if options in _G.ctx:
contx = _G.ctx[options] # preset context
else:
contx = _G.context # current context
options = {}
options = merge({
'separators': None
,'autoUpdate': False
,'refresh': False
,'escape': False
,'standalone': False
}, {} if not options else options)
if 'context' in options:
if options['context'] in _G.ctx:
contx = _G.ctx[options['context']] # preset context
elif not contx:
contx = _G.context # current context
del options['context']
if not contx: contx = _G.context # current context
_G.escape = False if False == options['escape'] else True
if 'parsed' not in options and not Contemplate.hasTpl(tpl, contx.id):
path = Contemplate.findTpl(tpl, contx.id)
if not path: return '' if isinstance(data, dict) else None
tpldef = {}
tpldef[tpl] = path
Contemplate.add(tpldef, contx.id)
# Figure out if we're getting a template, or if we need to
# load the template - and be sure to cache the result.
if options['refresh'] or ((tpl not in contx.cache) and (tpl not in _G.glob.cache)):
_ctx = _G.context
_G.context = contx
contx.cache[tpl] = get_cached_template(tpl, contx, options)
_G.context = _ctx
tmpl = contx.cache[tpl] if tpl in contx.cache else _G.glob.cache[tpl]
tmpl.autonomus(options['standalone'])
# Provide some basic currying to the user
return str(tmpl.render(data)) if isinstance(data, dict) else tmpl
def inline(tpl, reps = None, compiled = False):
if isinstance(tpl, Contemplate.InlineTemplate): return str(tpl.render(reps))
return Contemplate.InlineTemplate(tpl, reps, compiled)
def concat(*args):
return ''.join(map(str, args))
def join(sep, args, skip_empty = False):
if args is None: return ''
skip_empty = skip_empty is True
if not isinstance(args, (list,tuple)): return '' if skip_empty and not len(str(args)) else str(args)
if sep is None: sep = ''
if not isinstance(sep, str): sep = str(sep)
l = len(args)
out = (Contemplate.join(sep, args[0], skip_empty) if isinstance(args[0], (list,tuple)) else ('' if skip_empty and (args[0] is None or not len(str(args[0]))) else str(args[0]))) if l > 0 else ''
for i in range(1, l):
s = Contemplate.join(sep, args[i], skip_empty) if isinstance(args[i], (list,tuple)) else ('' if skip_empty and (args[i] is None or not len(str(args[i]))) else str(args[i]))
if (not skip_empty) or len(s) > 0: out += sep + s
return out
def keys(o):
return array_keys(o) if isinstance(o, (list,tuple,dict)) else []
def values(o):
return array_values(o) if isinstance(o, (list,tuple,dict)) else []
def items(o):
return enumerate(o) if isinstance(o, (list,tuple)) else (o.items() if isinstance(o, dict) else [])
def count(a):
# http://www.php2python.com/wiki/function.count/
return 0 if a is None else len(a)
def is_array(v, strict = False):
return isinstance(v, list) if strict else isinstance(v, (tuple,list,dict))
def in_array(v, a):
return (v in a)
def is_list(v):
return isinstance(v, list)
def haskey(v, *args):
if not v or not isinstance(v, (list,tuple,dict)): return False
tmp = v
for i in range(len(args)):
if isinstance(tmp, (list,tuple)):
k = int(args[i])
if k < 0 or k >= len(tmp): return False
tmp = tmp[k]
elif isinstance(tmp, dict):
k = str(args[i])
if k not in tmp: return False
tmp = tmp[k]
else:
return False
return True
def time():
return php_time()
def date(format, timestamp = None):
if timestamp is None: timestamp = php_time()
return php_date(format, timestamp)
def lowercase(s):
return str(s).lower()
def uppercase(s):
return str(s).upper()
def striptags(s):
global _G
return re.sub(_G.TAG_RE, '', s)
def e(s, entities = True):
f = ''
if entities:
for c in s:
if '&' == c: f += '&'
elif '<' == c: f += '<'
elif '>' == c: f += '>'
elif '"' == c: f += '"'
elif '\'' == c: f += '''
else: f += c
else:
for c in s:
if '&' == c: f += '&'
elif '<' == c: f += '<'
elif '>' == c: f += '>'
elif '"' == c: f += '"'
elif '\'' == c: f += '''
else: f += c
return f
def json_encode(v):
return json.dumps(v)
def json_decode(v):
return json.loads(v)
def urlencode(s):
return urlencode(s)
def urldecode(s):
return urldecode(s)
def buildquery(data):
return http_build_query(data, '&')
def parsequery(s):
return parse_str(s)
def queryvar(url, add_keys, remove_keys = None):
global _G
if remove_keys is not None and not isinstance(remove_keys, list):
remove_keys = [remove_keys]
if remove_keys and len(remove_keys):
# https://davidwalsh.name/php-remove-variable
keys = remove_keys
for key in keys:
url = re.sub(r'(\?|&)' + re.escape(urlencode(str(key))) + r'(\[[^\[\]]*\])*(=[^&]+)?', '\\1', url)
url = re.sub(_G.AMP_RE, '&', url).replace('?&', '?')
last = url[-1]
if '?' == last or '&' == last:
url = url[0:-1]
if add_keys and len(add_keys):
keys = add_keys
q = '?' if -1 == url.find('?') else '&'
for key in keys:
value = keys[key]
key = urlencode(str(key))
if isinstance(value, (list,tuple,dict)):
if isinstance(value, (list,tuple)):
for v in value:
url += q + key + '[]=' + urlencode(str(v))
q = '&'
else:
for k in value:
url += q + key + '[' + urlencode(k) + ']=' + urlencode(str(value[k]))
q = '&'
else:
url += q + key + '=' + urlencode(str(value))
q = '&'
return url
def get(v, keys, default_value = None):
if not Contemplate.is_array(keys, True): keys = [keys]
o = v
found = 1
for key in keys:
if isinstance(o, (list,tuple)):
key = int(key)
if 0 <= key and key < len(o):
o = o[key]
else:
found = 0
break
elif isinstance(o, dict):
key = str(key)
if key in o:
o = o[key]
else:
found = 0
break
else:
key = str(key)
if hasattr(o, key):
o = getattr(o, key)
else:
keyGetter = 'get' + key[0].upper() + key[1:]
if hasattr(o, keyGetter) and callable(getattr(o, keyGetter)):
o = getattr(o, keyGetter)()
else:
found = 0
break
return o if found else default_value
def uuid(namespace = 'UUID'):
global _G
_G.uuid += 1
return '_'.join([str(namespace), str(_G.uuid), str(php_time())])
def merge(m, *args):
numargs = len(args)
if numargs < 1: return m
merged = m
for arg in args:
# http://www.php2python.com/wiki/function.array-merge/
merged = dict(merged)
merged.update(arg)
return merged
#local_variable = local_variable
#is_local_variable = is_local_variable
# extra for py version
def empty(v):
# exactly like php's function
return (isinstance(v, str) and "0" == v) or not bool(v)
def trim(s, charlist = None):
return s.strip(charlist) if charlist else s.strip()
def ltrim(s, charlist = None):
return s.lstrip(charlist) if charlist else s.lstrip()
def rtrim(s, charlist = None):
return s.rstrip(charlist) if charlist else s.rstrip()
sprintf = sprintf
vsprintf = vsprintf
# init the engine on load
Contemplate.init()
# if used with 'import *'
__all__ = ['Contemplate']
|