UNPKG

13.6 kBJavaScriptView Raw
1/**
2* Copyright (c) Microsoft. All rights reserved.
3*
4* Licensed under the Apache License, Version 2.0 (the "License");
5* you may not use this file except in compliance with the License.
6* You may obtain a copy of the License at
7* http://www.apache.org/licenses/LICENSE-2.0
8*
9* Unless required by applicable law or agreed to in writing, software
10* distributed under the License is distributed on an "AS IS" BASIS,
11* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12* See the License for the specific language governing permissions and
13* limitations under the License.
14*/
15
16'use strict';
17
18//
19// Extensions to Commander to support nested commands / help system
20//
21var _ = require('underscore');
22var commander = require('commander');
23var fs = require('fs');
24var path = require('path');
25var util = require('util');
26var utilsCore = require('./utilsCore');
27var pjson = require('../../package.json');
28var Constants = require('./constants');
29
30function ExtendedCommand(name) {
31 ExtendedCommand['super_'].call(this, name);
32}
33
34util.inherits(ExtendedCommand, commander.Command);
35
36_.extend(ExtendedCommand.prototype, {
37
38 //////////////////////////////
39 // override help subsystem
40
41 fullName: function () {
42 var name = this.name;
43 var scan = this.parent;
44 while (scan && scan.parent !== undefined) {
45 name = scan.name ? scan.name + ' ' + name : name;
46 scan = scan.parent;
47 }
48
49 return name;
50 },
51
52 usage: function (str) {
53 var ret;
54
55 if (str) {
56 ret = commander.Command.prototype.usage.call(this, str);
57 } else {
58 ret = commander.Command.prototype.usage.call(this);
59 ret = ret.replace(/,/g,' ');
60 }
61
62 return ret;
63 },
64
65 helpInformation: function() {
66 var self = this;
67
68 if (!self.parent) {
69 var raw = self.normalize(process.argv.slice(2));
70 var containsJson = raw.some(function (item) { return item === '--json'; });
71 if (containsJson) {
72 self.output.format({ json: true, level: 'data' });
73 self.output.json(self.helpJSON());
74 return '';
75 }
76 var packagePath = path.join(__dirname, '../../package.json');
77 var packageInfo = JSON.parse(fs.readFileSync(packagePath));
78
79 if (raw.indexOf('-v') >= 0) {
80 console.log(packageInfo.version);
81 } else if (raw.indexOf('--version') >= 0) {
82 console.log(util.format('%s (node: %s)', packageInfo.version, process.versions.node));
83 } else {
84 self.setupCommandLogFormat(true);
85
86 self.output.info(' _ _____ _ ___ ___'.cyan);
87 self.output.info(' /_\\ |_ / | | | _ \\ __|'.cyan);
88 self.output.info(' _ ___'.grey + '/ _ \\'.cyan + '__'.grey + '/ /| |_| | / _|'.cyan + '___ _ _'.grey);
89 self.output.info('(___ '.grey + '/_/ \\_\\/___|\\___/|_|_\\___|'.cyan + ' _____)'.grey);
90 self.output.info(' (_______ _ _) _ ______ _)_ _ '.grey);
91 self.output.info(' (______________ _ ) (___ _ _)'.grey);
92 self.output.info('');
93
94 self.output.info('Microsoft Azure: Microsoft\'s Cloud Platform');
95 self.output.info('');
96 self.output.info('Tool version', packageInfo.version);
97
98 self.helpCommands();
99 self.helpCategoriesSummary(self.showMore());
100 self.helpOptions();
101 self.showCommandMode();
102 }
103 } else {
104 self.output.help(self.description());
105 self.output.help('');
106 self.output.help('Usage:', self.fullName() + ' ' + self.usage());
107 self.helpOptions();
108 self.showCommandMode();
109 }
110
111 return '';
112 },
113
114 showCommandMode: function(){
115 this.output.help('');
116 var mode = this._mode;
117 if (!mode) {
118 mode = utilsCore.getMode();
119 }
120 var text = util.format('Current Mode: %s (Azure %s Management)', mode,
121 (mode === Constants.API_VERSIONS.ASM ? 'Service' : 'Resource'));
122 this.output.help(text);
123 },
124
125 showMore: function () {
126 var raw = this.normalize(process.argv.slice(2));
127 return raw.indexOf('--help') >= 0 || raw.indexOf('-h') >= 0;
128 },
129
130 categoryHelpInformation: function(containsJson) {
131 var self = this;
132 if (containsJson) {
133 var result = self.getJSONMetadata(true);
134 if (!result) {
135 throw new Error(util.format('Unable to provide help in JSON format for \'%s\'', self.fullName()));
136 }
137 self.output.format({ json: true, level: 'data' });
138 return self.output.json(result);
139 }
140 this.output.help(self.description());
141
142 if (this.name === 'vm' || this.name === 'vmss' || this.name === 'acs' || this.name === 'managed-disk' || this.name === 'managed-snapshot' || this.name === 'managed-image') {
143 self.helpComputeCategories(-1, 0, this.name);
144 self.helpCommands();
145 } else {
146 self.helpCommands();
147 self.helpCategories(-1);
148 }
149
150 self.helpOptions();
151 self.showCommandMode();
152 return '';
153 },
154
155 commandHelpInformation: function(containsJson) {
156 var self = this;
157 if (containsJson) {
158 var result = self.getJSONMetadata(false);
159 if (!result) {
160 throw new Error(util.format('Unable to provide help in JSON format for \'%s\'', self.fullName()));
161 }
162 self.output.format({ json: true, level: 'data' });
163 return self.output.json(result);
164 }
165 if (self._detailedDescription) {
166 this.output.help(self.detailedDescription());
167 } else {
168 this.output.help(self.description());
169 }
170
171 this.output.help('');
172 this.output.help('Usage:', self.fullName() + ' ' + self.usage());
173
174 self.helpOptions();
175 self.showCommandMode();
176
177 return '';
178 },
179
180 helpJSON: function() {
181 var self = this;
182 var result = _.tap({}, function(res){
183 if(_.isNull(self.parent) || _.isUndefined(self.parent)){
184 res.name = pjson.name;
185 res.description = pjson.description;
186 res.author = pjson.author;
187 res.version = pjson.version;
188 res.contributors = pjson.contributors;
189 res.homepage = pjson.homepage;
190 res.licenses = pjson.licenses;
191 } else {
192 res.name = self.fullName();
193 res.description = self.description();
194 }
195 res.usage = self.usage();
196 });
197
198 if (this.categories && Object.keys(this.categories).length > 0) {
199 result.categories = {};
200
201 for (var name in this.categories) {
202 result.categories[name] = this.categories[name].helpJSON();
203 }
204 }
205
206 if (this.commands && this.commands.length > 0) {
207 result.commands = [];
208
209 this.commands.forEach(function (cmd) {
210 var command = {
211 name: cmd.fullName(),
212 description: cmd.description(),
213 options: cmd.options,
214 usage: cmd.usage()
215 };
216
217 result.commands.push(command);
218 });
219 }
220
221 return result;
222 },
223
224 helpCategories: function(levels) {
225 for (var name in this.categories) {
226 var cat = this.categories[name];
227
228 this.output.help('');
229
230 this.output.help(cat.description().cyan);
231
232 if (levels === -1 || levels > 0) {
233 for (var index in cat.commands) {
234 var cmd = cat.commands[index];
235 this.output.help(' ', cmd.fullName() + ' ' + cmd.usage());
236 }
237
238 cat.helpCategories(levels !== -1 ? --levels : -1);
239 } else {
240 this.output.help(' ', cat.fullName());
241 }
242 }
243 },
244
245 helpComputeCategories: function(levels, depth, catName) {
246 for (var name in this.categories) {
247 var cat = this.categories[name];
248 if (depth === 0) {
249 this.output.help('');
250 this.output.help(cat.description().cyan);
251 }
252
253 if (levels === -1 || levels > 0) {
254 // Compress command names only for 'vmss config' category, i.e.
255 // 'vmss config sku set' & 'vmss config sku delete' =>
256 // 'vmss config sku set|delete'
257 var index = 0;
258 var cmd = null;
259 if (depth >= 1 && utilsCore.stringStartsWith(cat.fullName(), catName + ' config ')) {
260 var commandNames = [];
261 for (index in cat.commands) {
262 cmd = cat.commands[index];
263 commandNames.push(cmd.name);
264 }
265 this.output.help(' ', cat.fullName() + ' ' + commandNames.join('|') + ' [options]');
266 }
267 else {
268 for (index in cat.commands) {
269 cmd = cat.commands[index];
270 this.output.help(' ', cmd.fullName() + ' ' + cmd.usage());
271 }
272 }
273
274 cat.helpComputeCategories(levels !== -1 ? --levels : -1, depth + 1, catName);
275 } else {
276 this.output.help(' ', cat.fullName());
277 }
278 }
279 },
280
281 helpCategoriesSummary: function(showMore) {
282 var self = this;
283 var categories = [];
284 function scan(parent, levels, each) {
285 for (var name in parent.categories) {
286 var cat = parent.categories[name];
287
288 each(cat);
289
290 if (levels === -1 || levels > 0) {
291 scan(cat, levels !== -1 ? --levels : -1, each);
292 }
293 }
294 }
295
296 scan(this, showMore ? -1 : 0, function (cat) { categories.push(cat); });
297 var maxLength = 14;
298
299 // Sort categories by alphabetical order
300 categories.sort(function (a, b) {
301 return (a.fullName() < b.fullName()) ? -1 : (a.fullName() > b.fullName()) ? 1 : 0;
302 });
303
304 categories.forEach(function (cat) {
305 if (maxLength < cat.fullName().length)
306 maxLength = cat.fullName().length;
307 });
308
309 self.output.help('');
310 self.output.help('Commands:');
311 categories.forEach(function (cat) {
312 var name = cat.fullName();
313 while (name.length < maxLength) {
314 name += ' ';
315 }
316
317 self.output.help(' ' + name + ' ' + cat.description().cyan);
318 });
319 },
320
321 helpCommands: function() {
322 var self = this;
323
324 this.commands.forEach(function (cmd) {
325 self.output.help('');
326 self.output.help(cmd.description().cyan);
327 self.output.help(' ', cmd.fullName() + ' ' + cmd.usage());
328 });
329 },
330
331 helpOptions: function() {
332 var self = this;
333 self.output.help('');
334 self.output.help('Options:');
335 self.optionHelp().split('\n').forEach(function (line) { self.output.help(' ', line); });
336 },
337
338 //enable the flag for auto-complete to list files under
339 //current working directory
340 fileRelatedOption: function (flags, description) {
341 this.option(flags, description);
342 this.options[this.options.length - 1].fileRelatedOption = true;
343 return this;
344 },
345
346 option: function (flags, description, fn, defaultValue) {
347 var self = this;
348
349 var option = new commander.Option(flags, description);
350
351 // Remove preexistant option with same name
352 self.options = self.options.filter(function (o) {
353 return o.name() !== option.name() || o.long !== option.long;
354 });
355
356 var oname = option.name();
357 var name = utilsCore.camelcase(oname);
358
359 if (!self.optionValues) {
360 self.optionValues = {};
361 }
362
363 // default as 3rd arg
364 if ('function' !== typeof fn) {
365 defaultValue = fn;
366 fn = null;
367 }
368
369 // preassign default value only for --no-*, [optional], or <required>
370 if (false === option.bool || option.optional || option.required) {
371 // when --no-* we make sure default is true
372 if (false === option.bool) defaultValue = true;
373 // preassign only if we have a default
374 if (undefined !== defaultValue) self.optionValues[name] = defaultValue;
375 }
376
377 // register the option
378 this.options.push(option);
379
380 // when it's passed assign the value
381 // and conditionally invoke the callback
382 this.on(oname, function (val) {
383 // coercion
384 if (val && fn) val = fn(val);
385
386 // unassigned or bool
387 if ('boolean' === typeof self.optionValues[name] || 'undefined' === typeof self.optionValues[name]) {
388 // if no value, bool true, and we have a default, then use it!
389 if (!val) {
390 self.optionValues[name] = option.bool ? defaultValue || true : false;
391 } else {
392 self.optionValues[name] = val;
393 }
394 } else if (val) {
395 // reassign
396 self.optionValues[name] = val;
397 }
398 });
399
400 return this;
401 },
402
403 getJSONMetadata: function (isCategoryHelp) {
404 //Local function
405 function findItem(arr, item) {
406 var filteredArray = arr.filter(function (element) {
407 if (element.name === item) {
408 return element;
409 }
410 });
411 return filteredArray[0];
412 }
413 //Actual function definition
414 var self = this;
415 var cmdMetadatafile = path.join(__dirname, '..', 'plugins.' + utilsCore.getMode() + '.json');
416 var data = fs.readFileSync(cmdMetadatafile);
417 var jsonInfo = {};
418 try {
419 jsonInfo = JSON.parse(data);
420 } catch (err) {
421 throw new Error(util.format('Error occurred while parsing the json file \'%s\': \n%s',
422 cmdMetadatafile, util.inspect(err, { depth: null })));
423 }
424
425 var categories = jsonInfo.categories;
426 var cmdLookup = self.fullName().trim().split(' ');
427 var cats = cmdLookup;
428 var command = '';
429 if (!isCategoryHelp) {
430 command = cmdLookup[cmdLookup.length - 1];
431 var topCmd = findItem(jsonInfo.commands, command);
432 if (topCmd) return topCmd;
433 cats = cmdLookup.slice(0, cmdLookup.length - 1);
434 }
435
436 for (var i = 0; i< cats.length; i++) {
437 if (categories[cats[i]]) {
438 if (i === cats.length - 1) {
439 categories = categories[cats[i]];
440 } else {
441 categories = categories[cats[i]].categories;
442 }
443 }
444 }
445
446 if (!isCategoryHelp) {
447 return findItem(categories.commands, command);
448 } else {
449 return categories;
450 }
451 }
452});
453
454module.exports = ExtendedCommand;