diff --git a/node/pluginfw/plugins.js b/node/pluginfw/plugins.js new file mode 100644 index 000000000..39cdffa65 --- /dev/null +++ b/node/pluginfw/plugins.js @@ -0,0 +1,82 @@ +var npm = require("npm/lib/npm.js"); +var readInstalled = require("npm/lib/utils/read-installed.js"); +var relativize = require("npm/lib/utils/relativize.js"); +var readJson = require("npm/lib/utils/read-json.js"); +var path = require("path"); +var async = require("async"); +var fs = require("fs"); +var tsort = require("./tsort"); + +var PLUGIN_PREFIX = 'pluginomatic_'; + +exports.getPlugins = function (cb, prefix) { + prefix = prefix || PLUGIN_PREFIX; + + // Load list of installed NPM packages, flatten it to a list, and filter out only packages with names that + // ../.. and not just .. because current dir is like ETHERPAD_ROOT/node/node_modules (!!!!) + var dir = path.resolve(npm.dir, "../..") + readInstalled(dir, function (er, data) { + var plugins = {}; + var parts = {}; + function flatten(deps) { + Object.keys(deps).forEach(function (name) { + if (name.indexOf(prefix) == 0) { + plugins[name] = deps[name]; + } + if (deps[name].dependencies !== undefined) + flatten(deps[name].dependencies); + }); + } + flatten([data]); + + // Load plugin metadata pluginomatic.json + async.forEach( + Object.keys(plugins), + function (plugin_name, cb) { + fs.readFile( + path.resolve(plugins[plugin_name].path, "pluginomatic.json"), + function (er, data) { + plugin = JSON.parse(data); + plugin.package = plugins[plugin_name]; + plugins[plugin_name] = plugin; + plugin.parts.forEach(function (part) { + part.plugin = plugin; + part.full_name = plugin_name + "." + part.name; + parts[part.full_name] = part; + }); + cb(); + } + ); + }, + function (err) { + cb(err, plugins, exports.sortParts(parts)); + } + ); + }); +} + +exports.partsToParentChildList = function (parts) { + var res = []; + Object.keys(parts).forEach(function (name) { + (parts[name].post || []).forEach(function (child_name) { + res.push([name, child_name]); + }); + (parts[name].pre || []).forEach(function (parent_name) { + res.push([parent_name, name]); + }); + if (!parts[name].pre && !parts[name].post) { + res.push([name, ":" + name]); // Include apps with no dependency info + } + }); + return res; +} + +exports.sortParts = function(parts) { + return tsort( + exports.partsToParentChildList(parts) + ).filter( + function (name) { return parts[name] !== undefined; } + ).map( + function (name) { return parts[name]; } + ); +}; diff --git a/node/pluginfw/tsort.js b/node/pluginfw/tsort.js new file mode 100644 index 000000000..6591c51c7 --- /dev/null +++ b/node/pluginfw/tsort.js @@ -0,0 +1,112 @@ +/** + * general topological sort + * from https://gist.github.com/1232505 + * @author SHIN Suzuki (shinout310@gmail.com) + * @param Array edges : list of edges. each edge forms Array e.g. [12 , 3] + * + * @returns Array : topological sorted list of IDs + **/ + +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 + + var Node = function(id) { + this.id = id; + this.afters = []; + } + + // 1. build data structures + edges.forEach(function(v) { + var from = v[0], to = v[1]; + if (!nodes[from]) nodes[from] = new Node(from); + 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; + + // if already exists, do nothing + if (visited[idstr]) return; + + if (!Array.isArray(ancestors)) ancestors = []; + + ancestors.push(id); + + 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); + + visit(afterID.toString(), ancestors.map(function(v) { return v })); // recursive call + }); + + sorted.unshift(id); + }); + + return sorted; +} + +/** + * TEST + **/ +function tsortTest() { + + // example 1: success + var edges = [ + [1, 2], + [1, 3], + [2, 4], + [3, 4] + ]; + + var sorted = tsort(edges); + console.log(sorted); + + // example 2: failure ( A > B > C > A ) + edges = [ + ['A', 'B'], + ['B', 'C'], + ['C', 'A'] + ]; + + try { + sorted = tsort(edges); + } + catch (e) { + console.log(e.message); + } + + // example 3: generate random edges + var max = 100, 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)] ); + return ret; + })(); + + try { + sorted = tsort(edges); + console.log("succeeded", sorted); + } + catch (e) { + console.log("failed", e.message); + } + +} + + +// for node.js +if (typeof exports == 'object' && exports === this) { + module.exports = tsort; + if (process.argv[1] === __filename) tsortTest(); +} diff --git a/package.json b/package.json index 27b2125a1..940bf0a47 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "formidable" : "1.0.7", "log4js" : "0.4.1", "jsdom-nocontextifiy" : "0.2.10", - "async-stacktrace" : "0.0.2" + "async-stacktrace" : "0.0.2", + "npm" : "1.1", }, "devDependencies": { "jshint" : "*"