enb
Version:
Faster BEM/BEViS assembler
239 lines (225 loc) • 9.32 kB
JavaScript
/**
* Level
* =====
*/
var inherit = require('inherit');
var fs = require('fs');
var Vow = require('vow');
var LevelBuilder = require('./level-builder');
// TODO: Сделать 1-в-1 асинхронный аналог (с точно таким же порядком файлов на выходе).
/**
* Level — объектная модель уровня переопределения.
* @name Level
*/
module.exports = inherit({
/**
* Конструктор.
* @param {String} path Путь к уровню переопределения.
* @param {Function} [schemeBuilder]
*/
__constructor: function (path, schemeBuilder) {
this._path = path;
this.blocks = {};
this._loadPromise = null;
this._schemeBuilder = schemeBuilder;
},
/**
* Загружает из кэша.
*/
loadFromCache: function (data) {
this.blocks = data;
this._loadPromise = Vow.fulfill(this);
},
/**
* Возвращает структуру блоков.
* @returns {Object}
*/
getBlocks: function () {
return this.blocks;
},
/**
* Проверяет наличие блока с указанным именем.
* @param blockName
* @returns {Boolean}
*/
hasBlock: function (blockName) {
return this.blocks[blockName];
},
/**
* Возвращает абсолютный путь к уровню переопределения.
* @returns {String}
*/
getPath: function () {
return this._path;
},
/**
* Обрабатывает файл, добавляет его в необходимое место в структуре.
* @param {String} filename
* @param {Object} stat node.js Stat
* @param {String} parentElementName
* @param {String} elementName
* @param {String} modName
* @private
*/
_processFile: function (filename, stat, parentElementName, elementName, modName) {
var requiredBaseNameWithoutExt = (parentElementName ? parentElementName + '__' : '') +
elementName + (modName ? '_' + modName : '');
var baseName = filename.split('/').slice(-1)[0];
var baseNameParts = baseName.split('.');
var baseNameWithoutExt = stat.isDirectory() ?
baseNameParts.slice(0, baseNameParts.length - 1).join('.') :
baseNameParts[0];
var rl = requiredBaseNameWithoutExt.length;
var modVal;
var processFile = baseNameWithoutExt.indexOf(requiredBaseNameWithoutExt) === 0 && (
modName ?
(rl === baseNameWithoutExt.length) || baseNameWithoutExt.charAt(rl) === '_' :
baseNameWithoutExt === requiredBaseNameWithoutExt
);
if (!processFile && !modName && !parentElementName) {
var baseNameModParts = baseNameWithoutExt.split('_');
if (baseNameModParts.length === 2 && baseNameModParts[0] === requiredBaseNameWithoutExt) {
processFile = true;
modName = 'view';
modVal = baseNameModParts[1];
}
}
if (processFile) {
var suffix = stat.isDirectory() ? baseNameParts.pop() : baseNameParts.slice(1).join('.');
var fileInfo = {
name: baseName,
fullname: filename,
suffix: suffix,
mtime: stat.mtime.getTime(),
isDirectory: stat.isDirectory()
};
if (fileInfo.isDirectory) {
fileInfo.files = filterFiles(fs.readdirSync(filename)).map(function (subFilename) {
var subFullname = filename + '/' + subFilename;
var subStat = fs.statSync(subFullname);
return {
name: subFilename,
fullname: subFullname,
suffix: subFilename.split('.').slice(1).join('.'),
mtime: subStat.mtime.getTime(),
isDirectory: subStat.isDirectory()
};
});
}
var blockName = parentElementName || elementName;
var block = this.blocks[blockName] || (this.blocks[blockName] = {
name: blockName,
files: [],
dirs: [],
elements: {},
mods: {}
});
var destElement;
if (parentElementName) {
destElement = block.elements[elementName] || (block.elements[elementName] = {
name: elementName,
files: [],
dirs: [],
mods: {}
});
} else {
destElement = block;
}
var collectionKey = fileInfo.isDirectory ? 'dirs' : 'files';
if (modName) {
if (!modVal) {
if (rl !== baseNameWithoutExt.length) {
modVal = baseNameWithoutExt.substr(rl + 1);
} else {
modVal = '';
}
}
if (modName === 'view' && modVal === 'view') {
modVal = '';
}
var mod = destElement.mods[modName] || (destElement.mods[modName] = {});
var modValueFiles = (mod[modVal] || (mod[modVal] = {files: [], dirs: []}))[collectionKey];
modValueFiles.push(fileInfo);
} else {
destElement[collectionKey].push(fileInfo);
}
}
},
/**
* Загружает файлы и директории в папке модификатора.
* @param {String} parentElementName
* @param {String} elementName
* @param {String} modName
* @param {String} modDirPath
* @private
*/
_loadMod: function (parentElementName, elementName, modName, modDirPath) {
var _this = this;
filterFiles(fs.readdirSync(modDirPath)).forEach(function (filename) {
var fullname = modDirPath + '/' + filename;
var stat = fs.statSync(fullname);
_this._processFile(fullname, stat, parentElementName, elementName, modName);
});
},
/**
* Загружает файлы и директории в папке элемента или блока (если не указан parentElementName).
* @param {String} parentElementName
* @param {String} elementName
* @param {String} elementDirPath
* @param {String} containsElements
* @private
*/
_loadElement: function (parentElementName, elementName, elementDirPath, containsElements) {
var _this = this;
var requiredBaseNameWithoutExt = (parentElementName ? parentElementName + '__' : '') + elementName;
filterFiles(fs.readdirSync(elementDirPath)).forEach(function (filename) {
var fullname = elementDirPath + '/' + filename;
var stat = fs.statSync(fullname);
if (stat.isDirectory()) {
if (containsElements && filename.substr(0, 2) === '__') {
_this._loadElement(elementName, filename.substr(2), fullname, false);
} else if (filename.charAt(0) === '_') {
_this._loadMod(parentElementName, elementName, filename.substr(1), fullname);
} else if (filename.indexOf('.') !== -1 && filename.indexOf(requiredBaseNameWithoutExt + '.') === 0) {
_this._processFile(fullname, stat, parentElementName, elementName);
} else if (containsElements) {
_this._loadElement(elementName, filename, fullname, false);
}
} else if (stat.isFile()) {
_this._processFile(fullname, stat, parentElementName, elementName);
}
});
},
/**
* Загружает уровень перепределения: загружает структуру блоков, элементов и модификаторов.
*/
load: function () {
if (this._loadPromise) {
return this._loadPromise;
}
this._loadPromise = Vow.promise();
var _this = this;
if (this._schemeBuilder) {
var levelBuilder = new LevelBuilder();
Vow.when(this._schemeBuilder.buildLevel(this._path, levelBuilder)).then(function () {
_this.blocks = levelBuilder.getBlocks();
_this._loadPromise.fulfill(_this);
});
} else {
var path = this._path;
filterFiles(fs.readdirSync(path)).forEach(function (blockDir) {
var blockDirPath = path + '/' + blockDir;
if (fs.statSync(blockDirPath).isDirectory()) {
_this._loadElement(null, blockDir, blockDirPath, true);
}
});
this._loadPromise.fulfill(this);
}
return this._loadPromise;
}
});
function filterFiles(filenames) {
return filenames.filter(function (filename) {
return filename.charAt(0) !== '.';
});
}