Permalink
Browse files

Added caching, buffering, custom tags and global config support.

  • Loading branch information...
1 parent 91ad63f commit 46871efb74a0499977a101c969363ffe629a8bad Oliver Morgan committed Aug 29, 2010
Showing with 258 additions and 45 deletions.
  1. +115 −27 README.md
  2. +143 −18 index.js
View
142 README.md
@@ -4,32 +4,13 @@ Parrot is an incredibly lightweight, and super fast templating engine for node.j
All feedback is welcome.
-## Quick Start
+## Installation
Open up your project folder, create a folder called 'lib/parrot' if it doesn't exist already. Navigate to that folder and place the parrot index.js within that folder. The url to download the parrot files can be found here:
http://github.com/ollym/parrot/zipball/master
You should now have the parrot library installed in the lib/parrot folder.
-
-Within your application, you can use parrot like the following:
-
- var parrot = require('./lib/parrot');
-
- var output = parrot.render(input);
-
-Where the input variable is a string you would like parrot to render. If you would like to render a file, do the following:
-
- var parrot = require('./lib/parrot'),
- fs = require('fs');
-
- fs.readFile(file, function(data) {
-
- var output = parrot.render(data);
- });
-
-Where file is a string value of the file you wish to render, and output is the rendered template.
-
## Syntax Reference
Being lightweight, parrot offloads a lot of the processing to the V8 interpreter, which compiles the code and runs as fast as the rest of your application. In fact, parrot shouldn't add any overhead to your application at all! For this reason, the syntax within the parrot tags is javascript. You can perform any function you wish within the bounds of javascript and node.js.
@@ -60,10 +41,63 @@ As supposed to the example above, it can also be written as:
<% } %>
</html>
-As you would have noticed, the colon (:) and endfor directives have been replaced with curley brackets, and the <%= %> tags have been replaced with a print() function. Parrot works by buffering data sent using print() before returning it to the user.
+As you would have noticed, the colon (:) and endfor directives have been replaced with curly brackets, and the <%= %> tags have been replaced with a print() function. Parrot works by buffering data sent using print() before returning it to the user.
+
+## API Reference
+
+Within your application, you can use parrot like the following:
+
+ var parrot = require('./lib/parrot');
+
+ var output = parrot.render(input);
+
+Where the input variable is a string you would like parrot to render. If you would like to render a file, do the following:
-## Advanced Usage
-If you wish to expose functions and variables to the script, you can provide them within an object as the second parameter to the parrot function. For example:
+ var parrot = require('./lib/parrot'),
+ fs = require('fs');
+
+ fs.readFile(file, function(data) {
+
+ var output = parrot.render(data);
+ });
+
+Where file is a string value of the file you wish to render, and output is the rendered template.
+
+If you want to stream your template in chunks you can add a function as the second or third parameter and it will be called every time data is printed from the template. If the buffer configuration is set to true, then the entire output is provided to this function once rendering has ended. For example:
+
+ parrot.render(input, function(chunk) {
+
+ // Send chunks as they are received
+ res.write(chunk);
+ });
+
+Or if you have configurations set:
+
+ parrot.render(input, {cache: 0}, function(chunk) {
+
+ // Send chunks as they are received
+ res.write(chunk);
+ });
+
+> Note: If you have buffer set to true, then the chunk parameter passed to the callback function will be identical to the value returned by the method.
+
+### Advanced Methods
+
+If you want to manually flush the internal cache, you can do so by using the parrot.clearCache() method. For example:
+
+ parrot.clearCache();
+
+## Configuration
+
+To configure parrot you have two available options: global or local. Altering parrot's global configuration will affect every rendering process that takes values from the global configuration as they're not already defined locally. Both local configuration settings are the same.
+
+### Sandbox
+
+The sandbox property defines any variables or methods that you want to expose to the template. These can be functions you want to allow your template to use, or variables defined earlier in your script.
+
+Default: {}
+
+Example:
var input = '<%= square(2) %>';
@@ -72,11 +106,65 @@ If you wish to expose functions and variables to the script, you can provide the
}
var output = parrot.render(input, {
- square: square
+ sandbox: {
+ square: square
+ }
});
-Will output:
+ // Output = 4
+
+> Note you cannot replace the print() function, as this is reserved by parrot.
+
+### Cache
+
+Parrot will internally cache all rendered templates, which will dramatically reduce rendering time in future. Just incase you're loading dynamic content within your template, you define the cache property as an integer representing the number of seconds that parrot will hold the rendered template in the cache.
+
+Default: 3600 * 24 // 1 day (24 hours)
- 4
+Example:
+
+ parrot.render(input, {
+ cache: 3600 * 24 // Will cache a template for a whole day (24 hours)
+ });
+
+> Note: If the cache value is set to 0 or below, then the rendered templates will not be cached.
+
+### Buffer
+
+If your template requires heavy processing, and to wait for the whole template to render before returning it to the client is not feasible, you can set the buffer property to false. This will prompt parrot to return data as it is printed to a function given as the 3rd parameter to the render method. This is advisable for all heavy duty templates, as it allows the client to start downloading header resources before the entire template is rendered.
+
+Default: false
+
+Example:
+
+ parrot.render(input {
+ buffer: false,
+ }, function(chunk) {
+
+ // Write the chunk to the response stream
+ res.write(chunk);
+ });
+
+> Note: If buffer is set to true and a function is provided as the 2nd/3rd parameter, it will write the entire output to that function.
+
+> Note: The render method will still return the entire output once the template is finished rendering.
+
+### Tags
+
+The tags property allows you to define what tags you want in your template. By default they are: <% and %> but you can use this property to set them to whatever you want.
+
+Default: {
+ start: '<%',
+ end: '%>'
+}
+
+Example:
+
+ parrot.render(input, {
+ tags: {
+ start: '<?',
+ end: '?>'
+ }
+ });
-> Note you cannot replace the print() function, as this is reserved by parrot.
+> Note: Short tags will always work with an appended equals sign (=) to your start tag. So for the example above, it will be set to <?= ?> automatically.
View
161 index.js
@@ -1,9 +1,124 @@
-var Script = process.binding('evals').Script;
+var crypto = require('crypto'),
+ Script = process.binding('evals').Script,
+ cache = { };
-exports.render = function(data, sandbox) {
+// Defines parrot's version
+exports.version = '0.2.0';
- // Default sanbox to an empty object
- sandbox = sandbox || {};
+// Global configuration
+exports.config = {
+ sandbox: { },
+ cache: 5,
+ buffer: true,
+ tags: {
+ start: '<%',
+ end: '%>'
+ }
+};
+
+/**
+ * Clear's parrots internal cache
+ *
+ * @return undefined
+ */
+exports.clearCache = function() {
+
+ cache = {};
+}
+
+/**
+ * Renders a template
+ *
+ * @param data string The input data
+ * @param config object Any optional configuration options
+ * @return The rendered template
+ */
+exports.render = function(data, config, onprint) {
+
+ // If config is given as a function
+ if (typeof config === 'function') {
+
+ // Swap the parameters
+ onprint = config;
+ config = undefined;
+ }
+
+ if (config === undefined) {
+
+ // Use the global defaults
+ config = exports.config;
+ }
+ else {
+
+ // Set the cache configuration if none is defiend
+ config.cache = config.cache || exports.config.cache;
+
+ if (config.tags === undefined) {
+
+ // Default to the global tags
+ config.tags = exports.config.tags;
+ }
+ else {
+
+ // Default to the global tags if they aren't set
+ config.tags.start = config.tags.start || exports.config.tags.start;
+ config.tags.end = config.tags.start || exports.config.tags.start;
+ }
+
+ if (config.sandbox === undefined) {
+
+ // Set the sandbox defaults
+ config.sandbox = exports.config.sandbox;
+ }
+ else {
+
+ // Default to the global sandbox
+ var sandbox = exports.config.sandbox;
+
+ // Loop through each item in the sandbox
+ for (var key in config.sandbox) {
+
+ // And overwrite any existing sandbox item
+ sandbox[key] = config.sandbox[key];
+ }
+
+ // Replace the merged sandbox
+ config.sandbox = sandbox;
+ }
+ }
+
+ // Short forms for the start and end tags and get the parent callee
+ var et = config.tags.end,
+ st = config.tags.start,
+ ident = crypto.createHash('md5').update(data).digest('base64'),
+ output = '';
+
+ // Override the print function
+ config.sandbox.print = function(chunk) {
+
+ // We can only accept strings
+ chunk = chunk.toString();
+
+ // If the buffer configuration was set to false and the user defined a function
+ if ( ! config.buffer && typeof onprint === 'function') {
+
+ // Call the function with the data chunk
+ onprint.call(this, chunk);
+ }
+
+ // Append any data to the output buffer
+ output += chunk;
+ }
+
+ // If the output is already cached
+ if (cache[ident] !== undefined) {
+
+ // Print the entire output
+ config.sandbox.print(cache[ident]);
+
+ // And return the output
+ return output;
+ }
// Parrot can only process strings
data = data.toString();
@@ -12,27 +127,37 @@ exports.render = function(data, sandbox) {
data = 'print("' + data.replace(/"/gm, '\\"') + '");';
// Compile the input into executable javascript
- data = data.replace(/:\s*%>/gm, '{ %>')
- .replace(/<%=\s*(.+)\s*%>/gm, '"); print($1); print("')
- .replace(/<%\s*end(if|while|for|switch);*\s*%>/gmi, '"); } print("')
- .replace(/<%\s*(.+)\s*%>/gm, '"); $1 print("')
+ data = data.replace(new RegExp(':\\s*' + et, 'gm'), '{ %>')
+ .replace(new RegExp(st + '=\\s*(.+)\\s*' + et, 'gm'), '"); print($1); print("')
+ .replace(new RegExp(st + '\\s*end(if|while|for|switch);*\\s*' + et, 'gmi'), '"); } print("')
+ .replace(new RegExp(st + '\\s*(.+)\\s*' + et, 'gm'), '"); $1 print("')
.replace(/(\r\n|\r|\n)/gmi, '\\$1');
- // Get ready to take the output data
- var output = '';
+ // Execute the script, rendering the template
+ Script.runInNewContext(data, config.sandbox);
- // Override the print function
- sandbox.print = function(data) {
+ // If we have a valid cache amount
+ if (config.cache > 0) {
- // Append any data to the output buffer
- output += data;
+ // Cache the output
+ cache[ident] = output;
+
+ // Set a timeout of the time
+ setTimeout(function() {
+
+ // Delete the cache entry
+ delete cache[ident];
+
+ }, config.cache);
}
- console.log(data);
+ // If we have been buffering the output and onprint is a function
+ if (config.buffer && typeof onprint == 'function') {
- // Execute the script, rendering the template
- Script.runInNewContext(data, sandbox);
+ // Return the output value
+ return onprint.call(this, output);
+ }
// Return the output
return output;
-}
+};

0 comments on commit 46871ef

Please sign in to comment.