From 9a0078d5730e98330788add74cc7fe66b7122ecb Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 13 Aug 2008 18:24:17 +0200 Subject: [PATCH] Removed a few stdlib dependencies. This is the first step for IronPython support, the second one being a new lexer. --HG-- branch : trunk --- jinja2/compiler.py | 16 +-- jinja2/constants.py | 258 ++++++++++++++++++++++++++++++++++++++++++ jinja2/debug.py | 2 +- jinja2/environment.py | 7 +- jinja2/filters.py | 2 +- jinja2/lexer.py | 43 +++---- jinja2/nodes.py | 25 ---- jinja2/runtime.py | 9 +- jinja2/sandbox.py | 4 +- jinja2/utils.py | 48 ++++++-- 10 files changed, 338 insertions(+), 76 deletions(-) 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 @@ class Frame(object): 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 @@ class Frame(object): 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 @@ class CodeGenerator(NodeVisitor): # 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 @@ sociis sociosqu sodales sollicitudin suscipit suspendisse taciti tellus tempor 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 @@ class Environment(object): 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 @@ def do_wordwrap(s, width=79, break_long_words=True): 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 @@ from jinja2.utils import LRUCache _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 @@ class TokenStream(object): 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 @@ class Lexer(object): 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 @@ class Node(object): 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 @@ __all__ = ['LoopContext', 'Context', 'TemplateReference', 'Macro', 'Markup', '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 @@ _punctuation_re = re.compile( _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 @@ except TypeError, _error: 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 @@ def urlize(text, trim_url_limit=None, nofollow=False): '@' 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 @@ class Markup(unicode): >>> Markup("Main » About").unescape() u'Main \xbb About' """ + 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)) -- 2.26.2