├── jsjinja
├── utils.py
├── tests
│ ├── __init__.py
│ ├── templates
│ │ ├── set.tmpl
│ │ ├── eval.tmpl
│ │ ├── condexpr.tmpl
│ │ ├── unicode.tmpl
│ │ ├── import.tmpl
│ │ ├── partials
│ │ │ ├── include.tmpl
│ │ │ └── layout.tmpl
│ │ ├── if.tmpl
│ │ ├── context.tmpl
│ │ ├── block.tmpl
│ │ ├── include.tmpl
│ │ ├── tests.tmpl
│ │ ├── macro.tmpl
│ │ ├── extends.tmpl
│ │ ├── globals.tmpl
│ │ ├── for.tmpl
│ │ └── filters.tmpl
│ └── test_templates.py
├── src
│ ├── tests.coffee
│ ├── filters.coffee
│ ├── globals.coffee
│ └── runtime.coffee
├── ext.py
├── __init__.py
├── lib
│ ├── jinja2.runtime.min.js
│ └── jinja2.runtime.js
└── compiler.py
├── requirements.txt
├── requirements-test.txt
├── test.sh
├── setup.cfg
├── run-tests.py
├── .gitignore
├── MANIFEST.in
├── examples
├── static
│ ├── templates
│ │ ├── base.html
│ │ └── layout.html
│ ├── README.md
│ ├── generate.sh
│ ├── index.html
│ └── templates.js
└── dynamic
│ ├── README.md
│ ├── templates
│ ├── base.html
│ └── layout.html
│ └── app.py
├── Makefile
├── readme_template.tmpl
├── Cakefile
├── setup.py
├── requirements_utils.py
├── README.md
└── README.rst
/jsjinja/utils.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | jinja2>=2.5
--------------------------------------------------------------------------------
/requirements-test.txt:
--------------------------------------------------------------------------------
1 | nose>=1.0
2 | pyv8
--------------------------------------------------------------------------------
/jsjinja/tests/__init__.py:
--------------------------------------------------------------------------------
1 | import test_templates
--------------------------------------------------------------------------------
/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | nosetests -w jsjinja/tests/ -v
--------------------------------------------------------------------------------
/jsjinja/tests/templates/set.tmpl:
--------------------------------------------------------------------------------
1 | {% set x=1 %}
2 | {{x}}
--------------------------------------------------------------------------------
/jsjinja/tests/templates/eval.tmpl:
--------------------------------------------------------------------------------
1 | {% set a = 2*2 or 7 %}{{a}}
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [nosetests]
2 | verbosity=2
3 | detailed-errors=1
4 |
--------------------------------------------------------------------------------
/jsjinja/tests/templates/condexpr.tmpl:
--------------------------------------------------------------------------------
1 | {{'no_x' if not x else 'yes_x'}}
--------------------------------------------------------------------------------
/jsjinja/tests/templates/unicode.tmpl:
--------------------------------------------------------------------------------
1 | {% set other = 'ñ' %}
2 | áéíóú{{other}}
--------------------------------------------------------------------------------
/jsjinja/tests/templates/import.tmpl:
--------------------------------------------------------------------------------
1 | {% from 'macro.tmpl' import tes as test %}
2 | {{test(2)}}
--------------------------------------------------------------------------------
/jsjinja/tests/templates/partials/include.tmpl:
--------------------------------------------------------------------------------
1 | {% set local = None %}
2 | Partial include{{p}}
--------------------------------------------------------------------------------
/jsjinja/tests/templates/if.tmpl:
--------------------------------------------------------------------------------
1 | {% set c=True %}{% if d %}good{% elif c %}soso{% else %}bad{% endif %}
--------------------------------------------------------------------------------
/run-tests.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # from jinja2js.tests.test_templates import main
3 | # main()
4 |
--------------------------------------------------------------------------------
/jsjinja/tests/templates/context.tmpl:
--------------------------------------------------------------------------------
1 | {% if context %}Context is setted{% else %}Context is not setted{% endif %}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.egg-info
3 | build/
4 | dist/
5 | try*.py
6 | *.sublime-project
7 | *.sublime-workspace
8 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include Makefile README.md README.rst
2 | recursive-include jsjinja/lib *
3 | recursive-include jsjinja/tests *
--------------------------------------------------------------------------------
/examples/static/templates/base.html:
--------------------------------------------------------------------------------
1 | Site template
2 | {% block content %}
3 |
This is the main content
4 | {% endblock %}
--------------------------------------------------------------------------------
/jsjinja/tests/templates/block.tmpl:
--------------------------------------------------------------------------------
1 | {% block main %}
2 | Main block
3 | {% block innermain %}
4 | Inner block
5 | {% endblock %}
6 | {% endblock %}
--------------------------------------------------------------------------------
/jsjinja/tests/templates/include.tmpl:
--------------------------------------------------------------------------------
1 | {% set local=2 %}
2 | {% for p in [1,2] %}
3 | {% include "partials/include.tmpl" %}
4 | {% endfor %}
5 | {{local}}
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | develop:
2 | python setup.py develop
3 |
4 | build:
5 | cake sbuild
6 | python setup.py build
7 |
8 | test:
9 | python setup.py nosetests
10 |
--------------------------------------------------------------------------------
/jsjinja/tests/templates/tests.tmpl:
--------------------------------------------------------------------------------
1 | {% set var = 2 %}
2 | {% if var is defined %}
3 | Defined!
4 | {% else %}
5 | Not defined :(
6 | {% endif %}
7 | {{"yes" if 10 is divisibleby(3) else "no"}}
--------------------------------------------------------------------------------
/examples/static/README.md:
--------------------------------------------------------------------------------
1 | # Generating templates.js
2 | For generating the `templates.js` file you have to execute in the shell
3 | ```shell
4 | jsjinja -l -b templates -o templates.js templates/*
5 | ```
--------------------------------------------------------------------------------
/jsjinja/tests/templates/macro.tmpl:
--------------------------------------------------------------------------------
1 | {% set p = 2 %}{% macro x(s,b=4,c='asd') %}{{s}}-{{p}}-{{b}}-{{c}}{% endmacro %}
2 | {{x()}}
3 | {{x(1,b=2,c=3)}}
4 | {% macro tes(a) %}
5 | {{a}}
6 | {% endmacro %}
7 |
--------------------------------------------------------------------------------
/examples/static/generate.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # -l option is for include runtime lib in output
3 | # -b is the base dir of the templates
4 | # -o is output file
5 | jsjinja -l -b templates -o templates.js templates/*
--------------------------------------------------------------------------------
/examples/dynamic/README.md:
--------------------------------------------------------------------------------
1 | # Running dynamic app
2 | For running this example you must have [Flask](http://flask.pocoo.org/) and Jinja2Js installed and then execute in the shell
3 | ```shell
4 | python app.py
5 | ```
--------------------------------------------------------------------------------
/jsjinja/tests/templates/partials/layout.tmpl:
--------------------------------------------------------------------------------
1 | Layout
2 | {% block base %}
3 | Layout block
4 | {% endblock %}
5 | {% block base1 %}
6 | Layout base1
7 | {% block base11 %}
8 | Layout base11
9 | {% endblock %}
10 | {% endblock %}
--------------------------------------------------------------------------------
/jsjinja/tests/templates/extends.tmpl:
--------------------------------------------------------------------------------
1 | {% set s="partials/layout.tmpl" %}
2 | {% extends s %}
3 | {% block base %}
4 | {{ super() }}
5 | Extends block
6 | {% endblock %}
7 | {% block base11 %}
8 | Extends base11
9 | {% endblock %}
10 |
--------------------------------------------------------------------------------
/jsjinja/tests/templates/globals.tmpl:
--------------------------------------------------------------------------------
1 | {% set d = dict(a='b',c='d') %}
2 | {{d.a}}{{d['c']}}
3 | {% set pipe = joiner("|") %}
4 | {{pipe()}}a{{pipe()}}b
5 |
6 | {% set row_class = cycler('odd', 'even') %}
7 | {% for folder in range(10) %}
8 | {{ row_class.next() }}
9 | {% endfor %}
--------------------------------------------------------------------------------
/readme_template.tmpl:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block title %}Memberlist{% endblock %}
3 | {% block content %}
4 |
9 | {% endblock %}
10 |
--------------------------------------------------------------------------------
/examples/dynamic/templates/base.html:
--------------------------------------------------------------------------------
1 | {% block content %}
2 |
3 | {% if js %}
4 | Rendered with Javascript. View Python version
5 | {% else %}
6 | Rendered with Python. View Javascript version
7 | {% endif %}
8 |
9 | {% endblock %}
--------------------------------------------------------------------------------
/examples/static/templates/layout.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 | {{ super() }}
4 | Total sites: {{sites|count}}
5 |
10 | {% endblock %}
11 |
--------------------------------------------------------------------------------
/jsjinja/tests/templates/for.tmpl:
--------------------------------------------------------------------------------
1 | {% set x=[1,2,3] %}
2 | {% set y=[1,2,3] %}
3 | {% for a in x %}
4 | {% for b in y %}
5 | {{loop.index}}{{a}}={{b}}
6 | {% endfor %}
7 | {% endfor %}
8 | {% for a in x %}
9 | Second loop - {{ loop.cycle('odd', 'even') }}
10 | {% endfor %}
11 | {% for x in range(1,10,2) %}
12 | {% else %}
13 | No loop
14 | {% endfor %}
--------------------------------------------------------------------------------
/jsjinja/tests/templates/filters.tmpl:
--------------------------------------------------------------------------------
1 | {% set str = 'aba baba' %}
2 | {% set seq = [1] %}
3 | {% set seq_c = [1,2,3] %}
4 | {{str|capitalize}}
5 | {{str|title}}
6 | {{str|default('b')}}
7 | {#{str|upper}#}
8 | {#{str|lower}#}
9 | {{seq|random}}
10 | {{seq_c|first}}
11 | {{seq_c|last}}
12 | {% filter escape %}
13 | a
14 | asdfasdf&>|
15 | b
16 | c
17 | {% endfilter %}
18 | b
--------------------------------------------------------------------------------
/examples/dynamic/templates/layout.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 | {{ super() }}
4 | Total sites: {{sites|count}}
5 |
10 | Escaped var: {{''|e}}
11 | View the source for full experience ;)
12 | {% endblock %}
13 |
--------------------------------------------------------------------------------
/Cakefile:
--------------------------------------------------------------------------------
1 | # Module requires
2 | {spawn, exec} = require 'child_process'
3 | sys = require 'sys'
4 | Closure = require 'closure-compiler'
5 |
6 | printOutput = (process) ->
7 | process.stdout.on 'data', (data) -> sys.print data
8 | process.stderr.on 'data', (data) -> sys.print data
9 |
10 | watchJS = ->
11 | coffee = exec 'coffee -cj ./jsjinja/lib/jinja2.runtime.js ./jsjinja/src/runtime.coffee ./jsjinja/src/globals.coffee ./jsjinja/src/filters.coffee ./jsjinja/src/tests.coffee '
12 | closure = Closure.compile null, js:'./jsjinja/lib/jinja2.runtime.js', js_output_file:'./jsjinja/lib/jinja2.runtime.min.js', ->
13 | printOutput(coffee)
14 |
15 |
16 | task 'sbuild', 'Build task for Sublime Text', ->
17 | watchJS()
18 |
--------------------------------------------------------------------------------
/jsjinja/src/tests.coffee:
--------------------------------------------------------------------------------
1 | Jinja2.registerTest 'callable', (object) ->
2 | !!(obj && obj.constructor && obj.call && obj.apply)
3 |
4 | Jinja2.registerTest 'odd', (value) -> value%2==1
5 |
6 | Jinja2.registerTest 'even', (value) -> value%2==0
7 |
8 | Jinja2.registerTest 'divisibleby', (value, num) -> value%num==0
9 |
10 | Jinja2.registerTest 'defined', (value) -> typeof value != "undefined"
11 |
12 | Jinja2.registerTest 'undefined', (value) -> typeof value == "undefined"
13 |
14 | Jinja2.registerTest 'none', (value) -> value==null
15 |
16 | Jinja2.registerTest 'lower', (value) -> value==value.toLowerCase()
17 |
18 | Jinja2.registerTest 'upper', (value) -> value==value.toUpperCase()
19 |
20 | Jinja2.registerTest 'string', (value) ->
21 | toString.call(value) == '[object String]'
22 |
23 | Jinja2.registerTest 'mapping', (value) ->
24 | value is Object(value)
25 |
26 | Jinja2.registerTest 'number', (value) ->
27 | toString.call(value) == '[object Number]'
28 |
29 | Jinja2.registerTest 'sequence', (value) ->
30 | toString.call(value) == '[object Array]'
31 |
32 | Jinja2.registerTest 'sameas', (value, other) ->
33 | value is other
34 |
35 | Jinja2.registerTest 'iterable', (value) ->
36 | toString.call(value) == '[object Array]';
37 |
38 | Jinja2.registerTest 'escaped', (value) ->
39 | '__html__' in value
40 |
41 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | # from distribute_setup import use_setuptools
2 | # use_setuptools()
3 |
4 | from setuptools import setup, find_packages
5 | from requirements_utils import parse_dependency_links, parse_requirements
6 |
7 | print find_packages()
8 | setup(
9 | name='jsjinja',
10 | version='0.3.1',
11 | url='https://github.com/syrusakbary/jsjinja',
12 | download_url = 'git@github.com:syrusakbary/jsjinja.git',
13 | author='Syrus Akbary',
14 | author_email='me@syrusakbary.com',
15 | description='Jinja2 to Javascript compiler',
16 | long_description=open('README.rst').read(),
17 | classifiers=[
18 | 'Development Status :: 2 - Pre-Alpha',
19 | 'Environment :: Console',
20 | 'Intended Audience :: Developers',
21 | 'License :: OSI Approved :: BSD License',
22 | 'Operating System :: OS Independent',
23 | 'Programming Language :: Python :: 2.6',
24 | 'Programming Language :: Python :: 2.7',
25 | 'Topic :: Scientific/Engineering',
26 | ],
27 | platforms='any',
28 | packages=find_packages(),
29 | keywords='jinja2 javascript converter coffeescript',
30 | include_package_data=True,
31 | entry_points={
32 | 'console_scripts' : ['jsjinja = jsjinja:generate_template',]
33 | },
34 | install_requires = parse_requirements('requirements.txt'),
35 | dependency_links = parse_dependency_links('requirements.txt'),
36 | # setup_requires = ['nose>=1.0'],
37 | tests_require = parse_requirements('requirements-test.txt'),
38 | # test_dirs='jsjinja/testsuite',
39 | test_suite = "nose.collector"
40 | )
41 |
--------------------------------------------------------------------------------
/examples/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
11 |
12 |
13 |
14 |
15 |
16 |
20 |
21 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/jsjinja/src/filters.coffee:
--------------------------------------------------------------------------------
1 | Jinja2.registerFilter 'length', (obj) -> obj.length
2 |
3 | Jinja2.registerFilter 'count', (obj) -> obj.length
4 |
5 | Jinja2.registerFilter 'indent', (str, width, indentfirst) ->
6 | width or=4
7 | indention = if width then Array(width + 1).join(" ") else ""
8 | (if indentfirst then str else str.replace(/\n$/,'')).replace(/\n/g,"\n#{indention}")
9 |
10 | Jinja2.registerFilter 'random', (environment, seq) ->
11 | if seq then seq[Math.floor(Math.random() * seq.length)] else `undefined`
12 |
13 | Jinja2.registerFilter 'last', (environment, seq) ->
14 | if seq then seq[seq.length-1] else `undefined`
15 |
16 | Jinja2.registerFilter 'first', (environment, seq) ->
17 | if seq then seq[0] else `undefined`
18 |
19 | Jinja2.registerFilter 'title', (str) ->
20 | str.replace /\w\S*/g, (txt) -> txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()
21 |
22 | Jinja2.registerFilter 'lower', (str) -> str.toLowerCase()
23 |
24 | Jinja2.registerFilter 'upper', (str) -> str.toUpperCase()
25 |
26 |
27 | Jinja2.registerFilter 'capitalize', (str) ->
28 | str.charAt(0).toUpperCase() + str.slice(1)
29 |
30 | Jinja2.registerFilter 'escape', (html) ->
31 | String(html).replace(/&/g, "&").replace(//g, ">").replace /"/g, """
32 |
33 | Jinja2.registerFilter 'default', (value, default_value, bool) ->
34 | if ((bool and !value) or (value is undefined)) then default_value else value
35 |
36 | Jinja2.registerFilter 'truncate', (str, length, killwords, end) ->
37 | length or= 255
38 | end or='...'
39 | if str.length <= length
40 | str
41 | else if killwords
42 | str.substring(0, length)
43 | else
44 | str = str.substring(0, maxLength + 1)
45 | str = str.substring(0, Math.min(str.length, str.lastIndexOf(" ")))
46 | str + end
47 |
--------------------------------------------------------------------------------
/jsjinja/ext.py:
--------------------------------------------------------------------------------
1 | from jinja2 import nodes
2 | from jinja2.ext import Extension
3 | from jsjinja import JsJinja
4 | # from generate import generate, generate_js, generate_node, generate_all_templates
5 |
6 | class JsJinjaExtension(Extension):
7 | # a set of names that trigger the extension.
8 | tags = set(['jsjinja'])
9 |
10 | def __init__(self, environment):
11 | super(JsJinjaExtension, self).__init__(environment)
12 |
13 | # add the defaults to the environment
14 | environment.extend(
15 | jsjinja=JsJinja(self.environment)
16 | # generate_js=self.generate_js,
17 | # generate_all_templates=self.generate_all_templates,
18 | )
19 |
20 | # def generate_js(self, name=None, source=None):
21 | # if not source and not name:
22 | # raise Exception("You must specity the name or source (...).generate_js([name|source]=...)")
23 | # if not source:
24 | # source = generate_js(self.environment, name)
25 | # return generate(self.environment, source, name)
26 |
27 | # def generate_all_templates(self):
28 | # return generate_all_templates(self.environment)
29 |
30 | def parse(self, parser):
31 | parser.stream.next().lineno # lineno =
32 | body = parser.parse_statements(['name:endjsjinja'], drop_needle=True)
33 | node = nodes.Template(body,lineno=1)
34 | code = self.environment.jsjinja.generate_node(node or body[0],None)
35 | return nodes.Output([nodes.Const(code)]).set_lineno(1)
36 |
37 | # def a(self,*args,**kwargs):
38 | # return repr(args)+repr(kwargs)
39 | # # # print type(body[0])
40 | # # # lineno, body
41 | # # node = nodes.Template(body,lineno=1)
42 | # # generate_node(self.environment,node,'')
43 |
--------------------------------------------------------------------------------
/examples/dynamic/app.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, render_template, url_for, Response, redirect
2 | import json
3 |
4 | app = Flask(__name__)
5 |
6 | app.jinja_env.add_extension('jsjinja.ext.Jinja2JsExtension')
7 | app.jinja_env.autoescape = False
8 | app.debug = True
9 |
10 | template_context = {
11 | 'sites': [
12 | {
13 | 'name': 'Google',
14 | 'url': 'http://google.com/'
15 | },
16 | {
17 | 'name': 'Youtube',
18 | 'url': 'http://youtube.com/'
19 | },
20 | {
21 | 'name': 'Facebook',
22 | 'url': 'http://facebook.com/'
23 | },
24 | {
25 | 'name': 'Syrus Akbary',
26 | 'url': 'http://syrusakbary.com/'
27 | }
28 | ],
29 | }
30 |
31 | @app.route("/")
32 | def index():
33 | return redirect(url_for('render.js'))
34 |
35 | @app.route("/js",endpoint="render.js")
36 | def js():
37 | template_context_js = dict(template_context, js=True, other_url = url_for('render.python'))
38 |
39 | return '''
40 |
44 | ''' %json.dumps(template_context_js)
45 |
46 | @app.route("/templates.js")
47 | def js_templates():
48 | ret = '/* Jinja2 javascript runtime (minified) */\n'
49 | ret += app.jinja_env.jsjinja.lib(minified=True)
50 | ret += '\n/* Js compiled templates */\n'
51 | ret += app.jinja_env.jsjinja.generate_all()
52 | return Response(response=ret, status=200, mimetype="text/javascript")
53 |
54 | @app.route("/python",endpoint="render.python")
55 | def python():
56 | template_context_normal = dict(template_context, js=False, other_url = url_for('render.js'))
57 | return render_template("layout.html", **template_context_normal)
58 |
59 | if __name__ == "__main__":
60 | app.run()
61 |
--------------------------------------------------------------------------------
/jsjinja/src/globals.coffee:
--------------------------------------------------------------------------------
1 | Jinja2.registerGlobal 'range', ->
2 | start = undefined
3 | end = undefined
4 | step = undefined
5 | array = []
6 | switch arguments.length
7 | # when 0
8 | # throw new Error("range() expected at least 1 argument, got 0 - must be specified as [start,] stop[, step]")return array
9 | when 1
10 | start = 0
11 | end = Math.floor(arguments[0]) - 1
12 | step = 1
13 | when 2, 3
14 | # else
15 | start = Math.floor(arguments[0])
16 | end = Math.floor(arguments[1]) - 1
17 | s = arguments[2]
18 | s = 1 if typeof s is "undefined"
19 | step = Math.floor(s) # or (->
20 | # throw new Error("range() step argument must not be zero")
21 | # )()
22 | if step > 0
23 | i = start
24 |
25 | while i <= end
26 | array.push i
27 | i += step
28 | else if step < 0
29 | step = -step
30 | if start > end
31 | i = start
32 |
33 | while i > end + 1
34 | array.push i
35 | i -= step
36 | array
37 |
38 | Jinja2.registerGlobal 'dict', (->
39 | func = (obj) -> obj
40 | func.__append_kwargs__ = true
41 | func
42 | )()
43 |
44 | # class Cycler
45 | # constructor: (items) ->
46 | # log? '*****', @items
47 | # @reset()
48 |
49 | # reset: ->
50 | # @setPos 0
51 |
52 | # setPos: (@pos) ->
53 | # @current = @items[@pos]
54 |
55 | # next: ->
56 | # log? '*****___', @pos
57 | # rv = @current
58 | # @setPos (@pos+1)% (@items.length)
59 | # rv
60 |
61 | Jinja2.registerGlobal 'cycler', ->
62 | # log? arguments[0],arguments[1]
63 | cycler =
64 | items: arguments
65 | reset: ->
66 | cycler._setPos 0
67 | _setPos: (pos) ->
68 | cycler.pos = pos
69 | cycler.current = cycler.items[pos]
70 | next: ->
71 | rv = cycler.current
72 | cycler._setPos (cycler.pos+1)%(cycler.items.length)
73 | rv
74 | cycler.reset()
75 | return cycler
76 | # new Cycler(['odd','even'])
77 |
78 | Jinja2.registerGlobal 'joiner', (sep) ->
79 | sep or= ','
80 | used = false
81 | ->
82 | if not used
83 | used = true
84 | ''
85 | else
86 | sep
--------------------------------------------------------------------------------
/requirements_utils.py:
--------------------------------------------------------------------------------
1 | # Based on http://www.dabeaz.com/generators/ and http://cburgmer.posterous.com/pip-requirementstxt-and-setuppy
2 | import re
3 | import os
4 | import sys
5 |
6 |
7 | PY_VER_STR = "py%d%d" % sys.version_info[:2]
8 |
9 |
10 | def gen_find_applicable_requirements_filenames(base_filename):
11 | """
12 | Generator that yields the passed base requirements filename, along with its
13 | version specific requirements filename (if exists)
14 | """
15 | yield base_filename
16 |
17 | base, ext = os.path.splitext(base_filename)
18 | version_specific_filename = "%s.%s%s" % (base, PY_VER_STR, ext)
19 | if os.path.exists(version_specific_filename):
20 | yield version_specific_filename
21 |
22 | def gen_open_files(filenames):
23 | """
24 | Generator that takes an iterable containing filenames as input and yields a
25 | sequence of file objects that have been suitably open
26 | """
27 | for name in filenames:
28 | yield open(name)
29 |
30 | def gen_cat(sources):
31 | """
32 | Generator that concatenates multiple generators into a single sequence
33 | """
34 | for s in sources:
35 | for item in s:
36 | yield item
37 |
38 | def gen_lines_from_requirements(base_filename):
39 | """
40 | Generator that finds all applicable requirements filenames and yields their
41 | contents, line at a time
42 | """
43 | filenames = gen_find_applicable_requirements_filenames(base_filename)
44 | files = gen_open_files(filenames)
45 | lines = gen_cat(files)
46 | return lines
47 |
48 | def parse_requirements(base_filename):
49 | """
50 | Finds all applicable requirements filenames, parse them and return the
51 | requirements
52 | """
53 | requirements = []
54 | for line in gen_lines_from_requirements(base_filename):
55 | if re.match(r'(\s*#)|(\s*$)', line):
56 | continue
57 | if re.match(r'\s*-e\s+', line):
58 | requirements.append(re.sub(r'\s*-e\s+.*#egg=(.*)$', r'\1', line))
59 | elif re.match(r'\s*-f\s+', line):
60 | pass
61 | else:
62 | requirements.append(line)
63 |
64 | return requirements
65 |
66 | def parse_dependency_links(base_filename):
67 | """
68 | Finds all applicable requirements filenames, parse them and return the
69 | dependency links
70 | """
71 | dependency_links = []
72 | for line in gen_lines_from_requirements(base_filename):
73 | if re.match(r'\s*-[ef]\s+', line):
74 | dependency_links.append(re.sub(r'\s*-[ef]\s+', '', line))
75 |
76 | return dependency_links
77 |
--------------------------------------------------------------------------------
/jsjinja/__init__.py:
--------------------------------------------------------------------------------
1 | import jinja2
2 | import os
3 | from .compiler import generate
4 | import glob
5 | import re
6 |
7 | _lib_js = {}
8 |
9 | def lib(minified=False):
10 | global _lib_js
11 | key = "minified" if minified else "normal"
12 | if key not in _lib_js:
13 | runtime = os.path.join(os.path.dirname(__file__),"lib/jinja2.runtime.min.js" if minified else "lib/jinja2.runtime.js")
14 | _lib_js[key] = open(runtime,'r').read()
15 | return _lib_js[key]
16 |
17 | class JsJinja (object):
18 | js_environment = 'Jinja2'
19 | def __init__(self,environment=None):
20 | self.environment = environment or jinja2.Environment()
21 |
22 | def generate_node(self,node,name):
23 | return generate(node,self.environment,name,name,env=self.js_environment)
24 |
25 | def generate(self,filename):
26 | source, fn, _ = self.environment.loader.get_source(self.environment,filename)
27 | return self._generate(source,filename)
28 |
29 | def _generate(self,source,name):
30 | node = self.environment._parse(source,name,name)
31 | return self.generate_node(node,name)
32 |
33 | def generate_all(self):
34 | if not self.environment.loader:
35 | raise Exception("The Jinja2 environment doesn't have a template loader associated.\nYou must specify it for using the generate_all method.")
36 | templates = self.environment.list_templates()
37 | return ';'+';\n'.join(map(self.generate,templates))+';'
38 |
39 | def generate_source(self,source,name=None):
40 | return self._generate(source,name)
41 |
42 | lib = staticmethod(lib)
43 |
44 | def generate_template():
45 | from optparse import OptionParser
46 | j = JsJinja()
47 |
48 | usage = "usage: %prog [options] files"
49 | parser = OptionParser(usage)
50 | parser.add_option("-o", "--output", dest="output",default=None,
51 | help="write output to FILE", metavar="FILE")
52 | parser.add_option("-b", "--base", dest="base",default=None,
53 | help="Set tempalte dir for dropping it in template name", metavar="FILE")
54 | parser.add_option("-l", "--lib", dest="lib",default=False,
55 | help="Include Jinja2 runtime lib", action="store_true")
56 | (options, args) = parser.parse_args()
57 |
58 | if not args:
59 | raise Exception('You must specify input files')
60 |
61 | files = []
62 | for a in args:
63 | files += glob.glob(a)
64 |
65 | generated = [lib(True)] if options.lib else []
66 | for f in files:
67 | source = open(f).read()
68 | if options.base:
69 | f = re.sub('^'+options.base+'/?', '', f)
70 | gen = j.generate_source(source, f)
71 | generated.append(gen)
72 |
73 | generated = ';'+';\n'.join(generated)+';'
74 | output = options.output
75 | if output:
76 | with open(output,'w') as f:
77 | f.write(generated)
78 | else:
79 | print generated
80 |
81 | # j = jsjinja()
82 | # print j.generate('template.tmpl')
83 |
--------------------------------------------------------------------------------
/jsjinja/tests/test_templates.py:
--------------------------------------------------------------------------------
1 | #encoding: utf8
2 | import os
3 | import jinja2
4 | import jsjinja
5 |
6 | from nose import with_setup
7 |
8 |
9 | def setup_func():
10 | global jinja_env, processors
11 |
12 | def teardown_func():
13 | pass
14 |
15 |
16 | from pyv8 import PyV8
17 |
18 | TEMPLATE_FOLDER = 'templates/'
19 | env = jinja2.Environment(loader = jinja2.FileSystemLoader(TEMPLATE_FOLDER))
20 | env.add_extension('jsjinja.ext.JsJinjaExtension')
21 |
22 | class Global(PyV8.JSClass):
23 | def log(self,*args):
24 | print args
25 |
26 |
27 |
28 | ctx = PyV8.JSContext(Global())
29 | ctx.enter()
30 |
31 | ctx.eval(jsjinja.lib())
32 |
33 |
34 | # templates = env.list_templates()
35 |
36 | def test_extension():
37 | js = env.jsjinja.generate_source(source='{{a}}')
38 | ex = ctx.eval('(new (%s))'%js).render({"a":"test"})
39 | assert ex == "test"
40 |
41 | def test_extensiontag():
42 | template = '''{% jsjinja %}{% macro x(s) %}{{s}}{% endmacro %}{% endjsjinja %}'''
43 | t = env.from_string(template)
44 | js = str(t.render())
45 | print js
46 | ex = ctx.eval('(new (%s))'%js).module().x("test")
47 | assert ex == "test"
48 |
49 | code = env.jsjinja.generate_all()
50 | # raise Exception(code)
51 | ctx.eval(code)
52 |
53 | context = {'context':True}
54 |
55 |
56 | def compare_templates(f):
57 | jinja_template = env.get_template(f).render(context)
58 | # raise Exception(ctx.locals.Jinja2.templates[f])
59 | js_template = ctx.locals.Jinja2.getTemplate(f)
60 | js_template_rendered = unicode(js_template.render(context),'utf-8')
61 | print 'JS TEMPLATE:\n',js_template
62 | print 'Jinja:\n',jinja_template
63 | print 'Js:\n',js_template_rendered
64 | assert jinja_template == js_template_rendered
65 |
66 | def test_case_generator():
67 | templates = env.list_templates()
68 | for f in templates:
69 | yield compare_templates, f
70 |
71 | def main():
72 | """Runs the testsuite as command line application."""
73 | import nose
74 | try:
75 | nose.main(defaultTest="")
76 | except Exception, e:
77 | print 'Error: %s' % e
78 |
79 | if __name__ == '__main__':
80 | main()
81 | # for dirname, dirnames, filenames in os.walk(TEMPLATE_FOLDER):
82 |
83 |
84 |
85 | # print gen2('''{% extends "a" %}
86 | # {% block c %}
87 | # {% for i in x %}{{i}}:::{{loop.index}}
88 | # {% endfor %}
89 | # {% endblock %}
90 | # {% macro for1(datsa) -%}{{data}}{% endmacro %}
91 | # {% block a %}{{ super() }}
92 | # {% include "x" %}
93 | # {{a|capitalize}}
94 | # a
95 | # {{for1(s)}}
96 | # {% endblock %}''')
97 |
98 | # print gen('''{% extends "a" %}''')
99 | # t = '''{% set s=2 %}{% macro for1(data,dos,tres=3) -%}{{data}}{% set s=3233223 %}{{s}}{% endmacro %}{{for1(1)}}{{s}}'''
100 | # print gen(t)
101 | # template = env.from_string(t)
102 | # print template.render()
103 | # print gen('''{% from a import b %}''')
104 | # print gen2('''{% extends 'a' %}''')
105 |
106 |
107 | # print gen2('''{% set a = 2*2 or 7 %}{{a}}''')
108 | # print gen2('''{% for a,b in x(c) if a==1 %}{{loop}}{% for s in sa %}{{loop}}{% endfor %}{% else %}s{% endfor %}''')
109 |
110 | # filename = '%s%s'%(TEMPLATE_FOLDER,'include.tmpl')
111 | # template_string = open(filename).read()
112 | # code = gen2(template_string, filename)
113 |
114 |
115 | # with open('runtime.js', 'r') as f:
116 | # ctx.eval(f.read())
117 |
118 | # try:
119 | # # t = ctx.eval('new (%s)'%code).render(context)
120 | # t = ctx.eval(code)
121 | # # print code
122 | # js_template = ctx.locals.Jinja2.get(filename)
123 | # # print js_template.module().x(233)
124 | # s1 = env.from_string(template_string).render(context)
125 | # s2 = js_template.render(context)
126 | # except Exception, e:
127 | # print code
128 | # raise e
129 |
130 | # t = ctx.eval('new %s()'%code)
131 | # print t.prototype.root({'vars':{}})
132 | # print gen2('''{% extends 'a' %}{% set p=2 %}''')
133 | # print gen2('''{% extends 'a' %}{% block a %}{{s}}{% endblock %}s{% block r %}{{super}}{% endblock %}''')
134 | # print gen2('''{% set c=True %}{% if d or c %}{{a}}{% else %}{{d}}{% endif %}''')
135 |
136 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | JsJinja: Render Jinja2 Templates in JS
2 | =======================================
3 |
4 | JsJinja lets you use your [Jinja2](http://jinja.pocoo.org/) templates
5 | in Javascript. It **compile the Jinja2 templates to Javascript with
6 | no restrictions**.
7 |
8 | The js can be generated via command line `jsjinja ` or through the `{% jsjinja %}` tag in the templates.
9 |
10 | You can use:
11 |
12 | * Template inheritance (Include, Extends, Blocks, super, ...)
13 | * Import
14 | * Macros
15 | * Tests
16 | * Filters
17 | * Tags
18 |
19 | The only exception is that **you cannot use custom tags** like `{% customtag %}{% endcustomtag %}`.
20 |
21 | ## Installing
22 |
23 | First, you must do:
24 |
25 | ```
26 | pip install jsjinja
27 | ```
28 |
29 | # Nutshell
30 |
31 | Here a small example of a Jinja template:
32 |
33 | ```html
34 | {% extends 'base.html' %}
35 | {% block title %}Memberlist{% endblock %}
36 | {% block content %}
37 |
42 | {% endblock %}
43 | ```
44 |
45 | And here is the javascript compiled template:
46 |
47 | ```js
48 | (function() {
49 | Jinja2.extends(Template, Jinja2.Template);
50 | Jinja2.registerTemplate("readme_template.tmpl", Template);
51 | function Template() {return Template.__super__.constructor.apply(this, arguments);};
52 | Template.prototype.root = function (context) {
53 | var buf = "";
54 | var parent_template = Jinja2.getTemplate("base.html", "readme_template.tmpl");
55 | for (name in parent_template.blocks) {
56 | var parent_block = parent_template.blocks[name];
57 | context.blocks[name] = context.blocks[name] || [];
58 | context.blocks[name].push(parent_block)
59 | }
60 | buf += parent_template.root(context);
61 | return buf;
62 | }
63 | Template.prototype.block_content = function (context) {
64 | var buf = "";
65 | var l_users = context.resolve("users");
66 | buf += "\n \n";
79 | return buf;
80 | }
81 | Template.prototype.block_title = function (context) {
82 | var buf = "";
83 | buf += "Memberlist";
84 | return buf;
85 | }
86 | return Template;
87 | })()
88 | ```
89 |
90 | # Installation
91 |
92 | For begin using JsJinja just add `jsjinja.ext.JsJinjaExtension` to your Jinja2 Environment.
93 |
94 | Example:
95 |
96 | ```python
97 | import jinja2
98 | env = jinja2.Environment(extensions=['jsjinja.ext.JsJinjaExtension',])
99 | ```
100 |
101 | Or:
102 |
103 | ```python
104 | jinja_env.add_extension('jsjinja.ext.JsJinjaExtension')
105 | ```
106 |
107 | # Usage
108 |
109 | ## Generating js templates
110 |
111 | Once you have the JsJinja extension installed, you have to generate the js templates:
112 |
113 | ```python
114 | print jinja_env.jsjinja.generate('your_template.jinja2')
115 | ```
116 |
117 | Or just converting all
118 |
119 | ```python
120 | print jinja_env.jsjinja.generate_all()
121 | ```
122 |
123 | Or using the **command line utility**
124 |
125 | ```
126 | jsjinja
127 | ```
128 |
129 |
130 | ## Rendering the js templates
131 |
132 | For start using the templates you must include the `jinja2.runtime.js` script:
133 |
134 | ```html
135 |
136 | ```
137 |
138 | After you have included `jinja2.runtime.js` and the generated js templates, then
139 |
140 | ```js
141 | html = Jinja2.getTemplate("template.html").render({}})
142 | $('body').html(html)
143 | ```
144 |
145 | # Examples
146 |
147 | Library comes with a lot of examples, you can find them in [examples](https://github.com/SyrusAkbary/jsjinja/tree/master/examples/) directory.
148 |
149 | * [Static](https://github.com/SyrusAkbary/jsjinja/tree/master/examples/static) generation
150 | * [Dynamic](https://github.com/SyrusAkbary/jsjinja/tree/master/examples/dynamic) generation
151 |
152 |
153 | # Testing
154 |
155 | You must have `pyv8` and `nose` python packages installed. You can do the tests with
156 |
157 | ```shell
158 | ./test.sh
159 | ```
160 |
161 |
162 | # TODOs and BUGS
163 |
164 | See: http://github.com/syrusakbary/jsjinja/issues
165 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | JsJinja: Render Jinja2 Templates in JS
2 | ======================================
3 |
4 | JsJinja lets you use your `Jinja2`_ templates in Javascript. It
5 | **compile the Jinja2 templates to Javascript with no restrictions**.
6 |
7 | The js can be generated via command line ``jsjinja `` or
8 | through the ``{% jsjinja %}`` tag in the templates.
9 |
10 | You can use:
11 |
12 | - Template inheritance (Include, Extends, Blocks, super, …)
13 | - Import
14 | - Macros
15 | - Tests
16 | - Filters
17 | - Tags
18 |
19 | The only exception is that **you cannot use custom tags** like
20 | ``{% customtag %}{% endcustomtag %}``.
21 |
22 |
23 | Installing
24 | ----------
25 |
26 | First, you must do:
27 |
28 | ::
29 |
30 | pip install jsjinja
31 |
32 |
33 | Nutshell
34 | ========
35 |
36 | Here a small example of a Jinja template:
37 |
38 | .. code:: html+django
39 |
40 | {% extends 'base.html' %}
41 | {% block title %}Memberlist{% endblock %}
42 | {% block content %}
43 |
48 | {% endblock %}
49 |
50 | And here is the javascript compiled template:
51 |
52 | .. code:: javascript
53 |
54 | (function() {
55 | Jinja2.extends(Template, Jinja2.Template);
56 | Jinja2.registerTemplate("readme_template.tmpl", Template);
57 | function Template() {return Template.__super__.constructor.apply(this, arguments);};
58 | Template.prototype.root = function (context) {
59 | var buf = "";
60 | var parent_template = Jinja2.getTemplate("base.html", "readme_template.tmpl");
61 | for (name in parent_template.blocks) {
62 | var parent_block = parent_template.blocks[name];
63 | context.blocks[name] = context.blocks[name] || [];
64 | context.blocks[name].push(parent_block)
65 | }
66 | buf += parent_template.root(context);
67 | return buf;
68 | }
69 | Template.prototype.block_content = function (context) {
70 | var buf = "";
71 | var l_users = context.resolve("users");
72 | buf += "\n \n";
85 | return buf;
86 | }
87 | Template.prototype.block_title = function (context) {
88 | var buf = "";
89 | buf += "Memberlist";
90 | return buf;
91 | }
92 | return Template;
93 | })()
94 |
95 | Installation
96 | ============
97 |
98 | For begin using JsJinja just add ``jsjinja.ext.JsJinjaExtension`` to
99 | your Jinja2 Environment.
100 |
101 | Example:
102 |
103 | .. code:: python
104 |
105 | import jinja2
106 | env = jinja2.Environment(extensions=['jsjinja.ext.JsJinjaExtension',])
107 |
108 | Or:
109 |
110 | .. code:: python
111 |
112 | jinja_env.add_extension('jsjinja.ext.JsJinjaExtension')
113 |
114 | Usage
115 | =====
116 |
117 | Generating js templates
118 | -----------------------
119 |
120 | Once you have the JsJinja extension installed, you have to generate the
121 | js templates:
122 |
123 | .. code:: python
124 |
125 | print jinja_env.jsjinja.generate('your_template.jinja2')
126 |
127 | Or just converting all
128 |
129 | .. code:: python
130 |
131 | print jinja_env.jsjinja.generate_all()
132 |
133 | Or using the **command line utility**
134 |
135 | ::
136 |
137 | jsjinja
138 |
139 | Rendering the js templates
140 | --------------------------
141 |
142 | For start using the templates you must include the ``jinja2.runtime.js``
143 | script:
144 |
145 | .. code:: html
146 |
147 |
148 |
149 | After you have included ``jinja2.runtime.js`` and the generated js
150 | templates, then
151 |
152 | .. code:: javascript
153 |
154 | html = Jinja2.getTemplate("template.html").render({}})
155 | $('body').html(html)
156 |
157 | Examples
158 | ========
159 |
160 | Library comes with a lot of examples, you can find them in `examples`_
161 | directory.
162 |
163 | - `Static`_ generation
164 | - `Dynamic`_ generation
165 |
166 | Testing
167 | =======
168 |
169 | You must have ``pyv8`` and ``nose`` python packages installed. You can
170 | do the tests with
171 |
172 | ::
173 |
174 | ./test.sh
175 |
176 | TODOs and BUGS
177 | ==============
178 |
179 | See: http://github.com/syrusakbary/jsjinja/issues
180 |
181 | .. _Jinja2: http://jinja.pocoo.org/
182 | .. _examples: https://github.com/SyrusAkbary/jsjinja/tree/master/examples/
183 | .. _Static: https://github.com/SyrusAkbary/jsjinja/tree/master/examples/static
184 | .. _Dynamic: https://github.com/SyrusAkbary/jsjinja/tree/master/examples/dynamic
185 |
--------------------------------------------------------------------------------
/jsjinja/src/runtime.coffee:
--------------------------------------------------------------------------------
1 | `__hasProp = {}.hasOwnProperty,
2 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
3 | `
4 | root = exports ? this
5 |
6 | clone = (obj) ->
7 | target = {}
8 | for i of obj
9 | target[i] = obj[i] if obj.hasOwnProperty(i)
10 | target
11 |
12 | merge_options = (obj1, obj2) ->
13 | obj3 = {}
14 | for attrname of obj1
15 | obj3[attrname] = obj1[attrname]
16 | for attrname of obj2
17 | obj3[attrname] = obj2[attrname]
18 | obj3
19 |
20 | new_context = (template_name, blocks, vars, shared, globals, locals) ->
21 | vars = vars or {}
22 | parent = (if shared then vars else merge_options(globals or {}, vars))
23 | if locals
24 | if shared
25 | parent = clone(parent)
26 | for attrname of locals
27 | parent[attrname] = locals[attrname] if locals[attrname] isnt Jinja2.utils.missing
28 | new Context(parent, template_name, blocks)
29 |
30 | class Set
31 | add: (o) ->
32 | @[o] = true
33 | remove: (o) ->
34 | delete @[o]
35 |
36 | class Template
37 | constructor: ->
38 | @blocks = {}
39 | for key of @
40 | if key.indexOf('block_')==0
41 | k = key.slice(6)
42 | @blocks[k] = this[key]
43 | @blocks[k].__append_context__ = true
44 |
45 | root: ->
46 |
47 | new_context: (vars, shared, locals) ->
48 | new_context @name, @blocks, vars, !!shared, @globals, locals
49 |
50 | render: (obj) ->
51 | @root @new_context(obj)
52 |
53 | render_block: (name, obj) ->
54 | context = new Context(null, null, @blocks)
55 | context.vars = obj
56 | @["block_#{name}"] context
57 |
58 | module: ->
59 | context = new Context
60 | @root context
61 | module = {}
62 | for key of context.exported_vars
63 | module[key] = context.vars[key]
64 | module
65 |
66 | new_context: (vars, shared, locals) ->
67 | new_context @name, @blocks, vars, shared, @globals, locals
68 |
69 | class Context
70 | constructor: (@parent, name, blocks) ->
71 | @vars = {}
72 | @blocks = {}
73 | for block_name of blocks
74 | @blocks[block_name] = [blocks[block_name]]
75 | @exported_vars = new Set()
76 | super: (name, current) ->
77 | blocks = @blocks[name]
78 | index = blocks.indexOf(current) + 1
79 | blocks[index]
80 |
81 | resolve: (key) ->
82 | if @vars?.hasOwnProperty(key)
83 | return @vars[key]
84 | if @parent?.resolve
85 | return @parent.resolve(key)
86 | if @parent?.hasOwnProperty(key)
87 | return @parent[key]
88 | return Jinja2.globals[key]
89 |
90 | call: (f, args, kwargs) ->
91 | return if not f
92 | call_args = if not f.__args__ then args else []
93 | if f.__append_context__
94 | call_args.push(@)
95 | if f.__append_args__
96 | call_args.push(args)
97 | if f.__append_kwargs__
98 | call_args.push(kwargs)
99 |
100 | for arg of f.__args__
101 | call_args.push kwargs[f.__args__?[arg]] or args.pop()
102 | f.apply (f.constructor or null), call_args
103 |
104 | callfilter: (f, preargs, args, kwargs) ->
105 | return if not f
106 | call_args = preargs
107 | for arg of f.__args__
108 | call_args.push kwargs[f.__args__[arg]] or args.pop()
109 | f.apply null, call_args
110 |
111 |
112 | Jinja2 =
113 |
114 | version: 0.2
115 |
116 | templates: {}
117 |
118 | filters: {}
119 |
120 | globals: {}
121 |
122 | tests: {}
123 |
124 | registerGlobal: (key, value) ->
125 | @globals[key] = value
126 |
127 | registerFilter: (name, func) ->
128 | @filters[name] = func
129 |
130 | registerTest: (name, func) ->
131 | @tests[name] = func
132 |
133 | getFilter: (name) ->
134 | @filters[name]
135 |
136 | registerTemplate: (name, template) ->
137 | @templates[name] = template
138 |
139 | getTemplate: (name, from) ->
140 | new @templates[name]
141 |
142 | utils:
143 | to_string: (x) ->
144 | (if x then String(x) else "")
145 |
146 | missing: `undefined`
147 |
148 | format: (str,arr) ->
149 | callback = (exp, p0, p1, p2, p3, p4) ->
150 | return "%" if exp is "%%"
151 | return `undefined` if arr[++i] is `undefined`
152 | exp = (if p2 then parseInt(p2.substr(1)) else `undefined`)
153 | base = (if p3 then parseInt(p3.substr(1)) else `undefined`)
154 | val = undefined
155 | switch p4
156 | when "s"
157 | val = arr[i]
158 | when "c"
159 | val = arr[i][0]
160 | when "f"
161 | val = parseFloat(arr[i]).toFixed(exp)
162 | when "p"
163 | val = parseFloat(arr[i]).toPrecision(exp)
164 | when "e"
165 | val = parseFloat(arr[i]).toExponential(exp)
166 | when "x"
167 | val = parseInt(arr[i]).toString((if base then base else 16))
168 | when "d"
169 | val = parseFloat(parseInt(arr[i], (if base then base else 10)).toPrecision(exp)).toFixed(0)
170 | val = (if typeof (val) is "object" then JSON.stringify(val) else val.toString(base))
171 | sz = parseInt(p1) # padding size
172 | ch = (if p1 and p1[0] is "0" then "0" else " ") # isnull?
173 | val = (if p0 isnt `undefined` then val + ch else ch + val) while val.length < sz # isminus?
174 | val
175 | i = -1
176 | regex = /%(-)?(0?[0-9]+)?([.][0-9]+)?([#][0-9]+)?([scfpexd])/g
177 | str.replace regex, callback
178 |
179 | loop: (i, len) ->
180 | first: i is 0
181 | last: i is (len - 1)
182 | index: i + 1
183 | index0: i
184 | revindex: len - i
185 | revindex0: len - i - 1
186 | length: len
187 | cycle: -> arguments[i%arguments.length]
188 | __extends: `__extends`
189 | Template: Template
190 | Context: Context
191 |
192 | root.Jinja2 = Jinja2
--------------------------------------------------------------------------------
/jsjinja/lib/jinja2.runtime.min.js:
--------------------------------------------------------------------------------
1 | (function(){__hasProp={}.hasOwnProperty;__extends=function(a,b){function c(){this.constructor=a}for(var e in b)__hasProp.call(b,e)&&(a[e]=b[e]);c.prototype=b.prototype;a.prototype=new c;a.__super__=b.prototype;return a};var k,d,p,q,m,r,s=[].indexOf||function(a){for(var b=0,c=this.length;bd&&
9 | (d=-d,c>b))for(;c>b+1;)a.push(c),c-=d;return a});d.registerGlobal("dict",function(){var a;a=function(a){return a};a.__append_kwargs__=!0;return a}());d.registerGlobal("cycler",function(){var a;a={items:arguments,reset:function(){return a._setPos(0)},_setPos:function(b){a.pos=b;return a.current=a.items[b]},next:function(){var b;b=a.current;a._setPos((a.pos+1)%a.items.length);return b}};a.reset();return a});d.registerGlobal("joiner",function(a){var b;a||(a=",");b=!1;return function(){if(b)return a;
10 | b=!0;return""}});d.registerFilter("length",function(a){return a.length});d.registerFilter("count",function(a){return a.length});d.registerFilter("indent",function(a,b,c){b||(b=4);b=b?Array(b+1).join(" "):"";return(c?a:a.replace(/\n$/,"")).replace(/\n/g,"\n"+b)});d.registerFilter("random",function(a,b){if(b)return b[Math.floor(Math.random()*b.length)]});d.registerFilter("last",function(a,b){if(b)return b[b.length-1]});d.registerFilter("first",function(a,b){if(b)return b[0]});d.registerFilter("title",
11 | function(a){return a.replace(/\w\S*/g,function(a){return a.charAt(0).toUpperCase()+a.substr(1).toLowerCase()})});d.registerFilter("lower",function(a){return a.toLowerCase()});d.registerFilter("upper",function(a){return a.toUpperCase()});d.registerFilter("capitalize",function(a){return a.charAt(0).toUpperCase()+a.slice(1)});d.registerFilter("escape",function(a){return String(a).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")});d.registerFilter("default",function(a,
12 | b,c){return c&&!a||void 0===a?b:a});d.registerFilter("truncate",function(a,b,c,d){b||(b=255);d||(d="...");if(a.length<=b)return a;if(c)return a.substring(0,b);a=a.substring(0,maxLength+1);a=a.substring(0,Math.min(a.length,a.lastIndexOf(" ")));return a+d});d.registerTest("callable",function(){return!(!obj||!obj.constructor||!obj.call||!obj.apply)});d.registerTest("odd",function(a){return 1===a%2});d.registerTest("even",function(a){return 0===a%2});d.registerTest("divisibleby",function(a,b){return 0===
13 | a%b});d.registerTest("defined",function(a){return"undefined"!==typeof a});d.registerTest("undefined",function(a){return"undefined"===typeof a});d.registerTest("none",function(a){return null===a});d.registerTest("lower",function(a){return a===a.toLowerCase()});d.registerTest("upper",function(a){return a===a.toUpperCase()});d.registerTest("string",function(a){return"[object String]"===toString.call(a)});d.registerTest("mapping",function(a){return a===Object(a)});d.registerTest("number",function(a){return"[object Number]"===
14 | toString.call(a)});d.registerTest("sequence",function(a){return"[object Array]"===toString.call(a)});d.registerTest("sameas",function(a,b){return a===b});d.registerTest("iterable",function(a){return"[object Array]"===toString.call(a)});d.registerTest("escaped",function(a){return 0<=s.call(a,"__html__")})}).call(this);
15 |
--------------------------------------------------------------------------------
/examples/static/templates.js:
--------------------------------------------------------------------------------
1 | ;(function(){__hasProp={}.hasOwnProperty;__extends=function(a,b){function e(){this.constructor=a}for(var c in b)__hasProp.call(b,c)&&(a[c]=b[c]);e.prototype=b.prototype;a.prototype=new e;a.__super__=b.prototype;return a};var g,c,k,l,m=[].indexOf||function(a){for(var b=0,e=this.length;bd&&(d=-d,c>b))for(;c>b+1;)a.push(c),c-=d;return a});c.registerGlobal("dict",function(){var a;a=function(a){return a};a.__append_kwargs__=!0;return a}());c.registerGlobal("cycler",function(){var a;a={items:arguments,reset:function(){return a._setPos(0)},_setPos:function(b){a.pos=b;return a.current=
7 | a.items[b]},next:function(){var b;b=a.current;a._setPos((a.pos+1)%a.items.length);return b}};a.reset();return a});c.registerGlobal("joiner",function(a){var b;a||(a=",");b=!1;return function(){if(b)return a;b=!0;return""}});c.registerFilter("length",function(a){return a.length});c.registerFilter("count",function(a){return a.length});c.registerFilter("indent",function(a,b,c){b||(b=4);b=b?Array(b+1).join(" "):"";return(c?a:a.replace(/\n$/,"")).replace(/\n/g,"\n"+b)});c.registerFilter("random",function(a,
8 | b){if(b)return b[Math.floor(Math.random()*b.length)]});c.registerFilter("last",function(a,b){if(b)return b[b.length-1]});c.registerFilter("first",function(a,b){if(b)return b[0]});c.registerFilter("title",function(a){return a.replace(/\w\S*/g,function(a){return a.charAt(0).toUpperCase()+a.substr(1).toLowerCase()})});c.registerFilter("lower",function(a){return a.toLowerCase()});c.registerFilter("upper",function(a){return a.toUpperCase()});c.registerFilter("capitalize",function(a){return a.charAt(0).toUpperCase()+
9 | a.slice(1)});c.registerFilter("escape",function(a){return String(a).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")});c.registerFilter("default",function(a,b,c){return c&&!a||void 0===a?b:a});c.registerFilter("truncate",function(a,b,c,d){b||(b=255);d||(d="...");if(a.length<=b)return a;if(c)return a.substring(0,b);a=a.substring(0,maxLength+1);a=a.substring(0,Math.min(a.length,a.lastIndexOf(" ")));return a+d});c.registerTest("callable",function(){return!(!obj||
10 | !obj.constructor||!obj.call||!obj.apply)});c.registerTest("odd",function(a){return 1===a%2});c.registerTest("even",function(a){return 0===a%2});c.registerTest("divisibleby",function(a,b){return 0===a%b});c.registerTest("defined",function(a){return"undefined"!==typeof a});c.registerTest("undefined",function(a){return"undefined"===typeof a});c.registerTest("none",function(a){return null===a});c.registerTest("lower",function(a){return a===a.toLowerCase()});c.registerTest("upper",function(a){return a===
11 | a.toUpperCase()});c.registerTest("string",function(a){return"[object String]"===toString.call(a)});c.registerTest("mapping",function(a){return a===Object(a)});c.registerTest("number",function(a){return"[object Number]"===toString.call(a)});c.registerTest("sequence",function(a){return"[object Array]"===toString.call(a)});c.registerTest("sameas",function(a,b){return a===b});c.registerTest("iterable",function(a){return"[object Array]"===toString.call(a)});c.registerTest("escaped",function(a){return 0<=
12 | m.call(a,"__html__")})}).call(this);
13 | ;
14 | (function() {
15 | /* base.html */
16 | Jinja2.extends(Template, Jinja2.Template);
17 | Jinja2.registerTemplate("base.html", Template);
18 | function Template() {return Template.__super__.constructor.apply(this, arguments);};
19 | Template.prototype.root = function (context) {
20 | var buf = "";
21 | buf += "Site template\n";
22 | buf += context.blocks['content'][0](context);
23 | return buf;
24 | }
25 | Template.prototype.block_content = function (context) {
26 | var buf = "";
27 | buf += "\nThis is the main content
\n";
28 | return buf;
29 | }
30 | return Template;
31 | })();
32 | (function() {
33 | /* layout.html */
34 | Jinja2.extends(Template, Jinja2.Template);
35 | Jinja2.registerTemplate("layout.html", Template);
36 | function Template() {return Template.__super__.constructor.apply(this, arguments);};
37 | Template.prototype.root = function (context) {
38 | var buf = "";
39 | var parent_template = Jinja2.getTemplate("base.html", "layout.html");
40 | for (name in parent_template.blocks) {
41 | var parent_block = parent_template.blocks[name];
42 | context.blocks[name] = context.blocks[name] || [];
43 | context.blocks[name].push(parent_block)
44 | }
45 | buf += parent_template.root(context);
46 | return buf;
47 | }
48 | Template.prototype.block_content = function (context) {
49 | var buf = "";
50 | var l_super = context.super("content", Template.prototype.block_content)
51 | var l_sites = context.resolve("sites");
52 | t_1 = Jinja2.filters["count"]
53 | buf += "\n ";
54 | buf += context.call(l_super, [], {});
55 | buf += "\n Total sites: ";
56 | buf += context.callfilter(t_1, [l_sites], [], {});
57 | buf += "
\n \n";
70 | return buf;
71 | }
72 | return Template;
73 | })();
--------------------------------------------------------------------------------
/jsjinja/lib/jinja2.runtime.js:
--------------------------------------------------------------------------------
1 | // Generated by CoffeeScript 1.6.1
2 | (function() {
3 | __hasProp = {}.hasOwnProperty,
4 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
5 | ;
6 | var Context, Jinja2, Set, Template, clone, merge_options, new_context, root,
7 | __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
8 |
9 | root = typeof exports !== "undefined" && exports !== null ? exports : this;
10 |
11 | clone = function(obj) {
12 | var i, target;
13 | target = {};
14 | for (i in obj) {
15 | if (obj.hasOwnProperty(i)) {
16 | target[i] = obj[i];
17 | }
18 | }
19 | return target;
20 | };
21 |
22 | merge_options = function(obj1, obj2) {
23 | var attrname, obj3;
24 | obj3 = {};
25 | for (attrname in obj1) {
26 | obj3[attrname] = obj1[attrname];
27 | }
28 | for (attrname in obj2) {
29 | obj3[attrname] = obj2[attrname];
30 | }
31 | return obj3;
32 | };
33 |
34 | new_context = function(template_name, blocks, vars, shared, globals, locals) {
35 | var attrname, parent;
36 | vars = vars || {};
37 | parent = (shared ? vars : merge_options(globals || {}, vars));
38 | if (locals) {
39 | if (shared) {
40 | parent = clone(parent);
41 | }
42 | for (attrname in locals) {
43 | if (locals[attrname] !== Jinja2.utils.missing) {
44 | parent[attrname] = locals[attrname];
45 | }
46 | }
47 | }
48 | return new Context(parent, template_name, blocks);
49 | };
50 |
51 | Set = (function() {
52 |
53 | function Set() {}
54 |
55 | Set.prototype.add = function(o) {
56 | return this[o] = true;
57 | };
58 |
59 | Set.prototype.remove = function(o) {
60 | return delete this[o];
61 | };
62 |
63 | return Set;
64 |
65 | })();
66 |
67 | Template = (function() {
68 |
69 | function Template() {
70 | var k, key;
71 | this.blocks = {};
72 | for (key in this) {
73 | if (key.indexOf('block_') === 0) {
74 | k = key.slice(6);
75 | this.blocks[k] = this[key];
76 | this.blocks[k].__append_context__ = true;
77 | }
78 | }
79 | }
80 |
81 | Template.prototype.root = function() {};
82 |
83 | Template.prototype.new_context = function(vars, shared, locals) {
84 | return new_context(this.name, this.blocks, vars, !!shared, this.globals, locals);
85 | };
86 |
87 | Template.prototype.render = function(obj) {
88 | return this.root(this.new_context(obj));
89 | };
90 |
91 | Template.prototype.render_block = function(name, obj) {
92 | var context;
93 | context = new Context(null, null, this.blocks);
94 | context.vars = obj;
95 | return this["block_" + name](context);
96 | };
97 |
98 | Template.prototype.module = function() {
99 | var context, key, module;
100 | context = new Context;
101 | this.root(context);
102 | module = {};
103 | for (key in context.exported_vars) {
104 | module[key] = context.vars[key];
105 | }
106 | return module;
107 | };
108 |
109 | Template.prototype.new_context = function(vars, shared, locals) {
110 | return new_context(this.name, this.blocks, vars, shared, this.globals, locals);
111 | };
112 |
113 | return Template;
114 |
115 | })();
116 |
117 | Context = (function() {
118 |
119 | function Context(parent, name, blocks) {
120 | var block_name;
121 | this.parent = parent;
122 | this.vars = {};
123 | this.blocks = {};
124 | for (block_name in blocks) {
125 | this.blocks[block_name] = [blocks[block_name]];
126 | }
127 | this.exported_vars = new Set();
128 | }
129 |
130 | Context.prototype["super"] = function(name, current) {
131 | var blocks, index;
132 | blocks = this.blocks[name];
133 | index = blocks.indexOf(current) + 1;
134 | return blocks[index];
135 | };
136 |
137 | Context.prototype.resolve = function(key) {
138 | var _ref, _ref1, _ref2;
139 | if ((_ref = this.vars) != null ? _ref.hasOwnProperty(key) : void 0) {
140 | return this.vars[key];
141 | }
142 | if ((_ref1 = this.parent) != null ? _ref1.resolve : void 0) {
143 | return this.parent.resolve(key);
144 | }
145 | if ((_ref2 = this.parent) != null ? _ref2.hasOwnProperty(key) : void 0) {
146 | return this.parent[key];
147 | }
148 | return Jinja2.globals[key];
149 | };
150 |
151 | Context.prototype.call = function(f, args, kwargs) {
152 | var arg, call_args, _ref;
153 | if (!f) {
154 | return;
155 | }
156 | call_args = !f.__args__ ? args : [];
157 | if (f.__append_context__) {
158 | call_args.push(this);
159 | }
160 | if (f.__append_args__) {
161 | call_args.push(args);
162 | }
163 | if (f.__append_kwargs__) {
164 | call_args.push(kwargs);
165 | }
166 | for (arg in f.__args__) {
167 | call_args.push(kwargs[(_ref = f.__args__) != null ? _ref[arg] : void 0] || args.pop());
168 | }
169 | return f.apply(f.constructor || null, call_args);
170 | };
171 |
172 | Context.prototype.callfilter = function(f, preargs, args, kwargs) {
173 | var arg, call_args;
174 | if (!f) {
175 | return;
176 | }
177 | call_args = preargs;
178 | for (arg in f.__args__) {
179 | call_args.push(kwargs[f.__args__[arg]] || args.pop());
180 | }
181 | return f.apply(null, call_args);
182 | };
183 |
184 | return Context;
185 |
186 | })();
187 |
188 | Jinja2 = {
189 | version: 0.2,
190 | templates: {},
191 | filters: {},
192 | globals: {},
193 | tests: {},
194 | registerGlobal: function(key, value) {
195 | return this.globals[key] = value;
196 | },
197 | registerFilter: function(name, func) {
198 | return this.filters[name] = func;
199 | },
200 | registerTest: function(name, func) {
201 | return this.tests[name] = func;
202 | },
203 | getFilter: function(name) {
204 | return this.filters[name];
205 | },
206 | registerTemplate: function(name, template) {
207 | return this.templates[name] = template;
208 | },
209 | getTemplate: function(name, from) {
210 | return new this.templates[name];
211 | },
212 | utils: {
213 | to_string: function(x) {
214 | if (x) {
215 | return String(x);
216 | } else {
217 | return "";
218 | }
219 | },
220 | missing: undefined,
221 | format: function(str, arr) {
222 | var callback, i, regex;
223 | callback = function(exp, p0, p1, p2, p3, p4) {
224 | var base, ch, sz, val;
225 | if (exp === "%%") {
226 | return "%";
227 | }
228 | if (arr[++i] === undefined) {
229 | return undefined;
230 | }
231 | exp = (p2 ? parseInt(p2.substr(1)) : undefined);
232 | base = (p3 ? parseInt(p3.substr(1)) : undefined);
233 | val = void 0;
234 | switch (p4) {
235 | case "s":
236 | val = arr[i];
237 | break;
238 | case "c":
239 | val = arr[i][0];
240 | break;
241 | case "f":
242 | val = parseFloat(arr[i]).toFixed(exp);
243 | break;
244 | case "p":
245 | val = parseFloat(arr[i]).toPrecision(exp);
246 | break;
247 | case "e":
248 | val = parseFloat(arr[i]).toExponential(exp);
249 | break;
250 | case "x":
251 | val = parseInt(arr[i]).toString((base ? base : 16));
252 | break;
253 | case "d":
254 | val = parseFloat(parseInt(arr[i], (base ? base : 10)).toPrecision(exp)).toFixed(0);
255 | }
256 | val = (typeof val === "object" ? JSON.stringify(val) : val.toString(base));
257 | sz = parseInt(p1);
258 | ch = (p1 && p1[0] === "0" ? "0" : " ");
259 | while (val.length < sz) {
260 | val = (p0 !== undefined ? val + ch : ch + val);
261 | }
262 | return val;
263 | };
264 | i = -1;
265 | regex = /%(-)?(0?[0-9]+)?([.][0-9]+)?([#][0-9]+)?([scfpexd])/g;
266 | return str.replace(regex, callback);
267 | },
268 | loop: function(i, len) {
269 | return {
270 | first: i === 0,
271 | last: i === (len - 1),
272 | index: i + 1,
273 | index0: i,
274 | revindex: len - i,
275 | revindex0: len - i - 1,
276 | length: len,
277 | cycle: function() {
278 | return arguments[i % arguments.length];
279 | }
280 | };
281 | }
282 | },
283 | "__extends": __extends,
284 | Template: Template,
285 | Context: Context
286 | };
287 |
288 | root.Jinja2 = Jinja2;
289 |
290 | Jinja2.registerGlobal('range', function() {
291 | var array, end, i, s, start, step;
292 | start = void 0;
293 | end = void 0;
294 | step = void 0;
295 | array = [];
296 | switch (arguments.length) {
297 | case 1:
298 | start = 0;
299 | end = Math.floor(arguments[0]) - 1;
300 | step = 1;
301 | break;
302 | case 2:
303 | case 3:
304 | start = Math.floor(arguments[0]);
305 | end = Math.floor(arguments[1]) - 1;
306 | s = arguments[2];
307 | if (typeof s === "undefined") {
308 | s = 1;
309 | }
310 | step = Math.floor(s);
311 | }
312 | if (step > 0) {
313 | i = start;
314 | while (i <= end) {
315 | array.push(i);
316 | i += step;
317 | }
318 | } else if (step < 0) {
319 | step = -step;
320 | if (start > end) {
321 | i = start;
322 | while (i > end + 1) {
323 | array.push(i);
324 | i -= step;
325 | }
326 | }
327 | }
328 | return array;
329 | });
330 |
331 | Jinja2.registerGlobal('dict', (function() {
332 | var func;
333 | func = function(obj) {
334 | return obj;
335 | };
336 | func.__append_kwargs__ = true;
337 | return func;
338 | })());
339 |
340 | Jinja2.registerGlobal('cycler', function() {
341 | var cycler;
342 | cycler = {
343 | items: arguments,
344 | reset: function() {
345 | return cycler._setPos(0);
346 | },
347 | _setPos: function(pos) {
348 | cycler.pos = pos;
349 | return cycler.current = cycler.items[pos];
350 | },
351 | next: function() {
352 | var rv;
353 | rv = cycler.current;
354 | cycler._setPos((cycler.pos + 1) % cycler.items.length);
355 | return rv;
356 | }
357 | };
358 | cycler.reset();
359 | return cycler;
360 | });
361 |
362 | Jinja2.registerGlobal('joiner', function(sep) {
363 | var used;
364 | sep || (sep = ',');
365 | used = false;
366 | return function() {
367 | if (!used) {
368 | used = true;
369 | return '';
370 | } else {
371 | return sep;
372 | }
373 | };
374 | });
375 |
376 | Jinja2.registerFilter('length', function(obj) {
377 | return obj.length;
378 | });
379 |
380 | Jinja2.registerFilter('count', function(obj) {
381 | return obj.length;
382 | });
383 |
384 | Jinja2.registerFilter('indent', function(str, width, indentfirst) {
385 | var indention;
386 | width || (width = 4);
387 | indention = width ? Array(width + 1).join(" ") : "";
388 | return (indentfirst ? str : str.replace(/\n$/, '')).replace(/\n/g, "\n" + indention);
389 | });
390 |
391 | Jinja2.registerFilter('random', function(environment, seq) {
392 | if (seq) {
393 | return seq[Math.floor(Math.random() * seq.length)];
394 | } else {
395 | return undefined;
396 | }
397 | });
398 |
399 | Jinja2.registerFilter('last', function(environment, seq) {
400 | if (seq) {
401 | return seq[seq.length - 1];
402 | } else {
403 | return undefined;
404 | }
405 | });
406 |
407 | Jinja2.registerFilter('first', function(environment, seq) {
408 | if (seq) {
409 | return seq[0];
410 | } else {
411 | return undefined;
412 | }
413 | });
414 |
415 | Jinja2.registerFilter('title', function(str) {
416 | return str.replace(/\w\S*/g, function(txt) {
417 | return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
418 | });
419 | });
420 |
421 | Jinja2.registerFilter('lower', function(str) {
422 | return str.toLowerCase();
423 | });
424 |
425 | Jinja2.registerFilter('upper', function(str) {
426 | return str.toUpperCase();
427 | });
428 |
429 | Jinja2.registerFilter('capitalize', function(str) {
430 | return str.charAt(0).toUpperCase() + str.slice(1);
431 | });
432 |
433 | Jinja2.registerFilter('escape', function(html) {
434 | return String(html).replace(/&/g, "&").replace(//g, ">").replace(/"/g, """);
435 | });
436 |
437 | Jinja2.registerFilter('default', function(value, default_value, bool) {
438 | if ((bool && !value) || (value === void 0)) {
439 | return default_value;
440 | } else {
441 | return value;
442 | }
443 | });
444 |
445 | Jinja2.registerFilter('truncate', function(str, length, killwords, end) {
446 | length || (length = 255);
447 | end || (end = '...');
448 | if (str.length <= length) {
449 | return str;
450 | } else if (killwords) {
451 | return str.substring(0, length);
452 | } else {
453 | str = str.substring(0, maxLength + 1);
454 | str = str.substring(0, Math.min(str.length, str.lastIndexOf(" ")));
455 | return str + end;
456 | }
457 | });
458 |
459 | Jinja2.registerTest('callable', function(object) {
460 | return !!(obj && obj.constructor && obj.call && obj.apply);
461 | });
462 |
463 | Jinja2.registerTest('odd', function(value) {
464 | return value % 2 === 1;
465 | });
466 |
467 | Jinja2.registerTest('even', function(value) {
468 | return value % 2 === 0;
469 | });
470 |
471 | Jinja2.registerTest('divisibleby', function(value, num) {
472 | return value % num === 0;
473 | });
474 |
475 | Jinja2.registerTest('defined', function(value) {
476 | return typeof value !== "undefined";
477 | });
478 |
479 | Jinja2.registerTest('undefined', function(value) {
480 | return typeof value === "undefined";
481 | });
482 |
483 | Jinja2.registerTest('none', function(value) {
484 | return value === null;
485 | });
486 |
487 | Jinja2.registerTest('lower', function(value) {
488 | return value === value.toLowerCase();
489 | });
490 |
491 | Jinja2.registerTest('upper', function(value) {
492 | return value === value.toUpperCase();
493 | });
494 |
495 | Jinja2.registerTest('string', function(value) {
496 | return toString.call(value) === '[object String]';
497 | });
498 |
499 | Jinja2.registerTest('mapping', function(value) {
500 | return value === Object(value);
501 | });
502 |
503 | Jinja2.registerTest('number', function(value) {
504 | return toString.call(value) === '[object Number]';
505 | });
506 |
507 | Jinja2.registerTest('sequence', function(value) {
508 | return toString.call(value) === '[object Array]';
509 | });
510 |
511 | Jinja2.registerTest('sameas', function(value, other) {
512 | return value === other;
513 | });
514 |
515 | Jinja2.registerTest('iterable', function(value) {
516 | return toString.call(value) === '[object Array]';
517 | });
518 |
519 | Jinja2.registerTest('escaped', function(value) {
520 | return __indexOf.call(value, '__html__') >= 0;
521 | });
522 |
523 | }).call(this);
524 |
--------------------------------------------------------------------------------
/jsjinja/compiler.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | jinja2.compiler
4 | ~~~~~~~~~~~~~~~
5 |
6 | Compiles nodes into python code.
7 |
8 | :copyright: (c) 2012 by Syrus Akbary.
9 | :license: BSD, see LICENSE for more details.
10 | """
11 | from cStringIO import StringIO
12 | from itertools import chain
13 | from copy import deepcopy
14 | from jinja2 import nodes
15 | from jinja2.nodes import EvalContext
16 | from jinja2.visitor import NodeVisitor
17 | from jinja2.exceptions import TemplateAssertionError
18 | from jinja2.utils import Markup, concat, escape
19 | from jinja2._compat import next
20 | from keyword import iskeyword as is_python_keyword
21 |
22 | operators = {
23 | 'eq': '==',
24 | 'ne': '!=',
25 | 'gt': '>',
26 | 'gteq': '>=',
27 | 'lt': '<',
28 | 'lteq': '<=',
29 | 'in': 'in',
30 | 'notin': 'not in'
31 | }
32 |
33 | try:
34 | exec '(0 if 0 else 0)'
35 | except SyntaxError:
36 | have_condexpr = False
37 | else:
38 | have_condexpr = True
39 |
40 |
41 | # what method to iterate over items do we want to use for dict iteration
42 | # in generated code? on 2.x let's go with iteritems, on 3.x with items
43 | if hasattr(dict, 'iteritems'):
44 | dict_item_iter = 'iteritems'
45 | else:
46 | dict_item_iter = 'items'
47 |
48 |
49 | # does if 0: dummy(x) get us x into the scope?
50 | def unoptimize_before_dead_code():
51 | x = 42
52 | def f():
53 | if 0: dummy(x)
54 | return f
55 | unoptimize_before_dead_code = bool(unoptimize_before_dead_code().func_closure)
56 |
57 |
58 | def generate(node, environment, name, filename, stream=None,
59 | defer_init=False, env=None):
60 | """Generate the python source for a node tree."""
61 | # if not isinstance(node, nodes.Template):
62 | # raise TypeError('Can\'t compile non template nodes')
63 | generator = CodeGenerator(environment, name, filename, stream, defer_init)
64 | # generator.visit(node)
65 | if isinstance(node, nodes.Template):
66 | generator.visit(node, environment=env)
67 | else:
68 | eval_ctx = EvalContext(environment, name)
69 | frame = Frame(eval_ctx)
70 | generator.visit(node, frame=frame)
71 | if stream is None:
72 | return generator.stream.getvalue()
73 |
74 |
75 | def has_safe_repr(value):
76 | """Does the node have a safe representation?"""
77 | if value is None or value is NotImplemented or value is Ellipsis:
78 | return True
79 | if isinstance(value, (bool, int, long, float, complex, basestring,
80 | xrange, Markup)):
81 | return True
82 | if isinstance(value, (tuple, list, set, frozenset)):
83 | for item in value:
84 | if not has_safe_repr(item):
85 | return False
86 | return True
87 | elif isinstance(value, dict):
88 | for key, value in value.iteritems():
89 | if not has_safe_repr(key):
90 | return False
91 | if not has_safe_repr(value):
92 | return False
93 | return True
94 | return False
95 |
96 |
97 | def find_undeclared(nodes, names):
98 | """Check if the names passed are accessed undeclared. The return value
99 | is a set of all the undeclared names from the sequence of names found.
100 | """
101 | visitor = UndeclaredNameVisitor(names)
102 | try:
103 | for node in nodes:
104 | visitor.visit(node)
105 | except VisitorExit:
106 | pass
107 | return visitor.undeclared
108 |
109 |
110 | class Identifiers(object):
111 | """Tracks the status of identifiers in frames."""
112 |
113 | def __init__(self):
114 | # variables that are known to be declared (probably from outer
115 | # frames or because they are special for the frame)
116 | self.declared = set()
117 |
118 | # undeclared variables from outer scopes
119 | self.outer_undeclared = set()
120 |
121 | # names that are accessed without being explicitly declared by
122 | # this one or any of the outer scopes. Names can appear both in
123 | # declared and undeclared.
124 | self.undeclared = set()
125 |
126 | # names that are declared locally
127 | self.declared_locally = set()
128 |
129 | # names that are declared by parameters
130 | self.declared_parameter = set()
131 |
132 | def add_special(self, name):
133 | """Register a special name like `loop`."""
134 | self.undeclared.discard(name)
135 | self.declared.add(name)
136 |
137 | def is_declared(self, name):
138 | """Check if a name is declared in this or an outer scope."""
139 | if name in self.declared_locally or name in self.declared_parameter:
140 | return True
141 | return name in self.declared
142 |
143 | def copy(self):
144 | return deepcopy(self)
145 |
146 |
147 | class Frame(object):
148 | """Holds compile time information for us."""
149 |
150 | def __init__(self, eval_ctx, parent=None):
151 | self.eval_ctx = eval_ctx
152 | self.identifiers = Identifiers()
153 |
154 | # a toplevel frame is the root + soft frames such as if conditions.
155 | self.toplevel = False
156 |
157 | # the root frame is basically just the outermost frame, so no if
158 | # conditions. This information is used to optimize inheritance
159 | # situations.
160 | self.rootlevel = False
161 |
162 | # in some dynamic inheritance situations the compiler needs to add
163 | # write tests around output statements.
164 | self.require_output_check = parent and parent.require_output_check
165 |
166 | # inside some tags we are using a buffer rather than yield statements.
167 | # this for example affects {% filter %} or {% macro %}. If a frame
168 | # is buffered this variable points to the name of the list used as
169 | # buffer.
170 | self.buffer = None
171 |
172 | # the name of the block we're in, otherwise None.
173 | self.block = parent and parent.block or None
174 |
175 | # a set of actually assigned names
176 | self.assigned_names = set()
177 |
178 | # the parent of this frame
179 | self.parent = parent
180 |
181 | if parent is not None:
182 | self.identifiers.declared.update(
183 | parent.identifiers.declared |
184 | parent.identifiers.declared_parameter |
185 | parent.assigned_names
186 | )
187 | self.identifiers.outer_undeclared.update(
188 | parent.identifiers.undeclared -
189 | self.identifiers.declared
190 | )
191 | self.buffer = parent.buffer
192 |
193 | def copy(self):
194 | """Create a copy of the current one."""
195 | rv = object.__new__(self.__class__)
196 | rv.__dict__.update(self.__dict__)
197 | rv.identifiers = object.__new__(self.identifiers.__class__)
198 | rv.identifiers.__dict__.update(self.identifiers.__dict__)
199 | return rv
200 |
201 | def inspect(self, nodes):
202 | """Walk the node and check for identifiers. If the scope is hard (eg:
203 | enforce on a python level) overrides from outer scopes are tracked
204 | differently.
205 | """
206 | visitor = FrameIdentifierVisitor(self.identifiers)
207 | for node in nodes:
208 | visitor.visit(node)
209 |
210 | def find_shadowed(self, extra=()):
211 | """Find all the shadowed names. extra is an iterable of variables
212 | that may be defined with `add_special` which may occour scoped.
213 | """
214 | i = self.identifiers
215 | return (i.declared | i.outer_undeclared) & \
216 | (i.declared_locally | i.declared_parameter) | \
217 | set(x for x in extra if i.is_declared(x))
218 |
219 | def inner(self):
220 | """Return an inner frame."""
221 | return Frame(self.eval_ctx, self)
222 |
223 | def soft(self):
224 | """Return a soft frame. A soft frame may not be modified as
225 | standalone thing as it shares the resources with the frame it
226 | was created of, but it's not a rootlevel frame any longer.
227 | """
228 | rv = self.copy()
229 | rv.rootlevel = False
230 | return rv
231 |
232 | __copy__ = copy
233 |
234 |
235 | class VisitorExit(RuntimeError):
236 | """Exception used by the `UndeclaredNameVisitor` to signal a stop."""
237 |
238 |
239 | class DependencyFinderVisitor(NodeVisitor):
240 | """A visitor that collects filter and test calls."""
241 |
242 | def __init__(self):
243 | self.filters = set()
244 | self.tests = set()
245 |
246 | def visit_Filter(self, node):
247 | self.generic_visit(node)
248 | self.filters.add(node.name)
249 |
250 | def visit_Test(self, node):
251 | self.generic_visit(node)
252 | self.tests.add(node.name)
253 |
254 | def visit_Block(self, node):
255 | """Stop visiting at blocks."""
256 |
257 |
258 | class UndeclaredNameVisitor(NodeVisitor):
259 | """A visitor that checks if a name is accessed without being
260 | declared. This is different from the frame visitor as it will
261 | not stop at closure frames.
262 | """
263 |
264 | def __init__(self, names):
265 | self.names = set(names)
266 | self.undeclared = set()
267 |
268 | def visit_Name(self, node):
269 | if node.ctx == 'load' and node.name in self.names:
270 | self.undeclared.add(node.name)
271 | if self.undeclared == self.names:
272 | raise VisitorExit()
273 | else:
274 | self.names.discard(node.name)
275 |
276 | def visit_Block(self, node):
277 | """Stop visiting a blocks."""
278 |
279 |
280 | class FrameIdentifierVisitor(NodeVisitor):
281 | """A visitor for `Frame.inspect`."""
282 |
283 | def __init__(self, identifiers):
284 | self.identifiers = identifiers
285 |
286 | def visit_Name(self, node):
287 | """All assignments to names go through this function."""
288 | if node.ctx == 'store':
289 | self.identifiers.declared_locally.add(node.name)
290 | elif node.ctx == 'param':
291 | self.identifiers.declared_parameter.add(node.name)
292 | elif node.ctx == 'load' and not \
293 | self.identifiers.is_declared(node.name):
294 | self.identifiers.undeclared.add(node.name)
295 |
296 | def visit_If(self, node):
297 | self.visit(node.test)
298 | real_identifiers = self.identifiers
299 |
300 | old_names = real_identifiers.declared_locally | \
301 | real_identifiers.declared_parameter
302 |
303 | def inner_visit(nodes):
304 | if not nodes:
305 | return set()
306 | self.identifiers = real_identifiers.copy()
307 | for subnode in nodes:
308 | self.visit(subnode)
309 | rv = self.identifiers.declared_locally - old_names
310 | # we have to remember the undeclared variables of this branch
311 | # because we will have to pull them.
312 | real_identifiers.undeclared.update(self.identifiers.undeclared)
313 | self.identifiers = real_identifiers
314 | return rv
315 |
316 | body = inner_visit(node.body)
317 | else_ = inner_visit(node.else_ or ())
318 |
319 | # the differences between the two branches are also pulled as
320 | # undeclared variables
321 | real_identifiers.undeclared.update(body.symmetric_difference(else_) -
322 | real_identifiers.declared)
323 |
324 | # remember those that are declared.
325 | real_identifiers.declared_locally.update(body | else_)
326 |
327 | def visit_Macro(self, node):
328 | self.identifiers.declared_locally.add(node.name)
329 |
330 | def visit_Import(self, node):
331 | self.generic_visit(node)
332 | self.identifiers.declared_locally.add(node.target)
333 |
334 | def visit_FromImport(self, node):
335 | self.generic_visit(node)
336 | for name in node.names:
337 | if isinstance(name, tuple):
338 | self.identifiers.declared_locally.add(name[1])
339 | else:
340 | self.identifiers.declared_locally.add(name)
341 |
342 | def visit_Assign(self, node):
343 | """Visit assignments in the correct order."""
344 | self.visit(node.node)
345 | self.visit(node.target)
346 |
347 | def visit_For(self, node):
348 | """Visiting stops at for blocks. However the block sequence
349 | is visited as part of the outer scope.
350 | """
351 | self.visit(node.iter)
352 |
353 | def visit_CallBlock(self, node):
354 | self.visit(node.call)
355 |
356 | def visit_FilterBlock(self, node):
357 | self.visit(node.filter)
358 |
359 | def visit_Scope(self, node):
360 | """Stop visiting at scopes."""
361 |
362 | def visit_Block(self, node):
363 | """Stop visiting at blocks."""
364 |
365 |
366 | class CompilerExit(Exception):
367 | """Raised if the compiler encountered a situation where it just
368 | doesn't make sense to further process the code. Any block that
369 | raises such an exception is not further processed.
370 | """
371 |
372 | try:
373 | import simplejson as json
374 | except ImportError:
375 | import json
376 |
377 | class JSVar(object):
378 | def __init__(self,var): self.var = var
379 |
380 | def __repr__(self): return json.dumps(self.var)
381 |
382 | class CodeGenerator(NodeVisitor):
383 |
384 | def __init__(self, environment, name, filename, stream=None,
385 | defer_init=False):
386 | if stream is None:
387 | stream = StringIO()
388 | self.environment = environment
389 | self.name = name
390 | self.filename = filename
391 | self.stream = stream
392 | self.created_block_context = False
393 | self.defer_init = defer_init
394 |
395 | # aliases for imports
396 | self.import_aliases = {}
397 |
398 | # a registry for all blocks. Because blocks are moved out
399 | # into the global python scope they are registered here
400 | self.blocks = {}
401 |
402 | # the number of extends statements so far
403 | self.extends_so_far = 0
404 |
405 | # some templates have a rootlevel extends. In this case we
406 | # can safely assume that we're a child template and do some
407 | # more optimizations.
408 | self.has_known_extends = False
409 |
410 | # the current line number
411 | self.code_lineno = 1
412 |
413 | # registry of all filters and tests (global, not block local)
414 | self.tests = {}
415 | self.filters = {}
416 |
417 | # the debug information
418 | self.debug_info = []
419 | self._write_debug_info = None
420 |
421 | # the number of new lines before the next write()
422 | self._new_lines = 0
423 |
424 | # the line number of the last written statement
425 | self._last_line = 0
426 |
427 | # true if nothing was written so far.
428 | self._first_write = True
429 |
430 | # used by the `temporary_identifier` method to get new
431 | # unique, temporary identifier
432 | self._last_identifier = 0
433 |
434 | # the current indentation
435 | self._indentation = 0
436 |
437 | # -- Various compilation helpers
438 |
439 | def fail(self, msg, lineno):
440 | """Fail with a :exc:`TemplateAssertionError`."""
441 | raise TemplateAssertionError(msg, lineno, self.name, self.filename)
442 |
443 | def temporary_identifier(self):
444 | """Get a new unique identifier."""
445 | self._last_identifier += 1
446 | return 't_%d' % self._last_identifier
447 |
448 | def buffer(self, frame):
449 | """Enable buffering for the frame from that point onwards."""
450 | frame.buffer = self.temporary_identifier()
451 | self.writeline('var %s = "";' % frame.buffer)
452 |
453 | def return_buffer_contents(self, frame):
454 | """Return the buffer contents of the frame."""
455 | if frame.eval_ctx.volatile:
456 | self.writeline('if context.eval_ctx.autoescape ')
457 | self.indent()
458 | self.writeline('return escape(%s);' % frame.buffer)
459 | self.outdent()
460 | self.writeline('else ')
461 | self.indent()
462 | self.writeline('return %s;' % frame.buffer)
463 | self.outdent()
464 | elif frame.eval_ctx.autoescape:
465 | self.writeline('return escape(%s);' % frame.buffer)
466 | else:
467 | self.writeline('return %s;' % frame.buffer)
468 |
469 | def indent(self):
470 | """Indent by one."""
471 | self.write('{')
472 | self._indentation += 1
473 |
474 | def outdent(self, step=1):
475 | """Outdent by step."""
476 | for i in xrange(step):
477 | self._indentation -= 1
478 | self.writeline('}')
479 |
480 | def start_write(self, frame, node=None):
481 | """Yield or write into the frame buffer."""
482 | if frame.buffer is None:
483 | self.writeline('buf += ', node)
484 | else:
485 | self.writeline('%s += (' % frame.buffer, node)
486 |
487 | def end_write(self, frame):
488 | """End the writing process started by `start_write`."""
489 | if frame.buffer is not None:
490 | self.write(')')
491 |
492 | def simple_write(self, s, frame, node=None):
493 | """Simple shortcut for start_write + write + end_write."""
494 | self.start_write(frame, node)
495 | self.write(s)
496 | self.end_write(frame)
497 |
498 | def blockvisit(self, nodes, frame):
499 | """Visit a list of nodes as block in a frame. If the current frame
500 | is no buffer a dummy ``if 0: yield None`` is written automatically
501 | unless the force_generator parameter is set to False.
502 | """
503 | # if frame.buffer is None:
504 | # self.writeline('if 0: yield None')
505 | # else:
506 | # self.writeline('pass')
507 | try:
508 | for node in nodes:
509 | self.visit(node, frame)
510 | except CompilerExit:
511 | pass
512 |
513 | def write(self, x):
514 | """Write a string into the output stream."""
515 | if self._new_lines:
516 | if not self._first_write:
517 | self.stream.write('\n' * self._new_lines)
518 | self.code_lineno += self._new_lines
519 | if self._write_debug_info is not None:
520 | self.debug_info.append((self._write_debug_info,
521 | self.code_lineno))
522 | self._write_debug_info = None
523 | self._first_write = False
524 | self.stream.write(' ' * self._indentation)
525 | self._new_lines = 0
526 | self.stream.write(x)
527 |
528 | def writeline(self, x, node=None, extra=0):
529 | """Combination of newline and write."""
530 | self.newline(node, extra)
531 | self.write(x)
532 |
533 | def newline(self, node=None, extra=0):
534 | """Add one or more newlines before the next write."""
535 | self._new_lines = max(self._new_lines, 1 + extra)
536 | if node is not None and node.lineno != self._last_line:
537 | self._write_debug_info = node.lineno
538 | self._last_line = node.lineno
539 |
540 | def signature(self, node, frame, extra_kwargs=None):
541 | """Writes a function call to the stream for the current node.
542 | A leading comma is added automatically. The extra keyword
543 | arguments may not include python keywords otherwise a syntax
544 | error could occour. The extra keyword arguments should be given
545 | as python dict.
546 | """
547 | # if any of the given keyword arguments is a python keyword
548 | # we have to make sure that no invalid call is created.
549 | kwarg_workaround = False
550 | for kwarg in chain((x.key for x in node.kwargs), extra_kwargs or ()):
551 | if is_python_keyword(kwarg):
552 | kwarg_workaround = True
553 | break
554 |
555 | self.write(', [')
556 |
557 | first = True
558 | for arg in node.args:
559 | if not first:
560 | self.write(', ')
561 | self.visit(arg, frame)
562 | if first: first = False
563 | self.write(']')
564 |
565 | if not kwarg_workaround:
566 | self.write(', {')
567 | first = True
568 | for kwarg in node.kwargs:
569 | if not first:
570 | self.write(', ')
571 | self.visit(kwarg, frame)
572 | if first: first = False
573 | self.write('}')
574 | if extra_kwargs is not None:
575 | for key, value in extra_kwargs.iteritems():
576 | self.write(', %s=%s' % (key, value))
577 |
578 | if node.dyn_args:
579 | self.write(', *')
580 | self.visit(node.dyn_args, frame)
581 |
582 | if kwarg_workaround:
583 | if node.dyn_kwargs is not None:
584 | self.write(', **dict({')
585 | else:
586 | self.write(', **{')
587 | for kwarg in node.kwargs:
588 | self.write('%r: ' % kwarg.key)
589 | self.visit(kwarg.value, frame)
590 | self.write(', ')
591 | if extra_kwargs is not None:
592 | for key, value in extra_kwargs.iteritems():
593 | self.write('%r: %s, ' % (JSVar(key), value))
594 | if node.dyn_kwargs is not None:
595 | self.write('}, **')
596 | self.visit(node.dyn_kwargs, frame)
597 | self.write(')')
598 | else:
599 | self.write('}')
600 |
601 | elif node.dyn_kwargs is not None:
602 | self.write(', **')
603 | self.visit(node.dyn_kwargs, frame)
604 |
605 | def pull_locals(self, frame):
606 | """Pull all the references identifiers into the local scope."""
607 | for name in frame.identifiers.undeclared:
608 | self.writeline('var l_%s = context.resolve(%r);' % (name, JSVar(name)))
609 |
610 | def pull_dependencies(self, nodes):
611 | """Pull all the dependencies."""
612 | visitor = DependencyFinderVisitor()
613 | for node in nodes:
614 | visitor.visit(node)
615 | for dependency in 'filters', 'tests':
616 | mapping = getattr(self, dependency)
617 | for name in getattr(visitor, dependency):
618 | if name not in mapping:
619 | mapping[name] = self.temporary_identifier()
620 | self.writeline('%s = Jinja2.%s[%r]' %
621 | (mapping[name], dependency, JSVar(name)))
622 |
623 | def unoptimize_scope(self, frame):
624 | """Disable Python optimizations for the frame."""
625 | # XXX: this is not that nice but it has no real overhead. It
626 | # mainly works because python finds the locals before dead code
627 | # is removed. If that breaks we have to add a dummy function
628 | # that just accepts the arguments and does nothing.
629 | if frame.identifiers.declared:
630 | self.writeline('%sdummy(%s)' % (
631 | unoptimize_before_dead_code and 'if (false) ' or '',
632 | ', '.join('l_' + name for name in frame.identifiers.declared)
633 | ))
634 |
635 | def push_scope(self, frame, extra_vars=()):
636 | """This function returns all the shadowed variables in a dict
637 | in the form name: alias and will write the required assignments
638 | into the current scope. No indentation takes place.
639 |
640 | This also predefines locally declared variables from the loop
641 | body because under some circumstances it may be the case that
642 |
643 | `extra_vars` is passed to `Frame.find_shadowed`.
644 | """
645 | aliases = {}
646 | for name in frame.find_shadowed(extra_vars):
647 | aliases[name] = ident = self.temporary_identifier()
648 | self.writeline('var %s = l_%s;' % (ident, name))
649 | to_declare = set()
650 | for name in frame.identifiers.declared_locally:
651 | if name not in aliases:
652 | to_declare.add('var l_' + name)
653 | if to_declare:
654 | self.writeline(' = '.join(to_declare) + ' = undefined;')
655 | return aliases
656 |
657 | def pop_scope(self, aliases, frame):
658 | """Restore all aliases and delete unused variables."""
659 | for name, alias in aliases.iteritems():
660 | self.writeline('var l_%s = %s;' % (name, alias))
661 | to_delete = set()
662 | for name in frame.identifiers.declared_locally:
663 | if name not in aliases:
664 | to_delete.add('l_' + name)
665 | if to_delete:
666 | # we cannot use the del statement here because enclosed
667 | # scopes can trigger a SyntaxError:
668 | # a = 42; b = lambda: a; del a
669 | self.writeline(' = '.join(to_delete) + ' = undefined;')
670 |
671 | def function_scoping(self, node, frame, children=None,
672 | find_special=True):
673 | """In Jinja a few statements require the help of anonymous
674 | functions. Those are currently macros and call blocks and in
675 | the future also recursive loops. As there is currently
676 | technical limitation that doesn't allow reading and writing a
677 | variable in a scope where the initial value is coming from an
678 | outer scope, this function tries to fall back with a common
679 | error message. Additionally the frame passed is modified so
680 | that the argumetns are collected and callers are looked up.
681 |
682 | This will return the modified frame.
683 | """
684 | # we have to iterate twice over it, make sure that works
685 | if children is None:
686 | children = node.iter_child_nodes()
687 | children = list(children)
688 | func_frame = frame.inner()
689 | func_frame.inspect(children)
690 |
691 | # variables that are undeclared (accessed before declaration) and
692 | # declared locally *and* part of an outside scope raise a template
693 | # assertion error. Reason: we can't generate reasonable code from
694 | # it without aliasing all the variables.
695 | # this could be fixed in Python 3 where we have the nonlocal
696 | # keyword or if we switch to bytecode generation
697 | overriden_closure_vars = (
698 | func_frame.identifiers.undeclared &
699 | func_frame.identifiers.declared &
700 | (func_frame.identifiers.declared_locally |
701 | func_frame.identifiers.declared_parameter)
702 | )
703 | if overriden_closure_vars:
704 | self.fail('It\'s not possible to set and access variables '
705 | 'derived from an outer scope! (affects: %s)' %
706 | ', '.join(sorted(overriden_closure_vars)), node.lineno)
707 |
708 | # remove variables from a closure from the frame's undeclared
709 | # identifiers.
710 | func_frame.identifiers.undeclared -= (
711 | func_frame.identifiers.undeclared &
712 | func_frame.identifiers.declared
713 | )
714 |
715 | # no special variables for this scope, abort early
716 | if not find_special:
717 | return func_frame
718 |
719 | func_frame.accesses_kwargs = False
720 | func_frame.accesses_varargs = False
721 | func_frame.accesses_caller = False
722 | func_frame.arguments = args = ['l_' + x.name for x in node.args]
723 |
724 | undeclared = find_undeclared(children, ('caller', 'kwargs', 'varargs'))
725 |
726 | if 'caller' in undeclared:
727 | func_frame.accesses_caller = True
728 | func_frame.identifiers.add_special('caller')
729 | args.append('l_caller')
730 | if 'kwargs' in undeclared:
731 | func_frame.accesses_kwargs = True
732 | func_frame.identifiers.add_special('kwargs')
733 | args.append('l_kwargs')
734 | if 'varargs' in undeclared:
735 | func_frame.accesses_varargs = True
736 | func_frame.identifiers.add_special('varargs')
737 | args.append('l_varargs')
738 | return func_frame
739 |
740 | def macro_body(self, node, frame, children=None, defaults={}):
741 | """Dump the function def of a macro or call block."""
742 | frame = self.function_scoping(node, frame, children)
743 | # macros are delayed, they never require output checks
744 | frame.require_output_check = False
745 | args = frame.arguments
746 | # XXX: this is an ugly fix for the loop nesting bug
747 | # (tests.test_old_bugs.test_loop_call_bug). This works around
748 | # a identifier nesting problem we have in general. It's just more
749 | # likely to happen in loops which is why we work around it. The
750 | # real solution would be "nonlocal" all the identifiers that are
751 | # leaking into a new python frame and might be used both unassigned
752 | # and assigned.
753 | if 'loop' in frame.identifiers.declared:
754 | args = args + ['l_loop=l_loop']
755 | self.writeline('var l_%s = function (%s) ' % (node.name, ', '.join(args)), node)
756 | self.indent()
757 | for kw_name,_ in defaults.items():
758 | self.writeline('if (typeof l_%(name)s == \'undefined\') l_%(name)s = arguments.callee.__defaults__.%(name)s;'%{'name':kw_name})
759 | self.buffer(frame)
760 | self.pull_locals(frame)
761 | self.blockvisit(node.body, frame)
762 | self.return_buffer_contents(frame)
763 | self.outdent()
764 | return frame
765 |
766 | def get_defaults(self,node):
767 | defaults = {}
768 | l = len(node.defaults)
769 | for i, d in enumerate(node.defaults):
770 | kwarg_name = node.args[-l+i].name
771 | defaults[kwarg_name] = d.value
772 | return defaults
773 |
774 | def macro_def(self, node, frame, defaults={}):
775 | """Dump the macro definition for the def created by macro_body."""
776 | arg_tuple = ', '.join(repr(JSVar(x.name)) for x in node.args)
777 | name = getattr(node, 'name', None)
778 |
779 | self.writeline('l_%s.__args__ = [%s];' %
780 | (name, arg_tuple))
781 | self.writeline('l_%s.__defaults__ = %r;' %
782 | (name, JSVar(defaults)))
783 |
784 | # self.write('], %r, %r, %r)' % (
785 | # JSVar(bool(frame.accesses_kwargs)),
786 | # JSVar(bool(frame.accesses_varargs)),
787 | # JSVar(bool(frame.accesses_caller))
788 | # ))
789 |
790 | def position(self, node):
791 | """Return a human readable position for the node."""
792 | rv = 'line %d' % node.lineno
793 | if self.name is not None:
794 | rv += ' in ' + repr(self.name)
795 | return rv
796 |
797 | def initbuffer(self):
798 | self.writeline('var buf = "";')
799 |
800 | def returnbuffer(self):
801 | self.writeline('return buf;')
802 | # -- Statement Visitors
803 |
804 | def visit_Template(self, node, frame=None, environment=None):
805 | assert frame is None, 'no root frame allowed'
806 | eval_ctx = EvalContext(self.environment, self.name)
807 | # if not unoptimize_before_dead_code:
808 | # self.writeline('var dummy = function() {};')
809 |
810 | # if we want a deferred initialization we cannot move the
811 | # environment into a local name
812 | envenv = not self.defer_init and ', Jinja2' or ''
813 |
814 | # do we have an extends tag at all? If not, we can save some
815 | # overhead by just not processing any inheritance code.
816 | have_extends = node.find(nodes.Extends) is not None
817 |
818 | # find all blocks
819 | for block in node.find_all(nodes.Block):
820 | if block.name in self.blocks:
821 | self.fail('block %r defined twice' % JSVar(block.name), block.lineno)
822 | self.blocks[block.name] = block
823 |
824 | # find all imports and import them
825 | for import_ in node.find_all(nodes.ImportedName):
826 | if import_.importname not in self.import_aliases:
827 | imp = import_.importname
828 | self.import_aliases[imp] = alias = self.temporary_identifier()
829 | if '.' in imp:
830 | module, obj = imp.rsplit('.', 1)
831 | self.writeline('from %s import %s as %s' %
832 | (module, obj, alias))
833 | else:
834 | self.writeline('import %s as %s' % (imp, alias))
835 |
836 | # add the load name
837 | template_name = JSVar(self.name)
838 | self.writeline('(function() ')
839 | self.indent()
840 | self.writeline('/* %s */' % self.name)
841 | self.writeline('Jinja2.__extends(Template, Jinja2.Template);')
842 | if self.name:
843 | self.writeline('Jinja2.registerTemplate(%r, Template);' % template_name)
844 | self.writeline('function Template() {return Template.__super__.constructor.apply(this, arguments);};')
845 | # self.indent()
846 | # self.writeline('environment = environment;')
847 | # self.outdent()
848 | self.writeline('Template.prototype.root = function (context) ')
849 | # process the root
850 | frame = Frame(eval_ctx)
851 | frame.inspect(node.body)
852 | frame.toplevel = frame.rootlevel = True
853 | frame.require_output_check = have_extends and not self.has_known_extends
854 | self.indent()
855 | # self.writeline('environment = this.environment;')
856 | self.initbuffer();
857 | # if have_extends:
858 | # self.writeline('var parent_template;')
859 | if 'self' in find_undeclared(node.body, ('self',)):
860 | frame.identifiers.add_special('self')
861 | self.writeline('l_self = Jinja2.TemplateReference(context)')
862 | self.pull_locals(frame)
863 | self.pull_dependencies(node.body)
864 | self.blockvisit(node.body, frame)
865 | # make sure that the parent root is called.
866 | if have_extends:
867 | if not self.has_known_extends:
868 | self.writeline('if (parent_template) ')
869 | self.indent()
870 | self.writeline('buf += parent_template.root(context);')
871 | # self.outdent((not self.has_known_extends))
872 | # at this point we now have the blocks collected and can visit them too.
873 | self.returnbuffer()
874 | self.outdent()
875 |
876 | for name, block in self.blocks.iteritems():
877 | block_frame = Frame(eval_ctx)
878 | block_frame.inspect(block.body)
879 | block_frame.block = name
880 | self.writeline('Template.prototype.block_%s = function (context) ' % (name),
881 | block)
882 | self.indent()
883 | self.initbuffer()
884 | undeclared = find_undeclared(block.body, ('self', 'super'))
885 | if 'self' in undeclared:
886 | block_frame.identifiers.add_special('self')
887 | self.writeline('var l_self = Jinja2.TemplateReference(context)')
888 | if 'super' in undeclared:
889 | block_frame.identifiers.add_special('super')
890 | # self.writeline('log(context);')
891 | self.writeline('var l_super = context.super(%r, '
892 | 'Template.prototype.block_%s)' % (JSVar(name), name))
893 | self.pull_locals(block_frame)
894 | self.pull_dependencies(block.body)
895 | self.blockvisit(block.body, block_frame)
896 | self.returnbuffer()
897 | self.outdent()
898 |
899 | self.writeline('return Template;')
900 | self.outdent()
901 | self.write(')()')
902 | # self.writeline('blocks = {%s}' % ', '.join('%r: block_%s' % (x, x)
903 | # for x in self.blocks),
904 | # extra=1)
905 |
906 | # add a function that returns the debug info
907 | # self.writeline('debug_info = %r' % '&'.join('%s=%s' % x for x
908 | # in self.debug_info))
909 |
910 | def visit_Block(self, node, frame):
911 | """Call a block and register it for the template."""
912 | level = 1
913 | if frame.toplevel:
914 | # if we know that we are a child template, there is no need to
915 | # check if we are one
916 | if self.has_known_extends:
917 | return
918 | if self.extends_so_far > 0:
919 | self.writeline('if parent_template is None:')
920 | self.indent()
921 | level += 1
922 | context = node.scoped and 'context.derived(locals())' or 'context'
923 | self.writeline('buf += context.blocks[%r][0](%s);' % (
924 | node.name, context), node)
925 |
926 | def visit_Extends(self, node, frame):
927 | """Calls the extender."""
928 | if not frame.toplevel:
929 | self.fail('cannot use extend from a non top-level scope',
930 | node.lineno)
931 |
932 | # if the number of extends statements in general is zero so
933 | # far, we don't have to add a check if something extended
934 | # the template before this one.
935 | if self.extends_so_far > 0:
936 |
937 | # if we have a known extends we just add a template runtime
938 | # error into the generated code. We could catch that at compile
939 | # time too, but i welcome it not to confuse users by throwing the
940 | # same error at different times just "because we can".
941 | if not self.has_known_extends:
942 | self.writeline('if parent_template is not None:')
943 | self.indent()
944 | self.writeline('raise TemplateRuntimeError(%r)' %
945 | 'extended multiple times')
946 | self.outdent()
947 |
948 | # if we have a known extends already we don't need that code here
949 | # as we know that the template execution will end here.
950 | if self.has_known_extends:
951 | raise CompilerExit()
952 |
953 | self.writeline('var parent_template = Jinja2.getTemplate(', node)
954 | self.visit(node.template, frame)
955 | self.write(', %r);' % JSVar(self.name))
956 | self.writeline('for (name in parent_template.blocks) ')
957 | self.indent()
958 | self.writeline('var parent_block = parent_template.blocks[name];')
959 | self.writeline('context.blocks[name] = context.blocks[name] || [];')
960 | self.writeline('context.blocks[name].push(parent_block)')
961 | self.outdent()
962 |
963 | # if this extends statement was in the root level we can take
964 | # advantage of that information and simplify the generated code
965 | # in the top level from this point onwards
966 | if frame.rootlevel:
967 | self.has_known_extends = True
968 |
969 | # and now we have one more
970 | self.extends_so_far += 1
971 |
972 | def visit_Include(self, node, frame):
973 | """Handles includes."""
974 | if node.with_context:
975 | self.unoptimize_scope(frame)
976 | if node.ignore_missing:
977 | self.writeline('try ')
978 | self.indent()
979 |
980 | func_name = 'get_or_select_template'
981 | if isinstance(node.template, nodes.Const):
982 | if isinstance(node.template.value, basestring):
983 | func_name = 'getTemplate'
984 | elif isinstance(node.template.value, (tuple, list)):
985 | func_name = 'selectTemplate'
986 | elif isinstance(node.template, (nodes.Tuple, nodes.List)):
987 | func_name = 'selectTemplate'
988 |
989 | self.writeline('template = Jinja2.%s(' % func_name, node)
990 | self.visit(node.template, frame)
991 | self.write(', %r)' % JSVar(self.name))
992 | if node.ignore_missing:
993 | self.outdent()
994 | self.writeline('catch (e) {}')
995 | self.writeline('else ')
996 | self.indent()
997 |
998 | if node.with_context:
999 | first = True
1000 | self.writeline('var locals = {')
1001 | names = list (frame.identifiers.declared) # + list(frame.assigned_names)
1002 | for l in names:
1003 | if not first: self.write(',')
1004 | self.write('%r:l_%s'%(JSVar(l),l))
1005 | first = False
1006 | self.write('};')
1007 | self.simple_write('template.root(template.new_context(context.parent, true, locals))', frame)
1008 | # self.writeline('for event in template.root_render_func('
1009 | # 'template.new_context(context.parent, True, '
1010 | # 'locals())):')
1011 | else:
1012 | self.writeline('for event in template.module._body_stream:')
1013 |
1014 | # self.indent()
1015 | # self.simple_write('event', frame)
1016 | # self.outdent()
1017 |
1018 | if node.ignore_missing:
1019 | self.outdent()
1020 |
1021 | def visit_Import(self, node, frame):
1022 | """Visit regular imports."""
1023 | if node.with_context:
1024 | self.unoptimize_scope(frame)
1025 | self.writeline('var l_%s = ' % node.target, node)
1026 | # self.write(';')
1027 | if frame.toplevel:
1028 | self.write('context.vars[%r] = ' % node.target)
1029 | self.write('Jinja2.getTemplate(')
1030 | self.visit(node.template, frame)
1031 | self.write(', %r).' % self.name)
1032 | if node.with_context:
1033 | self.write('make_module(context.parent, True, locals())')
1034 | else:
1035 | self.write('module()')
1036 | if frame.toplevel and not node.target.startswith('_'):
1037 | self.writeline('context.exported_vars.remove(%r);' % node.target)
1038 | frame.assigned_names.add(node.target)
1039 |
1040 | def visit_FromImport(self, node, frame):
1041 | """Visit named imports."""
1042 | self.newline(node)
1043 | self.write('var included_template = Jinja2.getTemplate(')
1044 | self.visit(node.template, frame)
1045 | self.write(', %r).' % self.name)
1046 | if node.with_context:
1047 | self.write('make_module(context.parent, True)')
1048 | else:
1049 | self.write('module()')
1050 | self.write(';')
1051 | var_names = []
1052 | discarded_names = []
1053 | for name in node.names:
1054 | if isinstance(name, tuple):
1055 | name, alias = name
1056 | else:
1057 | alias = name
1058 | self.writeline('var l_%s = included_template['
1059 | '%r]' % (alias, name))
1060 | self.writeline('if (typeof l_%s == "undefined")' % alias)
1061 | self.indent()
1062 | self.writeline('l_%s = Jinja2.undefined(%r %% '
1063 | 'included_template.__name__, '
1064 | 'name=%r)' %
1065 | (alias, 'the template %%r (imported on %s) does '
1066 | 'not export the requested name %s' % (
1067 | self.position(node),
1068 | repr(name)
1069 | ), name))
1070 | self.outdent()
1071 | if frame.toplevel:
1072 | var_names.append(alias)
1073 | if not alias.startswith('_'):
1074 | discarded_names.append(alias)
1075 | frame.assigned_names.add(alias)
1076 |
1077 | if var_names:
1078 | if len(var_names) == 1:
1079 | name = var_names[0]
1080 | self.writeline('context.vars[%r] = l_%s' % (name, name))
1081 | else:
1082 | self.writeline('context.vars.update({%s})' % ', '.join(
1083 | '%r: l_%s' % (name, name) for name in var_names
1084 | ))
1085 | if discarded_names:
1086 | if len(discarded_names) == 1:
1087 | self.writeline('context.exported_vars.remove(%r);' %
1088 | discarded_names[0])
1089 | else:
1090 | self.writeline('context.exported_vars.difference_'
1091 | 'update((%s));' % ', '.join(map(repr, discarded_names)))
1092 |
1093 | def visit_For(self, node, frame):
1094 | # when calculating the nodes for the inner frame we have to exclude
1095 | # the iterator contents from it
1096 | children = node.iter_child_nodes(exclude=('iter',))
1097 | if node.recursive:
1098 | loop_frame = self.function_scoping(node, frame, children,
1099 | find_special=False)
1100 | else:
1101 | loop_frame = frame.inner()
1102 | loop_frame.inspect(children)
1103 |
1104 | # try to figure out if we have an extended loop. An extended loop
1105 | # is necessary if the loop is in recursive mode if the special loop
1106 | # variable is accessed in the body.
1107 | extended_loop = node.recursive or 'loop' in \
1108 | find_undeclared(node.iter_child_nodes(
1109 | only=('body',)), ('loop',))
1110 |
1111 | # if we don't have an recursive loop we have to find the shadowed
1112 | # variables at that point. Because loops can be nested but the loop
1113 | # variable is a special one we have to enforce aliasing for it.
1114 | if not node.recursive:
1115 | aliases = self.push_scope(loop_frame, ('loop',))
1116 |
1117 | # otherwise we set up a buffer and add a function def
1118 | else:
1119 | self.writeline('var loop = function(reciter, loop_render_func):', node)
1120 | self.indent()
1121 | self.buffer(loop_frame)
1122 | aliases = {}
1123 |
1124 | # make sure the loop variable is a special one and raise a template
1125 | # assertion error if a loop tries to write to loop
1126 | if extended_loop:
1127 | loop_frame.identifiers.add_special('loop')
1128 | for name in node.find_all(nodes.Name):
1129 | if name.ctx == 'store' and name.name == 'loop':
1130 | self.fail('Can\'t assign to special loop variable '
1131 | 'in for-loop target', name.lineno)
1132 |
1133 | self.pull_locals(loop_frame)
1134 | if node.else_:
1135 | iteration_indicator = self.temporary_identifier()
1136 | self.writeline('var %s = 1;' % iteration_indicator)
1137 |
1138 | # Create a fake parent loop if the else or test section of a
1139 | # loop is accessing the special loop variable and no parent loop
1140 | # exists.
1141 | if 'loop' not in aliases and 'loop' in find_undeclared(
1142 | node.iter_child_nodes(only=('else_', 'test')), ('loop',)):
1143 | self.writeline("l_loop = Jinja2.undefined(%r, name='loop')" %
1144 | ("'loop' is undefined. the filter section of a loop as well "
1145 | "as the else block don't have access to the special 'loop'"
1146 | " variable of the current loop. Because there is no parent "
1147 | "loop it's undefined. Happened in loop on %s" %
1148 | self.position(node)))
1149 |
1150 | if isinstance(node.target,nodes.Name):
1151 | loop_vars = [node.target.name]
1152 | else:
1153 | loop_vars = [n.name for n in node.target.find_all(nodes.Name)]
1154 |
1155 | # self.writeline(str(loop_vars))
1156 | loop_frame.identifiers.declared.add(*loop_vars)
1157 | # self.visit(node.target, loop_frame)
1158 |
1159 | # self.write(extended_loop and ', l_loop in LoopContext(' or ' in ')
1160 |
1161 | # if we have an extened loop and a node test, we filter in the
1162 | # "outer frame".
1163 | # self.visit(node.iter, loop_frame)
1164 |
1165 | if node.recursive:
1166 | self.write(', recurse=loop_render_func):')
1167 | else:
1168 | if extended_loop:
1169 | self.writeline('var l_loop;')
1170 |
1171 | var_ref = self.temporary_identifier()
1172 | self.writeline('var %s = '%var_ref)
1173 | self.visit(node.iter, loop_frame);
1174 | self.write(';')
1175 | var_i = self.temporary_identifier()
1176 | var_len = self.temporary_identifier()
1177 | self.writeline('for (var %(i)s= 0, %(len)s = %(ref)s.length; %(i)s<%(len)s; %(i)s++) '%dict(i=var_i,len=var_len, ref=var_ref), node)
1178 |
1179 | #self.write(extended_loop and ')) {' or ') {')
1180 |
1181 | self.indent()
1182 | # tests in not extended loops become a continue
1183 | if node.test is not None:
1184 | self.writeline('if (!(')
1185 | self.visit(node.test, loop_frame)
1186 | self.write(')) ')
1187 | self.indent()
1188 | self.writeline('continue;')
1189 | self.outdent()
1190 |
1191 | if len(loop_vars)>1:
1192 | self.writeline('%s;'%', '.join(['l_%s = %s[%d]'%(l,var_ref,i) for i,l in enumerate(loop_vars)]))
1193 | else:
1194 | self.writeline('l_%s = %s[%s];'%(loop_vars[0],var_ref,var_i))
1195 |
1196 | if extended_loop:
1197 | self.writeline('l_loop = Jinja2.utils.loop(%s,%s);'%(var_i,var_len))
1198 | self.blockvisit(node.body, loop_frame)
1199 | if node.else_:
1200 | self.writeline('%s = 0;' % iteration_indicator)
1201 | self.outdent()
1202 | if node.else_:
1203 | self.writeline('if (%s) ' % iteration_indicator)
1204 | self.indent()
1205 | self.blockvisit(node.else_, loop_frame)
1206 | self.outdent()
1207 | # reset the aliases if there are any.
1208 | if not node.recursive:
1209 | self.pop_scope(aliases, loop_frame)
1210 |
1211 | # if the node was recursive we have to return the buffer contents
1212 | # and start the iteration code
1213 | if node.recursive:
1214 | self.return_buffer_contents(loop_frame)
1215 | self.outdent()
1216 | self.start_write(frame, node)
1217 | self.write('loop(')
1218 | self.visit(node.iter, frame)
1219 | self.write(', loop)')
1220 | self.end_write(frame)
1221 |
1222 | def visit_If(self, node, frame):
1223 | if_frame = frame.soft()
1224 | self.writeline('if (', node)
1225 | self.visit(node.test, if_frame)
1226 | self.write(') ')
1227 | self.indent()
1228 | self.blockvisit(node.body, if_frame)
1229 | self.outdent()
1230 | if node.else_:
1231 | self.writeline('else ')
1232 | self.indent()
1233 | self.blockvisit(node.else_, if_frame)
1234 | self.outdent()
1235 |
1236 | def visit_Macro(self, node, frame):
1237 | defaults = self.get_defaults(node)
1238 | macro_frame = self.macro_body(node, frame, defaults=defaults)
1239 | self.macro_def(node, macro_frame, defaults=defaults)
1240 | if frame.toplevel:
1241 | self.writeline('context.vars[%r] = l_%s;' % (node.name,node.name))
1242 | if not node.name.startswith('_'):
1243 | self.writeline('context.exported_vars.add(%r);' % node.name)
1244 | frame.assigned_names.add(node.name)
1245 |
1246 | def visit_CallBlock(self, node, frame):
1247 | children = node.iter_child_nodes(exclude=('call',))
1248 | call_frame = self.macro_body(node, frame, children)
1249 | self.writeline('var caller = ')
1250 | self.macro_def(node, call_frame)
1251 | self.start_write(frame, node)
1252 | self.visit_Call(node.call, call_frame, forward_caller=True)
1253 | self.end_write(frame)
1254 | self.write(';')
1255 |
1256 | def visit_FilterBlock(self, node, frame):
1257 | filter_frame = frame.inner()
1258 | filter_frame.inspect(node.iter_child_nodes())
1259 | aliases = self.push_scope(filter_frame)
1260 | self.pull_locals(filter_frame)
1261 | self.buffer(filter_frame)
1262 | self.blockvisit(node.body, filter_frame)
1263 | self.start_write(frame, node)
1264 | self.visit_Filter(node.filter, filter_frame)
1265 | self.end_write(frame)
1266 | self.pop_scope(aliases, filter_frame)
1267 |
1268 | def visit_ExprStmt(self, node, frame):
1269 | self.newline(node)
1270 | self.visit(node.node, frame)
1271 |
1272 | def visit_Output(self, node, frame):
1273 | # if we have a known extends statement, we don't output anything
1274 | # if we are in a require_output_check section
1275 | if self.has_known_extends and frame.require_output_check:
1276 | return
1277 |
1278 | if self.environment.finalize:
1279 | finalize = lambda x: unicode(self.environment.finalize(x))
1280 | else:
1281 | finalize = unicode
1282 |
1283 | # if we are inside a frame that requires output checking, we do so
1284 | outdent_later = False
1285 | if frame.require_output_check:
1286 | self.writeline('if (!parent_template) ')
1287 | self.indent()
1288 | outdent_later = True
1289 |
1290 | # try to evaluate as many chunks as possible into a static
1291 | # string at compile time.
1292 | body = []
1293 | for child in node.nodes:
1294 | try:
1295 | const = child.as_const(frame.eval_ctx)
1296 | except nodes.Impossible:
1297 | body.append(child)
1298 | continue
1299 | # the frame can't be volatile here, becaus otherwise the
1300 | # as_const() function would raise an Impossible exception
1301 | # at that point.
1302 | try:
1303 | if frame.eval_ctx.autoescape:
1304 | if hasattr(const, '__html__'):
1305 | const = const.__html__()
1306 | else:
1307 | const = escape(const)
1308 | const = finalize(const)
1309 | except Exception:
1310 | # if something goes wrong here we evaluate the node
1311 | # at runtime for easier debugging
1312 | body.append(child)
1313 | continue
1314 | if body and isinstance(body[-1], list):
1315 | body[-1].append(const)
1316 | else:
1317 | body.append([const])
1318 |
1319 | # if we have less than 3 nodes or a buffer we yield or extend/append
1320 | if len(body) < 3 or frame.buffer is not None:
1321 | if frame.buffer is not None:
1322 | pass
1323 | # for one item we append, for more we extend
1324 | # if len(body) == 1:
1325 | # self.writeline('%s.push(' % frame.buffer)
1326 | # else:
1327 | # pass
1328 | for item in body:
1329 | if isinstance(item, list):
1330 | val = JSVar(concat(item))
1331 | if frame.buffer is None:
1332 | self.writeline('buf += %r;'% val)
1333 | else:
1334 | self.writeline('%s += %r;'%(frame.buffer,val))
1335 | else:
1336 | if frame.buffer is None:
1337 | self.writeline('buf += ', item)
1338 | else:
1339 | self.writeline('%s += ('%frame.buffer, item)
1340 | close = 1
1341 | if frame.eval_ctx.volatile:
1342 | self.write('(context.eval_ctx.autoescape and'
1343 | ' escape or Jinja2.utils.to_string)(')
1344 | elif frame.eval_ctx.autoescape:
1345 | self.write('escape(')
1346 | else:
1347 | self.write('Jinja2.utils.to_string(')
1348 | if self.environment.finalize is not None:
1349 | self.write('Jinja2.finalize(')
1350 | close += 1
1351 | self.visit(item, frame)
1352 | self.write(')' * close)
1353 | self.write('')
1354 | if frame.buffer is not None:
1355 | self.write(');')
1356 |
1357 | # otherwise we create a format string as this is faster in that case
1358 | else:
1359 | idx = -1
1360 | for argument in body:
1361 | self.writeline('buf += ')
1362 | if isinstance(argument, list):
1363 | self.write("%r;"%JSVar(concat(argument)))
1364 | continue
1365 | close = 0
1366 | if frame.eval_ctx.volatile:
1367 | self.write('(context.eval_ctx.autoescape and'
1368 | ' escape or String)(')
1369 | close += 1
1370 | elif frame.eval_ctx.autoescape:
1371 | self.write('escape(')
1372 | close += 1
1373 | if self.environment.finalize is not None:
1374 | self.write('Jinja2.finalize(')
1375 | close += 1
1376 | self.visit(argument, frame)
1377 | self.write(')' * close )
1378 | self.write(';')
1379 |
1380 | if outdent_later:
1381 | self.outdent()
1382 |
1383 | def visit_Assign(self, node, frame):
1384 | self.newline(node)
1385 | # toplevel assignments however go into the local namespace and
1386 | # the current template's context. We create a copy of the frame
1387 | # here and add a set so that the Name visitor can add the assigned
1388 | # names here.
1389 | if frame.toplevel:
1390 | assignment_frame = frame.copy()
1391 | assignment_frame.toplevel_assignments = set()
1392 | else:
1393 | assignment_frame = frame
1394 | self.write('var ')
1395 | self.visit(node.target, assignment_frame)
1396 | self.write(' = ')
1397 | self.visit(node.node, frame)
1398 | self.write(';')
1399 | # make sure toplevel assignments are added to the context.
1400 | if frame.toplevel:
1401 | public_names = [x for x in assignment_frame.toplevel_assignments
1402 | if not x.startswith('_')]
1403 | if len(assignment_frame.toplevel_assignments) == 1:
1404 | name = next(iter(assignment_frame.toplevel_assignments))
1405 | self.writeline('context.vars[%r] = l_%s;' % (name, name))
1406 | else:
1407 | self.writeline('context.vars.update({')
1408 | for idx, name in enumerate(assignment_frame.toplevel_assignments):
1409 | if idx:
1410 | self.write(', ')
1411 | self.write('%r: l_%s' % (name, name))
1412 | self.write('})')
1413 | if public_names:
1414 | if len(public_names) == 1:
1415 | self.writeline('context.exported_vars.add(%r);' %
1416 | public_names[0])
1417 | else:
1418 | self.writeline('context.exported_vars.update((%s));' %
1419 | ', '.join(map(lambda x:repr(JSVar(x)), public_names)))
1420 |
1421 | # -- Expression Visitors
1422 |
1423 | def visit_Name(self, node, frame):
1424 | if node.ctx == 'store' and frame.toplevel:
1425 | frame.toplevel_assignments.add(node.name)
1426 | self.write('l_%s'%node.name)
1427 | frame.assigned_names.add(node.name)
1428 |
1429 | def visit_Const(self, node, frame):
1430 | val = node.value
1431 | self.write(repr(JSVar(val)))
1432 | # if isinstance(val, float):
1433 | # self.write(str(val))
1434 | # else:
1435 | # self.write(repr(val))
1436 |
1437 | def visit_TemplateData(self, node, frame):
1438 | try:
1439 | self.write(repr(node.as_const(frame.eval_ctx)))
1440 | except nodes.Impossible:
1441 | self.write('(context.eval_ctx.autoescape and escape or identity)(%r)'
1442 | % node.data)
1443 |
1444 | def visit_Tuple(self, node, frame):
1445 | self.write('[')
1446 | idx = -1
1447 | for idx, item in enumerate(node.items):
1448 | if idx:
1449 | self.write(', ')
1450 | self.visit(item, frame)
1451 | self.write(']')
1452 |
1453 | def visit_List(self, node, frame):
1454 | self.write('[')
1455 | for idx, item in enumerate(node.items):
1456 | if idx:
1457 | self.write(', ')
1458 | self.visit(item, frame)
1459 | self.write(']')
1460 |
1461 | def visit_Dict(self, node, frame):
1462 | self.write('{')
1463 | for idx, item in enumerate(node.items):
1464 | if idx:
1465 | self.write(', ')
1466 | self.visit(item.key, frame)
1467 | self.write(': ')
1468 | self.visit(item.value, frame)
1469 | self.write('}')
1470 |
1471 | def binop(operator, interceptable=True):
1472 | def visitor(self, node, frame):
1473 | if self.environment.sandboxed and \
1474 | operator in self.environment.intercepted_binops:
1475 | self.write('Jinja2.call_binop(context, %r, ' % operator)
1476 | self.visit(node.left, frame)
1477 | self.write(', ')
1478 | self.visit(node.right, frame)
1479 | else:
1480 | self.write('(')
1481 | self.visit(node.left, frame)
1482 | self.write(' %s ' % operator)
1483 | self.visit(node.right, frame)
1484 | self.write(')')
1485 | return visitor
1486 |
1487 | def uaop(operator, interceptable=True):
1488 | def visitor(self, node, frame):
1489 | if self.environment.sandboxed and \
1490 | operator in self.environment.intercepted_unops:
1491 | self.write('Jinja2.call_unop(context, %r, ' % operator)
1492 | self.visit(node.node, frame)
1493 | else:
1494 | self.write('(' + operator)
1495 | self.visit(node.node, frame)
1496 | self.write(')')
1497 | return visitor
1498 |
1499 | visit_Add = binop('+')
1500 | visit_Sub = binop('-')
1501 | visit_Mul = binop('*')
1502 | visit_Div = binop('/')
1503 |
1504 | def visit_FloorDiv(self, node, frame):
1505 | self.writer.write("Math.floor(")
1506 | self.visit(node.left, frame)
1507 | self.writer.write(" / ")
1508 | self.visit(node.right, frame)
1509 | self.writer.write(")")
1510 |
1511 | def visit_Pow(self, node, frame):
1512 | self.writer.write("Math.pow(")
1513 | self.visit(node.left, frame)
1514 | self.writer.write(", ")
1515 | self.visit(node.right, frame)
1516 | self.writer.write(")")
1517 |
1518 | visit_Mod = binop('%')
1519 | visit_And = binop('&&', interceptable=False)
1520 | visit_Or = binop('||', interceptable=False)
1521 | visit_Pos = uaop('+')
1522 | visit_Neg = uaop('-')
1523 | visit_Not = uaop('! ', interceptable=False)
1524 | del binop, uaop
1525 |
1526 | def visit_Concat(self, node, frame):
1527 | if frame.eval_ctx.volatile:
1528 | func_name = '(context.eval_ctx.volatile and' \
1529 | ' markup_join or unicode_join)'
1530 | elif frame.eval_ctx.autoescape:
1531 | func_name = 'markup_join'
1532 | else:
1533 | func_name = 'unicode_join'
1534 | self.write('%s((' % func_name)
1535 | for arg in node.nodes:
1536 | self.visit(arg, frame)
1537 | self.write(', ')
1538 | self.write('))')
1539 |
1540 | def visit_Compare(self, node, frame):
1541 | self.visit(node.expr, frame)
1542 | for op in node.ops:
1543 | self.visit(op, frame)
1544 |
1545 | def visit_Operand(self, node, frame):
1546 | self.write(' %s ' % operators[node.op])
1547 | self.visit(node.expr, frame)
1548 |
1549 | def visit_Getattr(self, node, frame):
1550 | self.visit(node.node, frame)
1551 | self.write('[%r]'%node.attr)
1552 | # self.write('environment.getattr(')
1553 | # self.visit(node.node, frame)
1554 | # self.write(', %r)' % node.attr)
1555 |
1556 | def visit_Getitem(self, node, frame):
1557 | # slices bypass the environment getitem method.
1558 | # if isinstance(node.arg, nodes.Slice):
1559 | self.visit(node.node, frame)
1560 | self.write('[')
1561 | self.visit(node.arg, frame)
1562 | self.write(']')
1563 | # else:
1564 | # self.write('Jinja2.getitem(')
1565 | # self.visit(node.node, frame)
1566 | # self.write(', ')
1567 | # self.visit(node.arg, frame)
1568 | # self.write(')')
1569 |
1570 | def visit_Slice(self, node, frame):
1571 | if node.start is not None:
1572 | self.visit(node.start, frame)
1573 | self.write(':')
1574 | if node.stop is not None:
1575 | self.visit(node.stop, frame)
1576 | if node.step is not None:
1577 | self.write(':')
1578 | self.visit(node.step, frame)
1579 |
1580 | def visit_Filter(self, node, frame):
1581 | self.write('context.callfilter(%s, ['%self.filters[node.name])
1582 | func = self.environment.filters.get(node.name)
1583 | if func is None:
1584 | self.fail('no filter named %r' % node.name, node.lineno)
1585 | if getattr(func, 'contextfilter', False):
1586 | node.args.prepend('context')
1587 | self.write('context, ')
1588 | elif getattr(func, 'evalcontextfilter', False):
1589 | self.write('context.eval_ctx, ')
1590 | elif getattr(func, 'environmentfilter', False):
1591 | self.write('Jinja2, ')
1592 |
1593 | # if the filter node is None we are inside a filter block
1594 | # and want to write to the current buffer
1595 | if node.node is not None:
1596 | self.visit(node.node, frame)
1597 | elif frame.eval_ctx.volatile:
1598 | self.write('(context.eval_ctx.autoescape and'
1599 | ' escape(%s) or %s)' %
1600 | (frame.buffer, frame.buffer))
1601 | elif frame.eval_ctx.autoescape:
1602 | self.write('escape(%s)' % frame.buffer)
1603 | else:
1604 | self.write(frame.buffer)
1605 | self.write(']')
1606 | self.signature(node, frame)
1607 | self.write(')')
1608 |
1609 | def visit_Test(self, node, frame):
1610 | self.write(self.tests[node.name] + '(')
1611 | if node.name not in self.environment.tests:
1612 | self.fail('no test named %r' % node.name, node.lineno)
1613 | self.visit(node.node, frame)
1614 | self.signature(node, frame)
1615 | self.write(')')
1616 |
1617 | def visit_CondExpr(self, node, frame):
1618 | def write_expr2():
1619 | if node.expr2 is not None:
1620 | return self.visit(node.expr2, frame)
1621 | self.write('Jinja2.undefined(%r)' % ('the inline if-'
1622 | 'expression on %s evaluated to false and '
1623 | 'no else section was defined.' % self.position(node)))
1624 |
1625 | if not have_condexpr:
1626 | self.write('(')
1627 | self.visit(node.test, frame)
1628 | self.write(') && (')
1629 | self.visit(node.expr1, frame)
1630 | self.write(',) || (')
1631 | write_expr2()
1632 | self.write(')')
1633 | else:
1634 | self.write('((')
1635 | self.visit(node.test, frame)
1636 | self.write(')?')
1637 | self.visit(node.expr1, frame)
1638 | self.write(':')
1639 | write_expr2()
1640 | self.write(')')
1641 |
1642 | def visit_Call(self, node, frame, forward_caller=False):
1643 | if self.environment.sandboxed:
1644 | self.write('Jinja2.call(context, ')
1645 | else:
1646 | self.write('context.call(')
1647 | self.visit(node.node, frame)
1648 | extra_kwargs = forward_caller and {'caller': 'caller'} or None
1649 | self.signature(node, frame, extra_kwargs)
1650 | self.write(')')
1651 |
1652 | def visit_Keyword(self, node, frame):
1653 | # self.write(node.key + '=')
1654 | self.write('\'%s\':'%node.key)
1655 | self.visit(node.value, frame)
1656 |
1657 | # -- Unused nodes for extensions
1658 |
1659 | def visit_MarkSafe(self, node, frame):
1660 | self.write('escape(')
1661 | self.visit(node.expr, frame)
1662 | self.write(')')
1663 |
1664 | def visit_MarkSafeIfAutoescape(self, node, frame):
1665 | self.write('(context.eval_ctx.autoescape and Markup or identity)(')
1666 | self.visit(node.expr, frame)
1667 | self.write(')')
1668 |
1669 | def visit_EnvironmentAttribute(self, node, frame):
1670 | self.write('Jinja2.' + node.name)
1671 |
1672 | def visit_ExtensionAttribute(self, node, frame):
1673 | self.write('Jinja2.extensions[%r].%s' % (node.identifier, node.name))
1674 |
1675 | def visit_ImportedName(self, node, frame):
1676 | self.write(self.import_aliases[node.importname])
1677 |
1678 | def visit_InternalName(self, node, frame):
1679 | self.write(node.name)
1680 |
1681 | def visit_ContextReference(self, node, frame):
1682 | self.write('context')
1683 |
1684 | def visit_Continue(self, node, frame):
1685 | self.writeline('continue;', node)
1686 |
1687 | def visit_Break(self, node, frame):
1688 | self.writeline('break;', node)
1689 |
1690 | def visit_Scope(self, node, frame):
1691 | scope_frame = frame.inner()
1692 | scope_frame.inspect(node.iter_child_nodes())
1693 | aliases = self.push_scope(scope_frame)
1694 | self.pull_locals(scope_frame)
1695 | self.blockvisit(node.body, scope_frame)
1696 | self.pop_scope(aliases, scope_frame)
1697 |
1698 | def visit_EvalContextModifier(self, node, frame):
1699 | for keyword in node.options:
1700 | self.writeline('context.eval_ctx.%s = ' % keyword.key)
1701 | self.visit(keyword.value, frame)
1702 | try:
1703 | val = keyword.value.as_const(frame.eval_ctx)
1704 | except nodes.Impossible:
1705 | frame.eval_ctx.volatile = True
1706 | else:
1707 | setattr(frame.eval_ctx, keyword.key, val)
1708 |
1709 | def visit_ScopedEvalContextModifier(self, node, frame):
1710 | old_ctx_name = self.temporary_identifier()
1711 | safed_ctx = frame.eval_ctx.save()
1712 | self.writeline('%s = context.eval_ctx.save()' % old_ctx_name)
1713 | self.visit_EvalContextModifier(node, frame)
1714 | for child in node.body:
1715 | self.visit(child, frame)
1716 | frame.eval_ctx.revert(safed_ctx)
1717 | self.writeline('context.eval_ctx.revert(%s)' % old_ctx_name)
1718 |
--------------------------------------------------------------------------------