UNPKG

atom-nuclide

Version:

A unified developer experience for web and mobile development, built as a suite of features on top of Atom to provide hackability and the support of an active community.

253 lines (198 loc) 6.91 kB
var fs = require('fs'), inherits = require('util').inherits, path = require('path'); var THROW_CONFLICTS = true; function getPluginPath(config) { return (config && config.pluginPath) || path.join(__dirname, '../plugins'); } function PluginError(message) { this.name = 'Plugin Error'; this.message = this.name + ':\n' + message; } inherits(PluginError, Error); function findEq(collection, option, value) { return collection.filter(function(item) { return item[option] == value; })[0]; } function mergeByName(acceptor, donor, name, onConflict) { if (!donor || !donor.length) return; donor.forEach(function(note) { var sameOrigNote = findEq(acceptor, name, note[name]); if (sameOrigNote) { onConflict(sameOrigNote, note); } else { acceptor.push(note); } }, this); } var cachedPlugins = {}; function _setMockPlugins(pluginPath, plugins) { cachedPlugins[pluginPath] = plugins; } function getPlugins(config) { if (!config || !config.plugins) { return []; } var pluginPath = getPluginPath(config); if (cachedPlugins[pluginPath]) { return cachedPlugins[pluginPath]; } var dirlist; try { dirlist = fs.readdirSync(pluginPath); } catch (err) { dirlist = []; } var plugins = []; dirlist.reduce(function(plugins, subdir) { var _path = path.resolve(pluginPath, subdir, 'manifest.json'); var manifest; try { manifest = require(_path); // This excludes situation where we have two plugins with same name. if (subdir !== manifest.name) throw new PluginError('Plugin name in manifest.json is different from npm module name'); } catch (e) { console.error('Corrupted manifest.json in %s plugin\n%s\n%s', subdir, e.message, e.stack); return plugins; } validateManifest(manifest); plugins.push(manifest); return plugins; }, plugins); cachedPlugins[pluginPath] = plugins; return plugins; } function validateManifest(manifest) { manifest.session = manifest.session || {}; manifest.protocol = manifest.protocol || {}; manifest.protocol.domains = manifest.protocol.domains || []; } function InspectorJson(config) { var inspectorJsonPath = path.join(__dirname, '../front-end/inspector.json'), inspectorJson = JSON.parse(fs.readFileSync(inspectorJsonPath)), extendedInspectorJsonPath = path.join(__dirname, '../front-end-node/inspector.json'), extendedInspectorJson = JSON.parse(fs.readFileSync(extendedInspectorJsonPath)); this._config = config; this._notes = inspectorJson; this._merge(extendedInspectorJson); if (!config.plugins) return; var plugins = getPlugins(config); plugins.forEach(function(plugin) { var excludes = (plugin.exclude || []).map(function(name) { return { name: name, type: 'exclude' }; }); var overrides = plugin.override || []; var note = { name: 'plugins/' + plugin.name, type: plugin.type || '' }; var notes = excludes.concat(overrides).concat(note); this._merge(notes); }, this); } InspectorJson.prototype._merge = function(toMergeNotes) { var result = []; toMergeNotes.forEach(function(note) { if (note.type == 'exclude') return; result.push(note); }); this._notes.forEach(function(note) { var exists = findEq(toMergeNotes, 'name', note.name); if (exists) return; result.push(note); }); this._notes = result; }; InspectorJson.prototype.toJSON = function() { return this._notes; }; function ProtocolJson(config) { var protocolJsonPath = path.join(__dirname, '../tools/protocol.json'), protocolJson = JSON.parse(fs.readFileSync(protocolJsonPath)), extendedProtocolJsonPath = path.join(__dirname, '../front-end-node/protocol.json'), extendedProtocolJson = JSON.parse(fs.readFileSync(extendedProtocolJsonPath)); this._config = config; this._protocol = protocolJson; this._domains = protocolJson.domains; this._extendedDomains = extendedProtocolJson.domains; if (config.plugins) { var plugins = getPlugins(config); // At first step we merge all plugins in one protocol. // We expect what plugins doesn't have equal methods, events or types, // otherwise we throw an error, because this is unsolvable situation. plugins.forEach(function(plugin) { this._merge(THROW_CONFLICTS, plugin.name, this._extendedDomains, plugin.protocol.domains); }, this); } // At second step we merge plugins with main protocol. // Plugins can override original methods, events or types, // so we don't need to throw error on conflict, we only print a warning to console. this._merge(!THROW_CONFLICTS, '', this._domains, this._extendedDomains); } ProtocolJson.prototype._merge = function(throwConflicts, pluginName, origDomains, toMergeDomains) { if (!toMergeDomains.length) return; var uniqueName = 'domain', state = { throwConflicts: throwConflicts, plugin: pluginName }; mergeByName( origDomains, toMergeDomains, uniqueName, this._onDomainConflict.bind(this, state)); }; ProtocolJson.prototype._onDomainConflict = function(state, origDomain, toMergeDomain) { state.domain = toMergeDomain.domain; ['commands', 'events', 'types'].forEach(function(section) { // TODO(3y3): types are unique for protocol, not for domain. // We need to register types cache and search in it for conflicts. var uniqueName = section == 'types' ? 'id' : 'name', origSection = origDomain[section], toMergeSection = toMergeDomain[section]; if (!toMergeSection || !toMergeSection.length) return; if (!origSection || !origSection.length) { origDomain[section] = toMergeSection; return; } state.section = section; state.uname = uniqueName; mergeByName( origSection, toMergeSection, uniqueName, this._onItemConflict.bind(this, state)); }, this); }; ProtocolJson.prototype._onItemConflict = function(state, origItem, toMergeItem) { if (state.throwConflicts) { throw new PluginError( 'Unresolved conflict in ' + state.section + ' section of `' + state.plugin + '` plugin: ' + 'item with ' + state.uname + ' `' + toMergeItem[state.uname] + '` already exists.'); } else { console.warn( 'Item with ' + state.uname + ' `' + toMergeItem[state.uname] + '`' + ' in ' + state.section + ' section' + ' of ' + state.domain + ' domain' + ' was owerriden.'); } }; ProtocolJson.prototype.toJSON = function() { return this._protocol; }; function init(config) { module.exports.CWD = getPluginPath(config); } module.exports = { _setMockPlugins: _setMockPlugins, getPluginPath: getPluginPath, getPlugins: getPlugins, validateManifest: validateManifest, PluginError: PluginError, InspectorJson: InspectorJson, ProtocolJson: ProtocolJson, init: init };