fargv
Version:
Multi-customizable parser of process.argv for nodejs.
549 lines (315 loc) • 12.4 kB
JavaScript
const isObject = require("../dependencies/isObject");
const isEmptyObject = require("../dependencies/isEmptyObject");
const ObjectKeys = require("../dependencies/getObjectKeys");
const copyV = require("../dependencies/copyValWithoutBind");
const plural = require("../dependencies/possiblePlural");
const terminalWidth = (function () {
if (typeof process === 'object' && process.stdout && process.stdout.columns) {
return process.stdout.columns
}
return 80
})();
const terminalWidthOnePercent = terminalWidth / 100;
const flagsColumnWidth = Math.round(terminalWidth / 3);
//first - in percents, then - in pixels
const commandsColumnWidth = {
name: 30,
desc: 45,
usage: 25,
};
for(const prop in commandsColumnWidth) {
commandsColumnWidth[prop] = Math.round(terminalWidthOnePercent * commandsColumnWidth[prop])
}
const includeCommand = require("./includeCommand");
const cliui = require("cliui");
const examplesAndFlags = ["examples", "flags"];
const getHelpCommandPrefix = command => `\nHelp for command "${command}"`;
const getHelpFlagPrefix = flag => `\nHelp for flag "${flag}"`;
const getDescRequiredDeprecated = val => typeof val == "boolean" ? "" : ": " + val;
//flagValue: plainObject<usableOptions.help.flags[flagName]>; returns: array<string...,>
const parseFlagRightHelp = function(flagValue) {
const rightHints = [];
if(flagValue.required) {
rightHints[rightHints.length] = `[required${getDescRequiredDeprecated(flagValue.required)}]`;
}
if(flagValue.deprecated) {
rightHints[rightHints.length] = `[deprecated${getDescRequiredDeprecated(flagValue.deprecated)}]`;
}
if(flagValue.type) {
switch(flagValue.type) {
case "string": case "number": case "array": case "object": case "bigint": case "boolean":
rightHints[rightHints.length] = `[${flagValue.type}]`;
break;
}
}
if(flagValue.empty == false) {
rightHints[rightHints.length] = "[must be with value]";
} else if(flagValue.empty == true) {
rightHints[rightHints.length] = "[must be empty]";
}
return rightHints;
};
//flagName: string, defaultArgv: plainObject<usableOptions.defaultArgv>; returns: array<string...,>
const getAliases = function(flagName, defaultArgv) {
let aliases = [];
if(Array.isArray(defaultArgv[flagName])) {
aliases = copyV(defaultArgv[flagName][1]);
}
return aliases
};
/*
ui: cliui(),
flagName: string,
flagValue: plainObject<usableOptions.help.flags[flagName]>,
aliases: array<string...,>,
rightHints: array<string...>,
topPadding: number | undefined,
bottomPadding: number | undefined,
*/
const addFlagBlock = (ui, flagName, flagValue, aliases, rightHints, topPadding, bottomPadding) => {
if(!ui || !flagName || !flagValue) return;
aliases = Array.isArray(aliases) ? aliases : [];
rightHints = Array.isArray(rightHints) ? rightHints : [];
topPadding = typeof topPadding == "undefined" ? 1 : topPadding;
bottomPadding = typeof bottomPadding == "undefined" ? 1 : bottomPadding;
ui.div({
text: `--${flagName}${aliases.length ? ", alias" + (aliases.length > 1 ? "es: " : ": ") + aliases.map(el => (el.length > 1 ? "--" : "-") + el).join(", ") : ""}`,
padding: [topPadding, 0, bottomPadding, 2],
width: flagsColumnWidth
}, {
text: typeof flagValue.desc == "string" ? flagValue.desc : `No description`,
padding: [topPadding, 0, bottomPadding, 2],
width: flagsColumnWidth,
}, {
text: rightHints.map((el, i) => {
if(rightHints.length == 1 || !rightHints[i + 1]) return el;
return el + "\n"
}).join(""),
padding: [topPadding, 0, bottomPadding, 4],
width: flagsColumnWidth
});
};
//ui: cliui(), examples: array<string...,>, customHeader: null | string | undefined
const addExamplesBlock = (ui, examples, customHeader) => {
if(customHeader !== null) {
if(typeof customHeader == "string") {
ui.span(customHeader);
} else {
ui.span(
plural("Example", examples) + ":"
);
}
}
if(Array.isArray(examples)) {
ui.div("\n");
for(let i = 0; i < examples.length; i++) {
const example = examples[i];
if(typeof example == "string") {
ui.span(example);
}
}
ui.span();
} else {
const l = ObjectKeys(examples).length;
for(const exampleName in examples) {
const exampleValue = examples[exampleName];
ui.span((l > 1 ? "\n" : "") + "--" + exampleName + ":");
addExamplesBlock(ui, Array.isArray(exampleValue) ? exampleValue : [exampleValue], null);
}
}
};
/*
ui: cliui(),
usableOptions: plainObject<usableOptions>,
flags: array<string...,> | null,
forSpecific: boolean,
missingInsteadNotSet: boolean,
*/
const showAllFlags = (ui, usableOptions, flags, forSpecific, missingInsteadNotSet) => {
const hOptions = usableOptions.help;
const showExamples = forSpecific ? hOptions.showExamplesForSpecific : hOptions.showExamples;
const canHaveAliases = isObject(usableOptions.defaultArgv);
ui.div("\nFlags:");
let justFindAnyFlag = false;
if(isObject(hOptions.flags)) {
if(flags === null) flags = ObjectKeys(hOptions.flags);
for(let i = 0; i < flags.length; i++) {
const flagName = flags[i];
if(!isObject(hOptions.flags[flagName])) continue;
const flagValue = copyV(hOptions.flags[flagName]);
const rightHints = parseFlagRightHelp(flagValue);
const aliases = canHaveAliases ? getAliases(flagName, usableOptions.defaultArgv) : [];
const topPadding = !justFindAnyFlag ? 1 : 0;
const bottomPadding = (i + 1) == flags.length ? 0 : 1;
addFlagBlock(ui, flagName, flagValue, aliases, rightHints, topPadding, bottomPadding);
if(
flagValue.examples
&&
(showExamples == true || showExamples == "flags")
) {
const examples = Array.isArray(flagValue.examples) ? Object.assign([], flagValue.examples) : [flagValue.examples];
if(isObject(justFindAnyFlag)) justFindAnyFlag[flagName] = examples;
else justFindAnyFlag = { [flagName]: examples };
} else if(!justFindAnyFlag) {
justFindAnyFlag = true;
}
}
}
if(!justFindAnyFlag) {
ui.div(missingInsteadNotSet ? "no flags set" : "present but not set");
} else if(isObject(justFindAnyFlag)) {
ui.span();
addExamplesBlock(ui, justFindAnyFlag, `${plural("Flag example", justFindAnyFlag)}:\n`);
}
};
//usableOptions: plainObject<usableOptions>, flagName: string, fromAlias: string | undefined
const showFlagHelp = function (usableOptions, flagName, fromAlias) {
const hOptions = usableOptions.help;
const ui = cliui();
if(fromAlias) {
ui.div(getHelpFlagPrefix(fromAlias) + ` (alias of ${flagName}):`);
} else {
ui.div(getHelpFlagPrefix(flagName) + ":");
}
if(!isObject(hOptions.flags[flagName])) {
ui.div("present but not set");
} else {
const flagValue = copyV(hOptions.flags[flagName]);
const rightHints = parseFlagRightHelp(flagValue);
const aliases = getAliases(flagName, usableOptions.defaultArgv);
addFlagBlock(ui, flagName, flagValue, aliases, rightHints, 1, 0);
if(
flagValue.examples
&&
(hOptions.showExamplesForSpecific == true || hOptions.showExamplesForSpecific == "flags")
) {
if (Array.isArray(flagValue.examples)) {
ui.span();
addExamplesBlock(ui, flagValue.examples);
} else if (typeof flagValue.examples == "string") {
ui.span();
addExamplesBlock(ui, [flagValue.examples]);
}
}
}
ui.toString() && console.log(ui.toString());
};
//ui: cliui(), hOptions: plainObject<usableOptions.help>
const showAllCommands = function (ui, hOptions) {
ui.span();
ui.div("Commands(name | description | usage):");
for(const commandName in hOptions.commands) {
const hCommand = hOptions.commands[commandName];
const description = typeof hCommand.desc == "string" ? hCommand.desc : "No description";
const usage = typeof hCommand.usage == "string" ? hCommand.usage : "No usage desc";
const aliases = Array.isArray(hCommand.alias) ? hCommand.alias : [];
ui.span();
ui.div({
text: `${commandName}${aliases.length ? ", alias" + (aliases.length > 1 ? "es: " : ": ") + aliases.join(", ") : ""}`,
padding: [0, 0, 0, 2],
width: commandsColumnWidth.name,
}, {
text: description,
padding: [0, 0, 0, 2],
width: commandsColumnWidth.desc,
}, {
text: usage,
padding: [0, 0, 0, 2],
width: commandsColumnWidth.usage,
})
}
};
//exitProcess: boolean, rememberAllCommands: array<string...,>, rememberAllFlags: plainObject = { ...[prop]: 1 }
const showMainHelp = function(exitProcess, rememberAllCommands, rememberAllFlags) {
let showAll = true;
const hOptions = this.usableOptions.help;
//show help for specific command
if(rememberAllCommands.length && hOptions.showForSpecificCommand && isObject(hOptions.commands)) {
const commands = ObjectKeys(hOptions.commands).sort((a, b) => b.split(" ").length - a.split(" ").length);
const argvCommandsToStr = rememberAllCommands.join(" ");
for(let i = 0; i < commands.length; i++) {
const command = commands[i];
if(!isObject(hOptions.commands[command]) || isEmptyObject(hOptions.commands[command])) continue;
if(includeCommand(argvCommandsToStr, command)) {
showAll = false;
const helpToThisCommand = copyV(hOptions.commands[command]);
//desc, usage, alias, examples, flags
const hints = [];
if(typeof helpToThisCommand.desc == "string") {
hints[0] = helpToThisCommand.desc;
}
if(typeof helpToThisCommand.usage == "string") {
hints[1] = helpToThisCommand.usage;
}
if(Array.isArray(helpToThisCommand.alias)) {
hints[2] = " (alias"
+ (helpToThisCommand.alias.length > 1 ? "es" : "")
+ ": "
+ helpToThisCommand.alias.join(", ")
+ "):";
}
for(let i = 3; i <= 4; ++i) {
const whatOf = examplesAndFlags[i - 3];
if(Array.isArray(helpToThisCommand[whatOf])) {
hints[i] = helpToThisCommand[whatOf];
} else if(typeof helpToThisCommand[whatOf] == "string") {
hints[i] = [helpToThisCommand[whatOf]];
}
}
const ui = hints.length ? cliui() : false;
if(ui) {
ui.span(getHelpCommandPrefix(command) + (hints[2] ? hints[2] : ":"));
if(hints[0]) ui.div("\n\nDescription: " + hints[0]);
if(hints[1]) ui.div((hints[0] ? "\nU" : "u") + "sage: " + hints[1]);
if(
hints[3]
&&
(hOptions.showExamplesForSpecific == true || hOptions.showExamplesForSpecific == "commands")
) {
ui.span();
addExamplesBlock(ui, hints[3]);
}
if(hints[4]) {
showAllFlags(ui, this.usableOptions, hints[4], true);
}
ui.toString() && console.log(ui.toString());
}
}
}
} else if(
//show help for specific flag
hOptions.showForSpecificFlag &&
!rememberAllCommands.length &&
isObject(this.usableOptions.help.flags) &&
//slice -h
ObjectKeys(rememberAllFlags).slice(0, -1).length == 1
) {
const helpForFlagName = ObjectKeys(rememberAllFlags)[0];
if(hOptions.flags[helpForFlagName]) {
showAll = false;
showFlagHelp(this.usableOptions, helpForFlagName, false);
} else if(isObject(this.usableOptions.defaultArgv)) {
for(const name in this.usableOptions.defaultArgv) {
if(Array.isArray(this.usableOptions.defaultArgv[name]) && ~this.usableOptions.defaultArgv[name][1].indexOf(helpForFlagName)) {
showAll = false;
showFlagHelp(this.usableOptions, name, helpForFlagName);
}
}
}
}
if(showAll) {
//show all help
const ui = cliui();
if(hOptions.mainUsage) ui.div("\nUsage: " + hOptions.mainUsage);
if(hOptions.mainDesc) ui.div("\nDescription: " + hOptions.mainDesc);
if(isObject(hOptions.commands) && hOptions.showMainCommands) {
showAllCommands(ui, hOptions);
}
hOptions.showMainFlags && showAllFlags(ui, this.usableOptions, null, false, true);
typeof hOptions.mainCustomEndText == "string" && ui.span("\n" + hOptions.mainCustomEndText);
ui.toString() && console.log(ui.toString());
}
console.log("");
if(exitProcess) process.exit(0);
};
module.exports = showMainHelp;