beame-cli
Version:
Beame.io - common CLI handling
208 lines (170 loc) • 5.79 kB
JavaScript
;
const path = require('path');
function getParamsNames(fun) {
const names = fun.toString().match(/^[\s(]*function[^(]*\(([^)]*)\)/)[1]
.replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '')
.replace(/\s+/g, '').split(',');
let ret = (names.length == 1 && !names[0] ? [] : names);
ret = ret.filter(paramName => paramName != 'callback');
ret.hasFormat = !!fun.toText;
return ret;
}
function eachkv(hash, cb) {
for(let k in hash) {
cb(k, hash[k])
}
}
class BeameCli {
constructor(myName, modulesDir, modulesNames) {
this.myName = myName;
this.modulesDir = modulesDir;
this.modulesNames = modulesNames;
this.commands = {};
this.globalSchema = {};
}
setGlobalSchema(globalSchema) {
this.globalSchema = globalSchema;
}
loadModules() {
this.modulesNames.forEach(mod_name => {
this.commands[mod_name] = require(path.join(this.modulesDir, mod_name + '.js'));
});
}
parseArgv() {
this.argv = require('minimist')(process.argv.slice(2));
}
printUsage() {
console.log('Usage:');
eachkv(this.commands, (cmdName, subCommands) => {
eachkv(subCommands, (subCmdName, subCmdFunc) => {
const parametersSchema = Object.assign({}, this.globalSchema, subCmdFunc.params);
if (!parametersSchema) {
throw new Error(`Internal coding error: missing parametersSchema for command ${cmdName} ${subCmdName}`);
}
let paramsNames = getParamsNames(subCmdFunc);
if (paramsNames.hasFormat) {
paramsNames.push('format');
}
let params = paramsNames.map(paramName => {
let ret = '--' + paramName;
if (!parametersSchema[paramName]) {
console.log(`Internal coding error: missing ${paramName} for command ${cmdName} ${subCmdName}`);
throw new Error(`Internal coding error: missing ${paramName} for command ${cmdName} ${subCmdName}`);
}
if (parametersSchema[paramName].options) {
ret = ret + ' {' + parametersSchema[paramName].options.join('|') + '}';
} else {
ret = ret + ' ' + paramName;
}
if (!parametersSchema[paramName].required) {
ret = '[' + ret + ']';
}
return ret;
});
console.log(' ' + this.myName + ' ' + cmdName + ' ' + subCmdName + ' ' + params.join(' '));
});
});
this.usage();
}
run() {
this.parseArgv();
this.loadModules();
if(this.argv._.length < 2) {
this.printUsage();
process.exit(1);
}
let cmdName = this.argv._[0],
subCmdName = this.argv._[1],
cmd = this.commands[cmdName];
if (!cmd) {
console.error("Command '" + cmdName + "' not found. Valid top-level commands are: " + Object.keys(this.commands));
process.exit(1);
}
if (!this.commands[cmdName][subCmdName]) {
console.error("Sub-command '" + subCmdName + "' for command '" + cmdName + "' not found. Valid sub-commands are:");
for(let k in this.commands) {
console.error(" " + k);
}
process.exit(1);
}
if(!this.approveCommand(cmdName, subCmdName)) {
process.exit(1);
}
// TODO: handle boolean such as in "--fqdn --some-other-switch" or "--no-fqdn"
// Validate argv and build arguments for the function
const subCmdFunc = this.commands[cmdName][subCmdName];
let paramsNames = getParamsNames(subCmdFunc);
// if (paramsNames.hasFormat) {
// paramsNames.push('format');
// }
let args = paramsNames.map(paramName => {
const parametersSchema = Object.assign({}, this.globalSchema, subCmdFunc.params);
// Required parameter missing
if (parametersSchema[paramName].required && !(paramName in this.argv)) {
console.error("Command '" + cmdName + ' ' + subCmdName + "' - required argument '" + paramName + "' is missing.");
process.exit(1);
}
// Optional parameter missing
if (!parametersSchema[paramName].required && !(paramName in this.argv)) {
if (parametersSchema[paramName].default) {
return parametersSchema[paramName].default;
}
return null;
}
// Parameter must be one of the specified values ("options")
if (parametersSchema[paramName].options) {
if (!parametersSchema[paramName].options.includes(this.argv[paramName])) {
console.error("Command '" + cmdName + ' ' + subCmdName + "' - argument '" + paramName + "' must be one of: " + parametersSchema[paramName].options.join(','));
process.exit(1);
}
}
let arg = this.argv[paramName];
// Optionally decode base64-encoded argument.
// Do not decode what appears to be JSON.
if (parametersSchema[paramName].base64 && arg[0] != '{' && arg[0] != '"' && arg[0] != '[') {
arg = new Buffer(arg, 'base64').toString();
}
if (parametersSchema[paramName].json) {
//noinspection ES6ModulesDependencies,NodeModulesDependencies
arg = JSON.parse(arg);
}
return arg;
});
/**
*
* @param {Object} error
* @param {Object} output
*/
const commandResultsReady = (error, output) => {
if (error) {
console.error('=== Error occured. Details follow =======================');
if (error instanceof Error) {
console.error(error.stack);
} else {
console.error(error);
}
console.error('=========================================================');
process.exit(1);
}
if (output === undefined) {
return;
}
if (this.argv.format == 'json' || !this.commands[cmdName][subCmdName].toText) {
//noinspection ES6ModulesDependencies,NodeModulesDependencies
output = JSON.stringify(output);
} else {
output = this.commands[cmdName][subCmdName].toText(output).toString();
}
console.log(output);
}
// Run the command
args.push(commandResultsReady);
this.commands[cmdName][subCmdName].apply(null, args);
} // run()
usage() {
}
approveCommand() {
return true;
}
} // BeameCli
module.exports = BeameCli;