snakeskin
Version:
Компилятор блочных шаблонов c поддержкой наследования.
272 lines (217 loc) • 8.24 kB
JavaScript
var aliasRgxp = /__(.*?)__/;
/**
* Добавить новую директиву в пространство имён Snakeskin
*
* @param {string} name - название добавляемой директивы
* @param {Object} params - дополнительные параметры
*
* @param {?boolean=} [params.alias=false] - если true, то директива считается псевдонимом
* (только для приватных директив)
*
* @param {?boolean=} [params.text=false] - если true, то декларируется,
* что директива выводится как текст
*
* @param {?string=} [params.placement] - если параметр задан, то делается проверка
* где именно размещена директива ('global', 'template', ...)
*
* @param {?boolean=} [params.notEmpty=false] - если true, то директива не может быть "пустой"
* @param {(Array|string)=} [params.chain] - название главной директивы (цепи), к которой принадлежит директива,
* или массив названий
*
* @param {(Array|string)=} [params.group] - название группы, к которой принадлежит директива,
* или массив названий
*
* @param {?boolean=} [params.sys=false] - если true, то директива считается системной
* (например, block или proto, т.е. которые участвуют только на этапе трансляции)
*
* @param {?boolean=} [params.block=false] - если true, то директива считается блочной
* (т.е. требует закрывающей директивы)
*
* @param {?boolean=} [params.selfInclude=true] - если false, то директива не может быть вложена
* в директиву схожего типа
*
* @param {Object=} [params.replacers] - таблица коротких сокращений директивы
* replacers: {
* // В ключе должно быть не более 2-х символов
* '?': (cmd) => cmd.replace(/^\?/, 'void ')
* }
*
* @param {Object=} [params.inside] - таблица директив, которые могут быть вложены в исходную
* inside: {
* 'case': true,
* 'default': true
* }
*
* @param {Object=} [params.after] - таблица директив, которые могут идти после исходной
* after: {
* 'catch': true,
* 'finally': true
* }
*
* @param {function(this:DirObj, string, number, string, (boolean|number))} constr - конструктор директивы
* @param {?function(this:DirObj, string, number, string, (boolean|number))=} opt_destr - деструктор директивы
*/
Snakeskin.addDirective = function (name, params, constr, opt_destr) {
params = params || {};
forIn(params.replacers, (el, key) => {
if (key.length > 2) {
throw new Error(`Invalid shorthand key "${key}" (key.length > 2)`);
}
replacers[key] = el;
if (key.charAt(0) !== '/') {
shortMap[key] = true;
}
});
after[name] = params.after;
inside[name] = params.inside;
sys[name] = Boolean(params.sys);
block[name] = Boolean(params.block);
text[name] = Boolean(params.text);
if (params.alias) {
aliases[name] = name.replace(aliasRgxp, '$1');
}
if (params.group) {
let group = Array.isArray(params.group) ?
params.group : [params.group];
for (let i = -1; ++i < group.length;) {
if (!groups[group[i]]) {
groups[group[i]] = {};
groupsList[group[i]] = [];
}
groups[group[i]][name] = true;
groupsList[group[i]].push(`"${name}"`);
}
}
if (params.chain) {
let chain = Array.isArray(params.chain) ?
params.chain : [params.chain];
for (let i = -1; ++i < chain.length;) {
if (!chains[chain[i]]) {
chains[chain[i]] = {};
}
chains[chain[i]][name] = true;
}
}
params.selfInclude = params.selfInclude !== false;
if (!params.selfInclude) {
params.block = true;
}
Snakeskin.Directions[name] = function (command, commandLength, type, jsDoc) {
var dir = this;
var sourceName = getName(name),
dirName = getName(name);
if (dir.ctx) {
dirName = dir.name || dirName;
dir = dir.ctx;
}
var ignore = groups['ignore'][dirName],
struct = dir.structure;
dir.name = dirName;
switch (params.placement) {
case 'template':
if (!struct.parent) {
dir.error(`directive "${dirName}" can be used only within a ${groupsList['template'].join(', ')}`);
}
break;
case 'global':
if (struct.parent) {
dir.error(`directive "${dirName}" can be used only within the global space`);
}
break;
default:
if (params.placement && dir.hasParent(params.placement)) {
dir.error(`directive "${dirName}" can be used only within a "${params.placement}"`);
}
}
if (params.notEmpty && !command) {
return dir.error(`directive "${dirName}" should have a body`);
}
if (struct.strong) {
if (inside[struct.name][dirName]) {
dir.chainSpace = false;
} else if (!ignore && sourceName === dirName && dirName !== 'end') {
return dir.error(`directive "${dirName}" can't be used within the "${struct.name}"`);
}
}
if (!params.selfInclude && dir.has(dirName)) {
return dir.error(`directive "${dirName}" can't be used within the "${dirName}"`);
}
if (params.text) {
dir.text = true;
}
var from = dir.res.length;
constr.call(dir, command, commandLength, type, jsDoc);
if (dir.structure.params._from === void 0) {
dir.structure.params._from = from;
}
var newStruct = dir.structure;
if (inside[dirName]) {
newStruct.strong = true;
dir.chainSpace = true;
}
if (dirName === sourceName) {
if (struct === newStruct) {
if (!ignore && after[struct.name] && !after[struct.name][dirName]) {
return dir.error(`directive "${dirName}" can't be used after the "${struct.name}"`);
}
} else {
let siblings = sourceName === 'end' ?
newStruct.children : newStruct.parent && newStruct.parent.children;
if (siblings) {
let j = 1,
prev;
while ((prev = siblings[siblings.length - j]) && (prev.name === 'text' || prev === newStruct)) {
j++;
}
if (!ignore && prev && after[prev.name] && !after[prev.name][dirName]) {
return dir.error(`directive "${dirName}" can't be used after the "${prev.name}"`);
}
}
}
}
dir.applyQueue();
if (dir.inline === true) {
baseEnd.call(dir);
if (opt_destr) {
opt_destr.call(dir, command, commandLength, type, jsDoc);
}
dir.inline = null;
dir.structure = dir.structure.parent;
if (dir.blockStructure && dir.blockStructure.name === 'const') {
dir.blockStructure = dir.blockStructure.parent;
}
}
};
Snakeskin.Directions[`${name}End`] = opt_destr;
var baseEnd = Snakeskin.Directions[`${name}BaseEnd`] = function () {
var params = this.structure.params;
if (params._scope) {
this.scope.pop();
}
forIn(params._consts, (el, key) => {
constCache[this.tplName][key] = el;
});
var res = params._res ?
params._res : this.res;
var from = params._from,
to = res.length;
if (from == null) {
return;
}
var parent = this.structure.parent;
if ((!parent || parent.name === 'root') && !this.getGroup('define')[name] && from !== to) {
try {
this.evalStr(res.substring(from, to));
} catch (err) {
return this.error(err.message);
}
if (fsStack.length) {
this.source = this.source.substring(0, this.i + 1) +
this.replaceCData(fsStack.join('')) +
this.source.substring(this.i + 1);
fsStack.splice(0, fsStack.length);
}
}
};
};
//#include ./directions/*.js