├── 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