Permalink
Browse files

START

  • Loading branch information...
0 parents commit 75adfe26e0a8b0534a5ac9b02ed7884612b64832 @Floby committed Mar 29, 2013
Showing with 325 additions and 0 deletions.
  1. +19 −0 .gitignore
  2. +1 −0 index.js
  3. +138 −0 lib/Template.js
  4. +120 −0 lib/compile.js
  5. +1 −0 test/hw.markup.tpl
  6. +1 −0 test/hw.tpl
  7. +21 −0 test/sink.js
  8. +24 −0 test/test-onefile.js
@@ -0,0 +1,19 @@
+.*.sw[a-z]
+.*.un~
+
+node_modules
+
+lib-cov
+*.seed
+*.log
+*.csv
+*.dat
+*.out
+*.pid
+*.gz
+
+pids
+logs
+results
+
+npm-debug.log
@@ -0,0 +1 @@
+exports.Template = require('./lib/Template');
@@ -0,0 +1,138 @@
+var path = require('path');
+var stream = require('stream');
+var StreamStream = require('stream-stream');
+var fs = require('fs');
+var util = require('util');
+var process_template = require('./compile');
+var Script = require('vm').Script;
+
+function Template (filename, options) {
+ stream.Readable.call(this, options);
+
+ this._filename = path.resolve(process.cwd(), filename);
+ this._directory = path.dirname(this._filename);
+ if(this._directory == '.') this._directory = process.cwd();
+
+
+ this._output = new StreamStream();
+ this._output.on('readable', function() {
+ this.read(0);
+ }.bind(this));
+ this._output.on('end', function() {
+ this._ended = true;
+ }.bind(this));
+}
+util.inherits(Template, stream.Readable);
+
+Template.prototype._read = function _read(size) {
+ var data = this._output.read(size);
+ var res;
+ if(data === null) {
+ if(this._ended) res = this.push(null);
+ else res = this.push('');
+ }
+ else {
+ res = this.push(data);
+ }
+
+ if(res === false) {
+ // STOP READING
+ }
+};
+
+Template.prototype.pipe = function pipe() {
+ this.run();
+ return Template.super_.prototype.pipe.apply(this, arguments);
+};
+
+Template.prototype.run = function run() {
+ if(this._running) return;
+ this._running = true;
+ var self = this;
+ var print = make_print(this);
+ this.getScript(function(err, script) {
+ if(err) return self.emit(err);
+ try {
+ script(print);
+ }
+ catch(e) {
+ self.emit('error', e);
+ return false;
+ }
+ this._output.end();
+ var current = self._getPrintStream();
+ if(current) current.end();
+ });
+};
+
+Template.prototype.addStream = function addStream(s) {
+ if(!(s instanceof stream.Stream)) {
+ return this.emit('error', new TypeError('given parameter is not a stream'));
+ }
+ this._output.write(s);
+ this.read(0);
+};
+
+function make_print (template) {
+ return function print(str) {
+ var current = template._getPrintStream();
+ if(!current) {
+ current = new stream.PassThrough();
+ template._setPrintStream(current);
+ template.addStream(current);
+ }
+ current.write(str);
+ }
+}
+
+Template.prototype._getPrintStream = function _getPrintStream() {
+ return this._printStream;
+};
+Template.prototype._setPrintStream = function _setPrintStream(stream) {
+ this._printStream = stream;
+ return this;
+}
+
+Template.prototype.getScript = function getScript(cb) {
+ if(this._script) return cb(null, script);
+ this.once('script', cb);
+ if(this._script === null) return;
+ this._script = null;
+
+ fs.readFile(this._filename, 'utf8', function(err, data) {
+ if(err) {
+ return this.emit('error', err);
+ }
+ try {
+ var tpl = process_template(data);
+ }
+ catch(e) {
+ return this.emit('error', e);
+ }
+ tpl = "(function compiled_template(print, include, data, filters) {"
+ + tpl
+ + "})";
+
+ try {
+ var script = new Script(tpl, this._filename);
+ } catch(e) {
+ this.emit('error', e);
+ return false;
+ }
+ var f = this._sandbox ? script.runInNewContext(this._sandbox)
+ : script.runInThisContext();
+
+ this.emit('script', null, this._script = f);
+ }.bind(this));
+};
+
+Template.prototype.sandbox = function sandbox(object) {
+ this.sandbox = object || {};
+ if(object === false) delete this.sandbox;
+};
+Template.prototype.data = function(data) {
+ if(!data) return this._data;
+ this._data = data;
+};
+
+module.exports = Template;
@@ -0,0 +1,120 @@
+var sys = require ('sys')
+
+function substitute_affectation(str, p1, offset, s) {
+ var split = p1.split('|');
+ var str = split.shift();
+ if(split.length == 0) {
+ return '<%print('+ str +');%>';
+ }
+ var functions = [];
+ var parenthesis = '';
+
+ var filters = compile_template._filters;
+ for (var i = 0; i < split.length; i++) {
+ var name = split[i].trim();
+ if(name == '') continue;
+ if(filters[name]) {
+ functions.push('filters.'+name);
+ parenthesis += ')';
+ }
+ };
+ var result = "<% print("
+ + functions.join('(')
+ + '('
+ + str
+ + parenthesis
+ + ');%>';
+ return result;
+}
+
+function compile_template(template) {
+ // simple substitution from <%= /* */ %> forms to <% echo() %>
+ //template = template.replace(/<%=(.*)%>/g, "<% print($1); %>");
+ template = template.replace(/<%=([^>]*)%>/, substitute_affectation);
+ // substition for includes
+ template = template.replace(/<%@(.*)%>/g, "<% include($1); %>");
+ var result = ""; ///< result string
+ var current = 0; ///< current offset
+ var finished = false; ///< is the compilation over
+ /**
+ * dummy Exception class
+ */
+ function FinishedTemplate() {
+ // as you can see, this is dummy
+ };
+ /**
+ * gets a pure text out of the template
+ * until it encounters a <%
+ */
+ function getText() {
+ var begin = current,
+ end = template.indexOf('<%', current);
+ if(end == -1) {
+ finished = true;
+ end = template.length;
+ }
+ // extract content between boundaries
+ var content = template.substring(begin, end);
+ // split content by lines
+ // for better error reporting
+ // (keep orginal line numbers)
+ var split = content.split(/\n\r|\n|\r/);
+ // we keep the last one for later as it
+ // can be an incomplete line
+ var last = split.pop();
+ for(var i=0 ; i<split.length ; ++i) {
+ result += "print("+ JSON.stringify(split[i]+"\n") +");\n";
+ }
+ if(last !== '') {
+ // note: no \n at the end
+ result += "print("+ JSON.stringify(last) +");"
+ }
+ current = end;
+ if(finished) throw new FinishedTemplate();
+ }
+ function getSource() {
+ // the +2 is for ignoring the <%
+ var begin = current + 2,
+ end = template.indexOf('%>', current);
+ if(end == -1) {
+ finished = true;
+ end = template.length;
+ }
+ var content = template.substring(begin, end);
+ result += content;
+ // the +2 is for ignoring the %>
+ current = end + 2;
+ if(finished) throw new FinishedTemplate();
+ }
+
+ try {
+ while(true) {
+ // this loops ends when the
+ // FinishedTemplate exception
+ // is thrown
+ getText();
+ getSource();
+ }
+ } catch (e) {
+ if(e instanceof FinishedTemplate)
+ return result;
+ else
+ throw e;
+ }
+}
+
+//module.exports = process_template;
+module.exports = compile_template;
+compile_template._filters = {};
+compile_template.registerFilter = function(name, func) {
+ if(!/^[a-z0-9A-Z_]+$/.test(name))
+ throw new Error("filter name should be [a-zA-Z0-9_]+");
+ if(typeof(func) !== 'function')
+ throw new TypeError("no function given when registering filter "+name);
+
+ compile_template._filters[name] = func;
+};
+compile_template.registerFilter('lowercase', function(str) {
+ return str.toLowerCase();
+});
+
@@ -0,0 +1 @@
+<%= "hello" %> <% print("world!"); %>
@@ -0,0 +1 @@
+hello world!
@@ -0,0 +1,21 @@
+var util = require('util');
+var stream = require('stream');
+
+function Sink () {
+ if(!(this instanceof Sink)) return new Sink()
+ stream.Writable.call(this);
+ this._result = [];
+ this.on('finish', function() {
+ this.emit('data', this._result.join(''));
+ });
+}
+util.inherits(Sink, stream.Writable);
+
+Sink.prototype._write = function _write(chunk, encoding, callback) {
+ this._result.push(chunk.toString('utf8'));
+ return callback();
+};
+
+module.exports = Sink;
+
+
@@ -0,0 +1,24 @@
+var Sink = require('./sink');
+var blue = require('../');
+
+exports.testOneFile = function(test) {
+ var sink = Sink();
+ var t = new blue.Template(__dirname + '/hw.tpl');
+ t.pipe(sink).on('data', function(data) {
+ test.equal(data.trim(), 'hello world!', "sink data should be identical");
+ test.done();
+ });
+}
+
+exports.testOneFileWithMarkup = function(test) {
+ var sink = Sink();
+ var t = new blue.Template(__dirname + '/hw.markup.tpl');
+ t.pipe(sink).on('data', function(data) {
+ test.equal(data.trim(), 'hello world!', "sink data should be identical");
+ test.done();
+ });
+ t.on('error', function(err) {
+ test.fail(err.message);
+ test.done();
+ });
+}

0 comments on commit 75adfe2

Please sign in to comment.