diff options
Diffstat (limited to 'cgi/tenjin.py')
-rw-r--r-- | cgi/tenjin.py | 839 |
1 files changed, 507 insertions, 332 deletions
diff --git a/cgi/tenjin.py b/cgi/tenjin.py index db8cdde..ddc12bb 100644 --- a/cgi/tenjin.py +++ b/cgi/tenjin.py @@ -1,26 +1,26 @@ ## -## $Release: 1.1.1 $ -## $Copyright: copyright(c) 2007-2012 kuwata-lab.com all rights reserved. $ -## $License: MIT License $ +# $Release: 1.1.1 $ +# $Copyright: copyright(c) 2007-2012 kuwata-lab.com all rights reserved. $ +# $License: MIT License $ ## -## Permission is hereby granted, free of charge, to any person obtaining -## a copy of this software and associated documentation files (the -## "Software"), to deal in the Software without restriction, including -## without limitation the rights to use, copy, modify, merge, publish, -## distribute, sublicense, and/or sell copies of the Software, and to -## permit persons to whom the Software is furnished to do so, subject to -## the following conditions: +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: ## -## The above copyright notice and this permission notice shall be -## included in all copies or substantial portions of the Software. +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. ## -## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -## NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -## LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -## OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -## WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## """Very fast and light-weight template engine based embedded Python. @@ -29,15 +29,19 @@ http://www.kuwata-lab.com/tenjin/pytenjin-examples.html """ -__version__ = "$Release: 1.1.1 $"[10:-2] -__license__ = "$License: MIT License $"[10:-2] -__all__ = ('Template', 'Engine', ) +__version__ = "$Release: 1.1.1 $"[10:-2] +__license__ = "$License: MIT License $"[10:-2] +__all__ = ('Template', 'Engine', ) -import sys, os, re, time, marshal -from time import time as _time -from os.path import getmtime as _getmtime from os.path import isfile as _isfile +from os.path import getmtime as _getmtime +from time import time as _time +import sys +import os +import re +import time +import marshal random = pickle = unquote = None # lazy import python3 = sys.version_info[0] == 3 python2 = sys.version_info[0] == 2 @@ -46,12 +50,13 @@ logger = None ## -## utilities +# utilities ## def _write_binary_file(filename, content): global random - if random is None: from random import random + if random is None: + from random import random tmpfile = filename + str(random())[1:] f = open(tmpfile, 'w+b') # on windows, 'w+b' is preffered than 'wb' try: @@ -62,9 +67,11 @@ def _write_binary_file(filename, content): try: os.rename(tmpfile, filename) except: - os.remove(filename) # on windows, existing file should be removed before renaming + # on windows, existing file should be removed before renaming + os.remove(filename) os.rename(tmpfile, filename) + def _read_binary_file(filename): f = open(filename, 'rb') try: @@ -72,25 +79,32 @@ def _read_binary_file(filename): finally: f.close() + codecs = None # lazy import + def _read_text_file(filename, encoding=None): global codecs - if not codecs: import codecs + if not codecs: + import codecs f = codecs.open(filename, encoding=(encoding or 'utf-8')) try: return f.read() finally: f.close() + def _read_template_file(filename, encoding=None): - s = _read_binary_file(filename) ## binary(=str) - if encoding: s = s.decode(encoding) ## binary(=str) to unicode + s = _read_binary_file(filename) # binary(=str) + if encoding: + s = s.decode(encoding) # binary(=str) to unicode return s + _basestring = basestring -_unicode = unicode -_bytes = str +_unicode = unicode +_bytes = str + def _ignore_not_found_error(f, default=None): try: @@ -100,6 +114,7 @@ def _ignore_not_found_error(f, default=None): return default raise + def create_module(module_name, dummy_func=None, **kwargs): """ex. mod = create_module('tenjin.util')""" try: @@ -116,12 +131,13 @@ def create_module(module_name, dummy_func=None, **kwargs): exec(dummy_func.func_code, mod.__dict__) return mod + def _raise(exception_class, *args): raise exception_class(*args) ## -## helper method's module +# helper method's module ## def _dummy(): @@ -142,13 +158,16 @@ def _dummy(): """ if encode: if decode: - raise ValueError("can't specify both encode and decode encoding.") + raise ValueError( + "can't specify both encode and decode encoding.") else: def to_str(val, _str=str, _unicode=unicode, _isa=isinstance, _encode=encode): """Convert val into string or return '' if None. Unicode will be encoded into binary(=str).""" - if _isa(val, _str): return val - if val is None: return '' - #if _isa(val, _unicode): return val.encode(_encode) # unicode to binary(=str) + if _isa(val, _str): + return val + if val is None: + return '' + # if _isa(val, _unicode): return val.encode(_encode) # unicode to binary(=str) if _isa(val, _unicode): return val.encode(_encode) # unicode to binary(=str) return _str(val) @@ -156,18 +175,23 @@ def _dummy(): if decode: def to_str(val, _str=str, _unicode=unicode, _isa=isinstance, _decode=decode): """Convert val into string or return '' if None. Binary(=str) will be decoded into unicode.""" - #if _isa(val, _str): return val.decode(_decode) # binary(=str) to unicode + # if _isa(val, _str): return val.decode(_decode) # binary(=str) to unicode if _isa(val, _str): return val.decode(_decode) - if val is None: return '' - if _isa(val, _unicode): return val + if val is None: + return '' + if _isa(val, _unicode): + return val return _unicode(val) else: def to_str(val, _str=str, _unicode=unicode, _isa=isinstance): """Convert val into string or return '' if None. Both binary(=str) and unicode will be retruned as-is.""" - if _isa(val, _str): return val - if val is None: return '' - if _isa(val, _unicode): return val + if _isa(val, _str): + return val + if val is None: + return '' + if _isa(val, _unicode): + return val return _str(val) return to_str @@ -197,21 +221,21 @@ def _dummy(): class CaptureContext(object): def __init__(self, name, store_to_context=True, lvars=None): - self.name = name + self.name = name self.store_to_context = store_to_context self.lvars = lvars or sys._getframe(1).f_locals def __enter__(self): lvars = self.lvars self._buf_orig = lvars['_buf'] - lvars['_buf'] = _buf = [] + lvars['_buf'] = _buf = [] lvars['_extend'] = _buf.extend return self def __exit__(self, *args): lvars = self.lvars _buf = lvars['_buf'] - lvars['_buf'] = self._buf_orig + lvars['_buf'] = self._buf_orig lvars['_extend'] = self._buf_orig.extend lvars[self.name] = self.captured = ''.join(_buf) if self.store_to_context and '_context' in lvars: @@ -236,7 +260,8 @@ def _dummy(): lvars = sys._getframe(_depth).f_locals capture_context = lvars.pop('_capture_context', None) if not capture_context: - raise Exception('stop_capture(): start_capture() is not called before.') + raise Exception( + 'stop_capture(): start_capture() is not called before.') capture_context.store_to_context = store_to_context capture_context.__exit__() return capture_context.captured @@ -270,19 +295,25 @@ def _dummy(): global unquote if unquote is None: from urllib import unquote - dct = { 'lt':'<', 'gt':'>', 'amp':'&', 'quot':'"', '#039':"'", } + dct = {'lt': '<', 'gt': '>', 'amp': '&', 'quot': '"', '#039': "'", } + def unescape(s): - #return s.replace('<', '<').replace('>', '>').replace('"', '"').replace(''', "'").replace('&', '&') - return re.sub(r'&(lt|gt|quot|amp|#039);', lambda m: dct[m.group(1)], s) + # return s.replace('<', '<').replace('>', '>').replace('"', '"').replace(''', "'").replace('&', '&') + return re.sub(r'&(lt|gt|quot|amp|#039);', lambda m: dct[m.group(1)], s) s = to_str(s) - s = re.sub(r'%3C%60%23(.*?)%23%60%3E', lambda m: '#{%s}' % unquote(m.group(1)), s) - s = re.sub(r'%3C%60%24(.*?)%24%60%3E', lambda m: '${%s}' % unquote(m.group(1)), s) - s = re.sub(r'<`#(.*?)#`>', lambda m: '#{%s}' % unescape(m.group(1)), s) - s = re.sub(r'<`\$(.*?)\$`>', lambda m: '${%s}' % unescape(m.group(1)), s) + s = re.sub(r'%3C%60%23(.*?)%23%60%3E', + lambda m: '#{%s}' % unquote(m.group(1)), s) + s = re.sub(r'%3C%60%24(.*?)%24%60%3E', + lambda m: '${%s}' % unquote(m.group(1)), s) + s = re.sub(r'<`#(.*?)#`>', + lambda m: '#{%s}' % unescape(m.group(1)), s) + s = re.sub(r'<`\$(.*?)\$`>', + lambda m: '${%s}' % unescape(m.group(1)), s) s = re.sub(r'<`#(.*?)#`>', r'#{\1}', s) s = re.sub(r'<`\$(.*?)\$`>', r'${\1}', s) return s + helpers = create_module('tenjin.helpers', _dummy, sys=sys, re=re) helpers.__all__ = ['to_str', 'escape', 'echo', 'new_cycle', 'generate_tostrfunc', 'start_capture', 'stop_capture', 'capture_as', 'captured_as', @@ -293,13 +324,14 @@ generate_tostrfunc = helpers.generate_tostrfunc ## -## escaped module +# escaped module ## def _dummy(): global is_escaped, as_escaped, to_escaped global Escaped, EscapedStr, EscapedUnicode global __all__ - __all__ = ('is_escaped', 'as_escaped', 'to_escaped', ) #'Escaped', 'EscapedStr', + # 'Escaped', 'EscapedStr', + __all__ = ('is_escaped', 'as_escaped', 'to_escaped', ) class Escaped(object): """marking class that object is already escaped.""" @@ -319,8 +351,10 @@ def _dummy(): def as_escaped(s): """mark string as escaped, without escaping.""" - if isinstance(s, str): return EscapedStr(s) - if isinstance(s, unicode): return EscapedUnicode(s) + if isinstance(s, str): + return EscapedStr(s) + if isinstance(s, unicode): + return EscapedUnicode(s) raise TypeError("as_escaped(%r): expected str or unicode." % (s, )) def to_escaped(value): @@ -329,23 +363,24 @@ def _dummy(): if hasattr(value, '__html__'): value = value.__html__() if is_escaped(value): - #return value # EscapedUnicode should be convered into EscapedStr + # return value # EscapedUnicode should be convered into EscapedStr return as_escaped(_helpers.to_str(value)) - #if isinstance(value, _basestring): + # if isinstance(value, _basestring): # return as_escaped(_helpers.escape(value)) return as_escaped(_helpers.escape(_helpers.to_str(value))) + escaped = create_module('tenjin.escaped', _dummy, _helpers=helpers) ## -## module for html +# module for html ## def _dummy(): global escape_html, escape_xml, escape, tagattr, tagattrs, _normalize_attrs global checked, selected, disabled, nl2br, text2html, nv, js_link - #_escape_table = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' } + # _escape_table = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' } #_escape_pattern = re.compile(r'[&<>"]') ##_escape_callable = lambda m: _escape_table[m.group(0)] ##_escape_callable = lambda m: _escape_table.__get__(m.group(0)) @@ -353,22 +388,22 @@ def _dummy(): #_escape_callable = lambda m: _escape_get(m.group(0)) #_escape_sub = _escape_pattern.sub - #def escape_html(s): + # def escape_html(s): # return s # 3.02 - #def escape_html(s): + # def escape_html(s): # return _escape_pattern.sub(_escape_callable, s) # 6.31 - #def escape_html(s): + # def escape_html(s): # return _escape_sub(_escape_callable, s) # 6.01 - #def escape_html(s, _p=_escape_pattern, _f=_escape_callable): + # def escape_html(s, _p=_escape_pattern, _f=_escape_callable): # return _p.sub(_f, s) # 6.27 - #def escape_html(s, _sub=_escape_pattern.sub, _callable=_escape_callable): + # def escape_html(s, _sub=_escape_pattern.sub, _callable=_escape_callable): # return _sub(_callable, s) # 6.04 - #def escape_html(s): + # def escape_html(s): # s = s.replace('&', '&') # s = s.replace('<', '<') # s = s.replace('>', '>') @@ -384,9 +419,12 @@ def _dummy(): def tagattr(name, expr, value=None, escape=True): """(experimental) Return ' name="value"' if expr is true value, else '' (empty string). If value is not specified, expr is used as value instead.""" - if not expr and expr != 0: return _escaped.as_escaped('') - if value is None: value = expr - if escape: value = _escaped.to_escaped(value) + if not expr and expr != 0: + return _escaped.as_escaped('') + if value is None: + value = expr + if escape: + value = _escaped.to_escaped(value) return _escaped.as_escaped(' %s="%s"' % (name, value)) def tagattrs(**kwargs): @@ -399,14 +437,19 @@ def _dummy(): """ kwargs = _normalize_attrs(kwargs) esc = _escaped.to_escaped - s = ''.join([ ' %s="%s"' % (k, esc(v)) for k, v in kwargs.iteritems() if v or v == 0 ]) + s = ''.join([' %s="%s"' % (k, esc(v)) + for k, v in kwargs.iteritems() if v or v == 0]) return _escaped.as_escaped(s) def _normalize_attrs(kwargs): - if 'klass' in kwargs: kwargs['class'] = kwargs.pop('klass') - if 'checked' in kwargs: kwargs['checked'] = kwargs.pop('checked') and 'checked' or None - if 'selected' in kwargs: kwargs['selected'] = kwargs.pop('selected') and 'selected' or None - if 'disabled' in kwargs: kwargs['disabled'] = kwargs.pop('disabled') and 'disabled' or None + if 'klass' in kwargs: + kwargs['class'] = kwargs.pop('klass') + if 'checked' in kwargs: + kwargs['checked'] = kwargs.pop('checked') and 'checked' or None + if 'selected' in kwargs: + kwargs['selected'] = kwargs.pop('selected') and 'selected' or None + if 'disabled' in kwargs: + kwargs['disabled'] = kwargs.pop('disabled') and 'disabled' or None return kwargs def checked(expr): @@ -432,8 +475,9 @@ def _dummy(): if not text: return _escaped.as_escaped('') s = _escaped.to_escaped(text) - if use_nbsp: s = s.replace(' ', ' ') - #return nl2br(s) + if use_nbsp: + s = s.replace(' ', ' ') + # return nl2br(s) s = s.replace('\n', '<br />\n') return _escaped.as_escaped(s) @@ -449,19 +493,20 @@ def _dummy(): >>> nv('rank', 'A', '.', klass='error', style='color:red') 'name="rank" value="A" id="rank.A" class="error" style="color:red"' """ - name = _escaped.to_escaped(name) + name = _escaped.to_escaped(name) value = _escaped.to_escaped(value) s = sep and 'name="%s" value="%s" id="%s"' % (name, value, name+sep+value) \ - or 'name="%s" value="%s"' % (name, value) + or 'name="%s" value="%s"' % (name, value) html = kwargs and s + tagattrs(**kwargs) or s return _escaped.as_escaped(html) def js_link(label, onclick, **kwargs): s = kwargs and tagattrs(**kwargs) or '' html = '<a href="javascript:undefined" onclick="%s;return false"%s>%s</a>' % \ - (_escaped.to_escaped(onclick), s, _escaped.to_escaped(label)) + (_escaped.to_escaped(onclick), s, _escaped.to_escaped(label)) return _escaped.as_escaped(html) + html = create_module('tenjin.html', _dummy, helpers=helpers, _escaped=escaped) helpers.escape = html.escape_html helpers.html = html # for backward compatibility @@ -469,10 +514,11 @@ sys.modules['tenjin.helpers.html'] = html ## -## utility function to set default encoding of template files +# utility function to set default encoding of template files ## _template_encoding = (None, 'utf-8') # encodings for decode and encode + def set_template_encoding(decode=None, encode=None): """Set default encoding of template files. This should be called before importing helper functions. @@ -486,9 +532,11 @@ def set_template_encoding(decode=None, encode=None): if _template_encoding == (decode, encode): return if decode and encode: - raise ValueError("set_template_encoding(): cannot specify both decode and encode.") + raise ValueError( + "set_template_encoding(): cannot specify both decode and encode.") if not decode and not encode: - raise ValueError("set_template_encoding(): decode or encode should be specified.") + raise ValueError( + "set_template_encoding(): decode or encode should be specified.") if decode: Template.encoding = decode # unicode base template helpers.to_str = helpers.generate_tostrfunc(decode=decode) @@ -499,7 +547,7 @@ def set_template_encoding(decode=None, encode=None): ## -## Template class +# Template class ## class TemplateSyntaxError(SyntaxError): @@ -510,8 +558,8 @@ class TemplateSyntaxError(SyntaxError): return self.args[0] return ''.join([ "%s:%s:%s: %s\n" % (ex.filename, ex.lineno, ex.offset, ex.msg, ), - "%4d: %s\n" % (ex.lineno, ex.text.rstrip(), ), - " %s^\n" % (' ' * ex.offset, ), + "%4d: %s\n" % (ex.lineno, ex.text.rstrip(), ), + " %s^\n" % (' ' * ex.offset, ), ]) @@ -522,21 +570,21 @@ class Template(object): http://www.kuwata-lab.com/tenjin/pytenjin-examples.html """ - ## default value of attributes - filename = None - encoding = None + # default value of attributes + filename = None + encoding = None escapefunc = 'escape' - tostrfunc = 'to_str' - indent = 4 - preamble = None # "_buf = []; _expand = _buf.expand; _to_str = to_str; _escape = escape" - postamble = None # "print ''.join(_buf)" - smarttrim = None - args = None - timestamp = None - trace = False # if True then '<!-- begin: file -->' and '<!-- end: file -->' are printed + tostrfunc = 'to_str' + indent = 4 + preamble = None # "_buf = []; _expand = _buf.expand; _to_str = to_str; _escape = escape" + postamble = None # "print ''.join(_buf)" + smarttrim = None + args = None + timestamp = None + trace = False # if True then '<!-- begin: file -->' and '<!-- end: file -->' are printed def __init__(self, filename=None, encoding=None, input=None, escapefunc=None, tostrfunc=None, - indent=None, preamble=None, postamble=None, smarttrim=None, trace=None): + indent=None, preamble=None, postamble=None, smarttrim=None, trace=None): """Initailizer of Template class. filename:str (=None) @@ -565,29 +613,40 @@ class Template(object): If True then "<div>\\n#{_context}\\n</div>" is parsed as "<div>\\n#{_context}</div>". """ - if encoding is not None: self.encoding = encoding - if escapefunc is not None: self.escapefunc = escapefunc - if tostrfunc is not None: self.tostrfunc = tostrfunc - if indent is not None: self.indent = indent - if preamble is not None: self.preamble = preamble - if postamble is not None: self.postamble = postamble - if smarttrim is not None: self.smarttrim = smarttrim - if trace is not None: self.trace = trace + if encoding is not None: + self.encoding = encoding + if escapefunc is not None: + self.escapefunc = escapefunc + if tostrfunc is not None: + self.tostrfunc = tostrfunc + if indent is not None: + self.indent = indent + if preamble is not None: + self.preamble = preamble + if postamble is not None: + self.postamble = postamble + if smarttrim is not None: + self.smarttrim = smarttrim + if trace is not None: + self.trace = trace # - if preamble is True: self.preamble = "_buf = []" - if postamble is True: self.postamble = "print(''.join(_buf))" + if preamble is True: + self.preamble = "_buf = []" + if postamble is True: + self.postamble = "print(''.join(_buf))" if input: self.convert(input, filename) - self.timestamp = False # False means 'file not exist' (= Engine should not check timestamp of file) + # False means 'file not exist' (= Engine should not check timestamp of file) + self.timestamp = False elif filename: self.convert_file(filename) else: self._reset() def _reset(self, input=None, filename=None): - self.script = None + self.script = None self.bytecode = None - self.input = input + self.input = input self.filename = filename if input != None: i = input.find("\n") @@ -648,7 +707,8 @@ class Template(object): return pat def parse_stmts(self, buf, input): - if not input: return + if not input: + return rexp = self.stmt_pattern() is_bol = True index = 0 @@ -658,7 +718,7 @@ class Template(object): #code = input[m.start()+4+len(mspace):m.end()-len(close)-(rspace and len(rspace) or 0)] text = input[index:m.start()] index = m.end() - ## detect spaces at beginning of line + # detect spaces at beginning of line lspace = None if text == '': if is_bol: @@ -675,13 +735,13 @@ class Template(object): if s.isspace(): lspace, text = s, text[:rindex+1] #is_bol = rspace is not None - ## add text, spaces, and statement + # add text, spaces, and statement self.parse_exprs(buf, text, is_bol) is_bol = rspace is not None - #if mspace == "\n": + # if mspace == "\n": if mspace and mspace.endswith("\n"): code = "\n" + (code or "") - #if rspace == "\n": + # if rspace == "\n": if rspace and rspace.endswith("\n"): code = (code or "") + "\n" if code: @@ -708,10 +768,12 @@ class Template(object): def _add_args_declaration(self, buf, m): arr = (m.group(1) or '').split(',') - args = []; declares = [] + args = [] + declares = [] for s in arr: arg = s.strip() - if not s: continue + if not s: + continue if not re.match('^[a-zA-Z_]\w*$', arg): raise ValueError("%r: invalid template argument." % arg) args.append(arg) @@ -722,7 +784,8 @@ class Template(object): buf.append(''.join(declares) + "\n") s = '(?:\{.*?\}.*?)*' - EXPR_PATTERN = (r'#\{(.*?'+s+r')\}|\$\{(.*?'+s+r')\}|\{=(?:=(.*?)=|(.*?))=\}', re.S) + EXPR_PATTERN = ( + r'#\{(.*?'+s+r')\}|\$\{(.*?'+s+r')\}|\{=(?:=(.*?)=|(.*?))=\}', re.S) del s def expr_pattern(self): @@ -733,10 +796,14 @@ class Template(object): def get_expr_and_flags(self, match): expr1, expr2, expr3, expr4 = match.groups() - if expr1 is not None: return expr1, (False, True) # not escape, call to_str - if expr2 is not None: return expr2, (True, True) # call escape, call to_str - if expr3 is not None: return expr3, (False, True) # not escape, call to_str - if expr4 is not None: return expr4, (True, True) # call escape, call to_str + if expr1 is not None: + return expr1, (False, True) # not escape, call to_str + if expr2 is not None: + return expr2, (True, True) # call escape, call to_str + if expr3 is not None: + return expr3, (False, True) # not escape, call to_str + if expr4 is not None: + return expr4, (True, True) # call escape, call to_str def parse_exprs(self, buf, input, is_bol=False): buf2 = [] @@ -745,17 +812,18 @@ class Template(object): buf.append(''.join(buf2)) def _parse_exprs(self, buf, input, is_bol=False): - if not input: return + if not input: + return self.start_text_part(buf) rexp = self.expr_pattern() smarttrim = self.smarttrim nl = self.newline - nl_len = len(nl) + nl_len = len(nl) pos = 0 for m in rexp.finditer(input): start = m.start() - text = input[pos:start] - pos = m.end() + text = input[pos:start] + pos = m.end() expr, flags = self.get_expr_and_flags(m) # if text: @@ -763,7 +831,8 @@ class Template(object): self.add_expr(buf, expr, *flags) # if smarttrim: - flag_bol = text.endswith(nl) or not text and (start > 0 or is_bol) + flag_bol = text.endswith( + nl) or not text and (start > 0 or is_bol) if flag_bol and not flags[0] and input[pos:pos+nl_len] == nl: pos += nl_len buf.append("\n") @@ -779,7 +848,7 @@ class Template(object): def start_text_part(self, buf): self._add_localvars_assignments_to_text(buf) - #buf.append("_buf.extend((") + # buf.append("_buf.extend((") buf.append("_extend((") def _add_localvars_assignments_to_text(self, buf): @@ -796,30 +865,43 @@ class Template(object): return text def add_text(self, buf, text, encode_newline=False): - if not text: return + if not text: + return use_unicode = self.encoding and python2 buf.append(use_unicode and "u'''" or "'''") text = self._quote_text(text) - if not encode_newline: buf.extend((text, "''', ")) - elif text.endswith("\r\n"): buf.extend((text[0:-2], "\\r\\n''', ")) - elif text.endswith("\n"): buf.extend((text[0:-1], "\\n''', ")) - else: buf.extend((text, "''', ")) + if not encode_newline: + buf.extend((text, "''', ")) + elif text.endswith("\r\n"): + buf.extend((text[0:-2], "\\r\\n''', ")) + elif text.endswith("\n"): + buf.extend((text[0:-1], "\\n''', ")) + else: + buf.extend((text, "''', ")) _add_text = add_text def add_expr(self, buf, code, *flags): - if not code or code.isspace(): return + if not code or code.isspace(): + return flag_escape, flag_tostr = flags - if not self.tostrfunc: flag_tostr = False - if not self.escapefunc: flag_escape = False - if flag_tostr and flag_escape: s1, s2 = "_escape(_to_str(", ")), " - elif flag_tostr: s1, s2 = "_to_str(", "), " - elif flag_escape: s1, s2 = "_escape(", "), " - else: s1, s2 = "(", "), " + if not self.tostrfunc: + flag_tostr = False + if not self.escapefunc: + flag_escape = False + if flag_tostr and flag_escape: + s1, s2 = "_escape(_to_str(", ")), " + elif flag_tostr: + s1, s2 = "_to_str(", "), " + elif flag_escape: + s1, s2 = "_escape(", "), " + else: + s1, s2 = "(", "), " buf.extend((s1, code, s2, )) def add_stmt(self, buf, code): - if not code: return + if not code: + return lines = code.splitlines(True) # keep "\n" if lines[-1][-1] != "\n": lines[-1] = lines[-1] + "\n" @@ -840,59 +922,64 @@ class Template(object): else: buf[index] = self._localvars_assignments() + buf[index] - - _START_WORDS = dict.fromkeys(('for', 'if', 'while', 'def', 'try:', 'with', 'class'), True) - _END_WORDS = dict.fromkeys(('#end', '#endfor', '#endif', '#endwhile', '#enddef', '#endtry', '#endwith', '#endclass'), True) - _CONT_WORDS = dict.fromkeys(('elif', 'else:', 'except', 'except:', 'finally:'), True) - _WORD_REXP = re.compile(r'\S+') + _START_WORDS = dict.fromkeys( + ('for', 'if', 'while', 'def', 'try:', 'with', 'class'), True) + _END_WORDS = dict.fromkeys(('#end', '#endfor', '#endif', '#endwhile', + '#enddef', '#endtry', '#endwith', '#endclass'), True) + _CONT_WORDS = dict.fromkeys( + ('elif', 'else:', 'except', 'except:', 'finally:'), True) + _WORD_REXP = re.compile(r'\S+') depth = -1 ## - ## ex. - ## input = r""" - ## if items: + # ex. + # input = r""" + # if items: ## _buf.extend(('<ul>\n', )) ## i = 0 - ## for item in items: + # for item in items: ## i += 1 ## _buf.extend(('<li>', to_str(item), '</li>\n', )) - ## #endfor + # endfor ## _buf.extend(('</ul>\n', )) - ## #endif - ## """[1:] + # endif + # """[1:] ## lines = input.splitlines(True) ## block = self.parse_lines(lines) - ## #=> [ "if items:\n", - ## [ "_buf.extend(('<ul>\n', ))\n", + # => [ "if items:\n", + # [ "_buf.extend(('<ul>\n', ))\n", ## "i = 0\n", ## "for item in items:\n", - ## [ "i += 1\n", + # [ "i += 1\n", ## "_buf.extend(('<li>', to_str(item), '</li>\n', ))\n", - ## ], - ## "#endfor\n", + # ], + # "#endfor\n", ## "_buf.extend(('</ul>\n', ))\n", - ## ], - ## "#endif\n", - ## ] + # ], + # "#endif\n", + # ] def parse_lines(self, lines): block = [] try: self._parse_lines(lines.__iter__(), False, block, 0) except StopIteration: if self.depth > 0: - fname, linenum, colnum, linetext = self.filename, len(lines), None, None - raise TemplateSyntaxError("unexpected EOF.", (fname, linenum, colnum, linetext)) + fname, linenum, colnum, linetext = self.filename, len( + lines), None, None + raise TemplateSyntaxError( + "unexpected EOF.", (fname, linenum, colnum, linetext)) else: pass return block def _parse_lines(self, lines_iter, end_block, block, linenum): - if block is None: block = [] + if block is None: + block = [] _START_WORDS = self._START_WORDS - _END_WORDS = self._END_WORDS - _CONT_WORDS = self._CONT_WORDS - _WORD_REXP = self._WORD_REXP + _END_WORDS = self._END_WORDS + _CONT_WORDS = self._CONT_WORDS + _WORD_REXP = self._WORD_REXP get_line = lines_iter.next while True: line = get_line() @@ -905,11 +992,13 @@ class Template(object): if word in _END_WORDS: if word != end_block and word != '#end': if end_block is False: - msg = "'%s' found but corresponding statement is missing." % (word, ) + msg = "'%s' found but corresponding statement is missing." % ( + word, ) else: msg = "'%s' expected but got '%s'." % (end_block, word) colnum = m.start() + 1 - raise TemplateSyntaxError(msg, (self.filename, linenum, colnum, line)) + raise TemplateSyntaxError( + msg, (self.filename, linenum, colnum, line)) return block, line, None, linenum elif line.endswith(':\n') or line.endswith(':\r\n'): if word in _CONT_WORDS: @@ -920,16 +1009,19 @@ class Template(object): cont_word = None try: child_block, line, cont_word, linenum = \ - self._parse_lines(lines_iter, '#end'+word, [], linenum) + self._parse_lines( + lines_iter, '#end'+word, [], linenum) block.extend((child_block, line, )) while cont_word: # 'elif' or 'else:' child_block, line, cont_word, linenum = \ - self._parse_lines(lines_iter, '#end'+word, [], linenum) + self._parse_lines( + lines_iter, '#end'+word, [], linenum) block.extend((child_block, line, )) except StopIteration: msg = "'%s' is not closed." % (cont_word or word) colnum = m.start() + 1 - raise TemplateSyntaxError(msg, (self.filename, linenum, colnum, line)) + raise TemplateSyntaxError( + msg, (self.filename, linenum, colnum, line)) self.depth -= 1 else: block.append(line) @@ -953,7 +1045,6 @@ class Template(object): buf[:] = [] self._join_block(block, buf, 0) - def render(self, context=None, globals=None, _buf=None): """Evaluate python code with context dictionary. If _buf is None then return the result of evaluation as str, @@ -1003,11 +1094,12 @@ class Template(object): def compile(self): """compile self.script into self.bytecode""" - self.bytecode = compile(self.script, self.filename or '(tenjin)', 'exec') + self.bytecode = compile( + self.script, self.filename or '(tenjin)', 'exec') ## -## preprocessor class +# preprocessor class ## class Preprocessor(Template): @@ -1015,7 +1107,8 @@ class Preprocessor(Template): STMT_PATTERN = (r'<\?PY( |\t|\r?\n)(.*?) ?\?>([ \t]*\r?\n)?', re.S) - EXPR_PATTERN = (r'#\{\{(.*?)\}\}|\$\{\{(.*?)\}\}|\{#=(?:=(.*?)=|(.*?))=#\}', re.S) + EXPR_PATTERN = ( + r'#\{\{(.*?)\}\}|\$\{\{(.*?)\}\}|\{#=(?:=(.*?)=|(.*?))=#\}', re.S) def add_expr(self, buf, code, *flags): if not code or code.isspace(): @@ -1028,13 +1121,14 @@ class TemplatePreprocessor(object): factory = Preprocessor def __init__(self, factory=None): - if factory is not None: self.factory = factory + if factory is not None: + self.factory = factory self.globals = sys._getframe(1).f_globals def __call__(self, input, **kwargs): filename = kwargs.get('filename') - context = kwargs.get('context') or {} - globals = kwargs.get('globals') or self.globals + context = kwargs.get('context') or {} + globals = kwargs.get('globals') or self.globals template = self.factory() template.convert(input, filename) return template.render(context, globals=globals) @@ -1042,7 +1136,7 @@ class TemplatePreprocessor(object): class TrimPreprocessor(object): - _rexp = re.compile(r'^[ \t]+<', re.M) + _rexp = re.compile(r'^[ \t]+<', re.M) _rexp_all = re.compile(r'^[ \t]+', re.M) def __init__(self, all=False): @@ -1062,22 +1156,25 @@ class PrefixedLinePreprocessor(object): self.regexp = re.compile(r'^([ \t]*)' + prefix + r'(.*)', re.M) def convert_prefixed_lines(self, text): - fn = lambda m: "%s<?py%s ?>" % (m.group(1), m.group(2)) + def fn(m): return "%s<?py%s ?>" % (m.group(1), m.group(2)) return self.regexp.sub(fn, text) STMT_REXP = re.compile(r'<\?py\s.*?\?>', re.S) def __call__(self, input, **kwargs): - buf = []; append = buf.append + buf = [] + append = buf.append pos = 0 for m in self.STMT_REXP.finditer(input): text = input[pos:m.start()] stmt = m.group(0) pos = m.end() - if text: append(self.convert_prefixed_lines(text)) + if text: + append(self.convert_prefixed_lines(text)) append(stmt) rest = input[pos:] - if rest: append(self.convert_prefixed_lines(rest)) + if rest: + append(self.convert_prefixed_lines(rest)) return "".join(buf) @@ -1098,7 +1195,8 @@ class JavaScriptPreprocessor(object): self._parse_chunks(input, buf, filename) return ''.join(buf) - CHUNK_REXP = re.compile(r'(?:^( *)<|<)!-- *#(?:JS: (\$?\w+(?:\.\w+)*\(.*?\))|/JS:?) *-->([ \t]*\r?\n)?', re.M) + CHUNK_REXP = re.compile( + r'(?:^( *)<|<)!-- *#(?:JS: (\$?\w+(?:\.\w+)*\(.*?\))|/JS:?) *-->([ \t]*\r?\n)?', re.M) def _scan_chunks(self, input, filename): rexp = self.CHUNK_REXP @@ -1110,23 +1208,24 @@ class JavaScriptPreprocessor(object): pos = m.end() if funcdecl: if curr_funcdecl: - raise ParseError("%s is nested in %s. (file: %s, line: %s)" % \ - (funcdecl, curr_funcdecl, filename, _linenum(input, m.start()), )) + raise ParseError("%s is nested in %s. (file: %s, line: %s)" % + (funcdecl, curr_funcdecl, filename, _linenum(input, m.start()), )) curr_funcdecl = funcdecl else: if not curr_funcdecl: - raise ParseError("unexpected '<!-- #/JS -->'. (file: %s, line: %s)" % \ - (filename, _linenum(input, m.start()), )) + raise ParseError("unexpected '<!-- #/JS -->'. (file: %s, line: %s)" % + (filename, _linenum(input, m.start()), )) curr_funcdecl = None yield text, lspace, funcdecl, rspace, False if curr_funcdecl: - raise ParseError("%s is not closed by '<!-- #/JS -->'. (file: %s, line: %s)" % \ - (curr_funcdecl, filename, _linenum(input, m.start()), )) + raise ParseError("%s is not closed by '<!-- #/JS -->'. (file: %s, line: %s)" % + (curr_funcdecl, filename, _linenum(input, m.start()), )) rest = input[pos:] yield rest, None, None, None, True def _parse_chunks(self, input, buf, filename=None): - if not input: return + if not input: + return stag = '<script' if self._attrs: for k in self._attrs: @@ -1134,21 +1233,26 @@ class JavaScriptPreprocessor(object): stag += '>' etag = '</script>' for text, lspace, funcdecl, rspace, end_p in self._scan_chunks(input, filename): - if end_p: break + if end_p: + break if funcdecl: buf.append(text) if re.match(r'^\$?\w+\(', funcdecl): - buf.extend((lspace or '', stag, 'function ', funcdecl, "{var _buf='';", rspace or '')) + buf.extend((lspace or '', stag, 'function ', + funcdecl, "{var _buf='';", rspace or '')) else: m = re.match(r'(.+?)\((.*)\)', funcdecl) - buf.extend((lspace or '', stag, m.group(1), '=function(', m.group(2), "){var _buf='';", rspace or '')) + buf.extend((lspace or '', stag, m.group( + 1), '=function(', m.group(2), "){var _buf='';", rspace or '')) else: self._parse_stmts(text, buf) - buf.extend((lspace or '', "return _buf;};", etag, rspace or '')) + buf.extend( + (lspace or '', "return _buf;};", etag, rspace or '')) # buf.append(text) - STMT_REXP = re.compile(r'(?:^( *)<|<)\?js(\s.*?) ?\?>([ \t]*\r?\n)?', re.M | re.S) + STMT_REXP = re.compile( + r'(?:^( *)<|<)\?js(\s.*?) ?\?>([ \t]*\r?\n)?', re.M | re.S) def _scan_stmts(self, input): rexp = self.STMT_REXP @@ -1162,9 +1266,11 @@ class JavaScriptPreprocessor(object): yield rest, None, None, None, True def _parse_stmts(self, input, buf): - if not input: return + if not input: + return for text, lspace, code, rspace, end_p in self._scan_stmts(input): - if end_p: break + if end_p: + break if lspace is not None and rspace is not None: self._parse_exprs(text, buf) buf.extend((lspace, code, rspace)) @@ -1207,7 +1313,8 @@ class JavaScriptPreprocessor(object): yield rest, None, None, True def _parse_exprs(self, input, buf): - if not input: return + if not input: + return buf.append("_buf+=") extend = buf.extend op = '' @@ -1231,7 +1338,7 @@ class JavaScriptPreprocessor(object): def _escape_text(self, text): lines = text.splitlines(True) fn = self._escape_str - s = "\\\n".join( fn(line) for line in lines ) + s = "\\\n".join(fn(line) for line in lines) return "".join(("'", s, "'")) def _escape_str(self, string): @@ -1251,9 +1358,8 @@ function _EF(c){return _ET[c];}; JS_FUNC = escaped.EscapedStr(JS_FUNC) - ## -## cache storages +# cache storages ## class CacheStorage(object): @@ -1281,8 +1387,8 @@ class CacheStorage(object): return self._store(cachepath, dct) def _save_data_of(self, template): - return { 'args' : template.args, 'bytecode' : template.bytecode, - 'script': template.script, 'timestamp': template.timestamp } + return {'args': template.args, 'bytecode': template.bytecode, + 'script': template.script, 'timestamp': template.timestamp} def unset(self, cachepath): """remove template object from dict and cache file.""" @@ -1298,15 +1404,18 @@ class CacheStorage(object): def _load(self, cachepath): """(abstract) load dict object which represents template object attributes from cache file.""" - raise NotImplementedError.new("%s#_load(): not implemented yet." % self.__class__.__name__) + raise NotImplementedError.new( + "%s#_load(): not implemented yet." % self.__class__.__name__) def _store(self, cachepath, template): """(abstract) load dict object which represents template object attributes from cache file.""" - raise NotImplementedError.new("%s#_store(): not implemented yet." % self.__class__.__name__) + raise NotImplementedError.new( + "%s#_store(): not implemented yet." % self.__class__.__name__) def _delete(self, cachepath): """(abstract) remove template object from cache file.""" - raise NotImplementedError.new("%s#_delete(): not implemented yet." % self.__class__.__name__) + raise NotImplementedError.new( + "%s#_delete(): not implemented yet." % self.__class__.__name__) class MemoryCacheStorage(CacheStorage): @@ -1324,21 +1433,28 @@ class MemoryCacheStorage(CacheStorage): class FileCacheStorage(CacheStorage): def _load(self, cachepath): - if not _isfile(cachepath): return None - if logger: logger.info("[tenjin.%s] load cache (file=%r)" % (self.__class__.__name__, cachepath)) + if not _isfile(cachepath): + return None + if logger: + logger.info("[tenjin.%s] load cache (file=%r)" % + (self.__class__.__name__, cachepath)) data = _read_binary_file(cachepath) return self._restore(data) def _store(self, cachepath, dct): - if logger: logger.info("[tenjin.%s] store cache (file=%r)" % (self.__class__.__name__, cachepath)) + if logger: + logger.info("[tenjin.%s] store cache (file=%r)" % + (self.__class__.__name__, cachepath)) data = self._dump(dct) _write_binary_file(cachepath, data) def _restore(self, data): - raise NotImplementedError("%s._restore(): not implemented yet." % self.__class__.__name__) + raise NotImplementedError( + "%s._restore(): not implemented yet." % self.__class__.__name__) def _dump(self, dct): - raise NotImplementedError("%s._dump(): not implemented yet." % self.__class__.__name__) + raise NotImplementedError( + "%s._dump(): not implemented yet." % self.__class__.__name__) def _delete(self, cachepath): _ignore_not_found_error(lambda: os.unlink(cachepath)) @@ -1376,16 +1492,20 @@ class TextCacheStorage(FileCacheStorage): timestamp = encoding = args = None for line in header.split("\n"): key, val = line.split(": ", 1) - if key == 'timestamp': timestamp = float(val) - elif key == 'encoding': encoding = val - elif key == 'args': args = val.split(', ') - if encoding: script = script.decode(encoding) ## binary(=str) to unicode + if key == 'timestamp': + timestamp = float(val) + elif key == 'encoding': + encoding = val + elif key == 'args': + args = val.split(', ') + if encoding: + script = script.decode(encoding) # binary(=str) to unicode return {'args': args, 'script': script, 'timestamp': timestamp} def _dump(self, dct): s = dct['script'] if dct.get('encoding') and isinstance(s, unicode): - s = s.encode(dct['encoding']) ## unicode to binary(=str) + s = s.encode(dct['encoding']) # unicode to binary(=str) sb = [] sb.append("timestamp: %s\n" % dct['timestamp']) if dct.get('encoding'): @@ -1397,7 +1517,8 @@ class TextCacheStorage(FileCacheStorage): s = ''.join(sb) if python3: if isinstance(s, str): - s = s.encode(dct.get('encoding') or 'utf-8') ## unicode(=str) to binary + # unicode(=str) to binary + s = s.encode(dct.get('encoding') or 'utf-8') return s def _save_data_of(self, template): @@ -1406,27 +1527,30 @@ class TextCacheStorage(FileCacheStorage): return dct - ## -## abstract class for data cache +# abstract class for data cache ## class KeyValueStore(object): def get(self, key, *options): - raise NotImplementedError("%s.get(): not implemented yet." % self.__class__.__name__) + raise NotImplementedError( + "%s.get(): not implemented yet." % self.__class__.__name__) def set(self, key, value, *options): - raise NotImplementedError("%s.set(): not implemented yet." % self.__class__.__name__) + raise NotImplementedError( + "%s.set(): not implemented yet." % self.__class__.__name__) def delete(self, key, *options): - raise NotImplementedError("%s.del(): not implemented yet." % self.__class__.__name__) + raise NotImplementedError( + "%s.del(): not implemented yet." % self.__class__.__name__) def has(self, key, *options): - raise NotImplementedError("%s.has(): not implemented yet." % self.__class__.__name__) + raise NotImplementedError( + "%s.has(): not implemented yet." % self.__class__.__name__) ## -## memory base data cache +# memory base data cache ## class MemoryBaseStore(KeyValueStore): @@ -1471,7 +1595,7 @@ class MemoryBaseStore(KeyValueStore): ## -## file base data cache +# file base data cache ## class FileBaseStore(KeyValueStore): @@ -1492,7 +1616,7 @@ class FileBaseStore(KeyValueStore): def get(self, key, original_timestamp=None): fpath = self.filepath(key) - #if not _isfile(fpath): return None + # if not _isfile(fpath): return None stat = _ignore_not_found_error(lambda: os.stat(fpath), None) if stat is None: return None @@ -1505,9 +1629,9 @@ class FileBaseStore(KeyValueStore): self.delete(key) return None if self.encoding: - f = lambda: _read_text_file(fpath, self.encoding) + def f(): return _read_text_file(fpath, self.encoding) else: - f = lambda: _read_binary_file(fpath) + def f(): return _read_binary_file(fpath) return _ignore_not_found_error(f, None) def set(self, key, value, lifetime=0): @@ -1538,20 +1662,21 @@ class FileBaseStore(KeyValueStore): return True - ## -## html fragment cache helper class +# html fragment cache helper class ## class FragmentCacheHelper(object): """html fragment cache helper class.""" lifetime = 60 # 1 minute - prefix = None + prefix = None def __init__(self, store, lifetime=None, prefix=None): self.store = store - if lifetime is not None: self.lifetime = lifetime - if prefix is not None: self.prefix = prefix + if lifetime is not None: + self.lifetime = lifetime + if prefix is not None: + self.prefix = prefix def not_cached(self, cache_key, lifetime=None): """(obsolete. use cache_as() instead of this.) @@ -1560,14 +1685,19 @@ class FragmentCacheHelper(object): context['_cache_key'] = cache_key key = self.prefix and self.prefix + cache_key or cache_key value = self.store.get(key) - if value: ## cached - if logger: logger.debug('[tenjin.not_cached] %r: cached.' % (cache_key, )) + if value: # cached + if logger: + logger.debug('[tenjin.not_cached] %r: cached.' % (cache_key, )) context[key] = value return False - else: ## not cached - if logger: logger.debug('[tenjin.not_cached]: %r: not cached.' % (cache_key, )) - if key in context: del context[key] - if lifetime is None: lifetime = self.lifetime + else: # not cached + if logger: + logger.debug( + '[tenjin.not_cached]: %r: not cached.' % (cache_key, )) + if key in context: + del context[key] + if lifetime is None: + lifetime = self.lifetime context['_cache_lifetime'] = lifetime helpers.start_capture(cache_key, _depth=2) return True @@ -1579,9 +1709,9 @@ class FragmentCacheHelper(object): context = f_locals['_context'] cache_key = context.pop('_cache_key') key = self.prefix and self.prefix + cache_key or cache_key - if key in context: ## cached + if key in context: # cached value = context.pop(key) - else: ## not cached + else: # not cached value = helpers.stop_capture(False, _depth=2) lifetime = context.pop('_cache_lifetime') self.store.set(key, value, lifetime) @@ -1596,31 +1726,36 @@ class FragmentCacheHelper(object): _buf = sys._getframe(1).f_locals['_buf'] value = self.store.get(key) if value: - if logger: logger.debug('[tenjin.cache_as] %r: cache found.' % (cache_key, )) + if logger: + logger.debug('[tenjin.cache_as] %r: cache found.' % + (cache_key, )) _buf.append(value) else: - if logger: logger.debug('[tenjin.cache_as] %r: expired or not cached yet.' % (cache_key, )) + if logger: + logger.debug( + '[tenjin.cache_as] %r: expired or not cached yet.' % (cache_key, )) _buf_len = len(_buf) yield None value = ''.join(_buf[_buf_len:]) self.store.set(key, value, lifetime) -## you can change default store by 'tenjin.helpers.fragment_cache.store = ...' + +# you can change default store by 'tenjin.helpers.fragment_cache.store = ...' helpers.fragment_cache = FragmentCacheHelper(MemoryBaseStore()) -helpers.not_cached = helpers.fragment_cache.not_cached +helpers.not_cached = helpers.fragment_cache.not_cached helpers.echo_cached = helpers.fragment_cache.echo_cached -helpers.cache_as = helpers.fragment_cache.cache_as +helpers.cache_as = helpers.fragment_cache.cache_as helpers.__all__.extend(('not_cached', 'echo_cached', 'cache_as')) - ## -## helper class to find and read template +# helper class to find and read template ## class Loader(object): def exists(self, filepath): - raise NotImplementedError("%s.exists(): not implemented yet." % self.__class__.__name__) + raise NotImplementedError( + "%s.exists(): not implemented yet." % self.__class__.__name__) def find(self, filename, dirs=None): #: if dirs provided then search template file from it. @@ -1637,18 +1772,20 @@ class Loader(object): return None def abspath(self, filename): - raise NotImplementedError("%s.abspath(): not implemented yet." % self.__class__.__name__) + raise NotImplementedError( + "%s.abspath(): not implemented yet." % self.__class__.__name__) def timestamp(self, filepath): - raise NotImplementedError("%s.timestamp(): not implemented yet." % self.__class__.__name__) + raise NotImplementedError( + "%s.timestamp(): not implemented yet." % self.__class__.__name__) def load(self, filepath): - raise NotImplementedError("%s.timestamp(): not implemented yet." % self.__class__.__name__) - + raise NotImplementedError( + "%s.timestamp(): not implemented yet." % self.__class__.__name__) ## -## helper class to find and read files +# helper class to find and read files ## class FileSystemLoader(Loader): @@ -1676,7 +1813,8 @@ class FileSystemLoader(Loader): mtime2 = _getmtime(filepath) if mtime != mtime2: if logger: - logger.warn("[tenjin] %s.load(): timestamp is changed while reading file." % self.__class__.__name__) + logger.warn( + "[tenjin] %s.load(): timestamp is changed while reading file." % self.__class__.__name__) return input, mtime #: if file not exist, return None return _ignore_not_found_error(f) @@ -1689,9 +1827,8 @@ class TemplateNotFoundError(Exception): pass - ## -## template engine class +# template engine class ## class Engine(object): @@ -1701,15 +1838,15 @@ class Engine(object): http://www.kuwata-lab.com/tenjin/pytenjin-examples.html """ - ## default value of attributes - prefix = '' - postfix = '' - layout = None + # default value of attributes + prefix = '' + postfix = '' + layout = None templateclass = Template - path = None - cache = TextCacheStorage() # save converted Python code into text file - lang = None - loader = FileSystemLoader() + path = None + cache = TextCacheStorage() # save converted Python code into text file + lang = None + loader = FileSystemLoader() preprocess = False preprocessorclass = Preprocessor timestamp_interval = 1 # seconds @@ -1743,18 +1880,30 @@ class Engine(object): Options for Template class constructor. See document of Template.__init__() for details. """ - if prefix: self.prefix = prefix - if postfix: self.postfix = postfix - if layout: self.layout = layout - if templateclass: self.templateclass = templateclass - if preprocessorclass: self.preprocessorclass = preprocessorclass - if path is not None: self.path = path - if lang is not None: self.lang = lang - if loader is not None: self.loader = loader - if preprocess is not None: self.preprocess = preprocess - if pp is None: pp = [] - elif isinstance(pp, list): pass - elif isinstance(pp, tuple): pp = list(pp) + if prefix: + self.prefix = prefix + if postfix: + self.postfix = postfix + if layout: + self.layout = layout + if templateclass: + self.templateclass = templateclass + if preprocessorclass: + self.preprocessorclass = preprocessorclass + if path is not None: + self.path = path + if lang is not None: + self.lang = lang + if loader is not None: + self.loader = loader + if preprocess is not None: + self.preprocess = preprocess + if pp is None: + pp = [] + elif isinstance(pp, list): + pass + elif isinstance(pp, tuple): + pp = list(pp) else: raise TypeError("'pp' expected to be a list but got %r." % (pp,)) self.pp = pp @@ -1798,7 +1947,7 @@ class Engine(object): 'list' """ #: if template_name starts with ':', add prefix and postfix to it. - if template_name[0] == ':' : + if template_name[0] == ':': return self.prefix + template_name[1:] + self.postfix #: if template_name doesn't start with ':', just return it. return template_name @@ -1816,12 +1965,13 @@ class Engine(object): #if _globals is None: _globals = sys._getframe(3).f_globals #: preprocess template and return result #preprocessor = self.preprocessorclass(filepath, input=input) - #return preprocessor.render(_context, globals=_globals) + # return preprocessor.render(_context, globals=_globals) #: preprocesses input with _context and returns result. if '_engine' not in _context: self.hook_context(_context) for pp in self.pp: - input = pp.__call__(input, filename=filepath, context=_context, globals=_globals) + input = pp.__call__(input, filename=filepath, + context=_context, globals=_globals) return input def add_template(self, template): @@ -1837,7 +1987,7 @@ class Engine(object): now = _time() last_checked = getattr(template, '_last_checked_at', None) if last_checked and now < last_checked + self.timestamp_interval: - #if logger: logger.trace('[tenjin.%s] timestamp check skipped (%f < %f + %f)' % \ + # if logger: logger.trace('[tenjin.%s] timestamp check skipped (%f < %f + %f)' % \ # (self.__class__.__name__, now, template._last_checked_at, self.timestamp_interval)) return template #: if timestamp of template objectis same as file, return it. @@ -1845,9 +1995,10 @@ class Engine(object): template._last_checked_at = now return template #: if timestamp of template object is different from file, clear it - #cache._delete(cachepath) - if logger: logger.info("[tenjin.%s] cache expired (filepath=%r)" % \ - (self.__class__.__name__, filepath)) + # cache._delete(cachepath) + if logger: + logger.info("[tenjin.%s] cache expired (filepath=%r)" % + (self.__class__.__name__, filepath)) return None def get_template(self, template_name, _context=None, _globals=None): @@ -1868,7 +2019,8 @@ class Engine(object): #: if template file is not found then raise TemplateNotFoundError. filepath = self.loader.find(filename, self.path) if not filepath: - raise TemplateNotFoundError('%s: filename not found (path=%r).' % (filename, self.path)) + raise TemplateNotFoundError( + '%s: filename not found (path=%r).' % (filename, self.path)) # fullpath = self.loader.abspath(filepath) self._filepaths[filename] = (filepath, fullpath) @@ -1876,19 +2028,24 @@ class Engine(object): cachepath = self.cachename(fullpath) #: get template object from cache cache = self.cache - template = cache and self._get_template_from_cache(cachepath, filepath) or None + template = cache and self._get_template_from_cache( + cachepath, filepath) or None #: if template object is not found in cache or is expired... if not template: ret = self.loader.load(filepath) if not ret: - raise TemplateNotFoundError("%r: template not found." % filepath) + raise TemplateNotFoundError( + "%r: template not found." % filepath) input, timestamp = ret - if self.pp: ## required for preprocessing - if _context is None: _context = {} - if _globals is None: _globals = sys._getframe(1).f_globals + if self.pp: # required for preprocessing + if _context is None: + _context = {} + if _globals is None: + _globals = sys._getframe(1).f_globals input = self._preprocess(input, filepath, _context, _globals) #: create template object. - template = self._create_template(input, filepath, _context, _globals) + template = self._create_template( + input, filepath, _context, _globals) #: set timestamp and filename of template object. template.timestamp = timestamp template._last_checked_at = _time() @@ -1896,10 +2053,12 @@ class Engine(object): if cache: if not template.bytecode: #: ignores syntax error when compiling. - try: template.compile() - except SyntaxError: pass + try: + template.compile() + except SyntaxError: + pass cache.set(cachepath, template) - #else: + # else: # template.compile() #: template.filename = filepath @@ -1921,7 +2080,7 @@ class Engine(object): """ #: get local and global vars of caller. frame = sys._getframe(1) - locals = frame.f_locals + locals = frame.f_locals globals = frame.f_globals #: get _context from caller's local vars. assert '_context' in locals @@ -1930,12 +2089,14 @@ class Engine(object): if kwargs: context.update(kwargs) #: get template object with context data and global vars. - ## (context and globals are passed to get_template() only for preprocessing.) + # (context and globals are passed to get_template() only for preprocessing.) template = self.get_template(template_name, context, globals) #: if append_to_buf is true then add output to _buf. #: if append_to_buf is false then don't add output to _buf. - if append_to_buf: _buf = locals['_buf'] - else: _buf = None + if append_to_buf: + _buf = locals['_buf'] + else: + _buf = None #: render template and return output. s = template.render(context, globals, _buf=_buf) #: kwargs are removed from context data. @@ -1967,10 +2128,10 @@ class Engine(object): globals = sys._getframe(1).f_globals self.hook_context(context) while True: - ## context and globals are passed to get_template() only for preprocessing + # context and globals are passed to get_template() only for preprocessing template = self.get_template(template_name, context, globals) - content = template.render(context, globals) - layout = context.pop('_layout', layout) + content = template.render(context, globals) + layout = context.pop('_layout', layout) if layout is True or layout is None: layout = self.layout if not layout: @@ -1990,7 +2151,7 @@ class Engine(object): ## -## safe template and engine +# safe template and engine ## class SafeTemplate(Template): @@ -1998,7 +2159,7 @@ class SafeTemplate(Template): '#{...}' is not allowed with this class. Use '[==...==]' instead. """ - tostrfunc = 'to_str' + tostrfunc = 'to_str' escapefunc = 'to_escaped' def get_expr_and_flags(self, match): @@ -2007,7 +2168,7 @@ class SafeTemplate(Template): class SafePreprocessor(Preprocessor): - tostrfunc = 'to_str' + tostrfunc = 'to_str' escapefunc = 'to_escaped' def get_expr_and_flags(self, match): @@ -2018,20 +2179,23 @@ def _get_expr_and_flags(match, errmsg): expr1, expr2, expr3, expr4 = match.groups() if expr1 is not None: raise TemplateSyntaxError(errmsg % match.group(1)) - if expr2 is not None: return expr2, (True, False) # #{...} : call escape, not to_str - if expr3 is not None: return expr3, (False, True) # [==...==] : not escape, call to_str - if expr4 is not None: return expr4, (True, False) # [=...=] : call escape, not to_str + if expr2 is not None: + return expr2, (True, False) # #{...} : call escape, not to_str + if expr3 is not None: + return expr3, (False, True) # [==...==] : not escape, call to_str + if expr4 is not None: + return expr4, (True, False) # [=...=] : call escape, not to_str class SafeEngine(Engine): - templateclass = SafeTemplate + templateclass = SafeTemplate preprocessorclass = SafePreprocessor ## -## for Google App Engine -## (should separate into individual file or module?) +# for Google App Engine +# (should separate into individual file or module?) ## def _dummy(): @@ -2045,44 +2209,55 @@ def _dummy(): def __init__(self, lifetime=None, namespace=None): CacheStorage.__init__(self) - if lifetime is not None: self.lifetime = lifetime + if lifetime is not None: + self.lifetime = lifetime self.namespace = namespace def _load(self, cachepath): key = cachepath - if _tenjin.logger: _tenjin.logger.info("[tenjin.gae.GaeMemcacheCacheStorage] load cache (key=%r)" % (key, )) + if _tenjin.logger: + _tenjin.logger.info( + "[tenjin.gae.GaeMemcacheCacheStorage] load cache (key=%r)" % (key, )) return memcache.get(key, namespace=self.namespace) def _store(self, cachepath, dct): dct.pop('bytecode', None) key = cachepath - if _tenjin.logger: _tenjin.logger.info("[tenjin.gae.GaeMemcacheCacheStorage] store cache (key=%r)" % (key, )) - ret = memcache.set(key, dct, self.lifetime, namespace=self.namespace) + if _tenjin.logger: + _tenjin.logger.info( + "[tenjin.gae.GaeMemcacheCacheStorage] store cache (key=%r)" % (key, )) + ret = memcache.set(key, dct, self.lifetime, + namespace=self.namespace) if not ret: - if _tenjin.logger: _tenjin.logger.info("[tenjin.gae.GaeMemcacheCacheStorage] failed to store cache (key=%r)" % (key, )) + if _tenjin.logger: + _tenjin.logger.info( + "[tenjin.gae.GaeMemcacheCacheStorage] failed to store cache (key=%r)" % (key, )) def _delete(self, cachepath): key = cachepath memcache.delete(key, namespace=self.namespace) - class GaeMemcacheStore(KeyValueStore): lifetime = 0 def __init__(self, lifetime=None, namespace=None): - if lifetime is not None: self.lifetime = lifetime + if lifetime is not None: + self.lifetime = lifetime self.namespace = namespace def get(self, key): return memcache.get(key, namespace=self.namespace) def set(self, key, value, lifetime=None): - if lifetime is None: lifetime = self.lifetime + if lifetime is None: + lifetime = self.lifetime if memcache.set(key, value, lifetime, namespace=self.namespace): return True else: - if _tenjin.logger: _tenjin.logger.info("[tenjin.gae.GaeMemcacheStore] failed to set (key=%r)" % (key, )) + if _tenjin.logger: + _tenjin.logger.info( + "[tenjin.gae.GaeMemcacheStore] failed to set (key=%r)" % (key, )) return False def delete(self, key): @@ -2095,19 +2270,19 @@ def _dummy(): else: return True - def init(): global memcache, _tenjin if not memcache: from google.appengine.api import memcache - if not _tenjin: import tenjin as _tenjin - ## avoid cache confliction between versions - ver = os.environ.get('CURRENT_VERSION_ID', '1.1')#.split('.')[0] + if not _tenjin: + import tenjin as _tenjin + # avoid cache confliction between versions + ver = os.environ.get('CURRENT_VERSION_ID', '1.1') # .split('.')[0] Engine.cache = GaeMemcacheCacheStorage(namespace=ver) - ## set fragment cache store - helpers.fragment_cache.store = GaeMemcacheStore(namespace=ver) - helpers.fragment_cache.lifetime = 60 # 1 minute - helpers.fragment_cache.prefix = 'fragment.' + # set fragment cache store + helpers.fragment_cache.store = GaeMemcacheStore(namespace=ver) + helpers.fragment_cache.lifetime = 60 # 1 minute + helpers.fragment_cache.prefix = 'fragment.' gae = create_module('tenjin.gae', _dummy, |