summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2010-03-04 23:45:40 +0000
committerMike Bayer <mike_mp@zzzcomputing.com>2010-03-04 23:45:40 +0000
commita629df3f7ef4e36573671018a25e2e9aa0889dbf (patch)
treeeaefb6faad4bbaaf66ddfa27346b9cf2b29a414d
parent4d91d760cd4ef62192c74ff0aa6c27c3d6dff844 (diff)
downloadmako-a629df3f7ef4e36573671018a25e2e9aa0889dbf.tar.gz
- merged -r481:499 of py3k branch.
- Python 3 support is added ! See README.py3k for installation and testing notes. [ticket:119]
-rw-r--r--CHANGES4
-rw-r--r--README.py3k50
-rw-r--r--distribute_setup.py477
-rw-r--r--mako/ast.py36
-rw-r--r--mako/codegen.py13
-rw-r--r--mako/exceptions.py37
-rw-r--r--mako/filters.py7
-rw-r--r--mako/lexer.py112
-rw-r--r--mako/parsetree.py98
-rw-r--r--mako/pygen.py7
-rw-r--r--mako/pyparser.py49
-rw-r--r--mako/runtime.py41
-rw-r--r--mako/template.py26
-rw-r--r--mako/util.py35
-rw-r--r--setup.py10
-rw-r--r--test/__init__.py13
-rw-r--r--test/templates/chs_unicode_py3k.html11
-rw-r--r--test/templates/read_unicode_py3k.html10
-rw-r--r--test/templates/unicode_arguments_py3k.html10
-rw-r--r--test/templates/unicode_code_py3k.html7
-rw-r--r--test/templates/unicode_expr_py3k.html2
-rw-r--r--test/test_ast.py79
-rw-r--r--test/test_def.py33
-rw-r--r--test/test_exceptions.py59
-rw-r--r--test/test_inheritance.py27
-rw-r--r--test/test_lexer.py259
-rw-r--r--test/test_template.py223
27 files changed, 1400 insertions, 335 deletions
diff --git a/CHANGES b/CHANGES
index f048aed..d3ed5ea 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,5 +1,9 @@
0.3
- Python 2.3 support is dropped. [ticket:123]
+
+- Python 3 support is added ! See README.py3k
+ for installation and testing notes.
+ [ticket:119]
- Unit tests now run with nose. [ticket:127]
diff --git a/README.py3k b/README.py3k
new file mode 100644
index 0000000..3e3c8ab
--- /dev/null
+++ b/README.py3k
@@ -0,0 +1,50 @@
+=================
+PYTHON 3 SUPPORT
+=================
+
+Python 3 support in Mako is provided by the Python 2to3 script.
+
+Installing Distribute
+---------------------
+
+Distribute should be installed with the Python3 installation. The
+distribute bootloader is included.
+
+Running as a user with permission to modify the Python distribution,
+install Distribute:
+
+ python3 distribute_setup.py
+
+
+Installing Mako in Python 3
+---------------------------------
+
+Once Distribute is installed, Mako can be installed directly.
+The 2to3 process will kick in which takes several minutes:
+
+ python3 setup.py install
+
+Converting Tests, Examples, Source to Python 3
+----------------------------------------------
+
+To convert all files in the source distribution, run
+the 2to3 script:
+
+ 2to3 --no-diffs -w lib test examples
+
+The above will rewrite all files in-place in Python 3 format.
+
+Running Tests
+-------------
+
+To run the unit tests, ensure Distribute is installed as above,
+and also that at least the ./lib/ and ./test/ directories have been converted
+to Python 3 using the source tool above. A Python 3 version of Nose
+can be acquired from Bitbucket using Mercurial:
+
+ hg clone http://bitbucket.org/jpellerin/nose3/
+ cd nose3
+ python3 setup.py install
+
+The tests can then be run using the "nosetests3" script installed by the above.
+
diff --git a/distribute_setup.py b/distribute_setup.py
new file mode 100644
index 0000000..0021336
--- /dev/null
+++ b/distribute_setup.py
@@ -0,0 +1,477 @@
+#!python
+"""Bootstrap distribute installation
+
+If you want to use setuptools in your package's setup.py, just include this
+file in the same directory with it, and add this to the top of your setup.py::
+
+ from distribute_setup import use_setuptools
+ use_setuptools()
+
+If you want to require a specific version of setuptools, set a download
+mirror, or use an alternate download directory, you can do so by supplying
+the appropriate options to ``use_setuptools()``.
+
+This file can also be run as a script to install or upgrade setuptools.
+"""
+import os
+import sys
+import time
+import fnmatch
+import tempfile
+import tarfile
+from distutils import log
+
+try:
+ from site import USER_SITE
+except ImportError:
+ USER_SITE = None
+
+try:
+ import subprocess
+
+ def _python_cmd(*args):
+ args = (sys.executable,) + args
+ return subprocess.call(args) == 0
+
+except ImportError:
+ # will be used for python 2.3
+ def _python_cmd(*args):
+ args = (sys.executable,) + args
+ # quoting arguments if windows
+ if sys.platform == 'win32':
+ def quote(arg):
+ if ' ' in arg:
+ return '"%s"' % arg
+ return arg
+ args = [quote(arg) for arg in args]
+ return os.spawnl(os.P_WAIT, sys.executable, *args) == 0
+
+DEFAULT_VERSION = "0.6.10"
+DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/"
+SETUPTOOLS_FAKED_VERSION = "0.6c11"
+
+SETUPTOOLS_PKG_INFO = """\
+Metadata-Version: 1.0
+Name: setuptools
+Version: %s
+Summary: xxxx
+Home-page: xxx
+Author: xxx
+Author-email: xxx
+License: xxx
+Description: xxx
+""" % SETUPTOOLS_FAKED_VERSION
+
+
+def _install(tarball):
+ # extracting the tarball
+ tmpdir = tempfile.mkdtemp()
+ log.warn('Extracting in %s', tmpdir)
+ old_wd = os.getcwd()
+ try:
+ os.chdir(tmpdir)
+ tar = tarfile.open(tarball)
+ _extractall(tar)
+ tar.close()
+
+ # going in the directory
+ subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
+ os.chdir(subdir)
+ log.warn('Now working in %s', subdir)
+
+ # installing
+ log.warn('Installing Distribute')
+ if not _python_cmd('setup.py', 'install'):
+ log.warn('Something went wrong during the installation.')
+ log.warn('See the error message above.')
+ finally:
+ os.chdir(old_wd)
+
+
+def _build_egg(egg, tarball, to_dir):
+ # extracting the tarball
+ tmpdir = tempfile.mkdtemp()
+ log.warn('Extracting in %s', tmpdir)
+ old_wd = os.getcwd()
+ try:
+ os.chdir(tmpdir)
+ tar = tarfile.open(tarball)
+ _extractall(tar)
+ tar.close()
+
+ # going in the directory
+ subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
+ os.chdir(subdir)
+ log.warn('Now working in %s', subdir)
+
+ # building an egg
+ log.warn('Building a Distribute egg in %s', to_dir)
+ _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir)
+
+ finally:
+ os.chdir(old_wd)
+ # returning the result
+ log.warn(egg)
+ if not os.path.exists(egg):
+ raise IOError('Could not build the egg.')
+
+
+def _do_download(version, download_base, to_dir, download_delay):
+ egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg'
+ % (version, sys.version_info[0], sys.version_info[1]))
+ if not os.path.exists(egg):
+ tarball = download_setuptools(version, download_base,
+ to_dir, download_delay)
+ _build_egg(egg, tarball, to_dir)
+ sys.path.insert(0, egg)
+ import setuptools
+ setuptools.bootstrap_install_from = egg
+
+
+def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
+ to_dir=os.curdir, download_delay=15, no_fake=True):
+ # making sure we use the absolute path
+ to_dir = os.path.abspath(to_dir)
+ was_imported = 'pkg_resources' in sys.modules or \
+ 'setuptools' in sys.modules
+ try:
+ try:
+ import pkg_resources
+ if not hasattr(pkg_resources, '_distribute'):
+ if not no_fake:
+ _fake_setuptools()
+ raise ImportError
+ except ImportError:
+ return _do_download(version, download_base, to_dir, download_delay)
+ try:
+ pkg_resources.require("distribute>="+version)
+ return
+ except pkg_resources.VersionConflict:
+ e = sys.exc_info()[1]
+ if was_imported:
+ sys.stderr.write(
+ "The required version of distribute (>=%s) is not available,\n"
+ "and can't be installed while this script is running. Please\n"
+ "install a more recent version first, using\n"
+ "'easy_install -U distribute'."
+ "\n\n(Currently using %r)\n" % (version, e.args[0]))
+ sys.exit(2)
+ else:
+ del pkg_resources, sys.modules['pkg_resources'] # reload ok
+ return _do_download(version, download_base, to_dir,
+ download_delay)
+ except pkg_resources.DistributionNotFound:
+ return _do_download(version, download_base, to_dir,
+ download_delay)
+ finally:
+ if not no_fake:
+ _create_fake_setuptools_pkg_info(to_dir)
+
+def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
+ to_dir=os.curdir, delay=15):
+ """Download distribute from a specified location and return its filename
+
+ `version` should be a valid distribute version number that is available
+ as an egg for download under the `download_base` URL (which should end
+ with a '/'). `to_dir` is the directory where the egg will be downloaded.
+ `delay` is the number of seconds to pause before an actual download
+ attempt.
+ """
+ # making sure we use the absolute path
+ to_dir = os.path.abspath(to_dir)
+ try:
+ from urllib.request import urlopen
+ except ImportError:
+ from urllib2 import urlopen
+ tgz_name = "distribute-%s.tar.gz" % version
+ url = download_base + tgz_name
+ saveto = os.path.join(to_dir, tgz_name)
+ src = dst = None
+ if not os.path.exists(saveto): # Avoid repeated downloads
+ try:
+ log.warn("Downloading %s", url)
+ src = urlopen(url)
+ # Read/write all in one block, so we don't create a corrupt file
+ # if the download is interrupted.
+ data = src.read()
+ dst = open(saveto, "wb")
+ dst.write(data)
+ finally:
+ if src:
+ src.close()
+ if dst:
+ dst.close()
+ return os.path.realpath(saveto)
+
+
+def _patch_file(path, content):
+ """Will backup the file then patch it"""
+ existing_content = open(path).read()
+ if existing_content == content:
+ # already patched
+ log.warn('Already patched.')
+ return False
+ log.warn('Patching...')
+ _rename_path(path)
+ f = open(path, 'w')
+ try:
+ f.write(content)
+ finally:
+ f.close()
+ return True
+
+
+def _same_content(path, content):
+ return open(path).read() == content
+
+def _no_sandbox(function):
+ def __no_sandbox(*args, **kw):
+ try:
+ from setuptools.sandbox import DirectorySandbox
+ def violation(*args):
+ pass
+ DirectorySandbox._old = DirectorySandbox._violation
+ DirectorySandbox._violation = violation
+ patched = True
+ except ImportError:
+ patched = False
+
+ try:
+ return function(*args, **kw)
+ finally:
+ if patched:
+ DirectorySandbox._violation = DirectorySandbox._old
+ del DirectorySandbox._old
+
+ return __no_sandbox
+
+@_no_sandbox
+def _rename_path(path):
+ new_name = path + '.OLD.%s' % time.time()
+ log.warn('Renaming %s into %s', path, new_name)
+ os.rename(path, new_name)
+ return new_name
+
+def _remove_flat_installation(placeholder):
+ if not os.path.isdir(placeholder):
+ log.warn('Unkown installation at %s', placeholder)
+ return False
+ found = False
+ for file in os.listdir(placeholder):
+ if fnmatch.fnmatch(file, 'setuptools*.egg-info'):
+ found = True
+ break
+ if not found:
+ log.warn('Could not locate setuptools*.egg-info')
+ return
+
+ log.warn('Removing elements out of the way...')
+ pkg_info = os.path.join(placeholder, file)
+ if os.path.isdir(pkg_info):
+ patched = _patch_egg_dir(pkg_info)
+ else:
+ patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO)
+
+ if not patched:
+ log.warn('%s already patched.', pkg_info)
+ return False
+ # now let's move the files out of the way
+ for element in ('setuptools', 'pkg_resources.py', 'site.py'):
+ element = os.path.join(placeholder, element)
+ if os.path.exists(element):
+ _rename_path(element)
+ else:
+ log.warn('Could not find the %s element of the '
+ 'Setuptools distribution', element)
+ return True
+
+
+def _after_install(dist):
+ log.warn('After install bootstrap.')
+ placeholder = dist.get_command_obj('install').install_purelib
+ _create_fake_setuptools_pkg_info(placeholder)
+
+@_no_sandbox
+def _create_fake_setuptools_pkg_info(placeholder):
+ if not placeholder or not os.path.exists(placeholder):
+ log.warn('Could not find the install location')
+ return
+ pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1])
+ setuptools_file = 'setuptools-%s-py%s.egg-info' % \
+ (SETUPTOOLS_FAKED_VERSION, pyver)
+ pkg_info = os.path.join(placeholder, setuptools_file)
+ if os.path.exists(pkg_info):
+ log.warn('%s already exists', pkg_info)
+ return
+
+ log.warn('Creating %s', pkg_info)
+ f = open(pkg_info, 'w')
+ try:
+ f.write(SETUPTOOLS_PKG_INFO)
+ finally:
+ f.close()
+
+ pth_file = os.path.join(placeholder, 'setuptools.pth')
+ log.warn('Creating %s', pth_file)
+ f = open(pth_file, 'w')
+ try:
+ f.write(os.path.join(os.curdir, setuptools_file))
+ finally:
+ f.close()
+
+def _patch_egg_dir(path):
+ # let's check if it's already patched
+ pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
+ if os.path.exists(pkg_info):
+ if _same_content(pkg_info, SETUPTOOLS_PKG_INFO):
+ log.warn('%s already patched.', pkg_info)
+ return False
+ _rename_path(path)
+ os.mkdir(path)
+ os.mkdir(os.path.join(path, 'EGG-INFO'))
+ pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
+ f = open(pkg_info, 'w')
+ try:
+ f.write(SETUPTOOLS_PKG_INFO)
+ finally:
+ f.close()
+ return True
+
+
+def _before_install():
+ log.warn('Before install bootstrap.')
+ _fake_setuptools()
+
+
+def _under_prefix(location):
+ if 'install' not in sys.argv:
+ return True
+ args = sys.argv[sys.argv.index('install')+1:]
+ for index, arg in enumerate(args):
+ for option in ('--root', '--prefix'):
+ if arg.startswith('%s=' % option):
+ top_dir = arg.split('root=')[-1]
+ return location.startswith(top_dir)
+ elif arg == option:
+ if len(args) > index:
+ top_dir = args[index+1]
+ return location.startswith(top_dir)
+ elif option == '--user' and USER_SITE is not None:
+ return location.startswith(USER_SITE)
+ return True
+
+
+def _fake_setuptools():
+ log.warn('Scanning installed packages')
+ try:
+ import pkg_resources
+ except ImportError:
+ # we're cool
+ log.warn('Setuptools or Distribute does not seem to be installed.')
+ return
+ ws = pkg_resources.working_set
+ try:
+ setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools',
+ replacement=False))
+ except TypeError:
+ # old distribute API
+ setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools'))
+
+ if setuptools_dist is None:
+ log.warn('No setuptools distribution found')
+ return
+ # detecting if it was already faked
+ setuptools_location = setuptools_dist.location
+ log.warn('Setuptools installation detected at %s', setuptools_location)
+
+ # if --root or --preix was provided, and if
+ # setuptools is not located in them, we don't patch it
+ if not _under_prefix(setuptools_location):
+ log.warn('Not patching, --root or --prefix is installing Distribute'
+ ' in another location')
+ return
+
+ # let's see if its an egg
+ if not setuptools_location.endswith('.egg'):
+ log.warn('Non-egg installation')
+ res = _remove_flat_installation(setuptools_location)
+ if not res:
+ return
+ else:
+ log.warn('Egg installation')
+ pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO')
+ if (os.path.exists(pkg_info) and
+ _same_content(pkg_info, SETUPTOOLS_PKG_INFO)):
+ log.warn('Already patched.')
+ return
+ log.warn('Patching...')
+ # let's create a fake egg replacing setuptools one
+ res = _patch_egg_dir(setuptools_location)
+ if not res:
+ return
+ log.warn('Patched done.')
+ _relaunch()
+
+
+def _relaunch():
+ log.warn('Relaunching...')
+ # we have to relaunch the process
+ args = [sys.executable] + sys.argv
+ sys.exit(subprocess.call(args))
+
+
+def _extractall(self, path=".", members=None):
+ """Extract all members from the archive to the current working
+ directory and set owner, modification time and permissions on
+ directories afterwards. `path' specifies a different directory
+ to extract to. `members' is optional and must be a subset of the
+ list returned by getmembers().
+ """
+ import copy
+ import operator
+ from tarfile import ExtractError
+ directories = []
+
+ if members is None:
+ members = self
+
+ for tarinfo in members:
+ if tarinfo.isdir():
+ # Extract directories with a safe mode.
+ directories.append(tarinfo)
+ tarinfo = copy.copy(tarinfo)
+ tarinfo.mode = 448 # decimal for oct 0700
+ self.extract(tarinfo, path)
+
+ # Reverse sort directories.
+ if sys.version_info < (2, 4):
+ def sorter(dir1, dir2):
+ return cmp(dir1.name, dir2.name)
+ directories.sort(sorter)
+ directories.reverse()
+ else:
+ directories.sort(key=operator.attrgetter('name'), reverse=True)
+
+ # Set correct owner, mtime and filemode on directories.
+ for tarinfo in directories:
+ dirpath = os.path.join(path, tarinfo.name)
+ try:
+ self.chown(tarinfo, dirpath)
+ self.utime(tarinfo, dirpath)
+ self.chmod(tarinfo, dirpath)
+ except ExtractError:
+ e = sys.exc_info()[1]
+ if self.errorlevel > 1:
+ raise
+ else:
+ self._dbg(1, "tarfile: %s" % e)
+
+
+def main(argv, version=DEFAULT_VERSION):
+ """Install or upgrade setuptools and EasyInstall"""
+ tarball = download_setuptools()
+ _install(tarball)
+
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
diff --git a/mako/ast.py b/mako/ast.py
index 8d5b1d7..242b6ee 100644
--- a/mako/ast.py
+++ b/mako/ast.py
@@ -4,7 +4,8 @@
# 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"""
+"""utilities for analyzing expressions and blocks of Python
+code, as well as generating Python from AST nodes"""
from mako import exceptions, pyparser, util
import re
@@ -22,9 +23,12 @@ class PythonCode(object):
# 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
+ # 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, basestring):
expr = pyparser.parse(code.lstrip(), "exec", **exception_kwargs)
@@ -65,7 +69,9 @@ class PythonFragment(PythonCode):
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)
+ 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)
@@ -78,7 +84,9 @@ class PythonFragment(PythonCode):
elif keyword == 'except':
code = "try:pass\n" + code + "pass"
else:
- raise exceptions.CompileException("Unsupported control keyword: '%s'" % keyword, **exception_kwargs)
+ raise exceptions.CompileException(
+ "Unsupported control keyword: '%s'" %
+ keyword, **exception_kwargs)
super(PythonFragment, self).__init__(code, **exception_kwargs)
@@ -91,12 +99,17 @@ class FunctionDecl(object):
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)
+ 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.argnames[-1], **exception_kwargs)
+ raise exceptions.CompileException(
+ "'**%s' keyword argument not allowed here" %
+ self.argnames[-1], **exception_kwargs)
def get_argument_expressions(self, include_defaults=True):
"""return the argument declarations of this FunctionDecl as a printable list."""
+
namedecls = []
defaults = [d for d in self.defaults]
kwargs = self.kwargs
@@ -114,12 +127,17 @@ class FunctionDecl(object):
else:
default = len(defaults) and defaults.pop() or None
if include_defaults and default:
- namedecls.insert(0, "%s=%s" % (arg, pyparser.ExpressionGenerator(default).value()))
+ namedecls.insert(0, "%s=%s" %
+ (arg,
+ pyparser.ExpressionGenerator(default).value()
+ )
+ )
else:
namedecls.insert(0, arg)
return namedecls
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)
diff --git a/mako/codegen.py b/mako/codegen.py
index 41ab7fe..ded9396 100644
--- a/mako/codegen.py
+++ b/mako/codegen.py
@@ -25,6 +25,14 @@ def compile(node,
"""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 util.py3k and isinstance(source_encoding, unicode):
+ source_encoding = source_encoding.encode(source_encoding)
+
+
buf = util.FastEncodingBuffer()
printer = PythonPrinter(buf)
@@ -571,8 +579,9 @@ class _GenerateRenderMethod(object):
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.update(__M_dict_builtin([(__M_key, __M_locals_builtin()[__M_key]) for __M_key in [%s] if __M_key in __M_locals_builtin()]))' % ','.join([repr(x) for x in node.declared_identifiers()]))
-
+ 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.write_source_comment(node)
args = node.attributes.get('args')
diff --git a/mako/exceptions.py b/mako/exceptions.py
index dcd6a64..b8b7747 100644
--- a/mako/exceptions.py
+++ b/mako/exceptions.py
@@ -19,7 +19,9 @@ 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)
+ 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))
@@ -35,6 +37,9 @@ class SyntaxException(MakoException):
self.pos = pos
self.filename = filename
self.source = source
+
+class UnsupportedError(MakoException):
+ """raised when a retired feature is used."""
class TemplateLookupException(MakoException):
pass
@@ -130,17 +135,19 @@ class RichTraceback(object):
template_filename = info.template_filename or filename
except KeyError:
# A normal .py file (not a Template)
- try:
- fp = open(filename)
- 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))
+ if not util.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 = module_ln = 1
@@ -161,7 +168,9 @@ class RichTraceback(object):
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))
+ 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]:
@@ -171,7 +180,7 @@ class RichTraceback(object):
else:
try:
# A normal .py file (not a Template)
- fp = open(new_trcback[-1][0])
+ fp = open(new_trcback[-1][0], 'rb')
encoding = util.parse_encoding(fp)
fp.seek(0)
self.source = fp.read()
diff --git a/mako/filters.py b/mako/filters.py
index 9a5b21d..5f35714 100644
--- a/mako/filters.py
+++ b/mako/filters.py
@@ -7,6 +7,7 @@
import re, cgi, urllib, htmlentitydefs, codecs
from StringIO import StringIO
+from mako import util
xml_escapes = {
'&' : '&amp;',
@@ -166,5 +167,9 @@ DEFAULT_ESCAPES = {
'str':'str',
'n':'n'
}
-
+
+if util.py3k:
+ DEFAULT_ESCAPES.update({
+ 'unicode':'str'
+ })
diff --git a/mako/lexer.py b/mako/lexer.py
index caf295b..5e4a3bc 100644
--- a/mako/lexer.py
+++ b/mako/lexer.py
@@ -7,7 +7,7 @@
"""provides the Lexer class for parsing template strings into parse trees."""
import re, codecs
-from mako import parsetree, exceptions
+from mako import parsetree, exceptions, util
from mako.pygen import adjust_whitespace
_regexp_cache = {}
@@ -27,6 +27,12 @@ class Lexer(object):
self.control_line = []
self.disable_unicode = disable_unicode
self.encoding = input_encoding
+
+ if util.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__'):
@@ -42,10 +48,8 @@ class Lexer(object):
'filename':self.filename}
def match(self, regexp, flags=None):
- """match the given regular expression string and flags to the current text position.
+ """compile the given regexp, cache the reg, and call match_reg()."""
- if a match occurs, update the current text and line position."""
- mp = self.match_position
try:
reg = _regexp_cache[(regexp, flags)]
except KeyError:
@@ -54,6 +58,17 @@ class Lexer(object):
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:
@@ -128,45 +143,61 @@ class Lexer(object):
(node.keyword, self.control_line[-1].keyword),
**self.exception_kwargs)
- def parse(self):
- for preproc in self.preprocessor:
- self.text = preproc(self.text)
-
- if not isinstance(self.text, unicode) and self.text.startswith(codecs.BOM_UTF8):
- self.text = self.text[len(codecs.BOM_UTF8):]
+ _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, unicode):
+ 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'
- me = self.match_encoding()
- if me is not None and me != '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'" % me,
- self.text.decode('utf-8', 'ignore'),
- 0, 0, self.filename)
+ "magic encoding comment of '%s'" % m.group(1),
+ text.decode('utf-8', 'ignore'),
+ 0, 0, filename)
else:
- parsed_encoding = self.match_encoding()
-
- if parsed_encoding:
- self.encoding = parsed_encoding
-
- if not self.disable_unicode and not isinstance(self.text, unicode):
- if self.encoding:
- try:
- self.text = self.text.decode(self.encoding)
- except UnicodeDecodeError, e:
- raise exceptions.CompileException(
- "Unicode decode operation of encoding '%s' failed" %
- self.encoding,
- self.text.decode('utf-8', 'ignore'),
- 0, 0, self.filename)
+ m = self._coding_re.match(text.decode('utf-8', 'ignore'))
+ if m:
+ parsed_encoding = m.group(1)
else:
- try:
- self.text = self.text.decode()
- except UnicodeDecodeError, e:
- raise exceptions.CompileException(
- "Could not read template using encoding of 'ascii'. "
- "Did you forget a magic encoding comment?",
- self.text.decode('utf-8', 'ignore'), 0, 0, self.filename)
+ parsed_encoding = known_encoding or 'ascii'
+
+ if decode_raw:
+ try:
+ text = text.decode(parsed_encoding)
+ except UnicodeDecodeError, e:
+ 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):
@@ -206,13 +237,6 @@ class Lexer(object):
self.control_line[-1].pos, self.filename)
return self.template
- def match_encoding(self):
- match = self.match(r'#.*coding[:=]\s*([-\w.]+).*\r?\n')
- if match:
- return match.group(1)
- else:
- return None
-
def match_tag_start(self):
match = self.match(r'''
\<% # opening tag
diff --git a/mako/parsetree.py b/mako/parsetree.py
index 48e2d10..3a273ac 100644
--- a/mako/parsetree.py
+++ b/mako/parsetree.py
@@ -235,11 +235,13 @@ class Tag(Node):
attributes - raw dictionary of attribute key/value pairs
- expressions - a set of identifiers that are legal attributes, which can also contain embedded expressions
+ expressions - a set of identifiers that are legal attributes,
+ which can also contain embedded expressions
- nonexpressions - a set of identifiers that are legal attributes, which cannot contain embedded expressions
+ nonexpressions - a set of identifiers that are legal attributes,
+ which cannot contain embedded expressions
- **kwargs - other arguments passed to the Node superclass (lineno, pos)
+ \**kwargs - other arguments passed to the Node superclass (lineno, pos)
"""
super(Tag, self).__init__(**kwargs)
@@ -270,7 +272,9 @@ class Tag(Node):
m = re.match(r'^\${(.+?)}$', x)
if m:
code = ast.PythonCode(m.group(1), **self.exception_kwargs)
- undeclared_identifiers = undeclared_identifiers.union(code.undeclared_identifiers)
+ undeclared_identifiers = undeclared_identifiers.union(
+ code.undeclared_identifiers
+ )
expr.append("(%s)" % m.group(1))
else:
if x:
@@ -279,11 +283,15 @@ class Tag(Node):
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),
+ "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)
+ 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):
@@ -297,15 +305,21 @@ class Tag(Node):
self.keyword,
util.sorted_dict_repr(self.attributes),
(self.lineno, self.pos),
- [repr(x) for x in self.nodes]
+ 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)
+ 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 []
@@ -318,10 +332,19 @@ class NamespaceTag(Tag):
__keyword__ = 'namespace'
def __init__(self, keyword, attributes, **kwargs):
- super(NamespaceTag, self).__init__(keyword, attributes, (), ('name','inheritable','file','import','module'), (), **kwargs)
+ super(NamespaceTag, self).__init__(
+ keyword, attributes,
+ (),
+ ('name','inheritable',
+ 'file','import','module'),
+ (), **kwargs)
+
self.name = attributes.get('name', '__anon_%s' % hex(abs(id(self))))
if not 'name' in attributes and not 'import' in attributes:
- raise exceptions.CompileException("'name' and/or 'import' attributes are required for <%namespace>", **self.exception_kwargs)
+ raise exceptions.CompileException(
+ "'name' and/or 'import' attributes are required "
+ "for <%namespace>",
+ **self.exception_kwargs)
def declared_identifiers(self):
return []
@@ -330,8 +353,13 @@ 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)
+ super(TextTag, self).__init__(
+ keyword,
+ attributes, (),
+ ('filter'), (), **kwargs)
+ self.filter_args = ast.ArgumentList(
+ attributes.get('filter', ''),
+ **self.exception_kwargs)
class DefTag(Tag):
__keyword__ = 'def'
@@ -340,17 +368,22 @@ class DefTag(Tag):
super(DefTag, self).__init__(
keyword,
attributes,
- ('buffered', 'cached', 'cache_key', 'cache_timeout', 'cache_type', 'cache_dir', 'cache_url'),
+ ('buffered', 'cached', 'cache_key', 'cache_timeout',
+ 'cache_type', 'cache_dir', 'cache_url'),
('name','filter', 'decorator'),
('name',),
**kwargs)
name = attributes['name']
if re.match(r'^[\w_]+$',name):
- raise exceptions.CompileException("Missing parenthesis in %def", **self.exception_kwargs)
+ 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)
+ self.filter_args = ast.ArgumentList(
+ attributes.get('filter', ''),
+ **self.exception_kwargs)
def declared_identifiers(self):
return self.function_decl.argnames
@@ -359,13 +392,17 @@ class DefTag(Tag):
res = []
for c in self.function_decl.defaults:
res += list(ast.PythonCode(c, **self.exception_kwargs).undeclared_identifiers)
- return res + list(self.filter_args.undeclared_identifiers.difference(set(filters.DEFAULT_ESCAPES.keys())))
+ return res + list(self.filter_args.\
+ undeclared_identifiers.\
+ difference(filters.DEFAULT_ESCAPES.keys())
+ )
class CallTag(Tag):
__keyword__ = 'call'
def __init__(self, keyword, attributes, **kwargs):
- super(CallTag, self).__init__(keyword, attributes, ('args'), ('expr',), ('expr',), **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)
@@ -386,9 +423,18 @@ class CallNamespaceTag(Tag):
(),
(),
**kwargs)
- self.expression = "%s.%s(%s)" % (namespace, defname, ",".join(["%s=%s" % (k, v) for k, v in self.parsed_attributes.iteritems() if k != 'args']))
+
+ self.expression = "%s.%s(%s)" % (
+ namespace,
+ defname,
+ ",".join(["%s=%s" % (k, v) for k, v in
+ self.parsed_attributes.iteritems()
+ if k != 'args'])
+ )
self.code = ast.PythonCode(self.expression, **self.exception_kwargs)
- self.body_decl = ast.FunctionArgs(attributes.get('args', ''), **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.argnames)
@@ -400,7 +446,9 @@ class InheritTag(Tag):
__keyword__ = 'inherit'
def __init__(self, keyword, attributes, **kwargs):
- super(InheritTag, self).__init__(keyword, attributes, ('file',), (), ('file',), **kwargs)
+ super(InheritTag, self).__init__(
+ keyword, attributes,
+ ('file',), (), ('file',), **kwargs)
class PageTag(Tag):
__keyword__ = 'page'
@@ -409,12 +457,16 @@ class PageTag(Tag):
super(PageTag, self).__init__(
keyword,
attributes,
- ('cached', 'cache_key', 'cache_timeout', 'cache_type', 'cache_dir', 'cache_url', 'args', 'expression_filter'),
+ ('cached', 'cache_key', 'cache_timeout',
+ 'cache_type', 'cache_dir', 'cache_url',
+ 'args', 'expression_filter'),
(),
(),
**kwargs)
self.body_decl = ast.FunctionArgs(attributes.get('args', ''), **self.exception_kwargs)
- self.filter_args = ast.ArgumentList(attributes.get('expression_filter', ''), **self.exception_kwargs)
+ self.filter_args = ast.ArgumentList(
+ attributes.get('expression_filter', ''),
+ **self.exception_kwargs)
def declared_identifiers(self):
return self.body_decl.argnames
diff --git a/mako/pygen.py b/mako/pygen.py
index 914443b..aada94d 100644
--- a/mako/pygen.py
+++ b/mako/pygen.py
@@ -8,6 +8,7 @@
import re, string
from StringIO import StringIO
+from mako import exceptions
class PythonPrinter(object):
def __init__(self, stream):
@@ -84,7 +85,7 @@ class PythonPrinter(object):
# probably put extra closures - the resulting
# module wont compile.
if len(self.indent_detail) == 0:
- raise "Too many whitespace closures"
+ raise exceptions.SyntaxException("Too many whitespace closures")
self.indent_detail.pop()
if line is None:
@@ -200,7 +201,7 @@ class PythonPrinter(object):
if self._in_multi_line(entry):
self.stream.write(entry + "\n")
else:
- entry = string.expandtabs(entry)
+ 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")
@@ -260,7 +261,7 @@ def adjust_whitespace(text):
if in_multi_line(line):
lines.append(line)
else:
- line = string.expandtabs(line)
+ 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))
diff --git a/mako/pyparser.py b/mako/pyparser.py
index 7ae3a4f..b90278e 100644
--- a/mako/pyparser.py
+++ b/mako/pyparser.py
@@ -12,9 +12,23 @@ module is used.
from StringIO import StringIO
from mako import exceptions, util
+import operator
+
+if util.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')
-# words that cannot be assigned to (notably smaller than the total keys in __builtins__)
-reserved = set(['True', 'False', 'None'])
try:
import _ast
@@ -63,6 +77,18 @@ if _ast:
for n in node.targets:
self.visit(n)
self.in_assign_targets = in_a
+
+ if util.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.listener.undeclared_identifiers.add(node.type.id)
+ for statement in node.body:
+ self.visit(statement)
+
def visit_FunctionDef(self, node):
self._add_declared(node.name)
# push function state onto stack. dont log any
@@ -72,17 +98,19 @@ if _ast:
saved = {}
inf = self.in_function
self.in_function = True
+
for arg in node.args.args:
- if arg.id in self.local_ident_stack:
- saved[arg.id] = True
+ if arg_id(arg) in self.local_ident_stack:
+ saved[arg_id(arg)] = True
else:
- self.local_ident_stack[arg.id] = True
+ self.local_ident_stack[arg_id(arg)] = True
for n in node.body:
self.visit(n)
self.in_function = inf
for arg in node.args.args:
- if arg.id not in saved:
- del self.local_ident_stack[arg.id]
+ if arg_id(arg) not in saved:
+ del self.local_ident_stack[arg_id(arg)]
+
def visit_For(self, node):
# flip around visit
self.visit(node.iter)
@@ -94,7 +122,9 @@ if _ast:
def visit_Name(self, node):
if isinstance(node.ctx, _ast.Store):
self._add_declared(node.id)
- if node.id not in reserved and node.id not in self.listener.declared_identifiers and node.id not in self.local_ident_stack:
+ if 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:
@@ -128,9 +158,10 @@ if _ast:
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 for arg in node.args.args]
+ argnames = [arg_id(arg) for arg in node.args.args]
if node.args.vararg:
argnames.append(node.args.vararg)
if node.args.kwarg:
diff --git a/mako/runtime.py b/mako/runtime.py
index 583b79e..95828b4 100644
--- a/mako/runtime.py
+++ b/mako/runtime.py
@@ -18,6 +18,7 @@ class Context(object):
self._data.update(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
@@ -26,8 +27,13 @@ class Context(object):
# "caller" stack used by def calls with content
self.caller_stack = self._data['caller'] = CallerStack()
- lookup = property(lambda self:self._with_template.lookup)
- kwargs = property(lambda self:self._kwargs.copy())
+ @property
+ def lookup(self):
+ return self._with_template.lookup
+
+ @property
+ def kwargs(self):
+ return self._kwargs.copy()
def push_caller(self, caller):
self.caller_stack.append(caller)
@@ -87,6 +93,7 @@ class Context(object):
c._orig = self._orig
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
@@ -368,6 +375,7 @@ def _render(template, callable_, args, data, as_unicode=False):
else:
buf = util.StringIO()
context = Context(buf, **data)
+ context._outputting_as_unicode = as_unicode
context._with_template = template
_render_context(template, callable_, context, *args, **_kwargs_for_callable(callable_, data))
return context._pop_buffer().getvalue()
@@ -404,19 +412,24 @@ def _exec_template(callable_, context, args=None, kwargs=None):
try:
callable_(context, *args, **kwargs)
except Exception, e:
- error = e
+ _render_error(template, context, e)
except:
e = sys.exc_info()[0]
- error = e
- if error:
- if template.error_handler:
- result = template.error_handler(context, error)
- if not result:
- raise error
- else:
- error_template = exceptions.html_error_template()
- context._buffer_stack[:] = [util.FastEncodingBuffer(error_template.output_encoding, error_template.encoding_errors)]
- context._with_template = error_template
- error_template.render_context(context, error=error)
+ _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:
+ raise error
+ else:
+ error_template = exceptions.html_error_template()
+ if context._outputting_as_unicode:
+ context._buffer_stack[:] = [util.FastEncodingBuffer(unicode=True)]
+ else:
+ context._buffer_stack[:] = [util.FastEncodingBuffer(error_template.output_encoding, error_template.encoding_errors)]
+ context._with_template = error_template
+ error_template.render_context(context, error=error)
diff --git a/mako/template.py b/mako/template.py
index 87f6898..9104683 100644
--- a/mako/template.py
+++ b/mako/template.py
@@ -66,8 +66,14 @@ class Template(object):
self.output_encoding = output_encoding
self.encoding_errors = encoding_errors
self.disable_unicode = disable_unicode
+
+ if util.py3k and disable_unicode:
+ raise exceptions.UnsupportedError(
+ "Mako for Python 3 does not "
+ "support disabling Unicode")
+
if default_filters is None:
- if self.disable_unicode:
+ if util.py3k or self.disable_unicode:
self.default_filters = ['str']
else:
self.default_filters = ['unicode']
@@ -108,18 +114,18 @@ class Template(object):
os.stat(path)[stat.ST_MTIME] < filemtime:
_compile_module_file(
self,
- file(filename).read(),
+ open(filename, 'rb').read(),
filename,
path)
- module = imp.load_source(self.module_id, path, file(path))
+ module = imp.load_source(self.module_id, path, open(path, 'rb'))
del sys.modules[self.module_id]
if module._magic_number != codegen.MAGIC_NUMBER:
_compile_module_file(
self,
- file(filename).read(),
+ open(filename, 'rb').read(),
filename,
path)
- module = imp.load_source(self.module_id, path, file(path))
+ module = imp.load_source(self.module_id, path, open(path, 'rb'))
del sys.modules[self.module_id]
ModuleInfo(module, path, self, filename, None, None)
else:
@@ -127,7 +133,7 @@ class Template(object):
# in memory
(code, module) = _compile_text(
self,
- file(filename).read(),
+ open(filename, 'rb').read(),
filename)
self._source = None
self._code = code
@@ -318,7 +324,7 @@ class ModuleInfo(object):
if self.module_source is not None:
return self.module_source
else:
- return file(self.module_filename).read()
+ return open(self.module_filename).read()
@property
def source(self):
@@ -331,10 +337,10 @@ class ModuleInfo(object):
return self.template_source
else:
if self.module._source_encoding:
- return file(self.template_filename).read().\
+ return open(self.template_filename, 'rb').read().\
decode(self.module._source_encoding)
else:
- return file(self.template_filename).read()
+ return open(self.template_filename).read()
def _compile_text(template, text, filename):
identifier = template.module_id
@@ -355,7 +361,7 @@ def _compile_text(template, text, filename):
generate_magic_comment=template.disable_unicode)
cid = identifier
- if isinstance(cid, unicode):
+ if not util.py3k and isinstance(cid, unicode):
cid = cid.encode()
module = types.ModuleType(cid)
code = compile(source, cid, 'exec')
diff --git a/mako/util.py b/mako/util.py
index 88076c3..dcac5d7 100644
--- a/mako/util.py
+++ b/mako/util.py
@@ -6,16 +6,20 @@
import sys
-try:
- from cStringIO import StringIO
-except:
- from StringIO import StringIO
py3k = getattr(sys, 'py3kwarning', False) or sys.version_info >= (3, 0)
jython = sys.platform.startswith('java')
win32 = sys.platform.startswith('win')
-import codecs, re, weakref, os, time
+if py3k:
+ from io import StringIO
+else:
+ try:
+ from cStringIO import StringIO
+ except:
+ from StringIO import StringIO
+
+import codecs, re, weakref, os, time, operator
try:
import threading
@@ -60,6 +64,10 @@ def to_list(x, default=None):
else:
return x
+
+
+
+
class SetLikeDict(dict):
"""a dictionary that has some setlike methods on it"""
def union(self, other):
@@ -84,6 +92,9 @@ class FastEncodingBuffer(object):
self.unicode = unicode
self.errors = errors
self.write = self.data.append
+
+ def truncate(self):
+ self.data =[]
def getvalue(self):
if self.encoding:
@@ -138,8 +149,8 @@ class LRUCache(dict):
def _manage_size(self):
while len(self) > self.capacity + self.capacity * self.threshold:
- bytime = dict.values(self)
- bytime.sort(lambda a, b: cmp(b.timestamp, a.timestamp))
+ bytime = sorted(dict.values(self),
+ key=operator.attrgetter('timestamp'), reverse=True)
for item in bytime[self.capacity:]:
try:
del self[item.key]
@@ -154,13 +165,13 @@ _PYTHON_MAGIC_COMMENT_re = re.compile(
re.VERBOSE)
def parse_encoding(fp):
- """Deduce the encoding of a source file from magic comment.
+ """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.
+ The ``fp`` argument should be a seekable file object in binary mode.
"""
pos = fp.tell()
fp.seek(0)
@@ -170,11 +181,11 @@ def parse_encoding(fp):
if has_bom:
line1 = line1[len(codecs.BOM_UTF8):]
- m = _PYTHON_MAGIC_COMMENT_re.match(line1)
+ m = _PYTHON_MAGIC_COMMENT_re.match(line1.decode('ascii', 'ignore'))
if not m:
try:
import parser
- parser.suite(line1)
+ 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
@@ -183,7 +194,7 @@ def parse_encoding(fp):
pass
else:
line2 = fp.readline()
- m = _PYTHON_MAGIC_COMMENT_re.match(line2)
+ m = _PYTHON_MAGIC_COMMENT_re.match(line2.decode('ascii', 'ignore'))
if has_bom:
if m:
diff --git a/setup.py b/setup.py
index caed701..dda072f 100644
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,13 @@
from setuptools import setup, find_packages
import os
import re
+import sys
+
+extra = {}
+if sys.version_info >= (3, 0):
+ extra.update(
+ use_2to3=True,
+ )
v = file(os.path.join(os.path.dirname(__file__), 'mako', '__init__.py'))
VERSION = re.compile(r".*__version__ = '(.*?)'", re.S).match(v.read()).group(1)
@@ -25,10 +32,11 @@ SVN version:
""",
classifiers=[
- "Development Status :: 5 - Production/Stable",
+ 'Development Status :: 5 - Production/Stable',
'Environment :: Web Environment',
'Intended Audience :: Developers',
'Programming Language :: Python',
+ 'Programming Language :: Python :: 3',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
],
keywords='wsgi myghty mako',
diff --git a/test/__init__.py b/test/__init__.py
index c8c2a4d..1bad221 100644
--- a/test/__init__.py
+++ b/test/__init__.py
@@ -15,17 +15,26 @@ class TemplateTest(unittest.TestCase):
return Template(uri=filename, filename=filepath, module_directory=module_base, **kw)
def _file_path(self, filename):
+ name, ext = os.path.splitext(filename)
+
+ if py3k:
+ py3k_path = os.path.join(template_base, name + "_py3k" + ext)
+ if os.path.exists(py3k_path):
+ return py3k_path
+
return os.path.join(template_base, filename)
def _do_file_test(self, filename, expected, filters=None,
unicode_=True, template_args=None, **kw):
t1 = self._file_template(filename, **kw)
- self._do_test(t1, expected, filters=filters, unicode_=unicode_, template_args=template_args)
+ self._do_test(t1, expected, filters=filters,
+ unicode_=unicode_, template_args=template_args)
def _do_memory_test(self, source, expected, filters=None,
unicode_=True, template_args=None, **kw):
t1 = Template(text=source, **kw)
- self._do_test(t1, expected, filters=filters, unicode_=unicode_, template_args=template_args)
+ self._do_test(t1, expected, filters=filters,
+ unicode_=unicode_, template_args=template_args)
def _do_test(self, template, expected, filters=None, template_args=None, unicode_=True):
if template_args is None:
diff --git a/test/templates/chs_unicode_py3k.html b/test/templates/chs_unicode_py3k.html
new file mode 100644
index 0000000..35b888d
--- /dev/null
+++ b/test/templates/chs_unicode_py3k.html
@@ -0,0 +1,11 @@
+## -*- encoding:utf8 -*-
+<%
+ msg = '新中国的主席'
+%>
+
+<%def name="welcome(who, place='北京')">
+Welcome ${who} to ${place}.
+</%def>
+
+${name} 是 ${msg}<br/>
+${welcome('你')}
diff --git a/test/templates/read_unicode_py3k.html b/test/templates/read_unicode_py3k.html
new file mode 100644
index 0000000..380d356
--- /dev/null
+++ b/test/templates/read_unicode_py3k.html
@@ -0,0 +1,10 @@
+<%
+try:
+ file_content = open(path)
+except:
+ raise "Should never execute here"
+doc_content = ''.join(file_content.readlines())
+file_content.close()
+%>
+
+${bytes(doc_content, encoding='utf-8')}
diff --git a/test/templates/unicode_arguments_py3k.html b/test/templates/unicode_arguments_py3k.html
new file mode 100644
index 0000000..97e6d3a
--- /dev/null
+++ b/test/templates/unicode_arguments_py3k.html
@@ -0,0 +1,10 @@
+# coding: utf-8
+
+<%def name="my_def(x)">
+ x is: ${x}
+</%def>
+
+${my_def('drôle de petit voix m’a réveillé')}
+<%self:my_def x='drôle de petit voix m’a réveillé'/>
+<%self:my_def x="${'drôle de petit voix m’a réveillé'}"/>
+<%call expr="my_def('drôle de petit voix m’a réveillé')"/>
diff --git a/test/templates/unicode_code_py3k.html b/test/templates/unicode_code_py3k.html
new file mode 100644
index 0000000..76ed9cc
--- /dev/null
+++ b/test/templates/unicode_code_py3k.html
@@ -0,0 +1,7 @@
+## -*- coding: utf-8 -*-
+<%
+ x = "drôle de petit voix m’a réveillé."
+%>
+% if x=="drôle de petit voix m’a réveillé.":
+ hi, ${x}
+% endif
diff --git a/test/templates/unicode_expr_py3k.html b/test/templates/unicode_expr_py3k.html
new file mode 100644
index 0000000..4898257
--- /dev/null
+++ b/test/templates/unicode_expr_py3k.html
@@ -0,0 +1,2 @@
+## -*- coding: utf-8 -*-
+${"Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petit voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »"}
diff --git a/test/test_ast.py b/test/test_ast.py
index ed21456..bfdfd90 100644
--- a/test/test_ast.py
+++ b/test/test_ast.py
@@ -1,14 +1,12 @@
import unittest
from mako import ast, exceptions, pyparser, util
+from test import eq_
exception_kwargs = {'source':'', 'lineno':0, 'pos':0, 'filename':''}
class AstParseTest(unittest.TestCase):
- def setUp(self):
- pass
- def tearDown(self):
- pass
+
def test_locate_identifiers(self):
"""test the location of identifiers in a python code string"""
code = """
@@ -21,8 +19,8 @@ foo.hoho.lala.bar = 7 + gah.blah + u + blah
for lar in (1,2,3):
gh = 5
x = 12
-print "hello world, ", a, b
-print "Another expr", c
+("hello world, ", a, b)
+("Another expr", c)
"""
parsed = ast.PythonCode(code, **exception_kwargs)
assert parsed.declared_identifiers == set(['a','b','c', 'g', 'h', 'i', 'u', 'k', 'j', 'gh', 'lar', 'x'])
@@ -51,7 +49,7 @@ for x in data:
code = """
x = x + 5
for y in range(1, y):
- print "hi"
+ ("hi",)
[z for z in range(1, z)]
(q for q in range (1, q))
"""
@@ -59,9 +57,17 @@ for y in range(1, y):
assert parsed.undeclared_identifiers == set(['x', 'y', 'z', 'q', 'range'])
def test_locate_identifiers_4(self):
- code = """
+ if util.py3k:
+ code = """
+x = 5
+(y, )
+def mydef(mydefarg):
+ print("mda is", mydefarg)
+"""
+ else:
+ code = """
x = 5
-print y
+(y, )
def mydef(mydefarg):
print "mda is", mydefarg
"""
@@ -70,7 +76,16 @@ def mydef(mydefarg):
assert parsed.declared_identifiers == set(['mydef', 'x'])
def test_locate_identifiers_5(self):
- code = """
+ if util.py3k:
+ code = """
+try:
+ print(x)
+except:
+ print(y)
+"""
+ else:
+
+ code = """
try:
print x
except:
@@ -86,8 +101,15 @@ def foo():
"""
parsed = ast.PythonCode(code, **exception_kwargs)
assert parsed.undeclared_identifiers == set(['bar'])
-
- code = """
+
+ if util.py3k:
+ code = """
+def lala(x, y):
+ return x, y, z
+print(x)
+"""
+ else:
+ code = """
def lala(x, y):
return x, y, z
print x
@@ -95,8 +117,17 @@ print x
parsed = ast.PythonCode(code, **exception_kwargs)
assert parsed.undeclared_identifiers == set(['z', 'x'])
assert parsed.declared_identifiers == set(['lala'])
-
- code = """
+
+ if util.py3k:
+ code = """
+def lala(x, y):
+ def hoho():
+ def bar():
+ z = 7
+print(z)
+"""
+ else:
+ code = """
def lala(x, y):
def hoho():
def bar():
@@ -131,11 +162,7 @@ class Hi(object):
from foo import *
import x as bar
"""
- try:
- parsed = ast.PythonCode(code, **exception_kwargs)
- assert False
- except exceptions.CompileException, e:
- assert str(e).startswith("'import *' is not supported")
+ self.assertRaises(exceptions.CompileException, ast.PythonCode, code, **exception_kwargs)
def test_python_fragment(self):
parsed = ast.PythonFragment("for x in foo:", **exception_kwargs)
@@ -144,9 +171,12 @@ import x as bar
parsed = ast.PythonFragment("try:", **exception_kwargs)
- parsed = ast.PythonFragment("except MyException, e:", **exception_kwargs)
- assert parsed.declared_identifiers == set(['e'])
- assert parsed.undeclared_identifiers == set(['MyException'])
+ if util.py3k:
+ parsed = ast.PythonFragment("except MyException as e:", **exception_kwargs)
+ else:
+ parsed = ast.PythonFragment("except MyException, e:", **exception_kwargs)
+ eq_(parsed.declared_identifiers, set(['e']))
+ eq_(parsed.undeclared_identifiers, set(['MyException']))
def test_argument_list(self):
parsed = ast.ArgumentList("3, 5, 'hi', x+5, context.get('lala')", **exception_kwargs)
@@ -184,8 +214,6 @@ import x as bar
code = "str((x+7*y) / foo.bar(5,6)) + lala('ho')"
astnode = pyparser.parse(code)
newcode = pyparser.ExpressionGenerator(astnode).value()
- #print "newcode:" + newcode
- #print "result:" + eval(code, local_dict)
assert (eval(code, local_dict) == eval(newcode, local_dict))
a = ["one", "two", "three"]
@@ -195,8 +223,6 @@ import x as bar
code = "a[2] + hoho['somevalue'] + repr(g[3:5]) + repr(g[3:]) + repr(g[:5])"
astnode = pyparser.parse(code)
newcode = pyparser.ExpressionGenerator(astnode).value()
- #print newcode
- #print "result:", eval(code, local_dict)
assert(eval(code, local_dict) == eval(newcode, local_dict))
local_dict={'f':lambda :9, 'x':7}
@@ -209,7 +235,6 @@ import x as bar
local_dict={}
astnode = pyparser.parse(code)
newcode = pyparser.ExpressionGenerator(astnode).value()
- #print code, newcode
assert(eval(code, local_dict)) == eval(newcode, local_dict), "%s != %s" % (code, newcode)
diff --git a/test/test_def.py b/test/test_def.py
index 6cb1fdf..1f7a39a 100644
--- a/test/test_def.py
+++ b/test/test_def.py
@@ -1,9 +1,9 @@
from mako.template import Template
from mako import lookup
-import unittest
+from test import TemplateTest
from util import flatten_result, result_lines
-class DefTest(unittest.TestCase):
+class DefTest(TemplateTest):
def test_def_noargs(self):
template = Template("""
@@ -61,6 +61,7 @@ class DefTest(unittest.TestCase):
def test_toplevel(self):
"""test calling a def from the top level"""
+
template = Template("""
this is the body
@@ -73,15 +74,20 @@ class DefTest(unittest.TestCase):
this is b, ${x} ${y}
</%def>
- """, output_encoding='utf-8')
- assert flatten_result(template.get_def("a").render()) == "this is a"
- assert flatten_result(template.get_def("b").render(x=10, y=15)) == "this is b, 10 15"
- assert flatten_result(template.get_def("body").render()) == "this is the body"
+ """)
+
+ self._do_test(template.get_def("a"), "this is a", filters=flatten_result)
+ self._do_test(template.get_def("b"), "this is b, 10 15",
+ template_args={'x':10, 'y':15},
+ filters=flatten_result)
+ self._do_test(template.get_def("body"), "this is the body", filters=flatten_result)
+
-class ScopeTest(unittest.TestCase):
+class ScopeTest(TemplateTest):
"""test scoping rules. The key is, enclosing scope always takes precedence over contextual scope."""
+
def test_scope_one(self):
- t = Template("""
+ self._do_memory_test("""
<%def name="a()">
this is a, and y is ${y}
</%def>
@@ -94,8 +100,11 @@ class ScopeTest(unittest.TestCase):
${a()}
-""")
- assert flatten_result(t.render(y=None)) == "this is a, and y is None this is a, and y is 7"
+""",
+ "this is a, and y is None this is a, and y is 7",
+ filters=flatten_result,
+ template_args={'y':None}
+ )
def test_scope_two(self):
t = Template("""
@@ -372,7 +381,7 @@ class ScopeTest(unittest.TestCase):
"this is a, x is 15"
]
-class NestedDefTest(unittest.TestCase):
+class NestedDefTest(TemplateTest):
def test_nested_def(self):
t = Template("""
@@ -512,7 +521,7 @@ class NestedDefTest(unittest.TestCase):
""")
assert flatten_result(t.render(x=5)) == "b. c. x is 10. a: x is 5 x is 5"
-class ExceptionTest(unittest.TestCase):
+class ExceptionTest(TemplateTest):
def test_raise(self):
template = Template("""
<%
diff --git a/test/test_exceptions.py b/test/test_exceptions.py
index 52c9544..57b3bac 100644
--- a/test/test_exceptions.py
+++ b/test/test_exceptions.py
@@ -2,7 +2,7 @@
import sys
import unittest
-from mako import exceptions
+from mako import exceptions, util
from mako.template import Template
from mako.lookup import TemplateLookup
from util import result_lines
@@ -15,9 +15,9 @@ class ExceptionsTest(unittest.TestCase):
"""
try:
template = Template(code)
- template.render()
+ template.render_unicode()
except exceptions.CompileException, ce:
- html_error = exceptions.html_error_template().render()
+ html_error = exceptions.html_error_template().render_unicode()
assert ("CompileException: Fragment 'i = 0' is not a partial "
"control statement") in html_error
assert '<style>' in html_error
@@ -26,13 +26,13 @@ class ExceptionsTest(unittest.TestCase):
assert html_error_stripped.startswith('<html>')
assert html_error_stripped.endswith('</html>')
- not_full = exceptions.html_error_template().render(full=False)
+ not_full = exceptions.html_error_template().render_unicode(full=False)
assert '<html>' not in not_full
assert '</html>' not in not_full
assert '<style>' in not_full
assert '</style>' in not_full
- no_css = exceptions.html_error_template().render(css=False)
+ no_css = exceptions.html_error_template().render_unicode(css=False)
assert '<style>' not in no_css
assert '</style>' not in no_css
else:
@@ -41,20 +41,33 @@ class ExceptionsTest(unittest.TestCase):
def test_utf8_html_error_template(self):
"""test the html_error_template with a Template containing utf8 chars"""
- code = """# -*- coding: utf-8 -*-
+
+ if util.py3k:
+ code = """# -*- coding: utf-8 -*-
+% if 2 == 2: /an error
+${'привет'}
+% endif
+"""
+ else:
+ code = """# -*- coding: utf-8 -*-
% if 2 == 2: /an error
${u'привет'}
% endif
"""
try:
template = Template(code)
- template.render()
+ template.render_unicode()
except exceptions.CompileException, ce:
html_error = exceptions.html_error_template().render()
assert ("CompileException: Fragment 'if 2 == 2: /an "
"error' is not a partial control "
- "statement at line: 2 char: 1") in html_error
- assert u"3 ${u'привет'}".encode(sys.getdefaultencoding(),
+ "statement at line: 2 char: 1") in html_error.decode('utf-8')
+
+ if util.py3k:
+ assert u"3 ${'привет'}".encode(sys.getdefaultencoding(),
+ 'htmlentityreplace') in html_error
+ else:
+ assert u"3 ${u'привет'}".encode(sys.getdefaultencoding(),
'htmlentityreplace') in html_error
else:
assert False, ("This function should trigger a CompileException, "
@@ -66,8 +79,12 @@ ${u'привет'}
raise RuntimeError('test')
except:
html_error = exceptions.html_error_template().render()
- assert 'RuntimeError: test' in html_error
- assert "foo = u'&#x65E5;&#x672C;'" in html_error
+ if util.py3k:
+ assert 'RuntimeError: test' in html_error.decode('utf-8')
+ assert u"foo = '日本'" in html_error.decode('utf-8')
+ else:
+ assert 'RuntimeError: test' in html_error
+ assert "foo = u'&#x65E5;&#x672C;'" in html_error
def test_py_unicode_error_html_error_template(self):
@@ -75,8 +92,7 @@ ${u'привет'}
raise RuntimeError(u'日本')
except:
html_error = exceptions.html_error_template().render()
- assert 'RuntimeError: &#x65E5;&#x672C;' in html_error
- assert "RuntimeError(u'&#x65E5;&#x672C;')" in html_error
+ assert u"RuntimeError: 日本".encode('ascii', 'ignore') in html_error
def test_format_exceptions(self):
l = TemplateLookup(format_exceptions=True)
@@ -90,16 +106,21 @@ ${foobar}
${self.body()}
""")
- assert '<div class="sourceline">${foobar}</div>' in result_lines(l.get_template("foo.html").render())
+ assert '<div class="sourceline">${foobar}</div>' in result_lines(l.get_template("foo.html").render_unicode())
def test_utf8_format_exceptions(self):
"""test that htmlentityreplace formatting is applied to exceptions reported with format_exceptions=True"""
l = TemplateLookup(format_exceptions=True)
+ if util.py3k:
+ l.put_string("foo.html", """# -*- coding: utf-8 -*-\n${'привет' + foobar}""")
+ else:
+ l.put_string("foo.html", """# -*- coding: utf-8 -*-\n${u'привет' + foobar}""")
- l.put_string("foo.html", """# -*- coding: utf-8 -*-
-${u'привет' + foobar}
-""")
-
- assert '''<div class="highlight">2 ${u\'&#x43F;&#x440;&#x438;&#x432;&#x435;&#x442;\' + foobar}</div>''' in result_lines(l.get_template("foo.html").render())
+ if util.py3k:
+ assert u'<div class="sourceline">${\'привет\' + foobar}</div>'\
+ in result_lines(l.get_template("foo.html").render().decode('utf-8'))
+ else:
+ assert '<div class="highlight">2 ${u\'&#x43F;&#x440;&#x438;&#x432;&#x435;&#x442;\' + foobar}</div>' \
+ in result_lines(l.get_template("foo.html").render().decode('utf-8'))
diff --git a/test/test_inheritance.py b/test/test_inheritance.py
index c9c6990..9f978d2 100644
--- a/test/test_inheritance.py
+++ b/test/test_inheritance.py
@@ -1,5 +1,5 @@
from mako.template import Template
-from mako import lookup
+from mako import lookup, util
import unittest
from util import flatten_result, result_lines
@@ -187,10 +187,10 @@ ${next.body()}
this is the base.
<%
- sorted = pageargs.items()
- sorted.sort()
+ sorted_ = pageargs.items()
+ sorted_ = sorted(sorted_)
%>
- pageargs: (type: ${type(pageargs)}) ${sorted}
+ pageargs: (type: ${type(pageargs)}) ${sorted_}
<%def name="foo()">
${next.body(**context.kwargs)}
</%def>
@@ -202,11 +202,20 @@ ${next.body()}
<%page args="x, y, z=7"/>
print ${x}, ${y}, ${z}
""")
- assert result_lines(collection.get_template('index').render(x=5,y=10)) == [
- "this is the base.",
- "pageargs: (type: <type 'dict'>) [('x', 5), ('y', 10)]",
- "print 5, 10, 7"
- ]
+
+ if util.py3k:
+ assert result_lines(collection.get_template('index').render_unicode(x=5,y=10)) == [
+ "this is the base.",
+ "pageargs: (type: <class 'dict'>) [('x', 5), ('y', 10)]",
+ "print 5, 10, 7"
+ ]
+ else:
+ assert result_lines(collection.get_template('index').render_unicode(x=5,y=10)) == [
+ "this is the base.",
+ "pageargs: (type: <type 'dict'>) [('x', 5), ('y', 10)]",
+ "print 5, 10, 7"
+ ]
+
def test_pageargs_2(self):
collection = lookup.TemplateLookup()
collection.put_string("base", """
diff --git a/test/test_lexer.py b/test/test_lexer.py
index d934860..00d16af 100644
--- a/test/test_lexer.py
+++ b/test/test_lexer.py
@@ -1,14 +1,45 @@
import unittest
from mako.lexer import Lexer
-from mako import exceptions
+from mako import exceptions, util
from util import flatten_result, result_lines
from mako.template import Template
import re
-from test import TemplateTest, template_base, skip_if
-
-
+from test import TemplateTest, template_base, skip_if, eq_
+
+# create fake parsetree classes which are constructed
+# exactly as the repr() of a real parsetree object.
+# this allows us to use a Python construct as the source
+# of a comparable repr(), which is also hit by the 2to3 tool.
+
+def repr_arg(x):
+ if isinstance(x, dict):
+ return util.sorted_dict_repr(x)
+ else:
+ return repr(x)
+
+from mako import parsetree
+for cls in parsetree.__dict__.values():
+ if isinstance(cls, type) and \
+ issubclass(cls, parsetree.Node):
+ clsname = cls.__name__
+ exec ("""
+class %s(object):
+ def __init__(self, *args):
+ self.args = args
+ def __repr__(self):
+ return "%%s(%%s)" %% (
+ self.__class__.__name__,
+ ", ".join(repr_arg(x) for x in self.args)
+ )
+""" % clsname) in locals()
+
+
class LexerTest(TemplateTest):
+
+ def _compare(self, node, expected):
+ eq_(repr(node), repr(expected))
+
def test_text_and_tag(self):
template = """
<b>Hello world</b>
@@ -19,7 +50,10 @@ class LexerTest(TemplateTest):
and some more text.
"""
node = Lexer(template).parse()
- assert repr(node) == r"""TemplateNode({}, [Text(u'\n<b>Hello world</b>\n ', (1, 1)), DefTag(u'def', {u'name': u'foo()'}, (3, 9), ["Text(u'\\n this is a def.\\n ', (3, 28))"]), Text(u'\n \n and some more text.\n', (5, 16))])"""
+ self._compare(
+ node,
+ TemplateNode({}, [Text(u'\n<b>Hello world</b>\n ', (1, 1)), DefTag(u'def', {u'name': u'foo()'}, (3, 9), [Text(u'\n this is a def.\n ', (3, 28))]), Text(u'\n \n and some more text.\n', (5, 16))])
+ )
def test_unclosed_tag(self):
template = """
@@ -96,7 +130,10 @@ class LexerTest(TemplateTest):
% endif
"""
node = Lexer(template).parse()
- assert repr(node) == r"""TemplateNode({}, [Text(u'\n', (1, 1)), Comment(u'comment', (2, 1)), ControlLine(u'if', u'if foo:', False, (3, 1)), Text(u' hi\n', (4, 1)), ControlLine(u'if', u'endif', True, (5, 1)), Text(u' ', (6, 1)), TextTag(u'text', {}, (6, 9), ['Text(u\'\\n # more code\\n \\n % more code\\n <%illegal compionent>/></>\\n <%def name="laal()">def</%def>\\n \\n \\n \', (6, 16))']), Text(u'\n\n ', (14, 17)), DefTag(u'def', {u'name': u'foo()'}, (16, 9), ["Text(u'this is foo', (16, 28))"]), Text(u'\n \n', (16, 46)), ControlLine(u'if', u'if bar:', False, (18, 1)), Text(u' code\n', (19, 1)), ControlLine(u'if', u'endif', True, (20, 1)), Text(u' ', (21, 1))])"""
+ self._compare(
+ node,
+ TemplateNode({}, [Text(u'\n', (1, 1)), Comment(u'comment', (2, 1)), ControlLine(u'if', u'if foo:', False, (3, 1)), Text(u' hi\n', (4, 1)), ControlLine(u'if', u'endif', True, (5, 1)), Text(u' ', (6, 1)), TextTag(u'text', {}, (6, 9), [Text(u'\n # more code\n \n % more code\n <%illegal compionent>/></>\n <%def name="laal()">def</%def>\n \n \n ', (6, 16))]), Text(u'\n\n ', (14, 17)), DefTag(u'def', {u'name': u'foo()'}, (16, 9), [Text(u'this is foo', (16, 28))]), Text(u'\n \n', (16, 46)), ControlLine(u'if', u'if bar:', False, (18, 1)), Text(u' code\n', (19, 1)), ControlLine(u'if', u'endif', True, (20, 1)), Text(u' ', (21, 1))])
+ )
def test_def_syntax(self):
template = """
@@ -122,7 +159,10 @@ class LexerTest(TemplateTest):
"""
node = Lexer(template).parse()
- assert repr(node) == r"""TemplateNode({}, [Text(u'\n ', (1, 1)), DefTag(u'def', {u'name': u'adef()'}, (2, 13), ["Text(u'\\n adef\\n ', (2, 36))"]), Text(u'\n ', (4, 20))])"""
+ self._compare(
+ node,
+ TemplateNode({}, [Text(u'\n ', (1, 1)), DefTag(u'def', {u'name': u'adef()'}, (2, 13), [Text(u'\n adef\n ', (2, 36))]), Text(u'\n ', (4, 20))])
+ )
def test_ns_tag_closed(self):
template = """
@@ -130,14 +170,20 @@ class LexerTest(TemplateTest):
<%self:go x="1" y="2" z="${'hi' + ' ' + 'there'}"/>
"""
nodes = Lexer(template).parse()
- assert repr(nodes) == r"""TemplateNode({}, [Text(u'\n \n ', (1, 1)), CallNamespaceTag(u'self:go', {u'x': u'1', u'y': u'2', u'z': u"${'hi' + ' ' + 'there'}"}, (3, 13), []), Text(u'\n ', (3, 64))])"""
+ self._compare(
+ nodes,
+ TemplateNode({}, [Text(u'\n \n ', (1, 1)), CallNamespaceTag(u'self:go', {u'x': u'1', u'y': u'2', u'z': u"${'hi' + ' ' + 'there'}"}, (3, 13), []), Text(u'\n ', (3, 64))])
+ )
def test_ns_tag_empty(self):
template = """
<%form:option value=""></%form:option>
"""
nodes = Lexer(template).parse()
- assert repr(nodes) == r"""TemplateNode({}, [Text(u'\n ', (1, 1)), CallNamespaceTag(u'form:option', {u'value': u''}, (2, 13), []), Text(u'\n ', (2, 51))])"""
+ self._compare(
+ nodes,
+ TemplateNode({}, [Text(u'\n ', (1, 1)), CallNamespaceTag(u'form:option', {u'value': u''}, (2, 13), []), Text(u'\n ', (2, 51))])
+ )
def test_ns_tag_open(self):
template = """
@@ -147,19 +193,26 @@ class LexerTest(TemplateTest):
</%self:go>
"""
nodes = Lexer(template).parse()
- assert repr(nodes) == r"""TemplateNode({}, [Text(u'\n \n ', (1, 1)), CallNamespaceTag(u'self:go', {u'x': u'1', u'y': u'${process()}'}, (3, 13), ["Text(u'\\n this is the body\\n ', (3, 46))"]), Text(u'\n ', (5, 24))])"""
+ self._compare(
+ nodes,
+ TemplateNode({}, [Text(u'\n \n ', (1, 1)), CallNamespaceTag(u'self:go', {u'x': u'1', u'y': u'${process()}'}, (3, 13), [Text(u'\n this is the body\n ', (3, 46))]), Text(u'\n ', (5, 24))])
+ )
def test_expr_in_attribute(self):
"""test some slightly trickier expressions.
- you can still trip up the expression parsing, though, unless we integrated really deeply somehow with AST."""
+ you can still trip up the expression parsing,
+ though, unless we integrated really deeply somehow with AST."""
+
template = """
<%call expr="foo>bar and 'lala' or 'hoho'"/>
<%call expr='foo<bar and hoho>lala and "x" + "y"'/>
"""
nodes = Lexer(template).parse()
- #print nodes
- assert repr(nodes) == r"""TemplateNode({}, [Text(u'\n ', (1, 1)), CallTag(u'call', {u'expr': u"foo>bar and 'lala' or 'hoho'"}, (2, 13), []), Text(u'\n ', (2, 57)), CallTag(u'call', {u'expr': u'foo<bar and hoho>lala and "x" + "y"'}, (3, 13), []), Text(u'\n ', (3, 64))])"""
+ self._compare(
+ nodes,
+ TemplateNode({}, [Text(u'\n ', (1, 1)), CallTag(u'call', {u'expr': u"foo>bar and 'lala' or 'hoho'"}, (2, 13), []), Text(u'\n ', (2, 57)), CallTag(u'call', {u'expr': u'foo<bar and hoho>lala and "x" + "y"'}, (3, 13), []), Text(u'\n ', (3, 64))])
+ )
def test_pagetag(self):
@@ -169,7 +222,10 @@ class LexerTest(TemplateTest):
some template
"""
nodes = Lexer(template).parse()
- assert repr(nodes) == r"""TemplateNode({}, [Text(u'\n ', (1, 1)), PageTag(u'page', {u'args': u'a, b', u'cached': u'True'}, (2, 13), []), Text(u'\n \n some template\n ', (2, 48))])"""
+ self._compare(
+ nodes,
+ TemplateNode({}, [Text(u'\n ', (1, 1)), PageTag(u'page', {u'args': u'a, b', u'cached': u'True'}, (2, 13), []), Text(u'\n \n some template\n ', (2, 48))])
+ )
def test_nesting(self):
template = """
@@ -182,10 +238,38 @@ class LexerTest(TemplateTest):
"""
nodes = Lexer(template).parse()
- assert repr(nodes) == r"""TemplateNode({}, [Text(u'\n \n ', (1, 1)), NamespaceTag(u'namespace', {u'name': u'ns'}, (3, 9), ["Text(u'\\n ', (3, 31))", 'DefTag(u\'def\', {u\'name\': u\'lala(hi, there)\'}, (4, 13), ["Text(u\'\\\\n \', (4, 42))", "CallTag(u\'call\', {u\'expr\': u\'something()\'}, (5, 17), [])", "Text(u\'\\\\n \', (5, 44))"])', "Text(u'\\n ', (6, 20))"]), Text(u'\n \n ', (7, 22))])"""
+ self._compare(
+ nodes,
+ TemplateNode({}, [Text(u'\n \n ', (1, 1)), NamespaceTag(u'namespace', {u'name': u'ns'}, (3, 9), [Text(u'\n ', (3, 31)), DefTag(u'def', {u'name': u'lala(hi, there)'}, (4, 13), [Text(u'\n ', (4, 42)), CallTag(u'call', {u'expr': u'something()'}, (5, 17), []), Text(u'\n ', (5, 44))]), Text(u'\n ', (6, 20))]), Text(u'\n \n ', (7, 22))])
+ )
- def test_code(self):
- template = """
+ if util.py3k:
+ def test_code(self):
+ template = \
+ """
+ some text
+
+ <%
+ print("hi")
+ for x in range(1,5):
+ print(x)
+ %>
+
+ more text
+
+ <%!
+ import foo
+ %>
+ """
+ nodes = Lexer(template).parse()
+ self._compare(
+ nodes,
+ TemplateNode({}, [Text(u'\n some text\n \n ', (1, 1)), Code(u'\nprint("hi")\nfor x in range(1,5):\n print(x)\n \n', False, (4, 9)), Text(u'\n \n more text\n \n ', (8, 11)), Code(u'\nimport foo\n \n', True, (12, 9)), Text(u'\n ', (14, 11))])
+ )
+ else:
+ def test_code(self):
+ template = \
+ """
some text
<%
@@ -200,9 +284,11 @@ class LexerTest(TemplateTest):
import foo
%>
"""
- nodes = Lexer(template).parse()
- #print nodes
- assert repr(nodes) == r"""TemplateNode({}, [Text(u'\n some text\n \n ', (1, 1)), Code(u'\nprint "hi"\nfor x in range(1,5):\n print x\n \n', False, (4, 9)), Text(u'\n \n more text\n \n ', (8, 11)), Code(u'\nimport foo\n \n', True, (12, 9)), Text(u'\n ', (14, 11))])"""
+ nodes = Lexer(template).parse()
+ self._compare(
+ nodes,
+ TemplateNode({}, [Text(u'\n some text\n \n ', (1, 1)), Code(u'\nprint "hi"\nfor x in range(1,5):\n print x\n \n', False, (4, 9)), Text(u'\n \n more text\n \n ', (8, 11)), Code(u'\nimport foo\n \n', True, (12, 9)), Text(u'\n ', (14, 11))])
+ )
def test_code_and_tags(self):
template = """
@@ -225,7 +311,10 @@ class LexerTest(TemplateTest):
result: <%call expr="foo.x(result)"/>
"""
nodes = Lexer(template).parse()
- assert repr(nodes) == r"""TemplateNode({}, [Text(u'\n', (1, 1)), NamespaceTag(u'namespace', {u'name': u'foo'}, (2, 1), ["Text(u'\\n ', (2, 24))", 'DefTag(u\'def\', {u\'name\': u\'x()\'}, (3, 5), ["Text(u\'\\\\n this is x\\\\n \', (3, 22))"])', "Text(u'\\n ', (5, 12))", 'DefTag(u\'def\', {u\'name\': u\'y()\'}, (6, 5), ["Text(u\'\\\\n this is y\\\\n \', (6, 22))"])', "Text(u'\\n', (8, 12))"]), Text(u'\n\n', (9, 14)), Code(u'\nresult = []\ndata = get_data()\nfor x in data:\n result.append(x+7)\n\n', False, (11, 1)), Text(u'\n\n result: ', (16, 3)), CallTag(u'call', {u'expr': u'foo.x(result)'}, (18, 13), []), Text(u'\n', (18, 42))])"""
+ self._compare(
+ nodes,
+ TemplateNode({}, [Text(u'\n', (1, 1)), NamespaceTag(u'namespace', {u'name': u'foo'}, (2, 1), [Text(u'\n ', (2, 24)), DefTag(u'def', {u'name': u'x()'}, (3, 5), [Text(u'\n this is x\n ', (3, 22))]), Text(u'\n ', (5, 12)), DefTag(u'def', {u'name': u'y()'}, (6, 5), [Text(u'\n this is y\n ', (6, 22))]), Text(u'\n', (8, 12))]), Text(u'\n\n', (9, 14)), Code(u'\nresult = []\ndata = get_data()\nfor x in data:\n result.append(x+7)\n\n', False, (11, 1)), Text(u'\n\n result: ', (16, 3)), CallTag(u'call', {u'expr': u'foo.x(result)'}, (18, 13), []), Text(u'\n', (18, 42))])
+ )
def test_expression(self):
template = """
@@ -236,7 +325,10 @@ class LexerTest(TemplateTest):
${hi()}
"""
nodes = Lexer(template).parse()
- assert repr(nodes) == r"""TemplateNode({}, [Text(u'\n this is some ', (1, 1)), Expression(u'text', [], (2, 22)), Text(u' and this is ', (2, 29)), Expression(u'textwith ', ['escapes', 'moreescapes'], (2, 42)), Text(u'\n ', (2, 76)), DefTag(u'def', {u'name': u'hi()'}, (3, 9), ["Text(u'\\n give me ', (3, 27))", "Expression(u'foo()', [], (4, 21))", "Text(u' and ', (4, 29))", "Expression(u'bar()', [], (4, 34))", "Text(u'\\n ', (4, 42))"]), Text(u'\n ', (5, 16)), Expression(u'hi()', [], (6, 9)), Text(u'\n', (6, 16))])"""
+ self._compare(
+ nodes,
+ TemplateNode({}, [Text(u'\n this is some ', (1, 1)), Expression(u'text', [], (2, 22)), Text(u' and this is ', (2, 29)), Expression(u'textwith ', ['escapes', 'moreescapes'], (2, 42)), Text(u'\n ', (2, 76)), DefTag(u'def', {u'name': u'hi()'}, (3, 9), [Text(u'\n give me ', (3, 27)), Expression(u'foo()', [], (4, 21)), Text(u' and ', (4, 29)), Expression(u'bar()', [], (4, 34)), Text(u'\n ', (4, 42))]), Text(u'\n ', (5, 16)), Expression(u'hi()', [], (6, 9)), Text(u'\n', (6, 16))])
+ )
def test_tricky_expression(self):
@@ -245,36 +337,68 @@ class LexerTest(TemplateTest):
${x and "|" or "hi"}
"""
nodes = Lexer(template).parse()
- assert repr(nodes) == r"""TemplateNode({}, [Text(u'\n \n ', (1, 1)), Expression(u'x and "|" or "hi"', [], (3, 13)), Text(u'\n ', (3, 33))])"""
+ self._compare(
+ nodes,
+ TemplateNode({}, [Text(u'\n \n ', (1, 1)), Expression(u'x and "|" or "hi"', [], (3, 13)), Text(u'\n ', (3, 33))])
+ )
template = """
${hello + '''heres '{|}' text | | }''' | escape1}
"""
nodes = Lexer(template).parse()
- assert repr(nodes) == r"""TemplateNode({}, [Text(u'\n \n ', (1, 1)), Expression(u"hello + '''heres '{|}' text | | }''' ", ['escape1'], (3, 13)), Text(u'\n ', (3, 62))])"""
+ self._compare(
+ nodes,
+ TemplateNode({}, [Text(u'\n \n ', (1, 1)), Expression(u"hello + '''heres '{|}' text | | }''' ", ['escape1'], (3, 13)), Text(u'\n ', (3, 62))])
+ )
def test_tricky_code(self):
- template = """<% print 'hi %>' %>"""
- nodes = Lexer(template).parse()
- assert repr(nodes) == r"""TemplateNode({}, [Code(u"print 'hi %>' \n", False, (1, 1))])"""
+ if util.py3k:
+ template = """<% print('hi %>') %>"""
+ nodes = Lexer(template).parse()
+ self._compare(
+ nodes,
+ TemplateNode({}, [Code(u"print('hi %>') \n", False, (1, 1))])
+ )
+ else:
+ template = """<% print 'hi %>' %>"""
+ nodes = Lexer(template).parse()
+ self._compare(
+ nodes,
+ TemplateNode({}, [Code(u"print 'hi %>' \n", False, (1, 1))])
+ )
- template = r"""
- <%
- lines = src.split('\n')
- %>
-"""
- nodes = Lexer(template).parse()
-
def test_tricky_code_2(self):
template = """<%
# someone's comment
%>
"""
nodes = Lexer(template).parse()
- assert repr(nodes) == r"""TemplateNode({}, [Code(u" \n # someone's comment\n \n", False, (1, 1)), Text(u'\n ', (3, 11))])"""
-
- template= """<%
+ self._compare(
+ nodes,
+ TemplateNode({}, [Code(u" \n # someone's comment\n \n", False, (1, 1)), Text(u'\n ', (3, 11))])
+ )
+
+ if util.py3k:
+ def test_tricky_code_3(self):
+ template= """<%
+ print('hi')
+ # this is a comment
+ # another comment
+ x = 7 # someone's '''comment
+ print('''
+ there
+ ''')
+ # someone else's comment
+ %> '''and now some text '''"""
+ nodes = Lexer(template).parse()
+ self._compare(
+ nodes,
+ TemplateNode({}, [Code(u"\nprint('hi')\n# this is a comment\n# another comment\nx = 7 # someone's '''comment\nprint('''\n there\n ''')\n# someone else's comment\n \n", False, (1, 1)), Text(u" '''and now some text '''", (10, 11))])
+ )
+ else:
+ def test_tricky_code_3(self):
+ template= """<%
print 'hi'
# this is a comment
# another comment
@@ -284,8 +408,11 @@ class LexerTest(TemplateTest):
'''
# someone else's comment
%> '''and now some text '''"""
- nodes = Lexer(template).parse()
- assert repr(nodes) == r"""TemplateNode({}, [Code(u"\nprint 'hi'\n# this is a comment\n# another comment\nx = 7 # someone's '''comment\nprint '''\n there\n '''\n# someone else's comment\n \n", False, (1, 1)), Text(u" '''and now some text '''", (10, 11))])"""
+ nodes = Lexer(template).parse()
+ self._compare(
+ nodes,
+ TemplateNode({}, [Code(u"\nprint 'hi'\n# this is a comment\n# another comment\nx = 7 # someone's '''comment\nprint '''\n there\n '''\n# someone else's comment\n \n", False, (1, 1)), Text(u" '''and now some text '''", (10, 11))])
+ )
def test_control_lines(self):
template = """
@@ -302,8 +429,10 @@ text text la la
"""
nodes = Lexer(template).parse()
- #print nodes
- assert repr(nodes) == r"""TemplateNode({}, [Text(u'\ntext text la la\n', (1, 1)), ControlLine(u'if', u'if foo():', False, (3, 1)), Text(u' mroe text la la blah blah\n', (4, 1)), ControlLine(u'if', u'endif', True, (5, 1)), Text(u'\n and osme more stuff\n', (6, 1)), ControlLine(u'for', u'for l in range(1,5):', False, (8, 1)), Text(u' tex tesl asdl l is ', (9, 1)), Expression(u'l', [], (9, 24)), Text(u' kfmas d\n', (9, 28)), ControlLine(u'for', u'endfor', True, (10, 1)), Text(u' tetx text\n \n', (11, 1))])"""
+ self._compare(
+ nodes,
+ TemplateNode({}, [Text(u'\ntext text la la\n', (1, 1)), ControlLine(u'if', u'if foo():', False, (3, 1)), Text(u' mroe text la la blah blah\n', (4, 1)), ControlLine(u'if', u'endif', True, (5, 1)), Text(u'\n and osme more stuff\n', (6, 1)), ControlLine(u'for', u'for l in range(1,5):', False, (8, 1)), Text(u' tex tesl asdl l is ', (9, 1)), Expression(u'l', [], (9, 24)), Text(u' kfmas d\n', (9, 28)), ControlLine(u'for', u'endfor', True, (10, 1)), Text(u' tetx text\n \n', (11, 1))])
+ )
def test_control_lines_2(self):
template = \
@@ -315,7 +444,10 @@ text text la la
% endfor
"""
nodes = Lexer(template).parse()
- assert repr(nodes) == r"""TemplateNode({}, [Text(u'\n\n\n', (1, 1)), ControlLine(u'for', u"for file in requestattr['toc'].filenames:", False, (4, 1)), Text(u' x\n', (5, 1)), ControlLine(u'for', u'endfor', True, (6, 1))])"""
+ self._compare(
+ nodes,
+ TemplateNode({}, [Text(u'\n\n\n', (1, 1)), ControlLine(u'for', u"for file in requestattr['toc'].filenames:", False, (4, 1)), Text(u' x\n', (5, 1)), ControlLine(u'for', u'endfor', True, (6, 1))])
+ )
def test_long_control_lines(self):
template = \
@@ -326,7 +458,10 @@ text text la la
% endfor
"""
nodes = Lexer(template).parse()
- assert repr(nodes) == r"""TemplateNode({}, [Text(u'\n', (1, 1)), ControlLine(u'for', u"for file in \\\n requestattr['toc'].filenames:", False, (2, 1)), Text(u' x\n', (4, 1)), ControlLine(u'for', u'endfor', True, (5, 1)), Text(u' ', (6, 1))])"""
+ self._compare(
+ nodes,
+ TemplateNode({}, [Text(u'\n', (1, 1)), ControlLine(u'for', u"for file in \\\n requestattr['toc'].filenames:", False, (2, 1)), Text(u' x\n', (4, 1)), ControlLine(u'for', u'endfor', True, (5, 1)), Text(u' ', (6, 1))])
+ )
def test_unmatched_control(self):
template = """
@@ -381,7 +516,10 @@ text text la la
% endif
"""
nodes = Lexer(template).parse()
- assert repr(nodes) == r"""TemplateNode({}, [Text(u'\n', (1, 1)), ControlLine(u'if', u'if x:', False, (2, 1)), Text(u' hi\n', (3, 1)), ControlLine(u'elif', u'elif y+7==10:', False, (4, 1)), Text(u' there\n', (5, 1)), ControlLine(u'elif', u'elif lala:', False, (6, 1)), Text(u' lala\n', (7, 1)), ControlLine(u'else', u'else:', False, (8, 1)), Text(u' hi\n', (9, 1)), ControlLine(u'if', u'endif', True, (10, 1))])"""
+ self._compare(
+ nodes,
+ TemplateNode({}, [Text(u'\n', (1, 1)), ControlLine(u'if', u'if x:', False, (2, 1)), Text(u' hi\n', (3, 1)), ControlLine(u'elif', u'elif y+7==10:', False, (4, 1)), Text(u' there\n', (5, 1)), ControlLine(u'elif', u'elif lala:', False, (6, 1)), Text(u' lala\n', (7, 1)), ControlLine(u'else', u'else:', False, (8, 1)), Text(u' hi\n', (9, 1)), ControlLine(u'if', u'endif', True, (10, 1))])
+ )
def test_integration(self):
template = """<%namespace name="foo" file="somefile.html"/>
@@ -406,8 +544,10 @@ text text la la
</table>
"""
nodes = Lexer(template).parse()
- expected = r"""TemplateNode({}, [NamespaceTag(u'namespace', {u'file': u'somefile.html', u'name': u'foo'}, (1, 1), []), Text(u'\n', (1, 46)), Comment(u'inherit from foobar.html', (2, 1)), InheritTag(u'inherit', {u'file': u'foobar.html'}, (3, 1), []), Text(u'\n\n', (3, 31)), DefTag(u'def', {u'name': u'header()'}, (5, 1), ["Text(u'\\n <div>header</div>\\n', (5, 23))"]), Text(u'\n', (7, 8)), DefTag(u'def', {u'name': u'footer()'}, (8, 1), ["Text(u'\\n <div> footer</div>\\n', (8, 23))"]), Text(u'\n\n<table>\n', (10, 8)), ControlLine(u'for', u'for j in data():', False, (13, 1)), Text(u' <tr>\n', (14, 1)), ControlLine(u'for', u'for x in j:', False, (15, 1)), Text(u' <td>Hello ', (16, 1)), Expression(u'x', ['h'], (16, 23)), Text(u'</td>\n', (16, 30)), ControlLine(u'for', u'endfor', True, (17, 1)), Text(u' </tr>\n', (18, 1)), ControlLine(u'for', u'endfor', True, (19, 1)), Text(u'</table>\n', (20, 1))])"""
- assert repr(nodes) == expected
+ self._compare(
+ nodes,
+ TemplateNode({}, [NamespaceTag(u'namespace', {u'file': u'somefile.html', u'name': u'foo'}, (1, 1), []), Text(u'\n', (1, 46)), Comment(u'inherit from foobar.html', (2, 1)), InheritTag(u'inherit', {u'file': u'foobar.html'}, (3, 1), []), Text(u'\n\n', (3, 31)), DefTag(u'def', {u'name': u'header()'}, (5, 1), [Text(u'\n <div>header</div>\n', (5, 23))]), Text(u'\n', (7, 8)), DefTag(u'def', {u'name': u'footer()'}, (8, 1), [Text(u'\n <div> footer</div>\n', (8, 23))]), Text(u'\n\n<table>\n', (10, 8)), ControlLine(u'for', u'for j in data():', False, (13, 1)), Text(u' <tr>\n', (14, 1)), ControlLine(u'for', u'for x in j:', False, (15, 1)), Text(u' <td>Hello ', (16, 1)), Expression(u'x', ['h'], (16, 23)), Text(u'</td>\n', (16, 30)), ControlLine(u'for', u'endfor', True, (17, 1)), Text(u' </tr>\n', (18, 1)), ControlLine(u'for', u'endfor', True, (19, 1)), Text(u'</table>\n', (20, 1))])
+ )
def test_comment_after_statement(self):
template = """
@@ -418,12 +558,18 @@ text text la la
% endif #end
"""
nodes = Lexer(template).parse()
- assert repr(nodes) == r"""TemplateNode({}, [Text(u'\n', (1, 1)), ControlLine(u'if', u'if x: #comment', False, (2, 1)), Text(u' hi\n', (3, 1)), ControlLine(u'else', u'else: #next', False, (4, 1)), Text(u' hi\n', (5, 1)), ControlLine(u'if', u'endif #end', True, (6, 1))])"""
+ self._compare(
+ nodes,
+ TemplateNode({}, [Text(u'\n', (1, 1)), ControlLine(u'if', u'if x: #comment', False, (2, 1)), Text(u' hi\n', (3, 1)), ControlLine(u'else', u'else: #next', False, (4, 1)), Text(u' hi\n', (5, 1)), ControlLine(u'if', u'endif #end', True, (6, 1))])
+ )
def test_crlf(self):
- template = file(self._file_path("crlf.html")).read()
+ template = open(self._file_path("crlf.html"), 'rb').read()
nodes = Lexer(template).parse()
- assert repr(nodes) == r"""TemplateNode({}, [Text(u'<html>\r\n\r\n', (1, 1)), PageTag(u'page', {u'args': u"a=['foo',\n 'bar']"}, (3, 1), []), Text(u'\r\n\r\nlike the name says.\r\n\r\n', (4, 26)), ControlLine(u'for', u'for x in [1,2,3]:', False, (8, 1)), Text(u' ', (9, 1)), Expression(u'x', [], (9, 9)), Text(u'', (9, 13)), ControlLine(u'for', u'endfor', True, (10, 1)), Text(u'\r\n', (11, 1)), Expression(u"trumpeter == 'Miles' and trumpeter or \\\n 'Dizzy'", [], (12, 1)), Text(u'\r\n\r\n', (13, 15)), DefTag(u'def', {u'name': u'hi()'}, (15, 1), ["Text(u'\\r\\n hi!\\r\\n', (15, 19))"]), Text(u'\r\n\r\n</html>\r\n', (17, 8))])"""
+ self._compare(
+ nodes,
+ TemplateNode({}, [Text(u'<html>\r\n\r\n', (1, 1)), PageTag(u'page', {u'args': u"a=['foo',\n 'bar']"}, (3, 1), []), Text(u'\r\n\r\nlike the name says.\r\n\r\n', (4, 26)), ControlLine(u'for', u'for x in [1,2,3]:', False, (8, 1)), Text(u' ', (9, 1)), Expression(u'x', [], (9, 9)), Text(u'', (9, 13)), ControlLine(u'for', u'endfor', True, (10, 1)), Text(u'\r\n', (11, 1)), Expression(u"trumpeter == 'Miles' and trumpeter or \\\n 'Dizzy'", [], (12, 1)), Text(u'\r\n\r\n', (13, 15)), DefTag(u'def', {u'name': u'hi()'}, (15, 1), [Text(u'\r\n hi!\r\n', (15, 19))]), Text(u'\r\n\r\n</html>\r\n', (17, 8))])
+ )
assert flatten_result(Template(template).render()) == """<html> like the name says. 1 2 3 Dizzy </html>"""
def test_comments(self):
@@ -447,7 +593,10 @@ comment
hi
"""
nodes = Lexer(template).parse()
- assert repr(nodes) == r"""TemplateNode({}, [Text(u'\n<style>\n #someselector\n # other non comment stuff\n</style>\n', (1, 1)), Comment(u'a comment', (6, 1)), Text(u'\n# also not a comment\n\n', (7, 1)), Comment(u'this is a comment', (10, 1)), Text(u' \nthis is ## not a comment\n\n', (11, 1)), Comment(u' multiline\ncomment\n', (14, 1)), Text(u'\n\nhi\n', (16, 8))])"""
+ self._compare(
+ nodes,
+ TemplateNode({}, [Text(u'\n<style>\n #someselector\n # other non comment stuff\n</style>\n', (1, 1)), Comment(u'a comment', (6, 1)), Text(u'\n# also not a comment\n\n', (7, 1)), Comment(u'this is a comment', (10, 1)), Text(u' \nthis is ## not a comment\n\n', (11, 1)), Comment(u' multiline\ncomment\n', (14, 1)), Text(u'\n\nhi\n', (16, 8))])
+ )
def test_docs(self):
template = """
@@ -461,7 +610,10 @@ hi
</%def>
"""
nodes = Lexer(template).parse()
- assert repr(nodes) == r"""TemplateNode({}, [Text(u'\n ', (1, 1)), Comment(u'\n this is a comment\n ', (2, 9)), Text(u'\n ', (4, 16)), DefTag(u'def', {u'name': u'foo()'}, (5, 9), ["Text(u'\\n ', (5, 28))", "Comment(u'\\n this is the foo func\\n ', (6, 13))", "Text(u'\\n ', (8, 20))"]), Text(u'\n ', (9, 16))])"""
+ self._compare(
+ nodes,
+ TemplateNode({}, [Text(u'\n ', (1, 1)), Comment(u'\n this is a comment\n ', (2, 9)), Text(u'\n ', (4, 16)), DefTag(u'def', {u'name': u'foo()'}, (5, 9), [Text(u'\n ', (5, 28)), Comment(u'\n this is the foo func\n ', (6, 13)), Text(u'\n ', (8, 20))]), Text(u'\n ', (9, 16))])
+ )
def test_preprocess(self):
def preproc(text):
@@ -472,5 +624,8 @@ hi
# another comment
"""
nodes = Lexer(template, preprocessor=preproc).parse()
- assert repr(nodes) == r"""TemplateNode({}, [Text(u'\n hi\n', (1, 1)), Comment(u'old style comment', (3, 1)), Comment(u'another comment', (4, 1))])"""
+ self._compare(
+ nodes,
+ TemplateNode({}, [Text(u'\n hi\n', (1, 1)), Comment(u'old style comment', (3, 1)), Comment(u'another comment', (4, 1))])
+ )
diff --git a/test/test_template.py b/test/test_template.py
index f06ab5b..970565a 100644
--- a/test/test_template.py
+++ b/test/test_template.py
@@ -3,7 +3,7 @@
from mako.template import Template, ModuleTemplate
from mako.lookup import TemplateLookup
from mako.ext.preprocessors import convert_comments
-from mako import exceptions
+from mako import exceptions, util
import re, os
from util import flatten_result, result_lines
import codecs
@@ -50,10 +50,13 @@ class EncodingTest(TemplateTest):
directories=[template_base],
output_encoding='utf-8',
default_filters=['decode.utf8'])
- template = lookup.get_template('/chs_unicode.html')
+ if util.py3k:
+ template = lookup.get_template('/chs_unicode_py3k.html')
+ else:
+ template = lookup.get_template('/chs_unicode.html')
eq_(
- flatten_result(template.render(name='毛泽东')),
- '毛泽东 是 新中国的主席<br/> Welcome 你 to 北京.'
+ flatten_result(template.render_unicode(name='毛泽东')),
+ u'毛泽东 是 新中国的主席<br/> Welcome 你 to 北京.'
)
def test_unicode_bom(self):
@@ -76,14 +79,14 @@ class EncodingTest(TemplateTest):
def test_unicode_memory(self):
val = u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petit voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »"""
self._do_memory_test(
- "## -*- coding: utf-8 -*-\n" + val.encode('utf-8'),
+ ("## -*- coding: utf-8 -*-\n" + val).encode('utf-8'),
u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petit voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »"""
)
def test_unicode_text(self):
val = u"""<%text>Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petit voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »</%text>"""
self._do_memory_test(
- "## -*- coding: utf-8 -*-\n" + val.encode('utf-8'),
+ ("## -*- coding: utf-8 -*-\n" + val).encode('utf-8'),
u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petit voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »"""
)
@@ -96,19 +99,28 @@ class EncodingTest(TemplateTest):
<%text>Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petit voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »</%text>
</%call>"""
self._do_memory_test(
- "## -*- coding: utf-8 -*-\n" + val.encode('utf-8'),
+ ("## -*- coding: utf-8 -*-\n" + val).encode('utf-8'),
u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petit voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""",
filters=flatten_result
)
def test_unicode_literal_in_expr(self):
- self._do_memory_test(
- u"""## -*- coding: utf-8 -*-
- ${u"Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petit voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »"}
- """.encode('utf-8'),
- u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petit voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""",
- filters = lambda s:s.strip()
- )
+ if util.py3k:
+ self._do_memory_test(
+ u"""## -*- coding: utf-8 -*-
+ ${"Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petit voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »"}
+ """.encode('utf-8'),
+ u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petit voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""",
+ filters = lambda s:s.strip()
+ )
+ else:
+ self._do_memory_test(
+ u"""## -*- coding: utf-8 -*-
+ ${u"Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petit voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »"}
+ """.encode('utf-8'),
+ u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petit voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""",
+ filters = lambda s:s.strip()
+ )
def test_unicode_literal_in_expr_file(self):
self._do_file_test(
@@ -118,29 +130,54 @@ class EncodingTest(TemplateTest):
)
def test_unicode_literal_in_code(self):
- self._do_memory_test(
- u"""## -*- coding: utf-8 -*-
- <%
- context.write(u"Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petit voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »")
- %>
- """.encode('utf-8'),
- u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petit voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""",
- filters=lambda s:s.strip()
- )
+ if util.py3k:
+ self._do_memory_test(
+ u"""## -*- coding: utf-8 -*-
+ <%
+ context.write("Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petit voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »")
+ %>
+ """.encode('utf-8'),
+ u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petit voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""",
+ filters=lambda s:s.strip()
+ )
+ else:
+ self._do_memory_test(
+ u"""## -*- coding: utf-8 -*-
+ <%
+ context.write(u"Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petit voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »")
+ %>
+ """.encode('utf-8'),
+ u"""Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petit voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""",
+ filters=lambda s:s.strip()
+ )
def test_unicode_literal_in_controlline(self):
- self._do_memory_test(
- u"""## -*- coding: utf-8 -*-
- <%
- x = u"drôle de petit voix m’a réveillé."
- %>
- % if x==u"drôle de petit voix m’a réveillé.":
- hi, ${x}
- % endif
- """.encode('utf-8'),
- u"""hi, drôle de petit voix m’a réveillé.""",
- filters=lambda s:s.strip(),
- )
+ if util.py3k:
+ self._do_memory_test(
+ u"""## -*- coding: utf-8 -*-
+ <%
+ x = "drôle de petit voix m’a réveillé."
+ %>
+ % if x=="drôle de petit voix m’a réveillé.":
+ hi, ${x}
+ % endif
+ """.encode('utf-8'),
+ u"""hi, drôle de petit voix m’a réveillé.""",
+ filters=lambda s:s.strip(),
+ )
+ else:
+ self._do_memory_test(
+ u"""## -*- coding: utf-8 -*-
+ <%
+ x = u"drôle de petit voix m’a réveillé."
+ %>
+ % if x==u"drôle de petit voix m’a réveillé.":
+ hi, ${x}
+ % endif
+ """.encode('utf-8'),
+ u"""hi, drôle de petit voix m’a réveillé.""",
+ filters=lambda s:s.strip(),
+ )
def test_unicode_literal_in_tag(self):
self._do_file_test(
@@ -155,7 +192,7 @@ class EncodingTest(TemplateTest):
)
self._do_memory_test(
- file(self._file_path("unicode_arguments.html")).read(),
+ open(self._file_path("unicode_arguments.html"), 'rb').read(),
[
u'x is: drôle de petit voix m’a réveillé',
u'x is: drôle de petit voix m’a réveillé',
@@ -166,45 +203,83 @@ class EncodingTest(TemplateTest):
)
def test_unicode_literal_in_def(self):
- self._do_memory_test(
- u"""## -*- coding: utf-8 -*-
- <%def name="bello(foo, bar)">
- Foo: ${ foo }
- Bar: ${ bar }
- </%def>
- <%call expr="bello(foo=u'árvíztűrő tükörfúrógép', bar=u'ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP')">
- </%call>""".encode('utf-8'),
- u"""Foo: árvíztűrő tükörfúrógép Bar: ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP""",
- filters=flatten_result
- )
+ if util.py3k:
+ self._do_memory_test(
+ u"""## -*- coding: utf-8 -*-
+ <%def name="bello(foo, bar)">
+ Foo: ${ foo }
+ Bar: ${ bar }
+ </%def>
+ <%call expr="bello(foo='árvíztűrő tükörfúrógép', bar='ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP')">
+ </%call>""".encode('utf-8'),
+ u"""Foo: árvíztűrő tükörfúrógép Bar: ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP""",
+ filters=flatten_result
+ )
+
+ self._do_memory_test(
+ u"""## -*- coding: utf-8 -*-
+ <%def name="hello(foo='árvíztűrő tükörfúrógép', bar='ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP')">
+ Foo: ${ foo }
+ Bar: ${ bar }
+ </%def>
+ ${ hello() }""".encode('utf-8'),
+ u"""Foo: árvíztűrő tükörfúrógép Bar: ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP""",
+ filters=flatten_result
+ )
+ else:
+ self._do_memory_test(
+ u"""## -*- coding: utf-8 -*-
+ <%def name="bello(foo, bar)">
+ Foo: ${ foo }
+ Bar: ${ bar }
+ </%def>
+ <%call expr="bello(foo=u'árvíztűrő tükörfúrógép', bar=u'ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP')">
+ </%call>""".encode('utf-8'),
+ u"""Foo: árvíztűrő tükörfúrógép Bar: ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP""",
+ filters=flatten_result
+ )
- self._do_memory_test(
- u"""## -*- coding: utf-8 -*-
- <%def name="hello(foo=u'árvíztűrő tükörfúrógép', bar=u'ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP')">
- Foo: ${ foo }
- Bar: ${ bar }
- </%def>
- ${ hello() }""".encode('utf-8'),
- u"""Foo: árvíztűrő tükörfúrógép Bar: ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP""",
- filters=flatten_result
- )
+ self._do_memory_test(
+ u"""## -*- coding: utf-8 -*-
+ <%def name="hello(foo=u'árvíztűrő tükörfúrógép', bar=u'ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP')">
+ Foo: ${ foo }
+ Bar: ${ bar }
+ </%def>
+ ${ hello() }""".encode('utf-8'),
+ u"""Foo: árvíztűrő tükörfúrógép Bar: ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP""",
+ filters=flatten_result
+ )
def test_input_encoding(self):
"""test the 'input_encoding' flag on Template, and that unicode
objects arent double-decoded"""
- self._do_memory_test(
- u"hello ${f(u'śląsk')}",
- u"hello śląsk",
- input_encoding='utf-8',
- template_args={'f':lambda x:x}
- )
-
- self._do_memory_test(
- u"## -*- coding: utf-8 -*-\nhello ${f(u'śląsk')}",
- u"hello śląsk",
- template_args={'f':lambda x:x}
- )
+ if util.py3k:
+ self._do_memory_test(
+ u"hello ${f('śląsk')}",
+ u"hello śląsk",
+ input_encoding='utf-8',
+ template_args={'f':lambda x:x}
+ )
+
+ self._do_memory_test(
+ u"## -*- coding: utf-8 -*-\nhello ${f('śląsk')}",
+ u"hello śląsk",
+ template_args={'f':lambda x:x}
+ )
+ else:
+ self._do_memory_test(
+ u"hello ${f(u'śląsk')}",
+ u"hello śląsk",
+ input_encoding='utf-8',
+ template_args={'f':lambda x:x}
+ )
+
+ self._do_memory_test(
+ u"## -*- coding: utf-8 -*-\nhello ${f(u'śląsk')}",
+ u"hello śląsk",
+ template_args={'f':lambda x:x}
+ )
def test_raw_strings(self):
"""test that raw strings go straight thru with default_filters turned off"""
@@ -243,9 +318,13 @@ class EncodingTest(TemplateTest):
def test_read_unicode(self):
lookup = TemplateLookup(directories=[template_base],
filesystem_checks=True, output_encoding='utf-8')
- template = lookup.get_template('/read_unicode.html')
+ if util.py3k:
+ template = lookup.get_template('/read_unicode_py3k.html')
+ else:
+ template = lookup.get_template('/read_unicode.html')
data = template.render(path=self._file_path('internationalization.html'))
+ @skip_if(lambda: util.py3k)
def test_bytestring_passthru(self):
self._do_file_test(
'chs_utf8.html',
@@ -418,7 +497,7 @@ class ControlTest(TemplateTest):
t = Template("""
## this is a template.
% for x in y:
- % if x.has_key('test'):
+ % if 'test' in x:
yes x has test
% else:
no x does not have test
@@ -474,7 +553,7 @@ class RichTracebackTest(TemplateTest):
filename = 'unicode_syntax_error.html'
else:
filename = 'unicode_runtime_error.html'
- source = file(self._file_path(filename)).read()
+ source = open(self._file_path(filename), 'rb').read()
if not utf8:
source = source.decode('utf-8')
templateargs = {'filename':self._file_path(filename)}