kopia lustrzana https://github.com/c9/core
361 wiersze
10 KiB
JavaScript
361 wiersze
10 KiB
JavaScript
|
/**
|
||
|
* jsonalyzer server-side analysis component
|
||
|
*/
|
||
|
var vm = require("vm");
|
||
|
var Module = require("module");
|
||
|
var dirname = require("path").dirname;
|
||
|
var assert = require("assert");
|
||
|
var collabServer;
|
||
|
|
||
|
var plugins = {
|
||
|
"c9/assert": assert
|
||
|
};
|
||
|
var vfs;
|
||
|
var workspaceDir;
|
||
|
var homeDir;
|
||
|
var server;
|
||
|
var packer;
|
||
|
var handlers = {};
|
||
|
|
||
|
module.exports = function(_vfs, options, register) {
|
||
|
vfs = _vfs;
|
||
|
|
||
|
server = {
|
||
|
init: init,
|
||
|
|
||
|
registerHandler: registerHandler,
|
||
|
|
||
|
registerHandlers: registerHandlers,
|
||
|
|
||
|
callHandler: callHandler,
|
||
|
|
||
|
getHandlerList: getHandlerList
|
||
|
};
|
||
|
register(null, server);
|
||
|
};
|
||
|
|
||
|
function init(options, callback) {
|
||
|
if (!options.useCollab)
|
||
|
return callback();
|
||
|
|
||
|
workspaceDir = options.workspaceDir;
|
||
|
homeDir = options.homeDir;
|
||
|
|
||
|
vfs.use("collab", {}, function(err, collab) {
|
||
|
if (err)
|
||
|
return callback(err);
|
||
|
collabServer = collab.api;
|
||
|
callback();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function getClientDoc(path, options, callback) {
|
||
|
if (options.value)
|
||
|
return done(null, { contents: options.value });
|
||
|
|
||
|
if (!collabServer)
|
||
|
return done(new Error("No collab server found and cannot use local value"));
|
||
|
|
||
|
var timeout = setTimeout(function() {
|
||
|
var timeoutError = new Error("Collab server failed to provide document contents in time");
|
||
|
timeoutError.code = "ECOLLAB";
|
||
|
timeoutError.customData = {
|
||
|
path: path,
|
||
|
revNum: options.revNum
|
||
|
};
|
||
|
done(timeoutError);
|
||
|
}, 15000);
|
||
|
|
||
|
var docId = path.replace(/^\//, "");
|
||
|
collabServer.getDocument(
|
||
|
docId,
|
||
|
function(err, result) {
|
||
|
if (err) return done(err);
|
||
|
|
||
|
if (!result) {
|
||
|
var noResultError = new Error("Unable to open document or document not found");
|
||
|
noResultError.customData = {
|
||
|
path: path,
|
||
|
revNum: options.revNum
|
||
|
};
|
||
|
return done(noResultError);
|
||
|
}
|
||
|
|
||
|
if (options.revNum <= result.revNum) {
|
||
|
if (!result.contents)
|
||
|
return done(new Error("Collab server failed to provide document contents"));
|
||
|
return done(null, result);
|
||
|
}
|
||
|
|
||
|
collabServer.emitter.on("afterEditUpdate", function wait(e) {
|
||
|
if (e.docId !== docId || e.doc.revNum < options.revNum)
|
||
|
return;
|
||
|
collabServer.emitter.removeListener("afterEditUpdate", wait);
|
||
|
done(null, e.doc);
|
||
|
});
|
||
|
}
|
||
|
);
|
||
|
|
||
|
function done(err, doc) {
|
||
|
clearTimeout(timeout);
|
||
|
callback(err, doc);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function registerHandlers(list, options, callback) {
|
||
|
var results = [];
|
||
|
async.forEachSeries(
|
||
|
list,
|
||
|
function(plugin, next) {
|
||
|
registerHandler(
|
||
|
plugin.path,
|
||
|
plugin.contents,
|
||
|
plugin.options || options,
|
||
|
function(err, result) {
|
||
|
results.push(result);
|
||
|
next(err);
|
||
|
}
|
||
|
);
|
||
|
},
|
||
|
function(err) {
|
||
|
return callback(err, { summaries: results });
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
function registerHandler(handlerPath, contents, options, callback) {
|
||
|
options = options || {};
|
||
|
options.server = server;
|
||
|
options.vfs = vfs;
|
||
|
options.workspaceDir = workspaceDir || vfs.fsOptions.projectDir;
|
||
|
options.homeDir = homeDir;
|
||
|
options.defaultEnv = vfs.fsOptions.defaultEnv || {};
|
||
|
|
||
|
loadPlugin(handlerPath, contents, function(err, result) {
|
||
|
if (err) return callback(err);
|
||
|
|
||
|
handlers[handlerPath] = result;
|
||
|
result.defaultEnv = options.defaultEnv;
|
||
|
if (handlerPath === "jsonm/build/packer")
|
||
|
packer = new result.Packer();
|
||
|
|
||
|
if (!result.init)
|
||
|
return done();
|
||
|
|
||
|
result.init(options, done);
|
||
|
|
||
|
function done(err) {
|
||
|
if (err) return callback(err);
|
||
|
|
||
|
callback(null, getHandlerSummary(handlerPath, result));
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function getHandlerSummary(path, handler) {
|
||
|
var properties = {};
|
||
|
var functions = {};
|
||
|
for (var p in handler) {
|
||
|
if (!handler.hasOwnProperty(p))
|
||
|
continue;
|
||
|
// We don't send functions over vfs, but use callHandler() instead
|
||
|
if (typeof handler[p] === "function")
|
||
|
functions[p] = true;
|
||
|
else
|
||
|
properties[p] = handler[p];
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
path: path,
|
||
|
properties: properties,
|
||
|
functions: functions
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function getHandlerList(callback) {
|
||
|
callback(null, { handlers: Object.keys(handlers) });
|
||
|
}
|
||
|
|
||
|
function callHandler(handlerPath, method, args, options, callback) {
|
||
|
var handler = handlers[handlerPath];
|
||
|
if (!handler)
|
||
|
return callback(new Error("No such handler: " + handlerPath));
|
||
|
if (!handler[method])
|
||
|
return callback(new Error("No such method on " + handlerPath + ": " + method));
|
||
|
|
||
|
var revNum;
|
||
|
var isDone;
|
||
|
|
||
|
// Be extra defensive about collab errors
|
||
|
try {
|
||
|
setupCall();
|
||
|
} catch (e) {
|
||
|
if (isDone)
|
||
|
throw e;
|
||
|
done(e);
|
||
|
}
|
||
|
|
||
|
function setupCall() {
|
||
|
switch (method) {
|
||
|
case "analyzeCurrent":
|
||
|
case "findImports":
|
||
|
case "invoke":
|
||
|
var clientPath = args[0];
|
||
|
var osPath = options.filePath;
|
||
|
|
||
|
getClientDoc(clientPath, options, function(err, doc) {
|
||
|
if (err) return done(err);
|
||
|
if (!doc) {
|
||
|
// Document doesn't appear to exist in collab;
|
||
|
// we'll pass null instead and wait for the
|
||
|
// plugin to decide what to do.
|
||
|
revNum = -1;
|
||
|
return doCall();
|
||
|
}
|
||
|
|
||
|
args[0] = osPath;
|
||
|
args[1] = doc.contents;
|
||
|
args[3] = args[3] || {}; // options
|
||
|
args[3].clientPath = clientPath;
|
||
|
revNum = doc.revNum;
|
||
|
doCall();
|
||
|
});
|
||
|
break;
|
||
|
default:
|
||
|
doCall();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function doCall() {
|
||
|
// Be extra defensive about handler errors
|
||
|
try {
|
||
|
handler[method].apply(handler, args.concat(done));
|
||
|
} catch (e) {
|
||
|
if (isDone)
|
||
|
throw e;
|
||
|
done(e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function done(err/*, arg... */) {
|
||
|
isDone = true;
|
||
|
|
||
|
var args = [].slice.apply(arguments);
|
||
|
callback(
|
||
|
err,
|
||
|
{
|
||
|
result: packer.pack(args, { packStringDepth: 1 }),
|
||
|
revNum: revNum
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function loadPlugin(path, contents, callback) {
|
||
|
if (!path || path.match(/^\.|\.js$/))
|
||
|
return callback(new Error("Illegal module name: " + path));
|
||
|
if (!contents)
|
||
|
return callback(new Error("No contents provided: " + path));
|
||
|
|
||
|
|
||
|
var exports = {};
|
||
|
var module = { exports: exports };
|
||
|
var require = createRequire(path, plugins);
|
||
|
|
||
|
try {
|
||
|
var pathJS = path.replace(/(\.js)?$/, ".js");
|
||
|
var script = "(function(require, exports, module) {"
|
||
|
+ "var define = function(def) { def(require, exports, module) };"
|
||
|
+ contents.replace(/^\#\!.*/, '')
|
||
|
+ "})";
|
||
|
// using pathJS instead of {filename: pathJS} for compatibility with node 0.10
|
||
|
vm.runInThisContext(script, pathJS)(require, exports, module);
|
||
|
} catch (e) {
|
||
|
console.error("Error loading " + path + ":", e.stack);
|
||
|
e.message = ("Error loading " + path + ": " + e.message);
|
||
|
return callback(e);
|
||
|
}
|
||
|
|
||
|
plugins[path] = module.exports;
|
||
|
callback(null, module.exports);
|
||
|
}
|
||
|
|
||
|
function createRequire(path, localDefs) {
|
||
|
var parentModule = new Module(path);
|
||
|
parentModule.path = path;
|
||
|
parentModule.paths = Module._nodeModulePaths(dirname(path));
|
||
|
|
||
|
function createRequire(file) {
|
||
|
var normalized = normalizeModule(path, file);
|
||
|
if (normalized in localDefs)
|
||
|
return localDefs[normalized];
|
||
|
// TODO: fix relative path requires
|
||
|
try {
|
||
|
var exports = Module._load(file, parentModule);
|
||
|
return exports;
|
||
|
} catch (e) {
|
||
|
e.message = path + ": " + e.message;
|
||
|
throw e;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
createRequire.resolve = function(request) {
|
||
|
var resolved = Module._resolveFilename(request, parentModule);
|
||
|
return (resolved instanceof Array) ? resolved[1] : resolved;
|
||
|
};
|
||
|
|
||
|
createRequire.main = process.mainModule;
|
||
|
createRequire.extensions = require.extensions;
|
||
|
createRequire.cache = require.cache;
|
||
|
|
||
|
return createRequire;
|
||
|
}
|
||
|
|
||
|
function normalizeModule(parentId, moduleName) {
|
||
|
// normalize plugin requires
|
||
|
if (moduleName.indexOf("!") !== -1) {
|
||
|
var chunks = moduleName.split("!");
|
||
|
return normalizeModule(parentId, chunks[0]) + "!" + normalizeModule(parentId, chunks[1]);
|
||
|
}
|
||
|
// normalize relative requires
|
||
|
if (moduleName.charAt(0) == ".") {
|
||
|
var base = parentId.split("/").slice(0, -1).join("/");
|
||
|
moduleName = (base || parentId) + "/" + moduleName;
|
||
|
|
||
|
while (moduleName.indexOf(".") !== -1 && previous != moduleName) {
|
||
|
var previous = moduleName;
|
||
|
moduleName = moduleName.replace(/\/\.\//, "/").replace(/[^\/]+\/\.\.\//, "");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return moduleName;
|
||
|
}
|
||
|
|
||
|
var async = {
|
||
|
forEachSeries: function(arr, iterator, callback) {
|
||
|
callback = callback || function () {};
|
||
|
if (!arr.length) {
|
||
|
return callback();
|
||
|
}
|
||
|
var completed = 0;
|
||
|
var iterate = function () {
|
||
|
iterator(arr[completed], function (err) {
|
||
|
if (err) {
|
||
|
callback(err);
|
||
|
callback = function () {};
|
||
|
}
|
||
|
else {
|
||
|
completed += 1;
|
||
|
if (completed === arr.length) {
|
||
|
callback(null);
|
||
|
}
|
||
|
else {
|
||
|
iterate();
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
iterate();
|
||
|
}
|
||
|
};
|