UNPKG

8.33 kBJavaScriptView Raw
1"use strict";
2
3/**
4 * Module dependencies.
5 */
6
7var EventEmitter = require("events").EventEmitter
8 , Option = require("./option")
9 , VantageUtil = require("./util")
10 , _ = require("lodash")
11 ;
12
13/**
14 * Command prototype.
15 */
16
17var command = Command.prototype;
18
19/**
20 * Expose `Command`.
21 */
22
23module.exports = exports = Command;
24
25/**
26 * Initialize a new `Command` instance.
27 *
28 * @param {String} name
29 * @param {Vantage} parent
30 * @return {Command}
31 * @api public
32 */
33
34function Command(name, parent) {
35 if (!(this instanceof Command)) { return new Command(); }
36 this.commands = [];
37 this.options = [];
38 this._allowUnknownOption = false;
39 this._args = [];
40 this._aliases = [];
41 this._name = name;
42 this._relay = false;
43 this._hidden = false;
44 this._parent = parent;
45 this._mode = false;
46 this._catch = false;
47 this._init = void 0;
48 this._after = void 0;
49}
50
51/**
52 * Registers an option for given command.
53 *
54 * @param {String} flags
55 * @param {String} description
56 * @param {Function} fn
57 * @param {String} defaultValue
58 * @return {Command}
59 * @api public
60 */
61
62command.option = function(flags, description, fn, defaultValue) {
63
64 var self = this
65 , option = new Option(flags, description)
66 , oname = option.name()
67 , name = _camelcase(oname);
68
69 // default as 3rd arg
70 if (typeof fn !== "function") {
71 if (fn instanceof RegExp) {
72 var regex = fn;
73 fn = function(val, def) {
74 var m = regex.exec(val);
75 return m ? m[0] : def;
76 };
77 }
78 else {
79 defaultValue = fn;
80 fn = null;
81 }
82 }
83
84 // preassign default value only for --no-*, [optional], or <required>
85 if (option.bool === false || option.optional || option.required) {
86 // when --no-* we make sure default is true
87 if (option.bool === false) { defaultValue = true; }
88 // preassign only if we have a default
89 if (defaultValue !== undefined) { self[name] = defaultValue; }
90 }
91
92 // register the option
93 this.options.push(option);
94
95 // when it"s passed assign the value
96 // and conditionally invoke the callback
97 this.on(oname, function(val) {
98 // coercion
99 if (val !== null && fn) { val = fn(val, self[name] === undefined
100 ? defaultValue
101 : self[name]);
102 }
103
104 // unassigned or bool
105 if (typeof self[name] === "boolean" || typeof self[name] === "undefined") {
106 // if no value, bool true, and we have a default, then use it!
107 if (val === null) {
108 self[name] = option.bool
109 ? defaultValue || true
110 : false;
111 } else {
112 self[name] = val;
113 }
114 } else if (val !== null) {
115 // reassign
116 self[name] = val;
117 }
118 });
119
120 return this;
121};
122
123/**
124 * Defines an action for a given command.
125 *
126 * @param {Function} fn
127 * @return {Command}
128 * @api public
129 */
130
131command.action = function(fn) {
132 var self = this;
133 self._fn = fn;
134 return this;
135};
136
137/**
138 * Defines tabbed auto-completion rules
139 * for the given command.
140 *
141 * @param {Function} fn
142 * @return {Command}
143 * @api public
144 */
145
146command.autocompletion = function(fn, cb) {
147
148 if (!_.isFunction(fn)) {
149 throw new Error("An invalid object type was passed into the first parameter of command.autocompletion: function expected.");
150 }
151
152 this._autocompletion = fn;
153
154 return this;
155};
156
157/**
158 * Defines an init action for a mode command.
159 *
160 * @param {Function} fn
161 * @return {Command}
162 * @api public
163 */
164
165command.init = function(fn) {
166 var self = this;
167 if (self._mode !== true) {
168 throw Error("Cannot call init from a non-mode action.");
169 }
170 self._init = fn;
171 return this;
172};
173
174/**
175 * Defines a prompt delimiter for a
176 * mode once entered.
177 *
178 * @param {String} delimiter
179 * @return {Command}
180 * @api public
181 */
182
183command.delimiter = function(delimiter) {
184 this._delimiter = delimiter;
185 return this;
186};
187
188/**
189 * Defines an alias for a given command.
190 *
191 * @param {String} alias
192 * @return {Command}
193 * @api public
194 */
195
196command.alias = function(alias) {
197 this._aliases.push(alias);
198 return this;
199};
200
201/**
202 * Defines description for given command.
203 *
204 * @param {String} str
205 * @return {Command}
206 * @api public
207 */
208
209command.description = function(str) {
210 if (arguments.length === 0) { return this._description; }
211 this._description = str;
212 return this;
213};
214
215/**
216 * Returns the commands arguments as string.
217 *
218 * @param {String} desc
219 * @return {String}
220 * @api public
221 */
222
223command.arguments = function (desc) {
224 return this._parseExpectedArgs(desc.split(/ +/));
225};
226
227/**
228 * Returns the help info for given command.
229 *
230 * @return {String}
231 * @api public
232 */
233
234command.helpInformation = function() {
235
236 var desc = []
237 , cmdName = this._name
238 , alias = ""
239 ;
240
241 if (this._description) {
242 desc = [
243 " " + this._description
244 , ""
245 ];
246 }
247
248 if (this._aliases.length > 0) {
249 alias = " Alias: " + this._aliases.join(" | ") + "\n";
250 }
251 var usage = [
252 ""
253 , " Usage: " + cmdName + " " + this.usage()
254 , ""
255 ];
256
257 var cmds = [];
258
259 var options = [
260 " Options:"
261 , ""
262 , "" + this.optionHelp().replace(/^/gm, " ")
263 , ""
264 ];
265
266 var res = usage
267 .concat(cmds)
268 .concat(alias)
269 .concat(desc)
270 .concat(options)
271 .join("\n");
272
273 return res;
274};
275
276/**
277 * Doesn"t show command in the help menu.
278 *
279 * @return {Command}
280 * @api public
281 */
282
283command.hidden = function() {
284 this._hidden = true;
285 return this;
286};
287
288/**
289 * Returns the command usage string for help.
290 *
291 * @param {String} str
292 * @return {String}
293 * @api public
294 */
295
296command.usage = function(str) {
297 var args = this._args.map(function(arg) {
298 return VantageUtil.humanReadableArgName(arg);
299 });
300
301 var usage = "[options]"
302 + (this.commands.length ? " [command]" : "")
303 + (this._args.length ? " " + args.join(" ") : "");
304
305 if (arguments.length === 0) { return (this._usage || usage); }
306 this._usage = str;
307
308 return this;
309};
310
311/**
312 * Returns the help string for the command's options.
313 *
314 * @return {String}
315 * @api public
316 */
317
318command.optionHelp = function() {
319 var width = this._largestOptionLength();
320
321 // Prepend the help information
322 return [VantageUtil.pad("--help", width) + " " + "output usage information"]
323 .concat(this.options.map(function(option) {
324 return VantageUtil.pad(option.flags, width) + " " + option.description;
325 }))
326 .join("\n");
327};
328
329/**
330 * Returns the length of the longest option.
331 *
332 * @return {Integer}
333 * @api private
334 */
335
336command._largestOptionLength = function() {
337 return this.options.reduce(function(max, option) {
338 return Math.max(max, option.flags.length);
339 }, 0);
340};
341
342/**
343 * Adds a command to be executed after command completion.
344 *
345 * @param {Function} fn
346 * @return {Command}
347 * @api public
348 */
349
350command.after = function(fn) {
351 if (_.isFunction(fn)) {
352 this._after = fn;
353 }
354 return this;
355};
356
357/**
358 * Parses and returns expected command arguments.
359 *
360 * @param {String} args
361 * @return {Array}
362 * @api private
363 */
364
365command._parseExpectedArgs = function(args) {
366 if (!args.length) { return; }
367 var self = this;
368 args.forEach(function(arg) {
369 var argDetails = {
370 required: false,
371 name: "",
372 variadic: false
373 };
374
375 switch (arg[0]) {
376 case "<":
377 argDetails.required = true;
378 argDetails.name = arg.slice(1, -1);
379 break;
380 case "[":
381 argDetails.name = arg.slice(1, -1);
382 break;
383 }
384
385 if (argDetails.name.length > 3 && argDetails.name.slice(-3) === "...") {
386 argDetails.variadic = true;
387 argDetails.name = argDetails.name.slice(0, -3);
388 }
389 if (argDetails.name) {
390 self._args.push(argDetails);
391 }
392 });
393 return;
394};
395
396/**
397 * Converts string to camel case.
398 *
399 * @param {String} flag
400 * @return {String}
401 * @api private
402 */
403
404function _camelcase(flag) {
405 return flag.split("-").reduce(function(str, word) {
406 return str + word[0].toUpperCase() + word.slice(1);
407 });
408}
409
410/**
411 * Make command an EventEmitter.
412 */
413
414command.__proto__ = EventEmitter.prototype;
415