bem
Version:
442 lines (339 loc) • 12 kB
JavaScript
'use strict';
var VM = require('vm'),
Q = require('q'),
FS = require('fs'),
PATH = require('../path'),
U = require('../util'),
INHERIT = require('inherit'),
Tech = require('../tech').Tech;
exports.Tech = INHERIT(Tech, {
readContentSync: function(path, suffix) {
if (!PATH.existsSync(path)) return '';
return FS.readFileSync(path, 'utf8');
},
getCreateResult: function(path, suffix, vars) {
return [
'({',
' mustDeps: [],',
' shouldDeps: []',
'})'
].join('\n');
},
transformBuildDecl: function(decl) {
return this.expandDeps(decl)
.then(function(deps) {
var d = deps.serialize(),
o = {};
if(d['']) {
o.deps = d[''][''];
delete d[''][''];
}
U.isEmptyObject(d) || (o.depsByTechs = d);
return o;
});
},
expandDeps: function(decl) {
var _this = this;
return Q.when(decl)
.then(function(decl) {
return new Deps(decl.blocks || decl.deps).expandByFS(_this);
});
},
buildByDecl: function(decl, levels, output) {
var suffix = this.getTechName();
return this.storeBuildResult(
this.getPath(output, suffix),
suffix,
this.expandDeps(decl)
.then(function(deps) {
return deps.stringify();
}));
}
});
var Deps = exports.Deps = INHERIT({
__constructor: function(deps) {
this.items = {};
this.itemsByOrder = [];
this.uniqExpand = {};
// Force adding of root item to this.items
var rootItem = this.rootItem = new DepsItem({});
this.items[rootItem.buildKey()] = rootItem;
deps && this.parse(deps);
},
add: function(target, depsType, item) {
var items = this.items,
targetKey = target.buildKey(),
itemKey = item.buildKey();
if(!items[itemKey]) {
items[itemKey] = item;
this.itemsByOrder.push(itemKey);
}
(items[targetKey] || (items[targetKey] = target))[depsType].push(itemKey);
},
remove: function(target, item) {
target = this.items[target.buildKey()];
var itemKey = item.buildKey();
U.removeFromArray(target.shouldDeps, itemKey);
U.removeFromArray(target.mustDeps, itemKey);
},
clone: function(target) {
target || (target = new this.__self());
var items = this.items;
for(var i in items) {
if(!items.hasOwnProperty(i)) continue;
target.items[i] = items[i].clone();
}
target.itemsByOrder = this.itemsByOrder.concat();
target.tech = this.tech;
target.uniqExpand = this.uniqExpand;
return target;
},
parse: function(deps, ctx, fn) {
fn || (fn = function(i) {
this.add(this.rootItem, 'shouldDeps', i);
});
var _this = this,
forEachItem = function(type, items, ctx) {
items && !U.isEmptyObject(items) && (Array.isArray(items) ? items : [items]).forEach(function(item) {
if(isSimple(item)) {
var i = item;
(item = {})[type] = i;
}
item.name && (item[type] = item.name);
var depsItem = new DepsItem(item, ctx);
fn.call(_this, depsItem); // _this.add(rootItem, 'shouldDeps', depsItem);
_this.parse(
item.mustDeps,
depsItem,
function(i) {
this.add(depsItem, 'mustDeps', i);
});
_this.parse(
item.shouldDeps,
depsItem,
function(i) {
this.add(depsItem, 'shouldDeps', i);
});
_this.parse(
item.noDeps,
depsItem,
function(i) {
this.remove(depsItem, i);
});
forEachItem('elem', item.elems, depsItem);
var mods = item.mods;
if(mods && !Array.isArray(mods)) { // Object
var modsArr = [];
for(var m in mods) {
if(!mods.hasOwnProperty(m)) continue;
modsArr.push({ mod: m });
var mod = { mod: m }, v = mods[m];
Array.isArray(v) ? (mod.vals = v) : (mod.val = v);
modsArr.push(mod);
}
mods = modsArr;
}
forEachItem('mod', mods, depsItem);
forEachItem('val', item.vals, depsItem);
});
};
forEachItem('block', deps, ctx);
return this;
},
expandByFS: function(tech) {
this.tech = tech;
var _this = this,
depsCount1 = this.getCount(),
depsCount2;
return Q.when(this.expandOnceByFS())
.then(function again(newDeps) {
depsCount2 = newDeps.getCount();
if(depsCount1 !== depsCount2) {
depsCount1 = depsCount2;
return Q.when(newDeps.expandOnceByFS(), again);
}
return newDeps.clone(_this);
});
},
expandOnceByFS: function() {
var newDeps = this.clone(),
steps = this
.filter(function(item) {
return !newDeps.uniqExpand.hasOwnProperty(item.buildKey());
})
.map(function(item) {
newDeps.uniqExpand[item.buildKey()] = true;
return newDeps.expandItemByFS(item);
});
if (!steps.length) return Q.resolve(newDeps);
return newDeps;
},
expandItemByFS: function(item) {
var _this = this,
tech = this.tech;
return tech.getContext().getLevels()
.map(function(level) {
var path = tech.getPath(item.buildLevelPath(level)),
content = tech.readContentSync(path, tech.getTechName());
if (!content) return;
try {
_this.parse(VM.runInThisContext(content, path), item);
} catch(e) {
e.message = path + '\n' + e.message;
throw e;
}
});
},
subtract: function(deps) {
var items1 = this.items,
items2 = deps.items;
for(var k in items2)
if(k && items2.hasOwnProperty(k)) delete items1[k];
return this;
},
intersect: function(deps) {
var items1 = this.items,
items2 = deps.items,
newItems = {};
for(var k in items2) {
if((items2.hasOwnProperty(k) && items1.hasOwnProperty(k)) || !k)
newItems[k] = items1[k];
}
this.items = newItems;
return this;
},
getCount: function() {
var res = 0,
items = this.items;
for(var k in items) items.hasOwnProperty(k) && res++;
return res;
},
forEach: function(fn, uniq, itemsByOrder, ctx) {
uniq || (uniq = {});
var _this = this;
(itemsByOrder || this.items[''].shouldDeps).forEach(function(i) {
if(i = _this.items[i]) {
var key = i.buildKey(),
ctxTech = ctx && ctx.item.tech || '';
if(!uniq.hasOwnProperty(key) || !uniq[key].hasOwnProperty(ctxTech)) {
(uniq[key] || (uniq[key] = {}))[ctxTech] = true;
var newCtx = ctx || i;
_this.forEach(fn, uniq, i.mustDeps, newCtx);
fn.call(_this, i, newCtx);
_this.forEach(fn, uniq, i.shouldDeps, newCtx);
}
}
});
},
map: function(fn) {
var res = [];
this.forEach(function(item) {
res.push(fn.call(this, item));
});
return res;
},
filter: function(fn) {
var res = [];
this.forEach(function(item) {
if (fn.call(this, item)) res.push(item);
});
return res;
},
serialize: function() {
var byTech = {};
this.forEach(function(item, ctx) {
var t1 = ctx.item.tech || '',
t2 = item.item.tech || '',
techsByTech = byTech[t1] || (byTech[t1] = {}),
i = item.serialize();
i && (techsByTech[t2] || (techsByTech[t2] = [])).push(i);
});
return byTech;
},
stringify: function() {
var res = [],
deps = this.serialize();
if(deps['']) {
res.push('exports.deps = ' + JSON.stringify(deps[''][''], null, 4) + ';\n');
delete deps[''][''];
} else {
res.push('exports.deps = [];\n');
}
U.isEmptyObject(deps) || res.push('exports.depsByTechs = ' + JSON.stringify(deps, null, 4) + ';\n');
return res.join('');
}
});
var DepsItem = exports.DepsItem = INHERIT({
__constructor: function(item, ctx) {
this.shouldDeps = [];
this.mustDeps = [];
this.item = {};
this.extendByCtx({ item: item });
this.extendByCtx(ctx);
},
extendByCtx: function(ctx) {
if(ctx && (ctx = ctx.item)) {
var ks = ['tech', 'block', 'elem', 'mod', 'val'],
k;
while(k = ks.shift())
if(this.item[k]) break;
else ctx[k] && (this.item[k] = ctx[k]);
}
return this;
},
clone: function() {
var res = new this.__self({}, this);
res.shouldDeps = this.shouldDeps.concat();
res.mustDeps = this.mustDeps.concat();
this.hasOwnProperty('key') && (res.key = this.key);
return res;
},
extend: function(item) {
if(!item) return this;
var ds = ['mustDeps', 'shouldDeps'], d,
thisDeps, itemDeps;
while(d = ds.shift()) {
itemDeps = item[d] || (item[d] = {});
if(thisDeps = this.item[d]) {
for(var k in thisDeps)
if(thisDeps.hasOwnProperty(k)) {
if(!thisDeps[k].extend) throw 'bla';
(itemDeps[k] = thisDeps[k].extend(itemDeps[k]));
}
}
}
return item;
},
cache: function(cache) {
var key = this.buildKey();
return cache[key] = this.extend(cache[key]);
},
buildKey: function() {
if('key' in this) return this.key;
var i = this.item,
k = '';
if(i.block) {
k += i.block;
i.elem && (k += '__' + i.elem);
if(i.mod) {
k += '_' + i.mod;
i.val && (k += '_' + i.val);
}
}
i.tech && (k += '.' + i.tech);
return this.key = k;
},
buildLevelPath: function(level) {
return level.getByObj(this.item);
},
serialize: function() {
var res = {},
ks = ['tech', 'block', 'elem', 'mod', 'val'], k;
while(k = ks.shift()) this.item[k] && (res[k] = this.item[k]);
if(res.block) return res;
}
});
function isSimple(o) {
var t = typeof o;
return t === 'string' || t === 'number';
}