Skip to content

Instantly share code, notes, and snippets.

@andyedinborough
Created February 8, 2012 17:01
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andyedinborough/1771064 to your computer and use it in GitHub Desktop.
Save andyedinborough/1771064 to your computer and use it in GitHub Desktop.
A simple JavaScript implementation of the Razor view engine.

#RazorJS Yet another JavaScript implementation of the Razor view engine that aims to be simple and compatible for use both in the browser and in Node--simple enough for templating:

Razor.compile('hello @model.name')({ name: 'world' })

As well as a Node view-engine:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  Razor.view('test')
    .done(function(template) {
      res.end(template({ name: 'Andy' }));
    });
}).listen(1337, "127.0.0.1");
var rxExport = /(\/+\*?)\s*<export>\s*\*?\/*\s+((.*\s*)+)\s+(\/+\*?)\s*<\/export>(\s*\*\/\/)?/gi,
rxImport = /(\/+\*?)\s*<import\s*\/>(\s+\*\/\/)?/gi;
function customs(carrier, shipment){
shipment = rxExport.matches(shipment)[2] || '';
return carrier.replace(rxImport, shipment.trim());
}
RegExp.prototype.matches = function(str) {
this.lastIndex = 0;
return this.exec(str);
};
function test(){
var carrier = '1\r\n//<import />\r\n3',
shipment = '4\r\n//<export>\r\n2\r\n//</export>\r\n5',
output = customs(carrier, shipment),
expected = '1\r\n2\r\n3';
if(output !== expected) {
throw 'import/exports failed. ' +
'\r\n\t<expected>' + JSON.stringify(expected) + '</expected>' +
'\r\n\t<output>' + JSON.stringify(output) + '</output>';
}
}
test();
var ext = process.argv[2] || 'browser',
fs = require('fs'),
razorJs, razorExtJs,
razorJsFile = 'Razor.base.js',
razorExtJsFile = 'Razor.' + ext + '.js',
uglify = require('c:/users/andy/appdata/roaming/npm/node_modules/uglify-js'),
jshint = require('c:/users/andy/appdata/roaming/npm/node_modules/jshint'),
razorMinJs,
Razor, result;
fs.mkdir('bin');
fs.mkdir('bin/' + ext);
razorJs = fs.readFileSync(razorJsFile, 'utf8');
razorExtJs = fs.readFileSync(razorExtJsFile, 'utf8');
(function(){
var args = [].slice.call(arguments), arg;
while((arg = args.shift())){
if(!jshint.JSHINT(arg.code)){
var errors = jshint.JSHINT.errors, error;
while((error = errors.shift())){
console.warn(arg.file + ' (' + error.line + ',' + error.character + '): ' +
error.reason + '\r\n\t' + error.evidence);
}
}
}
})({ code: razorJs, file: razorJsFile}, {code: razorExtJs, file: razorExtJsFile });
razorJs = customs(razorJs, razorExtJs);
fs.writeFileSync('bin/' + ext + '/Razor.js', razorJs);
razorMinJs = uglify(razorJs);
fs.writeFileSync('bin/' + ext + '/Razor.min.js', razorMinJs);
<!doctype html>
<html>
<head>
</head>
<body>
<div id="notices"></div>
<script src="Razor.js"></script>
<script type="application/x-razor-js" data-view-id="notice">
@* this view is for displaying notices *@
<div class="notice@(model.icon || model.userID ? ' has-icon': '')" data-id="@model.id">
@if(model.icon) {
<img class="notice-icon" src="@model.icon" />
} else if (model.userID) {
<img class="notice-icon" src="http://tracky.com/content/icon/user/@(model.userID)[48].png" />
}
<div class="content">
<span class="paragraph">@model.message</span>
<div class="meta">@showDate()</div>
</div>
@if (model.buttons){
<div class="buttons"></div>
}
</div>
@helper showDate() {
if(model.date){
<time data-date="@model.date"> </time>
}
}
</script>
<script>
var view = Razor.view('notice'),
container = document.getElementById('notices'),
models = [
{ userID: 1, message: 'commented "Wat?!"', date: Date.now() }
];
models.forEach(function(model){
var div = document.createElement('div');
div.innerHTML = view(model);
container.appendChild(div);
});
</script>
</body>
</html>
var http = require('http'), Razor = require('./Razor');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/html'});
Razor.view('test')
.done(function(template) {
res.end(template({ name: 'Andy' }));
});
}).listen(1337, "127.0.0.1");
console.log('Server running at http://127.0.0.1:1337/');
<!doctype html>
<html>
<head>
<link href="http://code.jquery.com/qunit/qunit-git.css" rel="stylesheet" />
</head>
<body>
<h1 id="qunit-header">Razor</h1>
<h2 id="qunit-banner"></h2>
<div id="qunit-testrunner-toolbar"></div>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="http://code.jquery.com/qunit/qunit-git.js"></script>
<script src="bin/browser/Razor.js"></script>
<script>
test('Basic Parsing', function(){;
equal(Razor.compile('test')(), 'test', 'no razor');
equal(Razor.compile('test@test.com')(), 'test@test.com', 'email address');
equal(Razor.compile('test@@@(model.test).com')({ test: 'test' }), 'test@test.com', 'explicit code');
equal(Razor.compile('hello @model.name')({ name: 'world' }), 'hello world', 'model');
equal(Razor.compile('hello @model.name[0]')({ name: 'world' }), 'hello w', 'model w/ indexers');
equal(Razor.compile('hello @model.name("world")')({ name: function(n){ return n; } }), 'hello world', 'model w/ method');
equal(Razor.compile('te@*FAIL*@st')(), 'test', 'comment');
equal(Razor.compile('@if(model.name){ @model.name }')({ name: 'test' }), 'test', 'if statement');
equal(Razor.compile('@if(!model.name){ @fail(); } else { @model.name; }')({ name: 'test' }), 'test', 'if-else statement');
equal(Razor.compile('@if(!model.name){ @:test }')({}), 'test', 'text-mode');
equal(Razor.compile('@helper test(){ @:test }@test()')(), 'test', 'helper');
});
</script>
<script type="application/x-razor-js" data-view-id="test">
Hello @model.name
</script>
</body>
</html>
/*global window, exports */
/*jshint curly: false, evil: true */
(function () {
'use strict';
var Razor, global = new Function('return this')();
var Reader = (function () {
var reader = function (text) {
this.text = (text || '') + '';
this.position = -1;
this.length = this.text.length;
};
var Chunk = reader.Chunk = function (value, next) {
this.value = value || ''; this.next = next || '';
this.length = (this.value + this.next).length;
};
extend(Chunk.prototype, {
length: 0,
toString: function () { return this.value + this.next + ''; }
});
reader.prototype.read = function (len) {
var value = this.peek(len);
this.position = Math.min(this.length, this.position + (len || 1));
return value;
};
reader.prototype.readAll = function () {
if (this.position >= this.length) return undefined;
var value = this.text.substr(this.position + 1);
this.position = this.length;
return value;
};
reader.prototype.peek = function (len) {
if ((this.position + 1) >= this.length) return undefined;
return this.text.substr(this.position + 1, len || 1);
};
reader.prototype.seek = function (offset, pos) {
this.position = Math.max(0,
Math.min(this.length,
(pos === 0 ? 0 : pos === 2 ? this.length : this.position) +
(offset || 1)
)
);
return this.position === this.length;
};
function read(rdr, chars, until) {
var l, cache = [], len = chars.length, result = '', next = '';
function predicate(chr) {
l = chr.length;
next = cache[l] || (cache[l] = rdr.peek(l));
return next === chr;
}
while (true) {
cache.length = 0;
if (until === chars.some(predicate)) {
if (until) {
rdr.seek(l);
} else {
next = last(result);
result = result.length > 0 ? result.substr(0, result.length - 1) : '';
}
return new Chunk(result, next);
}
next = rdr.read();
if (next) {
result += next;
} else break;
}
return new Chunk(result, next);
}
reader.prototype.readUntil = function (chars) {
if (typeof chars === 'string') chars = [].slice.call(arguments);
return read(this, chars, true);
};
reader.prototype.readWhile = function (chars) {
if (typeof chars === 'string') chars = [].slice.call(arguments);
return read(this, chars, false);
};
return reader;
})();
//Reader Extensions
var rxValid = /^[a-z0-9\._]+/i;
function last(str) {
return (str = (str || ''))[str.length - 1] || '';
}
Reader.prototype.readWhitespace = function () {
return this.readWhile('\r', '\n', '\t', ' ');
};
Reader.prototype.readQuoted = function (quote) {
var result = '', block;
while (true) {
block = this.readUntil(quote);
if (!block) break;
result += block.value + block.next;
if (last(block.value) !== '\\')
break;
}
return result;
};
Reader.prototype.readQuotedUntil = function (chars) {
var result = '', block;
if (typeof chars == 'string') chars = [].slice.call(arguments);
chars = ['"', "'", '@*'].concat(chars);
while ((block = this.readUntil(chars))) {
result += block.value;
if (block.next === '"' || block.next === "'") {
result += block.next + this.readQuoted(block.next);
} else if (block.next === '@*') {
this.readUntil('*@');
} else break;
}
return new Reader.Chunk(result, block.next);
};
Reader.prototype.readBlock = function (open, close, numOpen) {
var block, blockChars = [open, close], ret = '';
numOpen = numOpen || 0;
while ((block = this.readUntil(blockChars))) {
ret += block.value;
if (block.next === open) {
numOpen++;
} else if (block.next === close) {
numOpen--;
}
if (numOpen === 0) {
ret += block.next;
return ret;
} else ret += block.next;
}
return ret;
};
var Cmd = function(code, type) {
this.code = code || '';
this.type = type || 0;
};
extend(Cmd.prototype, {
type: 0, code: '',
toString: function () {
var code = this.code;
if (this.type === 0) return code;
if (this.type === 2) return "writeLiteral(\"" + doubleEncode(code) + "\");";
return 'write(' + code + ');';
}
});
var _function_template = 'var page = this, writer = []; \r\nfunction write(txt){ writeLiteral(page.html.encode(txt)); }\r\nfunction writeLiteral(txt){ writer.push(txt); }\r\n#1\r\n#2\r\nwith(page){\r\n#0\r\n}\r\nreturn writer.join("");';
function parse(template, optimize) {
var rdr = new Reader(template),
level = arguments[1] || 0, mode = arguments[2] || 0,
cmds = [], helpers = [], sections = [], chunk, peek, block;
cmds.push = (function (push) {
return function (code, type) {
if (typeof code === 'string') code = [code];
code = code.map(function (x) {
return typeof x.code !== 'undefined' ? x : new Cmd(x, type);
});
push.apply(this, code);
};
})(cmds.push);
while (true) {
chunk = mode === 0 ? rdr.readUntil('@') : rdr.readQuotedUntil('@', '<');
if (!chunk || (!chunk.value && !chunk.next)) break;
peek = rdr.peek();
if (peek === '@') chunk.value += rdr.read();
if (chunk.value) {
if (mode === 0) cmds.push(chunk.value, 2);
else cmds.push(chunk.value);
}
if (mode === 1 && chunk.next === '<') {
var tagname = rdr.text.substr(rdr.position + 1).match(/^[a-z]+/i);
if (tagname) {
chunk = rdr.readUntil('>');
block = chunk + '';
if (last(chunk.value) !== '/') {
block += rdr.readUntil('</' + tagname + '>');
}
cmds.push(parse('<' + block, level + 1, 0));
}
}
if (peek === '*') rdr.readUntil('*@');
else if (peek === '(') {
block = rdr.readBlock('(', ')');
cmds.push(block.substr(1, block.length - 2), 1);
} else if (peek === '{') {
block = rdr.readBlock('{', '}');
cmds.push(parse(block.substr(0, block.length - 1), level + 1, 1).join('\n') + '}');
} else if (peek === ':' && mode === 1) {
block = rdr.readUntil('\n', '@');
while (block.next === '@' && rdr.peek(1) === '@') {
var temp = rdr.readUntil('\n', '@');
block.value += temp.value;
block.next = temp.next;
}
block.value = block.value.substr(1);
cmds.push(block.value.match(/(.*?)\s*$/)[1], 2);
cmds.push(block.value.match(/\s*$/)[0] || '', 0);
} else if (
(peek === 'i' && rdr.peek(2) === 'if') ||
(peek === 'd' && rdr.peek(2) === 'do') ||
(peek === 'f' && rdr.peek(3) === 'for') ||
(peek === 'w' && rdr.peek(5) === 'while') ||
(peek === 'h' && rdr.peek(6) === 'helper') ||
(peek === '7' && rdr.peek(7) === 'section')
) {
block = rdr.readBlock('{', '}');
if (peek === 'i') {
while (true) {
var whiteSpace = rdr.readWhitespace();
if (!whiteSpace) break;
else if (rdr.peek(4) !== 'else') {
rdr.seek(-whiteSpace.length);
break;
}
block += whiteSpace + rdr.readBlock('{', '}');
}
}
var parsed = parse(block.substr(0, block.length - 1), level + 1, 1).join('\n') + '}';
if (peek === 'h') helpers.push('function ' + parsed.substr(7));
else if (peek === 's') sections.push('function _section_' + parsed.substr(8));
else cmds.push(parsed);
} else if (peek && !rxValid.test(last(chunk.value))) {
var remain, match;
block = '';
while (true) {
remain = rdr.text.substr(rdr.position + 1);
match = remain.match(rxValid);
if (!match) break;
block += rdr.read(match[0].length);
peek = rdr.peek();
if (peek === '[' || peek === '(') {
block += rdr.readBlock(peek, peek === '[' ? ']' : ')');
}
}
if (block) cmds.push(block, 1);
} else if (mode === 0 && chunk.next) cmds.push('@', 2);
}
if (optimize !== false) {
cmds.reduce(function (a, b) {
if (a.type === 2 && b.type === 2) {
a.code += b.code;
} else if (a.type === 2 && b.type === 1) {
a.code = '"' + doubleEncode(a.code) + '"+(' + b.code + ')';
a.type = 1;
} else if (a.type == 1 && b.type === 1) {
a.code += '+(' + b.code + ')';
} else if (a.type === 1 && b.type === 2) {
if (last(a.code) === '"')
a.code = a.code.substr(0, a.code.length - 1);
else a.code += '+"';
a.code += doubleEncode(b.code) + '"';
} else {
return b;
}
b.code = '';
return a;
});
cmds = cmds.filter(function (a) {
return !!a.code;
});
}
if (level > 0) return cmds;
template = cmds.map(function (x) { return Cmd.prototype.toString.apply(x); }).join('\r\n');
template = _function_template
.replace('#0', template)
.replace('#1', helpers.map(returnEmpty).join('\r\n'))
.replace('#2', sections.map(returnEmpty).join('\r\n'));
return template;
}
function returnEmpty(func) {
var i = func.indexOf('{');
return func.substr(0, i + 1) + ' with (page) {\r\n' + func.substring(i + 1, func.lastIndexOf('}')) + '; return ""; } }';
}
function doubleEncode(txt) {
return txt.split('\r').join('\\r').split('\n').join('\\n').split('"').join('\\"');
}
var basePage = {
html: {
encode: function(value){
if(value === null || value === undefined) value = '';
if(value.__ishtml) return value;
if(typeof value !== 'string') value += '';
value = value
.split('&').join('&amp;')
.split('<').join('&lt;')
.split('"').join('&quot;');
return this.raw(value);
},
attributeEncode: function(value){
return this.encode(value);
},
raw: function(value){
value.__ishtml = true;
return value;
}
}
};
function compile(code, page, optimize) {
var func, parsed = parse(code, optimize);
try {
func = new Function(parsed);
} catch (x) {
global.console.error(x.message + ': ' + parsed);
throw x.message + ': ' + parsed;
}
return function (model, page1) {
var ctx = extend({}, basePage, page, page1, { model: model });
return func.apply(ctx);
};
}
function extend(a) {
for (var i = 1, ii = arguments.length; i < ii; i++) {
var b = arguments[i];
if (b)
for (var key in b)
if (b.hasOwnProperty(key))
a[key] = b[key];
}
return a;
}
var deferred = function Deferred() {
if (!(this instanceof Deferred)) return new Deferred();
var dq = [], aq = [], fq = [], state = 0, dfd = this, args,
process = function (arr, run) {
var cb;
while (run && (cb = arr.shift()))
cb.apply(dfd, args);
};
extend(dfd, {
done: function (cb) {
if (cb) dq.push(cb);
process(dq, state === 1);
process(aq, state !== 0);
return dfd;
},
always: function (cb) {
aq.push(cb);
process(aq, state !== 0);
return dfd;
},
fail: function (cb) {
if (cb) fq.push(cb);
process(fq, state === -1);
process(aq, state !== 0);
return dfd;
},
resolve: function () {
args = arguments;
if (state === 0) state = 1;
return dfd.done();
},
reject: function () {
args = arguments;
if (state === 0) state = -1;
return dfd.fail();
}
});
};
var views = {}, async = false;
function view(id, page) {
var template = views['~/' + id];
if (!template) {
var result = Razor.findView(id);
if (result instanceof deferred) {
async = true;
var dfd = deferred();
result.done(function (script) {
if (script) {
template = views['~/' + id] = Razor.compile(script, page);
}
dfd.resolve(template);
});
return dfd;
} else if (result) {
return views['~/' + id] = Razor.compile(result, page);
}
} else if (async) {
return deferred().resolve(template);
} else return template;
}
global[global.exports ? 'exports' : 'Razor'] = Razor = {
view: view, compile: compile, parse: parse, findView: null,
render: function (markup, model, page) { return compile(markup, page)(model); }
};
// <import/>
return Razor;
})();
/*global global, Razor */
/*jshint curly: false, evil: true */
(function () {
'use strict';
// <export>
Razor.findView = function findViewInDocument(id) {
var script;
[].slice.call(global.document.getElementsByTagName('script'))
.some(function (x) {
return x.type === 'application/x-razor-js' &&
x.getAttribute('data-view-id') === id &&
(script = x);
});
return script ? script.innerHTML : undefined;
};
// </export>
})();
/*global window, exports */
/*jshint curly: false, evil: true */
(function () {
'use strict';
var Razor, global = new Function('return this')();
var Reader = (function () {
var reader = function (text) {
this.text = (text || '') + '';
this.position = -1;
this.length = this.text.length;
};
var Chunk = reader.Chunk = function (value, next) {
this.value = value || ''; this.next = next || '';
this.length = (this.value + this.next).length;
};
extend(Chunk.prototype, {
length: 0,
toString: function () { return this.value + this.next + ''; }
});
reader.prototype.read = function (len) {
var value = this.peek(len);
this.position = Math.min(this.length, this.position + (len || 1));
return value;
};
reader.prototype.readAll = function () {
if (this.position >= this.length) return undefined;
var value = this.text.substr(this.position + 1);
this.position = this.length;
return value;
};
reader.prototype.peek = function (len) {
if ((this.position + 1) >= this.length) return undefined;
return this.text.substr(this.position + 1, len || 1);
};
reader.prototype.seek = function (offset, pos) {
this.position = Math.max(0,
Math.min(this.length,
(pos === 0 ? 0 : pos === 2 ? this.length : this.position) +
(offset || 1)
)
);
return this.position === this.length;
};
function read(rdr, chars, until) {
var l, cache = [], len = chars.length, result = '', next = '';
function predicate(chr) {
l = chr.length;
next = cache[l] || (cache[l] = rdr.peek(l));
return next === chr;
}
while (true) {
cache.length = 0;
if (until === chars.some(predicate)) {
if (until) {
rdr.seek(l);
} else {
next = last(result);
result = result.length > 0 ? result.substr(0, result.length - 1) : '';
}
return new Chunk(result, next);
}
next = rdr.read();
if (next) {
result += next;
} else break;
}
return new Chunk(result, next);
}
reader.prototype.readUntil = function (chars) {
if (typeof chars === 'string') chars = [].slice.call(arguments);
return read(this, chars, true);
};
reader.prototype.readWhile = function (chars) {
if (typeof chars === 'string') chars = [].slice.call(arguments);
return read(this, chars, false);
};
return reader;
})();
//Reader Extensions
var rxValid = /^[a-z0-9\._]+/i;
function last(str) {
return (str = (str || ''))[str.length - 1] || '';
}
Reader.prototype.readWhitespace = function () {
return this.readWhile('\r', '\n', '\t', ' ');
};
Reader.prototype.readQuoted = function (quote) {
var result = '', block;
while (true) {
block = this.readUntil(quote);
if (!block) break;
result += block.value + block.next;
if (last(block.value) !== '\\')
break;
}
return result;
};
Reader.prototype.readQuotedUntil = function (chars) {
var result = '', block;
if (typeof chars == 'string') chars = [].slice.call(arguments);
chars = ['"', "'", '@*'].concat(chars);
while ((block = this.readUntil(chars))) {
result += block.value;
if (block.next === '"' || block.next === "'") {
result += block.next + this.readQuoted(block.next);
} else if (block.next === '@*') {
this.readUntil('*@');
} else break;
}
return new Reader.Chunk(result, block.next);
};
Reader.prototype.readBlock = function (open, close, numOpen) {
var block, blockChars = [open, close], ret = '';
numOpen = numOpen || 0;
while ((block = this.readUntil(blockChars))) {
ret += block.value;
if (block.next === open) {
numOpen++;
} else if (block.next === close) {
numOpen--;
}
if (numOpen === 0) {
ret += block.next;
return ret;
} else ret += block.next;
}
return ret;
};
var Cmd = function(code, type) {
this.code = code || '';
this.type = type || 0;
};
extend(Cmd.prototype, {
type: 0, code: '',
toString: function () {
var code = this.code;
if (this.type === 0) return code;
if (this.type === 2) return "writeLiteral(\"" + doubleEncode(code) + "\");";
return 'write(' + code + ');';
}
});
var _function_template = 'var page = this, writer = []; \r\nfunction write(txt){ writeLiteral(page.html.encode(txt)); }\r\nfunction writeLiteral(txt){ writer.push(txt); }\r\n#1\r\n#2\r\nwith(page){\r\n#0\r\n}\r\nreturn writer.join("");';
function parse(template, optimize) {
var rdr = new Reader(template),
level = arguments[1] || 0, mode = arguments[2] || 0,
cmds = [], helpers = [], sections = [], chunk, peek, block;
cmds.push = (function (push) {
return function (code, type) {
if (typeof code === 'string') code = [code];
code = code.map(function (x) {
return typeof x.code !== 'undefined' ? x : new Cmd(x, type);
});
push.apply(this, code);
};
})(cmds.push);
while (true) {
chunk = mode === 0 ? rdr.readUntil('@') : rdr.readQuotedUntil('@', '<');
if (!chunk || (!chunk.value && !chunk.next)) break;
peek = rdr.peek();
if (peek === '@') chunk.value += rdr.read();
if (chunk.value) {
if (mode === 0) cmds.push(chunk.value, 2);
else cmds.push(chunk.value);
}
if (mode === 1 && chunk.next === '<') {
var tagname = rdr.text.substr(rdr.position + 1).match(/^[a-z]+/i);
if (tagname) {
chunk = rdr.readUntil('>');
block = chunk + '';
if (last(chunk.value) !== '/') {
block += rdr.readUntil('</' + tagname + '>');
}
cmds.push(parse('<' + block, level + 1, 0));
}
}
if (peek === '*') rdr.readUntil('*@');
else if (peek === '(') {
block = rdr.readBlock('(', ')');
cmds.push(block.substr(1, block.length - 2), 1);
} else if (peek === '{') {
block = rdr.readBlock('{', '}');
cmds.push(parse(block.substr(0, block.length - 1), level + 1, 1).join('\n') + '}');
} else if (peek === ':' && mode === 1) {
block = rdr.readUntil('\n', '@');
while (block.next === '@' && rdr.peek(1) === '@') {
var temp = rdr.readUntil('\n', '@');
block.value += temp.value;
block.next = temp.next;
}
block.value = block.value.substr(1);
cmds.push(block.value.match(/(.*?)\s*$/)[1], 2);
cmds.push(block.value.match(/\s*$/)[0] || '', 0);
} else if (
(peek === 'i' && rdr.peek(2) === 'if') ||
(peek === 'd' && rdr.peek(2) === 'do') ||
(peek === 'f' && rdr.peek(3) === 'for') ||
(peek === 'w' && rdr.peek(5) === 'while') ||
(peek === 'h' && rdr.peek(6) === 'helper') ||
(peek === '7' && rdr.peek(7) === 'section')
) {
block = rdr.readBlock('{', '}');
if (peek === 'i') {
while (true) {
var whiteSpace = rdr.readWhitespace();
if (!whiteSpace) break;
else if (rdr.peek(4) !== 'else') {
rdr.seek(-whiteSpace.length);
break;
}
block += whiteSpace + rdr.readBlock('{', '}');
}
}
var parsed = parse(block.substr(0, block.length - 1), level + 1, 1).join('\n') + '}';
if (peek === 'h') helpers.push('function ' + parsed.substr(7));
else if (peek === 's') sections.push('function _section_' + parsed.substr(8));
else cmds.push(parsed);
} else if (peek && !rxValid.test(last(chunk.value))) {
var remain, match;
block = '';
while (true) {
remain = rdr.text.substr(rdr.position + 1);
match = remain.match(rxValid);
if (!match) break;
block += rdr.read(match[0].length);
peek = rdr.peek();
if (peek === '[' || peek === '(') {
block += rdr.readBlock(peek, peek === '[' ? ']' : ')');
}
}
if (block) cmds.push(block, 1);
} else if (mode === 0 && chunk.next) cmds.push('@', 2);
}
if (optimize !== false) {
cmds.reduce(function (a, b) {
if (a.type === 2 && b.type === 2) {
a.code += b.code;
} else if (a.type === 2 && b.type === 1) {
a.code = '"' + doubleEncode(a.code) + '"+(' + b.code + ')';
a.type = 1;
} else if (a.type == 1 && b.type === 1) {
a.code += '+(' + b.code + ')';
} else if (a.type === 1 && b.type === 2) {
if (last(a.code) === '"')
a.code = a.code.substr(0, a.code.length - 1);
else a.code += '+"';
a.code += doubleEncode(b.code) + '"';
} else {
return b;
}
b.code = '';
return a;
});
cmds = cmds.filter(function (a) {
return !!a.code;
});
}
if (level > 0) return cmds;
template = cmds.map(function (x) { return Cmd.prototype.toString.apply(x); }).join('\r\n');
template = _function_template
.replace('#0', template)
.replace('#1', helpers.map(returnEmpty).join('\r\n'))
.replace('#2', sections.map(returnEmpty).join('\r\n'));
return template;
}
function returnEmpty(func) {
var i = func.indexOf('{');
return func.substr(0, i + 1) + ' with (page) {\r\n' + func.substring(i + 1, func.lastIndexOf('}')) + '; return ""; } }';
}
function doubleEncode(txt) {
return txt.split('\r').join('\\r').split('\n').join('\\n').split('"').join('\\"');
}
var basePage = {
html: {
encode: function(value){
if(value === null || value === undefined) value = '';
if(value.__ishtml) return value;
if(typeof value !== 'string') value += '';
value = value
.split('&').join('&amp;')
.split('<').join('&lt;')
.split('"').join('&quot;');
return this.raw(value);
},
attributeEncode: function(value){
return this.encode(value);
},
raw: function(value){
value.__ishtml = true;
return value;
}
}
};
function compile(code, page, optimize) {
var func, parsed = parse(code, optimize);
try {
func = new Function(parsed);
} catch (x) {
global.console.error(x.message + ': ' + parsed);
throw x.message + ': ' + parsed;
}
return function (model, page1) {
var ctx = extend({}, basePage, page, page1, { model: model });
return func.apply(ctx);
};
}
function extend(a) {
for (var i = 1, ii = arguments.length; i < ii; i++) {
var b = arguments[i];
if (b)
for (var key in b)
if (b.hasOwnProperty(key))
a[key] = b[key];
}
return a;
}
var deferred = function Deferred() {
if (!(this instanceof Deferred)) return new Deferred();
var dq = [], aq = [], fq = [], state = 0, dfd = this, args,
process = function (arr, run) {
var cb;
while (run && (cb = arr.shift()))
cb.apply(dfd, args);
};
extend(dfd, {
done: function (cb) {
if (cb) dq.push(cb);
process(dq, state === 1);
process(aq, state !== 0);
return dfd;
},
always: function (cb) {
aq.push(cb);
process(aq, state !== 0);
return dfd;
},
fail: function (cb) {
if (cb) fq.push(cb);
process(fq, state === -1);
process(aq, state !== 0);
return dfd;
},
resolve: function () {
args = arguments;
if (state === 0) state = 1;
return dfd.done();
},
reject: function () {
args = arguments;
if (state === 0) state = -1;
return dfd.fail();
}
});
};
var views = {}, async = false;
function view(id, page) {
var template = views['~/' + id];
if (!template) {
var result = Razor.findView(id);
if (result instanceof deferred) {
async = true;
var dfd = deferred();
result.done(function (script) {
if (script) {
template = views['~/' + id] = Razor.compile(script, page);
}
dfd.resolve(template);
});
return dfd;
} else if (result) {
return views['~/' + id] = Razor.compile(result, page);
}
} else if (async) {
return deferred().resolve(template);
} else return template;
}
global[global.exports ? 'exports' : 'Razor'] = Razor = {
view: view, compile: compile, parse: parse, findView: null,
render: function (markup, model, page) { return compile(markup, page)(model); }
};
Razor.findView = function findViewInDocument(id) {
var script;
[].slice.call(global.document.getElementsByTagName('script'))
.some(function (x) {
return x.type === 'application/x-razor-js' &&
x.getAttribute('data-view-id') === id &&
(script = x);
});
return script ? script.innerHTML : undefined;
};
return Razor;
})();
/*global window, exports */
/*jshint curly: false, evil: true */
(function () {
'use strict';
var Razor, global = new Function('return this')();
var Reader = (function () {
var reader = function (text) {
this.text = (text || '') + '';
this.position = -1;
this.length = this.text.length;
};
var Chunk = reader.Chunk = function (value, next) {
this.value = value || ''; this.next = next || '';
this.length = (this.value + this.next).length;
};
extend(Chunk.prototype, {
length: 0,
toString: function () { return this.value + this.next + ''; }
});
reader.prototype.read = function (len) {
var value = this.peek(len);
this.position = Math.min(this.length, this.position + (len || 1));
return value;
};
reader.prototype.readAll = function () {
if (this.position >= this.length) return undefined;
var value = this.text.substr(this.position + 1);
this.position = this.length;
return value;
};
reader.prototype.peek = function (len) {
if ((this.position + 1) >= this.length) return undefined;
return this.text.substr(this.position + 1, len || 1);
};
reader.prototype.seek = function (offset, pos) {
this.position = Math.max(0,
Math.min(this.length,
(pos === 0 ? 0 : pos === 2 ? this.length : this.position) +
(offset || 1)
)
);
return this.position === this.length;
};
function read(rdr, chars, until) {
var l, cache = [], len = chars.length, result = '', next = '';
function predicate(chr) {
l = chr.length;
next = cache[l] || (cache[l] = rdr.peek(l));
return next === chr;
}
while (true) {
cache.length = 0;
if (until === chars.some(predicate)) {
if (until) {
rdr.seek(l);
} else {
next = last(result);
result = result.length > 0 ? result.substr(0, result.length - 1) : '';
}
return new Chunk(result, next);
}
next = rdr.read();
if (next) {
result += next;
} else break;
}
return new Chunk(result, next);
}
reader.prototype.readUntil = function (chars) {
if (typeof chars === 'string') chars = [].slice.call(arguments);
return read(this, chars, true);
};
reader.prototype.readWhile = function (chars) {
if (typeof chars === 'string') chars = [].slice.call(arguments);
return read(this, chars, false);
};
return reader;
})();
//Reader Extensions
var rxValid = /^[a-z0-9\._]+/i;
function last(str) {
return (str = (str || ''))[str.length - 1] || '';
}
Reader.prototype.readWhitespace = function () {
return this.readWhile('\r', '\n', '\t', ' ');
};
Reader.prototype.readQuoted = function (quote) {
var result = '', block;
while (true) {
block = this.readUntil(quote);
if (!block) break;
result += block.value + block.next;
if (last(block.value) !== '\\')
break;
}
return result;
};
Reader.prototype.readQuotedUntil = function (chars) {
var result = '', block;
if (typeof chars == 'string') chars = [].slice.call(arguments);
chars = ['"', "'", '@*'].concat(chars);
while ((block = this.readUntil(chars))) {
result += block.value;
if (block.next === '"' || block.next === "'") {
result += block.next + this.readQuoted(block.next);
} else if (block.next === '@*') {
this.readUntil('*@');
} else break;
}
return new Reader.Chunk(result, block.next);
};
Reader.prototype.readBlock = function (open, close, numOpen) {
var block, blockChars = [open, close], ret = '';
numOpen = numOpen || 0;
while ((block = this.readUntil(blockChars))) {
ret += block.value;
if (block.next === open) {
numOpen++;
} else if (block.next === close) {
numOpen--;
}
if (numOpen === 0) {
ret += block.next;
return ret;
} else ret += block.next;
}
return ret;
};
var Cmd = function(code, type) {
this.code = code || '';
this.type = type || 0;
};
extend(Cmd.prototype, {
type: 0, code: '',
toString: function () {
var code = this.code;
if (this.type === 0) return code;
if (this.type === 2) return "writeLiteral(\"" + doubleEncode(code) + "\");";
return 'write(' + code + ');';
}
});
var _function_template = 'var page = this, writer = []; \r\nfunction write(txt){ writeLiteral(page.html.encode(txt)); }\r\nfunction writeLiteral(txt){ writer.push(txt); }\r\n#1\r\n#2\r\nwith(page){\r\n#0\r\n}\r\nreturn writer.join("");';
function parse(template, optimize) {
var rdr = new Reader(template),
level = arguments[1] || 0, mode = arguments[2] || 0,
cmds = [], helpers = [], sections = [], chunk, peek, block;
cmds.push = (function (push) {
return function (code, type) {
if (typeof code === 'string') code = [code];
code = code.map(function (x) {
return typeof x.code !== 'undefined' ? x : new Cmd(x, type);
});
push.apply(this, code);
};
})(cmds.push);
while (true) {
chunk = mode === 0 ? rdr.readUntil('@') : rdr.readQuotedUntil('@', '<');
if (!chunk || (!chunk.value && !chunk.next)) break;
peek = rdr.peek();
if (peek === '@') chunk.value += rdr.read();
if (chunk.value) {
if (mode === 0) cmds.push(chunk.value, 2);
else cmds.push(chunk.value);
}
if (mode === 1 && chunk.next === '<') {
var tagname = rdr.text.substr(rdr.position + 1).match(/^[a-z]+/i);
if (tagname) {
chunk = rdr.readUntil('>');
block = chunk + '';
if (last(chunk.value) !== '/') {
block += rdr.readUntil('</' + tagname + '>');
}
cmds.push(parse('<' + block, level + 1, 0));
}
}
if (peek === '*') rdr.readUntil('*@');
else if (peek === '(') {
block = rdr.readBlock('(', ')');
cmds.push(block.substr(1, block.length - 2), 1);
} else if (peek === '{') {
block = rdr.readBlock('{', '}');
cmds.push(parse(block.substr(0, block.length - 1), level + 1, 1).join('\n') + '}');
} else if (peek === ':' && mode === 1) {
block = rdr.readUntil('\n', '@');
while (block.next === '@' && rdr.peek(1) === '@') {
var temp = rdr.readUntil('\n', '@');
block.value += temp.value;
block.next = temp.next;
}
block.value = block.value.substr(1);
cmds.push(block.value.match(/(.*?)\s*$/)[1], 2);
cmds.push(block.value.match(/\s*$/)[0] || '', 0);
} else if (
(peek === 'i' && rdr.peek(2) === 'if') ||
(peek === 'd' && rdr.peek(2) === 'do') ||
(peek === 'f' && rdr.peek(3) === 'for') ||
(peek === 'w' && rdr.peek(5) === 'while') ||
(peek === 'h' && rdr.peek(6) === 'helper') ||
(peek === '7' && rdr.peek(7) === 'section')
) {
block = rdr.readBlock('{', '}');
if (peek === 'i') {
while (true) {
var whiteSpace = rdr.readWhitespace();
if (!whiteSpace) break;
else if (rdr.peek(4) !== 'else') {
rdr.seek(-whiteSpace.length);
break;
}
block += whiteSpace + rdr.readBlock('{', '}');
}
}
var parsed = parse(block.substr(0, block.length - 1), level + 1, 1).join('\n') + '}';
if (peek === 'h') helpers.push('function ' + parsed.substr(7));
else if (peek === 's') sections.push('function _section_' + parsed.substr(8));
else cmds.push(parsed);
} else if (peek && !rxValid.test(last(chunk.value))) {
var remain, match;
block = '';
while (true) {
remain = rdr.text.substr(rdr.position + 1);
match = remain.match(rxValid);
if (!match) break;
block += rdr.read(match[0].length);
peek = rdr.peek();
if (peek === '[' || peek === '(') {
block += rdr.readBlock(peek, peek === '[' ? ']' : ')');
}
}
if (block) cmds.push(block, 1);
} else if (mode === 0 && chunk.next) cmds.push('@', 2);
}
if (optimize !== false) {
cmds.reduce(function (a, b) {
if (a.type === 2 && b.type === 2) {
a.code += b.code;
} else if (a.type === 2 && b.type === 1) {
a.code = '"' + doubleEncode(a.code) + '"+(' + b.code + ')';
a.type = 1;
} else if (a.type == 1 && b.type === 1) {
a.code += '+(' + b.code + ')';
} else if (a.type === 1 && b.type === 2) {
if (last(a.code) === '"')
a.code = a.code.substr(0, a.code.length - 1);
else a.code += '+"';
a.code += doubleEncode(b.code) + '"';
} else {
return b;
}
b.code = '';
return a;
});
cmds = cmds.filter(function (a) {
return !!a.code;
});
}
if (level > 0) return cmds;
template = cmds.map(function (x) { return Cmd.prototype.toString.apply(x); }).join('\r\n');
template = _function_template
.replace('#0', template)
.replace('#1', helpers.map(returnEmpty).join('\r\n'))
.replace('#2', sections.map(returnEmpty).join('\r\n'));
return template;
}
function returnEmpty(func) {
var i = func.indexOf('{');
return func.substr(0, i + 1) + ' with (page) {\r\n' + func.substring(i + 1, func.lastIndexOf('}')) + '; return ""; } }';
}
function doubleEncode(txt) {
return txt.split('\r').join('\\r').split('\n').join('\\n').split('"').join('\\"');
}
var basePage = {
html: {
encode: function(value){
if(value === null || value === undefined) value = '';
if(value.__ishtml) return value;
if(typeof value !== 'string') value += '';
value = value
.split('&').join('&amp;')
.split('<').join('&lt;')
.split('"').join('&quot;');
return this.raw(value);
},
attributeEncode: function(value){
return this.encode(value);
},
raw: function(value){
value.__ishtml = true;
return value;
}
}
};
function compile(code, page, optimize) {
var func, parsed = parse(code, optimize);
try {
func = new Function(parsed);
} catch (x) {
global.console.error(x.message + ': ' + parsed);
throw x.message + ': ' + parsed;
}
return function (model, page1) {
var ctx = extend({}, basePage, page, page1, { model: model });
return func.apply(ctx);
};
}
function extend(a) {
for (var i = 1, ii = arguments.length; i < ii; i++) {
var b = arguments[i];
if (b)
for (var key in b)
if (b.hasOwnProperty(key))
a[key] = b[key];
}
return a;
}
var deferred = function Deferred() {
if (!(this instanceof Deferred)) return new Deferred();
var dq = [], aq = [], fq = [], state = 0, dfd = this, args,
process = function (arr, run) {
var cb;
while (run && (cb = arr.shift()))
cb.apply(dfd, args);
};
extend(dfd, {
done: function (cb) {
if (cb) dq.push(cb);
process(dq, state === 1);
process(aq, state !== 0);
return dfd;
},
always: function (cb) {
aq.push(cb);
process(aq, state !== 0);
return dfd;
},
fail: function (cb) {
if (cb) fq.push(cb);
process(fq, state === -1);
process(aq, state !== 0);
return dfd;
},
resolve: function () {
args = arguments;
if (state === 0) state = 1;
return dfd.done();
},
reject: function () {
args = arguments;
if (state === 0) state = -1;
return dfd.fail();
}
});
};
var views = {}, async = false;
function view(id, page) {
var template = views['~/' + id];
if (!template) {
var result = Razor.findView(id);
if (result instanceof deferred) {
async = true;
var dfd = deferred();
result.done(function (script) {
if (script) {
template = views['~/' + id] = Razor.compile(script, page);
}
dfd.resolve(template);
});
return dfd;
} else if (result) {
return views['~/' + id] = Razor.compile(result, page);
}
} else if (async) {
return deferred().resolve(template);
} else return template;
}
global[global.exports ? 'exports' : 'Razor'] = Razor = {
view: view, compile: compile, parse: parse, findView: null,
render: function (markup, model, page) { return compile(markup, page)(model); }
};
Razor.findView = function findViewInFileSystem(viewName) {
var fs = global.require('fs'), dfd = deferred();
if (viewName.substring(viewName.lastIndexOf('.')) !== '.jshtml')
viewName += '.jshtml';
fs.readFile(viewName, 'ascii', function (err, data) {
if (err) {
global.console.error("Could not open file: %s", err);
global.process.exit(1);
}
dfd.resolve(data.toString('ascii'));
});
return dfd;
};
global.console.log('ready',global);
return Razor;
})();
(function(){"use strict";function last(str){return(str=str||"")[str.length-1]||""}function parse(template,optimize){var rdr=new Reader(template),level=arguments[1]||0,mode=arguments[2]||0,cmds=[],helpers=[],sections=[],chunk,peek,block;cmds.push=function(push){return function(code,type){typeof code=="string"&&(code=[code]),code=code.map(function(x){return typeof x.code!="undefined"?x:new Cmd(x,type)}),push.apply(this,code)}}(cmds.push);for(;;){chunk=mode===0?rdr.readUntil("@"):rdr.readQuotedUntil("@","<");if(!chunk||!chunk.value&&!chunk.next)break;peek=rdr.peek(),peek==="@"&&(chunk.value+=rdr.read()),chunk.value&&(mode===0?cmds.push(chunk.value,2):cmds.push(chunk.value));if(mode===1&&chunk.next==="<"){var tagname=rdr.text.substr(rdr.position+1).match(/^[a-z]+/i);tagname&&(chunk=rdr.readUntil(">"),block=chunk+"",last(chunk.value)!=="/"&&(block+=rdr.readUntil("</"+tagname+">")),cmds.push(parse("<"+block,level+1,0)))}if(peek==="*")rdr.readUntil("*@");else if(peek==="(")block=rdr.readBlock("(",")"),cmds.push(block.substr(1,block.length-2),1);else if(peek==="{")block=rdr.readBlock("{","}"),cmds.push(parse(block.substr(0,block.length-1),level+1,1).join("\n")+"}");else if(peek===":"&&mode===1){block=rdr.readUntil("\n","@");while(block.next==="@"&&rdr.peek(1)==="@"){var temp=rdr.readUntil("\n","@");block.value+=temp.value,block.next=temp.next}block.value=block.value.substr(1),cmds.push(block.value.match(/(.*?)\s*$/)[1],2),cmds.push(block.value.match(/\s*$/)[0]||"",0)}else if(peek==="i"&&rdr.peek(2)==="if"||peek==="d"&&rdr.peek(2)==="do"||peek==="f"&&rdr.peek(3)==="for"||peek==="w"&&rdr.peek(5)==="while"||peek==="h"&&rdr.peek(6)==="helper"||peek==="7"&&rdr.peek(7)==="section"){block=rdr.readBlock("{","}");if(peek==="i")for(;;){var whiteSpace=rdr.readWhitespace();if(!whiteSpace)break;if(rdr.peek(4)!=="else"){rdr.seek(-whiteSpace.length);break}block+=whiteSpace+rdr.readBlock("{","}")}var parsed=parse(block.substr(0,block.length-1),level+1,1).join("\n")+"}";peek==="h"?helpers.push("function "+parsed.substr(7)):peek==="s"?sections.push("function _section_"+parsed.substr(8)):cmds.push(parsed)}else if(peek&&!rxValid.test(last(chunk.value))){var remain,match;block="";for(;;){remain=rdr.text.substr(rdr.position+1),match=remain.match(rxValid);if(!match)break;block+=rdr.read(match[0].length),peek=rdr.peek();if(peek==="["||peek==="(")block+=rdr.readBlock(peek,peek==="["?"]":")")}block&&cmds.push(block,1)}else mode===0&&chunk.next&&cmds.push("@",2)}return optimize!==!1&&(cmds.reduce(function(a,b){if(a.type===2&&b.type===2)a.code+=b.code;else if(a.type===2&&b.type===1)a.code='"'+doubleEncode(a.code)+'"+('+b.code+")",a.type=1;else if(a.type==1&&b.type===1)a.code+="+("+b.code+")";else{if(a.type!==1||b.type!==2)return b;last(a.code)==='"'?a.code=a.code.substr(0,a.code.length-1):a.code+='+"',a.code+=doubleEncode(b.code)+'"'}return b.code="",a}),cmds=cmds.filter(function(a){return!!a.code})),level>0?cmds:(template=cmds.map(function(x){return Cmd.prototype.toString.apply(x)}).join("\r\n"),template=_function_template.replace("#0",template).replace("#1",helpers.map(returnEmpty).join("\r\n")).replace("#2",sections.map(returnEmpty).join("\r\n")),template)}function returnEmpty(func){var i=func.indexOf("{");return func.substr(0,i+1)+" with (page) {\r\n"+func.substring(i+1,func.lastIndexOf("}"))+'; return ""; } }'}function doubleEncode(txt){return txt.split("\r").join("\\r").split("\n").join("\\n").split('"').join('\\"')}function compile(code,page,optimize){var func,parsed=parse(code,optimize);try{func=new Function(parsed)}catch(x){throw global.console.error(x.message+": "+parsed),x.message+": "+parsed}return function(model,page1){var ctx=extend({},basePage,page,page1,{model:model});return func.apply(ctx)}}function extend(a){for(var i=1,ii=arguments.length;i<ii;i++){var b=arguments[i];if(b)for(var key in b)b.hasOwnProperty(key)&&(a[key]=b[key])}return a}function view(id,page){var template=views["~/"+id];if(!!template)return async?deferred().resolve(template):template;var result=Razor.findView(id);if(result instanceof deferred){async=!0;var dfd=deferred();return result.done(function(script){script&&(template=views["~/"+id]=Razor.compile(script,page)),dfd.resolve(template)}),dfd}if(result)return views["~/"+id]=Razor.compile(result,page)}var Razor,global=(new Function("return this"))(),Reader=function(){function read(rdr,chars,until){function predicate(chr){return l=chr.length,next=cache[l]||(cache[l]=rdr.peek(l)),next===chr}var l,cache=[],len=chars.length,result="",next="";for(;;){cache.length=0;if(until===chars.some(predicate))return until?rdr.seek(l):(next=last(result),result=result.length>0?result.substr(0,result.length-1):""),new Chunk(result,next);next=rdr.read();if(!next)break;result+=next}return new Chunk(result,next)}var reader=function(text){this.text=(text||"")+"",this.position=-1,this.length=this.text.length},Chunk=reader.Chunk=function(value,next){this.value=value||"",this.next=next||"",this.length=(this.value+this.next).length};return extend(Chunk.prototype,{length:0,toString:function(){return this.value+this.next+""}}),reader.prototype.read=function(len){var value=this.peek(len);return this.position=Math.min(this.length,this.position+(len||1)),value},reader.prototype.readAll=function(){if(this.position>=this.length)return undefined;var value=this.text.substr(this.position+1);return this.position=this.length,value},reader.prototype.peek=function(len){return this.position+1>=this.length?undefined:this.text.substr(this.position+1,len||1)},reader.prototype.seek=function(offset,pos){return this.position=Math.max(0,Math.min(this.length,(pos===0?0:pos===2?this.length:this.position)+(offset||1))),this.position===this.length},reader.prototype.readUntil=function(chars){return typeof chars=="string"&&(chars=[].slice.call(arguments)),read(this,chars,!0)},reader.prototype.readWhile=function(chars){return typeof chars=="string"&&(chars=[].slice.call(arguments)),read(this,chars,!1)},reader}(),rxValid=/^[a-z0-9\._]+/i;Reader.prototype.readWhitespace=function(){return this.readWhile("\r","\n"," "," ")},Reader.prototype.readQuoted=function(quote){var result="",block;for(;;){block=this.readUntil(quote);if(!block)break;result+=block.value+block.next;if(last(block.value)!=="\\")break}return result},Reader.prototype.readQuotedUntil=function(chars){var result="",block;typeof chars=="string"&&(chars=[].slice.call(arguments)),chars=['"',"'","@*"].concat(chars);while(block=this.readUntil(chars)){result+=block.value;if(block.next==='"'||block.next==="'")result+=block.next+this.readQuoted(block.next);else{if(block.next!=="@*")break;this.readUntil("*@")}}return new Reader.Chunk(result,block.next)},Reader.prototype.readBlock=function(open,close,numOpen){var block,blockChars=[open,close],ret="";numOpen=numOpen||0;while(block=this.readUntil(blockChars)){ret+=block.value,block.next===open?numOpen++:block.next===close&&numOpen--;if(numOpen===0)return ret+=block.next,ret;ret+=block.next}return ret};var Cmd=function(code,type){this.code=code||"",this.type=type||0};extend(Cmd.prototype,{type:0,code:"",toString:function(){var code=this.code;return this.type===0?code:this.type===2?'writeLiteral("'+doubleEncode(code)+'");':"write("+code+");"}});var _function_template='var page = this, writer = []; \r\nfunction write(txt){ writeLiteral(page.html.encode(txt)); }\r\nfunction writeLiteral(txt){ writer.push(txt); }\r\n#1\r\n#2\r\nwith(page){\r\n#0\r\n}\r\nreturn writer.join("");',basePage={html:{encode:function(value){if(value===null||value===undefined)value="";return value.__ishtml?value:(typeof value!="string"&&(value+=""),value=value.split("&").join("&amp;").split("<").join("&lt;").split('"').join("&quot;"),this.raw(value))},attributeEncode:function(value){return this.encode(value)},raw:function(value){return value.__ishtml=!0,value}}},deferred=function e(){if(!(this instanceof Deferred))return new Deferred;var dq=[],aq=[],fq=[],state=0,dfd=this,args,process=function(arr,run){var cb;while(run&&(cb=arr.shift()))cb.apply(dfd,args)};extend(dfd,{done:function(cb){return cb&&dq.push(cb),process(dq,state===1),process(aq,state!==0),dfd},always:function(cb){return aq.push(cb),process(aq,state!==0),dfd},fail:function(cb){return cb&&fq.push(cb),process(fq,state===-1),process(aq,state!==0),dfd},resolve:function(){return args=arguments,state===0&&(state=1),dfd.done()},reject:function(){return args=arguments,state===0&&(state=-1),dfd.fail()}})},views={},async=!1;return global[global.exports?"exports":"Razor"]=Razor={view:view,compile:compile,parse:parse,findView:null,render:function(markup,model,page){return compile(markup,page)(model)}},Razor.findView=function(id){var script;return[].slice.call(global.document.getElementsByTagName("script")).some(function(x){return x.type==="application/x-razor-js"&&x.getAttribute("data-view-id")===id&&(script=x)}),script?script.innerHTML:undefined},Razor})()
(function(){"use strict";function last(str){return(str=str||"")[str.length-1]||""}function parse(template,optimize){var rdr=new Reader(template),level=arguments[1]||0,mode=arguments[2]||0,cmds=[],helpers=[],sections=[],chunk,peek,block;cmds.push=function(push){return function(code,type){typeof code=="string"&&(code=[code]),code=code.map(function(x){return typeof x.code!="undefined"?x:new Cmd(x,type)}),push.apply(this,code)}}(cmds.push);for(;;){chunk=mode===0?rdr.readUntil("@"):rdr.readQuotedUntil("@","<");if(!chunk||!chunk.value&&!chunk.next)break;peek=rdr.peek(),peek==="@"&&(chunk.value+=rdr.read()),chunk.value&&(mode===0?cmds.push(chunk.value,2):cmds.push(chunk.value));if(mode===1&&chunk.next==="<"){var tagname=rdr.text.substr(rdr.position+1).match(/^[a-z]+/i);tagname&&(chunk=rdr.readUntil(">"),block=chunk+"",last(chunk.value)!=="/"&&(block+=rdr.readUntil("</"+tagname+">")),cmds.push(parse("<"+block,level+1,0)))}if(peek==="*")rdr.readUntil("*@");else if(peek==="(")block=rdr.readBlock("(",")"),cmds.push(block.substr(1,block.length-2),1);else if(peek==="{")block=rdr.readBlock("{","}"),cmds.push(parse(block.substr(0,block.length-1),level+1,1).join("\n")+"}");else if(peek===":"&&mode===1){block=rdr.readUntil("\n","@");while(block.next==="@"&&rdr.peek(1)==="@"){var temp=rdr.readUntil("\n","@");block.value+=temp.value,block.next=temp.next}block.value=block.value.substr(1),cmds.push(block.value.match(/(.*?)\s*$/)[1],2),cmds.push(block.value.match(/\s*$/)[0]||"",0)}else if(peek==="i"&&rdr.peek(2)==="if"||peek==="d"&&rdr.peek(2)==="do"||peek==="f"&&rdr.peek(3)==="for"||peek==="w"&&rdr.peek(5)==="while"||peek==="h"&&rdr.peek(6)==="helper"||peek==="7"&&rdr.peek(7)==="section"){block=rdr.readBlock("{","}");if(peek==="i")for(;;){var whiteSpace=rdr.readWhitespace();if(!whiteSpace)break;if(rdr.peek(4)!=="else"){rdr.seek(-whiteSpace.length);break}block+=whiteSpace+rdr.readBlock("{","}")}var parsed=parse(block.substr(0,block.length-1),level+1,1).join("\n")+"}";peek==="h"?helpers.push("function "+parsed.substr(7)):peek==="s"?sections.push("function _section_"+parsed.substr(8)):cmds.push(parsed)}else if(peek&&!rxValid.test(last(chunk.value))){var remain,match;block="";for(;;){remain=rdr.text.substr(rdr.position+1),match=remain.match(rxValid);if(!match)break;block+=rdr.read(match[0].length),peek=rdr.peek();if(peek==="["||peek==="(")block+=rdr.readBlock(peek,peek==="["?"]":")")}block&&cmds.push(block,1)}else mode===0&&chunk.next&&cmds.push("@",2)}return optimize!==!1&&(cmds.reduce(function(a,b){if(a.type===2&&b.type===2)a.code+=b.code;else if(a.type===2&&b.type===1)a.code='"'+doubleEncode(a.code)+'"+('+b.code+")",a.type=1;else if(a.type==1&&b.type===1)a.code+="+("+b.code+")";else{if(a.type!==1||b.type!==2)return b;last(a.code)==='"'?a.code=a.code.substr(0,a.code.length-1):a.code+='+"',a.code+=doubleEncode(b.code)+'"'}return b.code="",a}),cmds=cmds.filter(function(a){return!!a.code})),level>0?cmds:(template=cmds.map(function(x){return Cmd.prototype.toString.apply(x)}).join("\r\n"),template=_function_template.replace("#0",template).replace("#1",helpers.map(returnEmpty).join("\r\n")).replace("#2",sections.map(returnEmpty).join("\r\n")),template)}function returnEmpty(func){var i=func.indexOf("{");return func.substr(0,i+1)+" with (page) {\r\n"+func.substring(i+1,func.lastIndexOf("}"))+'; return ""; } }'}function doubleEncode(txt){return txt.split("\r").join("\\r").split("\n").join("\\n").split('"').join('\\"')}function compile(code,page,optimize){var func,parsed=parse(code,optimize);try{func=new Function(parsed)}catch(x){throw global.console.error(x.message+": "+parsed),x.message+": "+parsed}return function(model,page1){var ctx=extend({},basePage,page,page1,{model:model});return func.apply(ctx)}}function extend(a){for(var i=1,ii=arguments.length;i<ii;i++){var b=arguments[i];if(b)for(var key in b)b.hasOwnProperty(key)&&(a[key]=b[key])}return a}function view(id,page){var template=views["~/"+id];if(!!template)return async?deferred().resolve(template):template;var result=Razor.findView(id);if(result instanceof deferred){async=!0;var dfd=deferred();return result.done(function(script){script&&(template=views["~/"+id]=Razor.compile(script,page)),dfd.resolve(template)}),dfd}if(result)return views["~/"+id]=Razor.compile(result,page)}var Razor,global=(new Function("return this"))(),Reader=function(){function read(rdr,chars,until){function predicate(chr){return l=chr.length,next=cache[l]||(cache[l]=rdr.peek(l)),next===chr}var l,cache=[],len=chars.length,result="",next="";for(;;){cache.length=0;if(until===chars.some(predicate))return until?rdr.seek(l):(next=last(result),result=result.length>0?result.substr(0,result.length-1):""),new Chunk(result,next);next=rdr.read();if(!next)break;result+=next}return new Chunk(result,next)}var reader=function(text){this.text=(text||"")+"",this.position=-1,this.length=this.text.length},Chunk=reader.Chunk=function(value,next){this.value=value||"",this.next=next||"",this.length=(this.value+this.next).length};return extend(Chunk.prototype,{length:0,toString:function(){return this.value+this.next+""}}),reader.prototype.read=function(len){var value=this.peek(len);return this.position=Math.min(this.length,this.position+(len||1)),value},reader.prototype.readAll=function(){if(this.position>=this.length)return undefined;var value=this.text.substr(this.position+1);return this.position=this.length,value},reader.prototype.peek=function(len){return this.position+1>=this.length?undefined:this.text.substr(this.position+1,len||1)},reader.prototype.seek=function(offset,pos){return this.position=Math.max(0,Math.min(this.length,(pos===0?0:pos===2?this.length:this.position)+(offset||1))),this.position===this.length},reader.prototype.readUntil=function(chars){return typeof chars=="string"&&(chars=[].slice.call(arguments)),read(this,chars,!0)},reader.prototype.readWhile=function(chars){return typeof chars=="string"&&(chars=[].slice.call(arguments)),read(this,chars,!1)},reader}(),rxValid=/^[a-z0-9\._]+/i;Reader.prototype.readWhitespace=function(){return this.readWhile("\r","\n"," "," ")},Reader.prototype.readQuoted=function(quote){var result="",block;for(;;){block=this.readUntil(quote);if(!block)break;result+=block.value+block.next;if(last(block.value)!=="\\")break}return result},Reader.prototype.readQuotedUntil=function(chars){var result="",block;typeof chars=="string"&&(chars=[].slice.call(arguments)),chars=['"',"'","@*"].concat(chars);while(block=this.readUntil(chars)){result+=block.value;if(block.next==='"'||block.next==="'")result+=block.next+this.readQuoted(block.next);else{if(block.next!=="@*")break;this.readUntil("*@")}}return new Reader.Chunk(result,block.next)},Reader.prototype.readBlock=function(open,close,numOpen){var block,blockChars=[open,close],ret="";numOpen=numOpen||0;while(block=this.readUntil(blockChars)){ret+=block.value,block.next===open?numOpen++:block.next===close&&numOpen--;if(numOpen===0)return ret+=block.next,ret;ret+=block.next}return ret};var Cmd=function(code,type){this.code=code||"",this.type=type||0};extend(Cmd.prototype,{type:0,code:"",toString:function(){var code=this.code;return this.type===0?code:this.type===2?'writeLiteral("'+doubleEncode(code)+'");':"write("+code+");"}});var _function_template='var page = this, writer = []; \r\nfunction write(txt){ writeLiteral(page.html.encode(txt)); }\r\nfunction writeLiteral(txt){ writer.push(txt); }\r\n#1\r\n#2\r\nwith(page){\r\n#0\r\n}\r\nreturn writer.join("");',basePage={html:{encode:function(value){if(value===null||value===undefined)value="";return value.__ishtml?value:(typeof value!="string"&&(value+=""),value=value.split("&").join("&amp;").split("<").join("&lt;").split('"').join("&quot;"),this.raw(value))},attributeEncode:function(value){return this.encode(value)},raw:function(value){return value.__ishtml=!0,value}}},deferred=function e(){if(!(this instanceof Deferred))return new Deferred;var dq=[],aq=[],fq=[],state=0,dfd=this,args,process=function(arr,run){var cb;while(run&&(cb=arr.shift()))cb.apply(dfd,args)};extend(dfd,{done:function(cb){return cb&&dq.push(cb),process(dq,state===1),process(aq,state!==0),dfd},always:function(cb){return aq.push(cb),process(aq,state!==0),dfd},fail:function(cb){return cb&&fq.push(cb),process(fq,state===-1),process(aq,state!==0),dfd},resolve:function(){return args=arguments,state===0&&(state=1),dfd.done()},reject:function(){return args=arguments,state===0&&(state=-1),dfd.fail()}})},views={},async=!1;return global[global.exports?"exports":"Razor"]=Razor={view:view,compile:compile,parse:parse,findView:null,render:function(markup,model,page){return compile(markup,page)(model)}},Razor.findView=function(viewName){var fs=global.require("fs"),dfd=deferred();return viewName.substring(viewName.lastIndexOf("."))!==".jshtml"&&(viewName+=".jshtml"),fs.readFile(viewName,"ascii",function(err,data){err&&(global.console.error("Could not open file: %s",err),global.process.exit(1)),dfd.resolve(data.toString("ascii"))}),dfd},global.console.log("ready",global),Razor})()
/*global global, Razor, deferred */
/*jshint curly: false, evil: true */
(function () {
'use strict';
// <export>
Razor.findView = function findViewInFileSystem(viewName) {
var fs = global.require('fs'), dfd = deferred();
if (viewName.substring(viewName.lastIndexOf('.')) !== '.jshtml')
viewName += '.jshtml';
fs.readFile(viewName, 'ascii', function (err, data) {
if (err) {
global.console.error("Could not open file: %s", err);
global.process.exit(1);
}
dfd.resolve(data.toString('ascii'));
});
return dfd;
};
// </export>
})();
<!doctype html>
<html>
<body>
Hello @model.name!
</body>
</html>
@andyedinborough
Copy link
Author

View it in action at jsFiddle: http://jsfiddle.net/andyedinborough/EpQAe/

@andyedinborough
Copy link
Author

This would be much less complex, and much more flexible by implementing it using a string reader https://github.com/andyedinborough/stress-css/blob/master/reader.js.

@andyedinborough
Copy link
Author

Now with @if(this){ <a>@that</a> } support

@andyedinborough
Copy link
Author

Now with support for embedding views as <script type="application/x-razor-js" data-view-id="myview" /> / Razor.view('myview')().

@andyedinborough
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment