From 061d8dc94f00d92625570f389fe58cf2dd17f94e Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Mon, 26 May 2014 13:33:56 +0200 Subject: [PATCH] Use API introspection. --- packages/xo-cli/package.json | 2 +- packages/xo-cli/src/cli.js | 300 +++++++++++++---------------------- packages/xo-cli/src/xo.js | 12 +- 3 files changed, 118 insertions(+), 196 deletions(-) diff --git a/packages/xo-cli/package.json b/packages/xo-cli/package.json index 1a571b498..9be374ea3 100644 --- a/packages/xo-cli/package.json +++ b/packages/xo-cli/package.json @@ -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" }, diff --git a/packages/xo-cli/src/cli.js b/packages/xo-cli/src/cli.js index d2992b6a1..8f92fdc2e 100644 --- a/packages/xo-cli/src/cli.js +++ b/packages/xo-cli/src/cli.js @@ -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 [] [] [] + Registers the XO instance to use. + + xo-cli --list-commands [--json] + Returns the list of available commands on the current XO instance. + + xo-cli [=]... + 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); + }); }; diff --git a/packages/xo-cli/src/xo.js b/packages/xo-cli/src/xo.js index 290bdd3ab..678a68689 100644 --- a/packages/xo-cli/src/xo.js +++ b/packages/xo-cli/src/xo.js @@ -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;