Use API introspection.

This commit is contained in:
Julien Fontanet 2014-05-26 13:33:56 +02:00
parent 84e6228f90
commit 061d8dc94f
3 changed files with 118 additions and 196 deletions

View File

@ -30,7 +30,7 @@
"l33teral": "^2.0.4",
"lodash": "^2.4.1",
"mkdirp": "^0.5.0",
"nomnom": "^1.6.2",
"multiline": "^0.3.4",
"ws": "^0.4.31",
"xdg": "^0.1.1"
},

View File

@ -3,8 +3,9 @@
//====================================================================
var _ = require('lodash');
var nomnom = require('nomnom')();
var Promise = require('bluebird');
var multiline = require('multiline');
var chalk = require('chalk');
//--------------------------------------------------------------------
@ -14,15 +15,6 @@ var Xo = require('./xo');
//====================================================================
//`nomnom.print()` version which does not use `process.exit()`.
nomnom.print = function (str, code) {
throw ''+ str;
// TODO: handles code.
};
//====================================================================
var connect = function () {
return config.load().bind({}).then(function (config) {
if (!config.server)
@ -37,7 +29,7 @@ var connect = function () {
this.xo = new Xo(config.server);
return this.xo.send('session.signInWithToken', {
return this.xo.call('session.signInWithToken', {
token: config.token,
});
}).then(function () {
@ -45,188 +37,114 @@ var connect = function () {
}).bind();
};
var wrap = function (val) {
return function () {
return val;
};
};
//====================================================================
module.exports = function (argv) {
var command;
var commandName;
var commandOpts;
var registerCommand = function (name, def, fn) {
var cmdParser = nomnom.command(name);
if (def.description)
{
cmdParser.help(def.description);
}
if (def.args)
{
var interactive = process.stdin.isTTY;
_.each(def.args, function (def, name) {
cmdParser.option(name, {
abbr: def.abbr,
flag: (def.type === 'boolean'),
help: def.description,
// Do not mark options as required if the program is run in
// interactive mode, they will be asked.
required: !interactive && def.required,
});
});
fn = (function (fn, args) {
return function (opts) {
var prompts = [];
_.each(args, function (def, name) {
if (!(name in opts))
{
prompts.push({
name: name,
message: def.prompt || def.description || name,
type: def.promptType || 'input',
});
}
});
if (prompts.length)
{
return prompt(prompts).then(function (answers) {
return fn(_.extend(opts, answers));
});
}
return fn(opts);
};
})(fn, def.args);
}
cmdParser.callback(function (opts) {
command = fn;
commandName = name;
commandOpts = opts;
});
};
registerCommand('add-server', {
description: 'adds a new Xen server',
args: {
host: {
description: 'host of the server',
required: true,
},
username: {
description: 'username to use to connect',
required: true,
},
password: {
description: 'password to use to connect',
promptType: 'password',
required: true,
},
},
}, function (opts) {
return connect().then(function (connection) {
return connection.send('server.add', {
host: opts.host,
username: opts.username,
password: opts.password,
});
}).return('ok');
});
registerCommand('register', {
description: 'registers the XO instance',
args: {
host: {
description: 'host/ip optionally followed by `:port` if not 80',
required: true,
},
email: {
description: 'email to use to connect',
required: true,
},
password: {
description: 'password to use to connect',
promptType: 'password',
required: true,
},
}
}, function (opts) {
return Promise.bind({opts: opts || {}}).then(function () {
this.xo = new Xo(opts.host);
return this.xo.send('session.signInWithPassword', {
email: this.opts.email,
password: this.opts.password,
});
}).then(function (user) {
console.log('Successfully logged with', user.email);
return this.xo.send('token.create');
}).then(function (token) {
console.log('Token created:', token);
return config.set({
server: this.opts.host,
token: token,
});
}).bind();
});
registerCommand('list-commands', {}, function () {
return connect().then(function (xo) {
return xo.send('system.listMethods').then(JSON.stringify);
});
});
registerCommand('show-command', {
args: {
name: {
required: true,
},
},
}, function (opts) {
return connect().then(function (xo) {
return xo.send('system.methodSignature', {
name: opts.name
}).then(console.log);
});
});
registerCommand('whoami', {
description: 'displays information about the current user',
}, function () {
return connect().then(function (xo) {
return xo.send('session.getUser');
}).then(function (user) {
if (user)
{
console.log('You are signed in as', user.email);
console.log('Your global permission is', user.permission);
}
else
{
console.log('Your are not signed in.');
}
});
});
// TODO: handle global `--config FILE` option.
var opts = nomnom
.option('version', {
flag: true,
help: 'prints the current version of xo-cli',
})
.parse(argv)
;
if (opts.version)
{
return 'xo-cli version '+ require('../package').version;
exports = module.exports = function (args) {
if (!args || !args.length) {
return help();
}
// Executes the selected command.
return command(commandOpts);
var fnName = args[0].replace(/^--|-\w/g, function (match) {
if (match === '--')
{
return '';
}
return match[1].toUpperCase();
});
if (fnName in exports) {
return exports[fnName](args.slice(1));
}
return exports.call(args);
};
//--------------------------------------------------------------------
var help = exports.help = wrap(multiline.stripIndent(function () {/*
Usage:
xo-cli --register [<XO-Server URL>] [<username>] [<password>]
Registers the XO instance to use.
xo-cli --list-commands [--json]
Returns the list of available commands on the current XO instance.
xo-cli <command> [<name>=<value>]...
Executes a command on the current XO instance.
*/}));
exports.version = wrap('xo-cli v'+ require('../package').version);
exports.register = function (args) {
};
exports.listCommands = function (args) {
return connect().then(function (xo) {
return xo.call('system.getMethodsInfo');
}).then(function (methods) {
if (args.indexOf('--json') !== -1)
{
return methods;
}
methods = _.pairs(methods);
methods.sort(function (a, b) {
a = a[0];
b = b[0];
if (a < b) {
return -1;
}
return +(a > b);
});
var str = [];
methods.forEach(function (method) {
var name = method[0];
var info = method[1];
str.push(chalk.bold.blue(name));
_.each(info.params || [], function (info, name) {
str.push(' ');
if (info.optional) {
str.push('[');
}
str.push(name, '=<', info.type || 'unknown', '>');
if (info.optional) {
str.push(']');
}
});
str.push('\n');
if (info.description) {
str.push(' ', info.description, '\n');
}
});
return str.join('');
});
};
var PARAM_RE = /^([^=]+)=(.*)$/;
exports.call = function (args) {
if (!args.length) {
throw 'missing command name';
}
var method = args.shift();
var params = {};
args.forEach(function (arg) {
var matches;
if (!(matches = arg.match(PARAM_RE))) {
throw 'invalid arg: '+arg;
}
params[matches[1]] = matches[2];
});
return connect().then(function (xo) {
return xo.call(method, params);
});
};

View File

@ -14,6 +14,12 @@ var WebSocket = (this && 'WebSocket' in this) ? this.WebSocket : require('ws');
//====================================================================
var notConnected = function () {
throw new Error('not connected');
};
//====================================================================
var Xo = function (url) {
this._url = url;
@ -102,9 +108,7 @@ _.extend(Xo.prototype, {
socket.on('close', function () {
// Closes accesses.
this.send = function () {
throw new Error('not connected');
};
this.send = notConnected;
// Fails all waiting requests.
_.each(this._deferreds, function (deferred) {
@ -121,7 +125,7 @@ _.extend(Xo.prototype, {
return deferred.promise;
},
send: function (method, params) {
call: function (method, params) {
return this.connect().then(function () {
var socket = this._socket;