mirror of
https://github.com/sstent/node.git
synced 2026-01-27 07:33:13 +00:00
270 lines
7.4 KiB
JavaScript
270 lines
7.4 KiB
JavaScript
var lookup = require('racer/lib/path').lookup
|
|
, trimLeading = require('html-util').trimLeading;
|
|
|
|
exports.wrapRemainder = wrapRemainder;
|
|
exports.extractPlaceholder = extractPlaceholder;
|
|
exports.pathFnArgs = pathFnArgs;
|
|
exports.ctxPath = ctxPath;
|
|
exports.dataValue = dataValue;
|
|
exports.setBoundFn = setBoundFn;
|
|
|
|
function wrapRemainder(tagName, remainder) {
|
|
if (!remainder) return false;
|
|
return !(new RegExp('^<\/' + tagName, 'i')).test(remainder);
|
|
}
|
|
|
|
var openPlaceholder = /^([\s\S]*?)(\{{1,3})([\s\S]*)/
|
|
, placeholderContent = /^\s*([\#\/]?)(?:(else\sif|if|else|unless|each|with|unescaped)(?!\())?\s*([^\s(>]*(?:\s*\([\s\S]*\))?)(?:\s+as\s+:([^\s>]+))?/;
|
|
|
|
function extractPlaceholder(text) {
|
|
var match = openPlaceholder.exec(text);
|
|
if (!match) return;
|
|
var pre = match[1]
|
|
, open = match[2]
|
|
, remainder = match[3]
|
|
, openLen = open.length
|
|
, bound = openLen === 1
|
|
, macro = openLen === 3
|
|
, end = matchBraces(remainder, openLen, 0, '{', '}')
|
|
, endInner = end - openLen
|
|
, inner = remainder.slice(0, endInner)
|
|
, post = remainder.slice(end)
|
|
, content = placeholderContent.exec(inner)
|
|
, escaped, name, type;
|
|
if (!content) return;
|
|
type = content[2];
|
|
escaped = true;
|
|
if (type === 'unescaped') {
|
|
escaped = false;
|
|
type = '';
|
|
}
|
|
name = content[3];
|
|
if (bound) name = name.replace(/\bthis\b/, '.');
|
|
if (macro && name === 'content') escaped = false;
|
|
return {
|
|
pre: trimLeading(pre)
|
|
, post: trimLeading(post)
|
|
, bound: bound
|
|
, macro: macro
|
|
, hash: content[1]
|
|
, escaped: escaped
|
|
, type: type
|
|
, name: name
|
|
, alias: content[4]
|
|
};
|
|
}
|
|
|
|
function matchBraces(text, num, i, openChar, closeChar) {
|
|
var close, hasClose, hasOpen, open;
|
|
i++;
|
|
while (num) {
|
|
close = text.indexOf(closeChar, i);
|
|
open = text.indexOf(openChar, i);
|
|
hasClose = ~close;
|
|
hasOpen = ~open;
|
|
if (hasClose && (!hasOpen || (close < open))) {
|
|
i = close + 1;
|
|
num--;
|
|
continue;
|
|
} else if (hasOpen) {
|
|
i = open + 1;
|
|
num++;
|
|
continue;
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
return i;
|
|
}
|
|
|
|
var fnCall = /^([^(]+)\s*\(\s*([\s\S]*?)\s*\)\s*$/
|
|
, argSeparator = /\s*([,(])\s*/g
|
|
, notSeparator = /[^,\s]/g
|
|
, notPathArg = /(?:^['"\d\-[{])|(?:^null$)|(?:^true$)|(?:^false$)/;
|
|
|
|
function fnArgs(inner) {
|
|
var args = []
|
|
, lastIndex = 0
|
|
, match, end, last;
|
|
while (match = argSeparator.exec(inner)) {
|
|
if (match[1] === '(') {
|
|
end = matchBraces(inner, 1, argSeparator.lastIndex, '(', ')');
|
|
args.push(inner.slice(lastIndex, end));
|
|
notSeparator.lastIndex = end;
|
|
lastIndex = argSeparator.lastIndex =
|
|
notSeparator.test(inner) ? notSeparator.lastIndex - 1 : end;
|
|
continue;
|
|
}
|
|
args.push(inner.slice(lastIndex, match.index));
|
|
lastIndex = argSeparator.lastIndex;
|
|
}
|
|
last = inner.slice(lastIndex);
|
|
if (last) args.push(last);
|
|
return args;
|
|
}
|
|
|
|
function fnCallError(name) {
|
|
throw new Error('malformed view function call: ' + name);
|
|
}
|
|
|
|
function fnArgValue(view, ctx, model, name, macro, arg) {
|
|
if (arg === 'null') return null;
|
|
if (arg === 'true') return true;
|
|
if (arg === 'false') return false;
|
|
var firstChar = arg.charAt(0)
|
|
, match;
|
|
if (firstChar === "'") {
|
|
match = /^'(.*)'$/.exec(arg) || fnCallError(name);
|
|
return match[1];
|
|
}
|
|
if (firstChar === '"') {
|
|
match = /^"(.*)"$/.exec(arg) || fnCallError(name);
|
|
return match[1];
|
|
}
|
|
if (/^[\d\-]/.test(firstChar) && !isNaN(arg)) {
|
|
return +arg;
|
|
}
|
|
if (firstChar === '[' || firstChar === '{') {
|
|
throw new Error('object literals not supported in view function call: ' + name);
|
|
}
|
|
return dataValue(view, ctx, model, arg, macro);
|
|
}
|
|
|
|
function fnValue(view, ctx, model, name, macro) {
|
|
var match = fnCall.exec(name) || fnCallError(name)
|
|
, fnName = match[1]
|
|
, args = fnArgs(match[2])
|
|
, fn, fnName, i;
|
|
for (i = args.length; i--;) {
|
|
args[i] = fnArgValue(view, ctx, model, name, macro, args[i]);
|
|
}
|
|
if (!(fn = view.getFns[fnName])) {
|
|
throw new Error('view function "' + fnName + '" not found for call: ' + name);
|
|
}
|
|
return fn.apply(null, args);
|
|
}
|
|
|
|
function pathFnArgs(name, paths) {
|
|
var match = fnCall.exec(name) || fnCallError(name)
|
|
, args = fnArgs(match[2])
|
|
, i, arg;
|
|
if (paths == null) paths = [];
|
|
for (i = args.length; i--;) {
|
|
arg = args[i];
|
|
if (notPathArg.test(arg)) continue;
|
|
if (~arg.indexOf('(')) {
|
|
pathFnArgs(arg, paths);
|
|
continue;
|
|
}
|
|
paths.push(arg);
|
|
}
|
|
return paths;
|
|
}
|
|
|
|
function macroName(ctx, name, noReplace) {
|
|
var macroCtx = ctx.$macroCtx
|
|
, path = ctxPath(macroCtx, name, false, noReplace)
|
|
, segments = path.split('.')
|
|
, base = segments[0].toLowerCase()
|
|
, remainder = segments[1]
|
|
, value = lookup(base, macroCtx)
|
|
, macroVar = value && value.$macroVar;
|
|
if (!macroVar) return remainder ? base + '.' + remainder : base;
|
|
return remainder ?
|
|
(/\.+/.test(macroVar) ? macroVar.slice(1) : macroVar) + '.' + remainder :
|
|
macroVar;
|
|
}
|
|
|
|
function ctxPath(ctx, name, macro, noReplace) {
|
|
if (macro) name = macroName(ctx, name, noReplace);
|
|
|
|
var firstChar = name.charAt(0)
|
|
, i, aliasName, firstChar, indices;
|
|
if (firstChar === ':') {
|
|
if (~(i = name.indexOf('.'))) {
|
|
aliasName = name.slice(1, i);
|
|
name = name.slice(i);
|
|
} else {
|
|
aliasName = name.slice(1);
|
|
name = '';
|
|
}
|
|
i = ctx.$depth - ctx.$aliases[aliasName];
|
|
if (i !== i) throw new Error("Can't find alias for " + aliasName);
|
|
} else if (firstChar === '.') {
|
|
i = 0;
|
|
while (name.charAt(i) === '.') {
|
|
i++;
|
|
}
|
|
name = i === name.length ? '' : name.slice(i - 1);
|
|
}
|
|
if (i && (name = ctx.$paths[i - 1] + name) && !noReplace) {
|
|
indices = ctx.$indices;
|
|
i = indices.length;
|
|
name = name.replace(/\$#/g, function() {
|
|
return indices[--i];
|
|
});
|
|
}
|
|
return name.replace(/\[([^\]]+)\]/g, function(match, name) {
|
|
return lookup(name, ctx);
|
|
});
|
|
}
|
|
|
|
function dataValue(view, ctx, model, name, macro) {
|
|
var path, value;
|
|
if (~name.indexOf('(')) {
|
|
return fnValue(view, ctx, model, name, macro);
|
|
}
|
|
if (macro) {
|
|
// Get macro content sections
|
|
value = lookup(name.toLowerCase(), ctx.$macroCtx);
|
|
if (value && !value.$macroVar) {
|
|
return typeof value === 'function' ? value(ctx, model) : value;
|
|
}
|
|
}
|
|
path = ctxPath(ctx, name, macro);
|
|
value = lookup(path, ctx);
|
|
if (value !== void 0) return value;
|
|
value = model.get(path);
|
|
return value !== void 0 ? value : model[path];
|
|
}
|
|
|
|
function setBoundFn(view, ctx, model, name, value) {
|
|
var match = fnCall.exec(name) || fnCallError(name)
|
|
, fnName = match[1]
|
|
, args = fnArgs(match[2])
|
|
, get = view.getFns[fnName]
|
|
, set = view.setFns[fnName]
|
|
, macro = false
|
|
, numInputs = set.length - 1
|
|
, arg, i, inputs, out, path, len;
|
|
|
|
if (!(get && set)) {
|
|
throw new Error('view function "' + fnName + '" not found for binding to: ' + name);
|
|
}
|
|
|
|
if (numInputs) {
|
|
inputs = [value];
|
|
i = 0;
|
|
while (i < numInputs) {
|
|
inputs.push(fnArgValue(view, ctx, model, name, macro, args[i++]));
|
|
}
|
|
out = set.apply(null, inputs);
|
|
} else {
|
|
out = set(value);
|
|
}
|
|
if (!out) return;
|
|
|
|
for (i = 0, len = out.length; i < len; i++) {
|
|
value = out[i];
|
|
arg = args[i + numInputs];
|
|
if (~arg.indexOf('(')) {
|
|
setBoundFn(view, ctx, model, arg, value);
|
|
continue;
|
|
}
|
|
if (value === void 0 || notPathArg.test(arg)) continue;
|
|
path = ctxPath(ctx, arg);
|
|
if (model.get(path) === value) continue;
|
|
model.set(path, value);
|
|
}
|
|
}
|