UNPKG

enb

Version:

Faster BEM/BEViS assembler

1,010 lines (975 loc) 39.2 kB
/** * BuildFlow * ========= */ var inherit = require('inherit'); var Vow = require('vow'); var vowFs = require('./fs/async-fs'); /** * BuildFlow — это хэлпер для упрощения создания полностью настраиваемых технологий. * * Например, сборка js: * ```javascript * module.exports = require('enb/lib/build-flow').create() * .name('js') * .target('target', '?.js') * .useFileList('js') * .justJoinFilesWithComments() * .createTech(); * ``` * @name BuildFlow */ var BuildFlow = inherit( /** @lends BuildFlow.prototype */ { /** * Конструктор. */ __constructor: function () { this._name = ''; this._usages = []; this._dependencies = []; this._targetOptionName = ''; this._defaultTargetName = ''; this._requiredOptions = []; this._options = {}; this._methods = {}; this._staticMethods = {}; this._deprecationNotice = null; this._optionAliases = {}; this._prepareFunc = function () {}; this._buildFunc = function () { throw new Error('You should declare build function using "build" method of BuildFlow.'); }; this._wrapFunc = function (data) { return data; }; this._saveFunc = function (filename, result) { return vowFs.write(filename, result, 'utf8'); }; this._cacheValidator = function () { return false; }; this._cacheSaver = function () {}; }, /** * Устанавливает имя технологии. * @param {String} techName * @returns {BuildFlow} */ name: function (techName) { return this._copyAnd(function (buildFlow) { buildFlow._name = techName; }); }, /** * @param {String} thisPackage * @param {String} [newPackage] * @param {String} [newTech] * @param {String} [desc] */ deprecated: function (thisPackage, newPackage, newTech, desc) { return this._copyAnd(function (buildFlow) { buildFlow._deprecationNotice = { thisPackage: thisPackage, newPackage: newPackage, newTech: newTech, desc: desc }; }); }, /** * Определяет опцию для технологии. * @param {String} optionName Имя опции. * @param {*} [defaultValue] Значение по умолчанию. * @param {String} [fieldName] Имя поля, в которое необходимо записать значение опции * (по умолчанию — "_<имя опции>"). * @returns {BuildFlow} */ defineOption: function (optionName, defaultValue, fieldName) { return this._copyAnd(function (buildFlow) { buildFlow._options[optionName] = { fieldName: fieldName || '_' + optionName, defaultValue: defaultValue }; }); }, /** * Определяет обязательную опцию для технологии. * @param {String} optionName Имя опции. * @param {String} [fieldName] Имя поля, в которое необходимо записать значение опции * (по умолчанию — "_<имя опции>"). * @returns {BuildFlow} */ defineRequiredOption: function (optionName, fieldName) { return this._copyAnd(function (buildFlow) { buildFlow._options[optionName] = { fieldName: fieldName || '_' + optionName }; buildFlow._requiredOptions.push(optionName); }); }, /** * Объявляет алиас для опции. * * @param {String} optionName * @param {String} aliasName */ optionAlias: function (optionName, aliasName) { return this._copyAnd(function (buildFlow) { buildFlow._optionAliases[aliasName] = optionName; }); }, /** * Требует список файлов по суффиксу или суффиксам. * Например, .useFileList("js") добавит в аргументы билдеру список файлов с расширением js. * Значение по умолчанию можно переопределить параметром sourceSuffixes * @param {String|Array} defaultSuffixes Значение по умолчанию. * @returns {BuildFlow} */ useFileList: function (defaultSuffixes) { return this._copyAnd(function (buildFlow) { buildFlow._addUsage( new BuildFlowLinkToFileList('filesTarget', '?.files', defaultSuffixes) ); }); }, /** * Требует список директорий по суффиксу или суффиксам. * Например, .useDirList("i18n") добавит в аргументы билдеру список директорий с расширением i18n. * @param {String|Array} suffixes * @returns {BuildFlow} */ useDirList: function (suffixes) { return this._copyAnd(function (buildFlow) { buildFlow._addUsage( new BuildFlowLinkToDirList('dirsTarget', '?.dirs', suffixes) ); }); }, /** * Отменяет требование целей другой ноды. * @param {String} name * @returns {BuildFlow} */ unuseTarget: function (name) { return this._copyAnd(function (buildFlow) { buildFlow._removeUsage(name); }); }, /** * Отменяет требование списка файлов. * @returns {BuildFlow} */ unuseFileList: function () { return this._copyAnd(function (buildFlow) { buildFlow._removeUsage('filesTarget'); }); }, /** * Отменяет требование списка директорий. * @returns {BuildFlow} */ unuseDirList: function () { return this._copyAnd(function (buildFlow) { buildFlow._removeUsage('dirsTarget'); }); }, /** * Требует имя файла другого таргета ноды, объявляет зависимость от этого таргета. * В аргументы билдера придет абсолютный путь к файлу. * @param {String} targetOptionName Имя опции для таргета. * @param {String} defaultTargetName Значение по умолчанию. * @returns {BuildFlow} */ useSourceFilename: function (targetOptionName, defaultTargetName) { return this._copyAnd(function (buildFlow) { buildFlow._addUsage(new BuildFlowLinkToTargetFilename(targetOptionName, defaultTargetName)); }); }, /** * Требует список имен файлов других таргетов ноды, объявляет зависимость от этого таргета. * В аргументы билдера придет абсолютный путь к файлу. * @param {String} targetOptionName Имя опции для таргета. * @param {String[]} [defaultTargetNames] Значение по умолчанию. * @returns {BuildFlow} */ useSourceListFilenames: function (targetOptionName, defaultTargetNames) { return this._copyAnd(function (buildFlow) { buildFlow._addUsage( new BuildFlowLinkToTargetList(targetOptionName, defaultTargetNames, BuildFlowLinkToTargetFilename) ); }); }, /** * Требует содержимое файла другого таргета ноды, объявляет зависимость от этого таргета. * В аргументы билдера придет содержимое файла. * @param {String} targetOptionName Имя опции для таргета. * @param {String} defaultTargetName Значение по умолчанию. * @returns {BuildFlow} */ useSourceText: function (targetOptionName, defaultTargetName) { return this._copyAnd(function (buildFlow) { buildFlow._addUsage(new BuildFlowLinkToTargetSource(targetOptionName, defaultTargetName)); }); }, /** * Требует результат выполнения другого таргета ноды, объявляет зависимость от этого таргета. * В аргументы билдера придет результат выполнения технологии. * @param {String} targetOptionName * @param {String} defaultTargetName * @returns {BuildFlow} */ useSourceResult: function (targetOptionName, defaultTargetName) { return this._copyAnd(function (buildFlow) { buildFlow._addUsage(new BuildFlowLinkToTargetResult(targetOptionName, defaultTargetName)); }); }, /** * Объявляет зависимость от таргета ноды, но не добявляет аргументов в билдер. * @param {String} targetOptionName * @param {String} defaultTargetName * @returns {BuildFlow} */ dependOn: function (targetOptionName, defaultTargetName) { return this._copyAnd(function (buildFlow) { buildFlow._addDep(new BuildFlowLinkToTargetNoResult(targetOptionName, defaultTargetName)); }); }, /** * Настраивает таргет для технологии. * @param {String} targetOptionName Имя опции, в котором передается таргет. * @param {String} defaultTargetName Имя таргета по умолчанию. * @returns {BuildFlow} */ target: function (targetOptionName, defaultTargetName) { return this._copyAnd(function (buildFlow) { buildFlow._targetOptionName = targetOptionName; buildFlow._defaultTargetName = defaultTargetName; }); }, /** * Определяет метод для сборки. Метод должен возвращать строку-результат сборки, либо промис, * который резолвится строкой. * Основной метод для технологии. Все источники, которые были определены через методы use* будут переданы в билдер, * как аргументы. * @param {Function} func * @returns {BuildFlow} */ builder: function (func) { return this._copyAnd(function (buildFlow) { buildFlow._buildFunc = func; }); }, /** * Определяет метод подготовки к сборке. Метод не принимает никаких аргументов. * * @param {Function} func * @returns {BuildFlow} */ prepare: function (func) { return this._copyAnd(function (buildFlow) { buildFlow._prepareFunc = func; }); }, /** * Определяет метод проверки кэша при сборке. * Метод должен возвращает булевое значение. * true — необходимо пересобрать таргет. * false — нет необходимости пересобирать таргет. * @param {Function} func * @returns {BuildFlow} */ needRebuild: function (func) { return this._copyAnd(function (buildFlow) { buildFlow._cacheValidator = func; }); }, /** * Сохраняет кэш для того, чтобы избежать лишнюю повторную сборку. * @param {Function} func * @returns {BuildFlow} */ saveCache: function (func) { return this._copyAnd(function (buildFlow) { buildFlow._cacheSaver = func; }); }, /** * Определяет функцию-враппер. * В функцию передает строка, которую возвращает билдер. * @param {Function} func * @returns {BuildFlow} */ wrapper: function (func) { return this._copyAnd(function (buildFlow) { buildFlow._wrapFunc = func; }); }, /** * Определяет функцию, сохраняющую результат выполнения билдера. * Фунция должна возвратить промис. * @param {Function} func * @returns {BuildFlow} */ saver: function (func) { return this._copyAnd(function (buildFlow) { buildFlow._saveFunc = func; }); }, /** * Определяет набор методов технологии. * @param {Object} methods Методы в виде хэш-таблицы (объекта). * @returns {BuildFlow} */ methods: function (methods) { return this._copyAnd(function (buildFlow) { Object.keys(methods).forEach(function (methodName) { buildFlow._methods[methodName] = methods[methodName]; }); }); }, /** * Определяет набор статических методов технологии. * @param {Object} staticMethods Методы в виде хэш-таблицы (объекта). * @returns {BuildFlow} */ staticMethods: function (staticMethods) { return this._copyAnd(function (buildFlow) { Object.keys(staticMethods).forEach(function (methodName) { buildFlow._staticMethods[methodName] = staticMethods[methodName]; }); }); }, /** * Создает копию инстанции BuildFlow. * @returns {BuildFlow} */ copy: function () { var result = new BuildFlow(); result._targetOptionName = this._targetOptionName; result._defaultTargetName = this._defaultTargetName; result._name = this._name; result._usages = this._usages.slice(0); result._dependencies = this._dependencies.slice(0); result._requiredOptions = this._requiredOptions.slice(0); result._buildFunc = this._buildFunc; result._saveFunc = this._saveFunc; result._wrapFunc = this._wrapFunc; result._cacheValidator = this._cacheValidator; result._cacheSaver = this._cacheSaver; result._prepareFunc = this._prepareFunc; result._deprecationNotice = this._deprecationNotice; var options = this._options; Object.keys(options).forEach(function (optName) { result._options[optName] = options[optName]; }); var methods = this._methods; Object.keys(methods).forEach(function (methodName) { result._methods[methodName] = methods[methodName]; }); var staticMethods = this._staticMethods; Object.keys(staticMethods).forEach(function (methodName) { result._staticMethods[methodName] = staticMethods[methodName]; }); var optionAliases = this._optionAliases; Object.keys(optionAliases).forEach(function (optionName) { result._optionAliases[optionName] = optionAliases[optionName]; }); return result; }, /** * Хэлпер для построения билдера, который просто объединяет файлы, переданные в аргументах. * @param wrapper * @returns {BuildFlow} */ justJoinFiles: function (wrapper) { return this._copyAnd(function (buildFlow) { buildFlow._buildFunc = function () { var _this = this; return Vow.all(Array.prototype.map.call(arguments, function (arg) { if (typeof arg === 'string') { return vowFs.read(arg, 'utf8').then(function (data) { return wrapper ? wrapper.call(_this, arg, data) : data; }); } else if (Array.isArray(arg)) { return _this._joinFiles(arg, wrapper); } else { return ''; } })).then(function (res) { return res.join('\n'); }); }; }); }, /** * Хэлпер для построения билдера, который объединяет файлы, переданные в аргументах, расставляя комментарии * о расположении файлов. * @returns {BuildFlow} */ justJoinFilesWithComments: function () { return this.justJoinFiles(function (filename, data) { var fn = this.node.relativePath(filename); return '/* begin: ' + fn + ' *' + '/\n' + data + '\n/* end: ' + fn + ' *' + '/'; }); }, /** * Хэлпер для построения билдера, который объединяет текст, переданный в аргументах. * @returns {BuildFlow} */ justJoinSources: function () { return this._copyAnd(function (buildFlow) { buildFlow._buildFunc = function () { var _this = this; return Vow.all(Array.prototype.map.call(arguments, function (arg) { if (typeof arg === 'string') { return arg; } else if (Array.isArray(arg)) { return _this._joinFiles(arg); } else { return ''; } })).then(function (res) { return res.join('\n'); }); }; }); }, _addUsage: function (usage) { return this._addToTargetLinks(this._usages, usage); }, _removeUsage: function (targetOptionName) { this._usages = this._usages.filter(function (link) { return link.getTargetOptionName() !== targetOptionName; }); }, _addDep: function (dep) { return this._addToTargetLinks(this._dependencies, dep); }, _addToTargetLinks: function (links, link) { var optionName = link.getTargetOptionName(); for (var i = 0, l = links.length; i < l; i++) { if (links[i].getTargetOptionName() === optionName) { links[i] = link; return this; } } links.push(link); return this; }, /** * Создает копию BuildFlow и выполняет переданную функцию для копии. * Возвращает копию. * @param {Function} func * @returns {BuildFlow} * @private */ _copyAnd: function (func) { var result = this.copy(); func(result); return result; }, /** * Создает технологию. * @returns {Tech} */ createTech: function () { var name = this._name; var targetOptionName = this._targetOptionName; var defaultTargetName = this._defaultTargetName; var usages = this._usages.concat(this._dependencies); var requiredOptions = this._requiredOptions; var options = this._options; var buildFunc = this._buildFunc; var saveFunc = this._saveFunc; var wrapFunc = this._wrapFunc; var cacheValidator = this._cacheValidator; var cacheSaver = this._cacheSaver; var preparer = this._prepareFunc; var methods = this._methods; var staticMethods = this._staticMethods; var deprecationNotice = this._deprecationNotice; var optionAliases = this._optionAliases; if (!name) { throw new Error('You should declare tech name using "name" method of BuildFlow.'); } if (!targetOptionName) { throw new Error('You should declare tech target name using "target" method of BuildFlow.'); } var resultTechMethods = { configure: function () { var _this = this; var node = this.node; Object.keys(optionAliases).forEach(function (aliasName) { if (this._options.hasOwnProperty(aliasName)) { var optionName = optionAliases[aliasName]; this._options[optionName] = this._options[aliasName]; delete this._options[aliasName]; } }, this); options.prependText = { fieldName: '_prependText', defaultValue: '' }; options.appendText = { fieldName: '_appendText', defaultValue: '' }; this._optionFieldNames = {}; Object.keys(options).forEach(function (optName) { var option = options[optName]; _this[option.fieldName] = _this.getOption(optName, option.defaultValue); _this._optionFieldNames[optName] = option.fieldName; }); this._target = node.unmaskTargetName( this._preprocessTargetName(defaultTargetName ? _this.getOption(targetOptionName, defaultTargetName) : _this.getRequiredOption(targetOptionName)) ); requiredOptions.forEach(function (requiredOption) { _this.getRequiredOption(requiredOption); }); usages.forEach(function (usage) { usage.configureUsages(_this, node); }); this._buildResult = undefined; }, getName: function () { return name; }, getTargets: function () { return [this._target]; }, _requireSources: function () { var _this = this; var node = this.node; return Vow.all(usages.map(function (usage) { return usage.requireTarget(_this, node); })); }, _isRebuildRequired: function () { var cache = this.node.getNodeCache(this._target); if (cacheValidator.call(this, cache)) { return true; } if (cache.needRebuildFile('target', this.node.resolvePath(this._target))) { return true; } for (var i = 0, l = usages.length; i < l; i++) { if (!usages[i].isCacheValid(this, this.node, cache)) { return true; } } return false; }, _saveCache: function () { var cache = this.node.getNodeCache(this._target); cache.cacheFileInfo('target', this.node.resolvePath(this._target)); for (var i = 0, l = usages.length; i < l; i++) { usages[i].saveCache(this, this.node, cache); } cacheSaver.call(this, cache); }, _getBuildResult: function () { return buildFunc.apply(this, arguments); }, _saveBuildResult: function () { return saveFunc.apply(this, arguments); }, _wrapBuildResult: function () { return wrapFunc.apply(this, arguments); }, _joinFiles: function (files, wrapper) { var _this = this; return Vow.all(files.map(function (fileInfo) { return vowFs.read(fileInfo.fullname, 'utf8').then(function (data) { return wrapper ? wrapper.call(_this, fileInfo.fullname, data) : data; }); })).then(function (results) { return results.join('\n'); }); }, _joinFilesWithComments: function (files) { var node = this.node; return this._joinFiles(files, function (filename, data) { var fn = node.relativePath(filename); return '/* begin: ' + fn + ' *' + '/\n' + data + '\n/* end: ' + fn + ' *' + '/'; }); }, _preprocessTargetName: function (targetName) { var _this = this; return targetName.replace(/{([^}]+)}/g, function (s, optName) { if (_this._optionFieldNames[optName]) { return _this[_this._optionFieldNames[optName]] || ''; } else { return _this.getOption(optName, ''); } }); }, setResult: function (value) { this._buildResult = value; }, _prepare: function () { return preparer.call(this); }, build: function () { var _this = this; var node = this.node; return Vow.when(_this._prepare()).then(function () { if (deprecationNotice) { node.getLogger().logTechIsDeprecated( _this._target, name, deprecationNotice.thisPackage, deprecationNotice.newTech || (deprecationNotice.newPackage ? name : ''), deprecationNotice.newPackage, deprecationNotice.desc ); } return _this._requireSources().then(function (results) { if (_this._isRebuildRequired()) { return Vow.when(_this._getBuildResult.apply(_this, results)).then(function (data) { return Vow.when(_this._wrapBuildResult(data)).then(function (wrappedData) { wrappedData = _this._prependText + wrappedData + _this._appendText; return Vow.when( _this._saveBuildResult(_this.node.resolvePath(_this._target), wrappedData) ).then(function () { _this._saveCache(); node.resolveTarget(_this._target, _this._buildResult); }); }); }); } else { node.isValidTarget(_this._target); node.resolveTarget(_this._target, _this._buildResult); return null; } }); }); } }; Object.keys(methods).forEach(function (methodName) { resultTechMethods[methodName] = methods[methodName]; }); /** * Результирующая технология, которая строится на основе инстанции BuildFlow. */ var resultTech = inherit(require('./tech/base-tech'), resultTechMethods, staticMethods); var currentBuildFlow = this; /** * Каждому классу технологий добавляем метод buildFlow, чтобы вернуть инстанцию BuildFlow, с помощью которой * была построена технология. * @returns {BuildFlow} */ resultTech.buildFlow = function () { return currentBuildFlow; }; return resultTech; } }); /** * Связь технологии с результатом выполнения другой цели. */ var BuildFlowLinkToTargetResult = inherit({ /** * @param {String} targetOptionName * @param {String} defaultTargetName */ __constructor: function (targetOptionName, defaultTargetName) { this._targetOptionName = targetOptionName; this._defaultTargetName = defaultTargetName; this._fieldName = '_' + targetOptionName; }, /** * Возвращает имя опции для данной связи. * * @returns {String} */ getTargetOptionName: function () { return this._targetOptionName; }, /** * Сохраняет размаскированное имя цели в поле в инстанции технологии. * * @param {Tech} tech * @param {Node} node */ configureUsages: function (tech, node) { tech[this._fieldName] = node.unmaskTargetName( tech._preprocessTargetName(tech.getOption(this._targetOptionName, this._defaultTargetName)) ); }, /** * Проверяет валидность кэша. * * @param {Tech} tech * @param {Node} node * @param {Cache} cache * @returns {Boolean} */ isCacheValid: function (tech, node, cache) { var targetName = tech[this._fieldName]; var targetPath = node.resolvePath(targetName); return !cache.needRebuildFile('target:' + targetName, targetPath); }, /** * Сохраняет в кэш информацию об использованных файлах. * * @param {Tech} tech * @param {Node} node * @param {Cache} cache */ saveCache: function (tech, node, cache) { var targetName = tech[this._fieldName]; var targetPath = node.resolvePath(targetName); cache.cacheFileInfo('target:' + targetName, targetPath); }, /** * Требует у ноды выполнения необходимой цели. * @param {Tech} tech * @param {Node} node * @returns {Promise} */ requireTarget: function (tech, node) { var _this = this; var processTargetResult = this._processTargetResult; return node.requireSources([tech[this._fieldName]]).spread(function (result) { return processTargetResult.call(_this, tech, node, result); }); }, /** * Возвращает имя поля, в которое связь пишет имя цели. * * @returns {String} */ getFieldName: function () { return this._fieldName; }, _processTargetResult: function (tech, node, result) { return result; } }); /** * Связь технологии с другой целью без получения результата. */ var BuildFlowLinkToTargetNoResult = inherit(BuildFlowLinkToTargetResult, { _processTargetResult: function () { return ''; } }); /** * Связь технологии с абсолютным путем к другой цели. */ var BuildFlowLinkToTargetFilename = inherit(BuildFlowLinkToTargetResult, { _processTargetResult: function (tech, node) { return node.resolvePath(tech[this._fieldName]); } }); /** * Связь технологии с текстовым содержимым файла другой цели. */ var BuildFlowLinkToTargetSource = inherit(BuildFlowLinkToTargetResult, { _processTargetResult: function (tech, node) { return vowFs.read(node.resolvePath(tech[this._fieldName]), 'utf8'); } }); /** * Связь технологии со списком файлов (префиксами). */ var BuildFlowLinkToFileList = inherit(BuildFlowLinkToTargetResult, { /** * @param {String} targetOptionName * @param {String} defaultTargetName * @param {String|Array} defaultSuffixes */ __constructor: function (targetOptionName, defaultTargetName, defaultSuffixes) { this._targetOptionName = targetOptionName; this._defaultTargetName = defaultTargetName; this._fieldName = '_' + targetOptionName; this._listName = '_list' + targetOptionName; this._suffixesOptionName = 'sourceSuffixes'; this._defaultSuffixes = defaultSuffixes; }, /** * Возвращает имя опции для данной связи. * * @returns {String} */ getTargetOptionName: function () { return this._targetOptionName; }, /** * Проверяет валидность кэша. * * @param {Tech} tech * @param {Node} node * @param {Cache} cache * @returns {Boolean} */ isCacheValid: function (tech, node, cache) { var targetName = tech[this._fieldName]; return !cache.needRebuildFileList('target:' + targetName, tech[this._listName]); }, /** * Сохраняет в кэш информацию об использованных файлах. * * @param {Tech} tech * @param {Node} node * @param {Cache} cache */ saveCache: function (tech, node, cache) { var targetName = tech[this._fieldName]; return cache.cacheFileList('target:' + targetName, tech[this._listName]); }, _processTargetResult: function (tech, node, result) { var suffixes = tech.getOption(this._suffixesOptionName) || this._defaultSuffixes; suffixes = (Array.isArray(suffixes) ? suffixes : [suffixes]); tech[this._listName] = result.getBySuffix(suffixes); return tech[this._listName]; } }); /** * Связь технологии со списком директорий (префиксами). */ var BuildFlowLinkToDirList = inherit(BuildFlowLinkToTargetResult, { /** * @param {String} targetOptionName * @param {String} defaultTargetName * @param {String|Array} suffixes */ __constructor: function (targetOptionName, defaultTargetName, suffixes) { this._targetOptionName = targetOptionName; this._defaultTargetName = defaultTargetName; this._fieldName = '_' + targetOptionName; this._listName = '_list' + targetOptionName; this._suffixesOptionName = 'sourceDirSuffixes'; this._suffixes = suffixes; }, /** * Возвращает имя опции для данной связи. * * @returns {String} */ getTargetOptionName: function () { return this._targetOptionName; }, /** * Проверяет валидность кэша. * * @param {Tech} tech * @param {Node} node * @param {Cache} cache * @returns {Boolean} */ isCacheValid: function (tech, node, cache) { var targetName = tech[this._fieldName]; var files = [].concat.apply([], tech[this._listName].map(function (dir) { return dir.files; })); return !cache.needRebuildFileList('target:' + targetName, files); }, /** * Сохраняет в кэш информацию об использованных файлах. * * @param {Tech} tech * @param {Node} node * @param {Cache} cache */ saveCache: function (tech, node, cache) { var targetName = tech[this._fieldName]; var files = [].concat.apply([], tech[this._listName].map(function (dir) { return dir.files; })); return cache.cacheFileList('target:' + targetName, files); }, _processTargetResult: function (tech, node, result) { var suffixes = tech.getOption(this._suffixesOptionName) || this._suffixes; suffixes = Array.isArray(suffixes) ? suffixes : [suffixes]; tech[this._listName] = result.getBySuffix(suffixes); return tech[this._listName]; } }); /** * Связь технологии со списком целей. */ var BuildFlowLinkToTargetList = inherit({ /** * @param {String} targetOptionName * @param {String[]} defaultTargetNames * @param {Function} linkClass */ __constructor: function (targetOptionName, defaultTargetNames, linkClass) { this._targetOptionName = targetOptionName; this._defaultTargetNames = defaultTargetNames || []; this._linkClass = linkClass; this._fieldName = '_' + targetOptionName; this._usagesFieldName = '_usageList_' + targetOptionName; }, /** * Возвращает имя опции для данной связи. * * @returns {String} */ getTargetOptionName: function () { return this._targetOptionName; }, /** * Сохраняет размаскированное имя цели в поле в инстанции технологии. * * @param {Tech} tech * @param {Node} node */ configureUsages: function (tech, node) { var _this = this; var links = []; var targetNames = []; var targetOptionName = this._targetOptionName; var i = 0; tech.getOption(this._targetOptionName, this._defaultTargetNames).forEach(function (targetName) { var link = new _this._linkClass(targetOptionName + '[' + i + ']', targetName); link.configureUsages(tech, node); targetNames.push(tech[link.getFieldName()]); links.push(link); i++; }); tech[this._fieldName] = targetNames; tech[this._usagesFieldName] = links; }, /** * Проверяет валидность кэша. * * @param {Tech} tech * @param {Node} node * @param {Cache} cache * @returns {Boolean} */ isCacheValid: function (tech, node, cache) { var links = tech[this._usagesFieldName]; for (var i = 0, l = links.length; i < l; i++) { if (!links[i].isCacheValid(tech, node, cache)) { return false; } } return true; }, /** * Сохраняет в кэш информацию об использованных файлах. * * @param {Tech} tech * @param {Node} node * @param {Cache} cache */ saveCache: function (tech, node, cache) { tech[this._usagesFieldName].forEach(function (link) { link.saveCache(tech, node, cache); }); }, /** * Требует у ноды выполнения необходимой цели. * @param {Tech} tech * @param {Node} node * @returns {Promise} */ requireTarget: function (tech, node) { return Vow.all(tech[this._usagesFieldName].map(function (link) { return link.requireTarget(tech, node); })); } }); exports.create = function () { return new BuildFlow(); };