Mako-1.0.7/0000775000175000017500000000000013131716021013467 5ustar classicclassic00000000000000Mako-1.0.7/setup.py0000664000175000017500000000563013131715725015217 0ustar classicclassic00000000000000from setuptools import setup, find_packages from setuptools.command.test import test as TestCommand import os import re import sys v = open(os.path.join(os.path.dirname(__file__), 'mako', '__init__.py')) VERSION = re.compile(r".*__version__ = '(.*?)'", re.S).match(v.read()).group(1) v.close() readme = open(os.path.join(os.path.dirname(__file__), 'README.rst')).read() if sys.version_info < (2, 6): raise Exception("Mako requires Python 2.6 or higher.") markupsafe_installs = ( sys.version_info >= (2, 6) and sys.version_info < (3, 0) ) or sys.version_info >= (3, 3) install_requires = [] if markupsafe_installs: install_requires.append('MarkupSafe>=0.9.2') try: import argparse except ImportError: install_requires.append('argparse') class PyTest(TestCommand): user_options = [('pytest-args=', 'a', "Arguments to pass to py.test")] def initialize_options(self): TestCommand.initialize_options(self) self.pytest_args = [] def finalize_options(self): TestCommand.finalize_options(self) self.test_args = [] self.test_suite = True def run_tests(self): # import here, cause outside the eggs aren't loaded import pytest errno = pytest.main(self.pytest_args) sys.exit(errno) setup(name='Mako', version=VERSION, description="A super-fast templating language that borrows the \ best ideas from the existing templating languages.", long_description=readme, classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'Programming Language :: Python', 'Programming Language :: Python :: 3', "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', ], keywords='templates', author='Mike Bayer', author_email='mike@zzzcomputing.com', url='http://www.makotemplates.org/', license='MIT', packages=find_packages('.', exclude=['examples*', 'test*']), tests_require=['pytest', 'mock'], cmdclass={'test': PyTest}, zip_safe=False, install_requires=install_requires, extras_require={}, entry_points=""" [python.templating.engines] mako = mako.ext.turbogears:TGPlugin [pygments.lexers] mako = mako.ext.pygmentplugin:MakoLexer html+mako = mako.ext.pygmentplugin:MakoHtmlLexer xml+mako = mako.ext.pygmentplugin:MakoXmlLexer js+mako = mako.ext.pygmentplugin:MakoJavascriptLexer css+mako = mako.ext.pygmentplugin:MakoCssLexer [babel.extractors] mako = mako.ext.babelplugin:extract [lingua.extractors] mako = mako.ext.linguaplugin:LinguaMakoExtractor [console_scripts] mako-render = mako.cmd:cmdline """ ) Mako-1.0.7/mako/0000775000175000017500000000000013131716021014416 5ustar classicclassic00000000000000Mako-1.0.7/mako/parsetree.py0000664000175000017500000004544513131715725017010 0ustar classicclassic00000000000000# mako/parsetree.py # Copyright (C) 2006-2016 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php """defines the parse tree components for Mako templates.""" from mako import exceptions, ast, util, filters, compat import re class Node(object): """base class for a Node in the parse tree.""" def __init__(self, source, lineno, pos, filename): self.source = source self.lineno = lineno self.pos = pos self.filename = filename @property def exception_kwargs(self): return {'source': self.source, 'lineno': self.lineno, 'pos': self.pos, 'filename': self.filename} def get_children(self): return [] def accept_visitor(self, visitor): def traverse(node): for n in node.get_children(): n.accept_visitor(visitor) method = getattr(visitor, "visit" + self.__class__.__name__, traverse) method(self) class TemplateNode(Node): """a 'container' node that stores the overall collection of nodes.""" def __init__(self, filename): super(TemplateNode, self).__init__('', 0, 0, filename) self.nodes = [] self.page_attributes = {} def get_children(self): return self.nodes def __repr__(self): return "TemplateNode(%s, %r)" % ( util.sorted_dict_repr(self.page_attributes), self.nodes) class ControlLine(Node): """defines a control line, a line-oriented python line or end tag. e.g.:: % if foo: (markup) % endif """ has_loop_context = False def __init__(self, keyword, isend, text, **kwargs): super(ControlLine, self).__init__(**kwargs) self.text = text self.keyword = keyword self.isend = isend self.is_primary = keyword in ['for', 'if', 'while', 'try', 'with'] self.nodes = [] if self.isend: self._declared_identifiers = [] self._undeclared_identifiers = [] else: code = ast.PythonFragment(text, **self.exception_kwargs) self._declared_identifiers = code.declared_identifiers self._undeclared_identifiers = code.undeclared_identifiers def get_children(self): return self.nodes def declared_identifiers(self): return self._declared_identifiers def undeclared_identifiers(self): return self._undeclared_identifiers def is_ternary(self, keyword): """return true if the given keyword is a ternary keyword for this ControlLine""" return keyword in { 'if': set(['else', 'elif']), 'try': set(['except', 'finally']), 'for': set(['else']) }.get(self.keyword, []) def __repr__(self): return "ControlLine(%r, %r, %r, %r)" % ( self.keyword, self.text, self.isend, (self.lineno, self.pos) ) class Text(Node): """defines plain text in the template.""" def __init__(self, content, **kwargs): super(Text, self).__init__(**kwargs) self.content = content def __repr__(self): return "Text(%r, %r)" % (self.content, (self.lineno, self.pos)) class Code(Node): """defines a Python code block, either inline or module level. e.g.:: inline: <% x = 12 %> module level: <%! import logger %> """ def __init__(self, text, ismodule, **kwargs): super(Code, self).__init__(**kwargs) self.text = text self.ismodule = ismodule self.code = ast.PythonCode(text, **self.exception_kwargs) def declared_identifiers(self): return self.code.declared_identifiers def undeclared_identifiers(self): return self.code.undeclared_identifiers def __repr__(self): return "Code(%r, %r, %r)" % ( self.text, self.ismodule, (self.lineno, self.pos) ) class Comment(Node): """defines a comment line. # this is a comment """ def __init__(self, text, **kwargs): super(Comment, self).__init__(**kwargs) self.text = text def __repr__(self): return "Comment(%r, %r)" % (self.text, (self.lineno, self.pos)) class Expression(Node): """defines an inline expression. ${x+y} """ def __init__(self, text, escapes, **kwargs): super(Expression, self).__init__(**kwargs) self.text = text self.escapes = escapes self.escapes_code = ast.ArgumentList(escapes, **self.exception_kwargs) self.code = ast.PythonCode(text, **self.exception_kwargs) def declared_identifiers(self): return [] def undeclared_identifiers(self): # TODO: make the "filter" shortcut list configurable at parse/gen time return self.code.undeclared_identifiers.union( self.escapes_code.undeclared_identifiers.difference( set(filters.DEFAULT_ESCAPES.keys()) ) ).difference(self.code.declared_identifiers) def __repr__(self): return "Expression(%r, %r, %r)" % ( self.text, self.escapes_code.args, (self.lineno, self.pos) ) class _TagMeta(type): """metaclass to allow Tag to produce a subclass according to its keyword""" _classmap = {} def __init__(cls, clsname, bases, dict): if getattr(cls, '__keyword__', None) is not None: cls._classmap[cls.__keyword__] = cls super(_TagMeta, cls).__init__(clsname, bases, dict) def __call__(cls, keyword, attributes, **kwargs): if ":" in keyword: ns, defname = keyword.split(':') return type.__call__(CallNamespaceTag, ns, defname, attributes, **kwargs) try: cls = _TagMeta._classmap[keyword] except KeyError: raise exceptions.CompileException( "No such tag: '%s'" % keyword, source=kwargs['source'], lineno=kwargs['lineno'], pos=kwargs['pos'], filename=kwargs['filename'] ) return type.__call__(cls, keyword, attributes, **kwargs) class Tag(compat.with_metaclass(_TagMeta, Node)): """abstract base class for tags. <%sometag/> <%someothertag> stuff """ __keyword__ = None def __init__(self, keyword, attributes, expressions, nonexpressions, required, **kwargs): r"""construct a new Tag instance. this constructor not called directly, and is only called by subclasses. :param keyword: the tag keyword :param attributes: raw dictionary of attribute key/value pairs :param expressions: a set of identifiers that are legal attributes, which can also contain embedded expressions :param nonexpressions: a set of identifiers that are legal attributes, which cannot contain embedded expressions :param \**kwargs: other arguments passed to the Node superclass (lineno, pos) """ super(Tag, self).__init__(**kwargs) self.keyword = keyword self.attributes = attributes self._parse_attributes(expressions, nonexpressions) missing = [r for r in required if r not in self.parsed_attributes] if len(missing): raise exceptions.CompileException( "Missing attribute(s): %s" % ",".join([repr(m) for m in missing]), **self.exception_kwargs) self.parent = None self.nodes = [] def is_root(self): return self.parent is None def get_children(self): return self.nodes def _parse_attributes(self, expressions, nonexpressions): undeclared_identifiers = set() self.parsed_attributes = {} for key in self.attributes: if key in expressions: expr = [] for x in re.compile(r'(\${.+?})', re.S).split(self.attributes[key]): m = re.compile(r'^\${(.+?)}$', re.S).match(x) if m: code = ast.PythonCode(m.group(1).rstrip(), **self.exception_kwargs) # we aren't discarding "declared_identifiers" here, # which we do so that list comprehension-declared # variables aren't counted. As yet can't find a # condition that requires it here. undeclared_identifiers = \ undeclared_identifiers.union( code.undeclared_identifiers) expr.append('(%s)' % m.group(1)) else: if x: expr.append(repr(x)) self.parsed_attributes[key] = " + ".join(expr) or repr('') elif key in nonexpressions: if re.search(r'\${.+?}', self.attributes[key]): raise exceptions.CompileException( "Attibute '%s' in tag '%s' does not allow embedded " "expressions" % (key, self.keyword), **self.exception_kwargs) self.parsed_attributes[key] = repr(self.attributes[key]) else: raise exceptions.CompileException( "Invalid attribute for tag '%s': '%s'" % (self.keyword, key), **self.exception_kwargs) self.expression_undeclared_identifiers = undeclared_identifiers def declared_identifiers(self): return [] def undeclared_identifiers(self): return self.expression_undeclared_identifiers def __repr__(self): return "%s(%r, %s, %r, %r)" % (self.__class__.__name__, self.keyword, util.sorted_dict_repr(self.attributes), (self.lineno, self.pos), self.nodes ) class IncludeTag(Tag): __keyword__ = 'include' def __init__(self, keyword, attributes, **kwargs): super(IncludeTag, self).__init__( keyword, attributes, ('file', 'import', 'args'), (), ('file',), **kwargs) self.page_args = ast.PythonCode( "__DUMMY(%s)" % attributes.get('args', ''), **self.exception_kwargs) def declared_identifiers(self): return [] def undeclared_identifiers(self): identifiers = self.page_args.undeclared_identifiers.\ difference(set(["__DUMMY"])).\ difference(self.page_args.declared_identifiers) return identifiers.union(super(IncludeTag, self). undeclared_identifiers()) class NamespaceTag(Tag): __keyword__ = 'namespace' def __init__(self, keyword, attributes, **kwargs): super(NamespaceTag, self).__init__( keyword, attributes, ('file',), ('name', 'inheritable', 'import', 'module'), (), **kwargs) self.name = attributes.get('name', '__anon_%s' % hex(abs(id(self)))) if 'name' not in attributes and 'import' not in attributes: raise exceptions.CompileException( "'name' and/or 'import' attributes are required " "for <%namespace>", **self.exception_kwargs) if 'file' in attributes and 'module' in attributes: raise exceptions.CompileException( "<%namespace> may only have one of 'file' or 'module'", **self.exception_kwargs ) def declared_identifiers(self): return [] class TextTag(Tag): __keyword__ = 'text' def __init__(self, keyword, attributes, **kwargs): super(TextTag, self).__init__( keyword, attributes, (), ('filter'), (), **kwargs) self.filter_args = ast.ArgumentList( attributes.get('filter', ''), **self.exception_kwargs) def undeclared_identifiers(self): return self.filter_args.\ undeclared_identifiers.\ difference(filters.DEFAULT_ESCAPES.keys()).union( self.expression_undeclared_identifiers ) class DefTag(Tag): __keyword__ = 'def' def __init__(self, keyword, attributes, **kwargs): expressions = ['buffered', 'cached'] + [ c for c in attributes if c.startswith('cache_')] super(DefTag, self).__init__( keyword, attributes, expressions, ('name', 'filter', 'decorator'), ('name',), **kwargs) name = attributes['name'] if re.match(r'^[\w_]+$', name): raise exceptions.CompileException( "Missing parenthesis in %def", **self.exception_kwargs) self.function_decl = ast.FunctionDecl("def " + name + ":pass", **self.exception_kwargs) self.name = self.function_decl.funcname self.decorator = attributes.get('decorator', '') self.filter_args = ast.ArgumentList( attributes.get('filter', ''), **self.exception_kwargs) is_anonymous = False is_block = False @property def funcname(self): return self.function_decl.funcname def get_argument_expressions(self, **kw): return self.function_decl.get_argument_expressions(**kw) def declared_identifiers(self): return self.function_decl.allargnames def undeclared_identifiers(self): res = [] for c in self.function_decl.defaults: res += list(ast.PythonCode(c, **self.exception_kwargs). undeclared_identifiers) return set(res).union( self.filter_args. undeclared_identifiers. difference(filters.DEFAULT_ESCAPES.keys()) ).union( self.expression_undeclared_identifiers ).difference( self.function_decl.allargnames ) class BlockTag(Tag): __keyword__ = 'block' def __init__(self, keyword, attributes, **kwargs): expressions = ['buffered', 'cached', 'args'] + [ c for c in attributes if c.startswith('cache_')] super(BlockTag, self).__init__( keyword, attributes, expressions, ('name', 'filter', 'decorator'), (), **kwargs) name = attributes.get('name') if name and not re.match(r'^[\w_]+$', name): raise exceptions.CompileException( "%block may not specify an argument signature", **self.exception_kwargs) if not name and attributes.get('args', None): raise exceptions.CompileException( "Only named %blocks may specify args", **self.exception_kwargs ) self.body_decl = ast.FunctionArgs(attributes.get('args', ''), **self.exception_kwargs) self.name = name self.decorator = attributes.get('decorator', '') self.filter_args = ast.ArgumentList( attributes.get('filter', ''), **self.exception_kwargs) is_block = True @property def is_anonymous(self): return self.name is None @property def funcname(self): return self.name or "__M_anon_%d" % (self.lineno, ) def get_argument_expressions(self, **kw): return self.body_decl.get_argument_expressions(**kw) def declared_identifiers(self): return self.body_decl.allargnames def undeclared_identifiers(self): return (self.filter_args. undeclared_identifiers. difference(filters.DEFAULT_ESCAPES.keys()) ).union(self.expression_undeclared_identifiers) class CallTag(Tag): __keyword__ = 'call' def __init__(self, keyword, attributes, **kwargs): super(CallTag, self).__init__(keyword, attributes, ('args'), ('expr',), ('expr',), **kwargs) self.expression = attributes['expr'] self.code = ast.PythonCode(self.expression, **self.exception_kwargs) self.body_decl = ast.FunctionArgs(attributes.get('args', ''), **self.exception_kwargs) def declared_identifiers(self): return self.code.declared_identifiers.union(self.body_decl.allargnames) def undeclared_identifiers(self): return self.code.undeclared_identifiers.\ difference(self.code.declared_identifiers) class CallNamespaceTag(Tag): def __init__(self, namespace, defname, attributes, **kwargs): super(CallNamespaceTag, self).__init__( namespace + ":" + defname, attributes, tuple(attributes.keys()) + ('args', ), (), (), **kwargs) self.expression = "%s.%s(%s)" % ( namespace, defname, ",".join(["%s=%s" % (k, v) for k, v in self.parsed_attributes.items() if k != 'args']) ) self.code = ast.PythonCode(self.expression, **self.exception_kwargs) self.body_decl = ast.FunctionArgs( attributes.get('args', ''), **self.exception_kwargs) def declared_identifiers(self): return self.code.declared_identifiers.union(self.body_decl.allargnames) def undeclared_identifiers(self): return self.code.undeclared_identifiers.\ difference(self.code.declared_identifiers) class InheritTag(Tag): __keyword__ = 'inherit' def __init__(self, keyword, attributes, **kwargs): super(InheritTag, self).__init__( keyword, attributes, ('file',), (), ('file',), **kwargs) class PageTag(Tag): __keyword__ = 'page' def __init__(self, keyword, attributes, **kwargs): expressions = \ ['cached', 'args', 'expression_filter', 'enable_loop'] + \ [c for c in attributes if c.startswith('cache_')] super(PageTag, self).__init__( keyword, attributes, expressions, (), (), **kwargs) self.body_decl = ast.FunctionArgs(attributes.get('args', ''), **self.exception_kwargs) self.filter_args = ast.ArgumentList( attributes.get('expression_filter', ''), **self.exception_kwargs) def declared_identifiers(self): return self.body_decl.allargnames Mako-1.0.7/mako/lookup.py0000664000175000017500000003123013131715725016312 0ustar classicclassic00000000000000# mako/lookup.py # Copyright (C) 2006-2016 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php import os import stat import posixpath import re from mako import exceptions, util from mako.template import Template try: import threading except: import dummy_threading as threading class TemplateCollection(object): """Represent a collection of :class:`.Template` objects, identifiable via URI. A :class:`.TemplateCollection` is linked to the usage of all template tags that address other templates, such as ``<%include>``, ``<%namespace>``, and ``<%inherit>``. The ``file`` attribute of each of those tags refers to a string URI that is passed to that :class:`.Template` object's :class:`.TemplateCollection` for resolution. :class:`.TemplateCollection` is an abstract class, with the usual default implementation being :class:`.TemplateLookup`. """ def has_template(self, uri): """Return ``True`` if this :class:`.TemplateLookup` is capable of returning a :class:`.Template` object for the given ``uri``. :param uri: String URI of the template to be resolved. """ try: self.get_template(uri) return True except exceptions.TemplateLookupException: return False def get_template(self, uri, relativeto=None): """Return a :class:`.Template` object corresponding to the given ``uri``. The default implementation raises :class:`.NotImplementedError`. Implementations should raise :class:`.TemplateLookupException` if the given ``uri`` cannot be resolved. :param uri: String URI of the template to be resolved. :param relativeto: if present, the given ``uri`` is assumed to be relative to this URI. """ raise NotImplementedError() def filename_to_uri(self, uri, filename): """Convert the given ``filename`` to a URI relative to this :class:`.TemplateCollection`.""" return uri def adjust_uri(self, uri, filename): """Adjust the given ``uri`` based on the calling ``filename``. When this method is called from the runtime, the ``filename`` parameter is taken directly to the ``filename`` attribute of the calling template. Therefore a custom :class:`.TemplateCollection` subclass can place any string identifier desired in the ``filename`` parameter of the :class:`.Template` objects it constructs and have them come back here. """ return uri class TemplateLookup(TemplateCollection): """Represent a collection of templates that locates template source files from the local filesystem. The primary argument is the ``directories`` argument, the list of directories to search: .. sourcecode:: python lookup = TemplateLookup(["/path/to/templates"]) some_template = lookup.get_template("/index.html") The :class:`.TemplateLookup` can also be given :class:`.Template` objects programatically using :meth:`.put_string` or :meth:`.put_template`: .. sourcecode:: python lookup = TemplateLookup() lookup.put_string("base.html", ''' ${self.next()} ''') lookup.put_string("hello.html", ''' <%include file='base.html'/> Hello, world ! ''') :param directories: A list of directory names which will be searched for a particular template URI. The URI is appended to each directory and the filesystem checked. :param collection_size: Approximate size of the collection used to store templates. If left at its default of ``-1``, the size is unbounded, and a plain Python dictionary is used to relate URI strings to :class:`.Template` instances. Otherwise, a least-recently-used cache object is used which will maintain the size of the collection approximately to the number given. :param filesystem_checks: When at its default value of ``True``, each call to :meth:`.TemplateLookup.get_template()` will compare the filesystem last modified time to the time in which an existing :class:`.Template` object was created. This allows the :class:`.TemplateLookup` to regenerate a new :class:`.Template` whenever the original source has been updated. Set this to ``False`` for a very minor performance increase. :param modulename_callable: A callable which, when present, is passed the path of the source file as well as the requested URI, and then returns the full path of the generated Python module file. This is used to inject alternate schemes for Python module location. If left at its default of ``None``, the built in system of generation based on ``module_directory`` plus ``uri`` is used. All other keyword parameters available for :class:`.Template` are mirrored here. When new :class:`.Template` objects are created, the keywords established with this :class:`.TemplateLookup` are passed on to each new :class:`.Template`. """ def __init__(self, directories=None, module_directory=None, filesystem_checks=True, collection_size=-1, format_exceptions=False, error_handler=None, disable_unicode=False, bytestring_passthrough=False, output_encoding=None, encoding_errors='strict', cache_args=None, cache_impl='beaker', cache_enabled=True, cache_type=None, cache_dir=None, cache_url=None, modulename_callable=None, module_writer=None, default_filters=None, buffer_filters=(), strict_undefined=False, imports=None, future_imports=None, enable_loop=True, input_encoding=None, preprocessor=None, lexer_cls=None, include_error_handler=None): self.directories = [posixpath.normpath(d) for d in util.to_list(directories, ()) ] self.module_directory = module_directory self.modulename_callable = modulename_callable self.filesystem_checks = filesystem_checks self.collection_size = collection_size if cache_args is None: cache_args = {} # transfer deprecated cache_* args if cache_dir: cache_args.setdefault('dir', cache_dir) if cache_url: cache_args.setdefault('url', cache_url) if cache_type: cache_args.setdefault('type', cache_type) self.template_args = { 'format_exceptions': format_exceptions, 'error_handler': error_handler, 'include_error_handler': include_error_handler, 'disable_unicode': disable_unicode, 'bytestring_passthrough': bytestring_passthrough, 'output_encoding': output_encoding, 'cache_impl': cache_impl, 'encoding_errors': encoding_errors, 'input_encoding': input_encoding, 'module_directory': module_directory, 'module_writer': module_writer, 'cache_args': cache_args, 'cache_enabled': cache_enabled, 'default_filters': default_filters, 'buffer_filters': buffer_filters, 'strict_undefined': strict_undefined, 'imports': imports, 'future_imports': future_imports, 'enable_loop': enable_loop, 'preprocessor': preprocessor, 'lexer_cls': lexer_cls } if collection_size == -1: self._collection = {} self._uri_cache = {} else: self._collection = util.LRUCache(collection_size) self._uri_cache = util.LRUCache(collection_size) self._mutex = threading.Lock() def get_template(self, uri): """Return a :class:`.Template` object corresponding to the given ``uri``. .. note:: The ``relativeto`` argument is not supported here at the moment. """ try: if self.filesystem_checks: return self._check(uri, self._collection[uri]) else: return self._collection[uri] except KeyError: u = re.sub(r'^\/+', '', uri) for dir in self.directories: # make sure the path seperators are posix - os.altsep is empty # on POSIX and cannot be used. dir = dir.replace(os.path.sep, posixpath.sep) srcfile = posixpath.normpath(posixpath.join(dir, u)) if os.path.isfile(srcfile): return self._load(srcfile, uri) else: raise exceptions.TopLevelLookupException( "Cant locate template for uri %r" % uri) def adjust_uri(self, uri, relativeto): """Adjust the given ``uri`` based on the given relative URI.""" key = (uri, relativeto) if key in self._uri_cache: return self._uri_cache[key] if uri[0] != '/': if relativeto is not None: v = self._uri_cache[key] = posixpath.join( posixpath.dirname(relativeto), uri) else: v = self._uri_cache[key] = '/' + uri else: v = self._uri_cache[key] = uri return v def filename_to_uri(self, filename): """Convert the given ``filename`` to a URI relative to this :class:`.TemplateCollection`.""" try: return self._uri_cache[filename] except KeyError: value = self._relativeize(filename) self._uri_cache[filename] = value return value def _relativeize(self, filename): """Return the portion of a filename that is 'relative' to the directories in this lookup. """ filename = posixpath.normpath(filename) for dir in self.directories: if filename[0:len(dir)] == dir: return filename[len(dir):] else: return None def _load(self, filename, uri): self._mutex.acquire() try: try: # try returning from collection one # more time in case concurrent thread already loaded return self._collection[uri] except KeyError: pass try: if self.modulename_callable is not None: module_filename = self.modulename_callable(filename, uri) else: module_filename = None self._collection[uri] = template = Template( uri=uri, filename=posixpath.normpath(filename), lookup=self, module_filename=module_filename, **self.template_args) return template except: # if compilation fails etc, ensure # template is removed from collection, # re-raise self._collection.pop(uri, None) raise finally: self._mutex.release() def _check(self, uri, template): if template.filename is None: return template try: template_stat = os.stat(template.filename) if template.module._modified_time < \ template_stat[stat.ST_MTIME]: self._collection.pop(uri, None) return self._load(template.filename, uri) else: return template except OSError: self._collection.pop(uri, None) raise exceptions.TemplateLookupException( "Cant locate template for uri %r" % uri) def put_string(self, uri, text): """Place a new :class:`.Template` object into this :class:`.TemplateLookup`, based on the given string of ``text``. """ self._collection[uri] = Template( text, lookup=self, uri=uri, **self.template_args) def put_template(self, uri, template): """Place a new :class:`.Template` object into this :class:`.TemplateLookup`, based on the given :class:`.Template` object. """ self._collection[uri] = template Mako-1.0.7/mako/exceptions.py0000664000175000017500000003024613131715725017170 0ustar classicclassic00000000000000# mako/exceptions.py # Copyright (C) 2006-2016 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php """exception classes""" import traceback import sys from mako import util, compat class MakoException(Exception): pass class RuntimeException(MakoException): pass def _format_filepos(lineno, pos, filename): if filename is None: return " at line: %d char: %d" % (lineno, pos) else: return " in file '%s' at line: %d char: %d" % (filename, lineno, pos) class CompileException(MakoException): def __init__(self, message, source, lineno, pos, filename): MakoException.__init__( self, message + _format_filepos(lineno, pos, filename)) self.lineno = lineno self.pos = pos self.filename = filename self.source = source class SyntaxException(MakoException): def __init__(self, message, source, lineno, pos, filename): MakoException.__init__( self, message + _format_filepos(lineno, pos, filename)) self.lineno = lineno self.pos = pos self.filename = filename self.source = source class UnsupportedError(MakoException): """raised when a retired feature is used.""" class NameConflictError(MakoException): """raised when a reserved word is used inappropriately""" class TemplateLookupException(MakoException): pass class TopLevelLookupException(TemplateLookupException): pass class RichTraceback(object): """Pull the current exception from the ``sys`` traceback and extracts Mako-specific template information. See the usage examples in :ref:`handling_exceptions`. """ def __init__(self, error=None, traceback=None): self.source, self.lineno = "", 0 if error is None or traceback is None: t, value, tback = sys.exc_info() if error is None: error = value or t if traceback is None: traceback = tback self.error = error self.records = self._init(traceback) if isinstance(self.error, (CompileException, SyntaxException)): self.source = self.error.source self.lineno = self.error.lineno self._has_source = True self._init_message() @property def errorname(self): return compat.exception_name(self.error) def _init_message(self): """Find a unicode representation of self.error""" try: self.message = compat.text_type(self.error) except UnicodeError: try: self.message = str(self.error) except UnicodeEncodeError: # Fallback to args as neither unicode nor # str(Exception(u'\xe6')) work in Python < 2.6 self.message = self.error.args[0] if not isinstance(self.message, compat.text_type): self.message = compat.text_type(self.message, 'ascii', 'replace') def _get_reformatted_records(self, records): for rec in records: if rec[6] is not None: yield (rec[4], rec[5], rec[2], rec[6]) else: yield tuple(rec[0:4]) @property def traceback(self): """Return a list of 4-tuple traceback records (i.e. normal python format) with template-corresponding lines remapped to the originating template. """ return list(self._get_reformatted_records(self.records)) @property def reverse_records(self): return reversed(self.records) @property def reverse_traceback(self): """Return the same data as traceback, except in reverse order. """ return list(self._get_reformatted_records(self.reverse_records)) def _init(self, trcback): """format a traceback from sys.exc_info() into 7-item tuples, containing the regular four traceback tuple items, plus the original template filename, the line number adjusted relative to the template source, and code line from that line number of the template.""" import mako.template mods = {} rawrecords = traceback.extract_tb(trcback) new_trcback = [] for filename, lineno, function, line in rawrecords: if not line: line = '' try: (line_map, template_lines) = mods[filename] except KeyError: try: info = mako.template._get_module_info(filename) module_source = info.code template_source = info.source template_filename = info.template_filename or filename except KeyError: # A normal .py file (not a Template) if not compat.py3k: try: fp = open(filename, 'rb') encoding = util.parse_encoding(fp) fp.close() except IOError: encoding = None if encoding: line = line.decode(encoding) else: line = line.decode('ascii', 'replace') new_trcback.append((filename, lineno, function, line, None, None, None, None)) continue template_ln = 1 source_map = mako.template.ModuleInfo.\ get_module_source_metadata( module_source, full_line_map=True) line_map = source_map['full_line_map'] template_lines = [line_ for line_ in template_source.split("\n")] mods[filename] = (line_map, template_lines) template_ln = line_map[lineno - 1] if template_ln <= len(template_lines): template_line = template_lines[template_ln - 1] else: template_line = None new_trcback.append((filename, lineno, function, line, template_filename, template_ln, template_line, template_source)) if not self.source: for l in range(len(new_trcback) - 1, 0, -1): if new_trcback[l][5]: self.source = new_trcback[l][7] self.lineno = new_trcback[l][5] break else: if new_trcback: try: # A normal .py file (not a Template) fp = open(new_trcback[-1][0], 'rb') encoding = util.parse_encoding(fp) fp.seek(0) self.source = fp.read() fp.close() if encoding: self.source = self.source.decode(encoding) except IOError: self.source = '' self.lineno = new_trcback[-1][1] return new_trcback def text_error_template(lookup=None): """Provides a template that renders a stack trace in a similar format to the Python interpreter, substituting source template filenames, line numbers and code for that of the originating source template, as applicable. """ import mako.template return mako.template.Template(r""" <%page args="error=None, traceback=None"/> <%! from mako.exceptions import RichTraceback %>\ <% tback = RichTraceback(error=error, traceback=traceback) %>\ Traceback (most recent call last): % for (filename, lineno, function, line) in tback.traceback: File "${filename}", line ${lineno}, in ${function or '?'} ${line | trim} % endfor ${tback.errorname}: ${tback.message} """) def _install_pygments(): global syntax_highlight, pygments_html_formatter from mako.ext.pygmentplugin import syntax_highlight # noqa from mako.ext.pygmentplugin import pygments_html_formatter # noqa def _install_fallback(): global syntax_highlight, pygments_html_formatter from mako.filters import html_escape pygments_html_formatter = None def syntax_highlight(filename='', language=None): return html_escape def _install_highlighting(): try: _install_pygments() except ImportError: _install_fallback() _install_highlighting() def html_error_template(): """Provides a template that renders a stack trace in an HTML format, providing an excerpt of code as well as substituting source template filenames, line numbers and code for that of the originating source template, as applicable. The template's default ``encoding_errors`` value is ``'htmlentityreplace'``. The template has two options. With the ``full`` option disabled, only a section of an HTML document is returned. With the ``css`` option disabled, the default stylesheet won't be included. """ import mako.template return mako.template.Template(r""" <%! from mako.exceptions import RichTraceback, syntax_highlight,\ pygments_html_formatter %> <%page args="full=True, css=True, error=None, traceback=None"/> % if full: Mako Runtime Error % endif % if css: % endif % if full: % endif

Error !

<% tback = RichTraceback(error=error, traceback=traceback) src = tback.source line = tback.lineno if src: lines = src.split('\n') else: lines = None %>

${tback.errorname}: ${tback.message|h}

% if lines:
% for index in range(max(0, line-4),min(len(lines), line+5)): <% if pygments_html_formatter: pygments_html_formatter.linenostart = index + 1 %> % if index + 1 == line: <% if pygments_html_formatter: old_cssclass = pygments_html_formatter.cssclass pygments_html_formatter.cssclass = 'error ' + old_cssclass %> ${lines[index] | syntax_highlight(language='mako')} <% if pygments_html_formatter: pygments_html_formatter.cssclass = old_cssclass %> % else: ${lines[index] | syntax_highlight(language='mako')} % endif % endfor
% endif
% for (filename, lineno, function, line) in tback.reverse_traceback:
${filename}, line ${lineno}:
<% if pygments_html_formatter: pygments_html_formatter.linenostart = lineno %>
${line | syntax_highlight(filename)}
% endfor
% if full: % endif """, output_encoding=sys.getdefaultencoding(), encoding_errors='htmlentityreplace') Mako-1.0.7/mako/compat.py0000664000175000017500000001154113131715725016267 0ustar classicclassic00000000000000import sys import time py3k = sys.version_info >= (3, 0) py33 = sys.version_info >= (3, 3) py2k = sys.version_info < (3,) py26 = sys.version_info >= (2, 6) py27 = sys.version_info >= (2, 7) jython = sys.platform.startswith('java') win32 = sys.platform.startswith('win') pypy = hasattr(sys, 'pypy_version_info') if py3k: # create a "getargspec" from getfullargspec(), which is not deprecated # in Py3K; getargspec() has started to emit warnings as of Py3.5. # As of Py3.4, now they are trying to move from getfullargspec() # to "signature()", but getfullargspec() is not deprecated, so stick # with that for now. import collections ArgSpec = collections.namedtuple( "ArgSpec", ["args", "varargs", "keywords", "defaults"]) from inspect import getfullargspec as inspect_getfullargspec def inspect_getargspec(func): return ArgSpec( *inspect_getfullargspec(func)[0:4] ) else: from inspect import getargspec as inspect_getargspec # noqa if py3k: from io import StringIO import builtins as compat_builtins from urllib.parse import quote_plus, unquote_plus from html.entities import codepoint2name, name2codepoint string_types = str, binary_type = bytes text_type = str from io import BytesIO as byte_buffer def u(s): return s def b(s): return s.encode("latin-1") def octal(lit): return eval("0o" + lit) else: import __builtin__ as compat_builtins # noqa try: from cStringIO import StringIO except: from StringIO import StringIO byte_buffer = StringIO from urllib import quote_plus, unquote_plus # noqa from htmlentitydefs import codepoint2name, name2codepoint # noqa string_types = basestring, # noqa binary_type = str text_type = unicode # noqa def u(s): return unicode(s, "utf-8") # noqa def b(s): return s def octal(lit): return eval("0" + lit) if py33: from importlib import machinery def load_module(module_id, path): return machinery.SourceFileLoader(module_id, path).load_module() else: import imp def load_module(module_id, path): fp = open(path, 'rb') try: return imp.load_source(module_id, path, fp) finally: fp.close() if py3k: def reraise(tp, value, tb=None, cause=None): if cause is not None: value.__cause__ = cause if value.__traceback__ is not tb: raise value.with_traceback(tb) raise value else: exec("def reraise(tp, value, tb=None, cause=None):\n" " raise tp, value, tb\n") def exception_as(): return sys.exc_info()[1] try: import threading if py3k: import _thread as thread else: import thread except ImportError: import dummy_threading as threading # noqa if py3k: import _dummy_thread as thread else: import dummy_thread as thread # noqa if win32 or jython: time_func = time.clock else: time_func = time.time try: from functools import partial except: def partial(func, *args, **keywords): def newfunc(*fargs, **fkeywords): newkeywords = keywords.copy() newkeywords.update(fkeywords) return func(*(args + fargs), **newkeywords) return newfunc all = all import json # noqa def exception_name(exc): return exc.__class__.__name__ try: from inspect import CO_VARKEYWORDS, CO_VARARGS def inspect_func_args(fn): if py3k: co = fn.__code__ else: co = fn.func_code nargs = co.co_argcount names = co.co_varnames args = list(names[:nargs]) varargs = None if co.co_flags & CO_VARARGS: varargs = co.co_varnames[nargs] nargs = nargs + 1 varkw = None if co.co_flags & CO_VARKEYWORDS: varkw = co.co_varnames[nargs] if py3k: return args, varargs, varkw, fn.__defaults__ else: return args, varargs, varkw, fn.func_defaults except ImportError: import inspect def inspect_func_args(fn): return inspect.getargspec(fn) if py3k: def callable(fn): return hasattr(fn, '__call__') else: callable = callable ################################################ # cross-compatible metaclass implementation # Copyright (c) 2010-2012 Benjamin Peterson def with_metaclass(meta, base=object): """Create a base class with a metaclass.""" return meta("%sBase" % meta.__name__, (base,), {}) ################################################ def arg_stringname(func_arg): """Gets the string name of a kwarg or vararg In Python3.4 a function's args are of _ast.arg type not _ast.name """ if hasattr(func_arg, 'arg'): return func_arg.arg else: return str(func_arg) Mako-1.0.7/mako/_ast_util.py0000664000175000017500000006213313131715725016772 0ustar classicclassic00000000000000# mako/_ast_util.py # Copyright (C) 2006-2016 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php """ ast ~~~ The `ast` module helps Python applications to process trees of the Python abstract syntax grammar. The abstract syntax itself might change with each Python release; this module helps to find out programmatically what the current grammar looks like and allows modifications of it. An abstract syntax tree can be generated by passing `ast.PyCF_ONLY_AST` as a flag to the `compile()` builtin function or by using the `parse()` function from this module. The result will be a tree of objects whose classes all inherit from `ast.AST`. A modified abstract syntax tree can be compiled into a Python code object using the built-in `compile()` function. Additionally various helper functions are provided that make working with the trees simpler. The main intention of the helper functions and this module in general is to provide an easy to use interface for libraries that work tightly with the python syntax (template engines for example). :copyright: Copyright 2008 by Armin Ronacher. :license: Python License. """ from _ast import * # noqa from mako.compat import arg_stringname BOOLOP_SYMBOLS = { And: 'and', Or: 'or' } BINOP_SYMBOLS = { Add: '+', Sub: '-', Mult: '*', Div: '/', FloorDiv: '//', Mod: '%', LShift: '<<', RShift: '>>', BitOr: '|', BitAnd: '&', BitXor: '^' } CMPOP_SYMBOLS = { Eq: '==', Gt: '>', GtE: '>=', In: 'in', Is: 'is', IsNot: 'is not', Lt: '<', LtE: '<=', NotEq: '!=', NotIn: 'not in' } UNARYOP_SYMBOLS = { Invert: '~', Not: 'not', UAdd: '+', USub: '-' } ALL_SYMBOLS = {} ALL_SYMBOLS.update(BOOLOP_SYMBOLS) ALL_SYMBOLS.update(BINOP_SYMBOLS) ALL_SYMBOLS.update(CMPOP_SYMBOLS) ALL_SYMBOLS.update(UNARYOP_SYMBOLS) def parse(expr, filename='', mode='exec'): """Parse an expression into an AST node.""" return compile(expr, filename, mode, PyCF_ONLY_AST) def to_source(node, indent_with=' ' * 4): """ This function can convert a node tree back into python sourcecode. This is useful for debugging purposes, especially if you're dealing with custom asts not generated by python itself. It could be that the sourcecode is evaluable when the AST itself is not compilable / evaluable. The reason for this is that the AST contains some more data than regular sourcecode does, which is dropped during conversion. Each level of indentation is replaced with `indent_with`. Per default this parameter is equal to four spaces as suggested by PEP 8, but it might be adjusted to match the application's styleguide. """ generator = SourceGenerator(indent_with) generator.visit(node) return ''.join(generator.result) def dump(node): """ A very verbose representation of the node passed. This is useful for debugging purposes. """ def _format(node): if isinstance(node, AST): return '%s(%s)' % (node.__class__.__name__, ', '.join('%s=%s' % (a, _format(b)) for a, b in iter_fields(node))) elif isinstance(node, list): return '[%s]' % ', '.join(_format(x) for x in node) return repr(node) if not isinstance(node, AST): raise TypeError('expected AST, got %r' % node.__class__.__name__) return _format(node) def copy_location(new_node, old_node): """ Copy the source location hint (`lineno` and `col_offset`) from the old to the new node if possible and return the new one. """ for attr in 'lineno', 'col_offset': if attr in old_node._attributes and attr in new_node._attributes \ and hasattr(old_node, attr): setattr(new_node, attr, getattr(old_node, attr)) return new_node def fix_missing_locations(node): """ Some nodes require a line number and the column offset. Without that information the compiler will abort the compilation. Because it can be a dull task to add appropriate line numbers and column offsets when adding new nodes this function can help. It copies the line number and column offset of the parent node to the child nodes without this information. Unlike `copy_location` this works recursive and won't touch nodes that already have a location information. """ def _fix(node, lineno, col_offset): if 'lineno' in node._attributes: if not hasattr(node, 'lineno'): node.lineno = lineno else: lineno = node.lineno if 'col_offset' in node._attributes: if not hasattr(node, 'col_offset'): node.col_offset = col_offset else: col_offset = node.col_offset for child in iter_child_nodes(node): _fix(child, lineno, col_offset) _fix(node, 1, 0) return node def increment_lineno(node, n=1): """ Increment the line numbers of all nodes by `n` if they have line number attributes. This is useful to "move code" to a different location in a file. """ for node in zip((node,), walk(node)): if 'lineno' in node._attributes: node.lineno = getattr(node, 'lineno', 0) + n def iter_fields(node): """Iterate over all fields of a node, only yielding existing fields.""" # CPython 2.5 compat if not hasattr(node, '_fields') or not node._fields: return for field in node._fields: try: yield field, getattr(node, field) except AttributeError: pass def get_fields(node): """Like `iter_fields` but returns a dict.""" return dict(iter_fields(node)) def iter_child_nodes(node): """Iterate over all child nodes or a node.""" for name, field in iter_fields(node): if isinstance(field, AST): yield field elif isinstance(field, list): for item in field: if isinstance(item, AST): yield item def get_child_nodes(node): """Like `iter_child_nodes` but returns a list.""" return list(iter_child_nodes(node)) def get_compile_mode(node): """ Get the mode for `compile` of a given node. If the node is not a `mod` node (`Expression`, `Module` etc.) a `TypeError` is thrown. """ if not isinstance(node, mod): raise TypeError('expected mod node, got %r' % node.__class__.__name__) return { Expression: 'eval', Interactive: 'single' }.get(node.__class__, 'expr') def get_docstring(node): """ Return the docstring for the given node or `None` if no docstring can be found. If the node provided does not accept docstrings a `TypeError` will be raised. """ if not isinstance(node, (FunctionDef, ClassDef, Module)): raise TypeError("%r can't have docstrings" % node.__class__.__name__) if node.body and isinstance(node.body[0], Str): return node.body[0].s def walk(node): """ Iterate over all nodes. This is useful if you only want to modify nodes in place and don't care about the context or the order the nodes are returned. """ from collections import deque todo = deque([node]) while todo: node = todo.popleft() todo.extend(iter_child_nodes(node)) yield node class NodeVisitor(object): """ Walks the abstract syntax tree and call visitor functions for every node found. The visitor functions may return values which will be forwarded by the `visit` method. Per default the visitor functions for the nodes are ``'visit_'`` + class name of the node. So a `TryFinally` node visit function would be `visit_TryFinally`. This behavior can be changed by overriding the `get_visitor` function. If no visitor function exists for a node (return value `None`) the `generic_visit` visitor is used instead. Don't use the `NodeVisitor` if you want to apply changes to nodes during traversing. For this a special visitor exists (`NodeTransformer`) that allows modifications. """ def get_visitor(self, node): """ Return the visitor function for this node or `None` if no visitor exists for this node. In that case the generic visit function is used instead. """ method = 'visit_' + node.__class__.__name__ return getattr(self, method, None) def visit(self, node): """Visit a node.""" f = self.get_visitor(node) if f is not None: return f(node) return self.generic_visit(node) def generic_visit(self, node): """Called if no explicit visitor function exists for a node.""" for field, value in iter_fields(node): if isinstance(value, list): for item in value: if isinstance(item, AST): self.visit(item) elif isinstance(value, AST): self.visit(value) class NodeTransformer(NodeVisitor): """ Walks the abstract syntax tree and allows modifications of nodes. The `NodeTransformer` will walk the AST and use the return value of the visitor functions to replace or remove the old node. If the return value of the visitor function is `None` the node will be removed from the previous location otherwise it's replaced with the return value. The return value may be the original node in which case no replacement takes place. Here an example transformer that rewrites all `foo` to `data['foo']`:: class RewriteName(NodeTransformer): def visit_Name(self, node): return copy_location(Subscript( value=Name(id='data', ctx=Load()), slice=Index(value=Str(s=node.id)), ctx=node.ctx ), node) Keep in mind that if the node you're operating on has child nodes you must either transform the child nodes yourself or call the generic visit function for the node first. Nodes that were part of a collection of statements (that applies to all statement nodes) may also return a list of nodes rather than just a single node. Usually you use the transformer like this:: node = YourTransformer().visit(node) """ def generic_visit(self, node): for field, old_value in iter_fields(node): old_value = getattr(node, field, None) if isinstance(old_value, list): new_values = [] for value in old_value: if isinstance(value, AST): value = self.visit(value) if value is None: continue elif not isinstance(value, AST): new_values.extend(value) continue new_values.append(value) old_value[:] = new_values elif isinstance(old_value, AST): new_node = self.visit(old_value) if new_node is None: delattr(node, field) else: setattr(node, field, new_node) return node class SourceGenerator(NodeVisitor): """ This visitor is able to transform a well formed syntax tree into python sourcecode. For more details have a look at the docstring of the `node_to_source` function. """ def __init__(self, indent_with): self.result = [] self.indent_with = indent_with self.indentation = 0 self.new_lines = 0 def write(self, x): if self.new_lines: if self.result: self.result.append('\n' * self.new_lines) self.result.append(self.indent_with * self.indentation) self.new_lines = 0 self.result.append(x) def newline(self, n=1): self.new_lines = max(self.new_lines, n) def body(self, statements): self.new_line = True self.indentation += 1 for stmt in statements: self.visit(stmt) self.indentation -= 1 def body_or_else(self, node): self.body(node.body) if node.orelse: self.newline() self.write('else:') self.body(node.orelse) def signature(self, node): want_comma = [] def write_comma(): if want_comma: self.write(', ') else: want_comma.append(True) padding = [None] * (len(node.args) - len(node.defaults)) for arg, default in zip(node.args, padding + node.defaults): write_comma() self.visit(arg) if default is not None: self.write('=') self.visit(default) if node.vararg is not None: write_comma() self.write('*' + arg_stringname(node.vararg)) if node.kwarg is not None: write_comma() self.write('**' + arg_stringname(node.kwarg)) def decorators(self, node): for decorator in node.decorator_list: self.newline() self.write('@') self.visit(decorator) # Statements def visit_Assign(self, node): self.newline() for idx, target in enumerate(node.targets): if idx: self.write(', ') self.visit(target) self.write(' = ') self.visit(node.value) def visit_AugAssign(self, node): self.newline() self.visit(node.target) self.write(BINOP_SYMBOLS[type(node.op)] + '=') self.visit(node.value) def visit_ImportFrom(self, node): self.newline() self.write('from %s%s import ' % ('.' * node.level, node.module)) for idx, item in enumerate(node.names): if idx: self.write(', ') self.write(item) def visit_Import(self, node): self.newline() for item in node.names: self.write('import ') self.visit(item) def visit_Expr(self, node): self.newline() self.generic_visit(node) def visit_FunctionDef(self, node): self.newline(n=2) self.decorators(node) self.newline() self.write('def %s(' % node.name) self.signature(node.args) self.write('):') self.body(node.body) def visit_ClassDef(self, node): have_args = [] def paren_or_comma(): if have_args: self.write(', ') else: have_args.append(True) self.write('(') self.newline(n=3) self.decorators(node) self.newline() self.write('class %s' % node.name) for base in node.bases: paren_or_comma() self.visit(base) # XXX: the if here is used to keep this module compatible # with python 2.6. if hasattr(node, 'keywords'): for keyword in node.keywords: paren_or_comma() self.write(keyword.arg + '=') self.visit(keyword.value) if getattr(node, "starargs", None): paren_or_comma() self.write('*') self.visit(node.starargs) if getattr(node, "kwargs", None): paren_or_comma() self.write('**') self.visit(node.kwargs) self.write(have_args and '):' or ':') self.body(node.body) def visit_If(self, node): self.newline() self.write('if ') self.visit(node.test) self.write(':') self.body(node.body) while True: else_ = node.orelse if len(else_) == 1 and isinstance(else_[0], If): node = else_[0] self.newline() self.write('elif ') self.visit(node.test) self.write(':') self.body(node.body) else: self.newline() self.write('else:') self.body(else_) break def visit_For(self, node): self.newline() self.write('for ') self.visit(node.target) self.write(' in ') self.visit(node.iter) self.write(':') self.body_or_else(node) def visit_While(self, node): self.newline() self.write('while ') self.visit(node.test) self.write(':') self.body_or_else(node) def visit_With(self, node): self.newline() self.write('with ') self.visit(node.context_expr) if node.optional_vars is not None: self.write(' as ') self.visit(node.optional_vars) self.write(':') self.body(node.body) def visit_Pass(self, node): self.newline() self.write('pass') def visit_Print(self, node): # XXX: python 2.6 only self.newline() self.write('print ') want_comma = False if node.dest is not None: self.write(' >> ') self.visit(node.dest) want_comma = True for value in node.values: if want_comma: self.write(', ') self.visit(value) want_comma = True if not node.nl: self.write(',') def visit_Delete(self, node): self.newline() self.write('del ') for idx, target in enumerate(node): if idx: self.write(', ') self.visit(target) def visit_TryExcept(self, node): self.newline() self.write('try:') self.body(node.body) for handler in node.handlers: self.visit(handler) def visit_TryFinally(self, node): self.newline() self.write('try:') self.body(node.body) self.newline() self.write('finally:') self.body(node.finalbody) def visit_Global(self, node): self.newline() self.write('global ' + ', '.join(node.names)) def visit_Nonlocal(self, node): self.newline() self.write('nonlocal ' + ', '.join(node.names)) def visit_Return(self, node): self.newline() self.write('return ') self.visit(node.value) def visit_Break(self, node): self.newline() self.write('break') def visit_Continue(self, node): self.newline() self.write('continue') def visit_Raise(self, node): # XXX: Python 2.6 / 3.0 compatibility self.newline() self.write('raise') if hasattr(node, 'exc') and node.exc is not None: self.write(' ') self.visit(node.exc) if node.cause is not None: self.write(' from ') self.visit(node.cause) elif hasattr(node, 'type') and node.type is not None: self.visit(node.type) if node.inst is not None: self.write(', ') self.visit(node.inst) if node.tback is not None: self.write(', ') self.visit(node.tback) # Expressions def visit_Attribute(self, node): self.visit(node.value) self.write('.' + node.attr) def visit_Call(self, node): want_comma = [] def write_comma(): if want_comma: self.write(', ') else: want_comma.append(True) self.visit(node.func) self.write('(') for arg in node.args: write_comma() self.visit(arg) for keyword in node.keywords: write_comma() self.write(keyword.arg + '=') self.visit(keyword.value) if getattr(node, "starargs", None): write_comma() self.write('*') self.visit(node.starargs) if getattr(node, "kwargs", None): write_comma() self.write('**') self.visit(node.kwargs) self.write(')') def visit_Name(self, node): self.write(node.id) def visit_NameConstant(self, node): self.write(str(node.value)) def visit_arg(self, node): self.write(node.arg) def visit_Str(self, node): self.write(repr(node.s)) def visit_Bytes(self, node): self.write(repr(node.s)) def visit_Num(self, node): self.write(repr(node.n)) def visit_Tuple(self, node): self.write('(') idx = -1 for idx, item in enumerate(node.elts): if idx: self.write(', ') self.visit(item) self.write(idx and ')' or ',)') def sequence_visit(left, right): def visit(self, node): self.write(left) for idx, item in enumerate(node.elts): if idx: self.write(', ') self.visit(item) self.write(right) return visit visit_List = sequence_visit('[', ']') visit_Set = sequence_visit('{', '}') del sequence_visit def visit_Dict(self, node): self.write('{') for idx, (key, value) in enumerate(zip(node.keys, node.values)): if idx: self.write(', ') self.visit(key) self.write(': ') self.visit(value) self.write('}') def visit_BinOp(self, node): self.write('(') self.visit(node.left) self.write(' %s ' % BINOP_SYMBOLS[type(node.op)]) self.visit(node.right) self.write(')') def visit_BoolOp(self, node): self.write('(') for idx, value in enumerate(node.values): if idx: self.write(' %s ' % BOOLOP_SYMBOLS[type(node.op)]) self.visit(value) self.write(')') def visit_Compare(self, node): self.write('(') self.visit(node.left) for op, right in zip(node.ops, node.comparators): self.write(' %s ' % CMPOP_SYMBOLS[type(op)]) self.visit(right) self.write(')') def visit_UnaryOp(self, node): self.write('(') op = UNARYOP_SYMBOLS[type(node.op)] self.write(op) if op == 'not': self.write(' ') self.visit(node.operand) self.write(')') def visit_Subscript(self, node): self.visit(node.value) self.write('[') self.visit(node.slice) self.write(']') def visit_Slice(self, node): if node.lower is not None: self.visit(node.lower) self.write(':') if node.upper is not None: self.visit(node.upper) if node.step is not None: self.write(':') if not (isinstance(node.step, Name) and node.step.id == 'None'): self.visit(node.step) def visit_ExtSlice(self, node): for idx, item in node.dims: if idx: self.write(', ') self.visit(item) def visit_Yield(self, node): self.write('yield ') self.visit(node.value) def visit_Lambda(self, node): self.write('lambda ') self.signature(node.args) self.write(': ') self.visit(node.body) def visit_Ellipsis(self, node): self.write('Ellipsis') def generator_visit(left, right): def visit(self, node): self.write(left) self.visit(node.elt) for comprehension in node.generators: self.visit(comprehension) self.write(right) return visit visit_ListComp = generator_visit('[', ']') visit_GeneratorExp = generator_visit('(', ')') visit_SetComp = generator_visit('{', '}') del generator_visit def visit_DictComp(self, node): self.write('{') self.visit(node.key) self.write(': ') self.visit(node.value) for comprehension in node.generators: self.visit(comprehension) self.write('}') def visit_IfExp(self, node): self.visit(node.body) self.write(' if ') self.visit(node.test) self.write(' else ') self.visit(node.orelse) def visit_Starred(self, node): self.write('*') self.visit(node.value) def visit_Repr(self, node): # XXX: python 2.6 only self.write('`') self.visit(node.value) self.write('`') # Helper Nodes def visit_alias(self, node): self.write(node.name) if node.asname is not None: self.write(' as ' + node.asname) def visit_comprehension(self, node): self.write(' for ') self.visit(node.target) self.write(' in ') self.visit(node.iter) if node.ifs: for if_ in node.ifs: self.write(' if ') self.visit(if_) def visit_excepthandler(self, node): self.newline() self.write('except') if node.type is not None: self.write(' ') self.visit(node.type) if node.name is not None: self.write(' as ') self.visit(node.name) self.write(':') self.body(node.body) Mako-1.0.7/mako/lexer.py0000664000175000017500000004015713131715725016130 0ustar classicclassic00000000000000# mako/lexer.py # Copyright (C) 2006-2016 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php """provides the Lexer class for parsing template strings into parse trees.""" import re import codecs from mako import parsetree, exceptions, compat from mako.pygen import adjust_whitespace _regexp_cache = {} class Lexer(object): def __init__(self, text, filename=None, disable_unicode=False, input_encoding=None, preprocessor=None): self.text = text self.filename = filename self.template = parsetree.TemplateNode(self.filename) self.matched_lineno = 1 self.matched_charpos = 0 self.lineno = 1 self.match_position = 0 self.tag = [] self.control_line = [] self.ternary_stack = [] self.disable_unicode = disable_unicode self.encoding = input_encoding if compat.py3k and disable_unicode: raise exceptions.UnsupportedError( "Mako for Python 3 does not " "support disabling Unicode") if preprocessor is None: self.preprocessor = [] elif not hasattr(preprocessor, '__iter__'): self.preprocessor = [preprocessor] else: self.preprocessor = preprocessor @property def exception_kwargs(self): return {'source': self.text, 'lineno': self.matched_lineno, 'pos': self.matched_charpos, 'filename': self.filename} def match(self, regexp, flags=None): """compile the given regexp, cache the reg, and call match_reg().""" try: reg = _regexp_cache[(regexp, flags)] except KeyError: if flags: reg = re.compile(regexp, flags) else: reg = re.compile(regexp) _regexp_cache[(regexp, flags)] = reg return self.match_reg(reg) def match_reg(self, reg): """match the given regular expression object to the current text position. if a match occurs, update the current text and line position. """ mp = self.match_position match = reg.match(self.text, self.match_position) if match: (start, end) = match.span() if end == start: self.match_position = end + 1 else: self.match_position = end self.matched_lineno = self.lineno lines = re.findall(r"\n", self.text[mp:self.match_position]) cp = mp - 1 while (cp >= 0 and cp < self.textlength and self.text[cp] != '\n'): cp -= 1 self.matched_charpos = mp - cp self.lineno += len(lines) # print "MATCHED:", match.group(0), "LINE START:", # self.matched_lineno, "LINE END:", self.lineno # print "MATCH:", regexp, "\n", self.text[mp : mp + 15], \ # (match and "TRUE" or "FALSE") return match def parse_until_text(self, watch_nesting, *text): startpos = self.match_position text_re = r'|'.join(text) brace_level = 0 paren_level = 0 bracket_level = 0 while True: match = self.match(r'#.*\n') if match: continue match = self.match(r'(\"\"\"|\'\'\'|\"|\')[^\\]*?(\\.[^\\]*?)*\1', re.S) if match: continue match = self.match(r'(%s)' % text_re) if match and not (watch_nesting and (brace_level > 0 or paren_level > 0 or bracket_level > 0)): return \ self.text[startpos: self.match_position - len(match.group(1))],\ match.group(1) elif not match: match = self.match(r"(.*?)(?=\"|\'|#|%s)" % text_re, re.S) if match: brace_level += match.group(1).count('{') brace_level -= match.group(1).count('}') paren_level += match.group(1).count('(') paren_level -= match.group(1).count(')') bracket_level += match.group(1).count('[') bracket_level -= match.group(1).count(']') continue raise exceptions.SyntaxException( "Expected: %s" % ','.join(text), **self.exception_kwargs) def append_node(self, nodecls, *args, **kwargs): kwargs.setdefault('source', self.text) kwargs.setdefault('lineno', self.matched_lineno) kwargs.setdefault('pos', self.matched_charpos) kwargs['filename'] = self.filename node = nodecls(*args, **kwargs) if len(self.tag): self.tag[-1].nodes.append(node) else: self.template.nodes.append(node) # build a set of child nodes for the control line # (used for loop variable detection) # also build a set of child nodes on ternary control lines # (used for determining if a pass needs to be auto-inserted if self.control_line: control_frame = self.control_line[-1] control_frame.nodes.append(node) if not (isinstance(node, parsetree.ControlLine) and control_frame.is_ternary(node.keyword)): if self.ternary_stack and self.ternary_stack[-1]: self.ternary_stack[-1][-1].nodes.append(node) if isinstance(node, parsetree.Tag): if len(self.tag): node.parent = self.tag[-1] self.tag.append(node) elif isinstance(node, parsetree.ControlLine): if node.isend: self.control_line.pop() self.ternary_stack.pop() elif node.is_primary: self.control_line.append(node) self.ternary_stack.append([]) elif self.control_line and \ self.control_line[-1].is_ternary(node.keyword): self.ternary_stack[-1].append(node) elif self.control_line and \ not self.control_line[-1].is_ternary(node.keyword): raise exceptions.SyntaxException( "Keyword '%s' not a legal ternary for keyword '%s'" % (node.keyword, self.control_line[-1].keyword), **self.exception_kwargs) _coding_re = re.compile(r'#.*coding[:=]\s*([-\w.]+).*\r?\n') def decode_raw_stream(self, text, decode_raw, known_encoding, filename): """given string/unicode or bytes/string, determine encoding from magic encoding comment, return body as unicode or raw if decode_raw=False """ if isinstance(text, compat.text_type): m = self._coding_re.match(text) encoding = m and m.group(1) or known_encoding or 'ascii' return encoding, text if text.startswith(codecs.BOM_UTF8): text = text[len(codecs.BOM_UTF8):] parsed_encoding = 'utf-8' m = self._coding_re.match(text.decode('utf-8', 'ignore')) if m is not None and m.group(1) != 'utf-8': raise exceptions.CompileException( "Found utf-8 BOM in file, with conflicting " "magic encoding comment of '%s'" % m.group(1), text.decode('utf-8', 'ignore'), 0, 0, filename) else: m = self._coding_re.match(text.decode('utf-8', 'ignore')) if m: parsed_encoding = m.group(1) else: parsed_encoding = known_encoding or 'ascii' if decode_raw: try: text = text.decode(parsed_encoding) except UnicodeDecodeError: raise exceptions.CompileException( "Unicode decode operation of encoding '%s' failed" % parsed_encoding, text.decode('utf-8', 'ignore'), 0, 0, filename) return parsed_encoding, text def parse(self): self.encoding, self.text = self.decode_raw_stream( self.text, not self.disable_unicode, self.encoding, self.filename) for preproc in self.preprocessor: self.text = preproc(self.text) # push the match marker past the # encoding comment. self.match_reg(self._coding_re) self.textlength = len(self.text) while (True): if self.match_position > self.textlength: break if self.match_end(): break if self.match_expression(): continue if self.match_control_line(): continue if self.match_comment(): continue if self.match_tag_start(): continue if self.match_tag_end(): continue if self.match_python_block(): continue if self.match_text(): continue if self.match_position > self.textlength: break raise exceptions.CompileException("assertion failed") if len(self.tag): raise exceptions.SyntaxException("Unclosed tag: <%%%s>" % self.tag[-1].keyword, **self.exception_kwargs) if len(self.control_line): raise exceptions.SyntaxException( "Unterminated control keyword: '%s'" % self.control_line[-1].keyword, self.text, self.control_line[-1].lineno, self.control_line[-1].pos, self.filename) return self.template def match_tag_start(self): match = self.match(r''' \<% # opening tag ([\w\.\:]+) # keyword ((?:\s+\w+|\s*=\s*|".*?"|'.*?')*) # attrname, = \ # sign, string expression \s* # more whitespace (/)?> # closing ''', re.I | re.S | re.X) if match: keyword, attr, isend = match.groups() self.keyword = keyword attributes = {} if attr: for att in re.findall( r"\s*(\w+)\s*=\s*(?:'([^']*)'|\"([^\"]*)\")", attr): key, val1, val2 = att text = val1 or val2 text = text.replace('\r\n', '\n') attributes[key] = text self.append_node(parsetree.Tag, keyword, attributes) if isend: self.tag.pop() else: if keyword == 'text': match = self.match(r'(.*?)(?=\)', re.S) if not match: raise exceptions.SyntaxException( "Unclosed tag: <%%%s>" % self.tag[-1].keyword, **self.exception_kwargs) self.append_node(parsetree.Text, match.group(1)) return self.match_tag_end() return True else: return False def match_tag_end(self): match = self.match(r'\') if match: if not len(self.tag): raise exceptions.SyntaxException( "Closing tag without opening tag: " % match.group(1), **self.exception_kwargs) elif self.tag[-1].keyword != match.group(1): raise exceptions.SyntaxException( "Closing tag does not match tag: <%%%s>" % (match.group(1), self.tag[-1].keyword), **self.exception_kwargs) self.tag.pop() return True else: return False def match_end(self): match = self.match(r'\Z', re.S) if match: string = match.group() if string: return string else: return True else: return False def match_text(self): match = self.match(r""" (.*?) # anything, followed by: ( (?<=\n)(?=[ \t]*(?=%|\#\#)) # an eval or line-based # comment preceded by a # consumed newline and whitespace | (?=\${) # an expression | (?=') # the trailing newline helps # compiler.parse() not complain about indentation text = adjust_whitespace(text) + "\n" self.append_node( parsetree.Code, text, match.group(1) == '!', lineno=line, pos=pos) return True else: return False def match_expression(self): match = self.match(r"\${") if match: line, pos = self.matched_lineno, self.matched_charpos text, end = self.parse_until_text(True, r'\|', r'}') if end == '|': escapes, end = self.parse_until_text(True, r'}') else: escapes = "" text = text.replace('\r\n', '\n') self.append_node( parsetree.Expression, text, escapes.strip(), lineno=line, pos=pos) return True else: return False def match_control_line(self): match = self.match( r"(?<=^)[\t ]*(%(?!%)|##)[\t ]*((?:(?:\\r?\n)|[^\r\n])*)" r"(?:\r?\n|\Z)", re.M) if match: operator = match.group(1) text = match.group(2) if operator == '%': m2 = re.match(r'(end)?(\w+)\s*(.*)', text) if not m2: raise exceptions.SyntaxException( "Invalid control line: '%s'" % text, **self.exception_kwargs) isend, keyword = m2.group(1, 2) isend = (isend is not None) if isend: if not len(self.control_line): raise exceptions.SyntaxException( "No starting keyword '%s' for '%s'" % (keyword, text), **self.exception_kwargs) elif self.control_line[-1].keyword != keyword: raise exceptions.SyntaxException( "Keyword '%s' doesn't match keyword '%s'" % (text, self.control_line[-1].keyword), **self.exception_kwargs) self.append_node(parsetree.ControlLine, keyword, isend, text) else: self.append_node(parsetree.Comment, text) return True else: return False def match_comment(self): """matches the multiline version of a comment""" match = self.match(r"<%doc>(.*?)", re.S) if match: self.append_node(parsetree.Comment, match.group(1)) return True else: return False Mako-1.0.7/mako/template.py0000664000175000017500000006532313131715725016626 0ustar classicclassic00000000000000# mako/template.py # Copyright (C) 2006-2016 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php """Provides the Template class, a facade for parsing, generating and executing template strings, as well as template runtime operations.""" from mako.lexer import Lexer from mako import runtime, util, exceptions, codegen, cache, compat import os import re import shutil import stat import sys import tempfile import types import weakref class Template(object): r"""Represents a compiled template. :class:`.Template` includes a reference to the original template source (via the :attr:`.source` attribute) as well as the source code of the generated Python module (i.e. the :attr:`.code` attribute), as well as a reference to an actual Python module. :class:`.Template` is constructed using either a literal string representing the template text, or a filename representing a filesystem path to a source file. :param text: textual template source. This argument is mutually exclusive versus the ``filename`` parameter. :param filename: filename of the source template. This argument is mutually exclusive versus the ``text`` parameter. :param buffer_filters: string list of filters to be applied to the output of ``%def``\ s which are buffered, cached, or otherwise filtered, after all filters defined with the ``%def`` itself have been applied. Allows the creation of default expression filters that let the output of return-valued ``%def``\ s "opt out" of that filtering via passing special attributes or objects. :param bytestring_passthrough: When ``True``, and ``output_encoding`` is set to ``None``, and :meth:`.Template.render` is used to render, the `StringIO` or `cStringIO` buffer will be used instead of the default "fast" buffer. This allows raw bytestrings in the output stream, such as in expressions, to pass straight through to the buffer. This flag is forced to ``True`` if ``disable_unicode`` is also configured. .. versionadded:: 0.4 Added to provide the same behavior as that of the previous series. :param cache_args: Dictionary of cache configuration arguments that will be passed to the :class:`.CacheImpl`. See :ref:`caching_toplevel`. :param cache_dir: .. deprecated:: 0.6 Use the ``'dir'`` argument in the ``cache_args`` dictionary. See :ref:`caching_toplevel`. :param cache_enabled: Boolean flag which enables caching of this template. See :ref:`caching_toplevel`. :param cache_impl: String name of a :class:`.CacheImpl` caching implementation to use. Defaults to ``'beaker'``. :param cache_type: .. deprecated:: 0.6 Use the ``'type'`` argument in the ``cache_args`` dictionary. See :ref:`caching_toplevel`. :param cache_url: .. deprecated:: 0.6 Use the ``'url'`` argument in the ``cache_args`` dictionary. See :ref:`caching_toplevel`. :param default_filters: List of string filter names that will be applied to all expressions. See :ref:`filtering_default_filters`. :param disable_unicode: Disables all awareness of Python Unicode objects. See :ref:`unicode_disabled`. :param enable_loop: When ``True``, enable the ``loop`` context variable. This can be set to ``False`` to support templates that may be making usage of the name "``loop``". Individual templates can re-enable the "loop" context by placing the directive ``enable_loop="True"`` inside the ``<%page>`` tag -- see :ref:`migrating_loop`. :param encoding_errors: Error parameter passed to ``encode()`` when string encoding is performed. See :ref:`usage_unicode`. :param error_handler: Python callable which is called whenever compile or runtime exceptions occur. The callable is passed the current context as well as the exception. If the callable returns ``True``, the exception is considered to be handled, else it is re-raised after the function completes. Is used to provide custom error-rendering functions. .. seealso:: :paramref:`.Template.include_error_handler` - include-specific error handler function :param format_exceptions: if ``True``, exceptions which occur during the render phase of this template will be caught and formatted into an HTML error page, which then becomes the rendered result of the :meth:`.render` call. Otherwise, runtime exceptions are propagated outwards. :param imports: String list of Python statements, typically individual "import" lines, which will be placed into the module level preamble of all generated Python modules. See the example in :ref:`filtering_default_filters`. :param future_imports: String list of names to import from `__future__`. These will be concatenated into a comma-separated string and inserted into the beginning of the template, e.g. ``futures_imports=['FOO', 'BAR']`` results in ``from __future__ import FOO, BAR``. If you're interested in using features like the new division operator, you must use future_imports to convey that to the renderer, as otherwise the import will not appear as the first executed statement in the generated code and will therefore not have the desired effect. :param include_error_handler: An error handler that runs when this template is included within another one via the ``<%include>`` tag, and raises an error. Compare to the :paramref:`.Template.error_handler` option. .. versionadded:: 1.0.6 .. seealso:: :paramref:`.Template.error_handler` - top-level error handler function :param input_encoding: Encoding of the template's source code. Can be used in lieu of the coding comment. See :ref:`usage_unicode` as well as :ref:`unicode_toplevel` for details on source encoding. :param lookup: a :class:`.TemplateLookup` instance that will be used for all file lookups via the ``<%namespace>``, ``<%include>``, and ``<%inherit>`` tags. See :ref:`usage_templatelookup`. :param module_directory: Filesystem location where generated Python module files will be placed. :param module_filename: Overrides the filename of the generated Python module file. For advanced usage only. :param module_writer: A callable which overrides how the Python module is written entirely. The callable is passed the encoded source content of the module and the destination path to be written to. The default behavior of module writing uses a tempfile in conjunction with a file move in order to make the operation atomic. So a user-defined module writing function that mimics the default behavior would be: .. sourcecode:: python import tempfile import os import shutil def module_writer(source, outputpath): (dest, name) = \\ tempfile.mkstemp( dir=os.path.dirname(outputpath) ) os.write(dest, source) os.close(dest) shutil.move(name, outputpath) from mako.template import Template mytemplate = Template( filename="index.html", module_directory="/path/to/modules", module_writer=module_writer ) The function is provided for unusual configurations where certain platform-specific permissions or other special steps are needed. :param output_encoding: The encoding to use when :meth:`.render` is called. See :ref:`usage_unicode` as well as :ref:`unicode_toplevel`. :param preprocessor: Python callable which will be passed the full template source before it is parsed. The return result of the callable will be used as the template source code. :param lexer_cls: A :class:`.Lexer` class used to parse the template. The :class:`.Lexer` class is used by default. .. versionadded:: 0.7.4 :param strict_undefined: Replaces the automatic usage of ``UNDEFINED`` for any undeclared variables not located in the :class:`.Context` with an immediate raise of ``NameError``. The advantage is immediate reporting of missing variables which include the name. .. versionadded:: 0.3.6 :param uri: string URI or other identifier for this template. If not provided, the ``uri`` is generated from the filesystem path, or from the in-memory identity of a non-file-based template. The primary usage of the ``uri`` is to provide a key within :class:`.TemplateLookup`, as well as to generate the file path of the generated Python module file, if ``module_directory`` is specified. """ lexer_cls = Lexer def __init__(self, text=None, filename=None, uri=None, format_exceptions=False, error_handler=None, lookup=None, output_encoding=None, encoding_errors='strict', module_directory=None, cache_args=None, cache_impl='beaker', cache_enabled=True, cache_type=None, cache_dir=None, cache_url=None, module_filename=None, input_encoding=None, disable_unicode=False, module_writer=None, bytestring_passthrough=False, default_filters=None, buffer_filters=(), strict_undefined=False, imports=None, future_imports=None, enable_loop=True, preprocessor=None, lexer_cls=None, include_error_handler=None): if uri: self.module_id = re.sub(r'\W', "_", uri) self.uri = uri elif filename: self.module_id = re.sub(r'\W', "_", filename) drive, path = os.path.splitdrive(filename) path = os.path.normpath(path).replace(os.path.sep, "/") self.uri = path else: self.module_id = "memory:" + hex(id(self)) self.uri = self.module_id u_norm = self.uri if u_norm.startswith("/"): u_norm = u_norm[1:] u_norm = os.path.normpath(u_norm) if u_norm.startswith(".."): raise exceptions.TemplateLookupException( "Template uri \"%s\" is invalid - " "it cannot be relative outside " "of the root path." % self.uri) self.input_encoding = input_encoding self.output_encoding = output_encoding self.encoding_errors = encoding_errors self.disable_unicode = disable_unicode self.bytestring_passthrough = bytestring_passthrough or disable_unicode self.enable_loop = enable_loop self.strict_undefined = strict_undefined self.module_writer = module_writer if compat.py3k and disable_unicode: raise exceptions.UnsupportedError( "Mako for Python 3 does not " "support disabling Unicode") elif output_encoding and disable_unicode: raise exceptions.UnsupportedError( "output_encoding must be set to " "None when disable_unicode is used.") if default_filters is None: if compat.py3k or self.disable_unicode: self.default_filters = ['str'] else: self.default_filters = ['unicode'] else: self.default_filters = default_filters self.buffer_filters = buffer_filters self.imports = imports self.future_imports = future_imports self.preprocessor = preprocessor if lexer_cls is not None: self.lexer_cls = lexer_cls # if plain text, compile code in memory only if text is not None: (code, module) = _compile_text(self, text, filename) self._code = code self._source = text ModuleInfo(module, None, self, filename, code, text) elif filename is not None: # if template filename and a module directory, load # a filesystem-based module file, generating if needed if module_filename is not None: path = module_filename elif module_directory is not None: path = os.path.abspath( os.path.join( os.path.normpath(module_directory), u_norm + ".py" ) ) else: path = None module = self._compile_from_file(path, filename) else: raise exceptions.RuntimeException( "Template requires text or filename") self.module = module self.filename = filename self.callable_ = self.module.render_body self.format_exceptions = format_exceptions self.error_handler = error_handler self.include_error_handler = include_error_handler self.lookup = lookup self.module_directory = module_directory self._setup_cache_args( cache_impl, cache_enabled, cache_args, cache_type, cache_dir, cache_url ) @util.memoized_property def reserved_names(self): if self.enable_loop: return codegen.RESERVED_NAMES else: return codegen.RESERVED_NAMES.difference(['loop']) def _setup_cache_args(self, cache_impl, cache_enabled, cache_args, cache_type, cache_dir, cache_url): self.cache_impl = cache_impl self.cache_enabled = cache_enabled if cache_args: self.cache_args = cache_args else: self.cache_args = {} # transfer deprecated cache_* args if cache_type: self.cache_args['type'] = cache_type if cache_dir: self.cache_args['dir'] = cache_dir if cache_url: self.cache_args['url'] = cache_url def _compile_from_file(self, path, filename): if path is not None: util.verify_directory(os.path.dirname(path)) filemtime = os.stat(filename)[stat.ST_MTIME] if not os.path.exists(path) or \ os.stat(path)[stat.ST_MTIME] < filemtime: data = util.read_file(filename) _compile_module_file( self, data, filename, path, self.module_writer) module = compat.load_module(self.module_id, path) del sys.modules[self.module_id] if module._magic_number != codegen.MAGIC_NUMBER: data = util.read_file(filename) _compile_module_file( self, data, filename, path, self.module_writer) module = compat.load_module(self.module_id, path) del sys.modules[self.module_id] ModuleInfo(module, path, self, filename, None, None) else: # template filename and no module directory, compile code # in memory data = util.read_file(filename) code, module = _compile_text( self, data, filename) self._source = None self._code = code ModuleInfo(module, None, self, filename, code, None) return module @property def source(self): """Return the template source code for this :class:`.Template`.""" return _get_module_info_from_callable(self.callable_).source @property def code(self): """Return the module source code for this :class:`.Template`.""" return _get_module_info_from_callable(self.callable_).code @util.memoized_property def cache(self): return cache.Cache(self) @property def cache_dir(self): return self.cache_args['dir'] @property def cache_url(self): return self.cache_args['url'] @property def cache_type(self): return self.cache_args['type'] def render(self, *args, **data): """Render the output of this template as a string. If the template specifies an output encoding, the string will be encoded accordingly, else the output is raw (raw output uses `cStringIO` and can't handle multibyte characters). A :class:`.Context` object is created corresponding to the given data. Arguments that are explicitly declared by this template's internal rendering method are also pulled from the given ``*args``, ``**data`` members. """ return runtime._render(self, self.callable_, args, data) def render_unicode(self, *args, **data): """Render the output of this template as a unicode object.""" return runtime._render(self, self.callable_, args, data, as_unicode=True) def render_context(self, context, *args, **kwargs): """Render this :class:`.Template` with the given context. The data is written to the context's buffer. """ if getattr(context, '_with_template', None) is None: context._set_with_template(self) runtime._render_context(self, self.callable_, context, *args, **kwargs) def has_def(self, name): return hasattr(self.module, "render_%s" % name) def get_def(self, name): """Return a def of this template as a :class:`.DefTemplate`.""" return DefTemplate(self, getattr(self.module, "render_%s" % name)) def list_defs(self): """return a list of defs in the template. .. versionadded:: 1.0.4 """ return [i[7:] for i in dir(self.module) if i[:7] == 'render_'] def _get_def_callable(self, name): return getattr(self.module, "render_%s" % name) @property def last_modified(self): return self.module._modified_time class ModuleTemplate(Template): """A Template which is constructed given an existing Python module. e.g.:: t = Template("this is a template") f = file("mymodule.py", "w") f.write(t.code) f.close() import mymodule t = ModuleTemplate(mymodule) print t.render() """ def __init__(self, module, module_filename=None, template=None, template_filename=None, module_source=None, template_source=None, output_encoding=None, encoding_errors='strict', disable_unicode=False, bytestring_passthrough=False, format_exceptions=False, error_handler=None, lookup=None, cache_args=None, cache_impl='beaker', cache_enabled=True, cache_type=None, cache_dir=None, cache_url=None, include_error_handler=None, ): self.module_id = re.sub(r'\W', "_", module._template_uri) self.uri = module._template_uri self.input_encoding = module._source_encoding self.output_encoding = output_encoding self.encoding_errors = encoding_errors self.disable_unicode = disable_unicode self.bytestring_passthrough = bytestring_passthrough or disable_unicode self.enable_loop = module._enable_loop if compat.py3k and disable_unicode: raise exceptions.UnsupportedError( "Mako for Python 3 does not " "support disabling Unicode") elif output_encoding and disable_unicode: raise exceptions.UnsupportedError( "output_encoding must be set to " "None when disable_unicode is used.") self.module = module self.filename = template_filename ModuleInfo(module, module_filename, self, template_filename, module_source, template_source) self.callable_ = self.module.render_body self.format_exceptions = format_exceptions self.error_handler = error_handler self.include_error_handler = include_error_handler self.lookup = lookup self._setup_cache_args( cache_impl, cache_enabled, cache_args, cache_type, cache_dir, cache_url ) class DefTemplate(Template): """A :class:`.Template` which represents a callable def in a parent template.""" def __init__(self, parent, callable_): self.parent = parent self.callable_ = callable_ self.output_encoding = parent.output_encoding self.module = parent.module self.encoding_errors = parent.encoding_errors self.format_exceptions = parent.format_exceptions self.error_handler = parent.error_handler self.include_error_handler = parent.include_error_handler self.enable_loop = parent.enable_loop self.lookup = parent.lookup self.bytestring_passthrough = parent.bytestring_passthrough def get_def(self, name): return self.parent.get_def(name) class ModuleInfo(object): """Stores information about a module currently loaded into memory, provides reverse lookups of template source, module source code based on a module's identifier. """ _modules = weakref.WeakValueDictionary() def __init__(self, module, module_filename, template, template_filename, module_source, template_source): self.module = module self.module_filename = module_filename self.template_filename = template_filename self.module_source = module_source self.template_source = template_source self._modules[module.__name__] = template._mmarker = self if module_filename: self._modules[module_filename] = self @classmethod def get_module_source_metadata(cls, module_source, full_line_map=False): source_map = re.search( r"__M_BEGIN_METADATA(.+?)__M_END_METADATA", module_source, re.S).group(1) source_map = compat.json.loads(source_map) source_map['line_map'] = dict( (int(k), int(v)) for k, v in source_map['line_map'].items()) if full_line_map: f_line_map = source_map['full_line_map'] = [] line_map = source_map['line_map'] curr_templ_line = 1 for mod_line in range(1, max(line_map)): if mod_line in line_map: curr_templ_line = line_map[mod_line] f_line_map.append(curr_templ_line) return source_map @property def code(self): if self.module_source is not None: return self.module_source else: return util.read_python_file(self.module_filename) @property def source(self): if self.template_source is not None: if self.module._source_encoding and \ not isinstance(self.template_source, compat.text_type): return self.template_source.decode( self.module._source_encoding) else: return self.template_source else: data = util.read_file(self.template_filename) if self.module._source_encoding: return data.decode(self.module._source_encoding) else: return data def _compile(template, text, filename, generate_magic_comment): lexer = template.lexer_cls(text, filename, disable_unicode=template.disable_unicode, input_encoding=template.input_encoding, preprocessor=template.preprocessor) node = lexer.parse() source = codegen.compile(node, template.uri, filename, default_filters=template.default_filters, buffer_filters=template.buffer_filters, imports=template.imports, future_imports=template.future_imports, source_encoding=lexer.encoding, generate_magic_comment=generate_magic_comment, disable_unicode=template.disable_unicode, strict_undefined=template.strict_undefined, enable_loop=template.enable_loop, reserved_names=template.reserved_names) return source, lexer def _compile_text(template, text, filename): identifier = template.module_id source, lexer = _compile(template, text, filename, generate_magic_comment=template.disable_unicode) cid = identifier if not compat.py3k and isinstance(cid, compat.text_type): cid = cid.encode() module = types.ModuleType(cid) code = compile(source, cid, 'exec') # this exec() works for 2.4->3.3. exec(code, module.__dict__, module.__dict__) return (source, module) def _compile_module_file(template, text, filename, outputpath, module_writer): source, lexer = _compile(template, text, filename, generate_magic_comment=True) if isinstance(source, compat.text_type): source = source.encode(lexer.encoding or 'ascii') if module_writer: module_writer(source, outputpath) else: # make tempfiles in the same location as the ultimate # location. this ensures they're on the same filesystem, # avoiding synchronization issues. (dest, name) = tempfile.mkstemp(dir=os.path.dirname(outputpath)) os.write(dest, source) os.close(dest) shutil.move(name, outputpath) def _get_module_info_from_callable(callable_): if compat.py3k: return _get_module_info(callable_.__globals__['__name__']) else: return _get_module_info(callable_.func_globals['__name__']) def _get_module_info(filename): return ModuleInfo._modules[filename] Mako-1.0.7/mako/cache.py0000664000175000017500000001707313131715725016055 0ustar classicclassic00000000000000# mako/cache.py # Copyright (C) 2006-2016 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php from mako import compat, util _cache_plugins = util.PluginLoader("mako.cache") register_plugin = _cache_plugins.register register_plugin("beaker", "mako.ext.beaker_cache", "BeakerCacheImpl") class Cache(object): """Represents a data content cache made available to the module space of a specific :class:`.Template` object. .. versionadded:: 0.6 :class:`.Cache` by itself is mostly a container for a :class:`.CacheImpl` object, which implements a fixed API to provide caching services; specific subclasses exist to implement different caching strategies. Mako includes a backend that works with the Beaker caching system. Beaker itself then supports a number of backends (i.e. file, memory, memcached, etc.) The construction of a :class:`.Cache` is part of the mechanics of a :class:`.Template`, and programmatic access to this cache is typically via the :attr:`.Template.cache` attribute. """ impl = None """Provide the :class:`.CacheImpl` in use by this :class:`.Cache`. This accessor allows a :class:`.CacheImpl` with additional methods beyond that of :class:`.Cache` to be used programmatically. """ id = None """Return the 'id' that identifies this cache. This is a value that should be globally unique to the :class:`.Template` associated with this cache, and can be used by a caching system to name a local container for data specific to this template. """ starttime = None """Epochal time value for when the owning :class:`.Template` was first compiled. A cache implementation may wish to invalidate data earlier than this timestamp; this has the effect of the cache for a specific :class:`.Template` starting clean any time the :class:`.Template` is recompiled, such as when the original template file changed on the filesystem. """ def __init__(self, template, *args): # check for a stale template calling the # constructor if isinstance(template, compat.string_types) and args: return self.template = template self.id = template.module.__name__ self.starttime = template.module._modified_time self._def_regions = {} self.impl = self._load_impl(self.template.cache_impl) def _load_impl(self, name): return _cache_plugins.load(name)(self) def get_or_create(self, key, creation_function, **kw): """Retrieve a value from the cache, using the given creation function to generate a new value.""" return self._ctx_get_or_create(key, creation_function, None, **kw) def _ctx_get_or_create(self, key, creation_function, context, **kw): """Retrieve a value from the cache, using the given creation function to generate a new value.""" if not self.template.cache_enabled: return creation_function() return self.impl.get_or_create( key, creation_function, **self._get_cache_kw(kw, context)) def set(self, key, value, **kw): r"""Place a value in the cache. :param key: the value's key. :param value: the value. :param \**kw: cache configuration arguments. """ self.impl.set(key, value, **self._get_cache_kw(kw, None)) put = set """A synonym for :meth:`.Cache.set`. This is here for backwards compatibility. """ def get(self, key, **kw): r"""Retrieve a value from the cache. :param key: the value's key. :param \**kw: cache configuration arguments. The backend is configured using these arguments upon first request. Subsequent requests that use the same series of configuration values will use that same backend. """ return self.impl.get(key, **self._get_cache_kw(kw, None)) def invalidate(self, key, **kw): r"""Invalidate a value in the cache. :param key: the value's key. :param \**kw: cache configuration arguments. The backend is configured using these arguments upon first request. Subsequent requests that use the same series of configuration values will use that same backend. """ self.impl.invalidate(key, **self._get_cache_kw(kw, None)) def invalidate_body(self): """Invalidate the cached content of the "body" method for this template. """ self.invalidate('render_body', __M_defname='render_body') def invalidate_def(self, name): """Invalidate the cached content of a particular ``<%def>`` within this template. """ self.invalidate('render_%s' % name, __M_defname='render_%s' % name) def invalidate_closure(self, name): """Invalidate a nested ``<%def>`` within this template. Caching of nested defs is a blunt tool as there is no management of scope -- nested defs that use cache tags need to have names unique of all other nested defs in the template, else their content will be overwritten by each other. """ self.invalidate(name, __M_defname=name) def _get_cache_kw(self, kw, context): defname = kw.pop('__M_defname', None) if not defname: tmpl_kw = self.template.cache_args.copy() tmpl_kw.update(kw) elif defname in self._def_regions: tmpl_kw = self._def_regions[defname] else: tmpl_kw = self.template.cache_args.copy() tmpl_kw.update(kw) self._def_regions[defname] = tmpl_kw if context and self.impl.pass_context: tmpl_kw = tmpl_kw.copy() tmpl_kw.setdefault('context', context) return tmpl_kw class CacheImpl(object): """Provide a cache implementation for use by :class:`.Cache`.""" def __init__(self, cache): self.cache = cache pass_context = False """If ``True``, the :class:`.Context` will be passed to :meth:`get_or_create <.CacheImpl.get_or_create>` as the name ``'context'``. """ def get_or_create(self, key, creation_function, **kw): r"""Retrieve a value from the cache, using the given creation function to generate a new value. This function *must* return a value, either from the cache, or via the given creation function. If the creation function is called, the newly created value should be populated into the cache under the given key before being returned. :param key: the value's key. :param creation_function: function that when called generates a new value. :param \**kw: cache configuration arguments. """ raise NotImplementedError() def set(self, key, value, **kw): r"""Place a value in the cache. :param key: the value's key. :param value: the value. :param \**kw: cache configuration arguments. """ raise NotImplementedError() def get(self, key, **kw): r"""Retrieve a value from the cache. :param key: the value's key. :param \**kw: cache configuration arguments. """ raise NotImplementedError() def invalidate(self, key, **kw): r"""Invalidate a value in the cache. :param key: the value's key. :param \**kw: cache configuration arguments. """ raise NotImplementedError() Mako-1.0.7/mako/codegen.py0000664000175000017500000013640013131715725016412 0ustar classicclassic00000000000000# mako/codegen.py # Copyright (C) 2006-2016 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php """provides functionality for rendering a parsetree constructing into module source code.""" import time import re from mako.pygen import PythonPrinter from mako import util, ast, parsetree, filters, exceptions from mako import compat MAGIC_NUMBER = 10 # names which are hardwired into the # template and are not accessed via the # context itself TOPLEVEL_DECLARED = set(["UNDEFINED", "STOP_RENDERING"]) RESERVED_NAMES = set(['context', 'loop']).union(TOPLEVEL_DECLARED) def compile(node, uri, filename=None, default_filters=None, buffer_filters=None, imports=None, future_imports=None, source_encoding=None, generate_magic_comment=True, disable_unicode=False, strict_undefined=False, enable_loop=True, reserved_names=frozenset()): """Generate module source code given a parsetree node, uri, and optional source filename""" # if on Py2K, push the "source_encoding" string to be # a bytestring itself, as we will be embedding it into # the generated source and we don't want to coerce the # result into a unicode object, in "disable_unicode" mode if not compat.py3k and isinstance(source_encoding, compat.text_type): source_encoding = source_encoding.encode(source_encoding) buf = util.FastEncodingBuffer() printer = PythonPrinter(buf) _GenerateRenderMethod(printer, _CompileContext(uri, filename, default_filters, buffer_filters, imports, future_imports, source_encoding, generate_magic_comment, disable_unicode, strict_undefined, enable_loop, reserved_names), node) return buf.getvalue() class _CompileContext(object): def __init__(self, uri, filename, default_filters, buffer_filters, imports, future_imports, source_encoding, generate_magic_comment, disable_unicode, strict_undefined, enable_loop, reserved_names): self.uri = uri self.filename = filename self.default_filters = default_filters self.buffer_filters = buffer_filters self.imports = imports self.future_imports = future_imports self.source_encoding = source_encoding self.generate_magic_comment = generate_magic_comment self.disable_unicode = disable_unicode self.strict_undefined = strict_undefined self.enable_loop = enable_loop self.reserved_names = reserved_names class _GenerateRenderMethod(object): """A template visitor object which generates the full module source for a template. """ def __init__(self, printer, compiler, node): self.printer = printer self.compiler = compiler self.node = node self.identifier_stack = [None] self.in_def = isinstance(node, (parsetree.DefTag, parsetree.BlockTag)) if self.in_def: name = "render_%s" % node.funcname args = node.get_argument_expressions() filtered = len(node.filter_args.args) > 0 buffered = eval(node.attributes.get('buffered', 'False')) cached = eval(node.attributes.get('cached', 'False')) defs = None pagetag = None if node.is_block and not node.is_anonymous: args += ['**pageargs'] else: defs = self.write_toplevel() pagetag = self.compiler.pagetag name = "render_body" if pagetag is not None: args = pagetag.body_decl.get_argument_expressions() if not pagetag.body_decl.kwargs: args += ['**pageargs'] cached = eval(pagetag.attributes.get('cached', 'False')) self.compiler.enable_loop = self.compiler.enable_loop or eval( pagetag.attributes.get( 'enable_loop', 'False') ) else: args = ['**pageargs'] cached = False buffered = filtered = False if args is None: args = ['context'] else: args = [a for a in ['context'] + args] self.write_render_callable( pagetag or node, name, args, buffered, filtered, cached) if defs is not None: for node in defs: _GenerateRenderMethod(printer, compiler, node) if not self.in_def: self.write_metadata_struct() def write_metadata_struct(self): self.printer.source_map[self.printer.lineno] = \ max(self.printer.source_map) struct = { "filename": self.compiler.filename, "uri": self.compiler.uri, "source_encoding": self.compiler.source_encoding, "line_map": self.printer.source_map, } self.printer.writelines( '"""', '__M_BEGIN_METADATA', compat.json.dumps(struct), '__M_END_METADATA\n' '"""' ) @property def identifiers(self): return self.identifier_stack[-1] def write_toplevel(self): """Traverse a template structure for module-level directives and generate the start of module-level code. """ inherit = [] namespaces = {} module_code = [] self.compiler.pagetag = None class FindTopLevel(object): def visitInheritTag(s, node): inherit.append(node) def visitNamespaceTag(s, node): namespaces[node.name] = node def visitPageTag(s, node): self.compiler.pagetag = node def visitCode(s, node): if node.ismodule: module_code.append(node) f = FindTopLevel() for n in self.node.nodes: n.accept_visitor(f) self.compiler.namespaces = namespaces module_ident = set() for n in module_code: module_ident = module_ident.union(n.declared_identifiers()) module_identifiers = _Identifiers(self.compiler) module_identifiers.declared = module_ident # module-level names, python code if self.compiler.generate_magic_comment and \ self.compiler.source_encoding: self.printer.writeline("# -*- coding:%s -*-" % self.compiler.source_encoding) if self.compiler.future_imports: self.printer.writeline("from __future__ import %s" % (", ".join(self.compiler.future_imports),)) self.printer.writeline("from mako import runtime, filters, cache") self.printer.writeline("UNDEFINED = runtime.UNDEFINED") self.printer.writeline("STOP_RENDERING = runtime.STOP_RENDERING") self.printer.writeline("__M_dict_builtin = dict") self.printer.writeline("__M_locals_builtin = locals") self.printer.writeline("_magic_number = %r" % MAGIC_NUMBER) self.printer.writeline("_modified_time = %r" % time.time()) self.printer.writeline("_enable_loop = %r" % self.compiler.enable_loop) self.printer.writeline( "_template_filename = %r" % self.compiler.filename) self.printer.writeline("_template_uri = %r" % self.compiler.uri) self.printer.writeline( "_source_encoding = %r" % self.compiler.source_encoding) if self.compiler.imports: buf = '' for imp in self.compiler.imports: buf += imp + "\n" self.printer.writeline(imp) impcode = ast.PythonCode( buf, source='', lineno=0, pos=0, filename='template defined imports') else: impcode = None main_identifiers = module_identifiers.branch(self.node) module_identifiers.topleveldefs = \ module_identifiers.topleveldefs.\ union(main_identifiers.topleveldefs) module_identifiers.declared.update(TOPLEVEL_DECLARED) if impcode: module_identifiers.declared.update(impcode.declared_identifiers) self.compiler.identifiers = module_identifiers self.printer.writeline("_exports = %r" % [n.name for n in main_identifiers.topleveldefs.values()] ) self.printer.write_blanks(2) if len(module_code): self.write_module_code(module_code) if len(inherit): self.write_namespaces(namespaces) self.write_inherit(inherit[-1]) elif len(namespaces): self.write_namespaces(namespaces) return list(main_identifiers.topleveldefs.values()) def write_render_callable(self, node, name, args, buffered, filtered, cached): """write a top-level render callable. this could be the main render() method or that of a top-level def.""" if self.in_def: decorator = node.decorator if decorator: self.printer.writeline( "@runtime._decorate_toplevel(%s)" % decorator) self.printer.start_source(node.lineno) self.printer.writelines( "def %s(%s):" % (name, ','.join(args)), # push new frame, assign current frame to __M_caller "__M_caller = context.caller_stack._push_frame()", "try:" ) if buffered or filtered or cached: self.printer.writeline("context._push_buffer()") self.identifier_stack.append( self.compiler.identifiers.branch(self.node)) if (not self.in_def or self.node.is_block) and '**pageargs' in args: self.identifier_stack[-1].argument_declared.add('pageargs') if not self.in_def and ( len(self.identifiers.locally_assigned) > 0 or len(self.identifiers.argument_declared) > 0 ): self.printer.writeline("__M_locals = __M_dict_builtin(%s)" % ','.join([ "%s=%s" % (x, x) for x in self.identifiers.argument_declared ])) self.write_variable_declares(self.identifiers, toplevel=True) for n in self.node.nodes: n.accept_visitor(self) self.write_def_finish(self.node, buffered, filtered, cached) self.printer.writeline(None) self.printer.write_blanks(2) if cached: self.write_cache_decorator( node, name, args, buffered, self.identifiers, toplevel=True) def write_module_code(self, module_code): """write module-level template code, i.e. that which is enclosed in <%! %> tags in the template.""" for n in module_code: self.printer.start_source(n.lineno) self.printer.write_indented_block(n.text) def write_inherit(self, node): """write the module-level inheritance-determination callable.""" self.printer.writelines( "def _mako_inherit(template, context):", "_mako_generate_namespaces(context)", "return runtime._inherit_from(context, %s, _template_uri)" % (node.parsed_attributes['file']), None ) def write_namespaces(self, namespaces): """write the module-level namespace-generating callable.""" self.printer.writelines( "def _mako_get_namespace(context, name):", "try:", "return context.namespaces[(__name__, name)]", "except KeyError:", "_mako_generate_namespaces(context)", "return context.namespaces[(__name__, name)]", None, None ) self.printer.writeline("def _mako_generate_namespaces(context):") for node in namespaces.values(): if 'import' in node.attributes: self.compiler.has_ns_imports = True self.printer.start_source(node.lineno) if len(node.nodes): self.printer.writeline("def make_namespace():") export = [] identifiers = self.compiler.identifiers.branch(node) self.in_def = True class NSDefVisitor(object): def visitDefTag(s, node): s.visitDefOrBase(node) def visitBlockTag(s, node): s.visitDefOrBase(node) def visitDefOrBase(s, node): if node.is_anonymous: raise exceptions.CompileException( "Can't put anonymous blocks inside " "<%namespace>", **node.exception_kwargs ) self.write_inline_def(node, identifiers, nested=False) export.append(node.funcname) vis = NSDefVisitor() for n in node.nodes: n.accept_visitor(vis) self.printer.writeline("return [%s]" % (','.join(export))) self.printer.writeline(None) self.in_def = False callable_name = "make_namespace()" else: callable_name = "None" if 'file' in node.parsed_attributes: self.printer.writeline( "ns = runtime.TemplateNamespace(%r," " context._clean_inheritance_tokens()," " templateuri=%s, callables=%s, " " calling_uri=_template_uri)" % ( node.name, node.parsed_attributes.get('file', 'None'), callable_name, ) ) elif 'module' in node.parsed_attributes: self.printer.writeline( "ns = runtime.ModuleNamespace(%r," " context._clean_inheritance_tokens()," " callables=%s, calling_uri=_template_uri," " module=%s)" % ( node.name, callable_name, node.parsed_attributes.get( 'module', 'None') ) ) else: self.printer.writeline( "ns = runtime.Namespace(%r," " context._clean_inheritance_tokens()," " callables=%s, calling_uri=_template_uri)" % ( node.name, callable_name, ) ) if eval(node.attributes.get('inheritable', "False")): self.printer.writeline("context['self'].%s = ns" % (node.name)) self.printer.writeline( "context.namespaces[(__name__, %s)] = ns" % repr(node.name)) self.printer.write_blanks(1) if not len(namespaces): self.printer.writeline("pass") self.printer.writeline(None) def write_variable_declares(self, identifiers, toplevel=False, limit=None): """write variable declarations at the top of a function. the variable declarations are in the form of callable definitions for defs and/or name lookup within the function's context argument. the names declared are based on the names that are referenced in the function body, which don't otherwise have any explicit assignment operation. names that are assigned within the body are assumed to be locally-scoped variables and are not separately declared. for def callable definitions, if the def is a top-level callable then a 'stub' callable is generated which wraps the current Context into a closure. if the def is not top-level, it is fully rendered as a local closure. """ # collection of all defs available to us in this scope comp_idents = dict([(c.funcname, c) for c in identifiers.defs]) to_write = set() # write "context.get()" for all variables we are going to # need that arent in the namespace yet to_write = to_write.union(identifiers.undeclared) # write closure functions for closures that we define # right here to_write = to_write.union( [c.funcname for c in identifiers.closuredefs.values()]) # remove identifiers that are declared in the argument # signature of the callable to_write = to_write.difference(identifiers.argument_declared) # remove identifiers that we are going to assign to. # in this way we mimic Python's behavior, # i.e. assignment to a variable within a block # means that variable is now a "locally declared" var, # which cannot be referenced beforehand. to_write = to_write.difference(identifiers.locally_declared) if self.compiler.enable_loop: has_loop = "loop" in to_write to_write.discard("loop") else: has_loop = False # if a limiting set was sent, constraint to those items in that list # (this is used for the caching decorator) if limit is not None: to_write = to_write.intersection(limit) if toplevel and getattr(self.compiler, 'has_ns_imports', False): self.printer.writeline("_import_ns = {}") self.compiler.has_imports = True for ident, ns in self.compiler.namespaces.items(): if 'import' in ns.attributes: self.printer.writeline( "_mako_get_namespace(context, %r)." "_populate(_import_ns, %r)" % ( ident, re.split(r'\s*,\s*', ns.attributes['import']) )) if has_loop: self.printer.writeline( 'loop = __M_loop = runtime.LoopStack()' ) for ident in to_write: if ident in comp_idents: comp = comp_idents[ident] if comp.is_block: if not comp.is_anonymous: self.write_def_decl(comp, identifiers) else: self.write_inline_def(comp, identifiers, nested=True) else: if comp.is_root(): self.write_def_decl(comp, identifiers) else: self.write_inline_def(comp, identifiers, nested=True) elif ident in self.compiler.namespaces: self.printer.writeline( "%s = _mako_get_namespace(context, %r)" % (ident, ident) ) else: if getattr(self.compiler, 'has_ns_imports', False): if self.compiler.strict_undefined: self.printer.writelines( "%s = _import_ns.get(%r, UNDEFINED)" % (ident, ident), "if %s is UNDEFINED:" % ident, "try:", "%s = context[%r]" % (ident, ident), "except KeyError:", "raise NameError(\"'%s' is not defined\")" % ident, None, None ) else: self.printer.writeline( "%s = _import_ns.get" "(%r, context.get(%r, UNDEFINED))" % (ident, ident, ident)) else: if self.compiler.strict_undefined: self.printer.writelines( "try:", "%s = context[%r]" % (ident, ident), "except KeyError:", "raise NameError(\"'%s' is not defined\")" % ident, None ) else: self.printer.writeline( "%s = context.get(%r, UNDEFINED)" % (ident, ident) ) self.printer.writeline("__M_writer = context.writer()") def write_def_decl(self, node, identifiers): """write a locally-available callable referencing a top-level def""" funcname = node.funcname namedecls = node.get_argument_expressions() nameargs = node.get_argument_expressions(as_call=True) if not self.in_def and ( len(self.identifiers.locally_assigned) > 0 or len(self.identifiers.argument_declared) > 0): nameargs.insert(0, 'context._locals(__M_locals)') else: nameargs.insert(0, 'context') self.printer.writeline("def %s(%s):" % (funcname, ",".join(namedecls))) self.printer.writeline( "return render_%s(%s)" % (funcname, ",".join(nameargs))) self.printer.writeline(None) def write_inline_def(self, node, identifiers, nested): """write a locally-available def callable inside an enclosing def.""" namedecls = node.get_argument_expressions() decorator = node.decorator if decorator: self.printer.writeline( "@runtime._decorate_inline(context, %s)" % decorator) self.printer.writeline( "def %s(%s):" % (node.funcname, ",".join(namedecls))) filtered = len(node.filter_args.args) > 0 buffered = eval(node.attributes.get('buffered', 'False')) cached = eval(node.attributes.get('cached', 'False')) self.printer.writelines( # push new frame, assign current frame to __M_caller "__M_caller = context.caller_stack._push_frame()", "try:" ) if buffered or filtered or cached: self.printer.writelines( "context._push_buffer()", ) identifiers = identifiers.branch(node, nested=nested) self.write_variable_declares(identifiers) self.identifier_stack.append(identifiers) for n in node.nodes: n.accept_visitor(self) self.identifier_stack.pop() self.write_def_finish(node, buffered, filtered, cached) self.printer.writeline(None) if cached: self.write_cache_decorator(node, node.funcname, namedecls, False, identifiers, inline=True, toplevel=False) def write_def_finish(self, node, buffered, filtered, cached, callstack=True): """write the end section of a rendering function, either outermost or inline. this takes into account if the rendering function was filtered, buffered, etc. and closes the corresponding try: block if any, and writes code to retrieve captured content, apply filters, send proper return value.""" if not buffered and not cached and not filtered: self.printer.writeline("return ''") if callstack: self.printer.writelines( "finally:", "context.caller_stack._pop_frame()", None ) if buffered or filtered or cached: if buffered or cached: # in a caching scenario, don't try to get a writer # from the context after popping; assume the caching # implemenation might be using a context with no # extra buffers self.printer.writelines( "finally:", "__M_buf = context._pop_buffer()" ) else: self.printer.writelines( "finally:", "__M_buf, __M_writer = context._pop_buffer_and_writer()" ) if callstack: self.printer.writeline("context.caller_stack._pop_frame()") s = "__M_buf.getvalue()" if filtered: s = self.create_filter_callable(node.filter_args.args, s, False) self.printer.writeline(None) if buffered and not cached: s = self.create_filter_callable(self.compiler.buffer_filters, s, False) if buffered or cached: self.printer.writeline("return %s" % s) else: self.printer.writelines( "__M_writer(%s)" % s, "return ''" ) def write_cache_decorator(self, node_or_pagetag, name, args, buffered, identifiers, inline=False, toplevel=False): """write a post-function decorator to replace a rendering callable with a cached version of itself.""" self.printer.writeline("__M_%s = %s" % (name, name)) cachekey = node_or_pagetag.parsed_attributes.get('cache_key', repr(name)) cache_args = {} if self.compiler.pagetag is not None: cache_args.update( ( pa[6:], self.compiler.pagetag.parsed_attributes[pa] ) for pa in self.compiler.pagetag.parsed_attributes if pa.startswith('cache_') and pa != 'cache_key' ) cache_args.update( ( pa[6:], node_or_pagetag.parsed_attributes[pa] ) for pa in node_or_pagetag.parsed_attributes if pa.startswith('cache_') and pa != 'cache_key' ) if 'timeout' in cache_args: cache_args['timeout'] = int(eval(cache_args['timeout'])) self.printer.writeline("def %s(%s):" % (name, ','.join(args))) # form "arg1, arg2, arg3=arg3, arg4=arg4", etc. pass_args = [ "%s=%s" % ((a.split('=')[0],) * 2) if '=' in a else a for a in args ] self.write_variable_declares( identifiers, toplevel=toplevel, limit=node_or_pagetag.undeclared_identifiers() ) if buffered: s = "context.get('local')."\ "cache._ctx_get_or_create("\ "%s, lambda:__M_%s(%s), context, %s__M_defname=%r)" % ( cachekey, name, ','.join(pass_args), ''.join(["%s=%s, " % (k, v) for k, v in cache_args.items()]), name ) # apply buffer_filters s = self.create_filter_callable(self.compiler.buffer_filters, s, False) self.printer.writelines("return " + s, None) else: self.printer.writelines( "__M_writer(context.get('local')." "cache._ctx_get_or_create(" "%s, lambda:__M_%s(%s), context, %s__M_defname=%r))" % ( cachekey, name, ','.join(pass_args), ''.join(["%s=%s, " % (k, v) for k, v in cache_args.items()]), name, ), "return ''", None ) def create_filter_callable(self, args, target, is_expression): """write a filter-applying expression based on the filters present in the given filter names, adjusting for the global 'default' filter aliases as needed.""" def locate_encode(name): if re.match(r'decode\..+', name): return "filters." + name elif self.compiler.disable_unicode: return filters.NON_UNICODE_ESCAPES.get(name, name) else: return filters.DEFAULT_ESCAPES.get(name, name) if 'n' not in args: if is_expression: if self.compiler.pagetag: args = self.compiler.pagetag.filter_args.args + args if self.compiler.default_filters: args = self.compiler.default_filters + args for e in args: # if filter given as a function, get just the identifier portion if e == 'n': continue m = re.match(r'(.+?)(\(.*\))', e) if m: ident, fargs = m.group(1, 2) f = locate_encode(ident) e = f + fargs else: e = locate_encode(e) assert e is not None target = "%s(%s)" % (e, target) return target def visitExpression(self, node): self.printer.start_source(node.lineno) if len(node.escapes) or \ ( self.compiler.pagetag is not None and len(self.compiler.pagetag.filter_args.args) ) or \ len(self.compiler.default_filters): s = self.create_filter_callable(node.escapes_code.args, "%s" % node.text, True) self.printer.writeline("__M_writer(%s)" % s) else: self.printer.writeline("__M_writer(%s)" % node.text) def visitControlLine(self, node): if node.isend: self.printer.writeline(None) if node.has_loop_context: self.printer.writeline('finally:') self.printer.writeline("loop = __M_loop._exit()") self.printer.writeline(None) else: self.printer.start_source(node.lineno) if self.compiler.enable_loop and node.keyword == 'for': text = mangle_mako_loop(node, self.printer) else: text = node.text self.printer.writeline(text) children = node.get_children() # this covers the three situations where we want to insert a pass: # 1) a ternary control line with no children, # 2) a primary control line with nothing but its own ternary # and end control lines, and # 3) any control line with no content other than comments if not children or ( compat.all(isinstance(c, (parsetree.Comment, parsetree.ControlLine)) for c in children) and compat.all((node.is_ternary(c.keyword) or c.isend) for c in children if isinstance(c, parsetree.ControlLine))): self.printer.writeline("pass") def visitText(self, node): self.printer.start_source(node.lineno) self.printer.writeline("__M_writer(%s)" % repr(node.content)) def visitTextTag(self, node): filtered = len(node.filter_args.args) > 0 if filtered: self.printer.writelines( "__M_writer = context._push_writer()", "try:", ) for n in node.nodes: n.accept_visitor(self) if filtered: self.printer.writelines( "finally:", "__M_buf, __M_writer = context._pop_buffer_and_writer()", "__M_writer(%s)" % self.create_filter_callable( node.filter_args.args, "__M_buf.getvalue()", False), None ) def visitCode(self, node): if not node.ismodule: self.printer.start_source(node.lineno) self.printer.write_indented_block(node.text) if not self.in_def and len(self.identifiers.locally_assigned) > 0: # if we are the "template" def, fudge locally # declared/modified variables into the "__M_locals" dictionary, # which is used for def calls within the same template, # to simulate "enclosing scope" self.printer.writeline( '__M_locals_builtin_stored = __M_locals_builtin()') self.printer.writeline( '__M_locals.update(__M_dict_builtin([(__M_key,' ' __M_locals_builtin_stored[__M_key]) for __M_key in' ' [%s] if __M_key in __M_locals_builtin_stored]))' % ','.join([repr(x) for x in node.declared_identifiers()])) def visitIncludeTag(self, node): self.printer.start_source(node.lineno) args = node.attributes.get('args') if args: self.printer.writeline( "runtime._include_file(context, %s, _template_uri, %s)" % (node.parsed_attributes['file'], args)) else: self.printer.writeline( "runtime._include_file(context, %s, _template_uri)" % (node.parsed_attributes['file'])) def visitNamespaceTag(self, node): pass def visitDefTag(self, node): pass def visitBlockTag(self, node): if node.is_anonymous: self.printer.writeline("%s()" % node.funcname) else: nameargs = node.get_argument_expressions(as_call=True) nameargs += ['**pageargs'] self.printer.writeline( "if 'parent' not in context._data or " "not hasattr(context._data['parent'], '%s'):" % node.funcname) self.printer.writeline( "context['self'].%s(%s)" % (node.funcname, ",".join(nameargs))) self.printer.writeline("\n") def visitCallNamespaceTag(self, node): # TODO: we can put namespace-specific checks here, such # as ensure the given namespace will be imported, # pre-import the namespace, etc. self.visitCallTag(node) def visitCallTag(self, node): self.printer.writeline("def ccall(caller):") export = ['body'] callable_identifiers = self.identifiers.branch(node, nested=True) body_identifiers = callable_identifiers.branch(node, nested=False) # we want the 'caller' passed to ccall to be used # for the body() function, but for other non-body() # <%def>s within <%call> we want the current caller # off the call stack (if any) body_identifiers.add_declared('caller') self.identifier_stack.append(body_identifiers) class DefVisitor(object): def visitDefTag(s, node): s.visitDefOrBase(node) def visitBlockTag(s, node): s.visitDefOrBase(node) def visitDefOrBase(s, node): self.write_inline_def(node, callable_identifiers, nested=False) if not node.is_anonymous: export.append(node.funcname) # remove defs that are within the <%call> from the # "closuredefs" defined in the body, so they dont render twice if node.funcname in body_identifiers.closuredefs: del body_identifiers.closuredefs[node.funcname] vis = DefVisitor() for n in node.nodes: n.accept_visitor(vis) self.identifier_stack.pop() bodyargs = node.body_decl.get_argument_expressions() self.printer.writeline("def body(%s):" % ','.join(bodyargs)) # TODO: figure out best way to specify # buffering/nonbuffering (at call time would be better) buffered = False if buffered: self.printer.writelines( "context._push_buffer()", "try:" ) self.write_variable_declares(body_identifiers) self.identifier_stack.append(body_identifiers) for n in node.nodes: n.accept_visitor(self) self.identifier_stack.pop() self.write_def_finish(node, buffered, False, False, callstack=False) self.printer.writelines( None, "return [%s]" % (','.join(export)), None ) self.printer.writelines( # push on caller for nested call "context.caller_stack.nextcaller = " "runtime.Namespace('caller', context, " "callables=ccall(__M_caller))", "try:") self.printer.start_source(node.lineno) self.printer.writelines( "__M_writer(%s)" % self.create_filter_callable( [], node.expression, True), "finally:", "context.caller_stack.nextcaller = None", None ) class _Identifiers(object): """tracks the status of identifier names as template code is rendered.""" def __init__(self, compiler, node=None, parent=None, nested=False): if parent is not None: # if we are the branch created in write_namespaces(), # we don't share any context from the main body(). if isinstance(node, parsetree.NamespaceTag): self.declared = set() self.topleveldefs = util.SetLikeDict() else: # things that have already been declared # in an enclosing namespace (i.e. names we can just use) self.declared = set(parent.declared).\ union([c.name for c in parent.closuredefs.values()]).\ union(parent.locally_declared).\ union(parent.argument_declared) # if these identifiers correspond to a "nested" # scope, it means whatever the parent identifiers # had as undeclared will have been declared by that parent, # and therefore we have them in our scope. if nested: self.declared = self.declared.union(parent.undeclared) # top level defs that are available self.topleveldefs = util.SetLikeDict(**parent.topleveldefs) else: self.declared = set() self.topleveldefs = util.SetLikeDict() self.compiler = compiler # things within this level that are referenced before they # are declared (e.g. assigned to) self.undeclared = set() # things that are declared locally. some of these things # could be in the "undeclared" list as well if they are # referenced before declared self.locally_declared = set() # assignments made in explicit python blocks. # these will be propagated to # the context of local def calls. self.locally_assigned = set() # things that are declared in the argument # signature of the def callable self.argument_declared = set() # closure defs that are defined in this level self.closuredefs = util.SetLikeDict() self.node = node if node is not None: node.accept_visitor(self) illegal_names = self.compiler.reserved_names.intersection( self.locally_declared) if illegal_names: raise exceptions.NameConflictError( "Reserved words declared in template: %s" % ", ".join(illegal_names)) def branch(self, node, **kwargs): """create a new Identifiers for a new Node, with this Identifiers as the parent.""" return _Identifiers(self.compiler, node, self, **kwargs) @property def defs(self): return set(self.topleveldefs.union(self.closuredefs).values()) def __repr__(self): return "Identifiers(declared=%r, locally_declared=%r, "\ "undeclared=%r, topleveldefs=%r, closuredefs=%r, "\ "argumentdeclared=%r)" %\ ( list(self.declared), list(self.locally_declared), list(self.undeclared), [c.name for c in self.topleveldefs.values()], [c.name for c in self.closuredefs.values()], self.argument_declared) def check_declared(self, node): """update the state of this Identifiers with the undeclared and declared identifiers of the given node.""" for ident in node.undeclared_identifiers(): if ident != 'context' and\ ident not in self.declared.union(self.locally_declared): self.undeclared.add(ident) for ident in node.declared_identifiers(): self.locally_declared.add(ident) def add_declared(self, ident): self.declared.add(ident) if ident in self.undeclared: self.undeclared.remove(ident) def visitExpression(self, node): self.check_declared(node) def visitControlLine(self, node): self.check_declared(node) def visitCode(self, node): if not node.ismodule: self.check_declared(node) self.locally_assigned = self.locally_assigned.union( node.declared_identifiers()) def visitNamespaceTag(self, node): # only traverse into the sub-elements of a # <%namespace> tag if we are the branch created in # write_namespaces() if self.node is node: for n in node.nodes: n.accept_visitor(self) def _check_name_exists(self, collection, node): existing = collection.get(node.funcname) collection[node.funcname] = node if existing is not None and \ existing is not node and \ (node.is_block or existing.is_block): raise exceptions.CompileException( "%%def or %%block named '%s' already " "exists in this template." % node.funcname, **node.exception_kwargs) def visitDefTag(self, node): if node.is_root() and not node.is_anonymous: self._check_name_exists(self.topleveldefs, node) elif node is not self.node: self._check_name_exists(self.closuredefs, node) for ident in node.undeclared_identifiers(): if ident != 'context' and \ ident not in self.declared.union(self.locally_declared): self.undeclared.add(ident) # visit defs only one level deep if node is self.node: for ident in node.declared_identifiers(): self.argument_declared.add(ident) for n in node.nodes: n.accept_visitor(self) def visitBlockTag(self, node): if node is not self.node and not node.is_anonymous: if isinstance(self.node, parsetree.DefTag): raise exceptions.CompileException( "Named block '%s' not allowed inside of def '%s'" % (node.name, self.node.name), **node.exception_kwargs) elif isinstance(self.node, (parsetree.CallTag, parsetree.CallNamespaceTag)): raise exceptions.CompileException( "Named block '%s' not allowed inside of <%%call> tag" % (node.name, ), **node.exception_kwargs) for ident in node.undeclared_identifiers(): if ident != 'context' and \ ident not in self.declared.union(self.locally_declared): self.undeclared.add(ident) if not node.is_anonymous: self._check_name_exists(self.topleveldefs, node) self.undeclared.add(node.funcname) elif node is not self.node: self._check_name_exists(self.closuredefs, node) for ident in node.declared_identifiers(): self.argument_declared.add(ident) for n in node.nodes: n.accept_visitor(self) def visitTextTag(self, node): for ident in node.undeclared_identifiers(): if ident != 'context' and \ ident not in self.declared.union(self.locally_declared): self.undeclared.add(ident) def visitIncludeTag(self, node): self.check_declared(node) def visitPageTag(self, node): for ident in node.declared_identifiers(): self.argument_declared.add(ident) self.check_declared(node) def visitCallNamespaceTag(self, node): self.visitCallTag(node) def visitCallTag(self, node): if node is self.node: for ident in node.undeclared_identifiers(): if ident != 'context' and \ ident not in self.declared.union( self.locally_declared): self.undeclared.add(ident) for ident in node.declared_identifiers(): self.argument_declared.add(ident) for n in node.nodes: n.accept_visitor(self) else: for ident in node.undeclared_identifiers(): if ident != 'context' and \ ident not in self.declared.union( self.locally_declared): self.undeclared.add(ident) _FOR_LOOP = re.compile( r'^for\s+((?:\(?)\s*[A-Za-z_][A-Za-z_0-9]*' r'(?:\s*,\s*(?:[A-Za-z_][A-Za-z0-9_]*),??)*\s*(?:\)?))\s+in\s+(.*):' ) def mangle_mako_loop(node, printer): """converts a for loop into a context manager wrapped around a for loop when access to the `loop` variable has been detected in the for loop body """ loop_variable = LoopVariable() node.accept_visitor(loop_variable) if loop_variable.detected: node.nodes[-1].has_loop_context = True match = _FOR_LOOP.match(node.text) if match: printer.writelines( 'loop = __M_loop._enter(%s)' % match.group(2), 'try:' # 'with __M_loop(%s) as loop:' % match.group(2) ) text = 'for %s in loop:' % match.group(1) else: raise SyntaxError("Couldn't apply loop context: %s" % node.text) else: text = node.text return text class LoopVariable(object): """A node visitor which looks for the name 'loop' within undeclared identifiers.""" def __init__(self): self.detected = False def _loop_reference_detected(self, node): if 'loop' in node.undeclared_identifiers(): self.detected = True else: for n in node.get_children(): n.accept_visitor(self) def visitControlLine(self, node): self._loop_reference_detected(node) def visitCode(self, node): self._loop_reference_detected(node) def visitExpression(self, node): self._loop_reference_detected(node) Mako-1.0.7/mako/pyparser.py0000664000175000017500000001673213131715725016660 0ustar classicclassic00000000000000# mako/pyparser.py # Copyright (C) 2006-2016 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php """Handles parsing of Python code. Parsing to AST is done via _ast on Python > 2.5, otherwise the compiler module is used. """ from mako import exceptions, util, compat from mako.compat import arg_stringname import operator if compat.py3k: # words that cannot be assigned to (notably # smaller than the total keys in __builtins__) reserved = set(['True', 'False', 'None', 'print']) # the "id" attribute on a function node arg_id = operator.attrgetter('arg') else: # words that cannot be assigned to (notably # smaller than the total keys in __builtins__) reserved = set(['True', 'False', 'None']) # the "id" attribute on a function node arg_id = operator.attrgetter('id') import _ast util.restore__ast(_ast) from mako import _ast_util def parse(code, mode='exec', **exception_kwargs): """Parse an expression into AST""" try: return _ast_util.parse(code, '', mode) except Exception: raise exceptions.SyntaxException( "(%s) %s (%r)" % ( compat.exception_as().__class__.__name__, compat.exception_as(), code[0:50] ), **exception_kwargs) class FindIdentifiers(_ast_util.NodeVisitor): def __init__(self, listener, **exception_kwargs): self.in_function = False self.in_assign_targets = False self.local_ident_stack = set() self.listener = listener self.exception_kwargs = exception_kwargs def _add_declared(self, name): if not self.in_function: self.listener.declared_identifiers.add(name) else: self.local_ident_stack.add(name) def visit_ClassDef(self, node): self._add_declared(node.name) def visit_Assign(self, node): # flip around the visiting of Assign so the expression gets # evaluated first, in the case of a clause like "x=x+5" (x # is undeclared) self.visit(node.value) in_a = self.in_assign_targets self.in_assign_targets = True for n in node.targets: self.visit(n) self.in_assign_targets = in_a if compat.py3k: # ExceptHandler is in Python 2, but this block only works in # Python 3 (and is required there) def visit_ExceptHandler(self, node): if node.name is not None: self._add_declared(node.name) if node.type is not None: self.visit(node.type) for statement in node.body: self.visit(statement) def visit_Lambda(self, node, *args): self._visit_function(node, True) def visit_FunctionDef(self, node): self._add_declared(node.name) self._visit_function(node, False) def _expand_tuples(self, args): for arg in args: if isinstance(arg, _ast.Tuple): for n in arg.elts: yield n else: yield arg def _visit_function(self, node, islambda): # push function state onto stack. dont log any more # identifiers as "declared" until outside of the function, # but keep logging identifiers as "undeclared". track # argument names in each function header so they arent # counted as "undeclared" inf = self.in_function self.in_function = True local_ident_stack = self.local_ident_stack self.local_ident_stack = local_ident_stack.union([ arg_id(arg) for arg in self._expand_tuples(node.args.args) ]) if islambda: self.visit(node.body) else: for n in node.body: self.visit(n) self.in_function = inf self.local_ident_stack = local_ident_stack def visit_For(self, node): # flip around visit self.visit(node.iter) self.visit(node.target) for statement in node.body: self.visit(statement) for statement in node.orelse: self.visit(statement) def visit_Name(self, node): if isinstance(node.ctx, _ast.Store): # this is eqiuvalent to visit_AssName in # compiler self._add_declared(node.id) elif node.id not in reserved and node.id \ not in self.listener.declared_identifiers and node.id \ not in self.local_ident_stack: self.listener.undeclared_identifiers.add(node.id) def visit_Import(self, node): for name in node.names: if name.asname is not None: self._add_declared(name.asname) else: self._add_declared(name.name.split('.')[0]) def visit_ImportFrom(self, node): for name in node.names: if name.asname is not None: self._add_declared(name.asname) else: if name.name == '*': raise exceptions.CompileException( "'import *' is not supported, since all identifier " "names must be explicitly declared. Please use the " "form 'from import , , " "...' instead.", **self.exception_kwargs) self._add_declared(name.name) class FindTuple(_ast_util.NodeVisitor): def __init__(self, listener, code_factory, **exception_kwargs): self.listener = listener self.exception_kwargs = exception_kwargs self.code_factory = code_factory def visit_Tuple(self, node): for n in node.elts: p = self.code_factory(n, **self.exception_kwargs) self.listener.codeargs.append(p) self.listener.args.append(ExpressionGenerator(n).value()) self.listener.declared_identifiers = \ self.listener.declared_identifiers.union( p.declared_identifiers) self.listener.undeclared_identifiers = \ self.listener.undeclared_identifiers.union( p.undeclared_identifiers) class ParseFunc(_ast_util.NodeVisitor): def __init__(self, listener, **exception_kwargs): self.listener = listener self.exception_kwargs = exception_kwargs def visit_FunctionDef(self, node): self.listener.funcname = node.name argnames = [arg_id(arg) for arg in node.args.args] if node.args.vararg: argnames.append(arg_stringname(node.args.vararg)) if compat.py2k: # kw-only args don't exist in Python 2 kwargnames = [] else: kwargnames = [arg_id(arg) for arg in node.args.kwonlyargs] if node.args.kwarg: kwargnames.append(arg_stringname(node.args.kwarg)) self.listener.argnames = argnames self.listener.defaults = node.args.defaults # ast self.listener.kwargnames = kwargnames if compat.py2k: self.listener.kwdefaults = [] else: self.listener.kwdefaults = node.args.kw_defaults self.listener.varargs = node.args.vararg self.listener.kwargs = node.args.kwarg class ExpressionGenerator(object): def __init__(self, astnode): self.generator = _ast_util.SourceGenerator(' ' * 4) self.generator.visit(astnode) def value(self): return ''.join(self.generator.result) Mako-1.0.7/mako/cmd.py0000775000175000017500000000405513131715725015554 0ustar classicclassic00000000000000# mako/cmd.py # Copyright (C) 2006-2016 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php from argparse import ArgumentParser from os.path import isfile, dirname import sys from mako.template import Template from mako.lookup import TemplateLookup from mako import exceptions def varsplit(var): if "=" not in var: return (var, "") return var.split("=", 1) def _exit(): sys.stderr.write(exceptions.text_error_template().render()) sys.exit(1) def cmdline(argv=None): parser = ArgumentParser("usage: %prog [FILENAME]") parser.add_argument( "--var", default=[], action="append", help="variable (can be used multiple times, use name=value)") parser.add_argument( "--template-dir", default=[], action="append", help="Directory to use for template lookup (multiple " "directories may be provided). If not given then if the " "template is read from stdin, the value defaults to be " "the current directory, otherwise it defaults to be the " "parent directory of the file provided.") parser.add_argument('input', nargs='?', default='-') options = parser.parse_args(argv) if options.input == '-': lookup_dirs = options.template_dir or ["."] lookup = TemplateLookup(lookup_dirs) try: template = Template(sys.stdin.read(), lookup=lookup) except: _exit() else: filename = options.input if not isfile(filename): raise SystemExit("error: can't find %s" % filename) lookup_dirs = options.template_dir or [dirname(filename)] lookup = TemplateLookup(lookup_dirs) try: template = Template(filename=filename, lookup=lookup) except: _exit() kw = dict([varsplit(var) for var in options.var]) try: sys.stdout.write(template.render(**kw)) except: _exit() if __name__ == "__main__": cmdline() Mako-1.0.7/mako/ext/0000775000175000017500000000000013131716021015216 5ustar classicclassic00000000000000Mako-1.0.7/mako/ext/preprocessors.py0000664000175000017500000000110413131715725020507 0ustar classicclassic00000000000000# ext/preprocessors.py # Copyright (C) 2006-2016 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php """preprocessing functions, used with the 'preprocessor' argument on Template, TemplateLookup""" import re def convert_comments(text): """preprocess old style comments. example: from mako.ext.preprocessors import convert_comments t = Template(..., preprocessor=convert_comments)""" return re.sub(r'(?<=\n)\s*#[^#]', "##", text) Mako-1.0.7/mako/ext/turbogears.py0000664000175000017500000000412413131715725017760 0ustar classicclassic00000000000000# ext/turbogears.py # Copyright (C) 2006-2016 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php from mako import compat from mako.lookup import TemplateLookup from mako.template import Template class TGPlugin(object): """TurboGears compatible Template Plugin.""" def __init__(self, extra_vars_func=None, options=None, extension='mak'): self.extra_vars_func = extra_vars_func self.extension = extension if not options: options = {} # Pull the options out and initialize the lookup lookup_options = {} for k, v in options.items(): if k.startswith('mako.'): lookup_options[k[5:]] = v elif k in ['directories', 'filesystem_checks', 'module_directory']: lookup_options[k] = v self.lookup = TemplateLookup(**lookup_options) self.tmpl_options = {} # transfer lookup args to template args, based on those available # in getargspec for kw in compat.inspect_getargspec(Template.__init__)[0]: if kw in lookup_options: self.tmpl_options[kw] = lookup_options[kw] def load_template(self, templatename, template_string=None): """Loads a template from a file or a string""" if template_string is not None: return Template(template_string, **self.tmpl_options) # Translate TG dot notation to normal / template path if '/' not in templatename: templatename = '/' + templatename.replace('.', '/') + '.' +\ self.extension # Lookup template return self.lookup.get_template(templatename) def render(self, info, format="html", fragment=False, template=None): if isinstance(template, compat.string_types): template = self.load_template(template) # Load extra vars func if provided if self.extra_vars_func: info.update(self.extra_vars_func()) return template.render(**info) Mako-1.0.7/mako/ext/autohandler.py0000664000175000017500000000344513131715725020116 0ustar classicclassic00000000000000# ext/autohandler.py # Copyright (C) 2006-2016 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php """adds autohandler functionality to Mako templates. requires that the TemplateLookup class is used with templates. usage: <%! from mako.ext.autohandler import autohandler %> <%inherit file="${autohandler(template, context)}"/> or with custom autohandler filename: <%! from mako.ext.autohandler import autohandler %> <%inherit file="${autohandler(template, context, name='somefilename')}"/> """ import posixpath import os import re def autohandler(template, context, name='autohandler'): lookup = context.lookup _template_uri = template.module._template_uri if not lookup.filesystem_checks: try: return lookup._uri_cache[(autohandler, _template_uri, name)] except KeyError: pass tokens = re.findall(r'([^/]+)', posixpath.dirname(_template_uri)) + [name] while len(tokens): path = '/' + '/'.join(tokens) if path != _template_uri and _file_exists(lookup, path): if not lookup.filesystem_checks: return lookup._uri_cache.setdefault( (autohandler, _template_uri, name), path) else: return path if len(tokens) == 1: break tokens[-2:] = [name] if not lookup.filesystem_checks: return lookup._uri_cache.setdefault( (autohandler, _template_uri, name), None) else: return None def _file_exists(lookup, path): psub = re.sub(r'^/', '', path) for d in lookup.directories: if os.path.exists(d + '/' + psub): return True else: return False Mako-1.0.7/mako/ext/pygmentplugin.py0000664000175000017500000001066213131715725020511 0ustar classicclassic00000000000000# ext/pygmentplugin.py # Copyright (C) 2006-2016 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php from pygments.lexers.web import \ HtmlLexer, XmlLexer, JavascriptLexer, CssLexer from pygments.lexers.agile import PythonLexer, Python3Lexer from pygments.lexer import DelegatingLexer, RegexLexer, bygroups, \ include, using from pygments.token import \ Text, Comment, Operator, Keyword, Name, String, Other from pygments.formatters.html import HtmlFormatter from pygments import highlight from mako import compat class MakoLexer(RegexLexer): name = 'Mako' aliases = ['mako'] filenames = ['*.mao'] tokens = { 'root': [ (r'(\s*)(\%)(\s*end(?:\w+))(\n|\Z)', bygroups(Text, Comment.Preproc, Keyword, Other)), (r'(\s*)(\%(?!%))([^\n]*)(\n|\Z)', bygroups(Text, Comment.Preproc, using(PythonLexer), Other)), (r'(\s*)(##[^\n]*)(\n|\Z)', bygroups(Text, Comment.Preproc, Other)), (r'''(?s)<%doc>.*?''', Comment.Preproc), (r'(<%)([\w\.\:]+)', bygroups(Comment.Preproc, Name.Builtin), 'tag'), (r'()', bygroups(Comment.Preproc, Name.Builtin, Comment.Preproc)), (r'<%(?=([\w\.\:]+))', Comment.Preproc, 'ondeftags'), (r'(<%(?:!?))(.*?)(%>)(?s)', bygroups(Comment.Preproc, using(PythonLexer), Comment.Preproc)), (r'(\$\{)(.*?)(\})', bygroups(Comment.Preproc, using(PythonLexer), Comment.Preproc)), (r'''(?sx) (.+?) # anything, followed by: (?: (?<=\n)(?=%(?!%)|\#\#) | # an eval or comment line (?=\#\*) | # multiline comment (?=', Comment.Preproc, '#pop'), (r'\s+', Text), ], 'attr': [ ('".*?"', String, '#pop'), ("'.*?'", String, '#pop'), (r'[^\s>]+', String, '#pop'), ], } class MakoHtmlLexer(DelegatingLexer): name = 'HTML+Mako' aliases = ['html+mako'] def __init__(self, **options): super(MakoHtmlLexer, self).__init__(HtmlLexer, MakoLexer, **options) class MakoXmlLexer(DelegatingLexer): name = 'XML+Mako' aliases = ['xml+mako'] def __init__(self, **options): super(MakoXmlLexer, self).__init__(XmlLexer, MakoLexer, **options) class MakoJavascriptLexer(DelegatingLexer): name = 'JavaScript+Mako' aliases = ['js+mako', 'javascript+mako'] def __init__(self, **options): super(MakoJavascriptLexer, self).__init__(JavascriptLexer, MakoLexer, **options) class MakoCssLexer(DelegatingLexer): name = 'CSS+Mako' aliases = ['css+mako'] def __init__(self, **options): super(MakoCssLexer, self).__init__(CssLexer, MakoLexer, **options) pygments_html_formatter = HtmlFormatter(cssclass='syntax-highlighted', linenos=True) def syntax_highlight(filename='', language=None): mako_lexer = MakoLexer() if compat.py3k: python_lexer = Python3Lexer() else: python_lexer = PythonLexer() if filename.startswith('memory:') or language == 'mako': return lambda string: highlight(string, mako_lexer, pygments_html_formatter) return lambda string: highlight(string, python_lexer, pygments_html_formatter) Mako-1.0.7/mako/ext/beaker_cache.py0000664000175000017500000000447513131715725020170 0ustar classicclassic00000000000000"""Provide a :class:`.CacheImpl` for the Beaker caching system.""" from mako import exceptions from mako.cache import CacheImpl try: from beaker import cache as beaker_cache except: has_beaker = False else: has_beaker = True _beaker_cache = None class BeakerCacheImpl(CacheImpl): """A :class:`.CacheImpl` provided for the Beaker caching system. This plugin is used by default, based on the default value of ``'beaker'`` for the ``cache_impl`` parameter of the :class:`.Template` or :class:`.TemplateLookup` classes. """ def __init__(self, cache): if not has_beaker: raise exceptions.RuntimeException( "Can't initialize Beaker plugin; Beaker is not installed.") global _beaker_cache if _beaker_cache is None: if 'manager' in cache.template.cache_args: _beaker_cache = cache.template.cache_args['manager'] else: _beaker_cache = beaker_cache.CacheManager() super(BeakerCacheImpl, self).__init__(cache) def _get_cache(self, **kw): expiretime = kw.pop('timeout', None) if 'dir' in kw: kw['data_dir'] = kw.pop('dir') elif self.cache.template.module_directory: kw['data_dir'] = self.cache.template.module_directory if 'manager' in kw: kw.pop('manager') if kw.get('type') == 'memcached': kw['type'] = 'ext:memcached' if 'region' in kw: region = kw.pop('region') cache = _beaker_cache.get_cache_region(self.cache.id, region, **kw) else: cache = _beaker_cache.get_cache(self.cache.id, **kw) cache_args = {'starttime': self.cache.starttime} if expiretime: cache_args['expiretime'] = expiretime return cache, cache_args def get_or_create(self, key, creation_function, **kw): cache, kw = self._get_cache(**kw) return cache.get(key, createfunc=creation_function, **kw) def put(self, key, value, **kw): cache, kw = self._get_cache(**kw) cache.put(key, value, **kw) def get(self, key, **kw): cache, kw = self._get_cache(**kw) return cache.get(key, **kw) def invalidate(self, key, **kw): cache, kw = self._get_cache(**kw) cache.remove_value(key, **kw) Mako-1.0.7/mako/ext/babelplugin.py0000664000175000017500000000403713131715725020072 0ustar classicclassic00000000000000# ext/babelplugin.py # Copyright (C) 2006-2016 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php """gettext message extraction via Babel: http://babel.edgewall.org/""" from babel.messages.extract import extract_python from mako.ext.extract import MessageExtractor class BabelMakoExtractor(MessageExtractor): def __init__(self, keywords, comment_tags, options): self.keywords = keywords self.options = options self.config = { 'comment-tags': u' '.join(comment_tags), 'encoding': options.get('input_encoding', options.get('encoding', None)), } super(BabelMakoExtractor, self).__init__() def __call__(self, fileobj): return self.process_file(fileobj) def process_python(self, code, code_lineno, translator_strings): comment_tags = self.config['comment-tags'] for lineno, funcname, messages, python_translator_comments \ in extract_python(code, self.keywords, comment_tags, self.options): yield (code_lineno + (lineno - 1), funcname, messages, translator_strings + python_translator_comments) def extract(fileobj, keywords, comment_tags, options): """Extract messages from Mako templates. :param fileobj: the file-like object the messages should be extracted from :param keywords: a list of keywords (i.e. function names) that should be recognized as translation functions :param comment_tags: a list of translator tags to search for and include in the results :param options: a dictionary of additional options (optional) :return: an iterator over ``(lineno, funcname, message, comments)`` tuples :rtype: ``iterator`` """ extractor = BabelMakoExtractor(keywords, comment_tags, options) for message in extractor(fileobj): yield message Mako-1.0.7/mako/ext/__init__.py0000664000175000017500000000000013131715725017327 0ustar classicclassic00000000000000Mako-1.0.7/mako/ext/extract.py0000664000175000017500000001024513131715725017256 0ustar classicclassic00000000000000import re from mako import compat from mako import lexer from mako import parsetree class MessageExtractor(object): def process_file(self, fileobj): template_node = lexer.Lexer( fileobj.read(), input_encoding=self.config['encoding']).parse() for extracted in self.extract_nodes(template_node.get_children()): yield extracted def extract_nodes(self, nodes): translator_comments = [] in_translator_comments = False input_encoding = self.config['encoding'] or 'ascii' comment_tags = list( filter(None, re.split(r'\s+', self.config['comment-tags']))) for node in nodes: child_nodes = None if in_translator_comments and \ isinstance(node, parsetree.Text) and \ not node.content.strip(): # Ignore whitespace within translator comments continue if isinstance(node, parsetree.Comment): value = node.text.strip() if in_translator_comments: translator_comments.extend( self._split_comment(node.lineno, value)) continue for comment_tag in comment_tags: if value.startswith(comment_tag): in_translator_comments = True translator_comments.extend( self._split_comment(node.lineno, value)) continue if isinstance(node, parsetree.DefTag): code = node.function_decl.code child_nodes = node.nodes elif isinstance(node, parsetree.BlockTag): code = node.body_decl.code child_nodes = node.nodes elif isinstance(node, parsetree.CallTag): code = node.code.code child_nodes = node.nodes elif isinstance(node, parsetree.PageTag): code = node.body_decl.code elif isinstance(node, parsetree.CallNamespaceTag): code = node.expression child_nodes = node.nodes elif isinstance(node, parsetree.ControlLine): if node.isend: in_translator_comments = False continue code = node.text elif isinstance(node, parsetree.Code): in_translator_comments = False code = node.code.code elif isinstance(node, parsetree.Expression): code = node.code.code else: continue # Comments don't apply unless they immediately precede the message if translator_comments and \ translator_comments[-1][0] < node.lineno - 1: translator_comments = [] translator_strings = [ comment[1] for comment in translator_comments] if isinstance(code, compat.text_type): code = code.encode(input_encoding, 'backslashreplace') used_translator_comments = False # We add extra newline to work around a pybabel bug # (see python-babel/babel#274, parse_encoding dies if the first # input string of the input is non-ascii) # Also, because we added it, we have to subtract one from # node.lineno code = compat.byte_buffer(compat.b('\n') + code) for message in self.process_python( code, node.lineno - 1, translator_strings): yield message used_translator_comments = True if used_translator_comments: translator_comments = [] in_translator_comments = False if child_nodes: for extracted in self.extract_nodes(child_nodes): yield extracted @staticmethod def _split_comment(lineno, comment): """Return the multiline comment at lineno split into a list of comment line numbers and the accompanying comment line""" return [(lineno + index, line) for index, line in enumerate(comment.splitlines())] Mako-1.0.7/mako/ext/linguaplugin.py0000664000175000017500000000317713131715725020310 0ustar classicclassic00000000000000import io from lingua.extractors import Extractor from lingua.extractors import Message from lingua.extractors import get_extractor from mako.ext.extract import MessageExtractor from mako import compat class LinguaMakoExtractor(Extractor, MessageExtractor): '''Mako templates''' extensions = ['.mako'] default_config = { 'encoding': 'utf-8', 'comment-tags': '', } def __call__(self, filename, options, fileobj=None): self.options = options self.filename = filename self.python_extractor = get_extractor('x.py') if fileobj is None: fileobj = open(filename, 'rb') return self.process_file(fileobj) def process_python(self, code, code_lineno, translator_strings): source = code.getvalue().strip() if source.endswith(compat.b(':')): if source in (compat.b('try:'), compat.b('else:')) or source.startswith(compat.b('except')): source = compat.b('') # Ignore try/except and else elif source.startswith(compat.b('elif')): source = source[2:] # Replace "elif" with "if" source += compat.b('pass') code = io.BytesIO(source) for msg in self.python_extractor( self.filename, self.options, code, code_lineno -1): if translator_strings: msg = Message(msg.msgctxt, msg.msgid, msg.msgid_plural, msg.flags, compat.u(' ').join( translator_strings + [msg.comment]), msg.tcomment, msg.location) yield msg Mako-1.0.7/mako/filters.py0000664000175000017500000001356313131715725016462 0ustar classicclassic00000000000000# mako/filters.py # Copyright (C) 2006-2016 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php import re import codecs from mako.compat import quote_plus, unquote_plus, codepoint2name, \ name2codepoint from mako import compat xml_escapes = { '&': '&', '>': '>', '<': '<', '"': '"', # also " in html-only "'": ''' # also ' in html-only } # XXX: " is valid in HTML and XML # ' is not valid HTML, but is valid XML def legacy_html_escape(s): """legacy HTML escape for non-unicode mode.""" s = s.replace("&", "&") s = s.replace(">", ">") s = s.replace("<", "<") s = s.replace('"', """) s = s.replace("'", "'") return s try: import markupsafe html_escape = markupsafe.escape except ImportError: html_escape = legacy_html_escape def xml_escape(string): return re.sub(r'([&<"\'>])', lambda m: xml_escapes[m.group()], string) def url_escape(string): # convert into a list of octets string = string.encode("utf8") return quote_plus(string) def legacy_url_escape(string): # convert into a list of octets return quote_plus(string) def url_unescape(string): text = unquote_plus(string) if not is_ascii_str(text): text = text.decode("utf8") return text def trim(string): return string.strip() class Decode(object): def __getattr__(self, key): def decode(x): if isinstance(x, compat.text_type): return x elif not isinstance(x, compat.binary_type): return decode(str(x)) else: return compat.text_type(x, encoding=key) return decode decode = Decode() _ASCII_re = re.compile(r'\A[\x00-\x7f]*\Z') def is_ascii_str(text): return isinstance(text, str) and _ASCII_re.match(text) ################################################################ class XMLEntityEscaper(object): def __init__(self, codepoint2name, name2codepoint): self.codepoint2entity = dict([(c, compat.text_type('&%s;' % n)) for c, n in codepoint2name.items()]) self.name2codepoint = name2codepoint def escape_entities(self, text): """Replace characters with their character entity references. Only characters corresponding to a named entity are replaced. """ return compat.text_type(text).translate(self.codepoint2entity) def __escape(self, m): codepoint = ord(m.group()) try: return self.codepoint2entity[codepoint] except (KeyError, IndexError): return '&#x%X;' % codepoint __escapable = re.compile(r'["&<>]|[^\x00-\x7f]') def escape(self, text): """Replace characters with their character references. Replace characters by their named entity references. Non-ASCII characters, if they do not have a named entity reference, are replaced by numerical character references. The return value is guaranteed to be ASCII. """ return self.__escapable.sub(self.__escape, compat.text_type(text) ).encode('ascii') # XXX: This regexp will not match all valid XML entity names__. # (It punts on details involving involving CombiningChars and Extenders.) # # .. __: http://www.w3.org/TR/2000/REC-xml-20001006#NT-EntityRef __characterrefs = re.compile(r'''& (?: \#(\d+) | \#x([\da-f]+) | ( (?!\d) [:\w] [-.:\w]+ ) ) ;''', re.X | re.UNICODE) def __unescape(self, m): dval, hval, name = m.groups() if dval: codepoint = int(dval) elif hval: codepoint = int(hval, 16) else: codepoint = self.name2codepoint.get(name, 0xfffd) # U+FFFD = "REPLACEMENT CHARACTER" if codepoint < 128: return chr(codepoint) return chr(codepoint) def unescape(self, text): """Unescape character references. All character references (both entity references and numerical character references) are unescaped. """ return self.__characterrefs.sub(self.__unescape, text) _html_entities_escaper = XMLEntityEscaper(codepoint2name, name2codepoint) html_entities_escape = _html_entities_escaper.escape_entities html_entities_unescape = _html_entities_escaper.unescape def htmlentityreplace_errors(ex): """An encoding error handler. This python `codecs`_ error handler replaces unencodable characters with HTML entities, or, if no HTML entity exists for the character, XML character references. >>> u'The cost was \u20ac12.'.encode('latin1', 'htmlentityreplace') 'The cost was €12.' """ if isinstance(ex, UnicodeEncodeError): # Handle encoding errors bad_text = ex.object[ex.start:ex.end] text = _html_entities_escaper.escape(bad_text) return (compat.text_type(text), ex.end) raise ex codecs.register_error('htmlentityreplace', htmlentityreplace_errors) # TODO: options to make this dynamic per-compilation will be added in a later # release DEFAULT_ESCAPES = { 'x': 'filters.xml_escape', 'h': 'filters.html_escape', 'u': 'filters.url_escape', 'trim': 'filters.trim', 'entity': 'filters.html_entities_escape', 'unicode': 'unicode', 'decode': 'decode', 'str': 'str', 'n': 'n' } if compat.py3k: DEFAULT_ESCAPES.update({ 'unicode': 'str' }) NON_UNICODE_ESCAPES = DEFAULT_ESCAPES.copy() NON_UNICODE_ESCAPES['h'] = 'filters.legacy_html_escape' NON_UNICODE_ESCAPES['u'] = 'filters.legacy_url_escape' Mako-1.0.7/mako/util.py0000664000175000017500000002525713131715725015772 0ustar classicclassic00000000000000# mako/util.py # Copyright (C) 2006-2016 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php import re import collections import codecs import os from mako import compat import operator def update_wrapper(decorated, fn): decorated.__wrapped__ = fn decorated.__name__ = fn.__name__ return decorated class PluginLoader(object): def __init__(self, group): self.group = group self.impls = {} def load(self, name): if name in self.impls: return self.impls[name]() else: import pkg_resources for impl in pkg_resources.iter_entry_points( self.group, name): self.impls[name] = impl.load return impl.load() else: from mako import exceptions raise exceptions.RuntimeException( "Can't load plugin %s %s" % (self.group, name)) def register(self, name, modulepath, objname): def load(): mod = __import__(modulepath) for token in modulepath.split(".")[1:]: mod = getattr(mod, token) return getattr(mod, objname) self.impls[name] = load def verify_directory(dir): """create and/or verify a filesystem directory.""" tries = 0 while not os.path.exists(dir): try: tries += 1 os.makedirs(dir, compat.octal("0775")) except: if tries > 5: raise def to_list(x, default=None): if x is None: return default if not isinstance(x, (list, tuple)): return [x] else: return x class memoized_property(object): """A read-only @property that is only evaluated once.""" def __init__(self, fget, doc=None): self.fget = fget self.__doc__ = doc or fget.__doc__ self.__name__ = fget.__name__ def __get__(self, obj, cls): if obj is None: return self obj.__dict__[self.__name__] = result = self.fget(obj) return result class memoized_instancemethod(object): """Decorate a method memoize its return value. Best applied to no-arg methods: memoization is not sensitive to argument values, and will always return the same value even when called with different arguments. """ def __init__(self, fget, doc=None): self.fget = fget self.__doc__ = doc or fget.__doc__ self.__name__ = fget.__name__ def __get__(self, obj, cls): if obj is None: return self def oneshot(*args, **kw): result = self.fget(obj, *args, **kw) memo = lambda *a, **kw: result memo.__name__ = self.__name__ memo.__doc__ = self.__doc__ obj.__dict__[self.__name__] = memo return result oneshot.__name__ = self.__name__ oneshot.__doc__ = self.__doc__ return oneshot class SetLikeDict(dict): """a dictionary that has some setlike methods on it""" def union(self, other): """produce a 'union' of this dict and another (at the key level). values in the second dict take precedence over that of the first""" x = SetLikeDict(**self) x.update(other) return x class FastEncodingBuffer(object): """a very rudimentary buffer that is faster than StringIO, but doesn't crash on unicode data like cStringIO.""" def __init__(self, encoding=None, errors='strict', as_unicode=False): self.data = collections.deque() self.encoding = encoding if as_unicode: self.delim = compat.u('') else: self.delim = '' self.as_unicode = as_unicode self.errors = errors self.write = self.data.append def truncate(self): self.data = collections.deque() self.write = self.data.append def getvalue(self): if self.encoding: return self.delim.join(self.data).encode(self.encoding, self.errors) else: return self.delim.join(self.data) class LRUCache(dict): """A dictionary-like object that stores a limited number of items, discarding lesser used items periodically. this is a rewrite of LRUCache from Myghty to use a periodic timestamp-based paradigm so that synchronization is not really needed. the size management is inexact. """ class _Item(object): def __init__(self, key, value): self.key = key self.value = value self.timestamp = compat.time_func() def __repr__(self): return repr(self.value) def __init__(self, capacity, threshold=.5): self.capacity = capacity self.threshold = threshold def __getitem__(self, key): item = dict.__getitem__(self, key) item.timestamp = compat.time_func() return item.value def values(self): return [i.value for i in dict.values(self)] def setdefault(self, key, value): if key in self: return self[key] else: self[key] = value return value def __setitem__(self, key, value): item = dict.get(self, key) if item is None: item = self._Item(key, value) dict.__setitem__(self, key, item) else: item.value = value self._manage_size() def _manage_size(self): while len(self) > self.capacity + self.capacity * self.threshold: bytime = sorted(dict.values(self), key=operator.attrgetter('timestamp'), reverse=True) for item in bytime[self.capacity:]: try: del self[item.key] except KeyError: # if we couldn't find a key, most likely some other thread # broke in on us. loop around and try again break # Regexp to match python magic encoding line _PYTHON_MAGIC_COMMENT_re = re.compile( r'[ \t\f]* \# .* coding[=:][ \t]*([-\w.]+)', re.VERBOSE) def parse_encoding(fp): """Deduce the encoding of a Python source file (binary mode) from magic comment. It does this in the same way as the `Python interpreter`__ .. __: http://docs.python.org/ref/encodings.html The ``fp`` argument should be a seekable file object in binary mode. """ pos = fp.tell() fp.seek(0) try: line1 = fp.readline() has_bom = line1.startswith(codecs.BOM_UTF8) if has_bom: line1 = line1[len(codecs.BOM_UTF8):] m = _PYTHON_MAGIC_COMMENT_re.match(line1.decode('ascii', 'ignore')) if not m: try: import parser parser.suite(line1.decode('ascii', 'ignore')) except (ImportError, SyntaxError): # Either it's a real syntax error, in which case the source # is not valid python source, or line2 is a continuation of # line1, in which case we don't want to scan line2 for a magic # comment. pass else: line2 = fp.readline() m = _PYTHON_MAGIC_COMMENT_re.match( line2.decode('ascii', 'ignore')) if has_bom: if m: raise SyntaxError( "python refuses to compile code with both a UTF8" " byte-order-mark and a magic encoding comment") return 'utf_8' elif m: return m.group(1) else: return None finally: fp.seek(pos) def sorted_dict_repr(d): """repr() a dictionary with the keys in order. Used by the lexer unit test to compare parse trees based on strings. """ keys = list(d.keys()) keys.sort() return "{" + ", ".join(["%r: %r" % (k, d[k]) for k in keys]) + "}" def restore__ast(_ast): """Attempt to restore the required classes to the _ast module if it appears to be missing them """ if hasattr(_ast, 'AST'): return _ast.PyCF_ONLY_AST = 2 << 9 m = compile("""\ def foo(): pass class Bar(object): pass if False: pass baz = 'mako' 1 + 2 - 3 * 4 / 5 6 // 7 % 8 << 9 >> 10 11 & 12 ^ 13 | 14 15 and 16 or 17 -baz + (not +18) - ~17 baz and 'foo' or 'bar' (mako is baz == baz) is not baz != mako mako > baz < mako >= baz <= mako mako in baz not in mako""", '', 'exec', _ast.PyCF_ONLY_AST) _ast.Module = type(m) for cls in _ast.Module.__mro__: if cls.__name__ == 'mod': _ast.mod = cls elif cls.__name__ == 'AST': _ast.AST = cls _ast.FunctionDef = type(m.body[0]) _ast.ClassDef = type(m.body[1]) _ast.If = type(m.body[2]) _ast.Name = type(m.body[3].targets[0]) _ast.Store = type(m.body[3].targets[0].ctx) _ast.Str = type(m.body[3].value) _ast.Sub = type(m.body[4].value.op) _ast.Add = type(m.body[4].value.left.op) _ast.Div = type(m.body[4].value.right.op) _ast.Mult = type(m.body[4].value.right.left.op) _ast.RShift = type(m.body[5].value.op) _ast.LShift = type(m.body[5].value.left.op) _ast.Mod = type(m.body[5].value.left.left.op) _ast.FloorDiv = type(m.body[5].value.left.left.left.op) _ast.BitOr = type(m.body[6].value.op) _ast.BitXor = type(m.body[6].value.left.op) _ast.BitAnd = type(m.body[6].value.left.left.op) _ast.Or = type(m.body[7].value.op) _ast.And = type(m.body[7].value.values[0].op) _ast.Invert = type(m.body[8].value.right.op) _ast.Not = type(m.body[8].value.left.right.op) _ast.UAdd = type(m.body[8].value.left.right.operand.op) _ast.USub = type(m.body[8].value.left.left.op) _ast.Or = type(m.body[9].value.op) _ast.And = type(m.body[9].value.values[0].op) _ast.IsNot = type(m.body[10].value.ops[0]) _ast.NotEq = type(m.body[10].value.ops[1]) _ast.Is = type(m.body[10].value.left.ops[0]) _ast.Eq = type(m.body[10].value.left.ops[1]) _ast.Gt = type(m.body[11].value.ops[0]) _ast.Lt = type(m.body[11].value.ops[1]) _ast.GtE = type(m.body[11].value.ops[2]) _ast.LtE = type(m.body[11].value.ops[3]) _ast.In = type(m.body[12].value.ops[0]) _ast.NotIn = type(m.body[12].value.ops[1]) def read_file(path, mode='rb'): fp = open(path, mode) try: data = fp.read() return data finally: fp.close() def read_python_file(path): fp = open(path, "rb") try: encoding = parse_encoding(fp) data = fp.read() if encoding: data = data.decode(encoding) return data finally: fp.close() Mako-1.0.7/mako/pygen.py0000664000175000017500000002334013131715725016126 0ustar classicclassic00000000000000# mako/pygen.py # Copyright (C) 2006-2016 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php """utilities for generating and formatting literal Python code.""" import re from mako import exceptions class PythonPrinter(object): def __init__(self, stream): # indentation counter self.indent = 0 # a stack storing information about why we incremented # the indentation counter, to help us determine if we # should decrement it self.indent_detail = [] # the string of whitespace multiplied by the indent # counter to produce a line self.indentstring = " " # the stream we are writing to self.stream = stream # current line number self.lineno = 1 # a list of lines that represents a buffered "block" of code, # which can be later printed relative to an indent level self.line_buffer = [] self.in_indent_lines = False self._reset_multi_line_flags() # mapping of generated python lines to template # source lines self.source_map = {} def _update_lineno(self, num): self.lineno += num def start_source(self, lineno): if self.lineno not in self.source_map: self.source_map[self.lineno] = lineno def write_blanks(self, num): self.stream.write("\n" * num) self._update_lineno(num) def write_indented_block(self, block): """print a line or lines of python which already contain indentation. The indentation of the total block of lines will be adjusted to that of the current indent level.""" self.in_indent_lines = False for l in re.split(r'\r?\n', block): self.line_buffer.append(l) self._update_lineno(1) def writelines(self, *lines): """print a series of lines of python.""" for line in lines: self.writeline(line) def writeline(self, line): """print a line of python, indenting it according to the current indent level. this also adjusts the indentation counter according to the content of the line. """ if not self.in_indent_lines: self._flush_adjusted_lines() self.in_indent_lines = True if ( line is None or re.match(r"^\s*#", line) or re.match(r"^\s*$", line) ): hastext = False else: hastext = True is_comment = line and len(line) and line[0] == '#' # see if this line should decrease the indentation level if ( not is_comment and (not hastext or self._is_unindentor(line)) ): if self.indent > 0: self.indent -= 1 # if the indent_detail stack is empty, the user # probably put extra closures - the resulting # module wont compile. if len(self.indent_detail) == 0: raise exceptions.SyntaxException( "Too many whitespace closures") self.indent_detail.pop() if line is None: return # write the line self.stream.write(self._indent_line(line) + "\n") self._update_lineno(len(line.split("\n"))) # see if this line should increase the indentation level. # note that a line can both decrase (before printing) and # then increase (after printing) the indentation level. if re.search(r":[ \t]*(?:#.*)?$", line): # increment indentation count, and also # keep track of what the keyword was that indented us, # if it is a python compound statement keyword # where we might have to look for an "unindent" keyword match = re.match(r"^\s*(if|try|elif|while|for|with)", line) if match: # its a "compound" keyword, so we will check for "unindentors" indentor = match.group(1) self.indent += 1 self.indent_detail.append(indentor) else: indentor = None # its not a "compound" keyword. but lets also # test for valid Python keywords that might be indenting us, # else assume its a non-indenting line m2 = re.match(r"^\s*(def|class|else|elif|except|finally)", line) if m2: self.indent += 1 self.indent_detail.append(indentor) def close(self): """close this printer, flushing any remaining lines.""" self._flush_adjusted_lines() def _is_unindentor(self, line): """return true if the given line is an 'unindentor', relative to the last 'indent' event received. """ # no indentation detail has been pushed on; return False if len(self.indent_detail) == 0: return False indentor = self.indent_detail[-1] # the last indent keyword we grabbed is not a # compound statement keyword; return False if indentor is None: return False # if the current line doesnt have one of the "unindentor" keywords, # return False match = re.match(r"^\s*(else|elif|except|finally).*\:", line) if not match: return False # whitespace matches up, we have a compound indentor, # and this line has an unindentor, this # is probably good enough return True # should we decide that its not good enough, heres # more stuff to check. # keyword = match.group(1) # match the original indent keyword # for crit in [ # (r'if|elif', r'else|elif'), # (r'try', r'except|finally|else'), # (r'while|for', r'else'), # ]: # if re.match(crit[0], indentor) and re.match(crit[1], keyword): # return True # return False def _indent_line(self, line, stripspace=''): """indent the given line according to the current indent level. stripspace is a string of space that will be truncated from the start of the line before indenting.""" return re.sub(r"^%s" % stripspace, self.indentstring * self.indent, line) def _reset_multi_line_flags(self): """reset the flags which would indicate we are in a backslashed or triple-quoted section.""" self.backslashed, self.triplequoted = False, False def _in_multi_line(self, line): """return true if the given line is part of a multi-line block, via backslash or triple-quote.""" # we are only looking for explicitly joined lines here, not # implicit ones (i.e. brackets, braces etc.). this is just to # guard against the possibility of modifying the space inside of # a literal multiline string with unfortunately placed # whitespace current_state = (self.backslashed or self.triplequoted) if re.search(r"\\$", line): self.backslashed = True else: self.backslashed = False triples = len(re.findall(r"\"\"\"|\'\'\'", line)) if triples == 1 or triples % 2 != 0: self.triplequoted = not self.triplequoted return current_state def _flush_adjusted_lines(self): stripspace = None self._reset_multi_line_flags() for entry in self.line_buffer: if self._in_multi_line(entry): self.stream.write(entry + "\n") else: entry = entry.expandtabs() if stripspace is None and re.search(r"^[ \t]*[^# \t]", entry): stripspace = re.match(r"^([ \t]*)", entry).group(1) self.stream.write(self._indent_line(entry, stripspace) + "\n") self.line_buffer = [] self._reset_multi_line_flags() def adjust_whitespace(text): """remove the left-whitespace margin of a block of Python code.""" state = [False, False] (backslashed, triplequoted) = (0, 1) def in_multi_line(line): start_state = (state[backslashed] or state[triplequoted]) if re.search(r"\\$", line): state[backslashed] = True else: state[backslashed] = False def match(reg, t): m = re.match(reg, t) if m: return m, t[len(m.group(0)):] else: return None, t while line: if state[triplequoted]: m, line = match(r"%s" % state[triplequoted], line) if m: state[triplequoted] = False else: m, line = match(r".*?(?=%s|$)" % state[triplequoted], line) else: m, line = match(r'#', line) if m: return start_state m, line = match(r"\"\"\"|\'\'\'", line) if m: state[triplequoted] = m.group(0) continue m, line = match(r".*?(?=\"\"\"|\'\'\'|#|$)", line) return start_state def _indent_line(line, stripspace=''): return re.sub(r"^%s" % stripspace, '', line) lines = [] stripspace = None for line in re.split(r'\r?\n', text): if in_multi_line(line): lines.append(line) else: line = line.expandtabs() if stripspace is None and re.search(r"^[ \t]*[^# \t]", line): stripspace = re.match(r"^([ \t]*)", line).group(1) lines.append(_indent_line(line, stripspace)) return "\n".join(lines) Mako-1.0.7/mako/__init__.py0000664000175000017500000000036613131715725016546 0ustar classicclassic00000000000000# mako/__init__.py # Copyright (C) 2006-2016 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php __version__ = '1.0.7' Mako-1.0.7/mako/runtime.py0000664000175000017500000006640113131715725016474 0ustar classicclassic00000000000000# mako/runtime.py # Copyright (C) 2006-2016 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php """provides runtime services for templates, including Context, Namespace, and various helper functions.""" from mako import exceptions, util, compat from mako.compat import compat_builtins import sys class Context(object): """Provides runtime namespace, output buffer, and various callstacks for templates. See :ref:`runtime_toplevel` for detail on the usage of :class:`.Context`. """ def __init__(self, buffer, **data): self._buffer_stack = [buffer] self._data = data self._kwargs = data.copy() self._with_template = None self._outputting_as_unicode = None self.namespaces = {} # "capture" function which proxies to the # generic "capture" function self._data['capture'] = compat.partial(capture, self) # "caller" stack used by def calls with content self.caller_stack = self._data['caller'] = CallerStack() def _set_with_template(self, t): self._with_template = t illegal_names = t.reserved_names.intersection(self._data) if illegal_names: raise exceptions.NameConflictError( "Reserved words passed to render(): %s" % ", ".join(illegal_names)) @property def lookup(self): """Return the :class:`.TemplateLookup` associated with this :class:`.Context`. """ return self._with_template.lookup @property def kwargs(self): """Return the dictionary of top level keyword arguments associated with this :class:`.Context`. This dictionary only includes the top-level arguments passed to :meth:`.Template.render`. It does not include names produced within the template execution such as local variable names or special names such as ``self``, ``next``, etc. The purpose of this dictionary is primarily for the case that a :class:`.Template` accepts arguments via its ``<%page>`` tag, which are normally expected to be passed via :meth:`.Template.render`, except the template is being called in an inheritance context, using the ``body()`` method. :attr:`.Context.kwargs` can then be used to propagate these arguments to the inheriting template:: ${next.body(**context.kwargs)} """ return self._kwargs.copy() def push_caller(self, caller): """Push a ``caller`` callable onto the callstack for this :class:`.Context`.""" self.caller_stack.append(caller) def pop_caller(self): """Pop a ``caller`` callable onto the callstack for this :class:`.Context`.""" del self.caller_stack[-1] def keys(self): """Return a list of all names established in this :class:`.Context`.""" return list(self._data.keys()) def __getitem__(self, key): if key in self._data: return self._data[key] else: return compat_builtins.__dict__[key] def _push_writer(self): """push a capturing buffer onto this Context and return the new writer function.""" buf = util.FastEncodingBuffer() self._buffer_stack.append(buf) return buf.write def _pop_buffer_and_writer(self): """pop the most recent capturing buffer from this Context and return the current writer after the pop. """ buf = self._buffer_stack.pop() return buf, self._buffer_stack[-1].write def _push_buffer(self): """push a capturing buffer onto this Context.""" self._push_writer() def _pop_buffer(self): """pop the most recent capturing buffer from this Context.""" return self._buffer_stack.pop() def get(self, key, default=None): """Return a value from this :class:`.Context`.""" return self._data.get(key, compat_builtins.__dict__.get(key, default)) def write(self, string): """Write a string to this :class:`.Context` object's underlying output buffer.""" self._buffer_stack[-1].write(string) def writer(self): """Return the current writer function.""" return self._buffer_stack[-1].write def _copy(self): c = Context.__new__(Context) c._buffer_stack = self._buffer_stack c._data = self._data.copy() c._kwargs = self._kwargs c._with_template = self._with_template c._outputting_as_unicode = self._outputting_as_unicode c.namespaces = self.namespaces c.caller_stack = self.caller_stack return c def _locals(self, d): """Create a new :class:`.Context` with a copy of this :class:`.Context`'s current state, updated with the given dictionary. The :attr:`.Context.kwargs` collection remains unaffected. """ if not d: return self c = self._copy() c._data.update(d) return c def _clean_inheritance_tokens(self): """create a new copy of this :class:`.Context`. with tokens related to inheritance state removed.""" c = self._copy() x = c._data x.pop('self', None) x.pop('parent', None) x.pop('next', None) return c class CallerStack(list): def __init__(self): self.nextcaller = None def __nonzero__(self): return self.__bool__() def __bool__(self): return len(self) and self._get_caller() and True or False def _get_caller(self): # this method can be removed once # codegen MAGIC_NUMBER moves past 7 return self[-1] def __getattr__(self, key): return getattr(self._get_caller(), key) def _push_frame(self): frame = self.nextcaller or None self.append(frame) self.nextcaller = None return frame def _pop_frame(self): self.nextcaller = self.pop() class Undefined(object): """Represents an undefined value in a template. All template modules have a constant value ``UNDEFINED`` present which is an instance of this object. """ def __str__(self): raise NameError("Undefined") def __nonzero__(self): return self.__bool__() def __bool__(self): return False UNDEFINED = Undefined() STOP_RENDERING = "" class LoopStack(object): """a stack for LoopContexts that implements the context manager protocol to automatically pop off the top of the stack on context exit """ def __init__(self): self.stack = [] def _enter(self, iterable): self._push(iterable) return self._top def _exit(self): self._pop() return self._top @property def _top(self): if self.stack: return self.stack[-1] else: return self def _pop(self): return self.stack.pop() def _push(self, iterable): new = LoopContext(iterable) if self.stack: new.parent = self.stack[-1] return self.stack.append(new) def __getattr__(self, key): raise exceptions.RuntimeException("No loop context is established") def __iter__(self): return iter(self._top) class LoopContext(object): """A magic loop variable. Automatically accessible in any ``% for`` block. See the section :ref:`loop_context` for usage notes. :attr:`parent` -> :class:`.LoopContext` or ``None`` The parent loop, if one exists. :attr:`index` -> `int` The 0-based iteration count. :attr:`reverse_index` -> `int` The number of iterations remaining. :attr:`first` -> `bool` ``True`` on the first iteration, ``False`` otherwise. :attr:`last` -> `bool` ``True`` on the last iteration, ``False`` otherwise. :attr:`even` -> `bool` ``True`` when ``index`` is even. :attr:`odd` -> `bool` ``True`` when ``index`` is odd. """ def __init__(self, iterable): self._iterable = iterable self.index = 0 self.parent = None def __iter__(self): for i in self._iterable: yield i self.index += 1 @util.memoized_instancemethod def __len__(self): return len(self._iterable) @property def reverse_index(self): return len(self) - self.index - 1 @property def first(self): return self.index == 0 @property def last(self): return self.index == len(self) - 1 @property def even(self): return not self.odd @property def odd(self): return bool(self.index % 2) def cycle(self, *values): """Cycle through values as the loop progresses. """ if not values: raise ValueError("You must provide values to cycle through") return values[self.index % len(values)] class _NSAttr(object): def __init__(self, parent): self.__parent = parent def __getattr__(self, key): ns = self.__parent while ns: if hasattr(ns.module, key): return getattr(ns.module, key) else: ns = ns.inherits raise AttributeError(key) class Namespace(object): """Provides access to collections of rendering methods, which can be local, from other templates, or from imported modules. To access a particular rendering method referenced by a :class:`.Namespace`, use plain attribute access: .. sourcecode:: mako ${some_namespace.foo(x, y, z)} :class:`.Namespace` also contains several built-in attributes described here. """ def __init__(self, name, context, callables=None, inherits=None, populate_self=True, calling_uri=None): self.name = name self.context = context self.inherits = inherits if callables is not None: self.callables = dict([(c.__name__, c) for c in callables]) callables = () module = None """The Python module referenced by this :class:`.Namespace`. If the namespace references a :class:`.Template`, then this module is the equivalent of ``template.module``, i.e. the generated module for the template. """ template = None """The :class:`.Template` object referenced by this :class:`.Namespace`, if any. """ context = None """The :class:`.Context` object for this :class:`.Namespace`. Namespaces are often created with copies of contexts that contain slightly different data, particularly in inheritance scenarios. Using the :class:`.Context` off of a :class:`.Namespace` one can traverse an entire chain of templates that inherit from one-another. """ filename = None """The path of the filesystem file used for this :class:`.Namespace`'s module or template. If this is a pure module-based :class:`.Namespace`, this evaluates to ``module.__file__``. If a template-based namespace, it evaluates to the original template file location. """ uri = None """The URI for this :class:`.Namespace`'s template. I.e. whatever was sent to :meth:`.TemplateLookup.get_template()`. This is the equivalent of :attr:`.Template.uri`. """ _templateuri = None @util.memoized_property def attr(self): """Access module level attributes by name. This accessor allows templates to supply "scalar" attributes which are particularly handy in inheritance relationships. .. seealso:: :ref:`inheritance_attr` :ref:`namespace_attr_for_includes` """ return _NSAttr(self) def get_namespace(self, uri): """Return a :class:`.Namespace` corresponding to the given ``uri``. If the given ``uri`` is a relative URI (i.e. it does not contain a leading slash ``/``), the ``uri`` is adjusted to be relative to the ``uri`` of the namespace itself. This method is therefore mostly useful off of the built-in ``local`` namespace, described in :ref:`namespace_local`. In most cases, a template wouldn't need this function, and should instead use the ``<%namespace>`` tag to load namespaces. However, since all ``<%namespace>`` tags are evaluated before the body of a template ever runs, this method can be used to locate namespaces using expressions that were generated within the body code of the template, or to conditionally use a particular namespace. """ key = (self, uri) if key in self.context.namespaces: return self.context.namespaces[key] else: ns = TemplateNamespace(uri, self.context._copy(), templateuri=uri, calling_uri=self._templateuri) self.context.namespaces[key] = ns return ns def get_template(self, uri): """Return a :class:`.Template` from the given ``uri``. The ``uri`` resolution is relative to the ``uri`` of this :class:`.Namespace` object's :class:`.Template`. """ return _lookup_template(self.context, uri, self._templateuri) def get_cached(self, key, **kwargs): """Return a value from the :class:`.Cache` referenced by this :class:`.Namespace` object's :class:`.Template`. The advantage to this method versus direct access to the :class:`.Cache` is that the configuration parameters declared in ``<%page>`` take effect here, thereby calling up the same configured backend as that configured by ``<%page>``. """ return self.cache.get(key, **kwargs) @property def cache(self): """Return the :class:`.Cache` object referenced by this :class:`.Namespace` object's :class:`.Template`. """ return self.template.cache def include_file(self, uri, **kwargs): """Include a file at the given ``uri``.""" _include_file(self.context, uri, self._templateuri, **kwargs) def _populate(self, d, l): for ident in l: if ident == '*': for (k, v) in self._get_star(): d[k] = v else: d[ident] = getattr(self, ident) def _get_star(self): if self.callables: for key in self.callables: yield (key, self.callables[key]) def __getattr__(self, key): if key in self.callables: val = self.callables[key] elif self.inherits: val = getattr(self.inherits, key) else: raise AttributeError( "Namespace '%s' has no member '%s'" % (self.name, key)) setattr(self, key, val) return val class TemplateNamespace(Namespace): """A :class:`.Namespace` specific to a :class:`.Template` instance.""" def __init__(self, name, context, template=None, templateuri=None, callables=None, inherits=None, populate_self=True, calling_uri=None): self.name = name self.context = context self.inherits = inherits if callables is not None: self.callables = dict([(c.__name__, c) for c in callables]) if templateuri is not None: self.template = _lookup_template(context, templateuri, calling_uri) self._templateuri = self.template.module._template_uri elif template is not None: self.template = template self._templateuri = template.module._template_uri else: raise TypeError("'template' argument is required.") if populate_self: lclcallable, lclcontext = \ _populate_self_namespace(context, self.template, self_ns=self) @property def module(self): """The Python module referenced by this :class:`.Namespace`. If the namespace references a :class:`.Template`, then this module is the equivalent of ``template.module``, i.e. the generated module for the template. """ return self.template.module @property def filename(self): """The path of the filesystem file used for this :class:`.Namespace`'s module or template. """ return self.template.filename @property def uri(self): """The URI for this :class:`.Namespace`'s template. I.e. whatever was sent to :meth:`.TemplateLookup.get_template()`. This is the equivalent of :attr:`.Template.uri`. """ return self.template.uri def _get_star(self): if self.callables: for key in self.callables: yield (key, self.callables[key]) def get(key): callable_ = self.template._get_def_callable(key) return compat.partial(callable_, self.context) for k in self.template.module._exports: yield (k, get(k)) def __getattr__(self, key): if key in self.callables: val = self.callables[key] elif self.template.has_def(key): callable_ = self.template._get_def_callable(key) val = compat.partial(callable_, self.context) elif self.inherits: val = getattr(self.inherits, key) else: raise AttributeError( "Namespace '%s' has no member '%s'" % (self.name, key)) setattr(self, key, val) return val class ModuleNamespace(Namespace): """A :class:`.Namespace` specific to a Python module instance.""" def __init__(self, name, context, module, callables=None, inherits=None, populate_self=True, calling_uri=None): self.name = name self.context = context self.inherits = inherits if callables is not None: self.callables = dict([(c.__name__, c) for c in callables]) mod = __import__(module) for token in module.split('.')[1:]: mod = getattr(mod, token) self.module = mod @property def filename(self): """The path of the filesystem file used for this :class:`.Namespace`'s module or template. """ return self.module.__file__ def _get_star(self): if self.callables: for key in self.callables: yield (key, self.callables[key]) for key in dir(self.module): if key[0] != '_': callable_ = getattr(self.module, key) if compat.callable(callable_): yield key, compat.partial(callable_, self.context) def __getattr__(self, key): if key in self.callables: val = self.callables[key] elif hasattr(self.module, key): callable_ = getattr(self.module, key) val = compat.partial(callable_, self.context) elif self.inherits: val = getattr(self.inherits, key) else: raise AttributeError( "Namespace '%s' has no member '%s'" % (self.name, key)) setattr(self, key, val) return val def supports_caller(func): """Apply a caller_stack compatibility decorator to a plain Python function. See the example in :ref:`namespaces_python_modules`. """ def wrap_stackframe(context, *args, **kwargs): context.caller_stack._push_frame() try: return func(context, *args, **kwargs) finally: context.caller_stack._pop_frame() return wrap_stackframe def capture(context, callable_, *args, **kwargs): """Execute the given template def, capturing the output into a buffer. See the example in :ref:`namespaces_python_modules`. """ if not compat.callable(callable_): raise exceptions.RuntimeException( "capture() function expects a callable as " "its argument (i.e. capture(func, *args, **kwargs))" ) context._push_buffer() try: callable_(*args, **kwargs) finally: buf = context._pop_buffer() return buf.getvalue() def _decorate_toplevel(fn): def decorate_render(render_fn): def go(context, *args, **kw): def y(*args, **kw): return render_fn(context, *args, **kw) try: y.__name__ = render_fn.__name__[7:] except TypeError: # < Python 2.4 pass return fn(y)(context, *args, **kw) return go return decorate_render def _decorate_inline(context, fn): def decorate_render(render_fn): dec = fn(render_fn) def go(*args, **kw): return dec(context, *args, **kw) return go return decorate_render def _include_file(context, uri, calling_uri, **kwargs): """locate the template from the given uri and include it in the current output.""" template = _lookup_template(context, uri, calling_uri) (callable_, ctx) = _populate_self_namespace( context._clean_inheritance_tokens(), template) kwargs = _kwargs_for_include(callable_, context._data, **kwargs) if template.include_error_handler: try: callable_(ctx, **kwargs) except Exception: result = template.include_error_handler(ctx, compat.exception_as()) if not result: compat.reraise(*sys.exc_info()) else: callable_(ctx, **kwargs) def _inherit_from(context, uri, calling_uri): """called by the _inherit method in template modules to set up the inheritance chain at the start of a template's execution.""" if uri is None: return None template = _lookup_template(context, uri, calling_uri) self_ns = context['self'] ih = self_ns while ih.inherits is not None: ih = ih.inherits lclcontext = context._locals({'next': ih}) ih.inherits = TemplateNamespace("self:%s" % template.uri, lclcontext, template=template, populate_self=False) context._data['parent'] = lclcontext._data['local'] = ih.inherits callable_ = getattr(template.module, '_mako_inherit', None) if callable_ is not None: ret = callable_(template, lclcontext) if ret: return ret gen_ns = getattr(template.module, '_mako_generate_namespaces', None) if gen_ns is not None: gen_ns(context) return (template.callable_, lclcontext) def _lookup_template(context, uri, relativeto): lookup = context._with_template.lookup if lookup is None: raise exceptions.TemplateLookupException( "Template '%s' has no TemplateLookup associated" % context._with_template.uri) uri = lookup.adjust_uri(uri, relativeto) try: return lookup.get_template(uri) except exceptions.TopLevelLookupException: raise exceptions.TemplateLookupException(str(compat.exception_as())) def _populate_self_namespace(context, template, self_ns=None): if self_ns is None: self_ns = TemplateNamespace('self:%s' % template.uri, context, template=template, populate_self=False) context._data['self'] = context._data['local'] = self_ns if hasattr(template.module, '_mako_inherit'): ret = template.module._mako_inherit(template, context) if ret: return ret return (template.callable_, context) def _render(template, callable_, args, data, as_unicode=False): """create a Context and return the string output of the given template and template callable.""" if as_unicode: buf = util.FastEncodingBuffer(as_unicode=True) elif template.bytestring_passthrough: buf = compat.StringIO() else: buf = util.FastEncodingBuffer( as_unicode=as_unicode, encoding=template.output_encoding, errors=template.encoding_errors) context = Context(buf, **data) context._outputting_as_unicode = as_unicode context._set_with_template(template) _render_context(template, callable_, context, *args, **_kwargs_for_callable(callable_, data)) return context._pop_buffer().getvalue() def _kwargs_for_callable(callable_, data): argspec = compat.inspect_func_args(callable_) # for normal pages, **pageargs is usually present if argspec[2]: return data # for rendering defs from the top level, figure out the args namedargs = argspec[0] + [v for v in argspec[1:3] if v is not None] kwargs = {} for arg in namedargs: if arg != 'context' and arg in data and arg not in kwargs: kwargs[arg] = data[arg] return kwargs def _kwargs_for_include(callable_, data, **kwargs): argspec = compat.inspect_func_args(callable_) namedargs = argspec[0] + [v for v in argspec[1:3] if v is not None] for arg in namedargs: if arg != 'context' and arg in data and arg not in kwargs: kwargs[arg] = data[arg] return kwargs def _render_context(tmpl, callable_, context, *args, **kwargs): import mako.template as template # create polymorphic 'self' namespace for this # template with possibly updated context if not isinstance(tmpl, template.DefTemplate): # if main render method, call from the base of the inheritance stack (inherit, lclcontext) = _populate_self_namespace(context, tmpl) _exec_template(inherit, lclcontext, args=args, kwargs=kwargs) else: # otherwise, call the actual rendering method specified (inherit, lclcontext) = _populate_self_namespace(context, tmpl.parent) _exec_template(callable_, context, args=args, kwargs=kwargs) def _exec_template(callable_, context, args=None, kwargs=None): """execute a rendering callable given the callable, a Context, and optional explicit arguments the contextual Template will be located if it exists, and the error handling options specified on that Template will be interpreted here. """ template = context._with_template if template is not None and \ (template.format_exceptions or template.error_handler): try: callable_(context, *args, **kwargs) except Exception: _render_error(template, context, compat.exception_as()) except: e = sys.exc_info()[0] _render_error(template, context, e) else: callable_(context, *args, **kwargs) def _render_error(template, context, error): if template.error_handler: result = template.error_handler(context, error) if not result: compat.reraise(*sys.exc_info()) else: error_template = exceptions.html_error_template() if context._outputting_as_unicode: context._buffer_stack[:] = [ util.FastEncodingBuffer(as_unicode=True)] else: context._buffer_stack[:] = [util.FastEncodingBuffer( error_template.output_encoding, error_template.encoding_errors)] context._set_with_template(error_template) error_template.render_context(context, error=error) Mako-1.0.7/mako/ast.py0000664000175000017500000001475313131715725015603 0ustar classicclassic00000000000000# mako/ast.py # Copyright (C) 2006-2016 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php """utilities for analyzing expressions and blocks of Python code, as well as generating Python from AST nodes""" from mako import exceptions, pyparser, compat import re class PythonCode(object): """represents information about a string containing Python code""" def __init__(self, code, **exception_kwargs): self.code = code # represents all identifiers which are assigned to at some point in # the code self.declared_identifiers = set() # represents all identifiers which are referenced before their # assignment, if any self.undeclared_identifiers = set() # note that an identifier can be in both the undeclared and declared # lists. # using AST to parse instead of using code.co_varnames, # code.co_names has several advantages: # - we can locate an identifier as "undeclared" even if # its declared later in the same block of code # - AST is less likely to break with version changes # (for example, the behavior of co_names changed a little bit # in python version 2.5) if isinstance(code, compat.string_types): expr = pyparser.parse(code.lstrip(), "exec", **exception_kwargs) else: expr = code f = pyparser.FindIdentifiers(self, **exception_kwargs) f.visit(expr) class ArgumentList(object): """parses a fragment of code as a comma-separated list of expressions""" def __init__(self, code, **exception_kwargs): self.codeargs = [] self.args = [] self.declared_identifiers = set() self.undeclared_identifiers = set() if isinstance(code, compat.string_types): if re.match(r"\S", code) and not re.match(r",\s*$", code): # if theres text and no trailing comma, insure its parsed # as a tuple by adding a trailing comma code += "," expr = pyparser.parse(code, "exec", **exception_kwargs) else: expr = code f = pyparser.FindTuple(self, PythonCode, **exception_kwargs) f.visit(expr) class PythonFragment(PythonCode): """extends PythonCode to provide identifier lookups in partial control statements e.g. for x in 5: elif y==9: except (MyException, e): etc. """ def __init__(self, code, **exception_kwargs): m = re.match(r'^(\w+)(?:\s+(.*?))?:\s*(#|$)', code.strip(), re.S) if not m: raise exceptions.CompileException( "Fragment '%s' is not a partial control statement" % code, **exception_kwargs) if m.group(3): code = code[:m.start(3)] (keyword, expr) = m.group(1, 2) if keyword in ['for', 'if', 'while']: code = code + "pass" elif keyword == 'try': code = code + "pass\nexcept:pass" elif keyword == 'elif' or keyword == 'else': code = "if False:pass\n" + code + "pass" elif keyword == 'except': code = "try:pass\n" + code + "pass" elif keyword == 'with': code = code + "pass" else: raise exceptions.CompileException( "Unsupported control keyword: '%s'" % keyword, **exception_kwargs) super(PythonFragment, self).__init__(code, **exception_kwargs) class FunctionDecl(object): """function declaration""" def __init__(self, code, allow_kwargs=True, **exception_kwargs): self.code = code expr = pyparser.parse(code, "exec", **exception_kwargs) f = pyparser.ParseFunc(self, **exception_kwargs) f.visit(expr) if not hasattr(self, 'funcname'): raise exceptions.CompileException( "Code '%s' is not a function declaration" % code, **exception_kwargs) if not allow_kwargs and self.kwargs: raise exceptions.CompileException( "'**%s' keyword argument not allowed here" % self.kwargnames[-1], **exception_kwargs) def get_argument_expressions(self, as_call=False): """Return the argument declarations of this FunctionDecl as a printable list. By default the return value is appropriate for writing in a ``def``; set `as_call` to true to build arguments to be passed to the function instead (assuming locals with the same names as the arguments exist). """ namedecls = [] # Build in reverse order, since defaults and slurpy args come last argnames = self.argnames[::-1] kwargnames = self.kwargnames[::-1] defaults = self.defaults[::-1] kwdefaults = self.kwdefaults[::-1] # Named arguments if self.kwargs: namedecls.append("**" + kwargnames.pop(0)) for name in kwargnames: # Keyword-only arguments must always be used by name, so even if # this is a call, print out `foo=foo` if as_call: namedecls.append("%s=%s" % (name, name)) elif kwdefaults: default = kwdefaults.pop(0) if default is None: # The AST always gives kwargs a default, since you can do # `def foo(*, a=1, b, c=3)` namedecls.append(name) else: namedecls.append("%s=%s" % ( name, pyparser.ExpressionGenerator(default).value())) else: namedecls.append(name) # Positional arguments if self.varargs: namedecls.append("*" + argnames.pop(0)) for name in argnames: if as_call or not defaults: namedecls.append(name) else: default = defaults.pop(0) namedecls.append("%s=%s" % ( name, pyparser.ExpressionGenerator(default).value())) namedecls.reverse() return namedecls @property def allargnames(self): return tuple(self.argnames) + tuple(self.kwargnames) class FunctionArgs(FunctionDecl): """the argument portion of a function declaration""" def __init__(self, code, **kwargs): super(FunctionArgs, self).__init__("def ANON(%s):pass" % code, **kwargs) Mako-1.0.7/README.rst0000664000175000017500000000265413131715725015177 0ustar classicclassic00000000000000========================= Mako Templates for Python ========================= Mako is a template library written in Python. It provides a familiar, non-XML syntax which compiles into Python modules for maximum performance. Mako's syntax and API borrows from the best ideas of many others, including Django templates, Cheetah, Myghty, and Genshi. Conceptually, Mako is an embedded Python (i.e. Python Server Page) language, which refines the familiar ideas of componentized layout and inheritance to produce one of the most straightforward and flexible models available, while also maintaining close ties to Python calling and scoping semantics. Nutshell ======== :: <%inherit file="base.html"/> <% rows = [[v for v in range(0,10)] for row in range(0,10)] %> % for row in rows: ${makerow(row)} % endfor
<%def name="makerow(row)"> % for name in row: ${name}\ % endfor Philosophy =========== Python is a great scripting language. Don't reinvent the wheel...your templates can handle it ! Documentation ============== See documentation for Mako at http://www.makotemplates.org/docs/ License ======== Mako is licensed under an MIT-style license (see LICENSE). Other incorporated projects may be licensed under different licenses. All licenses allow for non-commercial and commercial use. Mako-1.0.7/MANIFEST.in0000664000175000017500000000055613131715725015245 0ustar classicclassic00000000000000# any kind of "*" pulls in __init__.pyc files, # so all extensions are explicit. recursive-include doc *.html *.css *.txt *.js *.png *.py Makefile *.rst *.mako recursive-include examples *.py *.xml *.mako *.myt *.kid *.tmpl recursive-include test *.py *.html *.mako include README* AUTHORS LICENSE distribute_setup.py ez_setup.py CHANGES* prune doc/build/output Mako-1.0.7/AUTHORS0000664000175000017500000000043213131715725014550 0ustar classicclassic00000000000000Mako was created by Michael Bayer. Major contributing authors include: - Michael Bayer - Geoffrey T. Dairiki - Philip Jenvey - David Peckam - Armin Ronacher - Ben Bangert - Ben Trofatter Mako-1.0.7/PKG-INFO0000664000175000017500000000510013131716021014560 0ustar classicclassic00000000000000Metadata-Version: 1.1 Name: Mako Version: 1.0.7 Summary: A super-fast templating language that borrows the best ideas from the existing templating languages. Home-page: http://www.makotemplates.org/ Author: Mike Bayer Author-email: mike@zzzcomputing.com License: MIT Description: ========================= Mako Templates for Python ========================= Mako is a template library written in Python. It provides a familiar, non-XML syntax which compiles into Python modules for maximum performance. Mako's syntax and API borrows from the best ideas of many others, including Django templates, Cheetah, Myghty, and Genshi. Conceptually, Mako is an embedded Python (i.e. Python Server Page) language, which refines the familiar ideas of componentized layout and inheritance to produce one of the most straightforward and flexible models available, while also maintaining close ties to Python calling and scoping semantics. Nutshell ======== :: <%inherit file="base.html"/> <% rows = [[v for v in range(0,10)] for row in range(0,10)] %> % for row in rows: ${makerow(row)} % endfor
<%def name="makerow(row)"> % for name in row: ${name}\ % endfor Philosophy =========== Python is a great scripting language. Don't reinvent the wheel...your templates can handle it ! Documentation ============== See documentation for Mako at http://www.makotemplates.org/docs/ License ======== Mako is licensed under an MIT-style license (see LICENSE). Other incorporated projects may be licensed under different licenses. All licenses allow for non-commercial and commercial use. Keywords: templates Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content Mako-1.0.7/examples/0000775000175000017500000000000013131716021015305 5ustar classicclassic00000000000000Mako-1.0.7/examples/bench/0000775000175000017500000000000013131716021016364 5ustar classicclassic00000000000000Mako-1.0.7/examples/bench/basic.py0000664000175000017500000001540113131715725020032 0ustar classicclassic00000000000000# basic.py - basic benchmarks adapted from Genshi # Copyright (C) 2006 Edgewall Software # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in # the documentation and/or other materials provided with the # distribution. # 3. The name of the author may not be used to endorse or promote # products derived from this software without specific prior # written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE # GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER # IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from cgi import escape import os try: from StringIO import StringIO except ImportError: from io import StringIO import sys import timeit def u(stringlit): if sys.version_info >= (3,): return stringlit else: return stringlit.decode('latin1') __all__ = ['mako', 'mako_inheritance', 'jinja2', 'jinja2_inheritance', 'cheetah', 'django', 'myghty', 'genshi', 'kid'] # Templates content and constants TITLE = 'Just a test' USER = 'joe' ITEMS = ['Number %d' % num for num in range(1, 15)] U_ITEMS = [u(item) for item in ITEMS] def genshi(dirname, verbose=False): from genshi.template import TemplateLoader loader = TemplateLoader([dirname], auto_reload=False) template = loader.load('template.html') def render(): data = dict(title=TITLE, user=USER, items=ITEMS) return template.generate(**data).render('xhtml') if verbose: print(render()) return render def myghty(dirname, verbose=False): from myghty import interp interpreter = interp.Interpreter(component_root=dirname) def render(): data = dict(title=TITLE, user=USER, items=ITEMS) buffer = StringIO() interpreter.execute("template.myt", request_args=data, out_buffer=buffer) return buffer.getvalue() if verbose: print(render()) return render def mako(dirname, verbose=False): from mako.template import Template from mako.lookup import TemplateLookup disable_unicode = (sys.version_info < (3,)) lookup = TemplateLookup(directories=[dirname], filesystem_checks=False, disable_unicode=disable_unicode) template = lookup.get_template('template.html') def render(): return template.render(title=TITLE, user=USER, list_items=U_ITEMS) if verbose: print(template.code + " " + render()) return render mako_inheritance = mako def jinja2(dirname, verbose=False): from jinja2 import Environment, FileSystemLoader env = Environment(loader=FileSystemLoader(dirname)) template = env.get_template('template.html') def render(): return template.render(title=TITLE, user=USER, list_items=U_ITEMS) if verbose: print(render()) return render jinja2_inheritance = jinja2 def cheetah(dirname, verbose=False): from Cheetah.Template import Template filename = os.path.join(dirname, 'template.tmpl') template = Template(file=filename) def render(): template.__dict__.update({'title': TITLE, 'user': USER, 'list_items': U_ITEMS}) return template.respond() if verbose: print(dir(template)) print(template.generatedModuleCode()) print(render()) return render def django(dirname, verbose=False): from django.conf import settings settings.configure(TEMPLATE_DIRS=[os.path.join(dirname, 'templates')]) from django import template, templatetags from django.template import loader templatetags.__path__.append(os.path.join(dirname, 'templatetags')) tmpl = loader.get_template('template.html') def render(): data = {'title': TITLE, 'user': USER, 'items': ITEMS} return tmpl.render(template.Context(data)) if verbose: print(render()) return render def kid(dirname, verbose=False): import kid kid.path = kid.TemplatePath([dirname]) template = kid.Template(file='template.kid') def render(): template = kid.Template(file='template.kid', title=TITLE, user=USER, items=ITEMS) return template.serialize(output='xhtml') if verbose: print(render()) return render def run(engines, number=2000, verbose=False): basepath = os.path.abspath(os.path.dirname(__file__)) for engine in engines: dirname = os.path.join(basepath, engine) if verbose: print('%s:' % engine.capitalize()) print('--------------------------------------------------------') else: sys.stdout.write('%s:' % engine.capitalize()) t = timeit.Timer(setup='from __main__ import %s; render = %s(r"%s", %s)' % (engine, engine, dirname, verbose), stmt='render()') time = t.timeit(number=number) / number if verbose: print('--------------------------------------------------------') print('%.2f ms' % (1000 * time)) if verbose: print('--------------------------------------------------------') if __name__ == '__main__': engines = [arg for arg in sys.argv[1:] if arg[0] != '-'] if not engines: engines = __all__ verbose = '-v' in sys.argv if '-p' in sys.argv: try: import hotshot, hotshot.stats prof = hotshot.Profile("template.prof") benchtime = prof.runcall(run, engines, number=100, verbose=verbose) stats = hotshot.stats.load("template.prof") except ImportError: import cProfile, pstats stmt = "run(%r, number=%r, verbose=%r)" % (engines, 1000, verbose) cProfile.runctx(stmt, globals(), {}, "template.prof") stats = pstats.Stats("template.prof") stats.strip_dirs() stats.sort_stats('time', 'calls') stats.print_stats() else: run(engines, verbose=verbose) Mako-1.0.7/examples/bench/myghty/0000775000175000017500000000000013131716021017705 5ustar classicclassic00000000000000Mako-1.0.7/examples/bench/myghty/template.myt0000664000175000017500000000077113131715725022272 0ustar classicclassic00000000000000<%flags>inherit="base.myt" <%args> title items user <%method header> <%args scope="request"> title <% title %>
<& base.myt:greeting, name=user &>
<& base.myt:greeting, name="me"&>
<& base.myt:greeting, name="world" &>

Loop

%if items:
    % for i, item in enumerate(items):
  • ><% item %>
  • %
% Mako-1.0.7/examples/bench/myghty/base.myt0000664000175000017500000000065713131715725021374 0ustar classicclassic00000000000000 <%args scope="request"> title <& REQUEST:header &> % m.call_next() <%method greeting> <%args> name Hello, <% name | h %> Mako-1.0.7/examples/bench/django/0000775000175000017500000000000013131716021017626 5ustar classicclassic00000000000000Mako-1.0.7/examples/bench/django/templatetags/0000775000175000017500000000000013131716021022320 5ustar classicclassic00000000000000Mako-1.0.7/examples/bench/django/templatetags/bench.py0000664000175000017500000000033413131715725023763 0ustar classicclassic00000000000000from django.template import Library, Node, resolve_variable from django.utils.html import escape register = Library() def greeting(name): return 'Hello, %s!' % escape(name) greeting = register.simple_tag(greeting) Mako-1.0.7/examples/bench/django/templatetags/__init__.py0000664000175000017500000000000013131715725024431 0ustar classicclassic00000000000000Mako-1.0.7/examples/bench/kid/0000775000175000017500000000000013131716021017133 5ustar classicclassic00000000000000Mako-1.0.7/examples/bench/kid/template.kid0000664000175000017500000000114213131715725021447 0ustar classicclassic00000000000000 ${title}
${greeting(user)}
${greeting('me')}
${greeting('world')}

Loop

Mako-1.0.7/examples/bench/kid/base.kid0000664000175000017500000000052013131715725020545 0ustar classicclassic00000000000000

Hello, ${name}!

${item}