Removed a few stdlib dependencies.  This is the first step for IronPython support, the second one being a new lexer.

--HG--
branch : trunk
diff --git a/jinja2/compiler.py b/jinja2/compiler.py
index f1e182b..fab6ea0 100644
--- a/jinja2/compiler.py
+++ b/jinja2/compiler.py
@@ -8,14 +8,12 @@
     :copyright: Copyright 2008 by Armin Ronacher.
     :license: BSD.
 """
-from copy import copy
-from keyword import iskeyword
 from cStringIO import StringIO
 from itertools import chain
 from jinja2 import nodes
 from jinja2.visitor import NodeVisitor, NodeTransformer
 from jinja2.exceptions import TemplateAssertionError
-from jinja2.utils import Markup, concat, escape
+from jinja2.utils import Markup, concat, escape, is_python_keyword
 
 
 operators = {
@@ -164,8 +162,10 @@
 
     def copy(self):
         """Create a copy of the current one."""
-        rv = copy(self)
-        rv.identifiers = copy(self.identifiers)
+        rv = object.__new__(self.__class__)
+        rv.__dict__.update(self.__dict__)
+        rv.identifiers = object.__new__(self.identifiers.__class__)
+        rv.identifiers.__dict__.update(self.identifiers.__dict__)
         return rv
 
     def inspect(self, nodes, hard_scope=False):
@@ -186,10 +186,12 @@
         standalone thing as it shares the resources with the frame it
         was created of, but it's not a rootlevel frame any longer.
         """
-        rv = copy(self)
+        rv = self.copy()
         rv.rootlevel = False
         return rv
 
+    __copy__ = copy
+
 
 class VisitorExit(RuntimeError):
     """Exception used by the `UndeclaredNameVisitor` to signal a stop."""
@@ -449,7 +451,7 @@
         # we have to make sure that no invalid call is created.
         kwarg_workaround = False
         for kwarg in chain((x.key for x in node.kwargs), extra_kwargs or ()):
-            if iskeyword(kwarg):
+            if is_python_keyword(kwarg):
                 kwarg_workaround = True
                 break
 
diff --git a/jinja2/constants.py b/jinja2/constants.py
index a0b4a63..c471e79 100644
--- a/jinja2/constants.py
+++ b/jinja2/constants.py
@@ -30,3 +30,261 @@
 tempus tincidunt torquent tortor tristique turpis ullamcorper ultrices
 ultricies urna ut varius vehicula vel velit venenatis vestibulum vitae vivamus
 viverra volutpat vulputate'''
+
+
+#: a dict of all html entities + apos
+HTML_ENTITIES = {
+    'AElig': 198,
+    'Aacute': 193,
+    'Acirc': 194,
+    'Agrave': 192,
+    'Alpha': 913,
+    'Aring': 197,
+    'Atilde': 195,
+    'Auml': 196,
+    'Beta': 914,
+    'Ccedil': 199,
+    'Chi': 935,
+    'Dagger': 8225,
+    'Delta': 916,
+    'ETH': 208,
+    'Eacute': 201,
+    'Ecirc': 202,
+    'Egrave': 200,
+    'Epsilon': 917,
+    'Eta': 919,
+    'Euml': 203,
+    'Gamma': 915,
+    'Iacute': 205,
+    'Icirc': 206,
+    'Igrave': 204,
+    'Iota': 921,
+    'Iuml': 207,
+    'Kappa': 922,
+    'Lambda': 923,
+    'Mu': 924,
+    'Ntilde': 209,
+    'Nu': 925,
+    'OElig': 338,
+    'Oacute': 211,
+    'Ocirc': 212,
+    'Ograve': 210,
+    'Omega': 937,
+    'Omicron': 927,
+    'Oslash': 216,
+    'Otilde': 213,
+    'Ouml': 214,
+    'Phi': 934,
+    'Pi': 928,
+    'Prime': 8243,
+    'Psi': 936,
+    'Rho': 929,
+    'Scaron': 352,
+    'Sigma': 931,
+    'THORN': 222,
+    'Tau': 932,
+    'Theta': 920,
+    'Uacute': 218,
+    'Ucirc': 219,
+    'Ugrave': 217,
+    'Upsilon': 933,
+    'Uuml': 220,
+    'Xi': 926,
+    'Yacute': 221,
+    'Yuml': 376,
+    'Zeta': 918,
+    'aacute': 225,
+    'acirc': 226,
+    'acute': 180,
+    'aelig': 230,
+    'agrave': 224,
+    'alefsym': 8501,
+    'alpha': 945,
+    'amp': 38,
+    'and': 8743,
+    'ang': 8736,
+    'apos': 39,
+    'aring': 229,
+    'asymp': 8776,
+    'atilde': 227,
+    'auml': 228,
+    'bdquo': 8222,
+    'beta': 946,
+    'brvbar': 166,
+    'bull': 8226,
+    'cap': 8745,
+    'ccedil': 231,
+    'cedil': 184,
+    'cent': 162,
+    'chi': 967,
+    'circ': 710,
+    'clubs': 9827,
+    'cong': 8773,
+    'copy': 169,
+    'crarr': 8629,
+    'cup': 8746,
+    'curren': 164,
+    'dArr': 8659,
+    'dagger': 8224,
+    'darr': 8595,
+    'deg': 176,
+    'delta': 948,
+    'diams': 9830,
+    'divide': 247,
+    'eacute': 233,
+    'ecirc': 234,
+    'egrave': 232,
+    'empty': 8709,
+    'emsp': 8195,
+    'ensp': 8194,
+    'epsilon': 949,
+    'equiv': 8801,
+    'eta': 951,
+    'eth': 240,
+    'euml': 235,
+    'euro': 8364,
+    'exist': 8707,
+    'fnof': 402,
+    'forall': 8704,
+    'frac12': 189,
+    'frac14': 188,
+    'frac34': 190,
+    'frasl': 8260,
+    'gamma': 947,
+    'ge': 8805,
+    'gt': 62,
+    'hArr': 8660,
+    'harr': 8596,
+    'hearts': 9829,
+    'hellip': 8230,
+    'iacute': 237,
+    'icirc': 238,
+    'iexcl': 161,
+    'igrave': 236,
+    'image': 8465,
+    'infin': 8734,
+    'int': 8747,
+    'iota': 953,
+    'iquest': 191,
+    'isin': 8712,
+    'iuml': 239,
+    'kappa': 954,
+    'lArr': 8656,
+    'lambda': 955,
+    'lang': 9001,
+    'laquo': 171,
+    'larr': 8592,
+    'lceil': 8968,
+    'ldquo': 8220,
+    'le': 8804,
+    'lfloor': 8970,
+    'lowast': 8727,
+    'loz': 9674,
+    'lrm': 8206,
+    'lsaquo': 8249,
+    'lsquo': 8216,
+    'lt': 60,
+    'macr': 175,
+    'mdash': 8212,
+    'micro': 181,
+    'middot': 183,
+    'minus': 8722,
+    'mu': 956,
+    'nabla': 8711,
+    'nbsp': 160,
+    'ndash': 8211,
+    'ne': 8800,
+    'ni': 8715,
+    'not': 172,
+    'notin': 8713,
+    'nsub': 8836,
+    'ntilde': 241,
+    'nu': 957,
+    'oacute': 243,
+    'ocirc': 244,
+    'oelig': 339,
+    'ograve': 242,
+    'oline': 8254,
+    'omega': 969,
+    'omicron': 959,
+    'oplus': 8853,
+    'or': 8744,
+    'ordf': 170,
+    'ordm': 186,
+    'oslash': 248,
+    'otilde': 245,
+    'otimes': 8855,
+    'ouml': 246,
+    'para': 182,
+    'part': 8706,
+    'permil': 8240,
+    'perp': 8869,
+    'phi': 966,
+    'pi': 960,
+    'piv': 982,
+    'plusmn': 177,
+    'pound': 163,
+    'prime': 8242,
+    'prod': 8719,
+    'prop': 8733,
+    'psi': 968,
+    'quot': 34,
+    'rArr': 8658,
+    'radic': 8730,
+    'rang': 9002,
+    'raquo': 187,
+    'rarr': 8594,
+    'rceil': 8969,
+    'rdquo': 8221,
+    'real': 8476,
+    'reg': 174,
+    'rfloor': 8971,
+    'rho': 961,
+    'rlm': 8207,
+    'rsaquo': 8250,
+    'rsquo': 8217,
+    'sbquo': 8218,
+    'scaron': 353,
+    'sdot': 8901,
+    'sect': 167,
+    'shy': 173,
+    'sigma': 963,
+    'sigmaf': 962,
+    'sim': 8764,
+    'spades': 9824,
+    'sub': 8834,
+    'sube': 8838,
+    'sum': 8721,
+    'sup': 8835,
+    'sup1': 185,
+    'sup2': 178,
+    'sup3': 179,
+    'supe': 8839,
+    'szlig': 223,
+    'tau': 964,
+    'there4': 8756,
+    'theta': 952,
+    'thetasym': 977,
+    'thinsp': 8201,
+    'thorn': 254,
+    'tilde': 732,
+    'times': 215,
+    'trade': 8482,
+    'uArr': 8657,
+    'uacute': 250,
+    'uarr': 8593,
+    'ucirc': 251,
+    'ugrave': 249,
+    'uml': 168,
+    'upsih': 978,
+    'upsilon': 965,
+    'uuml': 252,
+    'weierp': 8472,
+    'xi': 958,
+    'yacute': 253,
+    'yen': 165,
+    'yuml': 255,
+    'zeta': 950,
+    'zwj': 8205,
+    'zwnj': 8204
+}
diff --git a/jinja2/debug.py b/jinja2/debug.py
index 9209054..f503c21 100644
--- a/jinja2/debug.py
+++ b/jinja2/debug.py
@@ -11,7 +11,7 @@
     :license: BSD.
 """
 import sys
-from types import CodeType
+from jinja2.utils import CodeType
 
 
 def translate_exception(exc_info):
diff --git a/jinja2/environment.py b/jinja2/environment.py
index 7aa7575..7faad26 100644
--- a/jinja2/environment.py
+++ b/jinja2/environment.py
@@ -10,7 +10,7 @@
 """
 import sys
 from jinja2.defaults import *
-from jinja2.lexer import Lexer, TokenStream
+from jinja2.lexer import get_lexer, TokenStream
 from jinja2.parser import Parser
 from jinja2.optimizer import optimize
 from jinja2.compiler import generate
@@ -281,10 +281,7 @@
 
         return _environment_sanity_check(rv)
 
-    @property
-    def lexer(self):
-        """Return a fresh lexer for the environment."""
-        return Lexer(self)
+    lexer = property(get_lexer, doc="The lexer for this environment.")
 
     def getitem(self, obj, argument):
         """Get an item or attribute of an object but prefer the item."""
diff --git a/jinja2/filters.py b/jinja2/filters.py
index 9cd3e50..da5f99d 100644
--- a/jinja2/filters.py
+++ b/jinja2/filters.py
@@ -10,7 +10,6 @@
 """
 import re
 import math
-import textwrap
 from random import choice
 from operator import itemgetter
 from itertools import imap, groupby
@@ -365,6 +364,7 @@
     parameter.  If you set the second parameter to `false` Jinja will not
     split words apart if they are longer than `width`.
     """
+    import textwrap
     return u'\n'.join(textwrap.wrap(s, width=width, expand_tabs=False,
                                    replace_whitespace=False,
                                    break_long_words=break_long_words))
diff --git a/jinja2/lexer.py b/jinja2/lexer.py
index 0597b7a..9702205 100644
--- a/jinja2/lexer.py
+++ b/jinja2/lexer.py
@@ -15,7 +15,6 @@
     :license: BSD, see LICENSE for more details.
 """
 import re
-import unicodedata
 from operator import itemgetter
 from collections import deque
 from jinja2.exceptions import TemplateSyntaxError
@@ -27,9 +26,9 @@
 _lexer_cache = LRUCache(50)
 
 # static regular expressions
-whitespace_re = re.compile(r'\s+(?um)')
+whitespace_re = re.compile(r'\s+', re.U)
 string_re = re.compile(r"('([^'\\]*(?:\\.[^'\\]*)*)'"
-                       r'|"([^"\\]*(?:\\.[^"\\]*)*)")(?ms)')
+                       r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S)
 integer_re = re.compile(r'\d+')
 name_re = re.compile(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b')
 float_re = re.compile(r'\d+\.\d+')
@@ -246,26 +245,22 @@
             self.next()
 
 
-class LexerMeta(type):
-    """Metaclass for the lexer that caches instances for
-    the same configuration in a weak value dictionary.
-    """
-
-    def __call__(cls, environment):
-        key = (environment.block_start_string,
-               environment.block_end_string,
-               environment.variable_start_string,
-               environment.variable_end_string,
-               environment.comment_start_string,
-               environment.comment_end_string,
-               environment.line_statement_prefix,
-               environment.trim_blocks,
-               environment.newline_sequence)
-        lexer = _lexer_cache.get(key)
-        if lexer is None:
-            lexer = type.__call__(cls, environment)
-            _lexer_cache[key] = lexer
-        return lexer
+def get_lexer(environment):
+    """Return a lexer which is probably cached."""
+    key = (environment.block_start_string,
+           environment.block_end_string,
+           environment.variable_start_string,
+           environment.variable_end_string,
+           environment.comment_start_string,
+           environment.comment_end_string,
+           environment.line_statement_prefix,
+           environment.trim_blocks,
+           environment.newline_sequence)
+    lexer = _lexer_cache.get(key)
+    if lexer is None:
+        lexer = Lexer(environment)
+        _lexer_cache[key] = lexer
+    return lexer
 
 
 class Lexer(object):
@@ -276,8 +271,6 @@
     Multiple environments can share the same lexer.
     """
 
-    __metaclass__ = LexerMeta
-
     def __init__(self, environment):
         # shortcuts
         c = lambda x: re.compile(x, re.M | re.S)
diff --git a/jinja2/nodes.py b/jinja2/nodes.py
index effa6d4..034becf 100644
--- a/jinja2/nodes.py
+++ b/jinja2/nodes.py
@@ -13,7 +13,6 @@
     :license: BSD, see LICENSE for more details.
 """
 import operator
-from copy import copy
 from itertools import chain, izip
 from collections import deque
 from jinja2.utils import Markup
@@ -154,30 +153,6 @@
             for result in child.find_all(node_type):
                 yield result
 
-    def copy(self):
-        """Return a deep copy of the node."""
-        result = object.__new__(self.__class__)
-        for field, value in self.iter_fields():
-            if isinstance(value, Node):
-                new_value = value.copy()
-            elif isinstance(value, list):
-                new_value = []
-                for item in value:
-                    if isinstance(item, Node):
-                        item = item.copy()
-                    else:
-                        item = copy(item)
-                    new_value.append(item)
-            else:
-                new_value = copy(value)
-            setattr(result, field, new_value)
-        for attr in self.attributes:
-            try:
-                setattr(result, attr, getattr(self, attr))
-            except AttributeError:
-                pass
-        return result
-
     def set_ctx(self, ctx):
         """Reset the context of a node and all child nodes.  Per default the
         parser will all generate nodes that have a 'load' context as it's the
diff --git a/jinja2/runtime.py b/jinja2/runtime.py
index 496b484..2c3aeb0 100644
--- a/jinja2/runtime.py
+++ b/jinja2/runtime.py
@@ -9,7 +9,6 @@
     :license: BSD.
 """
 import sys
-from types import FunctionType, MethodType
 from itertools import chain, imap
 from jinja2.utils import Markup, partial, soft_unicode, escape, missing, concat
 from jinja2.exceptions import UndefinedError, TemplateRuntimeError
@@ -20,7 +19,13 @@
            'TemplateRuntimeError', 'missing', 'concat', 'escape',
            'markup_join', 'unicode_join']
 
-_context_function_types = (FunctionType, MethodType)
+
+#: get the types we support for context functions.  We do not use types because
+#: IronPython doesn't provide that module out of the box.
+class _C(object):
+    meth = lambda: None
+_context_function_types = (type(lambda: None), type(_C.meth))
+del _C
 
 
 def markup_join(seq):
diff --git a/jinja2/sandbox.py b/jinja2/sandbox.py
index 20de369..e9ab1d9 100644
--- a/jinja2/sandbox.py
+++ b/jinja2/sandbox.py
@@ -13,11 +13,11 @@
     :license: BSD.
 """
 import operator
-from types import FunctionType, MethodType, TracebackType, CodeType, \
-     FrameType, GeneratorType
 from jinja2.runtime import Undefined
 from jinja2.environment import Environment
 from jinja2.exceptions import SecurityError
+from jinja2.utils import FunctionType, MethodType, TracebackType, CodeType, \
+     FrameType, GeneratorType
 
 
 #: maximum number of items a range may produce
diff --git a/jinja2/utils.py b/jinja2/utils.py
index b1c20b6..1ed6536 100644
--- a/jinja2/utils.py
+++ b/jinja2/utils.py
@@ -10,14 +10,11 @@
 """
 import re
 import sys
-import string
 try:
     from thread import allocate_lock
 except ImportError:
     from dummy_thread import allocate_lock
-from htmlentitydefs import name2codepoint
 from collections import deque
-from copy import deepcopy
 from itertools import imap
 
 
@@ -31,8 +28,8 @@
 _simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
 _striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
 _entity_re = re.compile(r'&([^;]+);')
-_entities = name2codepoint.copy()
-_entities['apos'] = 39
+_letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
+_digits = '0123456789'
 
 # special singleton representing missing values for the runtime
 missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})()
@@ -62,6 +59,40 @@
     del _test_gen_bug, _error
 
 
+# ironpython without stdlib doesn't have keyword
+try:
+    from keyword import iskeyword as is_python_keyword
+except ImportError:
+    _py_identifier_re = re.compile(r'^[a-zA-Z_][a-zA-Z0-9]*$')
+    def is_python_keyword(name):
+        if _py_identifier_re.search(name) is None:
+            return False
+        try:
+            exec name + " = 42"
+        except SyntaxError:
+            return False
+        return True
+
+
+# common types.  These do exist in the special types module too which however
+# does not exist in IronPython out of the box.
+class _C(object):
+    def method(self): pass
+def _func():
+    yield None
+FunctionType = type(_func)
+GeneratorType = type(_func())
+MethodType = type(_C.method)
+CodeType = type(_C.method.func_code)
+try:
+    raise TypeError()
+except TypeError:
+    _tb = sys.exc_info()[2]
+    TracebackType = type(_tb)
+    FrameType = type(_tb.tb_frame)
+del _C, _tb, _func
+
+
 def contextfunction(f):
     """This decorator can be used to mark a function or method context callable.
     A context callable is passed the active :class:`Context` as first argument when
@@ -179,7 +210,7 @@
                 '@' not in middle and
                 not middle.startswith('http://') and
                 len(middle) > 0 and
-                middle[0] in string.letters + string.digits and (
+                middle[0] in _letters + _digits and (
                     middle.endswith('.org') or
                     middle.endswith('.net') or
                     middle.endswith('.com')
@@ -355,10 +386,11 @@
         >>> Markup("Main &raquo; <em>About</em>").unescape()
         u'Main \xbb <em>About</em>'
         """
+        from jinja2.constants import HTML_ENTITIES
         def handle_match(m):
             name = m.group(1)
-            if name in _entities:
-                return unichr(_entities[name])
+            if name in HTML_ENTITIES:
+                return unichr(HTML_ENTITIES[name])
             try:
                 if name[:2] in ('#x', '#X'):
                     return unichr(int(name[2:], 16))