-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8319fe9
commit f0bd13b
Showing
9 changed files
with
358 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
var GLOBAL_FNS = 'function _esc_(s){return (s+"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");}\n' + | ||
'function _inline_(fn){var s=fn();return function(){return s;};}\n' | ||
|
||
// compile a source tree down to javascript | ||
export default function codegen(tree, opts) { | ||
if (!opts) opts = {} | ||
|
||
var global = [GLOBAL_FNS] | ||
var cnt = 0 | ||
|
||
var compress = function (str) { | ||
return opts.compress ? str.replace(/\s+/g, ' ') : str | ||
} | ||
|
||
var wrap = function (vars, body) { | ||
return vars ? 'with(locals){var _r=[];var _b={};\n' + body + '}\n' : 'with(locals){\n' + body + '}\n' | ||
} | ||
|
||
var debugable = function (url) { | ||
return '_' + (url || '').split('/').slice(-2).join('_').replace(/[^a-zA-Z]/g, '_') + '_' + (cnt++) | ||
} | ||
|
||
var stringify = function (tree) { | ||
var src = '' | ||
var pushBefore = false | ||
|
||
var push = function (value) { | ||
if (pushBefore) return src = src.slice(0, -3) + '+' + value + ');\n' | ||
src += '_r.push(' + value + ');\n' | ||
pushBefore = true | ||
} | ||
|
||
var logic = function (value) { | ||
pushBefore = false | ||
src += value + '\n' | ||
} | ||
|
||
tree.forEach(function (node) { | ||
if (node.type === 'STATIC') return push(JSON.stringify(compress(node.value))) | ||
if (node.type === 'EXPRESSION') return push('(' + node.value + ')') | ||
if (node.type === 'ESCAPE_EXPRESSION') return push('_esc_(' + node.value + ')') | ||
if (node.type === 'LOGIC') return logic(node.value) | ||
|
||
var locals = node.locals || 'locals' | ||
var name = node.name && JSON.stringify(node.name) | ||
var decl = node.name && JSON.stringify(node.name + '$decl') | ||
var id = debugable(node.url) | ||
|
||
if (node.type === 'BLOCK_ANONYMOUS') { | ||
global.push('function ' + id + '(_r,_b,locals){' + wrap(false, stringify(node.body)) + '}\n') | ||
return logic(id + '(_r,_b,' + locals + ');') | ||
} | ||
|
||
if (node.type === 'BLOCK_DECLARE') { | ||
logic('if (_b[' + decl + ']) _b[' + decl + '].toString=_inline_(_b[' + decl + '].toString);') | ||
logic('_r.push(_b[' + decl + ']={toString:function(){return _b[' + name + ']();}});') | ||
} | ||
|
||
global.push('function ' + id + '(locals){' + wrap(true, stringify(node.body) + 'return _r;') + '}\n') | ||
logic('_b[' + name + ']=function(){return ' + id + '(' + locals + ').join("");};') | ||
}) | ||
|
||
return src | ||
} | ||
|
||
var main = debugable(opts.name) | ||
var src = stringify(tree) | ||
return global.join('') + 'module.exports=function ' + main + '(locals){locals=locals||{};' + wrap(true, src) + 'return _r.join("");};' | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
import vm from 'vm' | ||
import path from 'path' | ||
import fs from 'fs' | ||
import parse from './parse.js' | ||
import codegen from './codegen.js' | ||
import watch from './watch.js' | ||
import sequence from './sequence.js' | ||
|
||
var noop = function () { } | ||
|
||
var requireSource = function (source) { | ||
var module = { exports: {} } | ||
vm.runInNewContext(source, { console: console, module: module }) | ||
return module.exports | ||
} | ||
|
||
export default function pejs(opts) { | ||
if (!opts) opts = {} | ||
|
||
opts.basedir = opts.basedir || process.cwd() | ||
|
||
var templates = {} | ||
var cache = templates.cache = {} | ||
var compress = opts.compress | ||
|
||
var resolvePath = function (name, dirname, callback) { | ||
let fileName = undefined | ||
let error = undefined | ||
|
||
try { | ||
fileName = path.resolve(dirname, name) | ||
} | ||
catch (err) { | ||
error = err | ||
} | ||
|
||
callback(error, fileName) | ||
} | ||
|
||
templates.render = function (name, locals, callback) { | ||
if (typeof locals === 'function') return templates.render(name, {}, locals) | ||
|
||
locals = locals || {} | ||
|
||
if (cache[name] && cache[name].render) { | ||
var result | ||
|
||
try { | ||
result = cache[name].render(locals) | ||
} catch (err) { | ||
return callback(err) | ||
} | ||
|
||
return callback(null, result) | ||
} | ||
|
||
templates.compile(name, function (err, source) { | ||
if (err) return callback(err) | ||
|
||
try { | ||
cache[name].render = cache[name].render || requireSource(source) | ||
} catch (err) { | ||
return callback(err) | ||
} | ||
|
||
templates.render(name, locals, callback) | ||
}) | ||
} | ||
|
||
templates.compile = function (name, opts, callback) { | ||
if (typeof opts === 'function') return templates.compile(name, {}, opts) | ||
if (cache[name] && cache[name].source) return callback(null, cache[name].source) | ||
|
||
opts = opts || {} | ||
|
||
var maybePrecompiled = function (callback) { | ||
if (opts.cejs === false) return callback() | ||
|
||
resolvePath(name, opts.dirname, function (err, path) { | ||
if (!path) return callback() | ||
|
||
fs.readFile(path + '.cejs', 'utf-8', function (err, src) { | ||
if (err) return callback(err) | ||
|
||
cache[name] = cache[name] || {} | ||
cache[name].source = cache[name].source || src | ||
callback(null, cache[name].source) | ||
}) | ||
}) | ||
} | ||
|
||
maybePrecompiled(function (err, source) { | ||
if (source) return callback(null, source) | ||
|
||
templates.parse(name, function (err, tree, url) { | ||
if (err) return callback(err) | ||
|
||
cache[name].source = cache[name].source || codegen(tree, { name: url, compress: compress }) | ||
callback(null, cache[name].source) | ||
}) | ||
}) | ||
} | ||
|
||
templates.parse = function (name, callback) { | ||
var files = [] | ||
|
||
var onsource = function (filename, source, callback) { | ||
var dirname = path.dirname(filename) | ||
var tree = parse(source, opts) | ||
|
||
files.push(filename) | ||
|
||
var nodes = [] | ||
var visit = function (node) { | ||
if (node.url) nodes.push(node) | ||
if (node.body) node.body.forEach(visit) | ||
} | ||
|
||
tree.forEach(visit) | ||
|
||
if (!nodes.length) return callback(null, tree, filename) | ||
|
||
var i = 0 | ||
var loop = function () { | ||
var node = nodes[i++] | ||
|
||
if (!node) return callback(null, tree, filename) | ||
|
||
resolveTemplate(node.url, dirname, function (err, resolved, url) { | ||
if (err) return callback(err) | ||
|
||
node.url = url | ||
node.body = resolved | ||
loop() | ||
}) | ||
} | ||
|
||
loop() | ||
} | ||
|
||
var resolveTemplate = function (name, dirname, callback) { | ||
resolvePath(name, dirname, function (err, filename) { | ||
if (err) return callback(err) | ||
|
||
fs.readFile(filename, 'utf-8', function (err, source) { | ||
if (err) return callback(err) | ||
|
||
onsource(filename, source, callback) | ||
}) | ||
}) | ||
} | ||
|
||
sequence(callback, function (free) { | ||
if (cache[name] && cache[name].tree) return free(null, cache[name].tree, cache[name].url) | ||
|
||
resolveTemplate(name, opts.basedir, function (err, tree, url) { | ||
if (err) return free(err) | ||
|
||
cache[name] = cache[name] || {} | ||
cache[name].tree = tree | ||
cache[name].url = url | ||
|
||
if (opts.watch === false) return free(null, tree, url) | ||
|
||
watch(files, function () { | ||
delete cache[name] | ||
}) | ||
|
||
free(null, tree, url) | ||
}) | ||
}) | ||
} | ||
|
||
return templates | ||
} | ||
|
||
pejs.__express = pejs().render |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
var STATIC = 'STATIC' | ||
var LOGIC = 'LOGIC' | ||
var EXPRESSION = 'EXPRESSION' | ||
var ESCAPE_EXPRESSION = 'ESCAPE_EXPRESSION' | ||
var BLOCK_DECLARE = 'BLOCK_DECLARE' | ||
var BLOCK_OVERRIDE = 'BLOCK_OVERRIDE' | ||
var BLOCK_ANONYMOUS = 'BLOCK_ANONYMOUS' | ||
|
||
var TOKEN_BEGIN = '<%' | ||
var TOKEN_END = '%>' | ||
|
||
var MATCH_BLOCK = /^(\w+)?\s*((?:'(?:(?:\\')|[^'])*')|(?:"(?:(?:\\")|[^"])*"))?(?:\s+(.+))?$/ | ||
|
||
// parse pejs source into a source tree | ||
export default function parse(src) { | ||
return Array.prototype.concat.apply([], src.split(TOKEN_END).map(function (slice) { | ||
return slice.split(TOKEN_BEGIN) | ||
})).map(function (data, i) { | ||
if (i % 2 === 0) return data && { type: STATIC, value: data } | ||
|
||
var pre = (data.match(/^(\S*)/g) || [])[0] | ||
var end = (data.match(/(\S*)$/g) || [])[0] | ||
var line = data.replace(/^\S*/g, '').replace(/\S*$/g, '').trim() | ||
var live = pre[1] === '[' | ||
var auto = pre === '{{' ? BLOCK_DECLARE : BLOCK_OVERRIDE | ||
var ctx = (pre + end).replace(/[\{\[]+/g, '{').replace(/[\}\]]+/g, '}') | ||
|
||
if (pre === '') return { type: LOGIC, value: line } | ||
if (pre === '#') return null | ||
if (pre === '=') return { type: ESCAPE_EXPRESSION, value: line } | ||
if (pre === '-') return { type: EXPRESSION, value: line } | ||
|
||
line = (line.match(MATCH_BLOCK) || []).slice(1) | ||
line = !line.length || (line[2] && !line[2]) ? {} : { | ||
name: line[0], | ||
url: line[1] && line[1].substr(1, line[1].length - 2).replace(/\\(.)/g, '$1'), | ||
locals: line[2] && line[2].trim() | ||
} | ||
|
||
if (ctx === '{}' && line.name) return { type: auto, live: live, name: line.name, locals: line.locals, url: line.url, body: [] } | ||
if (ctx === '{}' && line.url) return { type: BLOCK_ANONYMOUS, url: line.url, locals: line.locals } | ||
if (ctx === '{' && line.name) return { type: auto, live: live, name: line.name, locals: line.locals, capture: 1, body: [] } | ||
if (ctx === '}') return { capture: -1 } | ||
|
||
throw new SyntaxError('could not parse: <%' + data + '%>') | ||
}).reduce(function reduce(result, node) { | ||
var last = result[result.length - 1] | ||
|
||
if (!node) return result | ||
if (!last || !last.capture) return result.concat(node) | ||
|
||
last.capture += node.capture || 0 | ||
last.body = last.capture ? last.body.concat(node) : last.body.reduce(reduce, []) | ||
return result | ||
}, []) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
var free = true | ||
var waiting = [] | ||
|
||
// "locks" the execution - let everyone else wait for something to finish | ||
export default function lock(callback, fn) { // TODO: move to module | ||
if (!free) return waiting.push(arguments) | ||
|
||
free = false | ||
fn(function () { | ||
free = true | ||
callback.apply(null, arguments) | ||
if (waiting.length) lock.apply(null, waiting.shift()) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import fs from 'fs' | ||
|
||
var noop = function () { } | ||
var watchers = {} | ||
|
||
export default function watch(filenames, fn) { // TODO: find or create a module that does caching/watching for us | ||
var onchange = function () { | ||
filenames.forEach(function (filename) { | ||
if (!watchers[filename]) return | ||
watchers[filename].removeListener('change', onchange) | ||
}) | ||
|
||
fn() | ||
} | ||
|
||
filenames.forEach(function watchFile(filename) { | ||
if (watchers[filename]) return watchers[filename].once('change', onchange) | ||
|
||
watchers[filename] = fs.watch(filename, { persistent: false }, noop) | ||
watchers[filename].setMaxListeners(0) | ||
watchers[filename].once('change', function () { | ||
delete watchers[filename] | ||
this.close() | ||
}) | ||
|
||
watchFile(filename, fn) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.