diff --git a/.gitignore b/.gitignore index 65ed7f3..d5d3686 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /venv/ +/venv3/ /build/ /hamly/*.so *.pyc /dist/ -/hamly.egg-info/ \ No newline at end of file +/hamly.egg-info/ diff --git a/benchmark.py b/benchmark.py index 7cfea2c..78243cd 100644 --- a/benchmark.py +++ b/benchmark.py @@ -15,3 +15,8 @@ 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)] + +# print "-" * 80 +# print(hamly_template.template_source) + +# print(hamly_template(table=[dict(a=1,b=2) for x in range(2)])) diff --git a/hamly/ast_utils.py b/hamly/ast_utils.py index 9d23d40..8ef7351 100644 --- a/hamly/ast_utils.py +++ b/hamly/ast_utils.py @@ -10,23 +10,23 @@ def scalar_to_ast(value): return ast.Name("False", ast.Load()) elif value is None: return ast.Name("None", ast.Load()) - elif isinstance(value, basestring): + elif isinstance(value, str): return ast.Str(value) elif isinstance(value, int) or isinstance(value, float): return ast.Num(value) elif isinstance(value, list): - return ast.List(map(scalar_to_ast, value), ast.Load()) + return ast.List([scalar_to_ast(x) for x in value], ast.Load()) elif isinstance(value, tuple): - return ast.Tuple(map(scalar_to_ast, value), ast.Load()) + return ast.Tuple([scalar_to_ast(x) for x in value], ast.Load()) elif isinstance(value, dict): - return ast.Dict(map(scalar_to_ast, value.keys()), - map(scalar_to_ast, value.values())) + return ast.Dict([scalar_to_ast(x) for x in value.keys()], + [scalar_to_ast(x) for x in value.values()]) else: return value def make_call(name, *args): - ast_args = map(scalar_to_ast, args) + ast_args = [scalar_to_ast(x) for x in args] return ast.Call(ast.Name(name, ast.Load()), ast_args, [], None, None) @@ -35,7 +35,7 @@ def make_expr(node): def make_tuple(*elts): - ast_elts = map(scalar_to_ast, elts) + ast_elts = [scalar_to_ast(x) for x in elts] return ast.Tuple(ast_elts, ast.Load()) diff --git a/hamly/compiler.py b/hamly/compiler.py index 7f568f6..75177e7 100644 --- a/hamly/compiler.py +++ b/hamly/compiler.py @@ -25,7 +25,7 @@ def tagnode_to_ast(node): if node.attrs: for key, value in node.attrs.items(): callargs.append(make_tuple(key, value)) - block = sum(map(node_to_ast, node.children), [make_expr(make_call(OPEN_TAG, *callargs))]) + block = sum([node_to_ast(x) for x in node.children], [make_expr(make_call(OPEN_TAG, *callargs))]) block.append(make_expr(make_call(WRITE, "\n" % node.tagname))) return block @@ -35,8 +35,8 @@ def controlnode_to_ast(node): return [ast.Break()] mod = ast.parse("%s\n pass" % node.code) ctrl = mod.body[0] - ctrl.body = sum(map(node_to_ast, node.children), []) - ctrl.orelse = sum(map(node_to_ast, node.orelse), []) + ctrl.body = sum([node_to_ast(x) for x in node.children], []) + ctrl.orelse = sum([node_to_ast(x) for x in node.orelse], []) return [ctrl] diff --git a/hamly/const.py b/hamly/const.py index 90882d6..aa4a484 100644 --- a/hamly/const.py +++ b/hamly/const.py @@ -5,3 +5,4 @@ TO_STRING = "_h_to_string" QUOTEATTR = "_h_quoteattr" WRITE_ATTRS = "_h_write_attrs" +MAIN = "_h_main" \ No newline at end of file diff --git a/hamly/loader.py b/hamly/loader.py index 74e6d9e..52263df 100644 --- a/hamly/loader.py +++ b/hamly/loader.py @@ -1,10 +1,17 @@ # -*- coding: utf-8 -*- +import sys import ast + +from six import exec_ + +from .escape import escape, quoteattr, soft_unicode +from .html import write_attrs from .parser import parse from .compiler import compile_tree from .optimizer import optimize -from .const import WRITE, TO_STRING, ESCAPE, WRITE_MULTI, QUOTEATTR, WRITE_ATTRS +from .const import (WRITE, TO_STRING, ESCAPE, WRITE_MULTI, + QUOTEATTR, WRITE_ATTRS, MAIN) cache = {} @@ -13,33 +20,45 @@ def get_template(filename): if not cached: with open(filename) as fp: - source = fp.read().decode("utf-8") + source = fp.read()#.decode("utf-8") tree = parse(source) compiled = compile_tree(tree) module = ast.Module(compiled) optimized = optimize(module) - - optimized.body.insert(0, ast.ImportFrom(module='hamly.escape', - names=[ast.alias(name='escape', asname=ESCAPE), - ast.alias(name='quoteattr', asname=QUOTEATTR), - ast.alias(name='soft_unicode', asname=TO_STRING)], level=0)) - optimized.body.insert(0, ast.ImportFrom(module='hamly.html', names=[ast.alias(name='write_attrs', asname=WRITE_ATTRS)], level=0)) - template_source = "" try: from astmonkey import visitors template_source = visitors.to_source(optimized) except ImportError: - pass + try: + import codegen + template_source = codegen.to_source(optimized) + except ImportError: + template_source = "" + + # if sys.version_info[0] < 3: + # optimized.body.insert(0, ast.ImportFrom("hamly.escape", + # [ast.alias("escape", ESCAPE), + # ast.alias("quoteattr", QUOTEATTR), + # ast.alias("soft_unicode", TO_STRING)], + # 0)) + # optimized.body.insert(0, ast.ImportFrom("hamly.html", [ast.alias("write_attrs", WRITE_ATTRS)], 0)) + + code = compile(ast.fix_missing_locations(optimized), filename, "exec") - code = compile(ast.fix_missing_locations(optimized), filename, 'exec') + globs = { + ESCAPE: escape, + QUOTEATTR: quoteattr, + TO_STRING: soft_unicode, + WRITE_ATTRS: write_attrs + } scope = {} - exec code in scope - main_fun = scope["main"] - concat = ''.join + exec_(code, globs, scope) + main_fun = scope[MAIN] + concat = "".join def render(**kwargs): output = [] @@ -51,6 +70,6 @@ def render(**kwargs): setattr(render, "template_source", template_source) cached = render - cache[filename] = cached + # cache[filename] = cached return cached diff --git a/hamly/optimizer.py b/hamly/optimizer.py index 28039d3..8a92df0 100644 --- a/hamly/optimizer.py +++ b/hamly/optimizer.py @@ -2,9 +2,13 @@ import re import ast +import sys import copy -from .const import OPEN_TAG, WRITE, ESCAPE, TO_STRING, WRITE_MULTI, QUOTEATTR, WRITE_ATTRS +from six import exec_ + +from .const import (OPEN_TAG, WRITE, ESCAPE, TO_STRING, + WRITE_MULTI, QUOTEATTR, WRITE_ATTRS, MAIN) from .ast_utils import (make_call, make_expr, make_tuple, ast_True, make_cond, copy_loc, scalar_to_ast, defines_functions, ) from .escape import quoteattr, escape, soft_unicode @@ -127,7 +131,7 @@ def visit_Expr(self, node): if not dynamic_args: static_attrs_data = [] - write_attrs(map(self.evaluate, static_args), static_attrs_data.append) + write_attrs([self.evaluate(x) for x in static_args], static_attrs_data.append) block.append(self._write(''.join(static_attrs_data))) else: static_names = False @@ -201,7 +205,7 @@ def _flush(): _flush() result.append(item) _flush() - return map(self.visit, result) + return [self.visit(x) for x in result] def visit_For(self, node): return copy_loc(ast.For(node.target, node.iter, @@ -256,8 +260,8 @@ def visit_For(self, node): iter_assign = ast.Assign([node.target], scalar_to_ast(value)) code = compile(ast.fix_missing_locations(ast.Module([iter_assign])), '', 'exec') scope = {} - body = map(copy.deepcopy, node.body) - exec code in scope + body = [copy.deepcopy(x) for x in node.body] + exec_(code, {}, scope) for st in body: for name in names: st = SubstituteVisitor(name, scalar_to_ast(scope[name])).visit(st) @@ -324,7 +328,7 @@ def visit_Expr(self, node): values = node.value.args[:] values += impl.args.defaults[len(values) - max_args:] for arg, value in zip(impl.args.args, values): - body = map(SubstituteVisitor(arg.id, value).visit, body) + body = [SubstituteVisitor(arg.id, value).visit(x) for x in body] self._inline = True return body return node @@ -403,7 +407,7 @@ def visit_Call(self, node): and isinstance(node.args[0], ast.Call)\ and isinstance(node.args[0].func, ast.Name)\ and node.args[0].func.id == ESCAPE: - node.args = map(InterpolateStrings().visit, node.args) + node.args = [InterpolateStrings().visit(x) for x in node.args] return node @@ -437,6 +441,10 @@ def optimize(node): names.visit(node) names = list(set(names.names)) names.extend((WRITE, WRITE_MULTI)) - arguments = ast.arguments(args=[ast.Name(name, ast.Param()) for name in names], vararg=None, - kwarg="__kw", defaults=[]) - return ast.Module([ast.FunctionDef(name="main", args=arguments, body=node.body, decorator_list=[])]) + if sys.version_info[0] < 3: + arguments = ast.arguments(args=[ast.Name(name, ast.Param()) for name in names], vararg=None, + kwarg="__kw", defaults=[]) + else: + arguments = ast.arguments([ast.arg(name, None) for name in names], + None, None, [], '__kw', None, [], []) + return ast.Module([ast.FunctionDef(name=MAIN, args=arguments, body=node.body, decorator_list=[])]) diff --git a/setup.py b/setup.py index 00dce36..f5fee3c 100644 --- a/setup.py +++ b/setup.py @@ -1,17 +1,23 @@ # -*- coding: utf-8 -*- +import sys from setuptools import setup, Extension +if sys.version_info[0] < 3: + requires = ['pyparsing==1.5.7', 'pydot', 'astmonkey', 'six'] +else: + requires = ['codegen', 'six'] + setup( name='hamly', description='fast haml for python', keywords='web template haml', - version='0.1', + version='0.1.1', author='Victor Kotseruba', author_email='barbuzaster@gmail.com', license='MIT', packages=['hamly'], - install_requires=['pyparsing==1.5.7', 'pydot', 'astmonkey'], + install_requires=requires, include_package_data=True, zip_safe=False, ext_modules=[Extension('hamly.escape_fast', ['hamly/escape_fast.c'])]