| /* eslint-disable class-methods-use-this */ | 
| 'use strict'; | 
|   | 
| const | 
|     UTIL = require('util'), | 
|     PATH = require('path'), | 
|     EOL = require('os').EOL, | 
|   | 
|     Q = require('q'), | 
|     chalk = require('chalk'), | 
|   | 
|     CoaObject = require('./coaobject'), | 
|     Opt = require('./opt'), | 
|     Arg = require('./arg'), | 
|     completion = require('./completion'); | 
|   | 
| /** | 
|  * Command | 
|  * | 
|  * Top level entity. Commands may have options and arguments. | 
|  * | 
|  * @namespace | 
|  * @class Cmd | 
|  * @extends CoaObject | 
|  */ | 
| class Cmd extends CoaObject { | 
|     /** | 
|      * @constructs | 
|      * @param {COA.Cmd} [cmd] parent command | 
|      */ | 
|     constructor(cmd) { | 
|         super(cmd); | 
|   | 
|         this._parent(cmd); | 
|         this._cmds = []; | 
|         this._cmdsByName = {}; | 
|         this._opts = []; | 
|         this._optsByKey = {}; | 
|         this._args = []; | 
|         this._api = null; | 
|         this._ext = false; | 
|     } | 
|   | 
|     static create(cmd) { | 
|         return new Cmd(cmd); | 
|     } | 
|   | 
|     /** | 
|      * Returns object containing all its subcommands as methods | 
|      * to use from other programs. | 
|      * | 
|      * @returns {Object} | 
|      */ | 
|     get api() { | 
|         // Need _this here because of passed arguments into _api | 
|         const _this = this; | 
|         this._api || (this._api = function () { | 
|             return _this.invoke.apply(_this, arguments); | 
|         }); | 
|   | 
|         const cmds = this._cmdsByName; | 
|         Object.keys(cmds).forEach(cmd => { this._api[cmd] = cmds[cmd].api; }); | 
|   | 
|         return this._api; | 
|     } | 
|   | 
|     _parent(cmd) { | 
|         this._cmd = cmd || this; | 
|   | 
|         this.isRootCmd || | 
|             cmd._cmds.push(this) && | 
|             this._name && | 
|             (this._cmd._cmdsByName[this._name] = this); | 
|   | 
|         return this; | 
|     } | 
|   | 
|     get isRootCmd() { | 
|         return this._cmd === this; | 
|     } | 
|   | 
|     /** | 
|      * Set a canonical command identifier to be used anywhere in the API. | 
|      * | 
|      * @param {String} name - command name | 
|      * @returns {COA.Cmd} - this instance (for chainability) | 
|      */ | 
|     name(name) { | 
|         super.name(name); | 
|   | 
|         this.isRootCmd || | 
|             (this._cmd._cmdsByName[name] = this); | 
|   | 
|         return this; | 
|     } | 
|   | 
|     /** | 
|      * Create new or add existing subcommand for current command. | 
|      * | 
|      * @param {COA.Cmd} [cmd] existing command instance | 
|      * @returns {COA.Cmd} new subcommand instance | 
|      */ | 
|     cmd(cmd) { | 
|         return cmd? | 
|             cmd._parent(this) | 
|             : new Cmd(this); | 
|     } | 
|   | 
|     /** | 
|      * Create option for current command. | 
|      * | 
|      * @returns {COA.Opt} new option instance | 
|      */ | 
|     opt() { | 
|         return new Opt(this); | 
|     } | 
|   | 
|     /** | 
|      * Create argument for current command. | 
|      * | 
|      * @returns {COA.Opt} new argument instance | 
|      */ | 
|     arg() { | 
|         return new Arg(this); | 
|     } | 
|   | 
|     /** | 
|      * Add (or set) action for current command. | 
|      * | 
|      * @param {Function} act - action function, | 
|      *         invoked in the context of command instance | 
|      *         and has the parameters: | 
|      *                 - {Object} opts - parsed options | 
|      *                 - {String[]} args - parsed arguments | 
|      *                 - {Object} res - actions result accumulator | 
|      *         It can return rejected promise by Cmd.reject (in case of error) | 
|      *         or any other value treated as result. | 
|      * @param {Boolean} [force=false] flag for set action instead add to existings | 
|      * @returns {COA.Cmd} - this instance (for chainability) | 
|      */ | 
|     act(act, force) { | 
|         if(!act) return this; | 
|   | 
|         (!this._act || force) && (this._act = []); | 
|         this._act.push(act); | 
|   | 
|         return this; | 
|     } | 
|   | 
|     /** | 
|      * Make command "helpful", i.e. add -h --help flags for print usage. | 
|      * | 
|      * @returns {COA.Cmd} - this instance (for chainability) | 
|      */ | 
|     helpful() { | 
|         return this.opt() | 
|             .name('help') | 
|             .title('Help') | 
|             .short('h') | 
|             .long('help') | 
|             .flag() | 
|             .only() | 
|             .act(function() { | 
|                 return this.usage(); | 
|             }) | 
|             .end(); | 
|     } | 
|   | 
|     /** | 
|      * Adds shell completion to command, adds "completion" subcommand, | 
|      * that makes all the magic. | 
|      * Must be called only on root command. | 
|      * | 
|      * @returns {COA.Cmd} - this instance (for chainability) | 
|      */ | 
|     completable() { | 
|         return this.cmd() | 
|             .name('completion') | 
|             .apply(completion) | 
|             .end(); | 
|     } | 
|   | 
|     /** | 
|      * Allow command to be extendable by external node.js modules. | 
|      * | 
|      * @param {String} [pattern]  Pattern of node.js module to find subcommands at. | 
|      * @returns {COA.Cmd} - this instance (for chainability) | 
|      */ | 
|     extendable(pattern) { | 
|         this._ext = pattern || true; | 
|         return this; | 
|     } | 
|   | 
|     _exit(msg, code) { | 
|         return process.once('exit', function(exitCode) { | 
|             msg && console[code === 0 ? 'log' : 'error'](msg); | 
|             process.exit(code || exitCode || 0); | 
|         }); | 
|     } | 
|   | 
|     /** | 
|      * Build full usage text for current command instance. | 
|      * | 
|      * @returns {String} usage text | 
|      */ | 
|     usage() { | 
|         const res = []; | 
|   | 
|         this._title && res.push(this._fullTitle()); | 
|   | 
|         res.push('', 'Usage:'); | 
|   | 
|         this._cmds.length | 
|             && res.push([ | 
|                 '', '', chalk.redBright(this._fullName()), chalk.blueBright('COMMAND'), | 
|                 chalk.greenBright('[OPTIONS]'), chalk.magentaBright('[ARGS]') | 
|             ].join(' ')); | 
|   | 
|         (this._opts.length + this._args.length) | 
|             && res.push([ | 
|                 '', '', chalk.redBright(this._fullName()), | 
|                 chalk.greenBright('[OPTIONS]'), chalk.magentaBright('[ARGS]') | 
|             ].join(' ')); | 
|   | 
|         res.push( | 
|             this._usages(this._cmds, 'Commands'), | 
|             this._usages(this._opts, 'Options'), | 
|             this._usages(this._args, 'Arguments') | 
|         ); | 
|   | 
|         return res.join(EOL); | 
|     } | 
|   | 
|     _usage() { | 
|         return chalk.blueBright(this._name) + ' : ' + this._title; | 
|     } | 
|   | 
|     _usages(os, title) { | 
|         if(!os.length) return; | 
|   | 
|         return ['', title + ':'] | 
|             .concat(os.map(o => `  ${o._usage()}`)) | 
|             .join(EOL); | 
|     } | 
|   | 
|     _fullTitle() { | 
|         return `${this.isRootCmd? '' : this._cmd._fullTitle() + EOL}${this._title}`; | 
|     } | 
|   | 
|     _fullName() { | 
|         return `${this.isRootCmd? '' : this._cmd._fullName() + ' '}${PATH.basename(this._name)}`; | 
|     } | 
|   | 
|     _ejectOpt(opts, opt) { | 
|         const pos = opts.indexOf(opt); | 
|         if(pos === -1) return; | 
|   | 
|         return opts[pos]._arr? | 
|             opts[pos] : | 
|             opts.splice(pos, 1)[0]; | 
|     } | 
|   | 
|     _checkRequired(opts, args) { | 
|         if(this._opts.some(opt => opt._only && opts.hasOwnProperty(opt._name))) return; | 
|   | 
|         const all = this._opts.concat(this._args); | 
|         let i; | 
|         while(i = all.shift()) | 
|             if(i._req && i._checkParsed(opts, args)) | 
|                 return this.reject(i._requiredText()); | 
|     } | 
|   | 
|     _parseCmd(argv, unparsed) { | 
|         unparsed || (unparsed = []); | 
|   | 
|         let i, | 
|             optSeen = false; | 
|         while(i = argv.shift()) { | 
|             i.indexOf('-') || (optSeen = true); | 
|   | 
|             if(optSeen || !/^\w[\w-_]*$/.test(i)) { | 
|                 unparsed.push(i); | 
|                 continue; | 
|             } | 
|   | 
|             let pkg, cmd = this._cmdsByName[i]; | 
|             if(!cmd && this._ext) { | 
|                 if(this._ext === true) { | 
|                     pkg = i; | 
|                     let c = this; | 
|                     while(true) { // eslint-disable-line | 
|                         pkg = c._name + '-' + pkg; | 
|                         if(c.isRootCmd) break; | 
|                         c = c._cmd; | 
|                     } | 
|                 } else if(typeof this._ext === 'string') | 
|                     pkg = ~this._ext.indexOf('%s')? | 
|                         UTIL.format(this._ext, i) : | 
|                         this._ext + i; | 
|   | 
|                 let cmdDesc; | 
|                 try { | 
|                     cmdDesc = require(pkg); | 
|                 } catch(e) { | 
|                     // Dummy | 
|                 } | 
|   | 
|                 if(cmdDesc) { | 
|                     if(typeof cmdDesc === 'function') { | 
|                         this.cmd().name(i).apply(cmdDesc).end(); | 
|                     } else if(typeof cmdDesc === 'object') { | 
|                         this.cmd(cmdDesc); | 
|                         cmdDesc.name(i); | 
|                     } else throw new Error('Error: Unsupported command declaration type, ' | 
|                         + 'should be a function or COA.Cmd() object'); | 
|   | 
|                     cmd = this._cmdsByName[i]; | 
|                 } | 
|             } | 
|   | 
|             if(cmd) return cmd._parseCmd(argv, unparsed); | 
|   | 
|             unparsed.push(i); | 
|         } | 
|   | 
|         return { cmd : this, argv : unparsed }; | 
|     } | 
|   | 
|     _parseOptsAndArgs(argv) { | 
|         const opts = {}, | 
|             args = {}, | 
|             nonParsedOpts = this._opts.concat(), | 
|             nonParsedArgs = this._args.concat(); | 
|   | 
|         let res, i; | 
|         while(i = argv.shift()) { | 
|             if(i !== '--' && i[0] === '-') { | 
|                 const m = i.match(/^(--\w[\w-_]*)=(.*)$/); | 
|                 if(m) { | 
|                     i = m[1]; | 
|                     this._optsByKey[i]._flag || argv.unshift(m[2]); | 
|                 } | 
|   | 
|                 const opt = this._ejectOpt(nonParsedOpts, this._optsByKey[i]); | 
|                 if(!opt) return this.reject(`Unknown option: ${i}`); | 
|   | 
|                 if(Q.isRejected(res = opt._parse(argv, opts))) return res; | 
|   | 
|                 continue; | 
|             } | 
|   | 
|             i === '--' && (i = argv.splice(0)); | 
|             Array.isArray(i) || (i = [i]); | 
|   | 
|             let a; | 
|             while(a = i.shift()) { | 
|                 let arg = nonParsedArgs.shift(); | 
|                 if(!arg) return this.reject(`Unknown argument: ${a}`); | 
|   | 
|                 arg._arr && nonParsedArgs.unshift(arg); | 
|                 if(Q.isRejected(res = arg._parse(a, args))) return res; | 
|             } | 
|         } | 
|   | 
|         return { | 
|             opts : this._setDefaults(opts, nonParsedOpts), | 
|             args : this._setDefaults(args, nonParsedArgs) | 
|         }; | 
|     } | 
|   | 
|     _setDefaults(params, desc) { | 
|         for(const item of desc) | 
|             item._def !== undefined && | 
|                 !params.hasOwnProperty(item._name) && | 
|                 item._saveVal(params, item._def); | 
|   | 
|         return params; | 
|     } | 
|   | 
|     _processParams(params, desc) { | 
|         const notExists = []; | 
|   | 
|         for(const item of desc) { | 
|             const n = item._name; | 
|   | 
|             if(!params.hasOwnProperty(n)) { | 
|                 notExists.push(item); | 
|                 continue; | 
|             } | 
|   | 
|             const vals = Array.isArray(params[n])? params[n] : [params[n]]; | 
|             delete params[n]; | 
|   | 
|             let res; | 
|             for(const v of vals) | 
|                 if(Q.isRejected(res = item._saveVal(params, v))) | 
|                     return res; | 
|         } | 
|   | 
|         return this._setDefaults(params, notExists); | 
|     } | 
|   | 
|     _parseArr(argv) { | 
|         return Q.when(this._parseCmd(argv), p => | 
|             Q.when(p.cmd._parseOptsAndArgs(p.argv), r => ({ | 
|                 cmd : p.cmd, | 
|                 opts : r.opts, | 
|                 args : r.args | 
|             }))); | 
|     } | 
|   | 
|     _do(inputPromise) { | 
|         return Q.when(inputPromise, input => { | 
|             return [this._checkRequired] | 
|                 .concat(input.cmd._act || []) | 
|                 .reduce((res, act) => | 
|                     Q.when(res, prev => act.call(input.cmd, input.opts, input.args, prev)), | 
|                     undefined); | 
|         }); | 
|     } | 
|   | 
|     /** | 
|      * Parse arguments from simple format like NodeJS process.argv | 
|      * and run ahead current program, i.e. call process.exit when all actions done. | 
|      * | 
|      * @param {String[]} argv - arguments | 
|      * @returns {COA.Cmd} - this instance (for chainability) | 
|      */ | 
|     run(argv) { | 
|         argv || (argv = process.argv.slice(2)); | 
|   | 
|         const cb = code => | 
|             res => res? | 
|                 this._exit(res.stack || res.toString(), (res.hasOwnProperty('exitCode')? res.exitCode : code) || 0) : | 
|                 this._exit(); | 
|   | 
|         Q.when(this.do(argv), cb(0), cb(1)).done(); | 
|   | 
|         return this; | 
|     } | 
|   | 
|     /** | 
|      * Invoke specified (or current) command using provided | 
|      * options and arguments. | 
|      * | 
|      * @param {String|String[]} [cmds] - subcommand to invoke (optional) | 
|      * @param {Object} [opts] - command options (optional) | 
|      * @param {Object} [args] - command arguments (optional) | 
|      * @returns {Q.Promise} | 
|      */ | 
|     invoke(cmds, opts, args) { | 
|         cmds || (cmds = []); | 
|         opts || (opts = {}); | 
|         args || (args = {}); | 
|         typeof cmds === 'string' && (cmds = cmds.split(' ')); | 
|   | 
|         if(arguments.length < 3 && !Array.isArray(cmds)) { | 
|             args = opts; | 
|             opts = cmds; | 
|             cmds = []; | 
|         } | 
|   | 
|         return Q.when(this._parseCmd(cmds), p => { | 
|             if(p.argv.length) | 
|                 return this.reject(`Unknown command: ${cmds.join(' ')}`); | 
|   | 
|             return Q.all([ | 
|                 this._processParams(opts, this._opts), | 
|                 this._processParams(args, this._args) | 
|             ]).spread((_opts, _args) => | 
|                 this._do({ | 
|                     cmd : p.cmd, | 
|                     opts : _opts, | 
|                     args : _args | 
|                 }) | 
|                 .fail(res => (res && res.exitCode === 0)? | 
|                     res.toString() : | 
|                     this.reject(res))); | 
|         }); | 
|     } | 
| } | 
|   | 
| /** | 
|  * Convenient function to run command from tests. | 
|  * | 
|  * @param {String[]} argv - arguments | 
|  * @returns {Q.Promise} | 
|  */ | 
| Cmd.prototype.do = function(argv) { | 
|     return this._do(this._parseArr(argv || [])); | 
| }; | 
|   | 
| module.exports = Cmd; |