azure-cli
Version:
Microsoft Azure Cross Platform Command Line tool
462 lines (391 loc) • 13.8 kB
JavaScript
/**
* Copyright (c) Microsoft. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
//
// Extensions to Commander to support nested commands / help system
//
var _ = require('underscore');
var commander = require('commander');
var fs = require('fs');
var path = require('path');
var util = require('util');
var utilsCore = require('./utilsCore');
var pjson = require('../../package.json');
var Constants = require('./constants');
function ExtendedCommand(name) {
ExtendedCommand['super_'].call(this, name);
}
util.inherits(ExtendedCommand, commander.Command);
_.extend(ExtendedCommand.prototype, {
//////////////////////////////
// override help subsystem
fullName: function () {
var name = this.name;
var scan = this.parent;
while (scan && scan.parent !== undefined) {
name = scan.name ? scan.name + ' ' + name : name;
scan = scan.parent;
}
return name;
},
usage: function (str) {
var ret;
if (str) {
ret = commander.Command.prototype.usage.call(this, str);
} else {
ret = commander.Command.prototype.usage.call(this);
ret = ret.replace(/,/g,' ');
}
return ret;
},
helpInformation: function() {
var self = this;
if (!self.parent) {
var raw = self.normalize(process.argv.slice(2));
var containsJson = raw.some(function (item) { return item === '--json'; });
if (containsJson) {
self.output.format({ json: true, level: 'data' });
self.output.json(self.helpJSON());
return '';
}
var packagePath = path.join(__dirname, '../../package.json');
var packageInfo = JSON.parse(fs.readFileSync(packagePath));
if (raw.indexOf('-v') >= 0) {
console.log(packageInfo.version);
} else if (raw.indexOf('--version') >= 0) {
console.log(util.format('%s (node: %s)', packageInfo.version, process.versions.node));
} else {
self.setupCommandLogFormat(true);
self.output.info(' _ _____ _ ___ ___'.cyan);
self.output.info(' /_\\ |_ / | | | _ \\ __|'.cyan);
self.output.info(' _ ___'.grey + '/ _ \\'.cyan + '__'.grey + '/ /| |_| | / _|'.cyan + '___ _ _'.grey);
self.output.info('(___ '.grey + '/_/ \\_\\/___|\\___/|_|_\\___|'.cyan + ' _____)'.grey);
self.output.info(' (_______ _ _) _ ______ _)_ _ '.grey);
self.output.info(' (______________ _ ) (___ _ _)'.grey);
self.output.info('');
self.output.info('Microsoft Azure: Microsoft\'s Cloud Platform');
self.output.info('');
self.output.info('Tool version', packageInfo.version);
self.helpCommands();
self.helpCategoriesSummary(self.showMore());
self.helpOptions();
self.showCommandMode();
}
} else {
self.output.help(self.description());
self.output.help('');
self.output.help('Usage:', self.fullName() + ' ' + self.usage());
self.helpOptions();
self.showCommandMode();
}
return '';
},
showMoveToNewCli: function() {
this.output.help('');
this.output.help('Consider Azure CLI 2.0, the preferred choice for ARM - https://aka.ms/move2cli2');
},
showCommandMode: function(){
this.output.help('');
var mode = this._mode;
if (!mode) {
mode = utilsCore.getMode();
}
var text = util.format('Current Mode: %s (Azure %s Management)', mode,
(mode === Constants.API_VERSIONS.ASM ? 'Service' : 'Resource'));
this.output.help(text);
if (mode === Constants.API_VERSIONS.ARM) {
this.showMoveToNewCli();
}
},
showMore: function () {
var raw = this.normalize(process.argv.slice(2));
return raw.indexOf('--help') >= 0 || raw.indexOf('-h') >= 0;
},
categoryHelpInformation: function(containsJson) {
var self = this;
if (containsJson) {
var result = self.getJSONMetadata(true);
if (!result) {
throw new Error(util.format('Unable to provide help in JSON format for \'%s\'', self.fullName()));
}
self.output.format({ json: true, level: 'data' });
return self.output.json(result);
}
this.output.help(self.description());
if (this.name === 'vm' || this.name === 'vmss' || this.name === 'acs' || this.name === 'managed-disk' || this.name === 'managed-snapshot' || this.name === 'managed-image') {
self.helpComputeCategories(-1, 0, this.name);
self.helpCommands();
} else {
self.helpCommands();
self.helpCategories(-1);
}
self.helpOptions();
self.showCommandMode();
return '';
},
commandHelpInformation: function(containsJson) {
var self = this;
if (containsJson) {
var result = self.getJSONMetadata(false);
if (!result) {
throw new Error(util.format('Unable to provide help in JSON format for \'%s\'', self.fullName()));
}
self.output.format({ json: true, level: 'data' });
return self.output.json(result);
}
if (self._detailedDescription) {
this.output.help(self.detailedDescription());
} else {
this.output.help(self.description());
}
this.output.help('');
this.output.help('Usage:', self.fullName() + ' ' + self.usage());
self.helpOptions();
self.showCommandMode();
return '';
},
helpJSON: function() {
var self = this;
var result = _.tap({}, function(res){
if(_.isNull(self.parent) || _.isUndefined(self.parent)){
res.name = pjson.name;
res.description = pjson.description;
res.author = pjson.author;
res.version = pjson.version;
res.contributors = pjson.contributors;
res.homepage = pjson.homepage;
res.licenses = pjson.licenses;
} else {
res.name = self.fullName();
res.description = self.description();
}
res.usage = self.usage();
});
if (this.categories && Object.keys(this.categories).length > 0) {
result.categories = {};
for (var name in this.categories) {
result.categories[name] = this.categories[name].helpJSON();
}
}
if (this.commands && this.commands.length > 0) {
result.commands = [];
this.commands.forEach(function (cmd) {
var command = {
name: cmd.fullName(),
description: cmd.description(),
options: cmd.options,
usage: cmd.usage()
};
result.commands.push(command);
});
}
return result;
},
helpCategories: function(levels) {
for (var name in this.categories) {
var cat = this.categories[name];
this.output.help('');
this.output.help(cat.description().cyan);
if (levels === -1 || levels > 0) {
for (var index in cat.commands) {
var cmd = cat.commands[index];
this.output.help(' ', cmd.fullName() + ' ' + cmd.usage());
}
cat.helpCategories(levels !== -1 ? --levels : -1);
} else {
this.output.help(' ', cat.fullName());
}
}
},
helpComputeCategories: function(levels, depth, catName) {
for (var name in this.categories) {
var cat = this.categories[name];
if (depth === 0) {
this.output.help('');
this.output.help(cat.description().cyan);
}
if (levels === -1 || levels > 0) {
// Compress command names only for 'vmss config' category, i.e.
// 'vmss config sku set' & 'vmss config sku delete' =>
// 'vmss config sku set|delete'
var index = 0;
var cmd = null;
if (depth >= 1 && utilsCore.stringStartsWith(cat.fullName(), catName + ' config ')) {
var commandNames = [];
for (index in cat.commands) {
cmd = cat.commands[index];
commandNames.push(cmd.name);
}
this.output.help(' ', cat.fullName() + ' ' + commandNames.join('|') + ' [options]');
}
else {
for (index in cat.commands) {
cmd = cat.commands[index];
this.output.help(' ', cmd.fullName() + ' ' + cmd.usage());
}
}
cat.helpComputeCategories(levels !== -1 ? --levels : -1, depth + 1, catName);
} else {
this.output.help(' ', cat.fullName());
}
}
},
helpCategoriesSummary: function(showMore) {
var self = this;
var categories = [];
function scan(parent, levels, each) {
for (var name in parent.categories) {
var cat = parent.categories[name];
each(cat);
if (levels === -1 || levels > 0) {
scan(cat, levels !== -1 ? --levels : -1, each);
}
}
}
scan(this, showMore ? -1 : 0, function (cat) { categories.push(cat); });
var maxLength = 14;
// Sort categories by alphabetical order
categories.sort(function (a, b) {
return (a.fullName() < b.fullName()) ? -1 : (a.fullName() > b.fullName()) ? 1 : 0;
});
categories.forEach(function (cat) {
if (maxLength < cat.fullName().length)
maxLength = cat.fullName().length;
});
self.output.help('');
self.output.help('Commands:');
categories.forEach(function (cat) {
var name = cat.fullName();
while (name.length < maxLength) {
name += ' ';
}
self.output.help(' ' + name + ' ' + cat.description().cyan);
});
},
helpCommands: function() {
var self = this;
this.commands.forEach(function (cmd) {
self.output.help('');
self.output.help(cmd.description().cyan);
self.output.help(' ', cmd.fullName() + ' ' + cmd.usage());
});
},
helpOptions: function() {
var self = this;
self.output.help('');
self.output.help('Options:');
self.optionHelp().split('\n').forEach(function (line) { self.output.help(' ', line); });
},
//enable the flag for auto-complete to list files under
//current working directory
fileRelatedOption: function (flags, description) {
this.option(flags, description);
this.options[this.options.length - 1].fileRelatedOption = true;
return this;
},
option: function (flags, description, fn, defaultValue) {
var self = this;
var option = new commander.Option(flags, description);
// Remove preexistant option with same name
self.options = self.options.filter(function (o) {
return o.name() !== option.name() || o.long !== option.long;
});
var oname = option.name();
var name = utilsCore.camelcase(oname);
if (!self.optionValues) {
self.optionValues = {};
}
// default as 3rd arg
if ('function' !== typeof fn) {
defaultValue = fn;
fn = null;
}
// preassign default value only for --no-*, [optional], or <required>
if (false === option.bool || option.optional || option.required) {
// when --no-* we make sure default is true
if (false === option.bool) defaultValue = true;
// preassign only if we have a default
if (undefined !== defaultValue) self.optionValues[name] = defaultValue;
}
// register the option
this.options.push(option);
// when it's passed assign the value
// and conditionally invoke the callback
this.on(oname, function (val) {
// coercion
if (val && fn) val = fn(val);
// unassigned or bool
if ('boolean' === typeof self.optionValues[name] || 'undefined' === typeof self.optionValues[name]) {
// if no value, bool true, and we have a default, then use it!
if (!val) {
self.optionValues[name] = option.bool ? defaultValue || true : false;
} else {
self.optionValues[name] = val;
}
} else if (val) {
// reassign
self.optionValues[name] = val;
}
});
return this;
},
getJSONMetadata: function (isCategoryHelp) {
//Local function
function findItem(arr, item) {
var filteredArray = arr.filter(function (element) {
if (element.name === item) {
return element;
}
});
return filteredArray[0];
}
//Actual function definition
var self = this;
var cmdMetadatafile = path.join(__dirname, '..', 'plugins.' + utilsCore.getMode() + '.json');
var data = fs.readFileSync(cmdMetadatafile);
var jsonInfo = {};
try {
jsonInfo = JSON.parse(data);
} catch (err) {
throw new Error(util.format('Error occurred while parsing the json file \'%s\': \n%s',
cmdMetadatafile, util.inspect(err, { depth: null })));
}
var categories = jsonInfo.categories;
var cmdLookup = self.fullName().trim().split(' ');
var cats = cmdLookup;
var command = '';
if (!isCategoryHelp) {
command = cmdLookup[cmdLookup.length - 1];
var topCmd = findItem(jsonInfo.commands, command);
if (topCmd) return topCmd;
cats = cmdLookup.slice(0, cmdLookup.length - 1);
}
for (var i = 0; i< cats.length; i++) {
if (categories[cats[i]]) {
if (i === cats.length - 1) {
categories = categories[cats[i]];
} else {
categories = categories[cats[i]].categories;
}
}
}
if (!isCategoryHelp) {
return findItem(categories.commands, command);
} else {
return categories;
}
}
});
module.exports = ExtendedCommand;