├── .gitignore ├── .npmignore ├── index.js ├── examples ├── views │ ├── _footer.jst │ ├── index.jst │ └── layout.jst ├── locales │ └── zh_CN.json ├── public │ └── stylesheets │ │ └── style.css └── app.js ├── package.json ├── bin └── jst ├── lib ├── filters.js ├── jst.js └── hash.js ├── Readme.md ├── jst.js └── crc32.js /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | test/* 4 | 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | .git/* 3 | test/* 4 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/jst'); 2 | -------------------------------------------------------------------------------- /examples/views/_footer.jst: -------------------------------------------------------------------------------- 1 | Template powered by node-jst. 2 | -------------------------------------------------------------------------------- /examples/locales/zh_CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello {name}": "你好 {name}", 3 | "There is a car": "有一辆车", 4 | "There are {n} cars": "有{n}辆车" 5 | } 6 | -------------------------------------------------------------------------------- /examples/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } -------------------------------------------------------------------------------- /examples/views/index.jst: -------------------------------------------------------------------------------- 1 |

{{ title }}

2 |

Welcome to {{ title }}

3 | 4 |

i18n

5 |

{{ _('Hello {name}', {name: 'JST'}) }}

6 |

{{ _n('There is a car', 'There are {n} cars', 1) }}

7 |

{{ _n('There is a car', 'There are {n} cars', 3) }}

8 | -------------------------------------------------------------------------------- /examples/views/layout.jst: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ title }} 6 | 7 | 8 | 9 | {{ body }} 10 | 11 | {{ partial('footer') }} 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jst", 3 | "description": "Node JavaScript Template, A pretty high performance template engine", 4 | "version": "0.0.14", 5 | "author": "Shaun Li ", 6 | "keywords": ["template", "engine", "jst"], 7 | "main": "./lib/jst.js", 8 | "dependencies": { 9 | "locales": ">0.0.1" 10 | , "commander": ">0.0.1" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /bin/jst: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var program = require('commander') 4 | , fs = require('fs') 5 | , jst = require('../index') 6 | 7 | program 8 | .version(jst.version) 9 | .parse(process.argv); 10 | 11 | var filenames = program.args; 12 | 13 | filenames.forEach(function(filename){ 14 | var template = fs.readFileSync(filename, 'utf8') 15 | 16 | console.log(jst.compile(template, true)); 17 | }) 18 | -------------------------------------------------------------------------------- /lib/filters.js: -------------------------------------------------------------------------------- 1 | // filters 2 | 3 | const htmlCodes = {'&': '&', '<': '<', '>': '>', '"': '"'}, 4 | htmlre = /&(?!\w+;)|<|>|"/g, 5 | htmlEscape = function (src) { return htmlCodes[src]; }, 6 | linere = /(\r\n|\r|\n)/g; 7 | 8 | exports.convert = function(src) { 9 | return src.split('|').reduce(function(varname, filter) { 10 | return 'filters.' + filter + '(' + varname + ')'; 11 | }); 12 | } 13 | 14 | function escape(src) { 15 | return typeof src !== 'string' ? src : src.replace(htmlre, htmlEscape); 16 | } 17 | 18 | function linebreaks(src) { 19 | return '

' + src.split(/\r\n|\n/g).join('

') + '

'; 20 | } 21 | 22 | function linebreaksbr(src) { 23 | return src.replace(linere, '
$1'); 24 | } 25 | 26 | function add(value) { 27 | return function(src) { return Number(value) + Number(src); }; 28 | } 29 | 30 | function markdown(src) { 31 | return src; // TODO: 32 | } 33 | 34 | exports.filters = { 35 | escape: escape, e: escape, 36 | linebreaks: linebreaks, 37 | linebreaksbr: linebreaksbr, 38 | markdown: markdown, md: markdown, 39 | add: add 40 | }; 41 | 42 | -------------------------------------------------------------------------------- /examples/app.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var express = require('express'), 7 | jst = require('jst'), 8 | locales = require('locales'); 9 | 10 | var app = module.exports = express(); 11 | 12 | // Configuration 13 | 14 | locales.configure({ 15 | locales: __dirname + '/locales' 16 | }); 17 | 18 | app.configure(function(){ 19 | app.set('views', __dirname + '/views'); 20 | app.set('view engine', 'jst'); 21 | app.engine('jst', jst.renderFile); 22 | app.use(express.bodyParser()); 23 | app.use(express.methodOverride()); 24 | app.use(function(req, res, next) { req.lang = 'zh_CN'; next(); }); 25 | app.use(locales.detector); // for i18n 26 | app.use(app.router); 27 | app.use(express.static(__dirname + '/public')); 28 | }); 29 | 30 | app.configure('development', function(){ 31 | app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); 32 | }); 33 | 34 | app.configure('production', function(){ 35 | app.use(express.errorHandler()); 36 | }); 37 | 38 | app.locals = { 39 | _: locales.gettext, 40 | _n: locales.ngettext 41 | }; 42 | 43 | // Routes 44 | 45 | app.get('/', function(req, res){ 46 | res.render('index', { 47 | title: 'Express' 48 | }); 49 | }); 50 | 51 | // Only listen on $ node app.js 52 | 53 | if (!module.parent) { 54 | app.listen(3000); 55 | console.log("Express server listening on port %d", 3000); 56 | } 57 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Node JavaScript Template 2 | 3 | Node-jst is a pretty high performance template engine and implemented 4 | with JavaScript for Node.js 5 | 6 | ## Installation 7 | 8 | via npm: 9 | 10 | npm install jst 11 | 12 | ## Features 13 | 14 | * Automatically caching of intermediate JavaScript 15 | * Unbuffered code for embed codes etc `{% code %}` or `{{ variable }}` 16 | * Enforcing coding standard, for example `{{ variable }}` is correct, but `{{variable}}` is wrong 17 | * Customizable filters 18 | 19 | ## Example 20 | 21 | {% if (user) { %} 22 |

{{ user.name }}

23 | {% } %} 24 | 25 | ## Usage 26 | 27 | var jst = require('jst'); 28 | 29 | // Render a string 30 | jst.render('Hello {{ name }}', {name: 'jst'}); 31 | 32 | // Render a file 33 | jst.renderFile('path/to/some.html', {name: 'jst'}, function(err, ctx) { 34 | // second arg are optional, 35 | // the callback can be the second arg 36 | }); 37 | 38 | // Compile a function 39 | var fn = jst.compile('Hello {{ name }}'); 40 | fn({name: 'jst'}); 41 | 42 | // Use `it.` as prefix of variables so that you can run it more than 30 times faster 43 | jst.render('Hello {{ it.name }}', {name: 'jst'}); 44 | 45 | // Filters 46 | jst.render('Hello {{ it.name|e }}', {name: 'jst'}); 47 | jst.render('{{ it.entry|e|linebreaks }}', {entry: '...'}); 48 | jst.render('{{ it.value|add(123) }}', {value: 123}); 49 | 50 | // Custom filters 51 | jst.addFilter('filterName', function(src) { ... }); 52 | jst.addFilters({anotherFilter: function(src) { ... }}); 53 | jst.render('{{ it.value|filterName }}', {value: 123}); 54 | jst.render('{{ it.value|anotherFilter }}', {value: 123}); 55 | // or 56 | jst.addFilter('filterName', function(arg1, arg2, arg3) { return function(src) { ... }}); 57 | jst.render('{{ it.value|filterName(1, 2, 3) }}', {value: 123}); 58 | 59 | // Client side 60 | 61 | 64 | 65 | ## Benchmarks 66 | 67 | [A brief comparison of some JavaScript templating engines on a short 68 | template: 7 DOM nodes ... 7 interpolated values.][link] 69 | 70 | [link]: http://jsperf.com/dom-vs-innerhtml-based-templating/144 71 | 72 | ## License 73 | 74 | (The MIT License) 75 | -------------------------------------------------------------------------------- /jst.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Node-jst for browsers 3 | * Copyright(c) 2011 Shaun Li 4 | * MIT Licensed 5 | */ 6 | 7 | // compiler 8 | 9 | window['jst'] = {}; 10 | 11 | ;(function(exports) { 12 | 13 | var htmlCodes = {'&': '&', '<': '<', '>': '>', '"': '"'}, 14 | htmlre = /&(?!\w+;)|<|>|"/g, 15 | htmlEscape = function (src) { return htmlCodes[src]; }, 16 | linere = /(\r\n|\r|\n)/g, 17 | // 18 | _cache = {}, 19 | _options = { 20 | useIt: false 21 | }; 22 | 23 | var filters = exports['filters'] = {}; 24 | 25 | function convertFilters(src) { 26 | return src.split('|').reduce(function(varname, filter) { 27 | return 'filters.' + filter + '(' + varname + ')'; 28 | }); 29 | } 30 | 31 | filters['e'] = filters['escape'] = function(src) { 32 | return typeof src !== 'string' ? src : src.replace(htmlre, htmlEscape); 33 | } 34 | 35 | filters['linebreaks'] = function(src) { 36 | return '

' + src.split(/\r\n|\n/g).join('

') + '

'; 37 | } 38 | 39 | filters['linebreaksbr'] = function(src) { 40 | return src.replace(linere, '
$1'); 41 | } 42 | 43 | filters['add'] = function(value) { 44 | return function(src) { return Number(value) + Number(src); }; 45 | } 46 | 47 | var prefixes = [ 48 | // s , c , v 49 | ['' , '"; ' , '" + ' ] , // s 50 | [' out += "' , ' ' , ' out += ' ] , // c 51 | [' + "' , '; ' , ' + ' ] , // v 52 | ['"' , '' , '' ] , // n 53 | ['"; ' , ' ' , '; ' ] // end 54 | ], 55 | codere = /\{[%\{] (.+?) [%\}]\}/g; 56 | 57 | var compile = exports.compile = function(ctx) { 58 | var m, i = 0, code = 'var out = ', last = 3 /* n */; 59 | 60 | _options.useIt = /{{ (e\()?it\./.test(ctx); 61 | 62 | ctx = ctx.replace(/[\t\r\n]/g, '').replace(/\{#.+?#\}/g, '') 63 | 64 | if (!_options.useIt) { 65 | code += '""; with(it) {'; 66 | last = 1 /* c */; 67 | } 68 | 69 | while ((m = codere.exec(ctx)) !== null) { 70 | if (m.index > 0 && m.index > i) { 71 | code += prefixes[last][0 /* s */] + ctx.substring(i, m.index).replace(/"/g, '\\"'); 72 | last = 0 /* s */; 73 | } 74 | 75 | if (m[0].indexOf('{%') === 0) { 76 | code += prefixes[last][1 /* c */] + m[1]; 77 | if (/\)$/.test(m[1])) code += ';'; 78 | last = 1 /* c */; 79 | } else if (m[0].indexOf('{{') === 0) { 80 | code += prefixes[last][2 /* v */] + convertFilters(m[1]); 81 | last = 2 /* v */; 82 | } 83 | 84 | i = m.index + m[0].length; 85 | } 86 | 87 | if (i < ctx.length) { 88 | code += prefixes[last][0 /* s */] + ctx.substring(i).replace(/"/g, '\\"'); 89 | last = 0 /* s */; 90 | } 91 | 92 | code += prefixes[4 /* end */][last]; 93 | 94 | if (!_options.useIt) 95 | code += '} '; 96 | 97 | code += 'return out;'; 98 | //console.log(code); 99 | 100 | var fn = new Function('it, filters', code); 101 | 102 | return function(args) { 103 | return fn.call(this, args, filters); 104 | } 105 | } 106 | 107 | var render = exports.render = function(ctx, args) { 108 | var fn; 109 | if (typeof crc32 === 'function') { 110 | var ck = crc32(ctx); 111 | fn = _cache[ck]; 112 | if (typeof fn === 'undefined') 113 | fn = _cache[ck] = compile(ctx); 114 | } else 115 | fn = compile(ctx); 116 | return fn(args); 117 | } 118 | 119 | })(jst); 120 | 121 | -------------------------------------------------------------------------------- /crc32.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Calculates the CRC32 checksum of a string. 3 | * 4 | * @param {String} str 5 | * @param {Boolean} hex 6 | * @return {String} checksum 7 | * @api public 8 | */ 9 | function crc32(str, hex) { 10 | var crc = ~0, i; 11 | for (i = 0, l = str.length; i < l; i++) { 12 | crc = (crc >>> 8) ^ crc32tab[(crc ^ str.charCodeAt(i)) & 0xff]; 13 | } 14 | crc = crc ^ -1; 15 | return hex ? crc.toString(16) : crc; 16 | }; 17 | 18 | var crc32tab = [ 19 | 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 20 | 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 21 | 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 22 | 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 23 | 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 24 | 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 25 | 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 26 | 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 27 | 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 28 | 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 29 | 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 30 | 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 31 | 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 32 | 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 33 | 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 34 | 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 35 | 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 36 | 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 37 | 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 38 | 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 39 | 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 40 | 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 41 | 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 42 | 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 43 | 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 44 | 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 45 | 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 46 | 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 47 | 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 48 | 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 49 | 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 50 | 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 51 | 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 52 | 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 53 | 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 54 | 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 55 | 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 56 | 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 57 | 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 58 | 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 59 | 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 60 | 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 61 | 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 62 | 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 63 | 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 64 | 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 65 | 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 66 | 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 67 | 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 68 | 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 69 | 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 70 | 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 71 | 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 72 | 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 73 | 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 74 | 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 75 | 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 76 | 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 77 | 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 78 | 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 79 | 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 80 | 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 81 | 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 82 | 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d 83 | ]; 84 | 85 | -------------------------------------------------------------------------------- /lib/jst.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Node-jst 3 | * Copyright(c) 2011 Shaun Li 4 | * MIT Licensed 5 | */ 6 | 7 | var fs = require('fs'), 8 | filters = require('./filters'), 9 | hash = require('./hash'); 10 | 11 | exports.version = '0.0.13'; 12 | 13 | var _cache = {}, 14 | _files = {}, 15 | _options = { 16 | useIt: false 17 | }; 18 | 19 | exports.configure = function(options) { 20 | for (var prop in options) { 21 | _options[prop] = options[prop]; 22 | } 23 | } 24 | 25 | exports.addFilter = function(name, fn) { 26 | filters.filters[name] = fn; 27 | } 28 | 29 | exports.addFilters = function(newFilters) { 30 | for (var name in newFilters) 31 | filters.filters[name] = newFilters[name]; 32 | } 33 | 34 | // compiler 35 | 36 | const prefixes = { 37 | n: {s: '"', c: '', v: ''}, 38 | s: {s: '', c: '"; ', v: '" + '}, 39 | c: {s: ' out += "', c: ' ', v: ' out += '}, 40 | v: {s: ' + "', c: '; ', v: ' + '}, 41 | end: {s: '"; ', c: ' ', v: '; '} 42 | }, 43 | codere = /\{[%\{] (.+?) [%\}]\}/g; 44 | 45 | var compile = exports.compile = function(ctx, isToCode) { 46 | var m, i = 0, code = 'var out = ', last = 'n'; 47 | 48 | _options.useIt = /{{ (e\()?it\./.test(ctx); 49 | 50 | ctx = ctx.replace(/[\t\r\n]/g, '').replace(/\{#.+?#\}/g, '') 51 | 52 | if (!_options.useIt) { 53 | code += '""; with(it) {'; 54 | last = 'c'; 55 | } 56 | 57 | while ((m = codere.exec(ctx)) !== null) { 58 | if (m.index > 0 && m.index > i) { 59 | code += prefixes[last]['s'] + ctx.substring(i, m.index).replace(/"/g, '\\"'); 60 | last = 's'; 61 | } 62 | 63 | if (m[0].indexOf('{%') === 0) { 64 | code += prefixes[last]['c'] + m[1]; 65 | if (/\)$/.test(m[1])) code += ';'; 66 | last = 'c'; 67 | } else if (m[0].indexOf('{{') === 0) { 68 | code += prefixes[last]['v'] + filters.convert(m[1]); 69 | last = 'v'; 70 | } 71 | 72 | i = m.index + m[0].length; 73 | } 74 | 75 | if (i < ctx.length) { 76 | code += prefixes[last]['s'] + ctx.substring(i).replace(/"/g, '\\"'); 77 | last = 's'; 78 | } 79 | 80 | code += prefixes['end'][last]; 81 | 82 | if (!_options.useIt) 83 | code += '} '; 84 | 85 | code += 'return out;'; 86 | 87 | if(isToCode) { 88 | return 'function(it) { var filters = jst.filters;' + code + '}'; 89 | } else { 90 | var fn = new Function('it, filters', code); 91 | 92 | return function(args) { 93 | return fn.call(this, args, filters.filters); 94 | } 95 | } 96 | } 97 | 98 | var render = exports.render = function(ctx, args) { 99 | var ck = hash.md5sum(ctx), 100 | fn = _cache[ck]; 101 | 102 | if (typeof fn === 'undefined') { 103 | fn = _cache[ck] = compile(ctx); 104 | } 105 | 106 | return fn(args); 107 | } 108 | 109 | var renderFile = exports.renderFile = function(filename, args, fn) { 110 | if (typeof args === 'function') { 111 | fn = args; 112 | args = {}; 113 | } 114 | 115 | var fk = hash.md5sum(filename), 116 | res; 117 | 118 | fs.stat(filename, function(err, stats) { 119 | if (err) 120 | return fn(err); 121 | 122 | _files[fk] = _files[fk] || {}; 123 | 124 | if (isempty(_files[fk]) || _files[fk].ctime < stats.ctime) { 125 | fs.readFile(filename, 'utf8', function(err, ctx) { 126 | if (err) 127 | return fn(err); 128 | 129 | _files[fk].ctime = stats.ctime; 130 | _files[fk].ctx = ctx; 131 | 132 | try { 133 | res = render(ctx, args); 134 | } catch(e) { fn(e); } 135 | fn(null, res); 136 | }); 137 | } else { 138 | try { 139 | res = render(_files[fk].ctx, args); 140 | } catch(e) { fn(e); } 141 | fn(null, res); 142 | } 143 | }); 144 | } 145 | 146 | function isempty(o) { 147 | for (var prop in o) 148 | if (o.hasOwnProperty(prop)) 149 | return false; 150 | return true; 151 | } 152 | 153 | -------------------------------------------------------------------------------- /lib/hash.js: -------------------------------------------------------------------------------- 1 | var crypto = require('crypto'); 2 | 3 | exports.md5sum = function(ctx) { 4 | return crypto.createHash('md5').update(ctx).digest('hex'); 5 | } 6 | 7 | /** 8 | * Calculates the CRC32 checksum of a string. 9 | * 10 | * @param {String} str 11 | * @param {Boolean} hex 12 | * @return {String} checksum 13 | * @api public 14 | */ 15 | exports.crc32 = function(str, hex) { 16 | var crc = ~0, i; 17 | for (i = 0, l = str.length; i < l; i++) { 18 | crc = (crc >>> 8) ^ crc32tab[(crc ^ str.charCodeAt(i)) & 0xff]; 19 | } 20 | crc = crc ^ -1; 21 | return hex ? crc.toString(16) : crc; 22 | }; 23 | 24 | var crc32tab = [ 25 | 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 26 | 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 27 | 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 28 | 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 29 | 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 30 | 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 31 | 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 32 | 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 33 | 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 34 | 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 35 | 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 36 | 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 37 | 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 38 | 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 39 | 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 40 | 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 41 | 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 42 | 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 43 | 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 44 | 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 45 | 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 46 | 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 47 | 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 48 | 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 49 | 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 50 | 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 51 | 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 52 | 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 53 | 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 54 | 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 55 | 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 56 | 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 57 | 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 58 | 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 59 | 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 60 | 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 61 | 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 62 | 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 63 | 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 64 | 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 65 | 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 66 | 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 67 | 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 68 | 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 69 | 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 70 | 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 71 | 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 72 | 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 73 | 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 74 | 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 75 | 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 76 | 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 77 | 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 78 | 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 79 | 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 80 | 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 81 | 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 82 | 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 83 | 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 84 | 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 85 | 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 86 | 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 87 | 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 88 | 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d 89 | ]; 90 | 91 | --------------------------------------------------------------------------------