UNPKG

beame-cli

Version:

Beame.io - common CLI handling

208 lines (170 loc) 5.79 kB
'use strict'; 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;