import os import re try: import json except ImportError: import simplejson as json from webassets.filter import Filter from webassets.utils import common_path_prefix __all__ = ('JST',) class JSTemplateFilter(Filter): """Common base class for the JST and Handlebars filters, and possibly other Javascript templating systems in the future. """ def concat(self, out, hunks, **kwargs): self.process_templates(out, hunks, **kwargs) def process_templates(self, out, hunks, **kw): raise NotImplementedError() def iter_templates_with_base(self, hunks): """Helper that for list of ``hunks``, as given to ``concat()``, yields 2-tuples of (name, hunk), with name being the name of the source file relative to the common prefix of all source files. In other words, each template gets the shortest possible name to identify it. """ base_path = self._find_base_path( [info['source_path'] for _, info in hunks]) + os.path.sep for hunk, info in hunks: name = info['source_path'] name = name[len(base_path):] name = os.path.splitext(name)[0] yield name, hunk def _find_base_path(self, paths): """Hmmm. There should aways be some common base path.""" if len(paths) == 1: return os.path.dirname(paths[0]) return common_path_prefix(paths) class JST(JSTemplateFilter): """This filter processes generic JavaScript templates. It will generate JavaScript code that runs all files through a template compiler, and makes the templates available as an object. It was inspired by `Jammit`_. For example, if you have a file named ``license.jst``: .. code-block:: html

Name: <%= name %>

Hometown: <%= birthplace %>
Then, after applying this filter, you could use the template in JavaScript: .. code-block:: javascript JST.license({name : "Moe", birthplace : "Brooklyn"}); The name of each template is derived from the filename. If your JST files are spread over different directories, the path up to the common prefix will be included. For example:: Bundle('templates/app1/license.jst', 'templates/app2/profile.jst', filters='jst') will make the templates available as ``app1/license`` and ``app2/profile``. .. note:: The filter is "generic" in the sense that it does not actually compile the templates, but wraps them in a JavaScript function call, and can thus be used with any template language. webassets also has filters for specific JavaScript template languages like :class:`~.filter.dust.DustJS` or :class:`~.filter.handlebars.Handlebars`, and those filters precompile the templates on the server, which means a performance boost on the client-side. Unless configured otherwise, the filter will use the same micro-templating language that `Jammit`_ uses, which is turn is the same one that is available in `underscore.js`_. The JavaScript code necessary to compile such templates will implicitly be included in the filter output. *Supported configuration options:* JST_COMPILER (template_function) A string that is inserted into the generated JavaScript code in place of the function to be called that should do the compiling. Unless you specify a custom function here, the filter will include the JavaScript code of it's own micro-templating language, which is the one used by `underscore.js`_ and `Jammit`_. If you assign a custom function, it is your responsibility to ensure that it is available in your final JavaScript. If this option is set to ``False``, then the template strings will be output directly, which is to say, ``JST.foo`` will be a string holding the raw source of the ``foo`` template. JST_NAMESPACE (namespace) How the templates should be made available in JavaScript. Defaults to ``window.JST``, which gives you a global ``JST`` object. JST_BARE (bare) Whether everything generated by this filter should be wrapped inside an anonymous function. Default to ``False``. .. note:: If you enable this option, the namespace must be a property of the ``window`` object, or you won't be able to access the templates. JST_DIR_SEPARATOR (separator) The separator character to use for templates within directories. Defaults to '/' .. _Jammit: .. _underscore.js: http://documentcloud.github.com/underscore/#template """ name = 'jst' options = { # The JavaScript compiler function to use 'template_function': 'JST_COMPILER', # The JavaScript namespace to put templates in 'namespace': 'JST_NAMESPACE', # Wrap everything in a closure 'bare': 'JST_BARE', # The path separator to use with templates in different directories 'separator': 'JST_DIR_SEPARATOR' } max_debug_level = None def setup(self): super(JST, self).setup() self.include_jst_script = (self.template_function == 'template') \ or self.template_function is None def process_templates(self, out, hunks, **kwargs): namespace = self.namespace or 'window.JST' if self.bare is False: out.write("(function(){\n") out.write("%s = %s || {};\n" % (namespace, namespace)) if self.include_jst_script: out.write("%s\n" % _jst_script) for name, hunk in self.iter_templates_with_base(hunks): # Make it a valid Javascript string. contents = json.dumps(hunk.data()) out.write("%s['%s'] = " % (namespace, self._get_jst_name(name))) if self.template_function is False: out.write("%s;\n" % (contents)) else: out.write("%s(%s);\n" % ( self.template_function or 'template', contents)) if self.bare is False: out.write("})();") def _get_jst_name(self, name): """Return the name for the JST with any path separators normalised""" return _path_separator_re.sub(self.separator or "/", name) _path_separator_re = re.compile(r'[/\\]+') _jst_script = 'var template = function(str){var fn = new Function(\'obj\', \'var \ __p=[],print=function(){__p.push.apply(__p,arguments);};\ with(obj||{}){__p.push(\\\'\'+str.replace(/\\\\/g, \'\\\\\\\\\')\ .replace(/\'/g, "\\\\\'").replace(/<%=([\\s\\S]+?)%>/g,\ function(match,code){return "\',"+code.replace(/\\\\\'/g, "\'")+",\'";})\ .replace(/<%([\\s\\S]+?)%>/g,function(match,code){return "\');"+code\ .replace(/\\\\\'/g, "\'").replace(/[\\r\\n\\t]/g,\' \')+"__p.push(\'";})\ .replace(/\\r/g,\'\\\\r\').replace(/\\n/g,\'\\\\n\')\ .replace(/\\t/g,\'\\\\t\')+"\');}return __p.join(\'\');");return fn;};'