[pypy-commit] benchmarks default: Add a chameleon benchmark

fijal noreply at buildbot.pypy.org
Wed Sep 28 15:42:56 CEST 2011


Author: Maciej Fijalkowski <fijall at gmail.com>
Branch: 
Changeset: r144:79a6304c1195
Date: 2011-09-28 09:28 -0300
http://bitbucket.org/pypy/benchmarks/changeset/79a6304c1195/

Log:	Add a chameleon benchmark

diff too long, truncating to 10000 out of 16152 lines

diff --git a/.hgignore b/.hgignore
--- a/.hgignore
+++ b/.hgignore
@@ -1,1 +1,2 @@
 .*\.py[co]
+.*~
\ No newline at end of file
diff --git a/benchmarks.py b/benchmarks.py
--- a/benchmarks.py
+++ b/benchmarks.py
@@ -42,11 +42,12 @@
 opts = {
     'gcbench' : {'iteration_scaling' : .10},
     'bm_mako' : {'bm_env': {'PYTHONPATH': relative('lib/mako')}},
+    'bm_chameleon': {'bm_env': {'PYTHONPATH': relative('lib/chameleon/src')}},
 }
 
 for name in ['float', 'nbody_modified', 'meteor-contest', 'fannkuch',
              'spectral-norm', 'chaos', 'telco', 'go', 'pyflate-fast',
-             'raytrace-simple', 'crypto_pyaes', 'bm_mako']:
+             'raytrace-simple', 'crypto_pyaes', 'bm_mako', 'bm_chameleon']:
     _register_new_bm(name, name, globals(), **opts.get(name, {}))
 for name in ['names', 'iteration', 'tcp', 'pb']:#, 'web', 'accepts']:
     iteration_scaling = 1.0
diff --git a/lib/chameleon/CHANGES.rst b/lib/chameleon/CHANGES.rst
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/CHANGES.rst
@@ -0,0 +1,721 @@
+Changes
+=======
+
+2.5.0 (2011-09-23)
+------------------
+
+Features:
+
+- An expression type ``structure:`` is now available which wraps the
+  expression result as *structure* such that it is not escaped on
+  insertion, e.g.::
+
+    <div id="content">
+       ${structure: context.body}
+    </div>
+
+  This also means that the ``structure`` keyword for ``tal:content``
+  and ``tal:replace`` now has an alternative spelling via the
+  expression type ``structure:``.
+
+- The string-based template constructor now accepts encoded input.
+
+2.4.6 (2011-09-23)
+------------------
+
+Bugfixes:
+
+- The ``tal:on-error`` statement should catch all exceptions.
+
+- Fixed issue that would prevent escaping of interpolation expression
+  values appearing in text.
+
+2.4.5 (2011-09-21)
+------------------
+
+Bugfixes:
+
+- The ``tal:on-error`` handler should have a ``error`` variable
+  defined that has the value of the exception thrown.
+
+- The ``tal:on-error`` statement is a substitution statement and
+  should support the "text" and "structure" insertion methods.
+
+2.4.4 (2011-09-15)
+------------------
+
+Bugfixes:
+
+- An encoding specified in the XML document preamble is now read and
+  used to decode the template input to unicode. This fixes issue #55.
+
+- Encoded expression input on Python 3 is now correctly
+  decoded. Previously, the string representation output would be
+  included instead of an actually decoded string.
+
+- Expression result conversion steps are now correctly included in
+  error handling such that the exception output points to the
+  expression location.
+
+2.4.3 (2011-09-13)
+------------------
+
+Features:
+
+- When an encoding is provided, pass the 'ignore' flag to avoid
+  decoding issues with bad input.
+
+Bugfixes:
+
+- Fixed pypy compatibility issue (introduced in previous release).
+
+2.4.2 (2011-09-13)
+------------------
+
+Bugfixes:
+
+- Fixed an issue in the compiler where an internal variable (such as a
+  translation default value) would be cached, resulting in variable
+  scope corruption (see issue #49).
+
+2.4.1 (2011-09-08)
+------------------
+
+Bugfixes:
+
+- Fixed an issue where a default value for an attribute would
+  sometimes spill over into another attribute.
+
+- Fixed issue where the use of the ``default`` name in an attribute
+  interpolation expression would print the attribute value. This is
+  unexpected, because it's an expression, not a static text suitable
+  for output. An attribute value of ``default`` now correctly drops
+  the attribute.
+
+2.4.0 (2011-08-22)
+------------------
+
+Features:
+
+- Added an option ``boolean_attributes`` to evaluate and render a
+  provided set of attributes using a boolean logic: if the attribute
+  is a true value, the value will be the attribute name, otherwise the
+  attribute is dropped.
+
+  In the reference implementation, the following attributes are
+  configured as boolean values when the template is rendered in
+  HTML-mode::
+
+      "compact", "nowrap", "ismap", "declare", "noshade",
+      "checked", "disabled", "readonly", "multiple", "selected",
+      "noresize", "defer"
+
+  Note that in Chameleon, these attributes must be manually provided.
+
+Bugfixes:
+
+- The carriage return character (used on Windows platforms) would
+  incorrectly be included in Python comments.
+
+  It is now replaced with a line break.
+
+  This fixes issue #44.
+
+2.3.8 (2011-08-19)
+------------------
+
+- Fixed import error that affected Python 2.5 only.
+
+2.3.7 (2011-08-19)
+------------------
+
+Features:
+
+- Added an option ``literal_false`` that disables the default behavior
+  of dropping an attribute for a value of ``False`` (in addition to
+  ``None``). This modified behavior is the behavior exhibited in
+  reference implementation.
+
+Bugfixes:
+
+- Undo attribute special HTML attribute behavior (see previous
+  release).
+
+  This turned out not to be a compatible behavior; rather, boolean
+  values should simply be coerced to a string.
+
+  Meanwhile, the reference implementation does support an HTML mode in
+  which the special attribute behavior is exhibited.
+
+  We do not currently support this mode.
+
+2.3.6 (2011-08-18)
+------------------
+
+Features:
+
+- Certain HTML attribute names now have a special behavior for a
+  attribute value of ``True`` (or ``default`` if no default is
+  defined). For these attributes, this return value will result in the
+  name being printed as the value::
+
+    <input type="input" tal:attributes="checked True" />
+
+  will be rendered as::
+
+    <input type="input" checked="checked" />
+
+  This behavior is compatible with the reference implementation.
+
+2.3.5 (2011-08-18)
+------------------
+
+Features:
+
+- Added support for the set operator (``{item, item, ...}``).
+
+Bugfixes:
+
+- If macro is defined on the same element as a translation name, this
+  no longer results in a "translation name not allowed outside
+  translation" error. This fixes issue #43.
+
+- Attribute fallback to dictionary lookup now works on multiple items
+  (e.g. ``d1.d2.d2``). This fixes issue #42.
+
+2.3.4 (2011-08-16)
+------------------
+
+Features:
+
+- When inserting content in either attributes or text, a value of
+  ``True`` (like ``False`` and ``None``) will result in no
+  action.
+
+- Use statically assigned variables for ``"attrs"`` and
+  ``"default"``. This change yields a performance improvement of
+  15-20%.
+
+- The template loader class now accepts an optional argument
+  ``default_extension`` which accepts a filename extension which will
+  be appended to the filename if there's not already an extension.
+
+Bugfixes:
+
+- The default symbol is now ``True`` for an attribute if the attribute
+  default is not provided. Note that the result is that the attribute
+  is dropped. This fixes issue #41.
+
+- Fixed an issue where assignment to a variable ``"type"`` would
+  fail. This fixes issue #40.
+
+- Fixed an issue where an (unsuccesful) assignment for a repeat loop
+  to a compiler internal name would not result in an error.
+
+- If the translation function returns the identical object, manually
+  coerce it to string. This fixes a compatibility issue with
+  translation functions which do not convert non-string objects to a
+  string value, but simply return them unchanged.
+
+2.3.3 (2011-08-15)
+------------------
+
+Features:
+
+- The ``load:`` expression now passes the initial keyword arguments to
+  its template loader (e.g. ``auto_reload`` and ``encoding``).
+
+- In the exception output, string variable values are now limited to a
+  limited output of characters, single line only.
+
+Bugfixes:
+
+- Fixed horizontal alignment of exception location info
+  (i.e. 'String:', 'Filename:' and 'Location:') such that they match
+  the template exception formatter.
+
+2.3.2 (2011-08-11)
+------------------
+
+Bugfixes:
+
+- Fixed issue where i18n:domain would not be inherited through macros
+  and slots. This fixes issue #37.
+
+2.3.1 (2011-08-11)
+------------------
+
+Features:
+
+- The ``Builtin`` node type may now be used to represent any Python
+  local or global name. This allows expression compilers to refer to
+  e.g. ``get`` or ``getitem``, or to explicit require a builtin object
+  such as one from the ``extra_builtins`` dictionary.
+
+Bugfixes:
+
+- Builtins which are not explicitly disallowed may now be redefined
+  and used as variables (e.g. ``nothing``).
+
+- Fixed compiler issue with circular node annotation loop.
+
+2.3 (2011-08-10)
+----------------
+
+Features:
+
+- Added support for the following syntax to disable inline evaluation
+  in a comment:
+
+    <!--? comment appears verbatim (no ${...} evaluation) -->
+
+  Note that the initial question mark character (?) will be omitted
+  from output.
+
+- The parser now accepts '<' and '>' in attributes. Note that this is
+  invalid markup. Previously, the '<' would not be accepted as a valid
+  attribute value, but this would result in an 'unexpected end tag'
+  error elsewhere. This fixes issue #38.
+
+- The expression compiler now provides methods ``assign_text`` and
+  ``assign_value`` such that a template engine might configure this
+  value conversion to support e.g. encoded strings.
+
+  Note that currently, the only client for the ``assign_text`` method
+  is the string expression type.
+
+- Enable template loader for string-based template classes. Note that
+  the ``filename`` keyword argument may be provided on initialization
+  to identify the template source by filename. This fixes issue #36.
+
+- Added ``extra_builtins`` option to the page template class. These
+  builtins are added to the default builtins dictionary at cook time
+  and may be provided at initialization using the ``extra_builtins``
+  keyword argument.
+
+Bugfixes:
+
+- If a translation domain is set for a fill slot, use this setting
+  instead of the macro template domain.
+
+- The Python expression compiler now correctly decodes HTML entities
+  ``'gt'`` and ``'lt'``. This fixes issue #32.
+
+- The string expression compiler now correctly handles encoded text
+  (when support for encoded strings is enabled). This fixes issue #35.
+
+- Fixed an issue where setting the ``filename`` attribute on a
+  file-based template would not automatically cause an invalidation.
+
+- Exceptions raised by Chameleon can now be copied via
+  ``copy.copy``. This fixes issue #36.
+  [leorochael]
+
+- If copying the exception fails in the exception handler, simply
+  re-raise the original exception and log a warning.
+
+2.2 (2011-07-28)
+----------------
+
+Features:
+
+- Added new expression type ``load:`` that allows loading a
+  template. Both relative and absolute paths are supported. If the
+  path given is relative, then it will be resolved with respect to the
+  directory of the template.
+
+- Added support for dynamic evaluation of expressions.
+
+  Note that this is to support legacy applications. It is not
+  currently wired into the provided template classes.
+
+- Template classes now have a ``builtins`` attribute which may be used
+  to define built-in variables always available in the template
+  variable scope.
+
+Incompatibilities:
+
+- The file-based template class no longer accepts a parameter
+  ``loader``. This parameter would be used to load a template from a
+  relative path, using a ``find(filename)`` method. This was however,
+  undocumented, and probably not very useful since we have the
+  ``TemplateLoader`` mechanism already.
+
+- The compiled template module now contains an ``initialize`` function
+  which takes values that map to the template builtins. The return
+  value of this function is a dictionary that contains the render
+  functions.
+
+Bugfixes:
+
+- The file-based template class no longer verifies the existance of a
+  template file (using ``os.lstat``). This now happens implicitly if
+  eager parsing is enabled, or otherwise when first needed (e.g. at
+  render time).
+
+  This is classified as a bug fix because the previous behavior was
+  probably not what you'd expect, especially if an application
+  initializes a lot of templates without needing to render them
+  immediately.
+
+2.1.1 (2011-07-28)
+------------------
+
+Features:
+
+- Improved exception display. The expression string is now shown in
+  the context of the original source (if available) with a marker
+  string indicating the location of the expression in the template
+  source.
+
+Bugfixes:
+
+- The ``structure`` insertion mode now correctly decodes entities for
+  any expression type (including ``string:``). This fixes issue #30.
+
+- Don't show internal variables in the exception formatter variable
+  listing.
+
+2.1 (2011-07-25)
+----------------
+
+Features:
+
+- Expression interpolation (using the ``${...}`` operator and
+  previously also ``$identifier``) now requires braces everywhere
+  except inside the ``string:`` expression type.
+
+  This change is motivated by a number of legacy templates in which
+  the interpolation format without braces ``$identifier`` appears as
+  text.
+
+2.0.2 (2011-07-25)
+------------------
+
+Bugfixes:
+
+- Don't use dynamic variable scope for lambda-scoped variables (#27).
+
+- Avoid duplication of exception class and message in traceback.
+
+- Fixed issue where a ``metal:fill-slot`` would be ignored if a macro
+  was set to be used on the same element (#16).
+
+2.0.1 (2011-07-23)
+------------------
+
+Bugfixes:
+
+- Fixed issue where global variable definition from macro slots would
+  fail (they would instead be local). This also affects error
+  reporting from inside slots because this would be recorded
+  internally as a global.
+
+- Fixed issue with template cache digest (used for filenames); modules
+  are now invalidated whenever any changes are made to the
+  distribution set available (packages on ``sys.path``).
+
+- Fixed exception handler to better let exceptions propagate through
+  the renderer.
+
+- The disk-based module compiler now mangles template source filenames
+  such that the output Python module is valid and at root level (dots
+  and hyphens are replaced by an underscore). This fixes issue #17.
+
+- Fixed translations (i18n) on Python 2.5.
+
+2.0 (2011-07-14)
+----------------
+
+- Point release.
+
+2.0-rc14 (2011-07-13)
+---------------------
+
+Bugfixes:
+
+- The tab character (``\t``) is now parsed correctly when used inside
+  tags.
+
+Features:
+
+- The ``RepeatDict`` class now works as a proxy behind a seperate
+  dictionary instance.
+
+- Added template constructor option ``keep_body`` which is a flag
+  (also available as a class attribute) that controls whether to save
+  the template body input in the ``body`` attribute.
+
+  This is disabled by default, unless debug-mode is enabled.
+
+- The page template loader class now accepts an optional ``formats``
+  argument which can be used to select an alternative template class.
+
+2.0-rc13 (2011-07-07)
+---------------------
+
+Bugfixes:
+
+- The backslash character (followed by optional whitespace and a line
+  break) was not correctly interpreted as a continuation for Python
+  expressions.
+
+Features:
+
+- The Python expression implementation is now more flexible for
+  external subclassing via a new ``parse`` method.
+
+2.0-rc12 (2011-07-04)
+---------------------
+
+Bugfixes:
+
+- Initial keyword arguments passed to a template now no longer "leak"
+  into the template variable space after a macro call.
+
+- An unexpected end tag is now an unrecoverable error.
+
+Features:
+
+- Improve exception output.
+
+2.0-rc11 (2011-05-26)
+---------------------
+
+Bugfixes:
+
+- Fixed issue where variable names that begin with an underscore were
+  seemingly allowed, but their use resulted in a compiler error.
+
+Features:
+
+- Template variable names are now allowed to be prefixed with a single
+  underscore, but not two or more (reserved for internal use).
+
+  Examples of valid names::
+
+    item
+    ITEM
+    _item
+    camelCase
+    underscore_delimited
+    help
+
+- Added support for Genshi's comment "drop" syntax::
+
+    <!--! This comment will be dropped -->
+
+  Note the additional exclamation (!) character.
+
+  This fixes addresses issue #10.
+
+2.0-rc10 (2011-05-24)
+---------------------
+
+Bugfixes:
+
+- The ``tal:attributes`` statement now correctly operates
+  case-insensitive. The attribute name given in the statement will
+  replace an existing attribute with the same name, without respect to
+  case.
+
+Features:
+
+- Added ``meta:interpolation`` statement to control expression
+  interpolation setting.
+
+  Strings that disable the setting: ``"off"`` and ``"false"``.
+  Strings that enable the setting: ``"on"`` and ``"true"``.
+
+- Expression interpolation now works inside XML comments.
+
+2.0-rc9 (2011-05-05)
+--------------------
+
+Features:
+
+- Better debugging support for string decode and conversion. If a
+  naive join fails, each element in the output will now be attempted
+  coerced to unicode to try and trigger the failure near to the bad
+  string.
+
+2.0-rc8 (2011-04-11)
+--------------------
+
+Bugfixes:
+
+- If a macro defines two slots with the same name, a caller will now
+  fill both with a single usage.
+
+- If a valid of ``None`` is provided as the translation function
+  argument, we now fall back to the class default.
+
+2.0-rc7 (2011-03-29)
+--------------------
+
+Bugfixes:
+
+- Fixed issue with Python 2.5 compatibility AST. This affected at
+  least PyPy 1.4.
+
+Features:
+
+- The ``auto_reload`` setting now defaults to the class value; the
+  base template class gives a default value of
+  ``chameleon.config.AUTO_RELOAD``. This change allows a subclass to
+  provide a custom default value (such as an application-specific
+  debug mode setting).
+
+
+2.0-rc6 (2011-03-19)
+--------------------
+
+Features:
+
+- Added support for ``target_language`` keyword argument to render
+  method. If provided, the argument will be curried onto the
+  translation function.
+
+Bugfixes:
+
+- The HTML entities 'lt', 'gt' and 'quot' appearing inside content
+  subtition expressions are now translated into their native character
+  values. This fixes an issue where you could not dynamically create
+  elements using the ``structure`` (which is possible in ZPT). The
+  need to create such structure stems from the lack of an expression
+  interpolation operator in ZPT.
+
+- Fixed duplicate file pointer issue with test suite (affected Windows
+  platforms only). This fixes issue #9.
+  [oliora]
+
+- Use already open file using ``os.fdopen`` when trying to write out
+  the module source. This fixes LP #731803.
+
+
+2.0-rc5 (2011-03-07)
+--------------------
+
+Bugfixes:
+
+- Fixed a number of issues concerning the escaping of attribute
+  values:
+
+  1) Static attribute values are now included as they appear in the
+     source.
+
+     This means that invalid attribute values such as ``"true &&
+     false"`` are now left alone. It's not the job of the template
+     engine to correct such markup, at least not in the default mode
+     of operation.
+
+  2) The string expression compiler no longer unescapes
+     values. Instead, this is left to each expression
+     compiler. Currently only the Python expression compiler unescapes
+     its input.
+
+  3) The dynamic escape code sequence now correctly only replaces
+     ampersands that are part of an HTML escape format.
+
+Imports:
+
+- The page template classes and the loader class can now be imported
+  directly from the ``chameleon`` module.
+
+Features:
+
+- If a custom template loader is not provided, relative paths are now
+  resolved using ``os.abspath`` (i.e. to the current working
+  directory).
+
+- Absolute paths are normalized using ``os.path.normpath`` and
+  ``os.path.expanduser``. This ensures that all paths are kept in
+  their "canonical" form.
+
+
+2.0-rc4 (2011-03-03)
+--------------------
+
+Bugfixes:
+
+- Fixed an issue where the output of an end-to-end string expression
+  would raise an exception if the expression evaluated to ``None`` (it
+  should simply output nothing).
+
+- The ``convert`` function (which is configurable on the template
+  class level) now defaults to the ``translate`` function (at
+  run-time).
+
+  This fixes an issue where message objects were not translated (and
+  thus converted to a string) using the a provided ``translate``
+  function.
+
+- Fixed string interpolation issue where an expression immediately
+  succeeded by a right curly bracket would not parse.
+
+  This fixes issue #5.
+
+- Fixed error where ``tal:condition`` would be evaluated after
+  ``tal:repeat``.
+
+Features:
+
+- Python expression is now a TALES expression. That means that the
+  pipe operator can be used to chain two or more expressions in a
+  try-except sequence.
+
+  This behavior was ported from the 1.x series. Note that while it's
+  still possible to use the pipe character ("|") in an expression, it
+  must now be escaped.
+
+- The template cache can now be shared by multiple processes.
+
+
+2.0-rc3 (2011-03-02)
+--------------------
+
+Bugfixes:
+
+- Fixed ``atexit`` handler.
+
+  This fixes issue #3.
+
+- If a cache directory is specified, it will now be used even when not
+  in debug mode.
+
+- Allow "comment" attribute in the TAL namespace.
+
+  This fixes an issue in the sense that the reference engine allows
+  any attribute within the TAL namespace. However, only "comment" is
+  in common use.
+
+- The template constructor now accepts a flag ``debug`` which puts the
+  template *instance* into debug-mode regardless of the global
+  setting.
+
+  This fixes issue #1.
+
+Features:
+
+- Added exception handler for exceptions raised while evaluating an
+  expression.
+
+  This handler raises (or attempts to) a new exception of the type
+  ``RenderError``, with an additional base class of the original
+  exception class. The string value of the exception is a formatted
+  error message which includes the expression that caused the
+  exception.
+
+  If we are unable to create the exception class, the original
+  exception is re-raised.
+
+2.0-rc2 (2011-02-28)
+--------------------
+
+- Fixed upload issue.
+
+2.0-rc1 (2011-02-28)
+--------------------
+
+- Initial public release. See documentation for what's new in this
+  series.
diff --git a/lib/chameleon/COPYRIGHT.txt b/lib/chameleon/COPYRIGHT.txt
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/COPYRIGHT.txt
@@ -0,0 +1,7 @@
+Copyright (c) 2011 Malthe Borch and Contributors. All Rights Reserved.
+
+Portions (c) Zope Foundation and contributors (http://www.zope.org/).
+
+Portions (c) Edgewall Software.
+
+Portions (c) 2008 Armin Ronacher.
diff --git a/lib/chameleon/LICENSE.txt b/lib/chameleon/LICENSE.txt
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/LICENSE.txt
@@ -0,0 +1,185 @@
+The majority of the code in Chameleon is supplied under this license:
+
+  A copyright notice accompanies this license document that identifies
+  the copyright holders.
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions are
+  met:
+
+  1.  Redistributions in source code must retain the accompanying
+      copyright notice, this list of conditions, and the following
+      disclaimer.
+
+  2.  Redistributions in binary form must reproduce the accompanying
+      copyright notice, this list of conditions, and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+  3.  Names of the copyright holders must not be used to endorse or
+      promote products derived from this software without prior
+      written permission from the copyright holders.
+
+  4.  If any files are modified, you must cause the modified files to
+      carry prominent notices stating that you changed the files and
+      the date of any change.
+
+  Disclaimer
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND
+    ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+    TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+    PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+    HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+    TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+    ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+
+Portions of the code in Chameleon are supplied under the ZPL (headers
+within individiual files indicate that these portions are licensed
+under the ZPL):
+
+  Zope Public License (ZPL) Version 2.1
+  -------------------------------------
+
+  A copyright notice accompanies this license document that
+  identifies the copyright holders.
+
+  This license has been certified as open source. It has also
+  been designated as GPL compatible by the Free Software
+  Foundation (FSF).
+
+  Redistribution and use in source and binary forms, with or
+  without modification, are permitted provided that the
+  following conditions are met:
+
+  1. Redistributions in source code must retain the
+     accompanying copyright notice, this list of conditions,
+     and the following disclaimer.
+
+  2. Redistributions in binary form must reproduce the accompanying
+     copyright notice, this list of conditions, and the
+     following disclaimer in the documentation and/or other
+     materials provided with the distribution.
+
+  3. Names of the copyright holders must not be used to
+     endorse or promote products derived from this software
+     without prior written permission from the copyright
+     holders.
+
+  4. The right to distribute this software or to use it for
+     any purpose does not give you the right to use
+     Servicemarks (sm) or Trademarks (tm) of the copyright
+     holders. Use of them is covered by separate agreement
+     with the copyright holders.
+
+  5. If any files are modified, you must cause the modified
+     files to carry prominent notices stating that you changed
+     the files and the date of any change.
+
+  Disclaimer
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS''
+    AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
+    NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+    AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
+    NO EVENT SHALL THE COPYRIGHT HOLDERS BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+    OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+    DAMAGE.
+
+Portions of the code in Chameleon are supplied under the BSD license
+(headers within individiual files indicate that these portions are
+licensed under this license):
+
+  All rights reserved.
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions
+  are met:
+
+   1. Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+   2. Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in
+      the documentation and/or other materials provided with the
+      distribution.
+   3. The name of the author may not be used to endorse or promote
+      products derived from this software without specific prior
+      written permission.
+
+  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+  ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+  IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+  IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+Portions of the code in Chameleon are supplied under the Python
+License (headers within individiual files indicate that these portions
+are licensed under this license):
+
+  PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
+  --------------------------------------------
+
+  1. This LICENSE AGREEMENT is between the Python Software Foundation
+  ("PSF"), and the Individual or Organization ("Licensee") accessing and
+  otherwise using this software ("Python") in source or binary form and
+  its associated documentation.
+
+  2. Subject to the terms and conditions of this License Agreement, PSF
+  hereby grants Licensee a nonexclusive, royalty-free, world-wide
+  license to reproduce, analyze, test, perform and/or display publicly,
+  prepare derivative works, distribute, and otherwise use Python
+  alone or in any derivative version, provided, however, that PSF's
+  License Agreement and PSF's notice of copyright, i.e., "Copyright (c)
+  2001, 2002, 2003, 2004 Python Software Foundation; All Rights Reserved"
+  are retained in Python alone or in any derivative version prepared
+  by Licensee.
+
+  3. In the event Licensee prepares a derivative work that is based on
+  or incorporates Python or any part thereof, and wants to make
+  the derivative work available to others as provided herein, then
+  Licensee hereby agrees to include in any such work a brief summary of
+  the changes made to Python.
+
+  4. PSF is making Python available to Licensee on an "AS IS"
+  basis.  PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+  IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
+  DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+  FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
+  INFRINGE ANY THIRD PARTY RIGHTS.
+
+  5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+  FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+  A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
+  OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+  6. This License Agreement will automatically terminate upon a material
+  breach of its terms and conditions.
+
+  7. Nothing in this License Agreement shall be deemed to create any
+  relationship of agency, partnership, or joint venture between PSF and
+  Licensee.  This License Agreement does not grant permission to use PSF
+  trademarks or trade name in a trademark sense to endorse or promote
+  products or services of Licensee, or any third party.
+
+  8. By copying, installing or otherwise using Python, Licensee
+  agrees to be bound by the terms and conditions of this License
+  Agreement.
diff --git a/lib/chameleon/Makefile b/lib/chameleon/Makefile
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/Makefile
@@ -0,0 +1,89 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    = docs
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = _build
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS)
+
+.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html      to make standalone HTML files"
+	@echo "  dirhtml   to make HTML files named index.html in directories"
+	@echo "  pickle    to make pickle files"
+	@echo "  json      to make JSON files"
+	@echo "  htmlhelp  to make HTML files and a HTML help project"
+	@echo "  qthelp    to make HTML files and a qthelp project"
+	@echo "  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  changes   to make an overview of all changed/added/deprecated items"
+	@echo "  linkcheck to check all external links for integrity"
+	@echo "  doctest   to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+	-rm -rf $(BUILDDIR)/*
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Chameleon.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Chameleon.qhc"
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
+	      "run these through (pdf)latex."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/lib/chameleon/README.rst b/lib/chameleon/README.rst
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/README.rst
@@ -0,0 +1,25 @@
+Overview
+========
+
+Chameleon is an HTML/XML template engine for `Python
+<http://www.python.org>`_. It uses the *page templates* language.
+
+You can use it in any Python web application with just about any
+version of Python (2.5 and up, including 3.x and `pypy
+<http://pypy.org>`_).
+
+Visit the `website <http://pagetemplates.org>`_ for more information
+or the `documentation <http://pagetemplates.org/docs/latest/>`_.
+
+License and Copyright
+---------------------
+
+This software is made available as-is under a BSD-like license [1]_
+(see included copyright notice).
+
+
+Notes
+-----
+
+.. [1] This software is licensed under the `Repoze
+       <http://repoze.org/license.html>`_ license.
diff --git a/lib/chameleon/distribute_setup.py b/lib/chameleon/distribute_setup.py
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/distribute_setup.py
@@ -0,0 +1,485 @@
+#!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.14"
+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 _no_sandbox(function):
+    def __no_sandbox(*args, **kw):
+        try:
+            from setuptools.sandbox import DirectorySandbox
+            if not hasattr(DirectorySandbox, '_old'):
+                def violation(*args):
+                    pass
+                DirectorySandbox._old = DirectorySandbox._violation
+                DirectorySandbox._violation = violation
+                patched = True
+            else:
+                patched = False
+        except ImportError:
+            patched = False
+
+        try:
+            return function(*args, **kw)
+        finally:
+            if patched:
+                DirectorySandbox._violation = DirectorySandbox._old
+                del DirectorySandbox._old
+
+    return __no_sandbox
+
+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
+
+_patch_file = _no_sandbox(_patch_file)
+
+def _same_content(path, content):
+    return open(path).read() == content
+
+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
+
+_remove_flat_installation = _no_sandbox(_remove_flat_installation)
+
+def _after_install(dist):
+    log.warn('After install bootstrap.')
+    placeholder = dist.get_command_obj('install').install_purelib
+    _create_fake_setuptools_pkg_info(placeholder)
+
+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()
+
+_create_fake_setuptools_pkg_info = _no_sandbox(_create_fake_setuptools_pkg_info)
+
+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
+
+_patch_egg_dir = _no_sandbox(_patch_egg_dir)
+
+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)
+        if arg == '--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
+    # pip marker to avoid a relaunch bug
+    if sys.argv[:3] == ['-c', 'install', '--single-version-externally-managed']:
+        sys.argv[0] = 'setup.py'
+    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/lib/chameleon/docs/conf.py b/lib/chameleon/docs/conf.py
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/docs/conf.py
@@ -0,0 +1,194 @@
+# -*- coding: utf-8 -*-
+#
+# Chameleon documentation build configuration file, created by
+# sphinx-quickstart on Sun Nov  1 16:08:00 2009.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.append(os.path.abspath('.'))
+
+# -- General configuration -----------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'Chameleon'
+copyright = u'2008-2011 by Malthe Borch and the Repoze Community'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '2.5'
+# The full version, including alpha/beta/rc tags.
+release = '2.5.0'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of documents that shouldn't be included in the build.
+#unused_docs = []
+
+# List of directories, relative to source directory, that shouldn't be searched
+# for source files.
+exclude_trees = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  Major themes that come with
+# Sphinx are currently 'default' and 'sphinxdoc'.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+html_title = "Chameleon %s documentation" % version
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = []
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bchameleonm,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_use_modindex = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = ''
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'chameleondoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'chameleon.tex', u'Chameleon Documentation',
+   u'Malthe Borch et. al', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_use_modindex = True
diff --git a/lib/chameleon/docs/configuration.rst b/lib/chameleon/docs/configuration.rst
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/docs/configuration.rst
@@ -0,0 +1,43 @@
+Configuration
+=============
+
+Most settings can be provided as keyword-arguments to the template
+constructor classes.
+
+There are certain settings which are required at environment
+level. Acceptable values are ``"0"``, ``"1"``, or the literals
+``"true"`` or ``"false"`` (case-insensitive).
+
+General usage
+-------------
+
+The following settings are useful in general.
+
+``CHAMELEON_EAGER``
+   Parse and compile templates on instantiation.
+
+``CHAMELEON_CACHE``
+
+   When set to a file system path, the template compiler will write
+   its output to files in this directory and use it as a cache.
+
+   This not only enables you to see the compiler output, but also
+   speeds up startup.
+
+``CHAMELEON_RELOAD``
+   This setting controls the default value of the ``auto_reload``
+   parameter.
+
+Development
+-----------
+
+The following settings are mostly useful during development or
+debugging of the library itself.
+
+``CHAMELEON_DEBUG``
+
+   Enables a set of debugging settings which make it easier to
+   discover and research issues with the engine itself.
+
+   This implicitly enables auto-reload for any template.
+
diff --git a/lib/chameleon/docs/index.rst b/lib/chameleon/docs/index.rst
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/docs/index.rst
@@ -0,0 +1,217 @@
+Chameleon
+=========
+
+Chameleon is an HTML/XML template engine for `Python
+<http://www.python.org>`_.
+
+It's designed to generate the document output of a web application,
+typically HTML markup or XML.
+
+The language used is *page templates*, originally a `Zope
+<http://www.zope.org>`_ invention [1]_, but available here as a
+:ref:`standalone library <no-dependencies>` that you can use in any
+script or application running Python 2.5 and up (including 3.x and
+`pypy <http://pypy.org>`_). It comes with a set of :ref:`new features
+<new-features>`, too.
+
+The template engine compiles templates into Python byte-code and is optimized
+for speed. For a complex template language, the performance is
+:ref:`very good <fast>`.
+
+  *Found a bug?* Please report issues to the `issue tracker <http://github.com/malthe/chameleon/issues>`_.
+
+  *Need help?* Post to the Pylons `discussion list <http://groups.google.com/group/pylons-discuss/>`_ or join the ``#pyramid`` channel on `Freenode IRC <http://freenode.net/>`_.
+
+Getting the code
+----------------
+
+You can `download <http://pypi.python.org/pypi/Chameleon#downloads>`_ the
+package from the Python package index or install the latest release
+using setuptools or the newer `distribute
+<http://packages.python.org/distribute/>`_ (required for Python 3.x)::
+
+  $ easy_install Chameleon
+
+.. _no-dependencies:
+
+There are no required library dependencies on Python 2.7 and up
+[2]_. On 2.5 and 2.6, the `ordereddict
+<http://pypi.python.org/pypi/ordereddict>`_ and `unittest2
+<http://pypi.python.org/pypi/unittest2>`_ packages are set as
+dependencies.
+
+The project is hosted in a `GitHub repository
+<http://github.com/malthe/chameleon>`_. Code contributions are
+welcome. The easiest way is to use the `pull request
+<http://help.github.com/pull-requests/>`_ interface.
+
+
+Introduction
+------------
+
+The *page templates* language is used within your document structure
+as special element attributes and text markup. Using a set of simple
+language constructs, you control the document flow, element
+repetition, text replacement and translation.
+
+.. note:: If you've used page templates in a Zope environment previously, note that Chameleon uses Python as the default expression language (instead of *path* expressions).
+
+The basic language (known as the *template attribute language* or TAL)
+is simple enough to grasp from an example:
+
+.. code-block:: genshi
+
+  <html>
+    <body>
+      <h1>Hello, ${'world'}!</h1>
+      <table>
+        <tr tal:repeat="row 'apple', 'banana', 'pineapple'">
+          <td tal:repeat="col 'juice', 'muffin', 'pie'">
+             ${row.capitalize()} ${col}
+          </td>
+        </tr>
+      </table>
+    </body>
+  </html>
+
+The ``${...}`` notation is short-hand for text insertion [3]_. The
+Python-expression inside the braces is evaluated and the result
+included in the output. By default, the string is escaped before
+insertion. To avoid this, use the ``structure:`` prefix:
+
+.. code-block:: genshi
+
+  <div>${structure: ...}</div>
+
+Note that if the expression result is an object that implements an
+``__html__()`` method [4]_, this method will be called and the result
+treated as "structure". An example of such an object is the
+``Markup`` class that's included as a utility::
+
+  from chameleon.utils import Markup
+  username = "<tt>%s</tt>" % username
+
+The macro language (known as the *macro expansion language* or METAL)
+provides a means of filling in portions of a generic template.
+
+On the left, the macro template; on the right, a template that loads
+and uses the macro, filling in the "content" slot:
+
+.. code-block:: genshi
+
+  <html xmlns="http://www.w3.org/1999/xhtml">             <metal:main use-macro="load: main.pt">
+    <head>                                                   <p metal:fill-slot="content">${structure: document.body}<p/>
+      <title>Example &mdash; ${document.title}</title>    </metal:main>
+    </head>
+    <body>
+      <h1>${document.title}</h1>
+
+      <div id="content">
+        <metal:content define-slot="content" />
+      </div>
+    </body>
+  </html>
+
+In the example, the expression type :ref:`load <load-expression>` is
+used to retrieve a template from the file system using a path relative
+to the calling template.
+
+The METAL system works with TAL such that you can for instance fill in
+a slot that appears in a ``tal:repeat`` loop, or refer to variables
+defined using ``tal:define``.
+
+The third language subset is the translation system (known as the
+*internationalization language* or I18N):
+
+.. code-block:: genshi
+
+  <html i18n:domain="example">
+
+    ...
+
+    <div i18n:translate="">
+       You have <span i18n:name="amount">${round(amount, 2)}</span> dollars in your account.
+    </div>
+
+    ...
+
+  </html>
+
+Each translation message is marked up using ``i18n:translate`` and
+values can be mapped using ``i18n:name``. Attributes are marked for
+translation using ``i18n:attributes``. The template engine generates
+`gettext <http://www.gnu.org/s/gettext/>`_ translation strings from
+the markup::
+
+  "You have ${amount} dollars in your account."
+
+If you use a web framework such as `Pyramid
+<https://docs.pylonsproject.org/docs/pyramid.html>`_, the translation
+system is set up automatically and will negotiate on a *target
+language* based on the HTTP request or other parameter. If not, then
+you need to configure this manually.
+
+Next steps
+----------
+
+This was just an introduction. There are a number of other basic
+statements that you need to know in order to use the language. This is
+all covered in the :ref:`language reference <language-reference>`.
+
+If you're already familiar with the page template language, you can
+skip ahead to the :ref:`getting started <getting-started-with-cpt>`
+section to learn how to use the template engine in your code.
+
+To learn about integration with your favorite web framework see the
+section on :ref:`framework integration <framework-integration>`.
+
+License
+-------
+
+This software is made available under a BSD-like license.
+
+
+Contents
+========
+
+.. toctree::
+   :maxdepth: 2
+
+   library.rst
+   reference.rst
+   integration.rst
+   configuration.rst
+
+Indices and Tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
+Notes
+=====
+
+.. [1] The template language specifications and API for the Page
+       Templates engine are based on Zope Page Templates (see in
+       particular `zope.pagetemplate
+       <http://pypi.python.org/pypi/zope.pagetemplate>`_). However,
+       the Chameleon compiler and Page Templates engine is an entirely
+       new codebase, packaged as a standalone distribution. It does
+       require a Zope software environment.
+
+.. [2] The translation system in Chameleon is pluggable and based on
+       `gettext <http://www.gnu.org/s/gettext/>`_.
+       There is built-in support for the `zope.i18n
+       <http://pypi.python.org/pypi/zope.i18n>`_ package. If this
+       package is installed, it will be used by default. The
+       `translationstring
+       <http://pypi.python.org/pypi/translationstring>`_ package
+       offers some of the same helper and utility classes, without the
+       Zope application interface.
+
+.. [3] This syntax was taken from `Genshi <http://genshi.edgewall.org/>`_.
+
+.. [4] See the `WebHelpers
+       <https://docs.pylonsproject.org/projects/webhelpers/dev/modules/html/__init__.html>`_
+       library which provide a simple wrapper around this method.
diff --git a/lib/chameleon/docs/integration.rst b/lib/chameleon/docs/integration.rst
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/docs/integration.rst
@@ -0,0 +1,41 @@
+.. _framework-integration:
+
+Integration
+===========
+
+Integration with Chameleon is available for a number of popular web
+frameworks. The framework will usually provide loading mechanisms and
+translation (internationalization) configuration.
+
+Pyramid
+-------
+
+Chameleon is the default template engine for the `Pyramid
+<http://pylonsproject.org/projects/pyramid/about>`_ framework. See the
+section on `Page Templates
+<http://docs.pylonsproject.org/projects/pyramid/1.1/narr/templates.html#chameleon-zpt-templates>`_ for a complete reference.
+
+Zope 2 / Plone
+--------------
+
+Install the `five.pt <http://pypi.python.org/pypi/five.pt>`_ package
+to replace the reference template engine (globally).
+
+Zope Toolkit (ZTK)
+------------------
+
+Install the `z3c.pt <http://pypi.python.org/pypi/z3c.pt>`_ package for
+applications based on the `Zope Toolkit
+<http://docs.zope.org/zopetoolkit/>`_ (ZTK). Note that you need to
+explicit use the template classes from this package.
+
+Grok
+----
+
+Support for the `Grok <http://grok.zope.org/>`_ framework is available
+in the `grokcore.chameleon
+<http://pypi.python.org/pypi/grokcore.chameleon>`_ package.
+
+This package will setup Grok's policy for templating integration and
+associate the Chameleon template components for the ``.cpt`` template
+filename extension.
diff --git a/lib/chameleon/docs/library.rst b/lib/chameleon/docs/library.rst
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/docs/library.rst
@@ -0,0 +1,238 @@
+Library Documentation
+=====================
+
+This section documents the package as a Python library. To learn about
+the page template language, consult the :ref:`language reference
+<language-reference>`.
+
+.. _getting-started-with-cpt:
+
+Getting started
+---------------
+
+There are several template constructor classes available, one for each
+of the combinations *text* or *xml*, and *string* or *file*.
+
+The file-based constructor requires an absolute path. To set up a
+templates directory *once*, use the template loader class::
+
+  import os
+
+  path = os.path.basedir(__file__)
+
+  from chameleon import PageTemplateLoader
+  templates = PageTemplateLoader(os.path.join(path, "templates"))
+
+Then, to load a template relative to the provided path, use dictionary
+syntax::
+
+  template = templates['hello.pt']
+
+Alternatively, use the appropriate template class directly. Let's try
+with a string input::
+
+  from chameleon import PageTemplate
+  template = PageTemplate("<div>Hello, ${name}.</div>")
+
+All template instances are callable. Provide variables by keyword
+argument::
+
+  >>> template(name='John')
+  '<div>Hello, John.</div>'
+
+.. _fast:
+
+Performance
+-----------
+
+The template engine compiles (or *translates*) template source code
+into Python byte-code. In simple templates this yields an increase in
+performance of about 7 times in comparison to the reference
+implementation.
+
+In benchmarks for the content management system `Plone
+<http://www.plone.org>`_, switching to Chameleon yields a request to
+response improvement of 20-50%.
+
+Extension
+---------
+
+You can extend the language through the expression engine by writing
+your own expression compiler.
+
+Let's try and write an expression compiler for an expression type that
+will simply uppercase the supplied value. We'll call it ``upper``.
+
+You can write such a compiler as a closure:
+
+.. code-block:: python
+
+   import ast
+
+   def uppercase_expression(string):
+       def compiler(target, engine):
+           uppercased = self.string.uppercase()
+           value = ast.Str(uppercased)
+           return [ast.Assign(targets=[target], value=value)]
+       return compiler
+
+To make it available under a certain prefix, we'll add it to the
+expression types dictionary.
+
+.. code-block:: python
+
+   from chameleon import PageTemplate
+   PageTemplate.expression_types['upper'] = uppercase_expression
+
+Alternatively, you could subclass the template class and set the
+attribute ``expression_types`` to a dictionary that includes your
+expression:
+
+.. code-block:: python
+
+   from chameleon import PageTemplateFile
+   from chameleon.tales import PythonExpr
+
+   class MyPageTemplateFile(PageTemplateFile):
+       expression_types = {
+           'python': PythonExpr,
+           'upper': uppercase_expression
+           }
+
+You can now uppercase strings *natively* in your templates::
+
+  <div tal:content="upper: hello, world" />
+
+It's probably best to stick with a Python expression::
+
+  <div tal:content="'hello, world'.upper()" />
+
+
+.. _whats-new:
+
+Changes between 1.x and 2.x
+---------------------------
+
+This sections describes new features, improvements and changes from
+1.x to 2.x.
+
+New parser
+~~~~~~~~~~
+
+This series features a new, custom-built parser, implemented in pure
+Python. It parses both HTML and XML inputs (the previous parser relied
+on the expat system library and was more strict about its input).
+
+The main benefit of the new parser is that the compiler is now able to
+point to the source location of parse- and compilation errors much
+more accurately. This should be a great aid in debugging these errors.
+
+Compatible output
+~~~~~~~~~~~~~~~~~
+
+The 2.x engine matches the output of the reference implementation more
+closely (usually exactly). There are less differences altogether; for
+instance, the method of escaping TALES expression (usually a
+semicolon) has been changed to match that of the reference
+implementation.
+
+New language features
+~~~~~~~~~~~~~~~~~~~~~
+
+This series also introduces a number of new language features:
+
+1. Support for the ``tal:on-error`` from the reference specification
+   has been added.
+
+2. Two new attributes ``tal:switch`` and ``tal:case`` have been added
+   to make element conditions more flexible.
+
+
+Code improvements
+~~~~~~~~~~~~~~~~~
+
+The template classes have been refactored and simplified allowing
+better reuse of code and more intuitive APIs on the lower levels.
+
+Expression engine
+~~~~~~~~~~~~~~~~~
+
+The expression engine has been redesigned to make it easier to
+understand and extend. The new engine is based on the ``ast`` module
+(available since Python 2.6; backports included for Python 2.5). This
+means that expression compilers now need to return a valid list of AST
+statements that include an assignment to the target node.
+
+Compiler
+~~~~~~~~
+
+The new compiler has been optimized for complex templates. As a
+result, in the benchmark suite included with the package, this
+compiler scores about half of the 1.x series. For most real world
+applications, the engine should still perform as well as the 1.x
+series.
+
+
+API reference
+-------------
+
+This section describes the documented API of the library.
+
+Template classes
+~~~~~~~~~~~~~~~~
+
+Use the ``PageTemplate*`` template classes to define a template from a
+string or file input:
+
+.. automodule:: chameleon
+
+  .. autoclass:: chameleon.PageTemplate
+
+     Note: The remaining classes take the same general configuration
+     arguments.
+
+     .. automethod:: render
+
+  .. autoclass:: chameleon.PageTemplateFile(filename, **config)
+
+  .. autoclass:: chameleon.PageTextTemplate
+
+  .. autoclass:: chameleon.PageTextTemplateFile
+
+Template loader
+~~~~~~~~~~~~~~~
+
+Some systems have framework support for loading templates from
+files. The following loader class is directly compatible with the
+Pylons framework and may be adapted to other frameworks:
+
+.. class:: chameleon.PageTemplateLoader(search_path=None, default_extension=None, **config)
+
+   Load templates from ``search_path`` (must be a string or a list of
+   strings)::
+
+     templates = PageTemplateLoader(path)
+     example = templates['example.pt']
+
+   If ``default_extension`` is provided, this will be added to inputs
+   that do not already have an extension::
+
+     templates = PageTemplateLoader(path, ".pt")
+     example = templates['example']
+
+   Any additional keyword arguments will be passed to the template
+   constructor::
+
+     templates = PageTemplateLoader(path, debug=True, encoding="utf-8")
+
+   .. automethod:: load
+
+Expression engine
+~~~~~~~~~~~~~~~~~
+
+For advanced integration, the compiler module provides support for
+dynamic expression evaluation:
+
+.. automodule:: chameleon.compiler
+
+  .. autoclass:: chameleon.compiler.ExpressionEvaluator
diff --git a/lib/chameleon/docs/reference.rst b/lib/chameleon/docs/reference.rst
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/docs/reference.rst
@@ -0,0 +1,1649 @@
+:tocdepth: 4
+
+.. _language-reference:
+
+.. highlight:: xml
+
+Language Reference
+==================
+
+The language reference is structured such that it can be read as a
+general introduction to the *page templates* language.
+
+It's split into parts that correspond to each of the main language
+features.
+
+Syntax
+######
+
+You can safely :ref:`skip this section <tal>` if you're familiar with
+how template languages work or just want to learn by example.
+
+An *attribute language* is a programming language designed to render
+documents written in XML or HTML markup.  The input must be a
+well-formed document.  The output from the template is usually
+XML-like but isn't required to be well-formed.
+
+The statements of the language are document tags with special
+attributes, and look like this::
+
+    <p namespace-prefix:command="argument"> ... </p>
+
+In the above example, the attribute
+``namespace-prefix:command="argument"`` is the statement, and the
+entire paragraph tag is the statement's element.  The statement's
+element is the portion of the document on which this statement
+operates.
+
+The namespace prefixes are typically declared once, at the top of a
+template (note that prefix declarations for the template language
+namespaces are omitted from the template output)::
+
+  <html xmlns="http://www.w3.org/1999/xhtml"
+        xmlns:tal="http://xml.zope.org/namespaces/tal"
+        xmlns:metal="http://xml.zope.org/namespaces/metal"
+        xmlns:i18n="http://xml.zope.org/namespaces/i18n">
+    ...
+  </html>
+
+Thankfully, sane namespace prefix defaults are in place to let us skip
+most of the boilerplate::
+
+  <html xmlns="http://www.w3.org/1999/xhtml">
+    <body>
+      <p tal:content="text"> ... </p>
+    </body>
+  </html>
+
+Note how ``tal`` is used without an explicit namespace
+declaration. Chameleon sets up defaults for ``metal`` and ``i18n`` as
+well.
+
+.. note:: Default prefixes are a special feature of Chameleon.
+
+.. _tal:
+
+Basics (TAL)
+############
+
+The *template attribute language* is used to create dynamic XML-like
+content.  It allows elements of a document to be replaced, repeated,
+or omitted.
+
+Statements
+----------
+
+These are the available statements:
+
+==================  ==============
+ Statement           Description
+==================  ==============
+``tal:define``      Define variables.
+``tal:switch``      Defines a switch condition
+``tal:condition``   Include element only if expression is true.
+``tal:repeat``      Repeat an element.
+``tal:case``        Includes element only if expression is equal to parent switch.
+``tal:content``     Substitute the content of an element.
+``tal:replace``     Replace the element with dynamic content.
+``tal:omit-tag``    Omit the element tags, leaving only the inner content.
+``tal:attributes``  Dynamically change or insert element attributes.
+``tal:on-error``    Substitute the content of an element if processing fails.
+==================  ==============
+
+When there is only one TAL statement per element, the order in which
+they are executed is simple.  Starting with the root element, each
+element's statements are executed, then each of its child elements is
+visited, in order, to do the same::
+
+  <html>
+    <meta>
+      <title tal:content="context.title" />
+    </meta>
+    <body>
+      <div tal:condition="items">
+        <p>These are your items:</p>
+        <ul>
+          <li tal:repeat="item items" tal:content="item" />
+        </ul>
+      </div>
+    </body>
+  </html>
+
+Any combination of statements may appear on the same element, except
+that the ``tal:content`` and ``tal:replace`` statements may not be
+used on the same element.
+
+.. note:: The ``tal:case`` and ``tal:switch`` statements are available
+          in Chameleon only.
+
+TAL does not use use the order in which statements are written in the
+tag to determine the order in which they are executed.  When an
+element has multiple statements, they are executed in the order
+printed in the table above.
+
+There is a reasoning behind this ordering.  Because users often want
+to set up variables for use in other statements contained within this
+element or subelements, ``tal:define`` is executed first. Then any
+switch statement. ``tal:condition`` follows, then ``tal:repeat``, then
+``tal:case``. We are now rendering an element; first ``tal:content``
+or ``tal:replace``. Finally, before ``tal:attributes``, we have
+``tal:omit-tag`` (which is implied with ``tal:replace``).
+
+.. note:: *TALES* is used as the expression language for the "stuff in
+   the quotes". The default syntax is simply Python, but
+   other inputs are possible --- see the section on :ref:`expressions
+   <tales>`.
+
+``tal:attributes``
+^^^^^^^^^^^^^^^^^^
+
+Updates or inserts element attributes.
+
+::
+
+  tal:attributes="href request.url"
+
+Syntax
+~~~~~~
+
+``tal:attributes`` syntax::
+
+    argument             ::= attribute_statement [';' attribute_statement]*
+    attribute_statement  ::= attribute_name expression
+    attribute_name       ::= [namespace-prefix ':'] Name
+    namespace-prefix     ::= Name
+
+
+Description
+~~~~~~~~~~~
+
+The ``tal:attributes`` statement replaces the value of an attribute
+(or creates an attribute) with a dynamic value.  The
+value of each expression is converted to a string, if necessary.
+
+.. note:: You can qualify an attribute name with a namespace prefix,
+   for example ``html:table``, if you are generating an XML document
+   with multiple namespaces.
+
+If an attribute expression evaluates to ``None``, the attribute is
+deleted from the statement element (or simply not inserted).
+
+If the expression evaluates to the symbol ``default`` (a symbol which
+is always available when evaluating attributes), its value is defined
+as the default static attribute value. If there is no such default
+value, a return value of ``default`` will drop the attribute.
+
+If you use ``tal:attributes`` on an element with an active
+``tal:replace`` command, the ``tal:attributes`` statement is ignored.
+
+If you use ``tal:attributes`` on an element with a ``tal:repeat``
+statement, the replacement is made on each repetition of the element,
+and the replacement expression is evaluated fresh for each repetition.
+
+.. note:: If you want to include a semicolon (";") in an expression, it
+          must be escaped by doubling it (";;") [1]_.
+
+Examples
+~~~~~~~~
+
+Replacing a link::
+
+    <a href="/sample/link.html"
+       tal:attributes="href context.url()"
+       >
+       ...
+    </a>
+
+Replacing two attributes::
+
+    <textarea rows="80" cols="20"
+              tal:attributes="rows request.rows();cols request.cols()"
+        />
+
+A checkbox input::
+
+    <input type="input" tal:attributes="checked True" />
+
+``tal:condition``
+^^^^^^^^^^^^^^^^^
+
+Conditionally includes or omits an element::
+
+  <div tal:condition="comments">
+    ...
+  </div>
+
+Syntax
+~~~~~~
+
+``tal:condition`` syntax::
+
+    argument ::= expression
+
+Description
+~~~~~~~~~~~
+
+ The ``tal:condition`` statement includes the statement element in the
+ template only if the condition is met, and omits it otherwise.  If
+ its expression evaluates to a *true* value, then normal processing of
+ the element continues, otherwise the statement element is immediately
+ removed from the template.  For these purposes, the value ``nothing``
+ is false, and ``default`` has the same effect as returning a true
+ value.
+
+.. note:: Like Python itself, ZPT considers None, zero, empty strings,
+   empty sequences, empty dictionaries, and instances which return a
+   nonzero value from ``__len__`` or ``__nonzero__`` false; all other
+   values are true, including ``default``.
+
+Examples
+~~~~~~~~
+
+Test a variable before inserting it::
+
+        <p tal:condition="request.message" tal:content="request.message" />
+
+Testing for odd/even in a repeat-loop::
+
+        <div tal:repeat="item range(10)">
+          <p tal:condition="repeat.item.even">Even</p>
+          <p tal:condition="repeat.item.odd">Odd</p>
+        </div>
+
+``tal:content``
+^^^^^^^^^^^^^^^
+
+Replaces the content of an element.
+
+Syntax
+~~~~~~
+
+``tal:content`` syntax::
+
+        argument ::= (['text'] | 'structure') expression
+
+Description
+~~~~~~~~~~~
+
+Rather than replacing an entire element, you can insert text or
+structure in place of its children with the ``tal:content`` statement.
+The statement argument is exactly like that of ``tal:replace``, and is
+interpreted in the same fashion.  If the expression evaluates to
+``nothing``, the statement element is left childless.  If the
+expression evaluates to ``default``, then the element's contents are
+evaluated.
+
+The default replacement behavior is ``text``, which replaces
+angle-brackets and ampersands with their HTML entity equivalents.  The
+``structure`` keyword passes the replacement text through unchanged,
+allowing HTML/XML markup to be inserted.  This can break your page if
+the text contains unanticipated markup (eg.  text submitted via a web
+form), which is the reason that it is not the default.
+
+.. note:: The ``structure`` keyword exists to provide backwards
+          compatibility.  In Chameleon, the ``structure:`` expression
+          type provides the same functionality (also for inline
+          expressions).
+
+
+Examples
+~~~~~~~~
+
+Inserting the user name::
+
+        <p tal:content="user.getUserName()">Fred Farkas</p>
+
+Inserting HTML/XML::
+
+        <p tal:content="structure context.getStory()">
+           Marked <b>up</b> content goes here.
+        </p>
+
+``tal:define``
+^^^^^^^^^^^^^^
+
+Defines local variables.
+
+Syntax
+~~~~~~
+
+``tal:define`` syntax::
+
+    argument ::= define_scope [';' define_scope]*
+    define_scope ::= (['local'] | 'global')
+    define_var define_var ::= variable_name
+    expression variable_name ::= Name
+
+Description
+~~~~~~~~~~~
+
+The ``tal:define`` statement defines variables.  When you define a
+local variable in a statement element, you can use that variable in
+that element and the elements it contains.  If you redefine a variable
+in a contained element, the new definition hides the outer element's
+definition within the inner element.
+
+Note that valid variable names are any Python identifier string
+including underscore, although two or more leading underscores are
+disallowed (used internally by the compiler). Further, names are
+case-sensitive.
+
+Python builtins are always "in scope", but most of them may be
+redefined (such as ``help``). Exceptions are:: ``float``, ``int``,
+``len``, ``long``, ``str``, ``None``, ``True`` and ``False``.
+
+In addition, the following names are reserved: ``econtext``,
+``rcontext``, ``translate``, ``decode`` and ``convert``.
+
+If the expression associated with a variable evaluates to ``nothing``,
+then that variable has the value ``nothing``, and may be used as such
+in further expressions. Likewise, if the expression evaluates to
+``default``, then the variable has the value ``default``, and may be
+used as such in further expressions.
+
+You can define two different kinds of variables: *local* and
+*global*. When you define a local variable in a statement element, you
+can only use that variable in that element and the elements it
+contains. If you redefine a local variable in a contained element, the
+new definition hides the outer element's definition within the inner
+element. When you define a global variables, you can use it in any
+element processed after the defining element. If you redefine a global
+variable, you replace its definition for the rest of the template.
+
+To set the definition scope of a variable, use the keywords ``local``
+or ``global`` in front of the assignment. The default setting is
+``local``; thus, in practice, only the ``global`` keyword is used.
+
+.. note:: If you want to include a semicolon (";") in an expression, it
+          must be escaped by doubling it (";;") [1]_.
+
+Examples
+~~~~~~~~
+
+Defining a variable::
+
+        tal:define="company_name 'Zope Corp, Inc.'"
+
+Defining two variables, where the second depends on the first::
+
+        tal:define="mytitle context.title; tlen len(mytitle)"
+
+
+``tal:switch`` and ``tal:case``
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Defines a switch clause.
+
+::
+
+  <ul tal:switch="len(items) % 2">
+    <li tal:case="True">odd</li>
+    <li tal:case="False">even</li>
+  </ul>
+
+Syntax
+~~~~~~
+
+``tal:case`` and ``tal:switch`` syntax::
+
+    argument ::= expression
+
+Description
+~~~~~~~~~~~
+
+The *switch* and *case* construct is a short-hand syntax for
+evaluating a set of expressions against a parent value.
+
+The ``tal:switch`` statement is used to set a new parent value and the
+``tal:case`` statement works like a condition and only allows content
+if the expression matches the value.
+
+If the case expression is is the symbol ``default``, it always matches
+the switch.
+
+.. note:: These statements are only available in Chameleon 2.x and not
+          part of the ZPT specification.
+
+Examples
+~~~~~~~~
+
+::
+
+  <ul tal:switch="item.type">
+    <li tal:case="'document'">
+      Document
+    </li>
+    <li tal:case="'folder'">
+      Folder
+    </li>
+  </ul>
+
+Note that any and all cases that match the switch will be allowed.
+
+
+``tal:omit-tag``
+^^^^^^^^^^^^^^^^
+
+Removes an element, leaving its contents.
+
+Syntax
+~~~~~~
+
+``tal:omit-tag`` syntax::
+
+        argument ::= [ expression ]
+
+Description
+~~~~~~~~~~~
+
+The ``tal:omit-tag`` statement leaves the contents of an element in
+place while omitting the surrounding start and end tags.
+
+If the expression evaluates to a *false* value, then normal processing
+of the element continues and the tags are not omitted.  If the
+expression evaluates to a *true* value, or no expression is provided,
+the statement element is replaced with its contents.
+
+.. note:: Like Python itself, ZPT considers None, zero, empty strings,
+   empty sequences, empty dictionaries, and instances which return a
+   nonzero value from ``__len__`` or ``__nonzero__`` false; all other
+   values are true, including ``default``.
+
+Examples
+~~~~~~~~
+
+Unconditionally omitting a tag::
+
+        <div tal:omit-tag="" comment="This tag will be removed">
+          <i>...but this text will remain.</i>
+        </div>
+
+Conditionally omitting a tag::
+
+        <b tal:omit-tag="not:bold">I may be bold.</b>
+
+The above example will omit the ``b`` tag if the variable ``bold`` is false.
+
+Creating ten paragraph tags, with no enclosing tag::
+
+        <span tal:repeat="n range(10)"
+              tal:omit-tag="">
+          <p tal:content="n">1</p>
+        </span>
+
+.. _tal_repeat:
+
+``tal:repeat``
+^^^^^^^^^^^^^^
+
+Repeats an element.
+
+Syntax
+~~~~~~
+
+``tal:repeat`` syntax::
+
+        argument      ::= variable_name expression
+        variable_name ::= Name
+
+Description
+~~~~~~~~~~~
+
+The ``tal:repeat`` statement replicates a sub-tree of your document
+once for each item in a sequence. The expression should evaluate to a
+sequence. If the sequence is empty, then the statement element is
+deleted, otherwise it is repeated for each value in the sequence.  If
+the expression is ``default``, then the element is left unchanged, and
+no new variables are defined.
+
+The ``variable_name`` is used to define a local variable and a repeat
+variable. For each repetition, the local variable is set to the
+current sequence element, and the repeat variable is set to an
+iteration object.
+
+Repeat variables
+~~~~~~~~~~~~~~~~~
+
+You use repeat variables to access information about the current
+repetition (such as the repeat index).  The repeat variable has the
+same name as the local variable, but is only accessible through the
+built-in variable named ``repeat``.
+
+The following information is available from the repeat variable:
+
+==================  ==============
+ Attribute           Description
+==================  ==============
+``index``           Repetition number, starting from zero.
+``number``          Repetition number, starting from one.
+``even``            True for even-indexed repetitions (0, 2, 4, ...).
+``odd``             True for odd-indexed repetitions (1, 3, 5, ...).
+``start``           True for the starting repetition (index 0).
+``end``             True for the ending, or final, repetition.
+``first``           True for the first item in a group - see note below
+``last``            True for the last item in a group - see note below
+``length``          Length of the sequence, which will be the total number of repetitions.
+``letter``          Repetition number as a lower-case letter: "a" - "z", "aa" - "az", "ba" - "bz", ..., "za" - "zz", "aaa" - "aaz", and so forth.
+``Letter``          Upper-case version of *letter*.
+``roman``           Repetition number as a lower-case roman numeral: "i", "ii", "iii", "iv", "v", etc.
+``Roman``           Upper-case version of *roman*.
+==================  ==============
+
+You can access the contents of the repeat variable using either
+dictionary- or attribute-style access, e.g. ``repeat['item'].start``
+or ``repeat.item.start``.
+
+.. note:: For legacy compatibility, the attributes ``odd``, ``even``, ``number``, ``letter``, ``Letter``, ``roman``, and ``Roman`` are callable (returning ``self``).
+
+Note that ``first`` and ``last`` are intended for use with sorted
+sequences.  They try to divide the sequence into group of items with
+the same value.
+
+Examples
+~~~~~~~~
+
+Iterating over a sequence of strings::    
+
+        <p tal:repeat="txt ('one', 'two', 'three')">
+           <span tal:replace="txt" />
+        </p>
+
+Inserting a sequence of table rows, and using the repeat variable
+to number the rows::
+
+        <table>
+          <tr tal:repeat="item here.cart">
+              <td tal:content="repeat.item.number">1</td>
+              <td tal:content="item.description">Widget</td>
+              <td tal:content="item.price">$1.50</td>
+          </tr>
+        </table>
+
+Nested repeats::
+
+        <table border="1">
+          <tr tal:repeat="row range(10)">
+            <td tal:repeat="column range(10)">
+              <span tal:define="x repeat.row.number; 
+                                y repeat.column.number; 
+                                z x * y"
+                    tal:replace="string:$x * $y = $z">1 * 1 = 1</span>
+            </td>
+          </tr>
+        </table>
+
+Insert objects. Separates groups of objects by type by drawing a rule
+between them::
+
+        <div tal:repeat="object objects">
+          <h2 tal:condition="repeat.object.first.meta_type"
+            tal:content="object.type">Meta Type</h2>
+          <p tal:content="object.id">Object ID</p>
+          <hr tal:condition="object.last.meta_type" />
+        </div>
+
+.. note:: the objects in the above example should already be sorted by
+   type.
+
+``tal:replace``
+^^^^^^^^^^^^^^^
+
+Replaces an element.
+
+Syntax
+~~~~~~
+
+``tal:replace`` syntax::
+
+        argument ::= ['structure'] expression
+
+Description
+~~~~~~~~~~~
+
+
+The ``tal:replace`` statement replaces an element with dynamic
+content.  It replaces the statement element with either text or a
+structure (unescaped markup). The body of the statement is an
+expression with an optional type prefix. The value of the expression
+is converted into an escaped string unless you provide the 'structure' prefix. Escaping consists of converting ``&amp;`` to
+``&amp;amp;``, ``&lt;`` to ``&amp;lt;``, and ``&gt;`` to ``&amp;gt;``.
+
+.. note:: If the inserted object provides an ``__html__`` method, that method is called with the result inserted as structure. This feature is not implemented by ZPT.
+
+If the expression evaluates to ``None``, the element is simply removed.  If the value is ``default``, then the element is left unchanged.
+
+Examples
+~~~~~~~~
+
+Inserting a title::
+
+        <span tal:replace="context.title">Title</span>
+
+Inserting HTML/XML::
+
+        <div tal:replace="structure table" />
+
+.. _tales:
+
+Expressions (TALES)
+###################
+
+The *Template Attribute Language Expression Syntax* (TALES) standard
+describes expressions that supply :ref:`tal` and
+:ref:`metal` with data.  TALES is *one* possible expression
+syntax for these languages, but they are not bound to this definition.
+Similarly, TALES could be used in a context having nothing to do with
+TAL or METAL.
+
+TALES expressions are described below with any delimiter or quote
+markup from higher language layers removed.  Here is the basic
+definition of TALES syntax::
+
+      Expression  ::= [type_prefix ':'] String
+      type_prefix ::= Name
+
+Here are some simple examples::
+
+      1 + 2
+      None
+      string:Hello, ${view.user_name}
+
+The optional *type prefix* determines the semantics and syntax of the
+*expression string* that follows it.  A given implementation of TALES
+can define any number of expression types, with whatever syntax you
+like. It also determines which expression type is indicated by
+omitting the prefix.
+
+Types
+-----
+
+These are the available TALES expression types:
+
+=============  ==============
+ Prefix        Description
+=============  ==============
+``exists``     Evaluate the result inside an exception handler; if one of the exceptions ``AttributeError``, ``LookupError``, ``TypeError``, ``NameError``, or ``KeyError`` is raised during evaluation, the result is ``False``, otherwise ``True``. Note that the original result is discarded in any case.
+``import``     Import a global symbol using dotted notation.
+``load``       Load a template relative to the current template or absolute.
+``not``        Negate the expression result
+``python``     Evaluate a Python expression
+``string``     Format a string
+``structure``  Wraps the expression result as *structure*.
+=============  ==============
+
+.. note:: The default expression type is ``python``.
+
+.. warning:: The Zope reference engine defaults to a ``path``
+             expression type, which is closely tied to the Zope
+             framework. This expression is not implemented in
+             Chameleon (but it's available in a Zope framework
+             compatibility package).
+
+There's a mechanism to allow fallback to alternative expressions, if
+one should fail (raise an exception). The pipe character ('|') is used
+to separate two expressions::
+
+  <div tal:define="page request.GET['page'] | 0">
+
+This mechanism applies only to the ``python`` expression type, and by
+derivation ``string``.
+
+.. _tales_built_in_names:
+
+``python``
+^^^^^^^^^^
+
+Evaluates a Python expression.
+
+Syntax
+~~~~~~
+
+Python expression syntax::
+
+        Any valid Python language expression
+
+Description
+~~~~~~~~~~~
+
+Python expressions are executed natively within the translated
+template source code. There is no built-in security apparatus.
+
+``string``
+^^^^^^^^^^
+
+Syntax
+~~~~~~
+
+String expression syntax::
+
+        string_expression ::= ( plain_string | [ varsub ] )*
+        varsub            ::= ( '$' Variable ) | ( '${ Expression }' )
+        plain_string      ::= ( '$$' | non_dollar )*
+        non_dollar        ::= any character except '$'
+
+Description
+~~~~~~~~~~~
+
+String expressions interpret the expression string as text. If no
+expression string is supplied the resulting string is *empty*. The
+string can contain variable substitutions of the form ``$name`` or
+``${expression}``, where ``name`` is a variable name, and ``expression`` is a TALES-expression. The escaped string value of the expression is inserted into the string.
+
+.. note:: To prevent a ``$`` from being interpreted this
+   way, it must be escaped as ``$$``.
+
+Examples
+~~~~~~~~
+
+Basic string formatting::
+
+    <span tal:replace="string:$this and $that">
+      Spam and Eggs
+    </span>
+
+    <p tal:content="string:${request.form['total']}">
+      total: 12
+    </p>
+
+Including a dollar sign::
+
+    <p tal:content="string:$$$cost">
+      cost: $42.00
+    </p>
+
+.. _import-expression:
+
+``import``
+^^^^^^^^^^
+
+Imports a module global.
+
+.. _structure-expression:
+
+``structure``
+^^^^^^^^^^^^^
+
+Wraps the expression result as *structure*: The replacement text is
+inserted into the document without escaping, allowing HTML/XML markup
+to be inserted.  This can break your page if the text contains
+unanticipated markup (eg.  text submitted via a web form), which is
+the reason that it is not the default.
+
+.. _load-expression:
+
+``load``
+^^^^^^^^
+
+Loads a template instance.
+
+Syntax
+~~~~~~
+
+Load expression syntax::
+
+         Relative or absolute file path
+
+Description
+~~~~~~~~~~~
+
+The template will be loaded using the same template class as the
+calling template.
+
+Examples
+~~~~~~~~
+
+Loading a template and using it as a macro::
+
+  <div tal:define="master load: ../master.pt" metal:use-macro="master" />
+
+
+Built-in names
+--------------
+
+These are the names always available in the TALES expression namespace:
+
+- ``default`` - special value used to specify that existing text or attributes should not be replaced. See the documentation for individual TAL statements for details on how they interpret *default*.
+
+- ``repeat`` - the *repeat* variables; see :ref:`tal_repeat` for more
+  information.
+
+- ``template`` - reference to the template which was first called; this symbol is carried over when using macros.
+
+- ``macros`` - reference to the macros dictionary that corresponds to the current template.
+
+
+.. _metal:
+
+Macros (METAL)
+##############
+
+The *Macro Expansion Template Attribute Language* (METAL) standard is
+a facility for HTML/XML macro preprocessing. It can be used in
+conjunction with or independently of TAL and TALES.
+
+Macros provide a way to define a chunk of presentation in one
+template, and share it in others, so that changes to the macro are
+immediately reflected in all of the places that share it.
+Additionally, macros are always fully expanded, even in a template's
+source text, so that the template appears very similar to its final
+rendering.
+
+A single Page Template can accomodate multiple macros.
+
+Namespace
+---------
+
+The METAL namespace URI and recommended alias are currently defined
+as::
+
+        xmlns:metal="http://xml.zope.org/namespaces/metal"
+
+Just like the TAL namespace URI, this URI is not attached to a web
+page; it's just a unique identifier.  This identifier must be used in
+all templates which use METAL.
+
+Statements
+----------
+
+METAL defines a number of statements:
+
+* ``metal:define-macro`` Define a macro.
+* ``metal:use-macro`` Use a macro.
+* ``metal:extend-macro`` Extend a macro.
+* ``metal:define-slot`` Define a macro customization point.
+* ``metal:fill-slot`` Customize a macro.
+
+Although METAL does not define the syntax of expression non-terminals,
+leaving that up to the implementation, a canonical expression syntax
+for use in METAL arguments is described in TALES Specification.
+
+``define-macro``
+^^^^^^^^^^^^^^^^
+
+Defines a macro.
+
+Syntax
+~~~~~~
+
+``metal:define-macro`` syntax::
+
+        argument ::= Name
+
+Description
+~~~~~~~~~~~
+
+The ``metal:define-macro`` statement defines a macro. The macro is named
+by the statement expression, and is defined as the element and its
+sub-tree.
+
+Examples
+~~~~~~~~
+
+Simple macro definition::
+
+        <p metal:define-macro="copyright">
+          Copyright 2011, <em>Foobar</em> Inc.
+        </p>
+
+``define-slot``
+^^^^^^^^^^^^^^^
+
+Defines a macro customization point.
+
+Syntax
+~~~~~~
+
+``metal:define-slot`` syntax::
+
+        argument ::= Name
+
+Description
+~~~~~~~~~~~
+
+The ``metal:define-slot`` statement defines a macro customization
+point or *slot*. When a macro is used, its slots can be replaced, in
+order to customize the macro. Slot definitions provide default content
+for the slot. You will get the default slot contents if you decide not
+to customize the macro when using it.
+
+The ``metal:define-slot`` statement must be used inside a
+``metal:define-macro`` statement.
+
+Slot names must be unique within a macro.
+
+Examples
+~~~~~~~~
+
+Simple macro with slot::
+
+        <p metal:define-macro="hello">
+          Hello <b metal:define-slot="name">World</b>
+        </p>
+
+This example defines a macro with one slot named ``name``. When you use
+this macro you can customize the ``b`` element by filling the ``name``
+slot.
+
+``fill-slot``
+^^^^^^^^^^^^^
+
+Customize a macro.
+
+Syntax
+~~~~~~
+
+``metal:fill-slot`` syntax::
+
+        argument ::= Name
+
+Description
+~~~~~~~~~~~
+
+The ``metal:fill-slot`` statement customizes a macro by replacing a
+*slot* in the macro with the statement element (and its content).
+
+The ``metal:fill-slot`` statement must be used inside a
+``metal:use-macro`` statement.
+
+Slot names must be unique within a macro.
+
+If the named slot does not exist within the macro, the slot
+contents will be silently dropped.
+
+Examples
+~~~~~~~~
+
+Given this macro::
+
+        <p metal:define-macro="hello">
+          Hello <b metal:define-slot="name">World</b>
+        </p>
+
+You can fill the ``name`` slot like so::
+
+        <p metal:use-macro="container['master.html'].macros.hello">
+          Hello <b metal:fill-slot="name">Kevin Bacon</b>
+        </p>
+
+``use-macro``
+^^^^^^^^^^^^^
+
+Use a macro.
+
+Syntax
+~~~~~~
+
+``metal:use-macro`` syntax::
+
+        argument ::= expression
+
+Description
+~~~~~~~~~~~
+
+The ``metal:use-macro`` statement replaces the statement element with
+a macro. The statement expression describes a macro definition.
+
+.. note:: In Chameleon the expression may point to a template instance; in this case it will be rendered in its entirety.
+
+``extend-macro``
+^^^^^^^^^^^^^^^^
+
+Extends a macro.
+
+Syntax
+~~~~~~
+
+``metal:extend-macro`` syntax::
+
+        argument ::= expression
+
+Description
+~~~~~~~~~~~
+
+To extend an existing macro, choose a name for the macro and add a
+define-macro attribute to a document element with the name as the
+argument. Add an extend-macro attribute to the document element with
+an expression referencing the base macro as the argument. The
+extend-macro must be used in conjunction with define-macro, and must
+not be used with use-macro. The element's subtree is the macro
+body.
+
+Examples
+~~~~~~~~
+
+::
+
+        <div metal:define-macro="page-header"
+             metal:extend-macro="standard_macros['page-header']">
+          <div metal:fill-slot="breadcrumbs">
+            You are here:
+            <div metal:define-slot="breadcrumbs"/>
+          </div>
+        </div>
+
+
+.. _i18n:
+
+Translation (I18N)
+##################
+
+Translation of template contents and attributes is supported via the
+``i18n`` namespace and message objects.
+
+Messages
+--------
+
+The translation machinery defines a message as *any object* which is
+not a string or a number and which does not provide an ``__html__``
+method.
+
+When any such object is inserted into the template, the translate
+function is invoked first to see if it needs translation. The result
+is always coerced to a native string before it's inserted into the
+template.
+
+Translation function
+--------------------
+
+The simplest way to hook into the translation machinery is to provide
+a translation function to the template constructor or at
+render-time. In either case it should be passed as the keyword
+argument ``translate``.
+
+The function has the following signature:
+
+.. code-block:: python
+
+   def translate(msgid, domain=None, mapping=None, context=None, target_language=None, default=None):
+       ...
+
+The result should be a string or ``None``. If another type of object
+is returned, it's automatically coerced into a string.
+
+If `zope.i18n <http://pypi.python.org/pypi/zope.i18n>`_ is available,
+the translation machinery defaults to using its translation
+function. Note that this function requires messages to conform to the
+message class from `zope.i18nmessageid
+<http://pypi.python.org/pypi/zope.i18nmessageid>`_; specifically,
+messages must have attributes ``domain``, ``mapping`` and
+``default``. Example use:
+
+.. code-block:: python
+
+   from zope.i18nmessageid import MessageFactory
+   _ = MessageFactory("food")
+
+   apple = _(u"Apple")
+
+There's currently no further support for other translation frameworks.
+
+Using Zope's translation framework
+-----------------------------------
+
+The translation function from ``zope.i18n`` relies on *translation
+domains* to provide translations.
+
+These are components that are registered for some translation domain
+identifier and which implement a ``translate`` method that translates
+messages for that domain.
+
+.. note:: To register translation domain components, the Zope Component Architecture must be used (see `zope.component <http://pypi.python.org/pypi/zope.component>`_).
+
+The easiest way to configure translation domains is to use the the
+``registerTranslations`` ZCML-directive; this requires the use of the
+`zope.configuration <http://pypi.python.org/pypi/zope.configuration>`_
+package. This will set up translation domains and gettext catalogs
+automatically:
+
+.. code-block:: xml
+
+  <configure xmlns="http://namespaces.zope.org/zope"
+             xmlns:i18n="http://xml.zope.org/namespaces/i18n">
+
+     <i18n:registerTranslations directory="locales" />
+
+  </configure>
+
+The ``./locales`` directory must follow a particular directory
+structure:
+
+.. code-block:: bash
+
+  ./locales/en/LC_MESSAGES
+  ./locales/de/LC_MESSAGES
+  ...
+
+In each of the ``LC_MESSAGES`` directories, one `GNU gettext
+<http://en.wikipedia.org/wiki/GNU_gettext>`_ file in the ``.po``
+format must be present per translation domain:
+
+.. code-block:: po
+
+  # ./locales/de/LC_MESSAGES/food.po
+
+  msgid ""
+  msgstr ""
+  "MIME-Version: 1.0\n"
+  "Content-Type: text/plain; charset=UTF-8\n"
+  "Content-Transfer-Encoding: 8bit\n"
+
+  msgid "Apple"
+  msgstr "Apfel"
+
+It may be necessary to compile the message catalog using the
+``msgfmt`` utility. This will produce a ``.mo`` file.
+
+Translation domains without gettext
+-----------------------------------
+
+The following example demonstrates how to manually set up and
+configure a translation domain for which messages are provided
+directly::
+
+  from zope import component
+  from zope.i18n.simpletranslationdomain import SimpleTranslationDomain
+
+  food = SimpleTranslationDomain("food", {
+      ('de', u'Apple'): u'Apfel',
+      })
+
+  component.provideUtility(food, food.domain)
+
+An example of a custom translation domain class::
+
+  from zope import interface
+
+  class TranslationDomain(object):
+       interface.implements(ITranslationDomain)
+
+       def translate(self, msgid, mapping=None, context=None,
+                    target_language=None, default=None):
+
+           ...
+
+  component.provideUtility(TranslationDomain(), name="custom")
+
+This approach can be used to integrate other translation catalog
+implementations.
+
+.. highlight:: xml
+
+Namespace
+---------
+
+The ``i18n`` namespace URI and recommended prefix are currently
+defined as::
+
+  xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+
+This is not a URL, but merely a unique identifier.  Do not expect a
+browser to resolve it successfully.
+
+Statements
+----------
+
+The allowable ``i18n`` statements are:
+
+- ``i18n:translate``
+- ``i18n:domain``
+- ``i18n:source``
+- ``i18n:target``
+- ``i18n:name``
+- ``i18n:attributes``
+- ``i18n:data``
+
+``i18n:translate``
+^^^^^^^^^^^^^^^^^^
+
+This attribute is used to mark units of text for translation.  If this
+attribute is specified with an empty string as the value, the message
+ID is computed from the content of the element bearing this attribute.
+Otherwise, the value of the element gives the message ID.
+
+``i18n:domain``
+^^^^^^^^^^^^^^^
+
+The ``i18n:domain`` attribute is used to specify the domain to be used
+to get the translation.  If not specified, the translation services
+will use a default domain.  The value of the attribute is used
+directly; it is not a TALES expression.
+
+``i18n:source``
+^^^^^^^^^^^^^^^
+
+The ``i18n:source`` attribute specifies the language of the text to be
+translated.  The default is ``nothing``, which means we don't provide
+this information to the translation services.
+
+
+``i18n:target``
+^^^^^^^^^^^^^^^
+
+The ``i18n:target`` attribute specifies the language of the
+translation we want to get.  If the value is ``default``, the language
+negotiation services will be used to choose the destination language.
+If the value is ``nothing``, no translation will be performed; this
+can be used to suppress translation within a larger translated unit.
+Any other value must be a language code.
+
+The attribute value is a TALES expression; the result of evaluating
+the expression is the language code or one of the reserved values.
+
+.. note:: ``i18n:target`` is primarily used for hints to text
+   extraction tools and translation teams.  If you had some text that
+   should only be translated to e.g. German, then it probably
+   shouldn't be wrapped in an ``i18n:translate`` span.
+
+``i18n:name``
+^^^^^^^^^^^^^
+
+Name the content of the current element for use in interpolation
+within translated content.  This allows a replaceable component in
+content to be re-ordered by translation.  For example::
+
+    <span i18n:translate=''>
+      <span tal:replace='context.name' i18n:name='name' /> was born in
+      <span tal:replace='context.country_of_birth' i18n:name='country' />.
+    </span>
+
+would cause this text to be passed to the translation service::
+
+    "${name} was born in ${country}."
+
+``i18n:attributes``
+^^^^^^^^^^^^^^^^^^^
+
+This attribute will allow us to translate attributes of HTML tags,
+such as the ``alt`` attribute in the ``img`` tag. The
+``i18n:attributes`` attribute specifies a list of attributes to be
+translated with optional message IDs for each; if multiple attribute
+names are given, they must be separated by semicolons.  Message IDs
+used in this context must not include whitespace.
+
+Note that the value of the particular attributes come either from the
+HTML attribute value itself or from the data inserted by
+``tal:attributes``.
+
+If an attibute is to be both computed using ``tal:attributes`` and
+translated, the translation service is passed the result of the TALES
+expression for that attribute.
+
+An example::
+
+    <img src="http://foo.com/logo" alt="Visit us"
+         tal:attributes="alt context.greeting"
+         i18n:attributes="alt"
+         >
+
+In this example, we let ``tal:attributes`` set the value of the ``alt``
+attribute to the text "Stop by for a visit!".  This text will be
+passed to the translation service, which uses the result of language
+negotiation to translate "Stop by for a visit!" into the requested
+language.  The example text in the template, "Visit us", will simply
+be discarded.
+
+Another example, with explicit message IDs::
+
+    <img src="../icons/uparrow.png" alt="Up"
+         i18n:attributes="src up-arrow-icon; alt up-arrow-alttext"
+         >
+
+Here, the message ID ``up-arrow-icon`` will be used to generate the
+link to an icon image file, and the message ID 'up-arrow-alttext' will
+be used for the "alt" text.
+
+``i18n:data``
+^^^^^^^^^^^^^
+
+Since TAL always returns strings, we need a way in ZPT to translate
+objects, one of the most obvious cases being ``datetime`` objects. The
+``data`` attribute will allow us to specify such an object, and
+``i18n:translate`` will provide us with a legal format string for that
+object.  If ``data`` is used, ``i18n:translate`` must be used to give
+an explicit message ID, rather than relying on a message ID computed
+from the content.
+
+Relation with TAL processing
+----------------------------
+
+The attributes defined in the ``i18n`` namespace modify the behavior
+of the TAL interpreter for the ``tal:attributes``, ``tal:content``,
+``tal:repeat``, and ``tal:replace`` attributes, but otherwise do not
+affect TAL processing.
+
+Since these attributes only affect TAL processing by causing
+translations to occur at specific times, using these with a TAL
+processor which does not support the ``i18n`` namespace degrades well;
+the structural expectations for a template which uses the ``i18n``
+support is no different from those for a page which does not.  The
+only difference is that translations will not be performed in a legacy
+processor.
+
+Relation with METAL processing
+-------------------------------
+
+When using translation with METAL macros, the internationalization
+context is considered part of the specific documents that page
+components are retrieved from rather than part of the combined page.
+This makes the internationalization context lexical rather than
+dynamic, making it easier for a site builder to understand the
+behavior of each element with respect to internationalization.
+
+Let's look at an example to see what this means::
+
+    <html i18n:translate='' i18n:domain='EventsCalendar'
+          metal:use-macro="container['master.html'].macros.thismonth">
+
+      <div metal:fill-slot='additional-notes'>
+        <ol tal:condition="context.notes">
+          <li tal:repeat="note context.notes">
+             <tal:block tal:omit-tag=""
+                        tal:condition="note.heading">
+               <strong tal:content="note.heading">
+                 Note heading goes here
+               </strong>
+               <br />
+             </tal:block>
+             <span tal:replace="note/description">
+               Some longer explanation for the note goes here.
+             </span>
+          </li>
+        </ol>
+      </div>
+
+    </html>
+
+And the macro source::
+
+    <html i18n:domain='CalendarService'>
+      <div tal:replace='python:DateTime().Month()'
+           i18n:translate=''>January</div>
+
+      <!-- really hairy TAL code here ;-) -->
+
+      <div define-slot="additional-notes">
+        Place for the application to add additional notes if desired.
+      </div>
+
+    </html>
+
+Note that the macro is using a different domain than the application
+(which it should be).  With lexical scoping, no special markup needs
+to be applied to cause the slot-filler in the application to be part
+of the same domain as the rest of the application's page components.
+If dynamic scoping were used, the internationalization context would
+need to be re-established in the slot-filler.
+
+
+Extracting translatable message
+-------------------------------
+
+Translators use `PO files
+<http://www.gnu.org/software/hello/manual/gettext/PO-Files.html>`_
+when translating messages. To create and update PO files you need to
+do two things: *extract* all messages from python and templates files
+and store them in a ``.pot`` file, and for each language *update* its
+``.po`` file.  Chameleon facilitates this by providing extractors for
+`Babel <http://babel.edgewall.org/>`_.  To use this you need modify
+``setup.py``. For example:
+
+.. code-block:: python
+
+   from setuptools import setup
+
+   setup(name="mypackage",
+         install_requires = [
+               "Babel",
+               ],
+         message_extractors = { "src": [
+               ("**.py",   "chameleon_python", None ),
+               ("**.pt",   "chameleon_xml", None ),
+               ]},
+         )
+
+This tells Babel to scan the ``src`` directory while using the
+``chameleon_python`` extractor for all ``.py`` files and the
+``chameleon_xml`` extractor for all ``.pt`` files.
+
+You can now use Babel to manage your PO files:
+
+.. code-block:: bash
+
+   python setup.py extract_messages --output-file=i18n/mydomain.pot
+   python setup.py update_catalog \
+             -l nl \
+             -i i18n/mydomain.pot \
+             -o i18n/nl/LC_MESSAGES/mydomain.po
+   python setup.py compile_catalog \
+             --directory i18n --locale nl
+
+You can also configure default options in a ``setup.cfg`` file. For example::
+
+   [compile_catalog]
+   domain = mydomain
+   directory = i18n
+   
+   [extract_messages]
+   copyright_holder = Acme Inc.
+   output_file = i18n/mydomain.pot
+   charset = UTF-8
+
+   [init_catalog]
+   domain = mydomain
+   input_file = i18n/mydomain.pot
+   output_dir = i18n
+
+   [update_catalog]
+   domain = mydomain
+   input_file = i18n/mydomain.pot
+   output_dir = i18n
+   previous = true
+
+You can now use the Babel commands directly::
+
+   python setup.py extract_messages
+   python setup.py update_catalog
+   python setup.py compile_catalog
+
+
+${...} operator
+###############
+
+The ``${...}`` notation is short-hand for text insertion. The
+Python-expression inside the braces is evaluated and the result
+included in the output (all inserted text is escaped by default):
+
+.. code-block:: html
+
+  <div id="section-${index + 1}">
+    ${content}
+  </div>
+
+To escape this behavior, prefix the notation with a backslash
+character: ``\${...}``.
+
+Note that if an object implements the ``__html__`` method, the result
+of this method will be inserted as-is (without XML escaping).
+
+Markup comments
+###############
+
+You can apply the "!" and "?" modifiers to change how comments are
+processed:
+
+Drop
+
+  ``<!--! This comment will be dropped from output -->``
+
+Verbatim
+
+  ``<!--? This comment will be included verbatim -->``
+
+  That is, evaluation of ``${...}`` expressions is disabled if the
+  comment opens with the "?" character.
+
+
+.. _new-features:
+
+Language extensions
+###################
+
+The page template language as implemented in the Chameleon library
+comes with a number of new features. Some take inspiration from
+`Genshi <http://genshi.edgewall.org/>`_.
+
+    *New expression types*
+
+       The :ref:`structure <structure-expression>` expression wraps an
+       expression result as *structure*::
+
+         <div>${structure: body.text}</div>
+
+       The :ref:`import <import-expression>` expression imports module globals::
+
+         <div tal:define="compile import: re.compile">
+           ...
+         </div>
+
+       This :ref:`load <load-expression>` expression loads templates
+       relative to the current template::
+
+         <div tal:define="compile load: main.pt">
+           ...
+         </div>
+
+    *Tuple unpacking*
+
+       The ``tal:define`` and ``tal:repeat`` statements supports tuple
+       unpacking::
+
+          tal:define="(a, b, c) [1, 2, 3]"
+
+       Extended `iterable unpacking
+       <http://www.python.org/dev/peps/pep-3132/>`_ using the asterisk
+       character is not currently supported (even for versions of
+       Python that support it natively).
+
+    *Dictionary lookup as fallback after attribute error*
+
+       If attribute lookup (using the ``obj.<name>`` syntax) raises an
+       ``AttributeError`` exception, a secondary lookup is attempted
+       using dictionary lookup --- ``obj['<name>']``.
+
+       Behind the scenes, this is done by rewriting all
+       attribute-lookups to a custom lookup call:
+
+       .. code-block:: python
+
+            def lookup_attr(obj, key):
+                try:
+                    return getattr(obj, key)
+                except AttributeError as exc:
+                    try:
+                        get = obj.__getitem__
+                    except AttributeError:
+                        raise exc
+                    try:
+                        return get(key)
+                    except KeyError:
+                        raise exc
+
+    *Inline string substitution*
+
+       In element attributes and in the text or tail of an element,
+       string expression interpolation is available using the
+       ``${...}`` syntax::
+
+          <span class="content-${item_type}">
+             ${title or item_id}
+          </span>
+
+    *Literal content*
+
+       While the ``tal:content`` and ``tal:repeat`` attributes both
+       support the ``structure`` keyword which inserts the content as
+       a literal (without XML-escape), an object may also provide an
+       ``__html__`` method to the same effect.
+
+       The result of the method will be inserted as *structure*.
+
+       This is particularly useful for content which is substituted
+       using the expression operator: ``"${...}"`` since the
+       ``structure`` keyword is not allowed here.
+
+    *Switches*
+
+       Two new attributes have been added: ``tal:switch`` and
+       ``tal:case``. A case attribute works like a condition and only
+       allows content if the value matches that of the nearest parent
+       switch value.
+
+
+Incompatibilities and differences
+#################################
+
+There are a number of incompatibilities and differences between the
+Chameleon language implementation and the Zope reference
+implementation (ZPT):
+
+    *Default expression*
+
+       The default expression type is Python.
+
+    *Template arguments*
+
+      Arguments passed by keyword to the render- or call method are
+      inserted directly into the template execution namespace. This is
+      different from ZPT where these are only available through the
+      ``options`` dictionary.
+
+      Zope::
+
+        <div tal:content="options/title" />
+
+      Chameleon::
+
+        <div tal:content="title" />
+
+    *Special symbols*
+
+      The ``CONTEXTS`` symbol is not available.
+
+The `z3c.pt <http://pypi.python.org/pypi/z3c.pt>`_ package works as a
+compatibility layer. The template classes in this package provide a
+implementation which is fully compatible with ZPT.
+
+Notes
+#####
+
+.. [1] This has been changed in 2.x. Previously, it was up to the
+       expression engine to parse the expression values including any
+       semicolons and since for instance Python-expressions can never
+       end in a semicolon, it was possible to clearly distinguish
+       between the different uses of the symbol, e.g.
+
+       ::
+
+         tal:define="text 'Hello world; goodbye world'"
+
+       The semicolon appearing in the definition above is part of the
+       Python-expression simply because it makes the expression
+       valid. Meanwhile:
+
+       ::
+
+         tal:define="text1 'Hello world'; text2 'goodbye world'"
+
+       The semicolon here must denote a second variable definition
+       because there is no valid Python-expression that includes it.
+
+       While this behavior works well in practice, it is incompatible
+       with the reference specification, and also blurs the interface
+       between the compiler and the expression engine. In 2.x we
+       therefore have to escape the semicolon by doubling it (as
+       defined by the specification):
+
+       ::
+
+         tal:define="text 'Hello world;; goodbye world'"
+
diff --git a/lib/chameleon/setup.py b/lib/chameleon/setup.py
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/setup.py
@@ -0,0 +1,90 @@
+__version__ = '2.5.0'
+
+import os
+import sys
+
+try:
+    from distribute_setup import use_setuptools
+    use_setuptools()
+except: # doesn't work under tox/pip
+    pass
+
+from setuptools import setup, find_packages
+from setuptools.command.test import test
+
+here = os.path.abspath(os.path.dirname(__file__))
+try:
+    README = open(os.path.join(here, 'README.rst')).read()
+    CHANGES = open(os.path.join(here, 'CHANGES.rst')).read()
+except: # doesn't work under tox/pip
+    README = ''
+    CHANGES = ''
+
+install_requires = []
+
+version = sys.version_info[:3]
+if version < (2, 7, 0):
+    install_requires.append("ordereddict")
+    install_requires.append("unittest2")
+
+
+class Benchmark(test):
+    description = "Run benchmarks"
+    user_options = []
+    test_suite = None
+
+    def initialize_options(self):
+        """init options"""
+        pass
+
+    def finalize_options(self):
+        """finalize options"""
+
+        self.distribution.tests_require = [
+            'zope.pagetemplate',
+            'zope.component',
+            'zope.i18n',
+            'zope.testing']
+
+    def run(self):
+        test.run(self)
+        self.with_project_on_sys_path(self.run_benchmark)
+
+    def run_benchmark(self):
+        from chameleon import benchmark
+        print("running benchmark...")
+
+        benchmark.start()
+
+setup(
+    name="Chameleon",
+    version=__version__,
+    description="Fast HTML/XML Template Compiler.",
+    long_description="\n\n".join((README, CHANGES)),
+    classifiers=[
+       "Development Status :: 4 - Beta",
+       "Intended Audience :: Developers",
+       "Programming Language :: Python",
+       "Programming Language :: Python :: 2",
+       "Programming Language :: Python :: 3",
+       "Programming Language :: Python :: 2.5",
+       "Programming Language :: Python :: 2.6",
+       "Programming Language :: Python :: 2.7",
+       "Programming Language :: Python :: 3.1",
+       "Programming Language :: Python :: 3.2",
+      ],
+    author="Malthe Borch",
+    author_email="mborch at gmail.com",
+    url="http://www.pagetemplates.org/",
+    license='BSD-like (http://repoze.org/license.html)',
+    packages=find_packages('src'),
+    package_dir = {'': 'src'},
+    include_package_data=True,
+    install_requires=install_requires,
+    zip_safe=False,
+    test_suite="chameleon.tests",
+    cmdclass={
+        'benchmark': Benchmark,
+        }
+    )
+
diff --git a/lib/chameleon/src/chameleon/__init__.py b/lib/chameleon/src/chameleon/__init__.py
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/__init__.py
@@ -0,0 +1,5 @@
+from .zpt.template import PageTemplate
+from .zpt.template import PageTemplateFile
+from .zpt.template import PageTextTemplate
+from .zpt.template import PageTextTemplateFile
+from .zpt.loader import TemplateLoader as PageTemplateLoader
diff --git a/lib/chameleon/src/chameleon/ast24.py b/lib/chameleon/src/chameleon/ast24.py
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/ast24.py
@@ -0,0 +1,135 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 by Armin Ronacher.
+# License: Python License.
+#
+
+import _ast
+
+from _ast import *
+
+
+def fix_missing_locations(node):
+    """
+    When you compile a node tree with compile(), the compiler expects lineno and
+    col_offset attributes for every node that supports them.  This is rather
+    tedious to fill in for generated nodes, so this helper adds these attributes
+    recursively where not already set, by setting them to the values of the
+    parent node.  It works recursively starting at *node*.
+    """
+    def _fix(node, lineno, col_offset):
+        if 'lineno' in node._attributes:
+            if not hasattr(node, 'lineno'):
+                node.lineno = lineno
+            else:
+                lineno = node.lineno
+        if 'col_offset' in node._attributes:
+            if not hasattr(node, 'col_offset'):
+                node.col_offset = col_offset
+            else:
+                col_offset = node.col_offset
+        for child in iter_child_nodes(node):
+            _fix(child, lineno, col_offset)
+    _fix(node, 1, 0)
+    return node
+
+
+def iter_child_nodes(node):
+    """
+    Yield all direct child nodes of *node*, that is, all fields that are nodes
+    and all items of fields that are lists of nodes.
+    """
+    for name, field in iter_fields(node):
+        if isinstance(field, (AST, _ast.AST)):
+            yield field
+        elif isinstance(field, list):
+            for item in field:
+                if isinstance(item, (AST, _ast.AST)):
+                    yield item
+
+
+def iter_fields(node):
+    """
+    Yield a tuple of ``(fieldname, value)`` for each field in ``node._fields``
+    that is present on *node*.
+    """
+
+    for field in node._fields or ():
+        try:
+            yield field, getattr(node, field)
+        except AttributeError:
+            pass
+
+
+def walk(node):
+    """
+    Recursively yield all child nodes of *node*, in no specified order.  This is
+    useful if you only want to modify nodes in place and don't care about the
+    context.
+    """
+    from collections import deque
+    todo = deque([node])
+    while todo:
+        node = todo.popleft()
+        todo.extend(iter_child_nodes(node))
+        yield node
+
+
+class NodeVisitor(object):
+    """
+    A node visitor base class that walks the abstract syntax tree and calls a
+    visitor function for every node found.  This function may return a value
+    which is forwarded by the `visit` method.
+
+    This class is meant to be subclassed, with the subclass adding visitor
+    methods.
+
+    Per default the visitor functions for the nodes are ``'visit_'`` +
+    class name of the node.  So a `TryFinally` node visit function would
+    be `visit_TryFinally`.  This behavior can be changed by overriding
+    the `visit` method.  If no visitor function exists for a node
+    (return value `None`) the `generic_visit` visitor is used instead.
+
+    Don't use the `NodeVisitor` if you want to apply changes to nodes during
+    traversing.  For this a special visitor exists (`NodeTransformer`) that
+    allows modifications.
+    """
+
+    def visit(self, node):
+        """Visit a node."""
+        method = 'visit_' + node.__class__.__name__
+        visitor = getattr(self, method, self.generic_visit)
+        return visitor(node)
+
+    def generic_visit(self, node):
+        """Called if no explicit visitor function exists for a node."""
+        for field, value in iter_fields(node):
+            if isinstance(value, list):
+                for item in value:
+                    if isinstance(item, (AST, _ast.AST)):
+                        self.visit(item)
+            elif isinstance(value, (AST, _ast.AST)):
+                self.visit(value)
+
+
+class AST(object):
+    _fields = ()
+    _attributes = 'lineno', 'col_offset'
+
+    def __init__(self, *args, **kwargs):
+        self.__dict__.update(kwargs)
+        self._fields = self._fields or ()
+        for name, value in zip(self._fields, args):
+            setattr(self, name, value)
+
+
+for name, cls in _ast.__dict__.items():
+    if isinstance(cls, type) and issubclass(cls, _ast.AST):
+        try:
+            cls.__bases__ = (AST, ) + cls.__bases__
+        except TypeError:
+            pass
+
+
+class ExceptHandler(AST):
+    _fields = "type", "name", "body"
diff --git a/lib/chameleon/src/chameleon/astutil.py b/lib/chameleon/src/chameleon/astutil.py
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/astutil.py
@@ -0,0 +1,884 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://genshi.edgewall.org/wiki/License.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://genshi.edgewall.org/log/.
+
+"""Support classes for generating code from abstract syntax trees."""
+
+try:
+    import ast
+except ImportError:
+    from chameleon import ast24 as ast
+
+import sys
+import logging
+import weakref
+
+node_annotations = weakref.WeakKeyDictionary()
+
+try:
+    node_annotations[ast.Name()] = None
+except TypeError:
+    logging.debug(
+        "Unable to create weak references to AST nodes. " \
+        "A lock will be used around compilation loop."
+        )
+
+    node_annotations = {}
+
+__docformat__ = 'restructuredtext en'
+
+
+def annotated(value):
+    node = load("annotation")
+    node_annotations[node] = value
+    return node
+
+
+def parse(source, mode='eval'):
+    return compile(source, '', mode, ast.PyCF_ONLY_AST)
+
+
+def load(name):
+    return ast.Name(id=name, ctx=ast.Load())
+
+
+def store(name):
+    return ast.Name(id=name, ctx=ast.Store())
+
+
+def param(name):
+    return ast.Name(id=name, ctx=ast.Param())
+
+
+def delete(name):
+    return ast.Name(id=name, ctx=ast.Del())
+
+
+def subscript(name, value, ctx):
+    return ast.Subscript(
+        value=value,
+        slice=ast.Index(value=ast.Str(s=name)),
+        ctx=ctx,
+        )
+
+
+def walk_names(target, mode):
+    for node in ast.walk(target):
+        if isinstance(node, ast.Name) and \
+               isinstance(node.ctx, mode):
+            yield node.id
+
+
+def copy(source, target):
+    target.__class__ = source.__class__
+    target.__dict__ = source.__dict__
+
+
+def swap(root, replacement, name):
+    for node in ast.walk(root):
+        if (isinstance(node, ast.Name) and
+            isinstance(node.ctx, ast.Load) and
+            node.id == name):
+            assert hasattr(replacement, '_fields')
+            node_annotations.setdefault(node, replacement)
+
+
+def marker(name):
+    return ast.Str(s="__%s" % name)
+
+
+class Node(object):
+    """AST baseclass that gives us a convenient initialization
+    method. We explicitly declare and use the ``_fields`` attribute."""
+
+    _fields = ()
+
+    def __init__(self, *args, **kwargs):
+        assert isinstance(self._fields, tuple)
+        self.__dict__.update(kwargs)
+        for name, value in zip(self._fields, args):
+            setattr(self, name, value)
+
+    def __repr__(self):
+        """Poor man's single-line pretty printer."""
+
+        name = type(self).__name__
+        return '<%s%s at %x>' % (
+            name,
+            "".join(" %s=%r" % (name, getattr(self, name, "\"?\""))
+                        for name in self._fields),
+            id(self)
+            )
+
+
+class Builtin(Node):
+    """Represents a Python builtin.
+
+    Used when a builtin is used internally by the compiler, to avoid
+    clashing with a user assignment (e.g. ``help`` is a builtin, but
+    also commonly assigned in templates).
+    """
+
+    _fields = "id", "ctx"
+
+    ctx = ast.Load()
+
+
+class Symbol(Node):
+    """Represents an importable symbol."""
+
+    _fields = "value",
+
+
+class Static(Node):
+    """Represents a static value."""
+
+    _fields = "value", "name"
+
+    name = None
+
+
+class Comment(Node):
+    _fields = "text", "space", "stmt"
+
+    stmt = None
+    space = ""
+
+
+class ASTCodeGenerator(object):
+    """General purpose base class for AST transformations.
+
+    Every visitor method can be overridden to return an AST node that has been
+    altered or replaced in some way.
+    """
+
+    def __init__(self, tree):
+        self.lines_info = []
+        self.line_info = []
+        self.lines = []
+        self.line = ""
+        self.last = None
+        self.indent = 0
+        self.blame_stack = []
+        self.visit(tree)
+
+        if self.line.strip():
+            self._new_line()
+
+        self.line = None
+        self.line_info = None
+
+        # strip trivial lines
+        self.code = "\n".join(
+            line.strip() and line or ""
+            for line in self.lines
+            )
+
+    def _change_indent(self, delta):
+        self.indent += delta
+
+    def _new_line(self):
+        if self.line is not None:
+            self.lines.append(self.line)
+            self.lines_info.append(self.line_info)
+        self.line = ' ' * 4 * self.indent
+        if len(self.blame_stack) == 0:
+            self.line_info = []
+            self.last = None
+        else:
+            self.line_info = [(0, self.blame_stack[-1],)]
+            self.last = self.blame_stack[-1]
+
+    def _write(self, s):
+        if len(s) == 0:
+            return
+        if len(self.blame_stack) == 0:
+            if self.last is not None:
+                self.last = None
+                self.line_info.append((len(self.line), self.last))
+        else:
+            if self.last != self.blame_stack[-1]:
+                self.last = self.blame_stack[-1]
+                self.line_info.append((len(self.line), self.last))
+        self.line += s
+
+    def flush(self):
+        if self.line:
+            self._new_line()
+
+    def visit(self, node):
+        if node is None:
+            return None
+        if type(node) is tuple:
+            return tuple([self.visit(n) for n in node])
+        try:
+            self.blame_stack.append((node.lineno, node.col_offset,))
+            info = True
+        except AttributeError:
+            info = False
+        visitor = getattr(self, 'visit_%s' % node.__class__.__name__, None)
+        if visitor is None:
+            raise Exception('No handler for ``%s`` (%s).' % (
+                node.__class__.__name__, repr(node)))
+        ret = visitor(node)
+        if info:
+            self.blame_stack.pop()
+        return ret
+
+    def visit_Module(self, node):
+        for n in node.body:
+            self.visit(n)
+    visit_Interactive = visit_Module
+    visit_Suite = visit_Module
+
+    def visit_Expression(self, node):
+        return self.visit(node.body)
+
+    # arguments = (expr* args, identifier? vararg,
+    #              identifier? kwarg, expr* defaults)
+    def visit_arguments(self, node):
+        first = True
+        no_default_count = len(node.args) - len(node.defaults)
+        for i, arg in enumerate(node.args):
+            if not first:
+                self._write(', ')
+            else:
+                first = False
+            self.visit(arg)
+            if i >= no_default_count:
+                self._write('=')
+                self.visit(node.defaults[i - no_default_count])
+        if getattr(node, 'vararg', None):
+            if not first:
+                self._write(', ')
+            else:
+                first = False
+            self._write('*' + node.vararg)
+        if getattr(node, 'kwarg', None):
+            if not first:
+                self._write(', ')
+            else:
+                first = False
+            self._write('**' + node.kwarg)
+
+    # FunctionDef(identifier name, arguments args,
+    #                           stmt* body, expr* decorators)
+    def visit_FunctionDef(self, node):
+        self._new_line()
+        for decorator in getattr(node, 'decorator_list', ()):
+            self._new_line()
+            self._write('@')
+            self.visit(decorator)
+        self._new_line()
+        self._write('def ' + node.name + '(')
+        self.visit(node.args)
+        self._write('):')
+        self._change_indent(1)
+        for statement in node.body:
+            self.visit(statement)
+        self._change_indent(-1)
+
+    # ClassDef(identifier name, expr* bases, stmt* body)
+    def visit_ClassDef(self, node):
+        self._new_line()
+        self._write('class ' + node.name)
+        if node.bases:
+            self._write('(')
+            self.visit(node.bases[0])
+            for base in node.bases[1:]:
+                self._write(', ')
+                self.visit(base)
+            self._write(')')
+        self._write(':')
+        self._change_indent(1)
+        for statement in node.body:
+            self.visit(statement)
+        self._change_indent(-1)
+
+    # Return(expr? value)
+    def visit_Return(self, node):
+        self._new_line()
+        self._write('return')
+        if getattr(node, 'value', None):
+            self._write(' ')
+            self.visit(node.value)
+
+    # Delete(expr* targets)
+    def visit_Delete(self, node):
+        self._new_line()
+        self._write('del ')
+        self.visit(node.targets[0])
+        for target in node.targets[1:]:
+            self._write(', ')
+            self.visit(target)
+
+    # Assign(expr* targets, expr value)
+    def visit_Assign(self, node):
+        self._new_line()
+        for target in node.targets:
+            self.visit(target)
+            self._write(' = ')
+        self.visit(node.value)
+
+    # AugAssign(expr target, operator op, expr value)
+    def visit_AugAssign(self, node):
+        self._new_line()
+        self.visit(node.target)
+        self._write(' ' + self.binary_operators[node.op.__class__] + '= ')
+        self.visit(node.value)
+
+    # Print(expr? dest, expr* values, bool nl)
+    def visit_Print(self, node):
+        self._new_line()
+        self._write('print')
+        if getattr(node, 'dest', None):
+            self._write(' >> ')
+            self.visit(node.dest)
+            if getattr(node, 'values', None):
+                self._write(', ')
+        else:
+            self._write(' ')
+        if getattr(node, 'values', None):
+            self.visit(node.values[0])
+            for value in node.values[1:]:
+                self._write(', ')
+                self.visit(value)
+        if not node.nl:
+            self._write(',')
+
+    # For(expr target, expr iter, stmt* body, stmt* orelse)
+    def visit_For(self, node):
+        self._new_line()
+        self._write('for ')
+        self.visit(node.target)
+        self._write(' in ')
+        self.visit(node.iter)
+        self._write(':')
+        self._change_indent(1)
+        for statement in node.body:
+            self.visit(statement)
+        self._change_indent(-1)
+        if getattr(node, 'orelse', None):
+            self._new_line()
+            self._write('else:')
+            self._change_indent(1)
+            for statement in node.orelse:
+                self.visit(statement)
+            self._change_indent(-1)
+
+    # While(expr test, stmt* body, stmt* orelse)
+    def visit_While(self, node):
+        self._new_line()
+        self._write('while ')
+        self.visit(node.test)
+        self._write(':')
+        self._change_indent(1)
+        for statement in node.body:
+            self.visit(statement)
+        self._change_indent(-1)
+        if getattr(node, 'orelse', None):
+            self._new_line()
+            self._write('else:')
+            self._change_indent(1)
+            for statement in node.orelse:
+                self.visit(statement)
+            self._change_indent(-1)
+
+    # If(expr test, stmt* body, stmt* orelse)
+    def visit_If(self, node):
+        self._new_line()
+        self._write('if ')
+        self.visit(node.test)
+        self._write(':')
+        self._change_indent(1)
+        for statement in node.body:
+            self.visit(statement)
+        self._change_indent(-1)
+        if getattr(node, 'orelse', None):
+            self._new_line()
+            self._write('else:')
+            self._change_indent(1)
+            for statement in node.orelse:
+                self.visit(statement)
+            self._change_indent(-1)
+
+    # With(expr context_expr, expr? optional_vars, stmt* body)
+    def visit_With(self, node):
+        self._new_line()
+        self._write('with ')
+        self.visit(node.context_expr)
+        if getattr(node, 'optional_vars', None):
+            self._write(' as ')
+            self.visit(node.optional_vars)
+        self._write(':')
+        self._change_indent(1)
+        for statement in node.body:
+            self.visit(statement)
+        self._change_indent(-1)
+
+    # Raise(expr? type, expr? inst, expr? tback)
+    def visit_Raise(self, node):
+        self._new_line()
+        self._write('raise')
+        if not getattr(node, "type", None):
+            return
+        self._write(' ')
+        self.visit(node.type)
+        if not node.inst:
+            return
+        self._write(', ')
+        self.visit(node.inst)
+        if not node.tback:
+            return
+        self._write(', ')
+        self.visit(node.tback)
+
+    # TryExcept(stmt* body, excepthandler* handlers, stmt* orelse)
+    def visit_TryExcept(self, node):
+        self._new_line()
+        self._write('try:')
+        self._change_indent(1)
+        for statement in node.body:
+            self.visit(statement)
+        self._change_indent(-1)
+        if getattr(node, 'handlers', None):
+            for handler in node.handlers:
+                self.visit(handler)
+        self._new_line()
+        if getattr(node, 'orelse', None):
+            self._write('else:')
+            self._change_indent(1)
+            for statement in node.orelse:
+                self.visit(statement)
+            self._change_indent(-1)
+
+    # excepthandler = (expr? type, expr? name, stmt* body)
+    def visit_ExceptHandler(self, node):
+        self._new_line()
+        self._write('except')
+        if getattr(node, 'type', None):
+            self._write(' ')
+            self.visit(node.type)
+        if getattr(node, 'name', None):
+            if sys.version_info[0] == 2:
+                assert getattr(node, 'type', None)
+                self._write(', ')
+            else:
+                self._write(' as ')
+            self.visit(node.name)
+        self._write(':')
+        self._change_indent(1)
+        for statement in node.body:
+            self.visit(statement)
+        self._change_indent(-1)
+    visit_excepthandler = visit_ExceptHandler
+
+    # TryFinally(stmt* body, stmt* finalbody)
+    def visit_TryFinally(self, node):
+        self._new_line()
+        self._write('try:')
+        self._change_indent(1)
+        for statement in node.body:
+            self.visit(statement)
+        self._change_indent(-1)
+
+        if getattr(node, 'finalbody', None):
+            self._new_line()
+            self._write('finally:')
+            self._change_indent(1)
+            for statement in node.finalbody:
+                self.visit(statement)
+            self._change_indent(-1)
+
+    # Assert(expr test, expr? msg)
+    def visit_Assert(self, node):
+        self._new_line()
+        self._write('assert ')
+        self.visit(node.test)
+        if getattr(node, 'msg', None):
+            self._write(', ')
+            self.visit(node.msg)
+
+    def visit_alias(self, node):
+        self._write(node.name)
+        if getattr(node, 'asname', None):
+            self._write(' as ')
+            self._write(node.asname)
+
+    # Import(alias* names)
+    def visit_Import(self, node):
+        self._new_line()
+        self._write('import ')
+        self.visit(node.names[0])
+        for name in node.names[1:]:
+            self._write(', ')
+            self.visit(name)
+
+    # ImportFrom(identifier module, alias* names, int? level)
+    def visit_ImportFrom(self, node):
+        self._new_line()
+        self._write('from ')
+        if node.level:
+            self._write('.' * node.level)
+        self._write(node.module)
+        self._write(' import ')
+        self.visit(node.names[0])
+        for name in node.names[1:]:
+            self._write(', ')
+            self.visit(name)
+
+    # Exec(expr body, expr? globals, expr? locals)
+    def visit_Exec(self, node):
+        self._new_line()
+        self._write('exec ')
+        self.visit(node.body)
+        if not node.globals:
+            return
+        self._write(', ')
+        self.visit(node.globals)
+        if not node.locals:
+            return
+        self._write(', ')
+        self.visit(node.locals)
+
+    # Global(identifier* names)
+    def visit_Global(self, node):
+        self._new_line()
+        self._write('global ')
+        self.visit(node.names[0])
+        for name in node.names[1:]:
+            self._write(', ')
+            self.visit(name)
+
+    # Expr(expr value)
+    def visit_Expr(self, node):
+        self._new_line()
+        self.visit(node.value)
+
+    # Pass
+    def visit_Pass(self, node):
+        self._new_line()
+        self._write('pass')
+
+    # Break
+    def visit_Break(self, node):
+        self._new_line()
+        self._write('break')
+
+    # Continue
+    def visit_Continue(self, node):
+        self._new_line()
+        self._write('continue')
+
+    ### EXPRESSIONS
+    def with_parens(f):
+        def _f(self, node):
+            self._write('(')
+            f(self, node)
+            self._write(')')
+        return _f
+
+    bool_operators = {ast.And: 'and', ast.Or: 'or'}
+
+    # BoolOp(boolop op, expr* values)
+    @with_parens
+    def visit_BoolOp(self, node):
+        joiner = ' ' + self.bool_operators[node.op.__class__] + ' '
+        self.visit(node.values[0])
+        for value in node.values[1:]:
+            self._write(joiner)
+            self.visit(value)
+
+    binary_operators = {
+        ast.Add: '+',
+        ast.Sub: '-',
+        ast.Mult: '*',
+        ast.Div: '/',
+        ast.Mod: '%',
+        ast.Pow: '**',
+        ast.LShift: '<<',
+        ast.RShift: '>>',
+        ast.BitOr: '|',
+        ast.BitXor: '^',
+        ast.BitAnd: '&',
+        ast.FloorDiv: '//'
+    }
+
+    # BinOp(expr left, operator op, expr right)
+    @with_parens
+    def visit_BinOp(self, node):
+        self.visit(node.left)
+        self._write(' ' + self.binary_operators[node.op.__class__] + ' ')
+        self.visit(node.right)
+
+    unary_operators = {
+        ast.Invert: '~',
+        ast.Not: 'not',
+        ast.UAdd: '+',
+        ast.USub: '-',
+    }
+
+    # UnaryOp(unaryop op, expr operand)
+    def visit_UnaryOp(self, node):
+        self._write(self.unary_operators[node.op.__class__] + ' ')
+        self.visit(node.operand)
+
+    # Lambda(arguments args, expr body)
+    @with_parens
+    def visit_Lambda(self, node):
+        self._write('lambda ')
+        self.visit(node.args)
+        self._write(': ')
+        self.visit(node.body)
+
+    # IfExp(expr test, expr body, expr orelse)
+    @with_parens
+    def visit_IfExp(self, node):
+        self.visit(node.body)
+        self._write(' if ')
+        self.visit(node.test)
+        self._write(' else ')
+        self.visit(node.orelse)
+
+    # Dict(expr* keys, expr* values)
+    def visit_Dict(self, node):
+        self._write('{')
+        for key, value in zip(node.keys, node.values):
+            self.visit(key)
+            self._write(': ')
+            self.visit(value)
+            self._write(', ')
+        self._write('}')
+
+    def visit_Set(self, node):
+        self._write('{')
+        elts = list(node.elts)
+        last = elts.pop()
+        for elt in elts:
+            self.visit(elt)
+            self._write(', ')
+        self.visit(last)
+        self._write('}')
+
+    # ListComp(expr elt, comprehension* generators)
+    def visit_ListComp(self, node):
+        self._write('[')
+        self.visit(node.elt)
+        for generator in node.generators:
+            # comprehension = (expr target, expr iter, expr* ifs)
+            self._write(' for ')
+            self.visit(generator.target)
+            self._write(' in ')
+            self.visit(generator.iter)
+            for ifexpr in generator.ifs:
+                self._write(' if ')
+                self.visit(ifexpr)
+        self._write(']')
+
+    # GeneratorExp(expr elt, comprehension* generators)
+    def visit_GeneratorExp(self, node):
+        self._write('(')
+        self.visit(node.elt)
+        for generator in node.generators:
+            # comprehension = (expr target, expr iter, expr* ifs)
+            self._write(' for ')
+            self.visit(generator.target)
+            self._write(' in ')
+            self.visit(generator.iter)
+            for ifexpr in generator.ifs:
+                self._write(' if ')
+                self.visit(ifexpr)
+        self._write(')')
+
+    # Yield(expr? value)
+    def visit_Yield(self, node):
+        self._write('yield')
+        if getattr(node, 'value', None):
+            self._write(' ')
+            self.visit(node.value)
+
+    comparison_operators = {
+        ast.Eq: '==',
+        ast.NotEq: '!=',
+        ast.Lt: '<',
+        ast.LtE: '<=',
+        ast.Gt: '>',
+        ast.GtE: '>=',
+        ast.Is: 'is',
+        ast.IsNot: 'is not',
+        ast.In: 'in',
+        ast.NotIn: 'not in',
+    }
+
+    # Compare(expr left, cmpop* ops, expr* comparators)
+    @with_parens
+    def visit_Compare(self, node):
+        self.visit(node.left)
+        for op, comparator in zip(node.ops, node.comparators):
+            self._write(' ' + self.comparison_operators[op.__class__] + ' ')
+            self.visit(comparator)
+
+    # Call(expr func, expr* args, keyword* keywords,
+    #                         expr? starargs, expr? kwargs)
+    def visit_Call(self, node):
+        self.visit(node.func)
+        self._write('(')
+        first = True
+        for arg in node.args:
+            if not first:
+                self._write(', ')
+            first = False
+            self.visit(arg)
+
+        for keyword in node.keywords:
+            if not first:
+                self._write(', ')
+            first = False
+            # keyword = (identifier arg, expr value)
+            self._write(keyword.arg)
+            self._write('=')
+            self.visit(keyword.value)
+        if getattr(node, 'starargs', None):
+            if not first:
+                self._write(', ')
+            first = False
+            self._write('*')
+            self.visit(node.starargs)
+
+        if getattr(node, 'kwargs', None):
+            if not first:
+                self._write(', ')
+            first = False
+            self._write('**')
+            self.visit(node.kwargs)
+        self._write(')')
+
+    # Repr(expr value)
+    def visit_Repr(self, node):
+        self._write('`')
+        self.visit(node.value)
+        self._write('`')
+
+    # Num(object n)
+    def visit_Num(self, node):
+        self._write(repr(node.n))
+
+    # Str(string s)
+    def visit_Str(self, node):
+        self._write(repr(node.s))
+
+    # Attribute(expr value, identifier attr, expr_context ctx)
+    def visit_Attribute(self, node):
+        self.visit(node.value)
+        self._write('.')
+        self._write(node.attr)
+
+    # Subscript(expr value, slice slice, expr_context ctx)
+    def visit_Subscript(self, node):
+        self.visit(node.value)
+        self._write('[')
+
+        def _process_slice(node):
+            if isinstance(node, ast.Ellipsis):
+                self._write('...')
+            elif isinstance(node, ast.Slice):
+                if getattr(node, 'lower', 'None'):
+                    self.visit(node.lower)
+                self._write(':')
+                if getattr(node, 'upper', None):
+                    self.visit(node.upper)
+                if getattr(node, 'step', None):
+                    self._write(':')
+                    self.visit(node.step)
+            elif isinstance(node, ast.Index):
+                self.visit(node.value)
+            elif isinstance(node, ast.ExtSlice):
+                self.visit(node.dims[0])
+                for dim in node.dims[1:]:
+                    self._write(', ')
+                    self.visit(dim)
+            else:
+                raise NotImplemented('Slice type not implemented')
+        _process_slice(node.slice)
+        self._write(']')
+
+    # Name(identifier id, expr_context ctx)
+    def visit_Name(self, node):
+        self._write(node.id)
+
+    # List(expr* elts, expr_context ctx)
+    def visit_List(self, node):
+        self._write('[')
+        for elt in node.elts:
+            self.visit(elt)
+            self._write(', ')
+        self._write(']')
+
+    # Tuple(expr *elts, expr_context ctx)
+    def visit_Tuple(self, node):
+        self._write('(')
+        for elt in node.elts:
+            self.visit(elt)
+            self._write(', ')
+        self._write(')')
+
+
+class AnnotationAwareVisitor(ast.NodeVisitor):
+    def visit(self, node):
+        annotation = node_annotations.get(node)
+        if annotation is not None:
+            assert hasattr(annotation, '_fields')
+            node = annotation
+
+        super(AnnotationAwareVisitor, self).visit(node)
+
+    def apply_transform(self, node):
+        if node not in node_annotations:
+            result = self.transform(node)
+            if result is not None and result is not node:
+                node_annotations[node] = result
+
+
+class NameLookupRewriteVisitor(AnnotationAwareVisitor):
+    def __init__(self, transform):
+        self.transform = transform
+        self.transformed = set()
+        self.scopes = [set()]
+
+    def __call__(self, node):
+        self.visit(node)
+        return self.transformed
+
+    def visit_Name(self, node):
+        scope = self.scopes[-1]
+        if isinstance(node.ctx, ast.Param):
+            scope.add(node.id)
+        elif node.id not in scope:
+            self.transformed.add(node.id)
+            self.apply_transform(node)
+
+    def visit_Lambda(self, node):
+        self.scopes.append(set())
+        try:
+            self.visit(node.args)
+            self.visit(node.body)
+        finally:
+            self.scopes.pop()
+
+
+class ItemLookupOnAttributeErrorVisitor(AnnotationAwareVisitor):
+    def __init__(self, transform):
+        self.transform = transform
+
+    def visit_Attribute(self, node):
+        self.generic_visit(node)
+        self.apply_transform(node)
diff --git a/lib/chameleon/src/chameleon/benchmark.py b/lib/chameleon/src/chameleon/benchmark.py
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/benchmark.py
@@ -0,0 +1,477 @@
+import unittest
+import time
+import os
+import re
+
+re_amp = re.compile(r'&(?!([A-Za-z]+|#[0-9]+);)')
+
+BIGTABLE_ZPT = """\
+<table xmlns="http://www.w3.org/1999/xhtml"
+xmlns:tal="http://xml.zope.org/namespaces/tal">
+<tr tal:repeat="row python: options['table']">
+<td tal:repeat="c python: row.values()">
+<span tal:define="d python: c + 1"
+tal:attributes="class python: 'column-' + str(d)"
+tal:content="python: d" />
+</td>
+</tr>
+</table>"""
+
+MANY_STRINGS_ZPT = """\
+<table xmlns="http://www.w3.org/1999/xhtml"
+xmlns:tal="http://xml.zope.org/namespaces/tal">
+<tr tal:repeat="i python: xrange(1000)">
+<td tal:content="string: number ${i}" />
+</tr>
+</table>
+"""
+
+HELLO_WORLD_ZPT = """\
+<html xmlns="http://www.w3.org/1999/xhtml"
+xmlns:tal="http://xml.zope.org/namespaces/tal">
+<body>
+<h1>Hello, world!</h1>
+</body>
+</html>
+"""
+
+I18N_ZPT = """\
+<html xmlns="http://www.w3.org/1999/xhtml"
+xmlns:tal="http://xml.zope.org/namespaces/tal"
+xmlns:i18n="http://xml.zope.org/namespaces/i18n">
+  <body>
+    <div tal:repeat="i python: xrange(10)">
+      <div i18n:translate="">
+        Hello world!
+      </div>
+      <div i18n:translate="hello_world">
+        Hello world!
+      </div>
+      <div i18n:translate="">
+        <sup>Hello world!</sup>
+      </div>
+    </div>
+  </body>
+</html>
+"""
+
+
+def benchmark(title):
+    def decorator(f):
+        def wrapper(*args):
+            print(
+                "==========================\n " \
+                "%s\n==========================" % \
+                title)
+            return f(*args)
+        return wrapper
+    return decorator
+
+
+def timing(func, *args, **kwargs):
+    t1 = t2 = time.time()
+    i = 0
+    while t2 - t1 < 3:
+        func(**kwargs)
+        func(**kwargs)
+        func(**kwargs)
+        func(**kwargs)
+        i += 4
+        t2 = time.time()
+    return float(10 * (t2 - t1)) / i
+
+
+START = 0
+END = 1
+TAG = 2
+
+
+def yield_tokens(table=None):
+    index = []
+    tag = index.append
+    _re_amp = re_amp
+    tag(START)
+    yield "<", "html", "", ">\n"
+    for r in table:
+        tag(START)
+        yield "<", "tr", "", ">\n"
+
+        for c in r.values():
+            d = c + 1
+            tag(START)
+            yield "<", "td", "", ">\n"
+
+            _tmp5 = d
+            if not isinstance(_tmp5, unicode):
+                _tmp5 = str(_tmp5)
+            if ('&' in _tmp5):
+                if (';' in _tmp5):
+                    _tmp5 = _re_amp.sub('&amp;', _tmp5)
+                else:
+                    _tmp5 = _tmp5.replace('&', '&amp;')
+            if ('<' in _tmp5):
+                _tmp5 = _tmp5.replace('<', '&lt;')
+            if ('>' in _tmp5):
+                _tmp5 = _tmp5.replace('>', '&gt;')
+            if ('"' in _tmp5):
+                _tmp5 = _tmp5.replace('"', '&quot;')
+            _tmp5 = "column-%s" % _tmp5
+
+            _tmp = d
+            if (_tmp.__class__ not in (str, unicode, int, float, )):
+                raise
+            if (_tmp is not None):
+                if not isinstance(_tmp, unicode):
+                    _tmp = str(_tmp)
+                if ('&' in _tmp):
+                    if (';' in _tmp):
+                        _tmp = _re_amp.sub('&amp;', _tmp)
+                    else:
+                        _tmp = _tmp.replace('&', '&amp;')
+                if ('<' in _tmp):
+                    _tmp = _tmp.replace('<', '&lt;')
+                if ('>' in _tmp):
+                    _tmp = _tmp.replace('>', '&gt;')
+            tag(START)
+
+            t = ["classicism"]
+
+            yield "<", "span", " ", t[0], '="', _tmp5, '"', ">\n"
+            tag(END)
+            yield "</", "span", ">\n"
+            tag(END)
+            yield "</", "td", ">\n"
+        tag(END)
+        yield "</", "tr", ">\n"
+    tag(END)
+    yield "</", "html", ">\n"
+
+
+def yield_tokens_dict_version(**kwargs):
+    index = []
+    tag = index.append
+    _re_amp = re_amp
+    tag(START)
+    yield "<", "html", "", ">\n"
+
+    for r in kwargs['table']:
+        kwargs['r'] = r
+        tag(START)
+        yield "<", "tr", "", ">\n"
+
+        for c in kwargs['r'].values():
+            kwargs['d'] = c + 1
+            tag(START)
+            yield "<", "td", "", ">\n"
+
+            _tmp5 = kwargs['d']
+            if not isinstance(_tmp5, unicode):
+                _tmp5 = str(_tmp5)
+            if ('&' in _tmp5):
+                if (';' in _tmp5):
+                    _tmp5 = _re_amp.sub('&amp;', _tmp5)
+                else:
+                    _tmp5 = _tmp5.replace('&', '&amp;')
+            if ('<' in _tmp5):
+                _tmp5 = _tmp5.replace('<', '&lt;')
+            if ('>' in _tmp5):
+                _tmp5 = _tmp5.replace('>', '&gt;')
+            if ('"' in _tmp5):
+                _tmp5 = _tmp5.replace('"', '&quot;')
+            _tmp5 = "column-%s" % _tmp5
+
+            _tmp = kwargs['d']
+            if (_tmp.__class__ not in (str, unicode, int, float, )):
+                raise
+            if (_tmp is not None):
+                if not isinstance(_tmp, unicode):
+                    _tmp = str(_tmp)
+                if ('&' in _tmp):
+                    if (';' in _tmp):
+                        _tmp = _re_amp.sub('&amp;', _tmp)
+                    else:
+                        _tmp = _tmp.replace('&', '&amp;')
+                if ('<' in _tmp):
+                    _tmp = _tmp.replace('<', '&lt;')
+                if ('>' in _tmp):
+                    _tmp = _tmp.replace('>', '&gt;')
+            tag(START)
+
+            t = ["classicism"]
+
+            yield "<", "span", " ", t[0], '="', _tmp5, '"', ">\n"
+            tag(END)
+            yield "</", "span", ">\n"
+            tag(END)
+            yield "</", "td", ">\n"
+        tag(END)
+        yield "</", "tr", ">\n"
+    tag(END)
+    yield "</", "html", ">\n"
+
+
+def yield_stream(table=None):
+    _re_amp = re_amp
+    yield START, ("html", "", "\n"), None
+    for r in table:
+        yield START, ("tr", "", "\n"), None
+
+        for c in r.values():
+            d = c + 1
+            yield START, ("td", "", "\n"), None
+
+            _tmp5 = d
+            if not isinstance(_tmp5, unicode):
+                _tmp5 = str(_tmp5)
+            if ('&' in _tmp5):
+                if (';' in _tmp5):
+                    _tmp5 = _re_amp.sub('&amp;', _tmp5)
+                else:
+                    _tmp5 = _tmp5.replace('&', '&amp;')
+            if ('<' in _tmp5):
+                _tmp5 = _tmp5.replace('<', '&lt;')
+            if ('>' in _tmp5):
+                _tmp5 = _tmp5.replace('>', '&gt;')
+            if ('"' in _tmp5):
+                _tmp5 = _tmp5.replace('"', '&quot;')
+            _tmp5 = "column-%s" % _tmp5
+
+            _tmp = d
+            if (_tmp.__class__ not in (str, unicode, int, float, )):
+                raise
+            if (_tmp is not None):
+                if not isinstance(_tmp, unicode):
+                    _tmp = str(_tmp)
+                if ('&' in _tmp):
+                    if (';' in _tmp):
+                        _tmp = _re_amp.sub('&amp;', _tmp)
+                    else:
+                        _tmp = _tmp.replace('&', '&amp;')
+                if ('<' in _tmp):
+                    _tmp = _tmp.replace('<', '&lt;')
+                if ('>' in _tmp):
+                    _tmp = _tmp.replace('>', '&gt;')
+            yield START, ("span", "", _tmp, " ", "class", _tmp5), None
+
+            yield END, ("span", "", "\n"), None
+            yield END, ("td", "", "\n"), None
+        yield END, ("tr", "", "\n"), None
+    yield END, ("html", "", "\n"), None
+
+from itertools import chain
+
+
+def bigtable_python_tokens(table=None, renderer=None):
+    iterable = renderer(table=table)
+    stream = chain(*iterable)
+    return "".join(stream)
+
+
+def bigtable_python_stream(table=None, renderer=None):
+    stream = renderer(table=table)
+    return "".join(stream_output(stream))
+
+
+def bigtable_python_stream_with_filter(table=None, renderer=None):
+    stream = renderer(table=table)
+    return "".join(stream_output(uppercase_filter(stream)))
+
+
+def uppercase_filter(stream):
+    for kind, data, pos in stream:
+        if kind is START:
+            data = (data[0], data[1], data[2].upper(),) + data[3:]
+        elif kind is END:
+            data = (data[0], data[1], data[2].upper())
+        elif kind is TAG:
+            raise NotImplemented
+        yield kind, data, pos
+
+
+def stream_output(stream):
+    for kind, data, pos in stream:
+        if kind is START:
+            tag = data[0]
+            yield "<%s" % tag
+            l = len(data)
+
+            # optimize for common cases
+            if l == 3:
+                pass
+            elif l == 6:
+                yield '%s%s="%s"' % (data[3], data[4], data[5])
+            else:
+                i = 3
+                while i < l:
+                    yield '%s%s="%s"' % (data[i], data[i + 1], data[i + 2])
+                    i += 3
+            yield "%s>%s" % (data[1], data[2])
+        elif kind is END:
+            yield "</%s%s>%s" % data
+        elif kind is TAG:
+            raise NotImplemented
+
+
+class Benchmarks(unittest.TestCase):
+    table = [dict(a=1, b=2, c=3, d=4, e=5, f=6, g=7, h=8, i=9, j=10) \
+             for x in range(1000)]
+
+    def setUp(self):
+        # set up i18n component
+        from zope.i18n import translate
+        from zope.i18n.interfaces import INegotiator
+        from zope.i18n.interfaces import ITranslationDomain
+        from zope.i18n.negotiator import Negotiator
+        from zope.i18n.simpletranslationdomain import SimpleTranslationDomain
+        from zope.i18n.tests.test_negotiator import Env
+        from zope.tales.tales import Context
+
+        self.env = Env(('klingon', 'da', 'en', 'fr', 'no'))
+
+        class ZopeI18NContext(Context):
+
+            def translate(self, msgid, domain=None, context=None,
+                          mapping=None, default=None):
+                context = self.vars['options']['env']
+                return translate(msgid, domain, mapping,
+                                 context=context, default=default)
+
+        def _getContext(self, contexts=None, **kwcontexts):
+            if contexts is not None:
+                if kwcontexts:
+                    kwcontexts.update(contexts)
+                else:
+                    kwcontexts = contexts
+            return ZopeI18NContext(self, kwcontexts)
+
+        def _pt_getEngineContext(namespace):
+            self = namespace['template']
+            engine = self.pt_getEngine()
+            return _getContext(engine, namespace)
+
+        import zope.component
+        zope.component.provideUtility(Negotiator(), INegotiator)
+        catalog = SimpleTranslationDomain('domain')
+        zope.component.provideUtility(catalog, ITranslationDomain, 'domain')
+        self.files = os.path.abspath(os.path.join(__file__, '..', 'input'))
+
+    @staticmethod
+    def _chameleon(body, **kwargs):
+        from .zpt.template import PageTemplate
+        return PageTemplate(body, **kwargs)
+
+    @staticmethod
+    def _zope(body):
+        from zope.pagetemplate.pagetemplatefile import PageTemplate
+        template = PageTemplate()
+        template.pt_edit(body, 'text/xhtml')
+        return template
+
+    @benchmark(u"BIGTABLE [python]")
+    def test_bigtable(self):
+        options = {'table': self.table}
+
+        t_chameleon = timing(self._chameleon(BIGTABLE_ZPT), options=options)
+        print("chameleon:         %7.2f" % t_chameleon)
+
+        t_chameleon_utf8 = timing(
+            self._chameleon(BIGTABLE_ZPT, encoding='utf-8'), options=options)
+        print("chameleon (utf-8): %7.2f" % t_chameleon_utf8)
+
+        t_tokens = timing(
+            bigtable_python_tokens, table=self.table, renderer=yield_tokens)
+        print("token:             %7.2f" % t_tokens)
+
+        t_tokens_dict_version = timing(
+            bigtable_python_tokens, table=self.table,
+            renderer=yield_tokens_dict_version)
+        print("token (dict):      %7.2f" % t_tokens_dict_version)
+
+        t_stream = timing(
+            bigtable_python_stream, table=self.table, renderer=yield_stream)
+        print("stream:            %7.2f" % t_stream)
+
+        t_zope = timing(self._zope(BIGTABLE_ZPT), table=self.table)
+        print("zope.pagetemplate: %7.2f" % t_zope)
+        print("                  %7.1fX" % (t_zope / t_chameleon))
+
+        print("--------------------------")
+        print("check: %d vs %d" % (
+            len(self._chameleon(BIGTABLE_ZPT)(options=options)),
+            len(self._zope(BIGTABLE_ZPT)(table=self.table))))
+        print("--------------------------")
+
+    @benchmark(u"MANY STRINGS [python]")
+    def test_many_strings(self):
+        t_chameleon = timing(self._chameleon(MANY_STRINGS_ZPT))
+        print("chameleon:         %7.2f" % t_chameleon)
+        t_zope = timing(self._zope(MANY_STRINGS_ZPT))
+        print("zope.pagetemplate: %7.2f" % t_zope)
+        print("                  %7.1fX" % (t_zope / t_chameleon))
+
+        print("--------------------------")
+        print("check: %d vs %d" % (
+            len(self._chameleon(MANY_STRINGS_ZPT)()),
+            len(self._zope(MANY_STRINGS_ZPT)())))
+        print("--------------------------")
+
+    @benchmark(u"HELLO WORLD")
+    def test_hello_world(self):
+        t_chameleon = timing(self._chameleon(HELLO_WORLD_ZPT)) * 1000
+        print("chameleon:         %7.2f" % t_chameleon)
+        t_zope = timing(self._zope(HELLO_WORLD_ZPT)) * 1000
+        print("zope.pagetemplate: %7.2f" % t_zope)
+        print("                  %7.1fX" % (t_zope / t_chameleon))
+
+        print("--------------------------")
+        print("check: %d vs %d" % (
+            len(self._chameleon(HELLO_WORLD_ZPT)()),
+            len(self._zope(HELLO_WORLD_ZPT)())))
+        print("--------------------------")
+
+    @benchmark(u"I18N")
+    def test_i18n(self):
+        from zope.i18n import translate
+        t_chameleon = timing(
+            self._chameleon(I18N_ZPT),
+            translate=translate,
+            language="klingon") * 1000
+        print("chameleon:         %7.2f" % t_chameleon)
+        t_zope = timing(self._zope(I18N_ZPT), env=self.env) * 1000
+        print("zope.pagetemplate: %7.2f" % t_zope)
+        print("                  %7.1fX" % (t_zope / t_chameleon))
+
+    @benchmark(u"COMPILATION")
+    def test_compilation(self):
+        template = self._chameleon(HELLO_WORLD_ZPT)
+
+        def chameleon_cook_and_render(template=template):
+            template.cook(HELLO_WORLD_ZPT)
+            template()
+
+        t_chameleon = timing(chameleon_cook_and_render) * 1000
+        print("chameleon:         %7.2f" % t_chameleon)
+
+        template = self._zope(HELLO_WORLD_ZPT)
+
+        def zope_cook_and_render(templte=template):
+            template._cook()
+            template()
+
+        t_zope = timing(zope_cook_and_render) * 1000
+        print("zope.pagetemplate: %7.2f" % t_zope)
+        print("                    %0.3fX" % (t_zope / t_chameleon))
+
+
+def start():
+    result = unittest.TestResult()
+    test = unittest.makeSuite(Benchmarks)
+    test.run(result)
+
+    for error in result.errors:
+        print("Error in %s...\n" % error[0])
+        print(error[1])
+
+    for failure in result.failures:
+        print("Failure in %s...\n" % failure[0])
+        print(failure[1])
diff --git a/lib/chameleon/src/chameleon/codegen.py b/lib/chameleon/src/chameleon/codegen.py
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/codegen.py
@@ -0,0 +1,215 @@
+try:
+    import ast
+except ImportError:
+    from chameleon import ast24 as ast
+
+import inspect
+import textwrap
+import types
+import copy
+
+try:
+    import __builtin__ as builtins
+except ImportError:
+    import builtins
+
+reverse_builtin_map = dict(
+    (value, name) for (name, value) in builtins.__dict__.items()
+    )
+
+try:
+    basestring
+except NameError:
+    basestring = str
+
+from .astutil import ASTCodeGenerator
+from .astutil import load
+from .astutil import store
+from .astutil import parse
+from .astutil import Builtin
+from .astutil import Symbol
+from .astutil import node_annotations
+
+from .exc import CompilationError
+
+
+try:
+    NATIVE_NUMBERS = int, float, long, bool
+except NameError:
+    NATIVE_NUMBERS = int, float, bool
+
+
+def template(function, mode='exec', **kw):
+    def wrapper(*vargs, **kwargs):
+        symbols = dict(zip(args, vargs + defaults))
+        symbols.update(kwargs)
+
+        class Visitor(ast.NodeVisitor):
+            def visit_Name(self, node):
+                value = symbols.get(node.id, self)
+                if value is not self:
+                    if isinstance(value, basestring):
+                        value = load(value)
+                    if isinstance(value, type) or value in reverse_builtin_map:
+                        name = reverse_builtin_map.get(value)
+                        if name is not None:
+                            value = Builtin(name)
+                        else:
+                            value = Symbol(value)
+
+                    assert node not in node_annotations
+                    assert hasattr(value, '_fields')
+                    node_annotations[node] = value
+
+        expr = parse(source, mode=mode)
+        if not isinstance(function, basestring):
+            expr = expr.body[0]
+
+        Visitor().visit(expr)
+        return expr.body
+
+    if isinstance(function, basestring):
+        source = function
+        defaults = args = ()
+        return wrapper(**kw)
+
+    source = textwrap.dedent(inspect.getsource(function))
+    argspec = inspect.getargspec(function)
+    args = argspec[0]
+    defaults = argspec[3] or ()
+    return wrapper
+
+
+class TemplateCodeGenerator(ASTCodeGenerator):
+    """Extends the standard Python code generator class with handlers
+    for the helper node classes:
+
+    - Symbol (an importable value)
+    - Static (value that can be made global)
+    - Builtin (from the builtins module)
+    - Marker (short-hand for a unique static object)
+
+    """
+
+    names = ()
+
+    def __init__(self, tree):
+        self.imports = {}
+        self.defines = {}
+        self.markers = {}
+
+        # Generate code
+        super(TemplateCodeGenerator, self).__init__(tree)
+
+    def visit_Module(self, node):
+        super(TemplateCodeGenerator, self).visit_Module(node)
+
+        # Make sure we terminate the line printer
+        self.flush()
+
+        # Clear lines array for import visits
+        body = self.lines
+        self.lines = []
+
+        while self.defines:
+            name, node = self.defines.popitem()
+            assignment = ast.Assign(targets=[store(name)], value=node)
+            self.visit(assignment)
+
+        # Make sure we terminate the line printer
+        self.flush()
+
+        # Clear lines array for import visits
+        defines = self.lines
+        self.lines = []
+
+        while self.imports:
+            value, node = self.imports.popitem()
+
+            if isinstance(value, types.ModuleType):
+                stmt = ast.Import(
+                    names=[ast.alias(name=value.__name__, asname=node.id)])
+            elif hasattr(value, '__name__'):
+                path = reverse_builtin_map.get(value)
+                if path is None:
+                    path = value.__module__
+                    name = value.__name__
+                stmt = ast.ImportFrom(
+                    module=path,
+                    names=[ast.alias(name=name, asname=node.id)],
+                    level=0,
+                )
+            else:
+                raise TypeError(value)
+
+            self.visit(stmt)
+
+        # Clear last import
+        self.flush()
+
+        # Stich together lines
+        self.lines += defines + body
+
+    def define(self, name, node):
+        assert node is not None
+        value = self.defines.get(name)
+
+        if value is node:
+            pass
+        elif value is None:
+            self.defines[name] = node
+        else:
+            raise CompilationError(
+                "Duplicate symbol name for define.", name)
+
+        return load(name)
+
+    def require(self, value):
+        if value is None:
+            return load("None")
+
+        if isinstance(value, NATIVE_NUMBERS):
+            return ast.Num(value)
+
+        node = self.imports.get(value)
+        if node is None:
+            # we come up with a unique symbol based on the class name
+            name = "_%s" % getattr(value, '__name__', str(value))
+            node = load(name)
+            self.imports[value] = store(node.id)
+
+        return node
+
+    def visit(self, node):
+        annotation = node_annotations.get(node)
+        if annotation is None:
+            super(TemplateCodeGenerator, self).visit(node)
+        else:
+            self.visit(annotation)
+
+    def visit_Comment(self, node):
+        if node.stmt is None:
+            self._new_line()
+        else:
+            self.visit(node.stmt)
+
+        for line in node.text.replace('\r', '\n').split('\n'):
+            self._new_line()
+            self._write("%s#%s" % (node.space, line))
+
+    def visit_Builtin(self, node):
+        name = load(node.id)
+        self.visit(name)
+
+    def visit_Symbol(self, node):
+        node = self.require(node.value)
+        self.visit(node)
+
+    def visit_Static(self, node):
+        if node.name is None:
+            name = "_static_%d" % id(node.value)
+        else:
+            name = node.name
+
+        node = self.define(name, node.value)
+        self.visit(node)
diff --git a/lib/chameleon/src/chameleon/compiler.py b/lib/chameleon/src/chameleon/compiler.py
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/compiler.py
@@ -0,0 +1,1337 @@
+import re
+import sys
+import itertools
+import logging
+import threading
+import functools
+
+from .astutil import load
+from .astutil import store
+from .astutil import param
+from .astutil import swap
+from .astutil import subscript
+from .astutil import node_annotations
+from .astutil import annotated
+from .astutil import NameLookupRewriteVisitor
+from .astutil import Comment
+from .astutil import Symbol
+from .astutil import Builtin
+
+from .codegen import TemplateCodeGenerator
+from .codegen import template
+
+from .tales import StringExpr
+from .tal import ErrorInfo
+from .i18n import fast_translate
+
+from .nodes import Text
+from .nodes import Value
+from .nodes import Substitution
+from .nodes import Assignment
+from .nodes import Module
+from .nodes import Context
+
+from .config import DEBUG_MODE
+from .exc import TranslationError
+
+from .utils import DebuggingOutputStream
+from .utils import char2entity
+from .utils import ListDictProxy
+from .utils import native_string
+from .utils import byte_string
+from .utils import string_type
+from .utils import unicode_string
+from .utils import version
+from .utils import ast
+from .utils import builtins
+
+
+if version >= (3, 0, 0):
+    long = int
+
+log = logging.getLogger('chameleon.compiler')
+
+COMPILER_INTERNALS_OR_DISALLOWED = set([
+    "econtext",
+    "rcontext",
+    "translate",
+    "decode",
+    "convert",
+    "str",
+    "int",
+    "float",
+    "long",
+    "len",
+    "None",
+    "True",
+    "False",
+    "RuntimeError",
+    ])
+
+
+RE_MANGLE = re.compile('[\-: ]')
+
+if DEBUG_MODE:
+    LIST = template("cls()", cls=DebuggingOutputStream, mode="eval")
+else:
+    LIST = template("[]", mode="eval")
+
+
+def identifier(prefix, suffix=None):
+    return "__%s_%s" % (prefix, mangle(suffix or id(prefix)))
+
+
+def mangle(string):
+    return RE_MANGLE.sub('_', str(string)).replace('\n', '')
+
+
+def load_econtext(name):
+    return template("getitem(KEY)", KEY=ast.Str(s=name), mode="eval")
+
+
+def store_econtext(name):
+    name = native_string(name)
+    return subscript(name, load("econtext"), ast.Store())
+
+
+def store_rcontext(name):
+    name = native_string(name)
+    return subscript(name, load("rcontext"), ast.Store())
+
+
+ at template
+def emit_node(node):  # pragma: no cover
+    __append(node)
+
+
+ at template
+def emit_node_if_non_trivial(node):  # pragma: no cover
+    if node is not None:
+        __append(node)
+
+
+ at template
+def emit_bool(target, s, default_marker=None,
+                 default=None):  # pragma: no cover
+    if target is default_marker:
+        target = default
+    elif target:
+        target = s
+    else:
+        target = None
+
+
+ at template
+def emit_convert(
+    target, encoded=byte_string, str=unicode_string,
+    long=long, type=type,
+    default_marker=None, default=None):  # pragma: no cover
+    if target is None:
+        pass
+    elif target is default_marker:
+        target = default
+    else:
+        __tt = type(target)
+
+        if __tt is int or __tt is float or __tt is long:
+            target = str(target)
+        elif __tt is encoded:
+            target = decode(target)
+        elif __tt is not str:
+            try:
+                target = target.__html__
+            except AttributeError:
+                __converted = convert(target)
+                target = str(target) if target is __converted else __converted
+            else:
+                target = target()
+
+
+ at template
+def emit_translate(target, msgid, default=None):  # pragma: no cover
+    target = translate(msgid, default=default, domain=__i18n_domain)
+
+
+ at template
+def emit_convert_and_escape(
+    target, quote=None, quote_entity=None, str=unicode_string, long=long,
+    type=type, encoded=byte_string,
+    default_marker=None, default=None):  # pragma: no cover
+    if target is None:
+        pass
+    elif target is default_marker:
+        target = default
+    else:
+        __tt = type(target)
+
+        if __tt is int or __tt is float or __tt is long:
+            target = str(target)
+        else:
+            try:
+                if __tt is encoded:
+                    target = decode(target)
+                elif __tt is not str:
+                    try:
+                        target = target.__html__
+                    except:
+                        __converted = convert(target)
+                        target = str(target) if target is __converted \
+                                 else __converted
+                    else:
+                        raise RuntimeError
+            except RuntimeError:
+                target = target()
+            else:
+                if target is not None:
+                    try:
+                        escape = __re_needs_escape(target) is not None
+                    except TypeError:
+                        pass
+                    else:
+                        if escape:
+                            # Character escape
+                            if '&' in target:
+                                target = target.replace('&', '&amp;')
+                            if '<' in target:
+                                target = target.replace('<', '&lt;')
+                            if '>' in target:
+                                target = target.replace('>', '&gt;')
+                            if quote is not None and quote in target:
+                                target = target.replace(quote, quote_entity)
+
+
+class ExpressionEngine(object):
+    """Expression engine.
+
+    This test demonstrates how to configure and invoke the engine.
+
+    >>> from chameleon import tales
+    >>> parser = tales.ExpressionParser({
+    ...     'python': tales.PythonExpr,
+    ...     'not': tales.NotExpr,
+    ...     'exists': tales.ExistsExpr,
+    ...     'string': tales.StringExpr,
+    ...     }, 'python')
+
+    >>> engine = ExpressionEngine(parser)
+
+    An expression evaluation function:
+
+    >>> eval = lambda expression: tales.test(
+    ...     tales.IdentityExpr(expression), engine)
+
+    We have provided 'python' as the default expression type. This
+    means that when no prefix is given, the expression is evaluated as
+    a Python expression:
+
+    >>> eval('not False')
+    True
+
+    Note that the ``type`` prefixes bind left. If ``not`` and
+    ``exits`` are two expression type prefixes, consider the
+    following::
+
+    >>> eval('not: exists: int(None)')
+    True
+
+    The pipe operator binds right. In the following example, but
+    arguments are evaluated against ``not: exists: ``.
+
+    >>> eval('not: exists: help')
+    False
+
+    >>> eval('string:test ${1}${2}')
+    'test 12'
+
+    """
+
+    supported_char_escape_set = set(('&', '<', '>'))
+
+    def __init__(self, parser, char_escape=(),
+                 default=None, default_marker=None):
+        self._parser = parser
+        self._char_escape = char_escape
+        self._default = default
+        self._default_marker = default_marker
+
+    def __call__(self, string, target):
+        # BBB: This method is deprecated. Instead, a call should first
+        # be made to ``parse`` and then one of the assignment methods
+        # ("value" or "text").
+
+        compiler = self.parse(string)
+        return compiler(string, target)
+
+    def parse(self, string):
+        expression = self._parser(string)
+        compiler = self.get_compiler(expression, string)
+        return ExpressionCompiler(compiler, self)
+
+    def get_compiler(self, expression, string):
+        def compiler(target, engine, result_type=None, *args):
+            stmts = expression(target, engine)
+
+            if result_type is not None:
+                method = getattr(self, '_convert_%s' % result_type)
+                steps = method(target, *args)
+                stmts.extend(steps)
+
+            try:
+                line, column = string.location
+                filename = string.filename
+            except AttributeError:
+                line, column = 0, 0
+                filename = "<string>"
+
+            return [ast.TryExcept(
+                body=stmts,
+                handlers=[ast.ExceptHandler(
+                    body=template(
+                        "rcontext.setdefault('__error__', [])."
+                        "append((string, line, col, src, sys.exc_info()[1]))\n"
+                        "raise",
+                        string=ast.Str(s=string),
+                        line=ast.Num(n=line),
+                        col=ast.Num(n=column),
+                        src=ast.Str(s=filename),
+                        sys=Symbol(sys),
+                        ),
+                    )],
+                )]
+
+        return compiler
+
+    def _convert_bool(self, target, s):
+        """Converts value given by ``target`` to a string ``s`` if the
+        target is a true value, otherwise ``None``.
+        """
+
+        return emit_bool(
+            target, ast.Str(s=s),
+            default=self._default,
+            default_marker=self._default_marker
+            )
+
+    def _convert_text(self, target):
+        """Converts value given by ``target`` to text."""
+
+        if self._char_escape:
+            # This is a cop-out - we really only support a very select
+            # set of escape characters
+            other = set(self._char_escape) - self.supported_char_escape_set
+
+            if other:
+                for supported in '"', '\'', '':
+                    if supported in self._char_escape:
+                        quote = supported
+                        break
+                else:
+                    raise RuntimeError(
+                        "Unsupported escape set: %s." % repr(self._char_escape)
+                        )
+            else:
+                quote = '\0'
+
+            entity = char2entity(quote or '\0')
+
+            return emit_convert_and_escape(
+                target,
+                quote=ast.Str(s=quote),
+                quote_entity=ast.Str(s=entity),
+                default=self._default,
+                default_marker=self._default_marker,
+                )
+
+        return emit_convert(
+            target,
+            default=self._default,
+            default_marker=self._default_marker,
+            )
+
+
+class ExpressionCompiler(object):
+    def __init__(self, compiler, engine):
+        self.compiler = compiler
+        self.engine = engine
+
+    def assign_bool(self, target, s):
+        return self.compiler(target, self.engine, "bool", s)
+
+    def assign_text(self, target):
+        return self.compiler(target, self.engine, "text")
+
+    def assign_value(self, target):
+        return self.compiler(target, self.engine)
+
+
+class ExpressionEvaluator(object):
+    """Evaluates dynamic expression.
+
+    This is not particularly efficient, but supported for legacy
+    applications.
+
+    >>> from chameleon import tales
+    >>> parser = tales.ExpressionParser({'python': tales.PythonExpr}, 'python')
+    >>> engine = functools.partial(ExpressionEngine, parser)
+
+    >>> evaluate = ExpressionEvaluator(engine, {
+    ...     'foo': 'bar',
+    ...     })
+
+    The evaluation function is passed the local and remote context,
+    the expression type and finally the expression.
+
+    >>> evaluate({'boo': 'baz'}, {}, 'python', 'foo + boo')
+    'barbaz'
+
+    The cache is now primed:
+
+    >>> evaluate({'boo': 'baz'}, {}, 'python', 'foo + boo')
+    'barbaz'
+
+    Note that the call method supports currying of the expression
+    argument:
+
+    >>> python = evaluate({'boo': 'baz'}, {}, 'python')
+    >>> python('foo + boo')
+    'barbaz'
+
+    """
+
+    __slots__ = "_engine", "_cache", "_names", "_builtins"
+
+    def __init__(self, engine, builtins):
+        self._engine = engine
+        self._names, self._builtins = zip(*builtins.items())
+        self._cache = {}
+
+    def __call__(self, econtext, rcontext, expression_type, string=None):
+        if string is None:
+            return functools.partial(
+                self.__call__, econtext, rcontext, expression_type
+                )
+
+        expression = "%s:%s" % (expression_type, string)
+
+        try:
+            evaluate = self._cache[expression]
+        except KeyError:
+            assignment = Assignment(["_result"], expression, True)
+            module = Module("evaluate", Context(assignment))
+
+            compiler = Compiler(
+                self._engine, module, ('econtext', 'rcontext') + self._names
+                )
+
+            env = {}
+            exec(compiler.code, env)
+            evaluate = self._cache[expression] = env["evaluate"]
+
+        evaluate(econtext, rcontext, *self._builtins)
+        return econtext['_result']
+
+
+class NameTransform(object):
+    """
+    >>> nt = NameTransform(set(('foo', 'bar', )), {'boo': 'boz'})
+    >>> def test(node):
+    ...     rewritten = nt(node)
+    ...     module = ast.Module([ast.fix_missing_locations(rewritten)])
+    ...     codegen = TemplateCodeGenerator(module)
+    ...     return codegen.code
+
+    Any odd name:
+
+    >>> test(load('frobnitz'))
+    "getitem('frobnitz')"
+
+    A 'builtin' name will first be looked up via ``get`` allowing fall
+    back to the global builtin value:
+
+    >>> test(load('foo'))
+    "get('foo', foo)"
+
+    Internal names (with two leading underscores) are left alone:
+
+    >>> test(load('__internal'))
+    '__internal'
+
+    Compiler internals or disallowed names:
+
+    >>> test(load('econtext'))
+    'econtext'
+
+    Aliased names:
+
+    >>> test(load('boo'))
+    'boz'
+
+    """
+
+    def __init__(self, builtins, aliases):
+        self.builtins = builtins
+        self.aliases = aliases
+
+    def __call__(self, node):
+        name = node.id
+
+        # Don't rewrite names that begin with an underscore; they are
+        # internal and can be assumed to be locally defined. This
+        # policy really should be part of the template program, not
+        # defined here in the compiler.
+        if name.startswith('__') or name in COMPILER_INTERNALS_OR_DISALLOWED:
+            return node
+
+        if isinstance(node.ctx, ast.Store):
+            return store_econtext(name)
+
+        aliased = self.aliases.get(name)
+        if aliased is not None:
+            return load(aliased)
+
+        # If the name is a Python global, first try acquiring it from
+        # the dynamic context, then fall back to the global.
+        if name in self.builtins:
+            return template(
+                "get(key, name)",
+                mode="eval",
+                key=ast.Str(s=name),
+                name=load(name),
+                )
+
+        # Otherwise, simply acquire it from the dynamic context.
+        return load_econtext(name)
+
+
+class ExpressionTransform(object):
+    """Internal wrapper to transform expression nodes into assignment
+    statements.
+
+    The node input may use the provided expression engine, but other
+    expression node types are supported such as ``Builtin`` which
+    simply resolves a built-in name.
+
+    Used internally be the compiler.
+    """
+
+    def __init__(self, engine_factory, cache, transform):
+        self.engine_factory = engine_factory
+        self.cache = cache
+        self.transform = transform
+
+    def __call__(self, expression, target):
+        if isinstance(target, string_type):
+            target = store(target)
+
+        stmts = self.translate(expression, target)
+
+        # Apply dynamic name rewrite transform to each statement
+        visitor = NameLookupRewriteVisitor(self.transform)
+
+        for stmt in stmts:
+            visitor(stmt)
+
+        return stmts
+
+    def translate(self, expression, target):
+        if isinstance(target, string_type):
+            target = store(target)
+
+        cached = self.cache.get(expression)
+
+        if cached is not None:
+            stmts = [ast.Assign(targets=[target], value=cached)]
+        elif isinstance(expression, ast.expr):
+            stmts = [ast.Assign(targets=[target], value=expression)]
+        else:
+            # The engine interface supports simple strings, which
+            # default to expression nodes
+            if isinstance(expression, string_type):
+                expression = Value(expression, True)
+
+            kind = type(expression).__name__
+            visitor = getattr(self, "visit_%s" % kind)
+            stmts = visitor(expression, target)
+
+            # Add comment
+            target_id = getattr(target, "id", target)
+            comment = Comment(" %r -> %s" % (expression, target_id))
+            stmts.insert(0, comment)
+
+        return stmts
+
+    def visit_Value(self, node, target):
+        engine = self.engine_factory()
+        compiler = engine.parse(node.value)
+        return compiler.assign_value(target)
+
+    def visit_Default(self, node, target):
+        value = annotated(node.marker)
+        return [ast.Assign(targets=[target], value=value)]
+
+    def visit_Substitution(self, node, target):
+        engine = self.engine_factory(
+            char_escape=node.char_escape,
+            default=node.default,
+            )
+        compiler = engine.parse(node.value)
+        return compiler.assign_text(target)
+
+    def visit_Negate(self, node, target):
+        return self.translate(node.value, target) + \
+               template("TARGET = not TARGET", TARGET=target)
+
+    def visit_Identity(self, node, target):
+        expression = self.translate(node.expression, "__expression")
+        value = self.translate(node.value, "__value")
+
+        return expression + value + \
+               template("TARGET = __expression is __value", TARGET=target)
+
+    def visit_Equality(self, node, target):
+        expression = self.translate(node.expression, "__expression")
+        value = self.translate(node.value, "__value")
+
+        return expression + value + \
+               template("TARGET = __expression == __value", TARGET=target)
+
+    def visit_Boolean(self, node, target):
+        engine = self.engine_factory()
+        compiler = engine.parse(node.value)
+        return compiler.assign_bool(target, node.s)
+
+    def visit_Interpolation(self, node, target):
+        expr = node.value
+        if isinstance(expr, Substitution):
+            engine = self.engine_factory(
+                char_escape=expr.char_escape,
+                default=expr.default,
+                )
+        elif isinstance(expr, Value):
+            engine = self.engine_factory()
+        else:
+            raise RuntimeError("Bad value: %r." % node.value)
+
+        expression = StringExpr(expr.value, node.braces_required)
+        compiler = engine.get_compiler(expression, expr.value)
+        return compiler(target, engine)
+
+    def visit_Translate(self, node, target):
+        if node.msgid is not None:
+            msgid = ast.Str(s=node.msgid)
+        else:
+            msgid = target
+        return self.translate(node.node, target) + \
+               emit_translate(target, msgid, default=target)
+
+    def visit_Static(self, node, target):
+        value = annotated(node)
+        return [ast.Assign(targets=[target], value=value)]
+
+    def visit_Builtin(self, node, target):
+        value = annotated(node)
+        return [ast.Assign(targets=[target], value=value)]
+
+
+class Compiler(object):
+    """Generic compiler class.
+
+    Iterates through nodes and yields Python statements which form a
+    template program.
+    """
+
+    exceptions = NameError, \
+                 ValueError, \
+                 AttributeError, \
+                 LookupError, \
+                 TypeError
+
+    defaults = {
+        'translate': Symbol(fast_translate),
+        'decode': Builtin("str"),
+        'convert': Builtin("str"),
+        }
+
+    lock = threading.Lock()
+
+    global_builtins = set(builtins.__dict__)
+
+    def __init__(self, engine_factory, node, builtins={}):
+        self._scopes = [set()]
+        self._expression_cache = {}
+        self._translations = []
+        self._builtins = builtins
+        self._aliases = [{}]
+
+        transform = NameTransform(
+            self.global_builtins | set(builtins),
+            ListDictProxy(self._aliases),
+            )
+
+        self._engine = ExpressionTransform(
+            engine_factory,
+            self._expression_cache,
+            transform,
+            )
+
+        if isinstance(node_annotations, dict):
+            self.lock.acquire()
+            backup = node_annotations.copy()
+        else:
+            backup = None
+
+        try:
+            module = ast.Module([])
+            module.body += self.visit(node)
+            ast.fix_missing_locations(module)
+            generator = TemplateCodeGenerator(module)
+        finally:
+            if backup is not None:
+                node_annotations.clear()
+                node_annotations.update(backup)
+                self.lock.release()
+
+        self.code = generator.code
+
+    def visit(self, node):
+        if node is None:
+            return ()
+        kind = type(node).__name__
+        visitor = getattr(self, "visit_%s" % kind)
+        iterator = visitor(node)
+        return list(iterator)
+
+    def visit_Sequence(self, node):
+        for item in node.items:
+            for stmt in self.visit(item):
+                yield stmt
+
+    def visit_Element(self, node):
+        self._aliases.append(self._aliases[-1].copy())
+
+        for stmt in self.visit(node.start):
+            yield stmt
+
+        for stmt in self.visit(node.content):
+            yield stmt
+
+        if node.end is not None:
+            for stmt in self.visit(node.end):
+                yield stmt
+
+        self._aliases.pop()
+
+    def visit_Module(self, node):
+        body = []
+
+        body += template("import re")
+        body += template("import functools")
+        body += template("__marker = object()")
+        body += template(
+            r"g_re_amp = re.compile(r'&(?!([A-Za-z]+|#[0-9]+);)')"
+        )
+        body += template(
+            r"g_re_needs_escape = re.compile(r'[&<>\"\']').search")
+
+        body += template(
+            r"__re_whitespace = "
+            r"functools.partial(re.compile('\s+').sub, ' ')",
+        )
+
+        # Visit module content
+        program = self.visit(node.program)
+
+        body += [ast.FunctionDef(
+            name=node.name, args=ast.arguments(
+                args=[param(b) for b in self._builtins],
+                defaults=(),
+                ),
+            body=program
+            )]
+
+        return body
+
+    def visit_MacroProgram(self, node):
+        functions = []
+
+        # Visit defined macros
+        macros = getattr(node, "macros", ())
+        names = []
+        for macro in macros:
+            stmts = self.visit(macro)
+            function = stmts[-1]
+            names.append(function.name)
+            functions += stmts
+
+        # Return function dictionary
+        functions += [ast.Return(value=ast.Dict(
+            keys=[ast.Str(s=name) for name in names],
+            values=[load(name) for name in names],
+            ))]
+
+        return functions
+
+    def visit_Context(self, node):
+        return template("getitem = econtext.__getitem__") + \
+               template("get = econtext.get") + \
+               self.visit(node.node)
+
+    def visit_Macro(self, node):
+        body = []
+
+        # Initialization
+        body += template("__append = __stream.append")
+        body += template("__re_amp = g_re_amp")
+        body += template("__re_needs_escape = g_re_needs_escape")
+
+        # Resolve defaults
+        for name in self.defaults:
+            body += template(
+                "NAME = econtext[KEY]",
+                NAME=name, KEY=ast.Str(s=name)
+            )
+
+        # Internal set of defined slots
+        self._slots = set()
+
+        # Visit macro body
+        nodes = itertools.chain(*tuple(map(self.visit, node.body)))
+
+        # Slot resolution
+        for name in self._slots:
+            body += template(
+                "try: NAME = econtext[KEY].pop()\n"
+                "except: NAME = None",
+                KEY=ast.Str(s=name), NAME=store(name))
+
+        # Append visited nodes
+        body += nodes
+
+        function_name = "render" if node.name is None else \
+                        "render_%s" % mangle(node.name)
+
+        function = ast.FunctionDef(
+            name=function_name, args=ast.arguments(
+                args=[
+                    param("__stream"),
+                    param("econtext"),
+                    param("rcontext"),
+                    param("__i18n_domain"),
+                    ],
+                defaults=[load("None")],
+            ),
+            body=body
+            )
+
+        yield function
+
+    def visit_Text(self, node):
+        return emit_node(ast.Str(s=node.value))
+
+    def visit_Domain(self, node):
+        backup = "__previous_i18n_domain_%d" % id(node)
+        return template("BACKUP = __i18n_domain", BACKUP=backup) + \
+               template("__i18n_domain = NAME", NAME=ast.Str(s=node.name)) + \
+               self.visit(node.node) + \
+               template("__i18n_domain = BACKUP", BACKUP=backup)
+
+    def visit_OnError(self, node):
+        body = []
+
+        fallback = identifier("__fallback")
+        body += template("fallback = len(__stream)", fallback=fallback)
+
+        self._enter_assignment((node.name, ))
+        fallback_body = self.visit(node.fallback)
+        self._leave_assignment((node.name, ))
+
+        error_assignment = template(
+            "econtext[key] = cls(__exc, rcontext['__error__'][-1][1:3])",
+            cls=ErrorInfo,
+            key=ast.Str(s=node.name),
+            )
+
+        body += [ast.TryExcept(
+            body=self.visit(node.node),
+            handlers=[ast.ExceptHandler(
+                type=ast.Tuple(elts=[Builtin("Exception")], ctx=ast.Load()),
+                name=store("__exc"),
+                body=(error_assignment + \
+                      template("del __stream[fallback:]", fallback=fallback) + \
+                      fallback_body
+                      ),
+                )]
+            )]
+
+        return body
+
+    def visit_Content(self, node):
+        name = "__content"
+        body = self._engine(node.expression, store(name))
+
+        if node.translate:
+            body += emit_translate(name, name)
+
+        if node.char_escape:
+            body += emit_convert_and_escape(name)
+        else:
+            body += emit_convert(name)
+
+        body += template("if NAME is not None: __append(NAME)", NAME=name)
+
+        return body
+
+    def visit_Interpolation(self, node):
+        name = identifier("content")
+        return self._engine(node, name) + \
+               emit_node_if_non_trivial(name)
+
+    def visit_Alias(self, node):
+        assert len(node.names) == 1
+        name = node.names[0]
+        target = self._aliases[-1][name] = identifier(name, id(node))
+        return self._engine(node.expression, target)
+
+    def visit_Assignment(self, node):
+        for name in node.names:
+            if name in COMPILER_INTERNALS_OR_DISALLOWED:
+                raise TranslationError(
+                    "Name disallowed by compiler.", name
+                    )
+
+            if name.startswith('__'):
+                raise TranslationError(
+                    "Name disallowed by compiler (double underscore).",
+                    name
+                    )
+
+        assignment = self._engine(node.expression, store("__value"))
+
+        if len(node.names) != 1:
+            target = ast.Tuple(
+                elts=[store_econtext(name) for name in node.names],
+                ctx=ast.Store(),
+            )
+        else:
+            target = store_econtext(node.names[0])
+
+        assignment.append(ast.Assign(targets=[target], value=load("__value")))
+
+        for name in node.names:
+            if not node.local:
+                assignment += template(
+                    "rcontext[KEY] = __value", KEY=ast.Str(s=native_string(name))
+                    )
+
+        return assignment
+
+    def visit_Define(self, node):
+        scope = set(self._scopes[-1])
+        self._scopes.append(scope)
+
+        for assignment in node.assignments:
+            if assignment.local:
+                for stmt in self._enter_assignment(assignment.names):
+                    yield stmt
+
+            for stmt in self.visit(assignment):
+                yield stmt
+
+        for stmt in self.visit(node.node):
+            yield stmt
+
+        for assignment in node.assignments:
+            if assignment.local:
+                for stmt in self._leave_assignment(assignment.names):
+                    yield stmt
+
+        self._scopes.pop()
+
+    def visit_Omit(self, node):
+        return self.visit_Condition(node)
+
+    def visit_Condition(self, node):
+        target = "__condition"
+        assignment = self._engine(node.expression, target)
+
+        assert assignment
+
+        for stmt in assignment:
+            yield stmt
+
+        body = self.visit(node.node) or [ast.Pass()]
+
+        orelse = getattr(node, "orelse", None)
+        if orelse is not None:
+            orelse = self.visit(orelse)
+
+        test = load(target)
+
+        yield ast.If(test=test, body=body, orelse=orelse)
+
+    def visit_Translate(self, node):
+        """Translation.
+
+        Visit items and assign output to a default value.
+
+        Finally, compile a translation expression and use either
+        result or default.
+        """
+
+        body = []
+
+        # Track the blocks of this translation
+        self._translations.append(set())
+
+        # Prepare new stream
+        append = identifier("append", id(node))
+        stream = identifier("stream", id(node))
+        body += template("s = new_list", s=stream, new_list=LIST) + \
+                template("a = s.append", a=append, s=stream)
+
+        # Visit body to generate the message body
+        code = self.visit(node.node)
+        swap(ast.Suite(body=code), load(append), "__append")
+        body += code
+
+        # Reduce white space and assign as message id
+        msgid = identifier("msgid", id(node))
+        body += template(
+            "msgid = __re_whitespace(''.join(stream)).strip()",
+            msgid=msgid, stream=stream
+        )
+
+        default = msgid
+
+        # Compute translation block mapping if applicable
+        names = self._translations[-1]
+        if names:
+            keys = []
+            values = []
+
+            for name in names:
+                stream, append = self._get_translation_identifiers(name)
+                keys.append(ast.Str(s=name))
+                values.append(load(stream))
+
+                # Initialize value
+                body.insert(
+                    0, ast.Assign(
+                        targets=[store(stream)],
+                        value=ast.Str(s=native_string(""))))
+
+            mapping = ast.Dict(keys=keys, values=values)
+        else:
+            mapping = None
+
+        # if this translation node has a name, use it as the message id
+        if node.msgid:
+            msgid = ast.Str(s=node.msgid)
+
+        # emit the translation expression
+        body += template(
+            "__append(translate("
+            "msgid, mapping=mapping, default=default, domain=__i18n_domain))",
+            msgid=msgid, default=default, mapping=mapping
+            )
+
+        # pop away translation block reference
+        self._translations.pop()
+
+        return body
+
+    def visit_Start(self, node):
+        try:
+            line, column = node.prefix.location
+        except AttributeError:
+            line, column = 0, 0
+
+        yield Comment(
+            " %s%s ... (%d:%d)\n"
+            " --------------------------------------------------------" % (
+                node.prefix, node.name, line, column))
+
+        if node.attributes:
+            for stmt in emit_node(ast.Str(s=node.prefix + node.name)):
+                yield stmt
+
+            for attribute in node.attributes:
+                for stmt in self.visit(attribute):
+                    yield stmt
+
+            for stmt in emit_node(ast.Str(s=node.suffix)):
+                yield stmt
+        else:
+            for stmt in emit_node(
+                ast.Str(s=node.prefix + node.name + node.suffix)):
+                yield stmt
+
+    def visit_End(self, node):
+        for stmt in emit_node(ast.Str(
+            s=node.prefix + node.name + node.space + node.suffix)):
+            yield stmt
+
+    def visit_Attribute(self, node):
+        f = node.space + node.name + node.eq + node.quote + "%s" + node.quote
+
+        # Static attributes are just outputted directly
+        if isinstance(node.expression, ast.Str):
+            s = f % node.expression.s
+            return template("__append(S)", S=ast.Str(s=s))
+
+        target = identifier("attr", node.name)
+        body = self._engine(node.expression, store(target))
+        return body + template(
+            "if TARGET is not None: __append(FORMAT % TARGET)",
+            FORMAT=ast.Str(s=f),
+            TARGET=target,
+            )
+
+    def visit_Cache(self, node):
+        body = []
+
+        for expression in node.expressions:
+            name = identifier("cache", id(expression))
+            target = store(name)
+
+            # Skip re-evaluation
+            if self._expression_cache.get(expression):
+                continue
+
+            body += self._engine(expression, target)
+            self._expression_cache[expression] = target
+
+        body += self.visit(node.node)
+
+        return body
+
+    def visit_UseInternalMacro(self, node):
+        if node.name is None:
+            render = "render"
+        else:
+            render = "render_%s" % mangle(node.name)
+
+        return template(
+            "f(__stream, econtext.copy(), rcontext, __i18n_domain)",
+            f=render) + \
+            template("econtext.update(rcontext)")
+
+    def visit_DefineSlot(self, node):
+        name = "__slot_%s" % mangle(node.name)
+        self._slots.add(name)
+        body = self.visit(node.node)
+
+        orelse = template(
+            "SLOT(__stream, econtext.copy(), rcontext, __i18n_domain)",
+            SLOT=name)
+        test = ast.Compare(
+            left=load(name),
+            ops=[ast.Is()],
+            comparators=[load("None")]
+            )
+
+        return [
+            ast.If(test=test, body=body or [ast.Pass()], orelse=orelse)
+            ]
+
+    def visit_Name(self, node):
+        """Translation name."""
+
+        if not self._translations:
+            raise TranslationError(
+                "Not allowed outside of translation.", node.name)
+
+        if node.name in self._translations[-1]:
+            raise TranslationError(
+                "Duplicate translation name: %s." % node.name)
+
+        self._translations[-1].add(node.name)
+        body = []
+
+        # prepare new stream
+        stream, append = self._get_translation_identifiers(node.name)
+        body += template("s = new_list", s=stream, new_list=LIST) + \
+                template("a = s.append", a=append, s=stream)
+
+        # generate code
+        code = self.visit(node.node)
+        swap(ast.Suite(body=code), load(append), "__append")
+        body += code
+
+        # output msgid
+        text = Text('${%s}' % node.name)
+        body += self.visit(text)
+
+        # Concatenate stream
+        body += template("stream = ''.join(stream)", stream=stream)
+
+        return body
+
+    def visit_UseExternalMacro(self, node):
+        callbacks = []
+        for slot in node.slots:
+            key = "__slot_%s" % mangle(slot.name)
+            fun = "__fill_%s" % mangle(slot.name)
+
+            body = template("getitem = econtext.__getitem__") + \
+                   template("get = econtext.get") + \
+                   self.visit(slot.node)
+
+            callbacks.append(
+                ast.FunctionDef(
+                    name=fun,
+                    args=ast.arguments(
+                        args=[
+                            param("__stream"),
+                            param("econtext"),
+                            param("rcontext"),
+                            param("__i18n_domain"),
+                            ],
+                        defaults=[load("__i18n_domain")],
+                        ),
+                    body=body or [ast.Pass()],
+                ))
+
+            key = ast.Str(s=key)
+
+            if node.extend:
+                update_body = None
+            else:
+                update_body = template("_slots.append(NAME)", NAME=fun)
+
+            callbacks.append(
+                ast.TryExcept(
+                    body=template("_slots = getitem(KEY)", KEY=key),
+                    handlers=[ast.ExceptHandler(
+                        body=template(
+                            "_slots = econtext[KEY] = [NAME]",
+                            KEY=key, NAME=fun,
+                        ))],
+                    orelse=update_body
+                    ))
+
+        assignment = self._engine(node.expression, store("__macro"))
+
+        return (
+            callbacks + \
+            assignment + \
+            template(
+                "__macro.include(__stream, econtext.copy(), " \
+                "rcontext, __i18n_domain)") + \
+            template("econtext.update(rcontext)")
+            )
+
+    def visit_Repeat(self, node):
+        # Used for loop variable definition and restore
+        self._scopes.append(set())
+
+        # Variable assignment and repeat key for single- and
+        # multi-variable repeat clause
+        if node.local:
+            contexts = "econtext",
+        else:
+            contexts = "econtext", "rcontext"
+
+        for name in node.names:
+            if name in COMPILER_INTERNALS_OR_DISALLOWED:
+                raise TranslationError(
+                    "Name disallowed by compiler.", name
+                    )
+
+        if len(node.names) > 1:
+            targets = [
+                ast.Tuple(elts=[
+                    subscript(native_string(name), load(context), ast.Store())
+                    for name in node.names], ctx=ast.Store())
+                for context in contexts
+                ]
+
+            key = ast.Tuple(
+                elts=[ast.Str(s=name) for name in node.names],
+                ctx=ast.Load())
+        else:
+            name = node.names[0]
+            targets = [
+                subscript(native_string(name), load(context), ast.Store())
+                for context in contexts
+                ]
+
+            key = ast.Str(s=node.names[0])
+
+        index = identifier("__index", id(node))
+        assignment = [ast.Assign(targets=targets, value=load("__item"))]
+
+        # Make repeat assignment in outer loop
+        names = node.names
+        local = node.local
+
+        outer = self._engine(node.expression, store("__iterator"))
+
+        if local:
+            outer[:] = list(self._enter_assignment(names)) + outer
+
+        outer += template(
+            "__iterator, INDEX = getitem('repeat')(key, __iterator)",
+            key=key, INDEX=index
+            )
+
+        # Set a trivial default value for each name assigned to make
+        # sure we assign a value even if the iteration is empty
+        outer += [ast.Assign(
+            targets=[store_econtext(name)
+                     for name in node.names],
+            value=load("None"))
+              ]
+
+        # Compute inner body
+        inner = self.visit(node.node)
+
+        # After each iteration, decrease the index
+        inner += template("index -= 1", index=index)
+
+        # For items up to N - 1, emit repeat whitespace
+        inner += template(
+            "if INDEX > 0: __append(WHITESPACE)",
+            INDEX=index, WHITESPACE=ast.Str(s=node.whitespace)
+            )
+
+        # Main repeat loop
+        outer += [ast.For(
+            target=store("__item"),
+            iter=load("__iterator"),
+            body=assignment + inner,
+            )]
+
+        # Finally, clean up assignment if it's local
+        if outer:
+            outer += self._leave_assignment(names)
+
+        self._scopes.pop()
+
+        return outer
+
+    def _get_translation_identifiers(self, name):
+        assert self._translations
+        prefix = id(self._translations[-1])
+        stream = identifier("stream_%d" % prefix, name)
+        append = identifier("append_%d" % prefix, name)
+        return stream, append
+
+    def _enter_assignment(self, names):
+        for name in names:
+            for stmt in template(
+                "BACKUP = get(KEY, __marker)",
+                BACKUP=identifier("backup_%s" % name, id(names)),
+                KEY=ast.Str(s=native_string(name)),
+                ):
+                yield stmt
+
+    def _leave_assignment(self, names):
+        for name in names:
+            for stmt in template(
+                "if BACKUP is __marker: del econtext[KEY]\n"
+                "else:                 econtext[KEY] = BACKUP",
+                BACKUP=identifier("backup_%s" % name, id(names)),
+                KEY=ast.Str(s=native_string(name)),
+                ):
+                yield stmt
diff --git a/lib/chameleon/src/chameleon/config.py b/lib/chameleon/src/chameleon/config.py
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/config.py
@@ -0,0 +1,47 @@
+import os
+import logging
+
+log = logging.getLogger('chameleon.config')
+
+# Define which values are read as true
+TRUE = ('y', 'yes', 't', 'true', 'on', '1')
+
+# If eager parsing is enabled, templates are parsed upon
+# instantiation, rather than when first called upon; this mode is
+# useful for verifying validity of templates across a project
+EAGER_PARSING = os.environ.pop('CHAMELEON_EAGER', 'false')
+EAGER_PARSING = EAGER_PARSING.lower() in TRUE
+
+# Debug mode is mostly useful for debugging the template engine
+# itself. When enabled, generated source code is written to disk to
+# ease step-debugging and some log levels are lowered to increase
+# output. Also, the generated source code is available in the
+# ``source`` attribute of the template instance if compilation
+# succeeded.
+DEBUG_MODE = os.environ.pop('CHAMELEON_DEBUG', 'false')
+DEBUG_MODE = DEBUG_MODE.lower() in TRUE
+
+# If a cache directory is specified, template source code will be
+# persisted on disk and reloaded between sessions
+path = os.environ.pop('CHAMELEON_CACHE', None)
+if path is not None:
+    CACHE_DIRECTORY = os.path.abspath(path)
+    if not os.path.exists(CACHE_DIRECTORY):
+        raise ValueError(
+            "Cache directory does not exist: %s." % CACHE_DIRECTORY
+            )
+    log.info("directory cache: %s." % CACHE_DIRECTORY)
+else:
+    CACHE_DIRECTORY = None
+
+# When auto-reload is enabled, templates are reloaded on file change.
+AUTO_RELOAD = os.environ.pop('CHAMELEON_RELOAD', 'false')
+AUTO_RELOAD = AUTO_RELOAD.lower() in TRUE
+
+for key in os.environ:
+    if key.lower().startswith('chameleon'):
+        log.warn("unknown environment variable set: \"%s\"." % key)
+
+# This is the slice length of the expression displayed in the
+# formatted exception string
+SOURCE_EXPRESSION_MARKER_LENGTH = 60
diff --git a/lib/chameleon/src/chameleon/exc.py b/lib/chameleon/src/chameleon/exc.py
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/exc.py
@@ -0,0 +1,266 @@
+# -*- coding: utf-8 -*-
+
+import traceback
+
+from .utils import format_kwargs
+from .utils import safe_native
+from .tokenize import Token
+from .config import SOURCE_EXPRESSION_MARKER_LENGTH as LENGTH
+
+
+def compute_source_marker(line, column, expression, size):
+    """Computes source marker location string.
+
+    >>> def test(l, c, e, s):
+    ...     s, marker = compute_source_marker(l, c, e, s)
+    ...     out = s + '\\n' + marker
+    ...
+    ...     # Replace dot with middle-dot to work around doctest ellipsis
+    ...     print(out.replace('...', '&#183;&#183;&#183;'))
+
+    >>> test('foo bar', 4, 'bar', 7)
+    foo bar
+        ^^^
+
+    >>> test('foo ${bar}', 4, 'bar', 10)
+    foo ${bar}
+          ^^^
+
+    >>> test('  foo bar', 6, 'bar', 6)
+    &#183;&#183;&#183; oo bar
+           ^^^
+
+    >>> test('  foo bar baz  ', 6, 'bar', 6)
+    &#183;&#183;&#183; o bar &#183;&#183;&#183;
+          ^^^
+
+    The entire expression is always shown, even if ``size`` does not
+    accomodate for it.
+
+    >>> test('  foo bar baz  ', 6, 'bar baz', 10)
+    &#183;&#183;&#183; oo bar baz
+           ^^^^^^^
+
+    >>> test('      foo bar', 10, 'bar', 5)
+    &#183;&#183;&#183; o bar
+          ^^^
+
+    >>> test('      foo bar', 10, 'boo', 5)
+    &#183;&#183;&#183; o bar
+          ^
+
+    """
+
+    s = line.lstrip()
+    column -= len(line) - len(s)
+    s = s.rstrip()
+
+    try:
+        i  = s[column:].index(expression)
+    except ValueError:
+        # If we can't find the expression
+        # (this shouldn't happen), simply
+        # use a standard size marker
+        marker = "^"
+    else:
+        column += i
+        marker = "^" * len(expression)
+
+    if len(expression) > size:
+        offset = column
+        size = len(expression)
+    else:
+        window = (size - len(expression)) / 2.0
+        offset = column - window
+        offset -= min(3, max(0, column + window + len(expression) - len(s)))
+        offset = int(offset)
+
+    if offset > 0:
+        s = s[offset:]
+        r = s.lstrip()
+        d = len(s) - len(r)
+        s = "... " + r
+        column += 4 - d
+        column -= offset
+
+        # This also adds to the displayed length
+        size += 4
+
+    if len(s) > size:
+        s = s[:size].rstrip() + " ..."
+
+    return s, column * " " + marker
+
+
+def format_exception(exc):
+    formatted = traceback.format_exception_only(type(exc), exc)[-1]
+    formatted_class = "%s:" % type(exc).__name__
+
+    if formatted.startswith(formatted_class):
+        formatted = formatted[len(formatted_class):].lstrip()
+
+    return formatted
+
+
+class TemplateError(Exception):
+    """An error raised by Chameleon.
+
+    Make sure the exceptions can be copied:
+
+    >>> from copy import copy
+    >>> copy(TemplateError('message', 'token'))
+    TemplateError('message', 'token')
+
+    """
+
+    def __init__(self, msg, token):
+        if not isinstance(token, Token):
+            token = Token(token, 0)
+
+        self.msg = msg
+        self.token = token
+        self.filename = token.filename
+
+    def __copy__(self):
+        inst = Exception.__new__(type(self))
+        inst.__dict__ = self.__dict__.copy()
+        return inst
+
+    def __str__(self):
+        text = "%s\n\n" % self.msg
+        text += " - String:   \"%s\"" % self.token
+
+        if self.filename:
+            text += "\n"
+            text += " - Filename: %s" % self.filename
+
+        try:
+            line, column = self.token.location
+        except AttributeError:
+            pass
+        else:
+            text += "\n"
+            text += " - Location: (%d:%d)" % (line, column)
+
+        return text
+
+    def __repr__(self):
+        try:
+            return "%s('%s', '%s')" % (
+                self.__class__.__name__, self.msg, self.token
+                )
+        except AttributeError:
+            return object.__repr__(self)
+
+    @property
+    def offset(self):
+        return getattr(self.token, "pos", 0)
+
+
+class ParseError(TemplateError):
+    """An error occurred during parsing.
+
+    Indicates an error on the structural level.
+    """
+
+
+class CompilationError(TemplateError):
+    """An error occurred during compilation.
+
+    Indicates a general compilation error.
+    """
+
+
+class TranslationError(TemplateError):
+    """An error occurred during translation.
+
+    Indicates a general translation error.
+    """
+
+
+class LanguageError(CompilationError):
+    """Language syntax error.
+
+    Indicates a syntactical error due to incorrect usage of the
+    template language.
+    """
+
+
+class ExpressionError(LanguageError):
+    """An error occurred compiling an expression.
+
+    Indicates a syntactical error in an expression.
+    """
+
+
+class ExceptionFormatter(object):
+    def __init__(self, errors, econtext, rcontext):
+        kwargs = rcontext.copy()
+        kwargs.update(econtext)
+
+        for name in tuple(kwargs):
+            if name.startswith('__'):
+                del kwargs[name]
+
+        self._errors = errors
+        self._kwargs = kwargs
+
+    def __call__(self):
+        # Format keyword arguments; consecutive arguments are indented
+        # for readability
+        try:
+            formatted = format_kwargs(self._kwargs)
+        except:
+            # the ``pprint.pformat`` method calls the representation
+            # method of the arguments; this may fail and since we're
+            # already in an exception handler, there's no point in
+            # pursuing this further
+            formatted = ()
+
+        for index, string in enumerate(formatted[1:]):
+            formatted[index + 1] = " " * 15 + string
+
+        out = []
+
+        for error in self._errors:
+            expression, line, column, filename, exc = error
+
+            if isinstance(exc, UnicodeDecodeError):
+                string = safe_native(exc.object)
+
+                s, marker = compute_source_marker(
+                    string, exc.start, string[exc.start:exc.end], LENGTH
+                    )
+
+                out.append(" - Stream:     %s" % s)
+                out.append("               %s" % marker)
+
+            out.append(" - Expression: \"%s\"" % expression)
+            out.append(" - Filename:   %s" % (filename or "<string>"))
+            out.append(" - Location:   (%d:%d)" % (line, column))
+
+            if filename and line and column:
+                try:
+                    f = open(filename, 'r')
+                except IOError:
+                    pass
+                else:
+                    try:
+                        # Pick out source line and format marker
+                        for i, l in enumerate(f):
+                            if i + 1 == line:
+                                s, marker = compute_source_marker(
+                                    l, column, expression, LENGTH
+                                    )
+
+                                out.append("")
+                                out.append(" - Source:     %s" % s)
+                                out.append("               %s" % marker)
+                                break
+                    finally:
+                        f.close()
+
+        out.append(" - Arguments:  %s" % "\n".join(formatted))
+        formatted_exc = format_exception(exc)
+
+        return "\n".join(map(safe_native, [formatted_exc] + out))
diff --git a/lib/chameleon/src/chameleon/i18n.py b/lib/chameleon/src/chameleon/i18n.py
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/i18n.py
@@ -0,0 +1,96 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+
+from .exc import CompilationError
+from .namespaces import I18N_NS as ZOPE_I18N_NS
+
+WHITELIST = frozenset([
+    "translate",
+    "domain",
+    "target",
+    "source",
+    "attributes",
+    "data",
+    "name",
+    ])
+
+try:  # pragma: no cover
+    str = unicode
+except NameError:
+    pass
+
+try:  # pragma: no cover
+    # optional: `zope.i18n`, `zope.i18nmessageid`
+    from zope.i18n import interpolate
+    from zope.i18n import translate
+    from zope.i18nmessageid import Message
+except ImportError:   # pragma: no cover
+
+    def fast_translate(msgid, domain=None, mapping=None, context=None,
+                       target_language=None, default=None):
+        if default is None:
+            return msgid
+
+        return default
+else:   # pragma: no cover
+    def fast_translate(msgid, domain=None, mapping=None, context=None,
+                       target_language=None, default=None):
+        if msgid is None:
+            return
+
+        if target_language is not None:
+            result = translate(
+                msgid, domain=domain, mapping=mapping, context=context,
+                target_language=target_language, default=default)
+            if result != msgid:
+                return result
+
+        if isinstance(msgid, Message):
+            default = msgid.default
+            mapping = msgid.mapping
+
+        if default is None:
+            default = str(msgid)
+
+        if not isinstance(default, basestring):
+            return default
+
+        return interpolate(default, mapping)
+
+def parse_attributes(attrs, xml=True):
+    d = {}
+
+    # filter out empty items, eg:
+    # i18n:attributes="value msgid; name msgid2;"
+    # would result in 3 items where the last one is empty
+    attrs = [spec for spec in attrs.split(";") if spec]
+
+    for spec in attrs:
+        parts = spec.split()
+        if len(parts) == 2:
+            attr, msgid = parts
+        elif len(parts) == 1:
+            attr = parts[0]
+            msgid = None
+        else:
+            raise CompilationError(
+                "Illegal i18n:attributes specification.", spec)
+        if not xml:
+            attr = attr.lower()
+        if attr in d:
+            raise CompilationError(
+                "Attribute may only be specified once in i18n:attributes", attr)
+        d[attr] = msgid
+
+    return d
diff --git a/lib/chameleon/src/chameleon/interfaces.py b/lib/chameleon/src/chameleon/interfaces.py
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/interfaces.py
@@ -0,0 +1,102 @@
+from zope.interface import Interface
+from zope.interface import Attribute
+
+
+class ITALExpressionErrorInfo(Interface):
+
+    type = Attribute("type",
+                     "The exception class.")
+
+    value = Attribute("value",
+                      "The exception instance.")
+
+    lineno = Attribute("lineno",
+                       "The line number the error occurred on in the source.")
+
+    offset = Attribute("offset",
+                       "The character offset at which the error occurred.")
+
+
+class ITALIterator(Interface):  # pragma: no cover
+    """A TAL iterator
+
+    Not to be confused with a Python iterator.
+    """
+
+    def next():
+        """Advance to the next value in the iteration, if possible
+
+        Return a true value if it was possible to advance and return
+        a false value otherwise.
+        """
+
+
+class ITALESIterator(ITALIterator):  # pragma: no cover
+    """TAL Iterator provided by TALES
+
+    Values of this iterator are assigned to items in the repeat namespace.
+
+    For example, with a TAL statement like: tal:repeat="item items",
+    an iterator will be assigned to "repeat/item".  The iterator
+    provides a number of handy methods useful in writing TAL loops.
+
+    The results are undefined of calling any of the methods except
+    'length' before the first iteration.
+    """
+
+    def index():
+        """Return the position (starting with "0") within the iteration
+        """
+
+    def number():
+        """Return the position (starting with "1") within the iteration
+        """
+
+    def even():
+        """Return whether the current position is even
+        """
+
+    def odd():
+        """Return whether the current position is odd
+        """
+
+    def parity():
+        """Return 'odd' or 'even' depending on the position's parity
+
+        Useful for assigning CSS class names to table rows.
+        """
+
+    def start():
+        """Return whether the current position is the first position
+        """
+
+    def end():
+        """Return whether the current position is the last position
+        """
+
+    def letter():
+        """Return the position (starting with "a") within the iteration
+        """
+
+    def Letter():
+        """Return the position (starting with "A") within the iteration
+        """
+
+    def roman():
+        """Return the position (starting with "i") within the iteration
+        """
+
+    def Roman():
+        """Return the position (starting with "I") within the iteration
+        """
+
+    def item():
+        """Return the item at the current position
+        """
+
+    def length():
+        """Return the length of the sequence
+
+        Note that this may fail if the TAL iterator was created on a Python
+        iterator.
+        """
diff --git a/lib/chameleon/src/chameleon/loader.py b/lib/chameleon/src/chameleon/loader.py
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/loader.py
@@ -0,0 +1,145 @@
+import os
+import imp
+import sys
+import py_compile
+import logging
+import functools
+import tempfile
+
+log = logging.getLogger('chameleon.loader')
+
+try:
+    str = unicode
+except NameError:
+    basestring = str
+
+
+def cache(func):
+    def load(self, *args, **kwargs):
+        template = self.registry.get(args)
+        if template is None:
+            self.registry[args] = template = func(self, *args, **kwargs)
+        return template
+    return load
+
+
+class TemplateLoader(object):
+    """Template loader class.
+
+    To load templates using relative filenames, pass a sequence of
+    paths (or a single path) as ``search_path``.
+
+    To apply a default filename extension to inputs which do not have
+    an extension already (i.e. no dot), provide this as
+    ``default_extension`` (e.g. ``'.pt'``).
+
+    Additional keyword-arguments will be passed on to the template
+    constructor.
+    """
+
+    default_extension = None
+
+    def __init__(self, search_path=None, default_extension=None, **kwargs):
+        if search_path is None:
+            search_path = []
+        if isinstance(search_path, basestring):
+            search_path = [search_path]
+        if default_extension is not None:
+            self.default_extension = ".%s" % default_extension.lstrip('.')
+        self.search_path = search_path
+        self.registry = {}
+        self.kwargs = kwargs
+
+    @cache
+    def load(self, filename, cls=None):
+        if cls is None:
+            raise ValueError("Unbound template loader.")
+
+        if self.default_extension is not None and '.' not in filename:
+            filename += self.default_extension
+
+        if os.path.isabs(filename):
+            return cls(filename, **self.kwargs)
+
+        for path in self.search_path:
+            path = os.path.join(path, filename)
+            if os.path.exists(path):
+                return cls(path, **self.kwargs)
+
+        raise ValueError("Template not found: %s." % filename)
+
+    def bind(self, cls):
+        return functools.partial(self.load, cls=cls)
+
+
+class MemoryLoader(object):
+    def build(self, source, filename):
+        code = compile(source, filename, 'exec')
+        env = {}
+        exec(code, env)
+        return env
+
+    def get(self, name):
+        return None
+
+
+class ModuleLoader(object):
+    def __init__(self, path):
+        self.path = path
+
+    def get(self, filename):
+        path = os.path.join(self.path, filename)
+        if os.path.exists(path):
+            log.debug("loading module from cache: %s." % filename)
+            base, ext = os.path.splitext(filename)
+            return self._load(base, path)
+        else:
+            log.debug('cache miss: %s' % filename)
+
+    def build(self, source, filename):
+        imp.acquire_lock()
+        try:
+            d = self.get(filename)
+            if d is not None:
+                return d
+
+            base, ext = os.path.splitext(filename)
+            name = os.path.join(self.path, base + ".py")
+
+            log.debug("writing source to disk (%d bytes)." % len(source))
+            fd, fn = tempfile.mkstemp(prefix=base, suffix='.tmp', dir=self.path)
+            temp = os.fdopen(fd, 'w')
+
+            try:
+                try:
+                    temp.write("%s\n" % '# -*- coding: utf-8 -*-')
+                    temp.write(source)
+                finally:
+                    temp.close()
+            except:
+                os.remove(fn)
+                raise
+
+            os.rename(fn, name)
+            log.debug("compiling %s into byte-code..." % filename)
+            py_compile.compile(name)
+
+            return self._load(base, name)
+        finally:
+            imp.release_lock()
+
+    def _load(self, base, filename):
+        imp.acquire_lock()
+        try:
+            module = sys.modules.get(base)
+            if module is None:
+                f = open(filename, 'rb')
+                try:
+                    assert base not in sys.modules
+                    module = imp.load_source(base, filename, f)
+                finally:
+                    f.close()
+        finally:
+            imp.release_lock()
+
+        return module.__dict__
diff --git a/lib/chameleon/src/chameleon/metal.py b/lib/chameleon/src/chameleon/metal.py
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/metal.py
@@ -0,0 +1,21 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+
+WHITELIST = frozenset([
+    "define-macro",
+    "extend-macro",
+    "use-macro",
+    "define-slot",
+    "fill-slot",
+    ])
diff --git a/lib/chameleon/src/chameleon/namespaces.py b/lib/chameleon/src/chameleon/namespaces.py
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/namespaces.py
@@ -0,0 +1,9 @@
+XML_NS = "http://www.w3.org/XML/1998/namespace"
+XMLNS_NS = "http://www.w3.org/2000/xmlns/"
+XHTML_NS = "http://www.w3.org/1999/xhtml"
+TAL_NS = "http://xml.zope.org/namespaces/tal"
+META_NS = "http://xml.zope.org/namespaces/meta"
+METAL_NS = "http://xml.zope.org/namespaces/metal"
+XI_NS = "http://www.w3.org/2001/XInclude"
+I18N_NS = "http://xml.zope.org/namespaces/i18n"
+PY_NS = "http://genshi.edgewall.org/"
diff --git a/lib/chameleon/src/chameleon/nodes.py b/lib/chameleon/src/chameleon/nodes.py
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/nodes.py
@@ -0,0 +1,206 @@
+from .astutil import Node
+
+
+class UseExternalMacro(Node):
+    """Extend external macro."""
+
+    _fields = "expression", "slots", "extend"
+
+
+class Sequence(Node):
+    """Element sequence."""
+
+    _fields = "items",
+
+
+class Content(Node):
+    """Content substitution."""
+
+    _fields = "expression", "char_escape", "translate"
+
+
+class Default(Node):
+    """Represents a default value."""
+
+    _fields = "marker",
+
+
+class Value(Node):
+    """Expression object value."""
+
+    _fields = "value",
+
+    def __repr__(self):
+        try:
+            line, column = self.value.location
+        except AttributeError:
+            line, column = 0, 0
+
+        return "<%s %r (%d:%d)>" % (
+            type(self).__name__, self.value, line, column
+            )
+
+
+class Substitution(Value):
+    """Expression value for text substitution."""
+
+    _fields = "value", "char_escape", "default"
+
+    default = None
+
+
+class Boolean(Value):
+    _fields = "value", "s"
+
+
+class Negate(Node):
+    """Wraps an expression with a negation."""
+
+    _fields = "value",
+
+
+class Element(Node):
+    """XML element."""
+
+    _fields = "start", "end", "content"
+
+
+class Attribute(Node):
+    """Element attribute."""
+
+    _fields = "name", "expression", "quote", "eq", "space"
+
+
+class Start(Node):
+    """Start-tag."""
+
+    _fields = "name", "prefix", "suffix", "attributes"
+
+
+class End(Node):
+    """End-tag."""
+
+    _fields = "name", "space", "prefix", "suffix"
+
+
+class Condition(Node):
+    """Node visited only if some condition holds."""
+
+    _fields = "expression", "node", "orelse"
+
+
+class Identity(Node):
+    """Condition expression that is true on identity."""
+
+    _fields = "expression", "value"
+
+
+class Equality(Node):
+    """Condition expression that is true on equality."""
+
+    _fields = "expression", "value"
+
+
+class Cache(Node):
+    """Cache (evaluate only once) the value of ``expression`` inside
+    ``node``.
+    """
+
+    _fields = "expressions", "node"
+
+
+class Assignment(Node):
+    """Variable assignment."""
+
+    _fields = "names", "expression", "local"
+
+
+class Alias(Assignment):
+    """Alias assignment.
+
+    Note that ``expression`` should be a cached or global value.
+    """
+
+    local = False
+
+
+class Define(Node):
+    """Variable definition in scope."""
+
+    _fields = "assignments", "node"
+
+
+class Repeat(Assignment):
+    """Iterate over provided assignment and repeat body."""
+
+    _fields = "names", "expression", "local", "whitespace", "node"
+
+
+class Macro(Node):
+    """Macro definition."""
+
+    _fields = "name", "body"
+
+
+class Program(Node):
+    _fields = "name", "body"
+
+
+class Module(Node):
+    _fields = "name", "program",
+
+
+class Context(Node):
+    _fields = "node",
+
+
+class Text(Node):
+    """Static text output."""
+
+    _fields = "value",
+
+
+class Interpolation(Text):
+    """String interpolation output."""
+
+    _fields = "value", "braces_required"
+
+
+class Translate(Node):
+    """Translate node."""
+
+    _fields = "msgid", "node"
+
+
+class Name(Node):
+    """Translation name."""
+
+    _fields = "name", "node"
+
+
+class Domain(Node):
+    """Update translation domain."""
+
+    _fields = "name", "node"
+
+
+class OnError(Node):
+    _fields = "fallback", "name", "node"
+
+
+class UseInternalMacro(Node):
+    """Use internal macro (defined inside same program)."""
+
+    _fields = "name",
+
+
+class FillSlot(Node):
+    """Fill a macro slot."""
+
+    _fields = "name", "node"
+
+
+class DefineSlot(Node):
+    """Define a macro slot."""
+
+    _fields = "name", "node"
diff --git a/lib/chameleon/src/chameleon/parser.py b/lib/chameleon/src/chameleon/parser.py
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/parser.py
@@ -0,0 +1,231 @@
+import re
+import logging
+
+try:
+    from collections import OrderedDict
+except ImportError:
+    from ordereddict import OrderedDict
+
+from .exc import ParseError
+from .namespaces import XML_NS
+from .tokenize import Token
+
+match_tag_prefix_and_name = re.compile(
+    r'^(?P<prefix></?)(?P<name>([^:\n ]+:)?[^ \n\t>/]+)'
+    '(?P<suffix>(?P<space>\s*)/?>)?',
+    re.UNICODE | re.DOTALL)
+match_single_attribute = re.compile(
+    r'(?P<space>\s+)(?!\d)'
+    r'(?P<name>[^ =/>\n\t]+)'
+    r'((?P<eq>\s*=\s*)'
+    r'((?P<quote>[\'"])(?P<value>.*?)(?P=quote)|'
+    r'(?P<alt_value>[^\s\'">/]+))|'
+    r'(?P<simple_value>(?![ \\n\\t\\r]*=)))',
+    re.UNICODE | re.DOTALL)
+match_comment = re.compile(
+    r'^<!--(?P<text>.*)-->$', re.DOTALL)
+match_cdata = re.compile(
+    r'^<!\[CDATA\[(?P<text>.*)\]>$', re.DOTALL)
+match_declaration = re.compile(
+    r'^<!(?P<text>[^>]+)>$', re.DOTALL)
+match_processing_instruction = re.compile(
+    r'^<\?(?P<text>.*?)\?>', re.DOTALL)
+match_xml_declaration = re.compile(r'^<\?xml(?=[ /])', re.DOTALL)
+
+log = logging.getLogger('chameleon.parser')
+
+
+def substitute(regex, repl, token):
+    if not isinstance(token, Token):
+        token = Token(token)
+
+    return Token(
+        regex.sub(repl, token),
+        token.pos,
+        token.source,
+        token.filename
+        )
+
+
+def groups(m, token):
+    result = []
+    for i, group in enumerate(m.groups()):
+        if group is not None:
+            j, k = m.span(i + 1)
+            group = token[j:k]
+
+        result.append(group)
+
+    return tuple(result)
+
+
+def groupdict(m, token):
+    d = m.groupdict()
+    for name, value in d.items():
+        if value is not None:
+            i, j = m.span(name)
+            d[name] = token[i:j]
+
+    return d
+
+
+def match_tag(token, regex=match_tag_prefix_and_name):
+    m = regex.match(token)
+    d = groupdict(m, token)
+
+    end = m.end()
+    token = token[end:]
+
+    attrs = d['attrs'] = []
+    for m in match_single_attribute.finditer(token):
+        attr = groupdict(m, token)
+        alt_value = attr.pop('alt_value', None)
+        if alt_value is not None:
+            attr['value'] = alt_value
+            attr['quote'] = ''
+        simple_value = attr.pop('simple_value', None)
+        if simple_value is not None:
+            attr['quote'] = ''
+            attr['value'] = ''
+            attr['eq'] = ''
+        attrs.append(attr)
+        d['suffix'] = token[m.end():]
+
+    return d
+
+
+def parse_tag(token, namespace):
+    node = match_tag(token)
+
+    update_namespace(node['attrs'], namespace)
+
+    if ':' in node['name']:
+        prefix = node['name'].split(':')[0]
+    else:
+        prefix = None
+
+    default = node['namespace'] = namespace.get(prefix, XML_NS)
+
+    node['ns_attrs'] = unpack_attributes(
+        node['attrs'], namespace, default)
+
+    return node
+
+
+def update_namespace(attributes, namespace):
+    # possibly update namespaces; we do this in a separate step
+    # because this assignment is irrespective of order
+    for attribute in attributes:
+        name = attribute['name']
+        value = attribute['value']
+        if name == 'xmlns':
+            namespace[None] = value
+        elif name.startswith('xmlns:'):
+            namespace[name[6:]] = value
+
+
+def unpack_attributes(attributes, namespace, default):
+    namespaced = OrderedDict()
+
+    for index, attribute in enumerate(attributes):
+        name = attribute['name']
+        value = attribute['value']
+
+        if ':' in name:
+            prefix = name.split(':')[0]
+            name = name[len(prefix) + 1:]
+            try:
+                ns = namespace[prefix]
+            except KeyError:
+                raise KeyError(
+                    "Undefined namespace prefix: %s." % prefix)
+        else:
+            ns = default
+        namespaced[ns, name] = value
+
+    return namespaced
+
+
+def identify(string):
+    if string.startswith("<"):
+        if string.startswith("<!--"):
+            return "comment"
+        if string.startswith("<![CDATA["):
+            return "cdata"
+        if string.startswith("<!"):
+            return "declaration"
+        if string.startswith("<?xml"):
+            return "xml_declaration"
+        if string.startswith("<?"):
+            return "processing_instruction"
+        if string.startswith("</"):
+            return "end_tag"
+        if string.endswith("/>"):
+            return "empty_tag"
+        if string.endswith(">"):
+            return "start_tag"
+        return "error"
+    return "text"
+
+
+class ElementParser(object):
+    """Parses tokens into elements."""
+
+    def __init__(self, stream, default_namespaces):
+        self.stream = stream
+        self.queue = []
+        self.index = []
+        self.namespaces = [default_namespaces.copy()]
+
+    def __iter__(self):
+        for token in self.stream:
+            item = self.parse(token)
+            self.queue.append(item)
+
+        return iter(self.queue)
+
+    def parse(self, token):
+        kind = identify(token)
+        visitor = getattr(self, "visit_%s" % kind, self.visit_default)
+        return visitor(kind, token)
+
+    def visit_comment(self, kind, token):
+        return "comment", (token, )
+
+    def visit_default(self, kind, token):
+        return "default", (token, )
+
+    def visit_text(self, kind, token):
+        return kind, (token, )
+
+    def visit_start_tag(self, kind, token):
+        namespace = self.namespaces[-1].copy()
+        self.namespaces.append(namespace)
+        node = parse_tag(token, namespace)
+        self.index.append((node['name'], len(self.queue)))
+        return kind, (node, )
+
+    def visit_end_tag(self, kind, token):
+        try:
+            namespace = self.namespaces.pop()
+        except IndexError:
+            raise ParseError("Unexpected end tag.", token)
+
+        node = parse_tag(token, namespace)
+
+        while self.index:
+            name, pos = self.index.pop()
+            if name == node['name']:
+                start, = self.queue.pop(pos)[1]
+                children = self.queue[pos:]
+                del self.queue[pos:]
+                break
+        else:
+            raise ParseError("Unexpected end tag.", token)
+
+        return "element", (start, node, children)
+
+    def visit_empty_tag(self, kind, token):
+        namespace = self.namespaces[-1].copy()
+        node = parse_tag(token, namespace)
+        return "element", (node, None, [])
diff --git a/lib/chameleon/src/chameleon/program.py b/lib/chameleon/src/chameleon/program.py
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/program.py
@@ -0,0 +1,38 @@
+try:
+    str = unicode
+except NameError:
+    long = int
+
+from .tokenize import iter_xml
+from .tokenize import iter_text
+from .parser import ElementParser
+from .namespaces import XML_NS
+from .namespaces import XMLNS_NS
+
+
+class ElementProgram(object):
+    DEFAULT_NAMESPACES = {
+        'xmlns': XMLNS_NS,
+        'xml': XML_NS,
+        }
+
+    tokenizers = {
+        'xml': iter_xml,
+        'text': iter_text,
+        }
+
+    def __init__(self, source, mode="xml", filename=None):
+        tokenizer = self.tokenizers[mode]
+        tokens = tokenizer(source, filename)
+        parser = ElementParser(tokens, self.DEFAULT_NAMESPACES)
+
+        self.body = []
+
+        for kind, args in parser:
+            node = self.visit(kind, args)
+            if node is not None:
+                self.body.append(node)
+
+    def visit(self, kind, args):
+        visitor = getattr(self, "visit_%s" % kind)
+        return visitor(*args)
diff --git a/lib/chameleon/src/chameleon/py25.py b/lib/chameleon/src/chameleon/py25.py
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/py25.py
@@ -0,0 +1,20 @@
+def lookup_attr(obj, key):
+    try:
+        return getattr(obj, key)
+    except AttributeError, exc:
+        try:
+            get = obj.__getitem__
+        except AttributeError:
+            raise exc
+        try:
+            return get(key)
+        except KeyError:
+            raise exc
+
+
+def raise_with_traceback(exc, tb):
+    raise type(exc), exc, tb
+
+
+def next(iter):
+    return iter.next()
diff --git a/lib/chameleon/src/chameleon/py26.py b/lib/chameleon/src/chameleon/py26.py
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/py26.py
@@ -0,0 +1,12 @@
+def lookup_attr(obj, key):
+    try:
+        return getattr(obj, key)
+    except AttributeError as exc:
+        try:
+            get = obj.__getitem__
+        except AttributeError:
+            raise exc
+        try:
+            return get(key)
+        except KeyError:
+            raise exc
diff --git a/lib/chameleon/src/chameleon/tal.py b/lib/chameleon/src/chameleon/tal.py
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/tal.py
@@ -0,0 +1,429 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+
+import re
+import copy
+
+from .exc import LanguageError
+from .utils import descriptorint
+from .utils import descriptorstr
+from .namespaces import XMLNS_NS
+from .parser import groups
+
+
+try:
+    next
+except NameError:
+    from chameleon.py25 import next
+
+try:
+    # optional library: `zope.interface`
+    import interfaces
+    import zope.interface
+except ImportError:
+    interfaces = None
+
+
+NAME = r"[a-zA-Z_][-a-zA-Z0-9_]*"
+DEFINE_RE = re.compile(r"(?s)\s*(?:(global|local)\s+)?" +
+                       r"(%s|\(%s(?:,\s*%s)*\))\s+(.*)\Z" % (NAME, NAME, NAME))
+SUBST_RE = re.compile(r"\s*(?:(text|structure)\s+)?(.*)\Z", re.S)
+ATTR_RE = re.compile(r"\s*([^\s]+)\s+([^\s].*)\Z", re.S)
+
+ENTITY_RE = re.compile(r'(&(#?)(x?)(\d{1,5}|\w{1,8});)')
+
+WHITELIST = frozenset([
+    "define",
+    "comment",
+    "condition",
+    "content",
+    "replace",
+    "repeat",
+    "attributes",
+    "on-error",
+    "omit-tag",
+    "script",
+    "switch",
+    "case",
+    ])
+
+
+def split_parts(arg):
+    # Break in pieces at undoubled semicolons and
+    # change double semicolons to singles:
+    i = 0
+    while i < len(arg):
+        m = ENTITY_RE.search(arg[i:])
+        if m is None:
+            break
+        arg = arg[:i + m.end()] + ';' + arg[i + m.end():]
+        i += m.end()
+
+    arg = arg.replace(";;", "\0")
+    parts = arg.split(';')
+    parts = [p.replace("\0", ";") for p in parts]
+    if len(parts) > 1 and not parts[-1].strip():
+        del parts[-1]  # It ended in a semicolon
+
+    return parts
+
+
+def parse_attributes(clause):
+    attrs = {}
+    for part in split_parts(clause):
+        m = ATTR_RE.match(part)
+        if not m:
+            raise LanguageError(
+                "Bad syntax in attributes.", clause)
+        name, expr = groups(m, part)
+        if name in attrs:
+            raise LanguageError(
+                "Duplicate attribute name in attributes.", part)
+
+        attrs[name] = expr
+
+    return attrs
+
+
+def parse_substitution(clause):
+    m = SUBST_RE.match(clause)
+    if m is None:
+        raise LanguageError(
+            "Invalid content substitution syntax.", clause)
+
+    key, expression = groups(m, clause)
+    if not key:
+        key = "text"
+
+    return key, expression
+
+
+def parse_defines(clause):
+    defines = []
+    for part in split_parts(clause):
+        m = DEFINE_RE.match(part)
+        if m is None:
+            return
+        context, name, expr = groups(m, part)
+        context = context or "local"
+
+        if name.startswith('('):
+            names = [n.strip() for n in name.strip('()').split(',')]
+        else:
+            names = (name,)
+
+        defines.append((context, names, expr))
+
+    return defines
+
+
+def prepare_attributes(attrs, dyn_attributes, ns_attributes, drop_ns):
+    drop = set([attribute['name'] for attribute, (ns, value)
+                in zip(attrs, ns_attributes)
+                if ns in drop_ns or (
+                    ns == XMLNS_NS and
+                    attribute['value'] in drop_ns
+                    )
+                ])
+
+    attributes = []
+    normalized = {}
+
+    for attribute in attrs:
+        name = attribute['name']
+
+        if name in drop:
+            continue
+
+        attributes.append((
+            name,
+            attribute['value'],
+            attribute['quote'],
+            attribute['space'],
+            attribute['eq'],
+            None,
+            ))
+
+        normalized[name.lower()] = len(attributes) - 1
+
+    for name, expr in dyn_attributes.items():
+        index = normalized.get(name.lower())
+        if index is not None:
+            _, text, quote, space, eq, _ = attributes[index]
+            add = attributes.__setitem__
+        else:
+            text = None
+            quote = '"'
+            space = " "
+            eq = "="
+            index = len(attributes)
+            add = attributes.insert
+
+        attribute = name, text, quote, space, eq, expr
+        add(index, attribute)
+
+    return attributes
+
+
+class RepeatItem(object):
+    if interfaces is not None:
+        zope.interface.implements(interfaces.ITALESIterator)
+
+    __slots__ = "length", "_iterator"
+
+    def __init__(self, iterator, length):
+        self.length = length
+        self._iterator = iterator
+
+    def __iter__(self):
+        return self._iterator
+
+    try:
+        iter(()).__len__
+    except AttributeError:
+        @property
+        def index(self):
+            try:
+                remaining = self._iterator.__length_hint__()
+            except AttributeError:
+                remaining = len(tuple(copy.copy(self._iterator)))
+            return self.length - remaining - 1
+    else:
+        @property
+        def index(self):
+            remaining = self._iterator.__len__()
+            return self.length - remaining - 1
+
+    @property
+    def start(self):
+        return self.index == 0
+
+    @property
+    def end(self):
+        return self.index == self.length - 1
+
+    @descriptorint
+    def number(self):
+        return self.index + 1
+
+    @descriptorstr
+    def odd(self):
+        """Returns a true value if the item index is odd.
+
+        >>> it = RepeatItem(iter(("apple", "pear")), 2)
+
+        >>> next(it._iterator)
+        'apple'
+        >>> it.odd()
+        ''
+
+        >>> next(it._iterator)
+        'pear'
+        >>> it.odd()
+        'odd'
+        """
+
+        return self.index % 2 == 1 and 'odd' or ''
+
+    @descriptorstr
+    def even(self):
+        """Returns a true value if the item index is even.
+
+        >>> it = RepeatItem(iter(("apple", "pear")), 2)
+
+        >>> next(it._iterator)
+        'apple'
+        >>> it.even()
+        'even'
+
+        >>> next(it._iterator)
+        'pear'
+        >>> it.even()
+        ''
+        """
+
+        return self.index % 2 == 0 and 'even' or ''
+
+    def next(self):
+        raise NotImplementedError(
+            "Method not implemented (can't update local variable).")
+
+    def _letter(self, base=ord('a'), radix=26):
+        """Get the iterator position as a lower-case letter
+
+        >>> it = RepeatItem(iter(("apple", "pear", "orange")), 3)
+        >>> next(it._iterator)
+        'apple'
+        >>> it.letter()
+        'a'
+        >>> next(it._iterator)
+        'pear'
+        >>> it.letter()
+        'b'
+        >>> next(it._iterator)
+        'orange'
+        >>> it.letter()
+        'c'
+        """
+
+        index = self.index
+        if index < 0:
+            raise TypeError("No iteration position")
+        s = ""
+        while 1:
+            index, off = divmod(index, radix)
+            s = chr(base + off) + s
+            if not index:
+                return s
+
+    letter = descriptorstr(_letter)
+
+    @descriptorstr
+    def Letter(self):
+        """Get the iterator position as an upper-case letter
+
+        >>> it = RepeatItem(iter(("apple", "pear", "orange")), 3)
+        >>> next(it._iterator)
+        'apple'
+        >>> it.Letter()
+        'A'
+        >>> next(it._iterator)
+        'pear'
+        >>> it.Letter()
+        'B'
+        >>> next(it._iterator)
+        'orange'
+        >>> it.Letter()
+        'C'
+        """
+
+        return self._letter(base=ord('A'))
+
+    @descriptorstr
+    def Roman(self, rnvalues=(
+                    (1000, 'M'), (900, 'CM'), (500, 'D'), (400, 'CD'),
+                    (100, 'C'), (90, 'XC'), (50, 'L'), (40, 'XL'),
+                    (10, 'X'), (9, 'IX'), (5, 'V'), (4, 'IV'), (1, 'I'))):
+        """Get the iterator position as an upper-case roman numeral
+
+        >>> it = RepeatItem(iter(("apple", "pear", "orange")), 3)
+        >>> next(it._iterator)
+        'apple'
+        >>> it.Roman()
+        'I'
+        >>> next(it._iterator)
+        'pear'
+        >>> it.Roman()
+        'II'
+        >>> next(it._iterator)
+        'orange'
+        >>> it.Roman()
+        'III'
+        """
+
+        n = self.index + 1
+        s = ""
+        for v, r in rnvalues:
+            rct, n = divmod(n, v)
+            s = s + r * rct
+        return s
+
+    @descriptorstr
+    def roman(self):
+        """Get the iterator position as a lower-case roman numeral
+
+        >>> it = RepeatItem(iter(("apple", "pear", "orange")), 3)
+        >>> next(it._iterator)
+        'apple'
+        >>> it.roman()
+        'i'
+        >>> next(it._iterator)
+        'pear'
+        >>> it.roman()
+        'ii'
+        >>> next(it._iterator)
+        'orange'
+        >>> it.roman()
+        'iii'
+        """
+
+        return self.Roman().lower()
+
+
+class RepeatDict(dict):
+    """Repeat dictionary implementation.
+
+    >>> repeat = RepeatDict({})
+    >>> iterator, length = repeat('numbers', range(5))
+    >>> length
+    5
+
+    >>> repeat['numbers']
+    <chameleon.tal.RepeatItem object at ...>
+
+    """
+
+    __slots__ = "__setitem__", "__getitem__", "__getattr__"
+
+    def __init__(self, d):
+        self.__setitem__ = d.__setitem__
+        self.__getitem__ = d.__getitem__
+        self.__getattr__ = d.__getitem__
+
+    def __call__(self, key, iterable):
+        """We coerce the iterable to a tuple and return an iterator
+        after registering it in the repeat dictionary."""
+
+        try:
+            iterable = tuple(iterable)
+        except TypeError:
+            if iterable is None:
+                iterable = ()
+            else:
+                # The message below to the TypeError is the Python
+                # 2.5-style exception message. Python 2.4.X also
+                # raises a TypeError, but with a different message.
+                # ("TypeError: iteration over non-sequence").  The
+                # Python 2.5 error message is more helpful.  We
+                # construct the 2.5-style message explicitly here so
+                # that both Python 2.4.X and Python 2.5+ will raise
+                # the same error.  This makes writing the tests eaiser
+                # and makes the output easier to understand.
+                raise TypeError("%r object is not iterable" %
+                                type(iterable).__name__)
+
+        length = len(iterable)
+        iterator = iter(iterable)
+
+        # Insert as repeat item
+        self[key] = RepeatItem(iterator, length)
+
+        return iterator, length
+
+
+class ErrorInfo(object):
+    """Information about an exception passed to an on-error handler."""
+
+    if interfaces is not None:
+        zope.interface.implements(interfaces.ITALExpressionErrorInfo)
+
+    def __init__(self, err, position=(None, None)):
+        if isinstance(err, Exception):
+            self.type = err.__class__
+            self.value = err
+        else:
+            self.type = err
+            self.value = None
+        self.lineno = position[0]
+        self.offset = position[1]
diff --git a/lib/chameleon/src/chameleon/tales.py b/lib/chameleon/src/chameleon/tales.py
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/tales.py
@@ -0,0 +1,634 @@
+import re
+import sys
+
+try:
+    import ast
+except ImportError:
+    from chameleon import ast24 as ast
+
+from .astutil import parse
+from .astutil import store
+from .astutil import load
+from .astutil import ItemLookupOnAttributeErrorVisitor
+from .codegen import TemplateCodeGenerator
+from .codegen import template
+from .codegen import reverse_builtin_map
+from .astutil import Builtin
+from .astutil import Symbol
+from .exc import ExpressionError
+from .utils import resolve_dotted
+from .utils import Markup
+from .tokenize import Token
+from .parser import groupdict
+from .parser import substitute
+
+try:
+    from .py26 import lookup_attr
+except SyntaxError:
+    from .py25 import lookup_attr
+
+
+split_parts = re.compile(r'(?<!\\)\|')
+match_prefix = re.compile(r'^\s*([a-z\-_]+):').match
+re_continuation = re.compile(r'\\\s*$', re.MULTILINE)
+
+try:
+    from __builtin__ import basestring
+except ImportError:
+    basestring = str
+
+
+def resolve_global(value):
+    name = reverse_builtin_map.get(value)
+    if name is not None:
+        return Builtin(name)
+
+    return Symbol(value)
+
+
+def test(expression, engine=None, **env):
+    if engine is None:
+        engine = SimpleEngine()
+
+    body = expression(store("result"), engine)
+    module = ast.Module(body)
+    module = ast.fix_missing_locations(module)
+    env['rcontext'] = {}
+    source = TemplateCodeGenerator(module).code
+    code = compile(source, '<string>', 'exec')
+    exec(code, env)
+    result = env["result"]
+
+    if isinstance(result, basestring):
+        result = str(result)
+
+    return result
+
+
+def transform_attribute(node):
+    return template(
+        "lookup(object, name)",
+        lookup=Symbol(lookup_attr),
+        object=node.value,
+        name=ast.Str(s=node.attr),
+        mode="eval"
+        )
+
+
+class TalesExpr(object):
+    """Base class.
+
+    This class helps implementations for the Template Attribute
+    Language Expression Syntax (TALES).
+
+    The syntax evaluates one or more expressions, separated by '|'
+    (pipe). The first expression that succeeds, is returned.
+
+    Expression:
+
+      expression    := (type ':')? line ('|' expression)?
+      line          := .*
+
+    Expression lines may not contain the pipe character unless
+    escaped. It has a special meaning:
+
+    If the expression to the left of the pipe fails (raises one of the
+    exceptions listed in ``catch_exceptions``), evaluation proceeds to
+    the expression(s) on the right.
+
+    Subclasses must implement ``translate`` which assigns a value for
+    a given expression.
+
+    >>> class PythonPipeExpr(TalesExpr):
+    ...     def translate(self, expression, target):
+    ...         compiler = PythonExpr(expression)
+    ...         return compiler(target, None)
+
+    >>> test(PythonPipeExpr('foo | bar | 42'))
+    42
+
+    >>> test(PythonPipeExpr('foo|42'))
+    42
+    """
+
+    exceptions = NameError, \
+                 ValueError, \
+                 AttributeError, \
+                 LookupError, \
+                 TypeError
+
+    def __init__(self, expression):
+        self.expression = expression
+
+    def __call__(self, target, engine):
+        remaining = self.expression
+        assignments = []
+
+        while remaining:
+            if match_prefix(remaining) is not None:
+                compiler = engine.parse(remaining)
+                assignment = compiler.assign_value(target)
+                remaining = ""
+            else:
+                for m in split_parts.finditer(remaining):
+                    expression = remaining[:m.start()]
+                    remaining = remaining[m.end():]
+                    break
+                else:
+                    expression = remaining
+                    remaining = ""
+
+                expression = expression.replace('\\|', '|')
+                assignment = self.translate(expression, target)
+            assignments.append(assignment)
+
+        if not assignments:
+            assignments.append(
+                self.translate(remaining, target)
+                )
+
+        for i, assignment in enumerate(reversed(assignments)):
+            if i == 0:
+                body = assignment
+            else:
+                body = [ast.TryExcept(
+                    body=assignment,
+                    handlers=[ast.ExceptHandler(
+                        type=ast.Tuple(
+                            elts=map(resolve_global, self.exceptions),
+                            ctx=ast.Load()),
+                        name=None,
+                        body=body,
+                        )],
+                    )]
+
+        return body
+
+    def translate(self, expression, target):
+        """Return statements that assign a value to ``target``."""
+
+        raise NotImplementedError(
+            "Must be implemented by a subclass.")
+
+
+class PathExpr(TalesExpr):
+    """Path expression compiler.
+
+    Syntax::
+
+        PathExpr ::= Path [ '|' Path ]*
+        Path ::= variable [ '/' URL_Segment ]*
+        variable ::= Name
+
+    For example::
+
+        request/cookies/oatmeal
+        nothing
+        here/some-file 2001_02.html.tar.gz/foo
+        root/to/branch | default
+
+    When a path expression is evaluated, it attempts to traverse
+    each path, from left to right, until it succeeds or runs out of
+    paths. To traverse a path, it first fetches the object stored in
+    the variable. For each path segment, it traverses from the current
+    object to the subobject named by the path segment.
+
+    Once a path has been successfully traversed, the resulting object
+    is the value of the expression. If it is a callable object, such
+    as a method or class, it is called.
+
+    The semantics of traversal (and what it means to be callable) are
+    implementation-dependent (see the ``translate`` method).
+    """
+
+    def translate(self, expression, target):
+        raise NotImplementedError(
+            "Path expressions are not yet implemented. "
+            "It's unclear whether a general implementation "
+            "can be devised.")
+
+
+class PythonExpr(TalesExpr):
+    """Python expression compiler.
+
+    >>> test(PythonExpr('2 + 2'))
+    4
+
+    The Python expression is a TALES expression. That means we can use
+    the pipe operator:
+
+    >>> test(PythonExpr('foo | 2 + 2 | 5'))
+    4
+
+    To include a pipe character, use a backslash escape sequence:
+
+    >>> test(PythonExpr('\"\|\"'))
+    '|'
+    """
+
+    transform = ItemLookupOnAttributeErrorVisitor(transform_attribute)
+
+    def parse(self, string):
+        return parse(string, 'eval').body
+
+    def translate(self, expression, target):
+        # Strip spaces
+        string = expression.strip()
+
+        # Conver line continuations to newlines
+        string = substitute(re_continuation, '\n', string)
+
+        # Convert newlines to spaces
+        string = string.replace('\n', ' ')
+
+        try:
+            value = self.parse(string)
+        except SyntaxError:
+            exc = sys.exc_info()[1]
+            raise ExpressionError(exc.msg, string)
+
+        # Transform attribute lookups to allow fallback to item lookup
+        self.transform.visit(value)
+
+        return [ast.Assign(targets=[target], value=value)]
+
+
+class ProxyExpr(TalesExpr):
+    def __init__(self, name, *args):
+        super(ProxyExpr, self).__init__(*args)
+        self.name = name
+
+    def translate(self, expression, target):
+        path = expression.strip()
+        return [ast.Assign(targets=[target], value=ast.Call(
+            func=load(self.name),
+            args=[ast.Str(s=path)],
+            keywords=[],
+            starargs=None,
+            kwargs=None
+            ))]
+
+
+class ImportExpr(object):
+    re_dotted = re.compile(r'^[A-Za-z.]+$')
+
+    def __init__(self, expression):
+        self.expression = expression
+
+    def __call__(self, target, engine):
+        string = self.expression.strip().replace('\n', ' ')
+        value = template(
+            "RESOLVE(NAME)",
+            RESOLVE=Symbol(resolve_dotted),
+            NAME=ast.Str(s=string),
+            mode="eval",
+            )
+        return [ast.Assign(targets=[target], value=value)]
+
+
+class NotExpr(object):
+    """Negates the expression.
+
+    >>> engine = SimpleEngine(PythonExpr)
+
+    >>> test(NotExpr('False'), engine)
+    True
+    >>> test(NotExpr('True'), engine)
+    False
+    """
+
+    def __init__(self, expression):
+        self.expression = expression
+
+    def __call__(self, target, engine):
+        compiler = engine.parse(self.expression)
+        body = compiler.assign_value(target)
+        return body + template("target = not target", target=target)
+
+
+class StructureExpr(object):
+    """Wraps the expression result as 'structure'.
+
+    >>> engine = SimpleEngine(PythonExpr)
+
+    >>> test(StructureExpr('\"<tt>foo</tt>\"'), engine)
+    s'<tt>foo</tt>'
+    """
+
+    wrapper_class = Symbol(Markup)
+
+    def __init__(self, expression):
+        self.expression = expression
+
+    def __call__(self, target, engine):
+        compiler = engine.parse(self.expression)
+        body = compiler.assign_value(target)
+        return body + template(
+            "target = wrapper(target)",
+            target=target,
+            wrapper=self.wrapper_class
+            )
+
+
+class IdentityExpr(object):
+    """Identity expression.
+
+    Exists to demonstrate the interface.
+
+    >>> test(IdentityExpr('42'))
+    42
+    """
+
+    def __init__(self, expression):
+        self.expression = expression
+
+    def __call__(self, target, engine):
+        compiler = engine.parse(self.expression)
+        return compiler.assign_value(target)
+
+
+class StringExpr(object):
+    """Similar to the built-in ``string.Template``, but uses an
+    expression engine to support pluggable string substitution
+    expressions.
+
+    Expr string:
+
+      string       := (text | substitution) (string)?
+      substitution := ('$' variable | '${' expression '}')
+      text         := .*
+
+    In other words, an expression string can contain multiple
+    substitutions. The text- and substitution parts will be
+    concatenated back into a string.
+
+    >>> test(StringExpr('Hello ${name}!'), name='world')
+    'Hello world!'
+
+    In the default configuration, braces may be omitted if the
+    expression is an identifier.
+
+    >>> test(StringExpr('Hello $name!'), name='world')
+    'Hello world!'
+
+    The ``braces_required`` flag changes this setting:
+
+    >>> test(StringExpr('Hello $name!', True))
+    'Hello $name!'
+
+    We can escape interpolation using the standard escaping
+    syntax:
+
+    >>> test(StringExpr('\\${name}'))
+    '\\\${name}'
+
+    Multiple interpolations in one:
+
+    >>> test(StringExpr(\"Hello ${'a'}${'b'}${'c'}!\"))
+    'Hello abc!'
+
+    Here's a more involved example taken from a javascript source:
+
+    >>> result = test(StringExpr(\"\"\"
+    ... function(oid) {
+    ...     $('#' + oid).autocomplete({source: ${'source'}});
+    ... }
+    ... \"\"\"))
+
+    >>> 'source: source' in result
+    True
+
+    In the above examples, the expression is evaluated using the
+    dummy engine which just returns the input as a string.
+
+    As an example, we'll implement an expression engine which
+    instead counts the number of characters in the expresion and
+    returns an integer result.
+
+    >>> class engine:
+    ...     @staticmethod
+    ...     def parse(expression):
+    ...         class compiler:
+    ...             @staticmethod
+    ...             def assign_text(target):
+    ...                 return [
+    ...                     ast.Assign(
+    ...                         targets=[target],
+    ...                         value=ast.Num(n=len(expression))
+    ...                     )]
+    ...
+    ...         return compiler
+
+    This will demonstrate how the string expression coerces the
+    input to a string.
+
+    >>> expr = StringExpr(
+    ...    'There are ${hello world} characters in \"hello world\"')
+
+    We evaluate the expression using the new engine:
+
+    >>> test(expr, engine)
+    'There are 11 characters in \"hello world\"'
+    """
+
+    braces_required_regex = re.compile(
+        r'(?<!\\)\$({(?P<expression>.*)})')
+
+    braces_optional_regex = re.compile(
+        r'(?<!\\)\$({(?P<expression>.*)}|(?P<variable>[A-Za-z][A-Za-z0-9_]*))')
+
+    def __init__(self, expression, braces_required=False):
+        # The code relies on the expression being a token string
+        if not isinstance(expression, Token):
+            expression = Token(expression, 0)
+
+        self.expression = expression
+        self.regex = self.braces_required_regex if braces_required else \
+                     self.braces_optional_regex
+
+    def __call__(self, name, engine):
+        """The strategy is to find possible expression strings and
+        call the ``validate`` function of the parser to validate.
+
+        For every possible starting point, the longest possible
+        expression is tried first, then the second longest and so
+        forth.
+
+        Example 1:
+
+          ${'expressions use the ${<expression>} format'}
+
+        The entire expression is attempted first and it is also the
+        only one that validates.
+
+        Example 2:
+
+          ${'Hello'} ${'world!'}
+
+        Validation of the longest possible expression (the entire
+        string) will fail, while the second round of attempts,
+        ``${'Hello'}`` and ``${'world!'}`` respectively, validate.
+
+        """
+
+        body = []
+        nodes = []
+        text = self.expression
+
+        while text:
+            matched = text
+            m = self.regex.search(matched)
+            if m is None:
+                nodes.append(ast.Str(s=text))
+                break
+
+            part = text[:m.start()]
+            text = text[m.start():]
+
+            if part:
+                node = ast.Str(s=part)
+                nodes.append(node)
+
+            if not body:
+                target = name
+            else:
+                target = store("%s_%d" % (name.id, text.pos))
+
+            while True:
+                d = groupdict(m, matched)
+                string = d["expression"] or d["variable"] or ""
+
+                try:
+                    compiler = engine.parse(string)
+                    body += compiler.assign_text(target)
+                except ExpressionError:
+                    matched = matched[m.start():m.end() - 1]
+                    m = self.regex.search(matched)
+                    if m is None:
+                        raise
+                else:
+                    break
+
+            # if this is the first expression, use the provided
+            # assignment name; otherwise, generate one (here based
+            # on the string position)
+            node = load(target.id)
+            nodes.append(node)
+            text = text[len(m.group()):]
+
+        if len(nodes) == 1:
+            target = nodes[0]
+        else:
+            nodes = [
+                template("NODE if NODE is not None else ''", NODE=node, mode="eval")
+                for node in nodes
+                ]
+
+            target = ast.BinOp(
+                left=ast.Str(s="%s" * len(nodes)),
+                op=ast.Mod(),
+                right=ast.Tuple(elts=nodes, ctx=ast.Load()))
+
+        body += [ast.Assign(targets=[name], value=target)]
+        return body
+
+
+class ExistsExpr(object):
+    """Boolean wrapper.
+
+    Return 0 if the expression results in an exception, otherwise 1.
+
+    As a means to generate exceptions, we set up an expression engine
+    which evaluates the provided expression using Python:
+
+    >>> engine = SimpleEngine(PythonExpr)
+
+    >>> test(ExistsExpr('int(0)'), engine)
+    1
+    >>> test(ExistsExpr('int(None)'), engine)
+    0
+
+    """
+
+    exceptions = AttributeError, LookupError, TypeError, NameError, KeyError
+
+    def __init__(self, expression):
+        self.expression = expression
+
+    def __call__(self, target, engine):
+        ignore = store("_ignore")
+        compiler = engine.parse(self.expression)
+        body = compiler.assign_value(ignore)
+
+        classes = map(resolve_global, self.exceptions)
+
+        return [
+            ast.TryExcept(
+                body=body,
+                handlers=[ast.ExceptHandler(
+                    type=ast.Tuple(elts=classes, ctx=ast.Load()),
+                    name=None,
+                    body=template("target = 0", target=target),
+                    )],
+                orelse=template("target = 1", target=target)
+                )
+            ]
+
+
+class ExpressionParser(object):
+    def __init__(self, factories, default):
+        self.factories = factories
+        self.default = default
+
+    def __call__(self, expression):
+        m = match_prefix(expression)
+        if m is not None:
+            prefix = m.group(1)
+            expression = expression[m.end():]
+        else:
+            prefix = self.default
+
+        try:
+            factory = self.factories[prefix]
+        except KeyError:
+            exc = sys.exc_info()[1]
+            raise LookupError(
+                "Unknown expression type: %s." % str(exc)
+                )
+
+        return factory(expression)
+
+
+class SimpleEngine(object):
+    expression = PythonExpr
+
+    def __init__(self, expression=None):
+        if expression is not None:
+            self.expression = expression
+
+    def parse(self, string):
+        compiler = self.expression(string)
+        return SimpleCompiler(compiler, self)
+
+
+class SimpleCompiler(object):
+    def __init__(self, compiler, engine):
+        self.compiler = compiler
+        self.engine = engine
+
+    def assign_text(self, target):
+        """Assign expression string as a text value."""
+
+        return self._assign_value_and_coerce(target, "str")
+
+    def assign_value(self, target):
+        """Assign expression string as object value."""
+
+        return self.compiler(target, self.engine)
+
+    def _assign_value_and_coerce(self, target, builtin):
+        return self.assign_value(target) + template(
+            "target = builtin(target)",
+            target=target,
+            builtin=builtin
+            )
diff --git a/lib/chameleon/src/chameleon/template.py b/lib/chameleon/src/chameleon/template.py
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/template.py
@@ -0,0 +1,325 @@
+from __future__ import with_statement
+
+import os
+import sys
+import copy
+import atexit
+import hashlib
+import shutil
+import logging
+import tempfile
+import inspect
+
+pkg_digest = hashlib.sha1(__name__.encode('utf-8'))
+
+try:
+    import pkg_resources
+except ImportError:
+    logging.info("Setuptools not installed. Unable to determine version.")
+else:
+    for path in sys.path:
+        for distribution in pkg_resources.find_distributions(path):
+            if distribution.has_version():
+                version = distribution.version.encode('utf-8')
+                pkg_digest.update(version)
+
+
+from .exc import TemplateError
+from .exc import ExceptionFormatter
+from .compiler import Compiler
+from .config import DEBUG_MODE
+from .config import AUTO_RELOAD
+from .config import EAGER_PARSING
+from .config import CACHE_DIRECTORY
+from .loader import ModuleLoader
+from .loader import MemoryLoader
+from .nodes import Module
+from .utils import DebuggingOutputStream
+from .utils import Scope
+from .utils import join
+from .utils import mangle
+from .utils import derive_formatted_exception
+from .utils import read_bytes
+from .utils import raise_with_traceback
+from .utils import byte_string
+
+
+log = logging.getLogger('chameleon.template')
+
+
+def _make_module_loader():
+    if CACHE_DIRECTORY:
+        path = CACHE_DIRECTORY
+    else:
+        path = tempfile.mkdtemp()
+
+        @atexit.register
+        def cleanup(path=path, shutil=shutil):
+            shutil.rmtree(path)
+
+    return ModuleLoader(path)
+
+
+class BaseTemplate(object):
+    """Template base class.
+
+    Takes a string input which must be one of the following:
+
+    - a unicode string (or string on Python 3);
+    - a utf-8 encoded byte string;
+    - a byte string for an XML document that defines an encoding
+      in the document premamble;
+    - an HTML document that specifies the encoding via the META tag.
+
+    Note that the template input is decoded, parsed and compiled on
+    initialization.
+    """
+
+    default_encoding = "utf-8"
+
+    # This attribute is strictly informational in this template class
+    # and is used in exception formatting. It may be set on
+    # initialization using the optional ``filename`` keyword argument.
+    filename = '<string>'
+
+    _cooked = False
+
+    if DEBUG_MODE or CACHE_DIRECTORY:
+        loader = _make_module_loader()
+    else:
+        loader = MemoryLoader()
+
+    if DEBUG_MODE:
+        output_stream_factory = DebuggingOutputStream
+    else:
+        output_stream_factory = list
+
+    debug = DEBUG_MODE
+
+    # The ``builtins`` dictionary can be used by a template class to
+    # add symbols which may not be redefined and which are (cheaply)
+    # available in the template variable scope
+    builtins = {}
+
+    # The ``builtins`` dictionary is updated with this dictionary at
+    # cook time. Note that it can be provided at class initialization
+    # using the ``extra_builtins`` keyword argument.
+    extra_builtins = {}
+
+    # Expression engine must be provided by subclass
+    engine = None
+
+    def __init__(self, body, **config):
+        self.__dict__.update(config)
+
+        if isinstance(body, byte_string):
+            body, encoding, content_type = read_bytes(
+                body, self.default_encoding
+                )
+        else:
+            content_type = body.startswith('<?xml')
+            encoding = None
+
+        self.content_type = content_type
+        self.content_encoding = encoding
+
+        self.cook(body)
+
+        if self.__dict__.get('debug') is True:
+            self.loader = _make_module_loader()
+
+    def __call__(self, **kwargs):
+        return self.render(**kwargs)
+
+    def __repr__(self):
+        return "<%s %s>" % (self.__class__.__name__, self.filename)
+
+    @property
+    def keep_body(self):
+        # By default, we only save the template body if we're
+        # in debugging mode (to save memory).
+        return self.__dict__.get('keep_body', DEBUG_MODE)
+
+    @property
+    def keep_source(self):
+        # By default, we only save the generated source code if we're
+        # in debugging mode (to save memory).
+        return self.__dict__.get('keep_source', DEBUG_MODE)
+
+    def cook(self, body):
+        digest = self._digest(body)
+        builtins_dict = self.builtins.copy()
+        builtins_dict.update(self.extra_builtins)
+        names, builtins = zip(*builtins_dict.items())
+        program = self._cook(body, digest, names)
+
+        initialize = program['initialize']
+        functions = initialize(*builtins)
+
+        for name, function in functions.items():
+            setattr(self, "_" + name, function)
+
+        self._cooked = True
+
+        if self.keep_body:
+            self.body = body
+
+    def cook_check(self):
+        assert self._cooked
+
+    def parse(self, body):
+        raise NotImplementedError("Must be implemented by subclass.")
+
+    def render(self, **__kw):
+        econtext = Scope(__kw)
+        rcontext = {}
+        self.cook_check()
+        stream = self.output_stream_factory()
+        try:
+            self._render(stream, econtext, rcontext)
+        except:
+            cls, exc, tb = sys.exc_info()
+            errors = rcontext.get('__error__')
+            if errors:
+                try:
+                    exc = copy.copy(exc)
+                except TypeError:
+                    name = type(exc).__name__
+                    log.warn("Unable to copy exception of type '%s'." % name)
+                else:
+                    formatter = ExceptionFormatter(errors, econtext, rcontext)
+                    exc = derive_formatted_exception(exc, cls, formatter)
+
+                raise_with_traceback(exc, tb)
+
+            raise
+
+        return join(stream)
+
+    def _get_module_name(self, digest):
+        return "%s.py" % digest
+
+    def _cook(self, body, digest, builtins):
+        name = self._get_module_name(digest)
+        cooked = self.loader.get(name)
+        if cooked is None:
+            try:
+                source = self._make(body, builtins)
+                if self.debug:
+                    source = "# template: %s\n#\n%s" % (self.filename, source)
+                if self.keep_source:
+                    self.source = source
+                cooked = self.loader.build(source, name)
+            except TemplateError:
+                exc = sys.exc_info()[1]
+                exc.filename = self.filename
+                raise
+        elif self.keep_source:
+            module = sys.modules.get(cooked.get('__name__'))
+            if module is not None:
+                self.source = inspect.getsource(module)
+            else:
+                self.source = None
+
+        return cooked
+
+    def _digest(self, body):
+        class_name = type(self).__name__.encode('utf-8')
+        sha = pkg_digest.copy()
+        sha.update(body.encode('utf-8', 'ignore'))
+        sha.update(class_name)
+        return sha.hexdigest()
+
+    def _compile(self, program, builtins):
+        compiler = Compiler(self.engine, program, builtins)
+        return compiler.code
+
+    def _make(self, body, builtins):
+        program = self.parse(body)
+        module = Module("initialize", program)
+        return self._compile(module, builtins)
+
+
+class BaseTemplateFile(BaseTemplate):
+    """File-based template base class.
+
+    Relative path names are supported only when a template loader is
+    provided as the ``loader`` parameter.
+    """
+
+    # Auto reload is not enabled by default because it's a significant
+    # performance hit
+    auto_reload = AUTO_RELOAD
+
+    def __init__(self, filename, auto_reload=None, **config):
+        # Normalize filename
+        filename = os.path.abspath(
+            os.path.normpath(os.path.expanduser(filename))
+            )
+
+        self.filename = filename
+
+        # Override reload setting only if value is provided explicitly
+        if auto_reload is not None:
+            self.auto_reload = auto_reload
+
+        self.__dict__.update(config)
+
+        # This is only necessary if the ``debug`` flag was passed as a
+        # keyword argument
+        if config.get('debug') is True:
+            self.loader = _make_module_loader()
+
+        if EAGER_PARSING:
+            self.cook_check()
+
+    def cook_check(self):
+        if self.auto_reload:
+            mtime = self.mtime()
+
+            if mtime != self._v_last_read:
+                self._v_last_read = mtime
+                self._cooked = False
+
+        if self._cooked is False:
+            body = self.read()
+            self.cook(body)
+
+    def mtime(self):
+        try:
+            return os.path.getmtime(self.filename)
+        except (IOError, OSError):
+            return 0
+
+    def read(self):
+        with open(self.filename, "rb") as f:
+            data = f.read()
+
+        body, encoding, content_type = read_bytes(
+            data, self.default_encoding
+            )
+
+        # In non-XML mode, we support various platform-specific line
+        # endings and convert them to the UNIX newline character
+        if content_type != "text/xml" and '\r' in body:
+            body = body.replace('\r\n', '\n').replace('\r', '\n')
+
+        self.content_type = content_type
+        self.content_encoding = encoding
+
+        return body
+
+    def _get_module_name(self, digest):
+        filename = os.path.basename(self.filename)
+        mangled = mangle(filename)
+        return "%s_%s.py" % (mangled, digest)
+
+    def _get_filename(self):
+        return self.__dict__.get('filename')
+
+    def _set_filename(self, filename):
+        self.__dict__['filename'] = filename
+        self._v_last_read = None
+        self._cooked = False
+
+    filename = property(_get_filename, _set_filename)
diff --git a/lib/chameleon/src/chameleon/tests/__init__.py b/lib/chameleon/src/chameleon/tests/__init__.py
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/tests/__init__.py
@@ -0,0 +1,1 @@
+#
diff --git a/lib/chameleon/src/chameleon/tests/inputs/001-interpolation.txt b/lib/chameleon/src/chameleon/tests/inputs/001-interpolation.txt
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/tests/inputs/001-interpolation.txt
@@ -0,0 +1,1 @@
+${'<Hello world>'}<&>
diff --git a/lib/chameleon/src/chameleon/tests/inputs/001-variable-scope.html b/lib/chameleon/src/chameleon/tests/inputs/001-variable-scope.html
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/tests/inputs/001-variable-scope.html
@@ -0,0 +1,7 @@
+<html>
+  <body py:with="text 'Hello world!'">
+    ${text}
+    $text
+  </body>
+  ${text | 'Goodbye world!'}
+</html>
diff --git a/lib/chameleon/src/chameleon/tests/inputs/001-variable-scope.pt b/lib/chameleon/src/chameleon/tests/inputs/001-variable-scope.pt
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/tests/inputs/001-variable-scope.pt
@@ -0,0 +1,11 @@
+<html>
+  <body tal:define="text 'Hello world!'">
+    ${text}
+  </body>
+  <tal:check condition="exists: text">
+    bad
+  </tal:check>
+  <tal:check condition="not: exists: text">
+    ok
+  </tal:check>
+</html>
diff --git a/lib/chameleon/src/chameleon/tests/inputs/001.xml b/lib/chameleon/src/chameleon/tests/inputs/001.xml
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/tests/inputs/001.xml
@@ -0,0 +1,4 @@
+<!DOCTYPE doc [
+<!ELEMENT doc (#PCDATA)>
+]>
+<doc></doc>
diff --git a/lib/chameleon/src/chameleon/tests/inputs/002-repeat-scope.pt b/lib/chameleon/src/chameleon/tests/inputs/002-repeat-scope.pt
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/tests/inputs/002-repeat-scope.pt
@@ -0,0 +1,8 @@
+<html>
+  <body>
+    <div tal:repeat="text ('Hello', 'Goodbye')">
+      <span tal:repeat="char ('!', '.')">${text}${char}</span>
+    </div>
+    <tal:check condition="not: exists: text">ok</tal:check>
+  </body>
+</html>
diff --git a/lib/chameleon/src/chameleon/tests/inputs/002.xml b/lib/chameleon/src/chameleon/tests/inputs/002.xml
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/tests/inputs/002.xml
@@ -0,0 +1,4 @@
+<!DOCTYPE doc [
+<!ELEMENT doc (#PCDATA)>
+]>
+<doc ></doc>
diff --git a/lib/chameleon/src/chameleon/tests/inputs/003-content.pt b/lib/chameleon/src/chameleon/tests/inputs/003-content.pt
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/tests/inputs/003-content.pt
@@ -0,0 +1,17 @@
+<html>
+  <body>
+    <div tal:content="'Hello world!'" />
+    <div tal:content="'Hello world!'" />1
+   2<div tal:content="'Hello world!'" />
+    <div tal:content="'Hello world!'" />3
+    <div tal:content="'Hello world!'">4</div>5
+   6<div tal:content="'Hello world!'"></div>
+    <div tal:content="1" />
+    <div tal:content="1.0" />
+    <div tal:content="True" />
+    <div tal:content="False" />
+    <div tal:content="0" />
+    <div tal:content="None" />
+    <div tal:replace="content" />
+  </body>
+</html>
diff --git a/lib/chameleon/src/chameleon/tests/inputs/003.xml b/lib/chameleon/src/chameleon/tests/inputs/003.xml
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/tests/inputs/003.xml
@@ -0,0 +1,4 @@
+<!DOCTYPE doc [
+<!ELEMENT doc (#PCDATA)>
+]>
+<doc></doc >
diff --git a/lib/chameleon/src/chameleon/tests/inputs/004-attributes.pt b/lib/chameleon/src/chameleon/tests/inputs/004-attributes.pt
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/tests/inputs/004-attributes.pt
@@ -0,0 +1,18 @@
+<html>
+  <body>
+    <span tal:attributes="class 'hello'" />
+    <span class="goodbye" tal:attributes="class 'hello'" />
+    <span CLASS="goodbye" tal:attributes="class 'hello'" />
+    <span tal:attributes="class None" />
+    <span a="1" b="2" c="3" tal:attributes="a None" />
+    <span a="1" b="2" c="3" tal:attributes="b None" />
+    <span a="1" b="2" c="3" tal:attributes="c None" />
+    <span a="1" b="2" c="3" tal:attributes="b None; c None" />
+    <span a="1" b="2" c="3" tal:attributes="b string:;;" />
+    <span a="1" b="2" c="3" tal:attributes="b string:&amp;" />
+    <span class="hello" tal:attributes="class 'goodbye'" />
+    <span class="hello" tal:attributes="class '&quot;goodbye&quot;'" />
+    <span class="hello" tal:attributes="class '\'goodbye\''" />
+    <span class='hello' tal:attributes="class '\'goodbye\''" />
+  </body>
+</html>
diff --git a/lib/chameleon/src/chameleon/tests/inputs/004.xml b/lib/chameleon/src/chameleon/tests/inputs/004.xml
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/tests/inputs/004.xml
@@ -0,0 +1,5 @@
+<!DOCTYPE doc [
+<!ELEMENT doc (#PCDATA)>
+<!ATTLIST doc a1 CDATA #IMPLIED>
+]>
+<doc a1="v1"></doc>
diff --git a/lib/chameleon/src/chameleon/tests/inputs/005-default.pt b/lib/chameleon/src/chameleon/tests/inputs/005-default.pt
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/tests/inputs/005-default.pt
@@ -0,0 +1,12 @@
+<html>
+  <body>
+    <img class="default" tal:attributes="class default" />
+    <img tal:attributes="class default" />
+    <span tal:content="default">Default</span>
+    <span tal:content="True">Default</span>
+    <span tal:content="False">Default</span>
+    <span tal:content="default">
+      <em>${'Computed default'}</em>
+    </span>
+  </body>
+</html>
diff --git a/lib/chameleon/src/chameleon/tests/inputs/005.xml b/lib/chameleon/src/chameleon/tests/inputs/005.xml
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/tests/inputs/005.xml
@@ -0,0 +1,5 @@
+<!DOCTYPE doc [
+<!ELEMENT doc (#PCDATA)>
+<!ATTLIST doc a1 CDATA #IMPLIED>
+]>
+<doc a1 = "v1"></doc>
diff --git a/lib/chameleon/src/chameleon/tests/inputs/006-attribute-interpolation.pt b/lib/chameleon/src/chameleon/tests/inputs/006-attribute-interpolation.pt
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/tests/inputs/006-attribute-interpolation.pt
@@ -0,0 +1,9 @@
+<html>
+  <body class="ltr" tal:define="hash string:#">
+    <img src="${'#'}" alt="copyright (c) ${2010}" />
+    <img src="" alt="copyright (c) ${2010}" tal:attributes="src string:$hash" />
+    <img src="" alt="copyright (c) ${2010}" tal:attributes="src string:${hash}" />
+    <img src="${None}" alt="$ignored" />
+    <img src="" alt="${'%stype \'str\'%s' % (chr(60), chr(62))}" />
+  </body>
+</html>
diff --git a/lib/chameleon/src/chameleon/tests/inputs/006.xml b/lib/chameleon/src/chameleon/tests/inputs/006.xml
new file mode 100644
--- /dev/null
+++ b/lib/chameleon/src/chameleon/tests/inputs/006.xml
@@ -0,0 +1,5 @@
+<!DOCTYPE doc [
+<!ELEMENT doc (#PCDATA)>
+<!ATTLIST doc a1 CDATA #IMPLIED>
+]>
+<doc a1='v1'></doc>
diff --git a/lib/chameleon/src/chameleon/tests/inputs/007-content-interpolation.pt b/lib/chameleon/src/chameleon/tests/inputs/007-content-interpolation.pt
new file mode 100644


More information about the pypy-commit mailing list