summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--luigi-template.js196
-rw-r--r--test.js58
2 files changed, 254 insertions, 0 deletions
diff --git a/luigi-template.js b/luigi-template.js
new file mode 100644
index 0000000..8bf35ef
--- /dev/null
+++ b/luigi-template.js
@@ -0,0 +1,196 @@
+LuigiTemplate = (function() {
+ "use strict";
+
+ var VERSION = '0.3.0';
+
+ // list of built-in filters
+ var FILTERS = {
+ hash: function(v, args) {
+ return 'hash(' + v + ')';
+ },
+
+ uc: function(v) {
+ return v.toUpperCase();
+ },
+
+ lc: function(v) {
+ return v.toLowerCase();
+ },
+
+ pluralize: function(v) {
+ return v + 's';
+ },
+
+ length: function(v) {
+ return v.length;
+ },
+
+ trim: function(v) {
+ return (v || '').replace(/^\s+|\s+$/mg, '');
+ },
+
+ h: (function() {
+ var LUT = {
+ '"': '"',
+ "'": ''',
+ '>': '>',
+ '<': '&lt;',
+ '&': '&amp;'
+ };
+
+ return function(v) {
+ if (v === undefined || v === null)
+ return '';
+
+ return (v || '').replace(/(['"<>&])/g, function(s) {
+ return LUT[s];
+ });
+ };
+ })()
+ };
+
+ var RES = {
+ run: /%\{\s*(\w+)((\s*\|\s*\w+\s*(\([\w\s,-]+\))?)*)\}/g,
+ filter: /(\w+)\s*(\(([\w\s,-]+)\))?/,
+ trim: /^\s+|\s+$/
+ };
+
+ function init(s, o) {
+ this.s = s;
+ this.o = o;
+ this.filters = (o && 'filters' in o) ? o.filters : {};
+ };
+
+ function safe_trim(s) {
+ return ((s !== undefined) ? s : '').replace(RES.trim, '');
+ }
+
+ // given a filter string, return a list of filters
+ function make_filter_list(s) {
+ var i, l, a, md, r = [], fs = s.split(/\s*\|\s*/);
+
+ if (s.length > 0 && fs.length > 0) {
+ for (i = 1, l = fs.length; i < l; i++) {
+ if (md = fs[i].match(RES.filter)) {
+ r.push({
+ k: md[1],
+ a: safe_trim(md[3]).split(/\s*,\s*/)
+ });
+ } else {
+ throw new Error("invalid filter string: " + fs[i]);
+ }
+ }
+ }
+
+ return r;
+ }
+
+ function get_filter(k, me) {
+ var r = me.filters[k] || FILTERS[k];
+
+ if (!r)
+ throw new Error("unknown filter: " + k);
+
+ return r;
+ }
+
+ function run(o) {
+ var i, l, f, fs, me = this;
+
+ // TODO: add compiled support here
+
+ return this.s.replace(RES.run, function(m, k, filters) {
+ var r = o[k];
+
+ // build filter list
+ fs = make_filter_list(filters);
+
+ // iterate over and apply each filter
+ for (i = 0, l = fs.length; i < l; i++) {
+ // get/check filter
+ f = get_filter(fs[i].k, me);
+
+ // apply filter
+ r = f(r, fs[i].a, o, this);
+ }
+
+ // return result
+ return r;
+ });
+ }
+
+ function get_inline_template(key) {
+ // get script element
+ var e = document.getElementById(key);
+ if (!e)
+ throw new Error('unknown inline template key: ' + key);
+
+ // return result
+ return e.innerText || '';
+ }
+
+ // declare constructor
+ var T = init;
+
+ // declare run method
+ T.prototype.run = run;
+
+ // declare cache constructor
+ T.Cache = function(templates, try_dom) {
+ this.templates = templates;
+ this.try_dom = !!try_dom;
+ this.cache = {};
+ };
+
+ // cache run method
+ T.Cache.prototype.run = function(key, args) {
+ if (!(key in this.cache)) {
+ var s = null;
+
+ if (key in this.templates) {
+ // get template from constructor templates
+ s = this.templates[key].join('');
+ } else if (this.try_dom) {
+ // get source from inline script tag
+ s = get_inline_template(key);
+ } else {
+ throw new Error('unknown key: ' + key);
+ }
+
+ // cache template
+ this.cache[key] = new T(s);
+ }
+
+ // run template
+ return this.cache[key].run(args);
+ };
+
+ // declare domcache constructor
+ T.DOMCache = function() {
+ this.cache = {};
+ };
+
+ // domcache run method
+ T.DOMCache.prototype.run = function(key, args) {
+ if (!(key in this.cache))
+ this.cache[key] = new T(get_inline_template(key));
+
+ // run template
+ return this.cache[key].run(args);
+ };
+
+ // create DOMCache singleton
+ T.dom = new T.DOMCache();
+
+ // expose filters and version
+ T.FILTERS = FILTERS;
+ T.VERSION = VERSION;
+
+ // add singleton run
+ T.run = function(s, o) {
+ return new T(s).run(o);
+ }
+
+ // expose interface
+ return T;
+}());
diff --git a/test.js b/test.js
new file mode 100644
index 0000000..afbad18
--- /dev/null
+++ b/test.js
@@ -0,0 +1,58 @@
+
+load('luigi-template.js');
+
+// define custom template filter
+function custom_filter(v) {
+ return "foo" + v + "bar";
+}
+
+function custom_filter_with_args(v, args) {
+ var i, l, r = [v];
+
+ for (i = 0, l = args.length; i < l; i++)
+ r.push(args[i]);
+
+ return r.join(' and ');
+}
+
+// add custom template filters
+LuigiTemplate.FILTERS.custom = custom_filter;
+LuigiTemplate.FILTERS.custom_args = custom_filter_with_args;
+
+// build template string
+var template_str = [
+ // test basic templates
+ "%{greet}, %{name}!",
+
+ // test filters and filters with parameters
+ "Your name hashes to: %{name|hash(sha1)|uc}",
+
+ // test custom filter
+ "Your custom filtered name is: %{name|custom}",
+
+ // test custom filter with arguments
+ "Your custom_args name is: %{name|custom_args(foo,bar,baz)}",
+
+ // test whitespace in filters
+ "random test: %{name | hash( sha512 ) | uc }",
+
+ // test pluralize filter
+ 'pluralize test (0): %{count_0} %{count_0 | pluralize(item)}',
+ 'pluralize test (1): %{count_1} %{count_1 | pluralize(item)}',
+ 'pluralize test (10): %{count_10} %{count_10 | pluralize(item)}',
+
+ // terminating newline
+ ''
+].join("\n");
+
+// build template
+var t = new LuigiTemplate(template_str);
+
+// print results
+print(t.run({
+ greet: 'hello',
+ name: 'paul',
+ count_0: 0,
+ count_1: 1,
+ count_10: 10
+}));