Skip to content

Commit

Permalink
Added caching, buffering, custom tags and global config support.
Browse files Browse the repository at this point in the history
  • Loading branch information
Oliver Morgan committed Aug 29, 2010
1 parent 91ad63f commit 46871ef
Show file tree
Hide file tree
Showing 2 changed files with 258 additions and 45 deletions.
142 changes: 115 additions & 27 deletions README.md
Expand Up @@ -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.
Expand Down Expand Up @@ -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) %>';

Expand All @@ -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.
161 changes: 143 additions & 18 deletions 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();
Expand All @@ -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.