Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Made template engine streaming.

  • Loading branch information...
commit 077aa2fcecf4abf53beb7cbcc051b91e678d83f7 1 parent 16e74c9
@tuxychandru authored
View
2  examples/CRUD/layout.html
@@ -14,6 +14,6 @@
<p />
- <%= include(view) %>
+ <% include(view); %>
</body>
</html>
View
2  examples/CRUD/views/executions/add.html
@@ -1,3 +1,3 @@
<form action="/executions" method="post">
- <%= include('executions/form') %>
+ <% include('executions/form'); %>
</form>
View
2  examples/CRUD/views/executions/edit.html
@@ -1,3 +1,3 @@
<form action="/executions/<%= execution._id %>/update" method="post">
- <%= include('executions/form') %>
+ <% include('executions/form'); %>
</form>
View
2  examples/CRUD/views/languages/add.html
@@ -1,3 +1,3 @@
<form action="/languages" method="post">
- <%= include('languages/form') %>
+ <% include('languages/form'); %>
</form>
View
2  examples/CRUD/views/languages/edit.html
@@ -1,3 +1,3 @@
<form action="/languages/<%= language._id %>/update" method="post">
- <%= include('languages/form') %>
+ <% include('languages/form'); %>
</form>
View
2  examples/CRUD/views/paradigms/add.html
@@ -1,3 +1,3 @@
<form action="/paradigms" method="post">
- <%= include('paradigms/form') %>
+ <% include('paradigms/form'); %>
</form>
View
2  examples/CRUD/views/paradigms/edit.html
@@ -1,3 +1,3 @@
<form action="/paradigms/<%= paradigm._id %>/update" method="post">
- <%= include('paradigms/form') %>
+ <% include('paradigms/form'); %>
</form>
View
2  examples/layout/layout.html
@@ -7,6 +7,6 @@
|
<a href="/about">About</a>
<p>
- <%= include(view) %>
+ <% include(view); %>
</body>
</html>
View
2  examples/realtime_search/layout.html
@@ -4,7 +4,7 @@
</head>
<body>
<div id="content">
- <%= include(view) %>
+ <% include(view); %>
</div>
</body>
</html>
View
2  examples/shoutbox/views/layout.html
@@ -11,7 +11,7 @@
<div id="boxtop"></div>
<div id="content">
- <%= include(view) %>
+ <% include(view); %>
</div>
<div id="boxbot"></div>
</div>
View
85 grasshopper/lib/ghp.js
@@ -15,17 +15,25 @@
*/
var fs = require('fs');
-var cache = {};
+var cache = {},
+ bufferSize = 8 * 1024;
var helpers = [require('./helpers')];
-function fill(templateFile, model, encoding, viewsDir, extn, locale) {
+function fill(templateFile, response, model, encoding, viewsDir, extn, locale, streamer) {
+ if(streamer === undefined) {
+ var endResponse = true;
+ streamer = new Streamer(response, encoding);
+ }
var template = cache[templateFile];
if(!template) {
var content = fs.readFileSync(templateFile, encoding);
cache[templateFile] = template = compile(content, helpers.length + 2);
}
- return template(model, [new IncludeHelper(model, encoding, viewsDir, extn, locale), {locale: locale}].concat(helpers));
+ template(streamer, model, [new IncludeHelper(streamer, model, encoding, viewsDir, extn, locale), {locale: locale}].concat(helpers));
+ if(endResponse) {
+ streamer.end();
+ }
}
exports.fill = fill;
@@ -34,44 +42,49 @@ exports.addHelpers = function(newHelpers) {
for(var i = 0; i < arguments.length; i++) {
helpers.push(arguments[i]);
}
-}
+};
+
+exports.configure = function(config) {
+ if(config.templateBufferSize) {
+ bufferSize = config.templateBufferSize;
+ }
+};
function compile(text, helpersCount) {
- var funcBody = "var _out = [], model = model || {};";
+ var funcBody = "model = model || {};";
for(var i = 0; i < helpersCount; i++) {
+ funcBody += "helpers[" + i + "].out = out;";
funcBody += "with(helpers[" + i + "]) {";
}
- funcBody += "with(model){ ";
+ funcBody += "with(model) {";
var parts = text.split("<%");
parts.forEach(function(part) {
if(part.indexOf("%>") == -1) {
- funcBody += "_out.push('" + escapeCode(part) + "');";
+ funcBody += "out.write('" + escapeCode(part) + "');";
} else if(part.charAt(0) == '=') {
var subParts = part.split('%>');
- funcBody += "_out.push(" + subParts[0].substring(1) + ");";
+ funcBody += "out.write(" + subParts[0].substring(1) + ");";
if(subParts.length > 1) {
- funcBody += "_out.push('" + escapeCode(subParts[1]) + "');";
+ funcBody += "out.write('" + escapeCode(subParts[1]) + "');";
}
} else {
var subParts = part.split('%>');
funcBody += subParts[0];
if(subParts.length > 1) {
- funcBody += "_out.push('" + escapeCode(subParts[1]) + "');";
+ funcBody += "out.write('" + escapeCode(subParts[1]) + "');";
}
}
});
funcBody += "}";
-
for(var i = 0; i < helpersCount; i++) {
funcBody += "}";
}
- funcBody += "return _out.join('');"
- return new Function("model", "helpers", funcBody);
+ return new Function("out", "model", "helpers", funcBody);
}
function escapeCode(str) {
@@ -79,7 +92,8 @@ function escapeCode(str) {
}
// Class: IncludeHelper
-function IncludeHelper(model, encoding, viewsDir, extn, locale) {
+function IncludeHelper(out, model, encoding, viewsDir, extn, locale) {
+ this.out = out;
this.model = model;
this.encoding = encoding;
this.viewsDir = viewsDir;
@@ -88,5 +102,46 @@ function IncludeHelper(model, encoding, viewsDir, extn, locale) {
}
IncludeHelper.prototype.include = function(templateFile) {
- return fill(this.viewsDir + '/' + templateFile + '.' + this.extn , this.model, this.encoding, this.viewsDir, this.extn, this.locale);
+ return fill(this.viewsDir + '/' + templateFile + '.' + this.extn , undefined, this.model,
+ this.encoding, this.viewsDir, this.extn, this.locale, this.out);
+}
+
+function Streamer(response, encoding) {
+ this._out = new Buffer(bufferSize);
+ this._response = response;
+ this._bufContentLen = 0;
+ this.encoding = encoding;
}
+
+Streamer.prototype.write = function(str) {
+ if(str !== undefined) {
+ if(this.needsFlush(str)) {
+ this.flush();
+ }
+
+ var strBytes = Buffer.byteLength(str, this.encoding);
+ this._out.write(str, this._bufContentLen, this.encoding);
+ this._bufContentLen += strBytes;
+ }
+};
+
+Streamer.prototype.needsFlush = function(str) {
+ var strBytes = Buffer.byteLength(str, this.encoding);
+ if(strBytes + this._bufContentLen > bufferSize) {
+ return true;
+ }
+ return false;
+};
+
+Streamer.prototype.flush = function() {
+ this._response.write(this._out.toString(this.encoding, 0, this._bufContentLen));
+ this._out = new Buffer(bufferSize);
+ this._bufContentLen = 0;
+};
+
+Streamer.prototype.end = function() {
+ if(this._bufContentLen > 0) {
+ this.flush();
+ }
+ this._response.end();
+};
View
1  grasshopper/lib/grasshopper.js
@@ -63,6 +63,7 @@ exports.configure = function(config) {
multipart.configure(config);
session.configure(config);
i18n.configure(config);
+ ghp.configure(config);
};
exports.initModel = model.init;
View
16 grasshopper/lib/renderer.js
@@ -197,8 +197,8 @@ RequestContext.prototype.render = function(view, useLayout) {
}
try {
- var content = ghp.fill(viewFile, this.model, this.encoding, viewsDir, this.extn, this.locale);
- this.send(content);
+ this.writeHead();
+ ghp.fill(viewFile, this.response, this.model, this.encoding, viewsDir, this.extn, this.locale);
} catch(e) {
this.handleError(e);
}
@@ -210,14 +210,18 @@ RequestContext.prototype.renderText = function(text) {
};
RequestContext.prototype.send = function(text) {
- this.headers['content-type'] += '; charset=' + this.charset
- this.response.writeHead(this.status, this.headers);
+ this.writeHead();
if(this.request.method != 'HEAD') {
this.response.write(text, this.encoding);
}
this.response.end();
};
+RequestContext.prototype.writeHead = function() {
+ this.headers['content-type'] += '; charset=' + this.charset
+ this.response.writeHead(this.status, this.headers);
+};
+
RequestContext.prototype.renderStatic = function() {
if(this.request.method != 'GET' && this.request.method != 'HEAD') {
this.extn = defaultViewExtn;
@@ -275,8 +279,8 @@ RequestContext.prototype.renderError = function(status, error) {
fs.stat(viewFile, function(err, stats) {
if(!err && stats.isFile()) {
try {
- var content = ghp.fill(viewFile, {error: error}, self.encoding, viewsDir, self.extn, this.locale);
- self.send(content);
+ self.writeHead();
+ ghp.fill(viewFile, self.response, {error: error}, self.encoding, viewsDir, self.extn, this.locale);
} catch(e) {
self.handleError(e);
}
View
2  test/fixtures/ghp/simple_with_include.txt
@@ -1 +1 @@
-Hello, <%= include('included') %>!
+Hello, <% include('included'); %>!
View
30 test/ghp-test.js
@@ -3,22 +3,40 @@ var assert = require('assert'),
exports.name = 'GHP Tests';
+function MockResponse() {
+ this.out = '';
+}
+
+MockResponse.prototype.write = function(str, encoding) {
+ this.out += str;
+};
+
+MockResponse.prototype.end = function() {
+ this.ended = true;
+};
+
exports.tests = {
'Fill simple template.': function(next) {
- var result = ghp.fill('./fixtures/ghp/simple.txt', {name: 'Chandru'}, 'utf8', './fixtures/ghp', 'txt');
- assert.equal(result, 'Hello, Chandru!\n');
+ var response = new MockResponse();
+ ghp.fill('./fixtures/ghp/simple.txt', response, {name: 'Chandru'}, 'utf8', './fixtures/ghp', 'txt');
+ assert.equal(response.out, 'Hello, Chandru!\n');
+ assert.ok(response.ended);
next();
},
'Fill template with include.': function(next) {
- var result = ghp.fill('./fixtures/ghp/simple_with_include.txt', {}, 'utf8', './fixtures/ghp', 'txt');
- assert.equal(result, 'Hello, Chandru\n!\n');
+ var response = new MockResponse();
+ ghp.fill('./fixtures/ghp/simple_with_include.txt', response, {}, 'utf8', './fixtures/ghp', 'txt');
+ assert.equal(response.out, 'Hello, Chandru\n!\n');
+ assert.ok(response.ended);
next();
},
'Template with newline in code.': function(next) {
- var result = ghp.fill('./fixtures/ghp/multiline_with_newline.txt', {items: ['A', 'B', 'C']}, 'utf8', './fixtures/ghp', 'txt');
- assert.equal(result, '<li>\nA</li>\n<li>B</li>\n<li>C\n</li>\n');
+ var response = new MockResponse();
+ ghp.fill('./fixtures/ghp/multiline_with_newline.txt', response, {items: ['A', 'B', 'C']}, 'utf8', './fixtures/ghp', 'txt');
+ assert.equal(response.out, '<li>\nA</li>\n<li>B</li>\n<li>C\n</li>\n');
+ assert.ok(response.ended);
next();
}
};
Please sign in to comment.
Something went wrong with that request. Please try again.