c9-core/plugins/c9.ide.language.jsonalyzer/server/jsonalyzer_server.js

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();
}
};