craydent-cli
Version:
Node module to manage command line execution and arguments
456 lines (453 loc) • 18.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const craydent_error_1 = require("craydent.error");
const craydent_isarray_1 = require("craydent.isarray");
const craydent_isasync_1 = require("craydent.isasync");
const craydent_isfunction_1 = require("craydent.isfunction");
const craydent_isgenerator_1 = require("craydent.isgenerator");
const craydent_isnull_1 = require("craydent.isnull");
const craydent_isobject_1 = require("craydent.isobject");
const craydent_logit_1 = require("craydent.logit");
const craydent_parseadvanced_1 = require("craydent.parseadvanced");
const craydent_parseboolean_1 = require("craydent.parseboolean");
const craydent_strip_1 = require("craydent.strip");
const craydent_syncroit_1 = require("craydent.syncroit");
const craydent_tryeval_1 = require("craydent.tryeval");
const _getFuncName_1 = require("../protected/_getFuncName");
const craydent_include_1 = require("craydent.include");
const syncro = craydent_syncroit_1.default;
function _cli_exec(command, options, callback) {
let child = (0, craydent_include_1.default)('child_process');
if ((0, craydent_isfunction_1.default)(options)) {
callback = options;
options = undefined;
}
return new Promise(function (res, rej) {
try {
if (!command) {
res(false);
}
options = options || {};
options.silent = !!options.silent;
let output = '';
const cprocess = child.exec(command, { env: process.env, maxBuffer: 20 * 1024 * 1024 }, function (err) {
const fin = !err || options.alwaysResolve ? res : rej;
if (options.outputOnly) {
if (callback) {
callback.call(cprocess, output);
}
return fin(output);
}
if (callback) {
const code = err ? err.code : 0;
callback.call(cprocess, code, output);
return !code ? fin(undefined) : fin({ code, output });
}
/* istanbul ignore next */
let code = err ? err.code : 0;
fin({ code, output });
});
cprocess.stdout.on('data', function (data) {
output += data;
if (!options.silent) {
process.stdout.write(data);
}
});
cprocess.stderr.on('data', function (data) {
output += data;
if (!options.silent) {
process.stdout.write(data);
}
});
}
catch (e) /* istanbul ignore next */ {
craydent_error_1.default && (0, craydent_error_1.default)('CLI.exec', e);
}
});
}
function add(opt, self) {
try {
opt.command = opt.command || "*";
let processedOptions = processOptions(opt, self), popt = processedOptions.options, options, value = processedOptions.value; // = self.Options;
options = self.Commands[opt.command] = self.Commands[opt.command] || [];
self._commandIndex.push(opt.command);
if (!(0, craydent_isnull_1.default)(self._potentialCommand) && self._potentialCommand == opt.command) {
self.CommandName = self._potentialCommand;
self._potentialCommand = null;
}
if (self.CommandName == opt.command) {
/* istanbul ignore else */
if (self.Arguments[0] == self.CommandName && !self._commandRemoved) {
self._commandRemoved = true;
self.Arguments.splice(0, 1);
}
}
// let val = !self.UsingLabels && self.Arguments[options.length], v;
let v;
for (let i = 0, len = popt.length; i < len /* && !isNull(value) */; i++) {
if (!(0, craydent_isnull_1.default)(v)) {
self[popt[i]._property] = v;
continue;
}
// if (isNull(val)) {
v = !(0, craydent_isnull_1.default)(value) ? value : opt.default;
// }
switch (popt[i].type.toLowerCase()) {
case "number":
v = Number(v);
v = isNaN(v) ? Number(popt[i].default) : v;
if (isNaN(v)) {
v = undefined;
}
break;
case "array":
case "object":
v = (0, craydent_tryeval_1.default)(v, craydent_parseadvanced_1.default) || v;
break;
case "bool":
case "boolean":
const tmp = (0, craydent_parseboolean_1.default)(v);
v = (0, craydent_isnull_1.default)(tmp) ? v : tmp;
break;
}
self[popt[i]._property] = v;
}
if (!opt.command || opt.command == '*') {
self.Options.push(opt);
}
options.push(opt);
}
catch (e) /* istanbul ignore next */ {
(0, craydent_error_1.default)('CLI.add', e);
}
return self;
}
function processOptions(option, self) {
/* istanbul ignore if */
if (!(0, craydent_isobject_1.default)(option)) {
throw `Error: Option [${JSON.stringify(option)}] must be an object. Option will be ignored.`;
}
// if (!option.option) { return { options: [option] }; }
let o = option.option.split(',');
let options = [], value;
for (let i = 0, len = o.length; i < len; i++) {
let prop = (0, craydent_strip_1.default)(o[i], '-');
// if (prop != "name" && self[prop]) { value.value = self[prop]; }
if (self[prop]) {
value = self[prop];
}
options.push({
option: o[i],
type: option.type || 'string',
description: option.description,
default: option.default,
command: option.command,
required: option.required,
_property: prop
});
}
return { options, value };
}
function renderOptions(options, extratabs) {
extratabs = extratabs || "";
let content = "", nlinetab = "\n\t";
for (let i = 0, len = options.length; i < len; i++) {
/* istanbul ignore next */
let option = options[i], optnames = (option.option || "").split(","), sep = "";
if (optnames.length > 1) {
optnames.sort(function (a, b) { return a.length - b.length; });
sep = ",";
}
content += nlinetab + extratabs + optnames[0] + sep;
content += `\t\t${option.type ? `(${option.type})` : "(string)"} ${option.description}${option.required ? "(required)" : ""}`;
for (let j = 1, jlen = optnames.length; j < jlen; j++) {
/* istanbul ignore else */
if (j + 1 == jlen) {
sep = "";
}
content += nlinetab + extratabs + optnames[j] + sep;
}
}
return content;
}
;
function validate(self) {
/* istanbul ignore next */
self.CommandName = self.CommandName || '*';
/* istanbul ignore next */
let options = self.Commands[self.CommandName] || [];
for (let i = 0, len = options.length; i < len; i++) {
const option = options[i], copt = (0, craydent_strip_1.default)(option.option.split(',')[0], '-');
/* istanbul ignore else */
if (self[copt] === undefined) {
/* istanbul ignore next */
self[copt] = self.UsingLabels && self.Arguments[i] || option.default;
}
/* istanbul ignore else */
if (option.required && (0, craydent_isnull_1.default)(self[copt])) {
throw `Option ${option.option} is required.`;
}
else if (option.type && !(0, craydent_isnull_1.default)(self[copt])) {
let type = option.type.toLowerCase();
switch (type) {
case "number":
self[copt] = isNaN(Number(self[copt])) ? self[copt] : Number(self[copt]);
break;
case "array":
case "object":
self[copt] = (0, craydent_tryeval_1.default)(self[copt], craydent_parseadvanced_1.default) || self[copt];
break;
case "bool":
case "boolean":
type = 'boolean';
const tmp = (0, craydent_parseboolean_1.default)(self[copt]);
self[copt] = (0, craydent_isnull_1.default)(tmp) ? self[copt] : tmp;
break;
}
if ((0, _getFuncName_1.default)(self[copt].constructor).toLowerCase() != type) {
throw `Option ${option.option} must be a ${option.type}.`;
}
}
}
}
class CLI {
constructor(params) {
/*|{
"info": "CLI parser for arguments and simplem method to execute shell commands",
"category": "CLI",
"parameters":[],
"overloads":[
{"parameters":[
{"options": "(CLIOption[]) Array of options having properties option(required:command option ex: -c), type(data type returned using typeof, ex:string), description, required(default:false)."}]}],
"url": "http://www.craydent.com/library/1.9.3/docs#CLI",
"returnType": "(CLI)"
}|*/
params = params || {};
let args = process.argv, self = this, cself = self, sindex = 2;
this._commandIndex = [];
this._potentialCommand = args[sindex];
this._commandRemoved = false;
this.Interpreter = args[0];
this.ScriptPath = args[1];
this.ScriptName = this.ScriptPath.substring(this.ScriptPath.lastIndexOf('/') + 1);
this.Name = params.name || "";
this.Info = params.info || "";
this.Synopsis = params.synopsis || "";
this.Copyright = params.copyright || "";
this.OptionsDescription = params.optionsDescription || "";
this.Description = params.description || "";
this.UsingLabels = true;
this.CommandName = "";
this.Commands = params.commands || { /*command:[options]*/};
this.Commands['*'] = self.Commands['*'] || [];
this.Options = [ /*{
option: "-c",
type:"string",
description:"",
default:"",
command:"",
required:false
}*/];
this.Arguments = [];
this.Notes = params.notes || "";
this.isMan = false;
this.isHelp = false;
this.waitForPending = [];
const command = args[2];
if (!~self._commandIndex.indexOf('*') && command && command[0] == "-") {
self.CommandName = "*";
}
else if (command == 'man' || command == 'help') {
self.CommandName = command;
self.isHelp = command == 'help'; // requesting help
self.isMan = command == 'man'; // requesting man
sindex++;
}
for (let i = sindex, len = args.length; i < len; i++) {
let arg = args[i];
/* istanbul ignore else */
if (arg[0] != '-') { // no label
self.UsingLabels = false;
cself[arg] = arg;
self.Arguments.push(arg);
}
else if (arg.startsWith('--')) { // this is a multi char label
// check if next arg is an option. if it is then this is a flag
if (!args[i + 1] || args[i + 1][0] == '-') {
cself[(0, craydent_strip_1.default)(arg, '-')] = true;
continue;
}
i++;
// add the value of the option as a property
cself[(0, craydent_strip_1.default)(arg, '-')] = args[i];
self.Arguments.push(arg);
self.Arguments.push(args[i]);
}
else if (arg.startsWith('-')) { // this is a single char label
const opts = (0, craydent_strip_1.default)(arg, '-');
if (opts.length == 1) {
// check if next arg is an option. if it is then this is a flag
if (!args[i + 1] || args[i + 1][0] == '-') {
cself[opts] = true;
continue;
}
i++;
cself[opts] = args[i];
self.Arguments.push(arg);
self.Arguments.push(args[i]);
continue;
}
for (var j = 0, jlen = opts.length; j < jlen; j++) {
cself[opts[j]] = true;
}
}
}
if (params.options) {
for (var i = 0, len = params.options.length; i < len; i++) {
this.add(params.options[i]);
}
}
}
isValid() {
try {
this.validate();
return true;
}
catch (e) {
(0, craydent_logit_1.default)(e);
return false;
}
}
validate() {
validate(this);
}
add(opt) {
add(opt, this);
}
option(opt) {
add(opt, this);
}
command(cmd, opts) {
try {
/* istanbul ignore next */
opts = opts || [];
let args = process.argv;
if ((0, craydent_isnull_1.default)(cmd)) {
throw "Command name must be provided. This operation will be ignored.";
}
let cindexCMD = cmd.split(/\s/)[0];
this._commandIndex.push(cindexCMD);
/* istanbul ignore else */
if (args[2] == cindexCMD) {
this.CommandName = cindexCMD;
}
/* istanbul ignore else */
if (!(0, craydent_isarray_1.default)(opts)) {
opts = [opts];
}
for (let i = 0, len = opts.length; i < len; i++) {
var opt = opts[i];
/* istanbul ignore next */
if (!(0, craydent_isobject_1.default)(opt)) {
(0, craydent_error_1.default)('CLI.command', new Error(`Option [${JSON.stringify(opt)}] must be an object. Option will be ignored.`));
continue;
}
opt.command = cmd;
add(opt, this);
}
/* istanbul ignore next */
this.Commands[cmd] = this.Commands[cmd] || [];
return this;
}
catch (e) {
craydent_error_1.default && (0, craydent_error_1.default)('CLI.command', e);
throw e;
}
}
action(name, cb) {
let args = process.argv, self = this;
/* istanbul ignore else */
if ((0, craydent_isfunction_1.default)(name) || (0, craydent_isgenerator_1.default)(name) || (0, craydent_isasync_1.default)(name)) {
cb = name;
name = self._commandIndex[self._commandIndex.length - 1];
}
/* istanbul ignore else */
if (self.CommandName == name) {
if ((0, craydent_isgenerator_1.default)(cb)) {
eval('self.waitForPending.push(syncro(function*(){ return yield* cb.call(self,args[2]); }));');
}
else if ((0, craydent_isasync_1.default)(cb)) {
eval('self.waitForPending.push((async function (){ return await cb.call(self,args[2]); })());');
}
else {
cb.call(self, name);
}
}
return self;
}
;
renderMan() {
try {
let nlinetab = "\n\t", dline = "\n\n";
let commands = "", self = this;
for (var prop in self.Commands) {
/* istanbul ignore if */
if (!self.Commands.hasOwnProperty(prop)) {
continue;
}
if (prop == '*') {
continue;
}
if (!commands) {
commands = `ADDITIONAL COMMANDS${nlinetab}`;
}
commands += `${prop}${renderOptions(self.Commands[prop], '\t')}\n${nlinetab}`;
}
commands += "\n";
let content = `NAME${nlinetab}${self.Name}${(self.Info ? " -- " + self.Info : "")}${dline}`;
content += `SYNOPSIS${nlinetab}${self.Synopsis}${dline}`;
content += `DESCRIPTION${nlinetab}${self.Description}${dline}`;
content += `OPTIONS${renderOptions(self.Options)}${dline}`;
content += commands;
content += `NOTES${nlinetab}${self.Notes}${dline}`;
return content;
}
catch (e) /* istanbul ignore next */ {
(0, craydent_error_1.default)('CLI.renderMan', e);
return "";
}
}
renderHelp() {
try {
let nlinetab = "\n\t", dline = "\n\n", self = this;
let commands = "";
let hasOptions = !!self.Options.length;
for (let prop in self.Commands) {
/* istanbul ignore if */
if (!self.Commands.hasOwnProperty(prop)) {
continue;
}
if (prop == '*') {
continue;
}
commands += `${prop}${renderOptions(self.Commands[prop], '\t')}\n${nlinetab}`;
hasOptions = hasOptions || !!self.Commands[prop].length;
}
let content = `Description: ${self.Synopsis}${dline}`;
content += `Usage: ${self.ScriptName}${(commands && " [command]")}${(hasOptions ? " [options]" : "")}${nlinetab}${commands}\n`;
content += `Options:${renderOptions(self.Options)}${dline}`;
return content;
}
catch (e) /* istanbul ignore next */ {
(0, craydent_error_1.default)('CLI.renderMan', e);
return "";
}
}
static exec(command, options, callback) {
return _cli_exec(command, options, callback);
}
exec(command, options, callback) {
return CLI.exec(command, options, callback);
}
}
exports.default = CLI;