mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-04-24 09:26:14 -04:00
lint: Run eslint --fix
on src/
This commit is contained in:
parent
b8d07a42eb
commit
8e5fd19db2
109 changed files with 9061 additions and 10572 deletions
|
@ -1,43 +1,41 @@
|
|||
var $, jQuery;
|
||||
$ = jQuery = require("ep_etherpad-lite/static/js/rjquery").$;
|
||||
var _ = require("underscore");
|
||||
let $, jQuery;
|
||||
$ = jQuery = require('ep_etherpad-lite/static/js/rjquery').$;
|
||||
const _ = require('underscore');
|
||||
|
||||
var pluginUtils = require('./shared');
|
||||
var defs = require('./plugin_defs');
|
||||
const pluginUtils = require('./shared');
|
||||
const defs = require('./plugin_defs');
|
||||
|
||||
exports.baseURL = '';
|
||||
|
||||
exports.ensure = function (cb) {
|
||||
if (!defs.loaded)
|
||||
exports.update(cb);
|
||||
else
|
||||
cb();
|
||||
if (!defs.loaded) exports.update(cb);
|
||||
else cb();
|
||||
};
|
||||
|
||||
exports.update = function (cb) {
|
||||
// It appears that this response (see #620) may interrupt the current thread
|
||||
// of execution on Firefox. This schedules the response in the run-loop,
|
||||
// which appears to fix the issue.
|
||||
var callback = function () {setTimeout(cb, 0);};
|
||||
$.ajaxSetup({ cache: false });
|
||||
jQuery.getJSON(exports.baseURL + 'pluginfw/plugin-definitions.json').done(function(data) {
|
||||
const callback = function () { setTimeout(cb, 0); };
|
||||
$.ajaxSetup({cache: false});
|
||||
jQuery.getJSON(`${exports.baseURL}pluginfw/plugin-definitions.json`).done((data) => {
|
||||
defs.plugins = data.plugins;
|
||||
defs.parts = data.parts;
|
||||
defs.hooks = pluginUtils.extractHooks(defs.parts, "client_hooks");
|
||||
defs.hooks = pluginUtils.extractHooks(defs.parts, 'client_hooks');
|
||||
defs.loaded = true;
|
||||
callback();
|
||||
}).fail(function(e){
|
||||
console.error("Failed to load plugin-definitions: " + err);
|
||||
}).fail((e) => {
|
||||
console.error(`Failed to load plugin-definitions: ${err}`);
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
function adoptPluginsFromAncestorsOf(frame) {
|
||||
// Bind plugins with parent;
|
||||
var parentRequire = null;
|
||||
let parentRequire = null;
|
||||
try {
|
||||
while (frame = frame.parent) {
|
||||
if (typeof (frame.require) !== "undefined") {
|
||||
if (typeof (frame.require) !== 'undefined') {
|
||||
parentRequire = frame.require;
|
||||
break;
|
||||
}
|
||||
|
@ -46,17 +44,17 @@ function adoptPluginsFromAncestorsOf(frame) {
|
|||
// Silence (this can only be a XDomain issue).
|
||||
}
|
||||
if (parentRequire) {
|
||||
var ancestorPluginDefs = parentRequire("ep_etherpad-lite/static/js/pluginfw/plugin_defs");
|
||||
const ancestorPluginDefs = parentRequire('ep_etherpad-lite/static/js/pluginfw/plugin_defs');
|
||||
defs.hooks = ancestorPluginDefs.hooks;
|
||||
defs.loaded = ancestorPluginDefs.loaded;
|
||||
defs.parts = ancestorPluginDefs.parts;
|
||||
defs.plugins = ancestorPluginDefs.plugins;
|
||||
var ancestorPlugins = parentRequire("ep_etherpad-lite/static/js/pluginfw/client_plugins");
|
||||
const ancestorPlugins = parentRequire('ep_etherpad-lite/static/js/pluginfw/client_plugins');
|
||||
exports.baseURL = ancestorPlugins.baseURL;
|
||||
exports.ensure = ancestorPlugins.ensure;
|
||||
exports.update = ancestorPlugins.update;
|
||||
} else {
|
||||
throw new Error("Parent plugins could not be found.")
|
||||
throw new Error('Parent plugins could not be found.');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* global exports, require */
|
||||
|
||||
var _ = require("underscore");
|
||||
var pluginDefs = require('./plugin_defs');
|
||||
const _ = require('underscore');
|
||||
const pluginDefs = require('./plugin_defs');
|
||||
|
||||
// Maps the name of a server-side hook to a string explaining the deprecation
|
||||
// (e.g., 'use the foo hook instead').
|
||||
|
@ -24,26 +24,24 @@ function checkDeprecation(hook) {
|
|||
deprecationWarned[hook.hook_fn_name] = true;
|
||||
}
|
||||
|
||||
exports.bubbleExceptions = true
|
||||
exports.bubbleExceptions = true;
|
||||
|
||||
var hookCallWrapper = function (hook, hook_name, args, cb) {
|
||||
const hookCallWrapper = function (hook, hook_name, args, cb) {
|
||||
if (cb === undefined) cb = function (x) { return x; };
|
||||
|
||||
checkDeprecation(hook);
|
||||
|
||||
// Normalize output to list for both sync and async cases
|
||||
var normalize = function(x) {
|
||||
const normalize = function (x) {
|
||||
if (x === undefined) return [];
|
||||
return x;
|
||||
}
|
||||
var normalizedhook = function () {
|
||||
return normalize(hook.hook_fn(hook_name, args, function (x) {
|
||||
return cb(normalize(x));
|
||||
}));
|
||||
}
|
||||
};
|
||||
const normalizedhook = function () {
|
||||
return normalize(hook.hook_fn(hook_name, args, (x) => cb(normalize(x))));
|
||||
};
|
||||
|
||||
if (exports.bubbleExceptions) {
|
||||
return normalizedhook();
|
||||
return normalizedhook();
|
||||
} else {
|
||||
try {
|
||||
return normalizedhook();
|
||||
|
@ -51,32 +49,32 @@ var hookCallWrapper = function (hook, hook_name, args, cb) {
|
|||
console.error([hook_name, hook.part.full_name, ex.stack || ex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.syncMapFirst = function (lst, fn) {
|
||||
var i;
|
||||
var result;
|
||||
let i;
|
||||
let result;
|
||||
for (i = 0; i < lst.length; i++) {
|
||||
result = fn(lst[i])
|
||||
result = fn(lst[i]);
|
||||
if (result.length) return result;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
exports.mapFirst = function (lst, fn, cb, predicate) {
|
||||
if (predicate == null) predicate = (x) => (x != null && x.length > 0);
|
||||
var i = 0;
|
||||
let i = 0;
|
||||
|
||||
var next = function () {
|
||||
if (i >= lst.length) return cb(null, []);
|
||||
fn(lst[i++], function (err, result) {
|
||||
fn(lst[i++], (err, result) => {
|
||||
if (err) return cb(err);
|
||||
if (predicate(result)) return cb(null, result);
|
||||
next();
|
||||
});
|
||||
}
|
||||
};
|
||||
next();
|
||||
}
|
||||
};
|
||||
|
||||
// Calls the hook function synchronously and returns the value provided by the hook function (via
|
||||
// callback or return value).
|
||||
|
@ -350,11 +348,9 @@ async function callHookFnAsync(hook, context) {
|
|||
exports.aCallAll = async (hookName, context, cb) => {
|
||||
if (context == null) context = {};
|
||||
const hooks = pluginDefs.hooks[hookName] || [];
|
||||
let resultsPromise = Promise.all(hooks.map((hook) => {
|
||||
return callHookFnAsync(hook, context)
|
||||
// `undefined` (but not `null`!) is treated the same as [].
|
||||
.then((result) => (result === undefined) ? [] : result);
|
||||
})).then((results) => _.flatten(results, 1));
|
||||
let resultsPromise = Promise.all(hooks.map((hook) => callHookFnAsync(hook, context)
|
||||
// `undefined` (but not `null`!) is treated the same as [].
|
||||
.then((result) => (result === undefined) ? [] : result))).then((results) => _.flatten(results, 1));
|
||||
if (cb != null) resultsPromise = resultsPromise.then((val) => cb(null, val), cb);
|
||||
return await resultsPromise;
|
||||
};
|
||||
|
@ -362,49 +358,45 @@ exports.aCallAll = async (hookName, context, cb) => {
|
|||
exports.callFirst = function (hook_name, args) {
|
||||
if (!args) args = {};
|
||||
if (pluginDefs.hooks[hook_name] === undefined) return [];
|
||||
return exports.syncMapFirst(pluginDefs.hooks[hook_name], function(hook) {
|
||||
return hookCallWrapper(hook, hook_name, args);
|
||||
});
|
||||
}
|
||||
return exports.syncMapFirst(pluginDefs.hooks[hook_name], (hook) => hookCallWrapper(hook, hook_name, args));
|
||||
};
|
||||
|
||||
function aCallFirst(hook_name, args, cb, predicate) {
|
||||
if (!args) args = {};
|
||||
if (!cb) cb = function () {};
|
||||
if (pluginDefs.hooks[hook_name] === undefined) return cb(null, []);
|
||||
exports.mapFirst(
|
||||
pluginDefs.hooks[hook_name],
|
||||
function (hook, cb) {
|
||||
hookCallWrapper(hook, hook_name, args, function (res) { cb(null, res); });
|
||||
},
|
||||
cb,
|
||||
predicate
|
||||
pluginDefs.hooks[hook_name],
|
||||
(hook, cb) => {
|
||||
hookCallWrapper(hook, hook_name, args, (res) => { cb(null, res); });
|
||||
},
|
||||
cb,
|
||||
predicate,
|
||||
);
|
||||
}
|
||||
|
||||
/* return a Promise if cb is not supplied */
|
||||
exports.aCallFirst = function (hook_name, args, cb, predicate) {
|
||||
if (cb === undefined) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
aCallFirst(hook_name, args, function(err, res) {
|
||||
return err ? reject(err) : resolve(res);
|
||||
}, predicate);
|
||||
return new Promise((resolve, reject) => {
|
||||
aCallFirst(hook_name, args, (err, res) => err ? reject(err) : resolve(res), predicate);
|
||||
});
|
||||
} else {
|
||||
return aCallFirst(hook_name, args, cb, predicate);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.callAllStr = function(hook_name, args, sep, pre, post) {
|
||||
exports.callAllStr = function (hook_name, args, sep, pre, post) {
|
||||
if (sep == undefined) sep = '';
|
||||
if (pre == undefined) pre = '';
|
||||
if (post == undefined) post = '';
|
||||
var newCallhooks = [];
|
||||
var callhooks = exports.callAll(hook_name, args);
|
||||
for (var i = 0, ii = callhooks.length; i < ii; i++) {
|
||||
const newCallhooks = [];
|
||||
const callhooks = exports.callAll(hook_name, args);
|
||||
for (let i = 0, ii = callhooks.length; i < ii; i++) {
|
||||
newCallhooks[i] = pre + callhooks[i] + post;
|
||||
}
|
||||
return newCallhooks.join(sep || "");
|
||||
}
|
||||
return newCallhooks.join(sep || '');
|
||||
};
|
||||
|
||||
exports.exportedForTestingOnly = {
|
||||
callHookFnAsync,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
const log4js = require('log4js');
|
||||
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins");
|
||||
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
|
||||
var npm = require("npm");
|
||||
var request = require("request");
|
||||
const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins');
|
||||
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
const npm = require('npm');
|
||||
const request = require('request');
|
||||
const util = require('util');
|
||||
|
||||
let npmIsLoaded = false;
|
||||
|
@ -13,20 +13,20 @@ const loadNpm = async () => {
|
|||
npm.on('log', log4js.getLogger('npm').log);
|
||||
};
|
||||
|
||||
var tasks = 0
|
||||
let tasks = 0;
|
||||
|
||||
function wrapTaskCb(cb) {
|
||||
tasks++;
|
||||
|
||||
return function() {
|
||||
return function () {
|
||||
cb && cb.apply(this, arguments);
|
||||
tasks--;
|
||||
if (tasks == 0) onAllTasksFinished();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function onAllTasksFinished() {
|
||||
hooks.aCallAll("restartServer", {}, function() {});
|
||||
hooks.aCallAll('restartServer', {}, () => {});
|
||||
}
|
||||
|
||||
exports.uninstall = async (pluginName, cb = null) => {
|
||||
|
@ -58,18 +58,18 @@ exports.install = async (pluginName, cb = null) => {
|
|||
};
|
||||
|
||||
exports.availablePlugins = null;
|
||||
var cacheTimestamp = 0;
|
||||
let cacheTimestamp = 0;
|
||||
|
||||
exports.getAvailablePlugins = function(maxCacheAge) {
|
||||
var nowTimestamp = Math.round(Date.now() / 1000);
|
||||
exports.getAvailablePlugins = function (maxCacheAge) {
|
||||
const nowTimestamp = Math.round(Date.now() / 1000);
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// check cache age before making any request
|
||||
if (exports.availablePlugins && maxCacheAge && (nowTimestamp - cacheTimestamp) <= maxCacheAge) {
|
||||
return resolve(exports.availablePlugins);
|
||||
}
|
||||
|
||||
request("https://static.etherpad.org/plugins.json", function(er, response, plugins) {
|
||||
request('https://static.etherpad.org/plugins.json', (er, response, plugins) => {
|
||||
if (er) return reject(er);
|
||||
|
||||
try {
|
||||
|
@ -87,26 +87,26 @@ exports.getAvailablePlugins = function(maxCacheAge) {
|
|||
};
|
||||
|
||||
|
||||
exports.search = function(searchTerm, maxCacheAge) {
|
||||
return exports.getAvailablePlugins(maxCacheAge).then(function(results) {
|
||||
var res = {};
|
||||
exports.search = function (searchTerm, maxCacheAge) {
|
||||
return exports.getAvailablePlugins(maxCacheAge).then((results) => {
|
||||
const res = {};
|
||||
|
||||
if (searchTerm) {
|
||||
searchTerm = searchTerm.toLowerCase();
|
||||
}
|
||||
|
||||
for (var pluginName in results) {
|
||||
for (const pluginName in results) {
|
||||
// for every available plugin
|
||||
if (pluginName.indexOf(plugins.prefix) != 0) continue; // TODO: Also search in keywords here!
|
||||
|
||||
if (searchTerm && !~results[pluginName].name.toLowerCase().indexOf(searchTerm)
|
||||
&& (typeof results[pluginName].description != "undefined" && !~results[pluginName].description.toLowerCase().indexOf(searchTerm) )
|
||||
) {
|
||||
if (typeof results[pluginName].description === "undefined") {
|
||||
console.debug('plugin without Description: %s', results[pluginName].name);
|
||||
}
|
||||
if (searchTerm && !~results[pluginName].name.toLowerCase().indexOf(searchTerm) &&
|
||||
(typeof results[pluginName].description !== 'undefined' && !~results[pluginName].description.toLowerCase().indexOf(searchTerm))
|
||||
) {
|
||||
if (typeof results[pluginName].description === 'undefined') {
|
||||
console.debug('plugin without Description: %s', results[pluginName].name);
|
||||
}
|
||||
|
||||
continue;
|
||||
continue;
|
||||
}
|
||||
|
||||
res[pluginName] = results[pluginName];
|
||||
|
|
|
@ -1,61 +1,61 @@
|
|||
const fs = require('fs').promises;
|
||||
const hooks = require('./hooks');
|
||||
var npm = require("npm/lib/npm.js");
|
||||
var readInstalled = require("./read-installed.js");
|
||||
var path = require("path");
|
||||
var tsort = require("./tsort");
|
||||
var util = require("util");
|
||||
var _ = require("underscore");
|
||||
var settings = require('../../../node/utils/Settings');
|
||||
const npm = require('npm/lib/npm.js');
|
||||
const readInstalled = require('./read-installed.js');
|
||||
const path = require('path');
|
||||
const tsort = require('./tsort');
|
||||
const util = require('util');
|
||||
const _ = require('underscore');
|
||||
const settings = require('../../../node/utils/Settings');
|
||||
|
||||
var pluginUtils = require('./shared');
|
||||
var defs = require('./plugin_defs');
|
||||
const pluginUtils = require('./shared');
|
||||
const defs = require('./plugin_defs');
|
||||
|
||||
exports.prefix = 'ep_';
|
||||
|
||||
exports.formatPlugins = function () {
|
||||
return _.keys(defs.plugins).join(", ");
|
||||
return _.keys(defs.plugins).join(', ');
|
||||
};
|
||||
|
||||
exports.formatPluginsWithVersion = function () {
|
||||
var plugins = [];
|
||||
_.forEach(defs.plugins, function(plugin) {
|
||||
if(plugin.package.name !== "ep_etherpad-lite"){
|
||||
var pluginStr = plugin.package.name + "@" + plugin.package.version;
|
||||
const plugins = [];
|
||||
_.forEach(defs.plugins, (plugin) => {
|
||||
if (plugin.package.name !== 'ep_etherpad-lite') {
|
||||
const pluginStr = `${plugin.package.name}@${plugin.package.version}`;
|
||||
plugins.push(pluginStr);
|
||||
}
|
||||
});
|
||||
return plugins.join(", ");
|
||||
return plugins.join(', ');
|
||||
};
|
||||
|
||||
exports.formatParts = function () {
|
||||
return _.map(defs.parts, function(part) { return part.full_name; }).join('\n');
|
||||
return _.map(defs.parts, (part) => part.full_name).join('\n');
|
||||
};
|
||||
|
||||
exports.formatHooks = function (hook_set_name) {
|
||||
var res = [];
|
||||
var hooks = pluginUtils.extractHooks(defs.parts, hook_set_name || 'hooks');
|
||||
const res = [];
|
||||
const hooks = pluginUtils.extractHooks(defs.parts, hook_set_name || 'hooks');
|
||||
|
||||
_.chain(hooks).keys().forEach(function (hook_name) {
|
||||
_.forEach(hooks[hook_name], function (hook) {
|
||||
res.push("<dt>" + hook.hook_name + "</dt><dd>" + hook.hook_fn_name + " from " + hook.part.full_name + "</dd>");
|
||||
_.chain(hooks).keys().forEach((hook_name) => {
|
||||
_.forEach(hooks[hook_name], (hook) => {
|
||||
res.push(`<dt>${hook.hook_name}</dt><dd>${hook.hook_fn_name} from ${hook.part.full_name}</dd>`);
|
||||
});
|
||||
});
|
||||
return "<dl>" + res.join("\n") + "</dl>";
|
||||
return `<dl>${res.join('\n')}</dl>`;
|
||||
};
|
||||
|
||||
const callInit = async () => {
|
||||
await Promise.all(Object.keys(defs.plugins).map(async (plugin_name) => {
|
||||
let plugin = defs.plugins[plugin_name];
|
||||
let ep_init = path.normalize(path.join(plugin.package.path, ".ep_initialized"));
|
||||
const plugin = defs.plugins[plugin_name];
|
||||
const ep_init = path.normalize(path.join(plugin.package.path, '.ep_initialized'));
|
||||
try {
|
||||
await fs.stat(ep_init);
|
||||
} catch (err) {
|
||||
await fs.writeFile(ep_init, 'done');
|
||||
await hooks.aCallAll("init_" + plugin_name, {});
|
||||
await hooks.aCallAll(`init_${plugin_name}`, {});
|
||||
}
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
exports.pathNormalization = function (part, hook_fn_name, hook_name) {
|
||||
const tmp = hook_fn_name.split(':'); // hook_fn_name might be something like 'C:\\foo.js:myFunc'.
|
||||
|
@ -65,32 +65,32 @@ exports.pathNormalization = function (part, hook_fn_name, hook_name) {
|
|||
const packageDir = path.dirname(defs.plugins[part.plugin].package.path);
|
||||
const fileName = path.normalize(path.join(packageDir, moduleName));
|
||||
return `${fileName}:${functionName}`;
|
||||
}
|
||||
};
|
||||
|
||||
exports.update = async function () {
|
||||
let packages = await exports.getPackages();
|
||||
var parts = {}; // Key is full name. sortParts converts this into a topologically sorted array.
|
||||
var plugins = {};
|
||||
const packages = await exports.getPackages();
|
||||
const parts = {}; // Key is full name. sortParts converts this into a topologically sorted array.
|
||||
const plugins = {};
|
||||
|
||||
// Load plugin metadata ep.json
|
||||
await Promise.all(Object.keys(packages).map(
|
||||
async (pluginName) => await loadPlugin(packages, pluginName, plugins, parts)));
|
||||
async (pluginName) => await loadPlugin(packages, pluginName, plugins, parts)));
|
||||
|
||||
defs.plugins = plugins;
|
||||
defs.parts = sortParts(parts);
|
||||
defs.hooks = pluginUtils.extractHooks(defs.parts, 'hooks', exports.pathNormalization);
|
||||
defs.loaded = true;
|
||||
await callInit();
|
||||
}
|
||||
};
|
||||
|
||||
exports.getPackages = async function () {
|
||||
// Load list of installed NPM packages, flatten it to a list, and filter out only packages with names that
|
||||
var dir = settings.root;
|
||||
let data = await util.promisify(readInstalled)(dir);
|
||||
const dir = settings.root;
|
||||
const data = await util.promisify(readInstalled)(dir);
|
||||
|
||||
var packages = {};
|
||||
const packages = {};
|
||||
function flatten(deps) {
|
||||
_.chain(deps).keys().each(function (name) {
|
||||
_.chain(deps).keys().each((name) => {
|
||||
if (name.indexOf(exports.prefix) === 0) {
|
||||
packages[name] = _.clone(deps[name]);
|
||||
// Delete anything that creates loops so that the plugin
|
||||
|
@ -100,48 +100,48 @@ exports.getPackages = async function () {
|
|||
}
|
||||
|
||||
// I don't think we need recursion
|
||||
//if (deps[name].dependencies !== undefined) flatten(deps[name].dependencies);
|
||||
// if (deps[name].dependencies !== undefined) flatten(deps[name].dependencies);
|
||||
});
|
||||
}
|
||||
|
||||
var tmp = {};
|
||||
const tmp = {};
|
||||
tmp[data.name] = data;
|
||||
flatten(tmp[data.name].dependencies);
|
||||
return packages;
|
||||
};
|
||||
|
||||
async function loadPlugin(packages, plugin_name, plugins, parts) {
|
||||
var plugin_path = path.resolve(packages[plugin_name].path, "ep.json");
|
||||
const plugin_path = path.resolve(packages[plugin_name].path, 'ep.json');
|
||||
try {
|
||||
let data = await fs.readFile(plugin_path);
|
||||
const data = await fs.readFile(plugin_path);
|
||||
try {
|
||||
var plugin = JSON.parse(data);
|
||||
plugin['package'] = packages[plugin_name];
|
||||
const plugin = JSON.parse(data);
|
||||
plugin.package = packages[plugin_name];
|
||||
plugins[plugin_name] = plugin;
|
||||
_.each(plugin.parts, function (part) {
|
||||
_.each(plugin.parts, (part) => {
|
||||
part.plugin = plugin_name;
|
||||
part.full_name = plugin_name + "/" + part.name;
|
||||
part.full_name = `${plugin_name}/${part.name}`;
|
||||
parts[part.full_name] = part;
|
||||
});
|
||||
} catch (ex) {
|
||||
console.error("Unable to parse plugin definition file " + plugin_path + ": " + ex.toString());
|
||||
console.error(`Unable to parse plugin definition file ${plugin_path}: ${ex.toString()}`);
|
||||
}
|
||||
} catch (er) {
|
||||
console.error("Unable to load plugin definition file " + plugin_path);
|
||||
console.error(`Unable to load plugin definition file ${plugin_path}`);
|
||||
}
|
||||
}
|
||||
|
||||
function partsToParentChildList(parts) {
|
||||
var res = [];
|
||||
_.chain(parts).keys().forEach(function (name) {
|
||||
_.each(parts[name].post || [], function (child_name) {
|
||||
const res = [];
|
||||
_.chain(parts).keys().forEach((name) => {
|
||||
_.each(parts[name].post || [], (child_name) => {
|
||||
res.push([name, child_name]);
|
||||
});
|
||||
_.each(parts[name].pre || [], function (parent_name) {
|
||||
_.each(parts[name].pre || [], (parent_name) => {
|
||||
res.push([parent_name, name]);
|
||||
});
|
||||
if (!parts[name].pre && !parts[name].post) {
|
||||
res.push([name, ":" + name]); // Include apps with no dependency info
|
||||
res.push([name, `:${name}`]); // Include apps with no dependency info
|
||||
}
|
||||
});
|
||||
return res;
|
||||
|
@ -150,10 +150,10 @@ function partsToParentChildList(parts) {
|
|||
// Used only in Node, so no need for _
|
||||
function sortParts(parts) {
|
||||
return tsort(
|
||||
partsToParentChildList(parts)
|
||||
partsToParentChildList(parts),
|
||||
).filter(
|
||||
function (name) { return parts[name] !== undefined; }
|
||||
(name) => parts[name] !== undefined,
|
||||
).map(
|
||||
function (name) { return parts[name]; }
|
||||
(name) => parts[name],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -89,248 +89,251 @@ as far as the left-most node_modules folder.
|
|||
*/
|
||||
|
||||
|
||||
var npm = require("npm/lib/npm.js")
|
||||
, fs = require("graceful-fs")
|
||||
, path = require("path")
|
||||
, asyncMap = require("slide").asyncMap
|
||||
, semver = require("semver")
|
||||
, log = require("log4js").getLogger('pluginfw')
|
||||
const npm = require('npm/lib/npm.js');
|
||||
const fs = require('graceful-fs');
|
||||
const path = require('path');
|
||||
const asyncMap = require('slide').asyncMap;
|
||||
const semver = require('semver');
|
||||
const log = require('log4js').getLogger('pluginfw');
|
||||
|
||||
function readJson(file, callback) {
|
||||
fs.readFile(file, function(er, buf) {
|
||||
if(er) {
|
||||
fs.readFile(file, (er, buf) => {
|
||||
if (er) {
|
||||
callback(er);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
callback( null, JSON.parse(buf.toString()) )
|
||||
} catch(er) {
|
||||
callback(er)
|
||||
callback(null, JSON.parse(buf.toString()));
|
||||
} catch (er) {
|
||||
callback(er);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = readInstalled
|
||||
module.exports = readInstalled;
|
||||
|
||||
function readInstalled (folder, cb) {
|
||||
function readInstalled(folder, cb) {
|
||||
/* This is where we clear the cache, these three lines are all the
|
||||
* new code there is */
|
||||
rpSeen = {};
|
||||
riSeen = [];
|
||||
var fuSeen = [];
|
||||
const fuSeen = [];
|
||||
|
||||
var d = npm.config.get("depth")
|
||||
readInstalled_(folder, null, null, null, 0, d, function (er, obj) {
|
||||
if (er) return cb(er)
|
||||
const d = npm.config.get('depth');
|
||||
readInstalled_(folder, null, null, null, 0, d, (er, obj) => {
|
||||
if (er) return cb(er);
|
||||
// now obj has all the installed things, where they're installed
|
||||
// figure out the inheritance links, now that the object is built.
|
||||
resolveInheritance(obj)
|
||||
cb(null, obj)
|
||||
})
|
||||
resolveInheritance(obj);
|
||||
cb(null, obj);
|
||||
});
|
||||
}
|
||||
|
||||
var rpSeen = {}
|
||||
function readInstalled_ (folder, parent, name, reqver, depth, maxDepth, cb) {
|
||||
//console.error(folder, name)
|
||||
var rpSeen = {};
|
||||
function readInstalled_(folder, parent, name, reqver, depth, maxDepth, cb) {
|
||||
// console.error(folder, name)
|
||||
|
||||
var installed
|
||||
, obj
|
||||
, real
|
||||
, link
|
||||
let installed,
|
||||
obj,
|
||||
real,
|
||||
link;
|
||||
|
||||
fs.readdir(path.resolve(folder, "node_modules"), function (er, i) {
|
||||
fs.readdir(path.resolve(folder, 'node_modules'), (er, i) => {
|
||||
// error indicates that nothing is installed here
|
||||
if (er) i = []
|
||||
installed = i.filter(function (f) { return f.charAt(0) !== "." })
|
||||
next()
|
||||
})
|
||||
if (er) i = [];
|
||||
installed = i.filter((f) => f.charAt(0) !== '.');
|
||||
next();
|
||||
});
|
||||
|
||||
readJson(path.resolve(folder, "package.json"), function (er, data) {
|
||||
obj = copy(data)
|
||||
readJson(path.resolve(folder, 'package.json'), (er, data) => {
|
||||
obj = copy(data);
|
||||
|
||||
if (!parent) {
|
||||
obj = obj || true
|
||||
er = null
|
||||
obj = obj || true;
|
||||
er = null;
|
||||
}
|
||||
return next(er)
|
||||
})
|
||||
return next(er);
|
||||
});
|
||||
|
||||
fs.lstat(folder, function (er, st) {
|
||||
fs.lstat(folder, (er, st) => {
|
||||
if (er) {
|
||||
if (!parent) real = true
|
||||
return next(er)
|
||||
if (!parent) real = true;
|
||||
return next(er);
|
||||
}
|
||||
fs.realpath(folder, function (er, rp) {
|
||||
//console.error("realpath(%j) = %j", folder, rp)
|
||||
real = rp
|
||||
if (st.isSymbolicLink()) link = rp
|
||||
next(er)
|
||||
})
|
||||
})
|
||||
fs.realpath(folder, (er, rp) => {
|
||||
// console.error("realpath(%j) = %j", folder, rp)
|
||||
real = rp;
|
||||
if (st.isSymbolicLink()) link = rp;
|
||||
next(er);
|
||||
});
|
||||
});
|
||||
|
||||
var errState = null
|
||||
, called = false
|
||||
function next (er) {
|
||||
if (errState) return
|
||||
let errState = null;
|
||||
let called = false;
|
||||
function next(er) {
|
||||
if (errState) return;
|
||||
if (er) {
|
||||
errState = er
|
||||
return cb(null, [])
|
||||
errState = er;
|
||||
return cb(null, []);
|
||||
}
|
||||
//console.error('next', installed, obj && typeof obj, name, real)
|
||||
if (!installed || !obj || !real || called) return
|
||||
called = true
|
||||
if (rpSeen[real]) return cb(null, rpSeen[real])
|
||||
// console.error('next', installed, obj && typeof obj, name, real)
|
||||
if (!installed || !obj || !real || called) return;
|
||||
called = true;
|
||||
if (rpSeen[real]) return cb(null, rpSeen[real]);
|
||||
if (obj === true) {
|
||||
obj = {dependencies:{}, path:folder}
|
||||
installed.forEach(function (i) { obj.dependencies[i] = "*" })
|
||||
obj = {dependencies: {}, path: folder};
|
||||
installed.forEach((i) => { obj.dependencies[i] = '*'; });
|
||||
}
|
||||
if (name && obj.name !== name) obj.invalid = true
|
||||
obj.realName = name || obj.name
|
||||
obj.dependencies = obj.dependencies || {}
|
||||
if (name && obj.name !== name) obj.invalid = true;
|
||||
obj.realName = name || obj.name;
|
||||
obj.dependencies = obj.dependencies || {};
|
||||
|
||||
// "foo":"http://blah" is always presumed valid
|
||||
if (reqver
|
||||
&& semver.validRange(reqver)
|
||||
&& !semver.satisfies(obj.version, reqver)) {
|
||||
obj.invalid = true
|
||||
if (reqver &&
|
||||
semver.validRange(reqver) &&
|
||||
!semver.satisfies(obj.version, reqver)) {
|
||||
obj.invalid = true;
|
||||
}
|
||||
|
||||
if (parent
|
||||
&& !(name in parent.dependencies)
|
||||
&& !(name in (parent.devDependencies || {}))) {
|
||||
obj.extraneous = true
|
||||
if (parent &&
|
||||
!(name in parent.dependencies) &&
|
||||
!(name in (parent.devDependencies || {}))) {
|
||||
obj.extraneous = true;
|
||||
}
|
||||
obj.path = obj.path || folder
|
||||
obj.realPath = real
|
||||
obj.link = link
|
||||
if (parent && !obj.link) obj.parent = parent
|
||||
rpSeen[real] = obj
|
||||
obj.depth = depth
|
||||
if (depth >= maxDepth) return cb(null, obj)
|
||||
asyncMap(installed, function (pkg, cb) {
|
||||
var rv = obj.dependencies[pkg]
|
||||
if (!rv && obj.devDependencies) rv = obj.devDependencies[pkg]
|
||||
readInstalled_( path.resolve(folder, "node_modules/"+pkg)
|
||||
, obj, pkg, obj.dependencies[pkg], depth + 1, maxDepth
|
||||
, cb )
|
||||
}, function (er, installedData) {
|
||||
if (er) return cb(er)
|
||||
installedData.forEach(function (dep) {
|
||||
obj.dependencies[dep.realName] = dep
|
||||
})
|
||||
obj.path = obj.path || folder;
|
||||
obj.realPath = real;
|
||||
obj.link = link;
|
||||
if (parent && !obj.link) obj.parent = parent;
|
||||
rpSeen[real] = obj;
|
||||
obj.depth = depth;
|
||||
if (depth >= maxDepth) return cb(null, obj);
|
||||
asyncMap(installed, (pkg, cb) => {
|
||||
let rv = obj.dependencies[pkg];
|
||||
if (!rv && obj.devDependencies) rv = obj.devDependencies[pkg];
|
||||
readInstalled_(path.resolve(folder, `node_modules/${pkg}`)
|
||||
, obj, pkg, obj.dependencies[pkg], depth + 1, maxDepth
|
||||
, cb);
|
||||
}, (er, installedData) => {
|
||||
if (er) return cb(er);
|
||||
installedData.forEach((dep) => {
|
||||
obj.dependencies[dep.realName] = dep;
|
||||
});
|
||||
|
||||
// any strings here are unmet things. however, if it's
|
||||
// optional, then that's fine, so just delete it.
|
||||
if (obj.optionalDependencies) {
|
||||
Object.keys(obj.optionalDependencies).forEach(function (dep) {
|
||||
if (typeof obj.dependencies[dep] === "string") {
|
||||
delete obj.dependencies[dep]
|
||||
Object.keys(obj.optionalDependencies).forEach((dep) => {
|
||||
if (typeof obj.dependencies[dep] === 'string') {
|
||||
delete obj.dependencies[dep];
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
return cb(null, obj)
|
||||
})
|
||||
return cb(null, obj);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// starting from a root object, call findUnmet on each layer of children
|
||||
var riSeen = []
|
||||
function resolveInheritance (obj) {
|
||||
if (typeof obj !== "object") return
|
||||
if (riSeen.indexOf(obj) !== -1) return
|
||||
riSeen.push(obj)
|
||||
if (typeof obj.dependencies !== "object") {
|
||||
obj.dependencies = {}
|
||||
var riSeen = [];
|
||||
function resolveInheritance(obj) {
|
||||
if (typeof obj !== 'object') return;
|
||||
if (riSeen.indexOf(obj) !== -1) return;
|
||||
riSeen.push(obj);
|
||||
if (typeof obj.dependencies !== 'object') {
|
||||
obj.dependencies = {};
|
||||
}
|
||||
Object.keys(obj.dependencies).forEach(function (dep) {
|
||||
findUnmet(obj.dependencies[dep])
|
||||
})
|
||||
Object.keys(obj.dependencies).forEach(function (dep) {
|
||||
resolveInheritance(obj.dependencies[dep])
|
||||
})
|
||||
Object.keys(obj.dependencies).forEach((dep) => {
|
||||
findUnmet(obj.dependencies[dep]);
|
||||
});
|
||||
Object.keys(obj.dependencies).forEach((dep) => {
|
||||
resolveInheritance(obj.dependencies[dep]);
|
||||
});
|
||||
}
|
||||
|
||||
// find unmet deps by walking up the tree object.
|
||||
// No I/O
|
||||
var fuSeen = []
|
||||
function findUnmet (obj) {
|
||||
if (fuSeen.indexOf(obj) !== -1) return
|
||||
fuSeen.push(obj)
|
||||
//console.error("find unmet", obj.name, obj.parent && obj.parent.name)
|
||||
var deps = obj.dependencies = obj.dependencies || {}
|
||||
//console.error(deps)
|
||||
const fuSeen = [];
|
||||
function findUnmet(obj) {
|
||||
if (fuSeen.indexOf(obj) !== -1) return;
|
||||
fuSeen.push(obj);
|
||||
// console.error("find unmet", obj.name, obj.parent && obj.parent.name)
|
||||
const deps = obj.dependencies = obj.dependencies || {};
|
||||
// console.error(deps)
|
||||
Object.keys(deps)
|
||||
.filter(function (d) { return typeof deps[d] === "string" })
|
||||
.forEach(function (d) {
|
||||
//console.error("find unmet", obj.name, d, deps[d])
|
||||
var r = obj.parent
|
||||
, found = null
|
||||
while (r && !found && typeof deps[d] === "string") {
|
||||
.filter((d) => typeof deps[d] === 'string')
|
||||
.forEach((d) => {
|
||||
// console.error("find unmet", obj.name, d, deps[d])
|
||||
let r = obj.parent;
|
||||
let found = null;
|
||||
while (r && !found && typeof deps[d] === 'string') {
|
||||
// if r is a valid choice, then use that.
|
||||
found = r.dependencies[d]
|
||||
if (!found && r.realName === d) found = r
|
||||
found = r.dependencies[d];
|
||||
if (!found && r.realName === d) found = r;
|
||||
|
||||
if (!found) {
|
||||
r = r.link ? null : r.parent
|
||||
continue
|
||||
}
|
||||
if ( typeof deps[d] === "string"
|
||||
&& !semver.satisfies(found.version, deps[d])) {
|
||||
if (!found) {
|
||||
r = r.link ? null : r.parent;
|
||||
continue;
|
||||
}
|
||||
if (typeof deps[d] === 'string' &&
|
||||
!semver.satisfies(found.version, deps[d])) {
|
||||
// the bad thing will happen
|
||||
log.warn(obj.path + " requires "+d+"@'"+deps[d]
|
||||
+"' but will load\n"
|
||||
+found.path+",\nwhich is version "+found.version
|
||||
,"unmet dependency")
|
||||
found.invalid = true
|
||||
log.warn(`${obj.path} requires ${d}@'${deps[d]
|
||||
}' but will load\n${
|
||||
found.path},\nwhich is version ${found.version}`
|
||||
, 'unmet dependency');
|
||||
found.invalid = true;
|
||||
}
|
||||
deps[d] = found;
|
||||
}
|
||||
deps[d] = found
|
||||
}
|
||||
|
||||
})
|
||||
return obj
|
||||
});
|
||||
return obj;
|
||||
}
|
||||
|
||||
function copy (obj) {
|
||||
if (!obj || typeof obj !== 'object') return obj
|
||||
if (Array.isArray(obj)) return obj.map(copy)
|
||||
function copy(obj) {
|
||||
if (!obj || typeof obj !== 'object') return obj;
|
||||
if (Array.isArray(obj)) return obj.map(copy);
|
||||
|
||||
var o = {}
|
||||
for (var i in obj) o[i] = copy(obj[i])
|
||||
return o
|
||||
const o = {};
|
||||
for (const i in obj) o[i] = copy(obj[i]);
|
||||
return o;
|
||||
}
|
||||
|
||||
if (module === require.main) {
|
||||
var util = require("util")
|
||||
console.error("testing")
|
||||
const util = require('util');
|
||||
console.error('testing');
|
||||
|
||||
var called = 0
|
||||
readInstalled(process.cwd(), function (er, map) {
|
||||
console.error(called ++)
|
||||
if (er) return console.error(er.stack || er.message)
|
||||
cleanup(map)
|
||||
console.error(util.inspect(map, true, 10, true))
|
||||
})
|
||||
let called = 0;
|
||||
readInstalled(process.cwd(), (er, map) => {
|
||||
console.error(called++);
|
||||
if (er) return console.error(er.stack || er.message);
|
||||
cleanup(map);
|
||||
console.error(util.inspect(map, true, 10, true));
|
||||
});
|
||||
|
||||
var seen = []
|
||||
function cleanup (map) {
|
||||
if (seen.indexOf(map) !== -1) return
|
||||
seen.push(map)
|
||||
for (var i in map) switch (i) {
|
||||
case "_id":
|
||||
case "path":
|
||||
case "extraneous": case "invalid":
|
||||
case "dependencies": case "name":
|
||||
continue
|
||||
default: delete map[i]
|
||||
}
|
||||
var dep = map.dependencies
|
||||
// delete map.dependencies
|
||||
if (dep) {
|
||||
// map.dependencies = dep
|
||||
for (var i in dep) if (typeof dep[i] === "object") {
|
||||
cleanup(dep[i])
|
||||
const seen = [];
|
||||
function cleanup(map) {
|
||||
if (seen.indexOf(map) !== -1) return;
|
||||
seen.push(map);
|
||||
for (var i in map) {
|
||||
switch (i) {
|
||||
case '_id':
|
||||
case 'path':
|
||||
case 'extraneous': case 'invalid':
|
||||
case 'dependencies': case 'name':
|
||||
continue;
|
||||
default: delete map[i];
|
||||
}
|
||||
}
|
||||
return map
|
||||
const dep = map.dependencies;
|
||||
// delete map.dependencies
|
||||
if (dep) {
|
||||
// map.dependencies = dep
|
||||
for (var i in dep) {
|
||||
if (typeof dep[i] === 'object') {
|
||||
cleanup(dep[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
var _ = require("underscore");
|
||||
var defs = require('./plugin_defs');
|
||||
const _ = require('underscore');
|
||||
const defs = require('./plugin_defs');
|
||||
|
||||
const disabledHookReasons = {
|
||||
hooks: {
|
||||
|
@ -9,70 +9,70 @@ const disabledHookReasons = {
|
|||
};
|
||||
|
||||
function loadFn(path, hookName) {
|
||||
var functionName
|
||||
, parts = path.split(":");
|
||||
let functionName;
|
||||
const parts = path.split(':');
|
||||
|
||||
// on windows: C:\foo\bar:xyz
|
||||
if (parts[0].length == 1) {
|
||||
if (parts.length == 3) {
|
||||
functionName = parts.pop();
|
||||
}
|
||||
path = parts.join(":");
|
||||
path = parts.join(':');
|
||||
} else {
|
||||
path = parts[0];
|
||||
functionName = parts[1];
|
||||
}
|
||||
|
||||
var fn = require(path);
|
||||
let fn = require(path);
|
||||
functionName = functionName ? functionName : hookName;
|
||||
|
||||
_.each(functionName.split("."), function (name) {
|
||||
_.each(functionName.split('.'), (name) => {
|
||||
fn = fn[name];
|
||||
});
|
||||
return fn;
|
||||
};
|
||||
}
|
||||
|
||||
function extractHooks(parts, hook_set_name, normalizer) {
|
||||
var hooks = {};
|
||||
_.each(parts,function (part) {
|
||||
const hooks = {};
|
||||
_.each(parts, (part) => {
|
||||
_.chain(part[hook_set_name] || {})
|
||||
.keys()
|
||||
.each(function (hook_name) {
|
||||
var hook_fn_name = part[hook_set_name][hook_name];
|
||||
.keys()
|
||||
.each((hook_name) => {
|
||||
let hook_fn_name = part[hook_set_name][hook_name];
|
||||
|
||||
/* On the server side, you can't just
|
||||
/* On the server side, you can't just
|
||||
* require("pluginname/whatever") if the plugin is installed as
|
||||
* a dependency of another plugin! Bah, pesky little details of
|
||||
* npm... */
|
||||
if (normalizer) {
|
||||
hook_fn_name = normalizer(part, hook_fn_name, hook_name);
|
||||
}
|
||||
if (normalizer) {
|
||||
hook_fn_name = normalizer(part, hook_fn_name, hook_name);
|
||||
}
|
||||
|
||||
const disabledReason = (disabledHookReasons[hook_set_name] || {})[hook_name];
|
||||
if (disabledReason) {
|
||||
console.error(`Hook ${hook_set_name}/${hook_name} is disabled. Reason: ${disabledReason}`);
|
||||
console.error(`The hook function ${hook_fn_name} from plugin ${part.plugin} ` +
|
||||
const disabledReason = (disabledHookReasons[hook_set_name] || {})[hook_name];
|
||||
if (disabledReason) {
|
||||
console.error(`Hook ${hook_set_name}/${hook_name} is disabled. Reason: ${disabledReason}`);
|
||||
console.error(`The hook function ${hook_fn_name} from plugin ${part.plugin} ` +
|
||||
'will never be called, which may cause the plugin to fail');
|
||||
console.error(`Please update the ${part.plugin} plugin to not use the ${hook_name} hook`);
|
||||
return;
|
||||
}
|
||||
console.error(`Please update the ${part.plugin} plugin to not use the ${hook_name} hook`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var hook_fn = loadFn(hook_fn_name, hook_name);
|
||||
if (!hook_fn) {
|
||||
throw "Not a function";
|
||||
}
|
||||
} catch (exc) {
|
||||
console.error("Failed to load '" + hook_fn_name + "' for '" + part.full_name + "/" + hook_set_name + "/" + hook_name + "': " + exc.toString())
|
||||
}
|
||||
if (hook_fn) {
|
||||
if (hooks[hook_name] == null) hooks[hook_name] = [];
|
||||
hooks[hook_name].push({"hook_name": hook_name, "hook_fn": hook_fn, "hook_fn_name": hook_fn_name, "part": part});
|
||||
}
|
||||
});
|
||||
try {
|
||||
var hook_fn = loadFn(hook_fn_name, hook_name);
|
||||
if (!hook_fn) {
|
||||
throw 'Not a function';
|
||||
}
|
||||
} catch (exc) {
|
||||
console.error(`Failed to load '${hook_fn_name}' for '${part.full_name}/${hook_set_name}/${hook_name}': ${exc.toString()}`);
|
||||
}
|
||||
if (hook_fn) {
|
||||
if (hooks[hook_name] == null) hooks[hook_name] = [];
|
||||
hooks[hook_name].push({hook_name, hook_fn, hook_fn_name, part});
|
||||
}
|
||||
});
|
||||
});
|
||||
return hooks;
|
||||
};
|
||||
}
|
||||
|
||||
exports.extractHooks = extractHooks;
|
||||
|
||||
|
@ -88,12 +88,12 @@ exports.extractHooks = extractHooks;
|
|||
* No plugins: []
|
||||
* Some plugins: [ 'ep_adminpads', 'ep_add_buttons', 'ep_activepads' ]
|
||||
*/
|
||||
exports.clientPluginNames = function() {
|
||||
var client_plugin_names = _.uniq(
|
||||
defs.parts
|
||||
.filter(function(part) { return part.hasOwnProperty('client_hooks'); })
|
||||
.map(function(part) { return 'plugin-' + part['plugin']; })
|
||||
exports.clientPluginNames = function () {
|
||||
const client_plugin_names = _.uniq(
|
||||
defs.parts
|
||||
.filter((part) => part.hasOwnProperty('client_hooks'))
|
||||
.map((part) => `plugin-${part.plugin}`),
|
||||
);
|
||||
|
||||
return client_plugin_names;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -8,27 +8,28 @@
|
|||
**/
|
||||
|
||||
function tsort(edges) {
|
||||
var nodes = {}, // hash: stringified id of the node => { id: id, afters: lisf of ids }
|
||||
sorted = [], // sorted list of IDs ( returned value )
|
||||
visited = {}; // hash: id of already visited node => true
|
||||
const nodes = {}; // hash: stringified id of the node => { id: id, afters: lisf of ids }
|
||||
const sorted = []; // sorted list of IDs ( returned value )
|
||||
const visited = {}; // hash: id of already visited node => true
|
||||
|
||||
var Node = function(id) {
|
||||
const Node = function (id) {
|
||||
this.id = id;
|
||||
this.afters = [];
|
||||
}
|
||||
};
|
||||
|
||||
// 1. build data structures
|
||||
edges.forEach(function(v) {
|
||||
var from = v[0], to = v[1];
|
||||
edges.forEach((v) => {
|
||||
const from = v[0]; const
|
||||
to = v[1];
|
||||
if (!nodes[from]) nodes[from] = new Node(from);
|
||||
if (!nodes[to]) nodes[to] = new Node(to);
|
||||
if (!nodes[to]) nodes[to] = new Node(to);
|
||||
nodes[from].afters.push(to);
|
||||
});
|
||||
|
||||
// 2. topological sort
|
||||
Object.keys(nodes).forEach(function visit(idstr, ancestors) {
|
||||
var node = nodes[idstr],
|
||||
id = node.id;
|
||||
const node = nodes[idstr];
|
||||
const id = node.id;
|
||||
|
||||
// if already exists, do nothing
|
||||
if (visited[idstr]) return;
|
||||
|
@ -39,11 +40,11 @@ function tsort(edges) {
|
|||
|
||||
visited[idstr] = true;
|
||||
|
||||
node.afters.forEach(function(afterID) {
|
||||
if (ancestors.indexOf(afterID) >= 0) // if already in ancestors, a closed chain exists.
|
||||
throw new Error('closed chain : ' + afterID + ' is in ' + id);
|
||||
node.afters.forEach((afterID) => {
|
||||
if (ancestors.indexOf(afterID) >= 0) // if already in ancestors, a closed chain exists.
|
||||
{ throw new Error(`closed chain : ${afterID} is in ${id}`); }
|
||||
|
||||
visit(afterID.toString(), ancestors.map(function(v) { return v })); // recursive call
|
||||
visit(afterID.toString(), ancestors.map((v) => v)); // recursive call
|
||||
});
|
||||
|
||||
sorted.unshift(id);
|
||||
|
@ -56,57 +57,55 @@ function tsort(edges) {
|
|||
* TEST
|
||||
**/
|
||||
function tsortTest() {
|
||||
|
||||
// example 1: success
|
||||
var edges = [
|
||||
let edges = [
|
||||
[1, 2],
|
||||
[1, 3],
|
||||
[2, 4],
|
||||
[3, 4]
|
||||
[3, 4],
|
||||
];
|
||||
|
||||
var sorted = tsort(edges);
|
||||
let sorted = tsort(edges);
|
||||
console.log(sorted);
|
||||
|
||||
// example 2: failure ( A > B > C > A )
|
||||
edges = [
|
||||
['A', 'B'],
|
||||
['B', 'C'],
|
||||
['C', 'A']
|
||||
['C', 'A'],
|
||||
];
|
||||
|
||||
try {
|
||||
sorted = tsort(edges);
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
console.log(e.message);
|
||||
}
|
||||
|
||||
// example 3: generate random edges
|
||||
var max = 100, iteration = 30;
|
||||
const max = 100; const
|
||||
iteration = 30;
|
||||
function randomInt(max) {
|
||||
return Math.floor(Math.random() * max) + 1;
|
||||
}
|
||||
|
||||
edges = (function() {
|
||||
var ret = [], i = 0;
|
||||
while (i++ < iteration) ret.push( [randomInt(max), randomInt(max)] );
|
||||
edges = (function () {
|
||||
const ret = []; let
|
||||
i = 0;
|
||||
while (i++ < iteration) ret.push([randomInt(max), randomInt(max)]);
|
||||
return ret;
|
||||
})();
|
||||
|
||||
try {
|
||||
sorted = tsort(edges);
|
||||
console.log("succeeded", sorted);
|
||||
console.log('succeeded', sorted);
|
||||
} catch (e) {
|
||||
console.log('failed', e.message);
|
||||
}
|
||||
catch (e) {
|
||||
console.log("failed", e.message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// for node.js
|
||||
if (typeof exports == 'object' && exports === this) {
|
||||
if (typeof exports === 'object' && exports === this) {
|
||||
module.exports = tsort;
|
||||
if (process.argv[1] === __filename) tsortTest();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue