/* globals Wsh: false */
/* globals process: false */
(function () {
if (Wsh && Wsh.Commander) return;
/**
* The Command-Prompt Interfaces for WSH (Windows Script Host). Similar to {@link https://github.com/tj/commander.js|Commander.js}.
*
* @namespace Commander
* @memberof Wsh
* @requires {@link https://github.com/tuckn/WshModeJs|tuckn/WshModeJs}
*/
Wsh.Commander = {};
// Shorthands
var CD = Wsh.Constants;
var util = Wsh.Util;
var path = Wsh.Path;
var insp = util.inspect;
var isArray = util.isArray;
var isFunction = util.isFunction;
var isString = util.isString;
var isPlainObject = util.isPlainObject;
var isSolidArray = util.isSolidArray;
var isSolidString = util.isSolidString;
var hasContent = util.hasContent;
// var isSameStr = util.isSameMeaning;
var obtain = util.obtainPropVal;
var includes = util.includes;
var startsWith = util.startsWith;
var endsWith = util.endsWith;
var cmd = Wsh.Commander;
/** @constant {string} */
var MODULE_TITLE = 'WshCore/Commander.js';
var throwErrNonArray = function (functionName, typeErrVal) {
util.throwTypeError('array', MODULE_TITLE, functionName, typeErrVal);
};
var throwErrNonObject = function (functionName, typeErrVal) {
util.throwTypeError('object', MODULE_TITLE, functionName, typeErrVal);
};
var throwErrNonStr = function (functionName, typeErrVal) {
util.throwTypeError('string', MODULE_TITLE, functionName, typeErrVal);
};
/** @constant {number} */
var I_FLAG = 0;
/** @constant {number} */
var I_DESCRIPTION = 1;
/** @constant {number} */
var I_FUNC_OR_DEFVAL = 2;
/** @constant {number} */
var I_DEFVAL = 3;
/** @constant {string} */
var TYPE_SWITCH_NC = 'SWITCH_NC'; // true|false
/** @constant {string} */
var TYPE_SWITCH_NO = 'SWITCH_NO'; // false|true
/** @constant {string} */
var TYPE_VAL = 'VALUE'; // undefined|true|String
var flagChar = '[0-9_.,!?+*$a-zA-Z]'; // @TODO Review
var reFlagStr = new RegExp('(-' + flagChar + ')?[,\\s]\\s?(--\\S+)\\s*(\\S*)', 'i');
var reShortFlag = new RegExp('^-(' + flagChar + ')$', 'i');
function isShortFlag (flag) { // Ex. "-s"
return reShortFlag.test(flag);
}
var reJoinedShortFlags = new RegExp('^-(' + flagChar + '{2,})$', 'i');
function isJoinedShortFlags (flag) { // Ex. "-Cfs"
return reJoinedShortFlags.test(flag);
}
var reLongFlag = new RegExp('^--(' + flagChar + '+(-' + flagChar + '+)*)$', 'i'); // @TODO Review
function isLongFlag (flag) { // Ex. "--file", "--save-file"
return reLongFlag.test(flag);
}
// _createCmdObj {{{
/**
* @typedef {object} typeCommandObj
* @property {string} name
* @property {boolean} isRequired
* @property {boolean} isArray - The following val type
* @property {(string|string[])} val - If isArray is true, Array. not String
* @property {string} schema - The source schema
*/
/**
* @typedef {object} typeCommandObject
* @property {string} name
* @property {string} description
* @property {object} version
* @property {object} help
* @property {Array} options
* @property {typeCommandObj[]} args
*/
/**
* @private
* @param {string} cmdSchema - Ex. "play" Ex. "play <consoleName> [gameTitle]"
* @returns {typeCommandObject}
*/
function _createCmdObj (cmdSchema) {
var functionName = '_createCmdObj';
if (!isString(cmdSchema)) throwErrNonStr(functionName, cmdSchema);
var cmdObj = {
name: '',
description: '',
args: [],
version: {},
help: {},
options: []
};
if (!isSolidString(cmdSchema)) return cmdObj;
var matches = cmdSchema.match(/(\S+)\s*(.*)/i);
// 1. Main Command
var userCmdSchema = matches[1];
if (!isSolidString(userCmdSchema)) {
throw new Error('\nError: [Invalid CommandSchema(main)]: ' + cmdSchema + '\n'
+ ' at ' + functionName + ' (' + MODULE_TITLE + ')');
}
cmdObj.name = userCmdSchema;
// 2. Subcommands (= Arguments)
var argsSchema = matches[2];
if (!isSolidString(argsSchema)) return cmdObj;
var argSchemas = argsSchema.split(/\s+/);
argSchemas.forEach(function (argSchema) {
var argMatches = argSchema.match(/^([<[])(\S+)([>\]])$/i);
if (!isSolidArray(argMatches)) {
throw new Error('\nError: [Invalid CommandSchema(arg)]: ' + cmdSchema + '\n'
+ ' at ' + functionName + ' (' + MODULE_TITLE + ')');
}
var argOpen = argMatches[1];
var argName = argMatches[2];
var argClose = argMatches[3];
if (!isSolidString(argOpen) || !isSolidString(argName) || !isSolidString(argClose)) {
throw new Error('\nError: [Invalid CommandSchema(argVal)]: ' + cmdSchema + '\n'
+ ' at ' + functionName + ' (' + MODULE_TITLE + ')');
}
var argObj = {
name: argName,
isSpecified: false,
isRequired: false,
schema: argSchema,
val: undefined
};
if (argOpen === '<' && argClose === '>') {
argObj.isRequired = true;
} else if (argOpen === '[' && argClose === ']') {
argObj.isRequired = false;
} else {
throw new Error('\nError: [Invalid CommandSchema](argType): ' + cmdSchema + '\n'
+ ' at ' + functionName + ' (' + MODULE_TITLE + ')');
}
if (endsWith(argName, '...')) {
argObj.isArray = true;
argObj.val = [];
}
cmdObj.args.push(argObj);
});
return cmdObj;
} // }}}
// _camelcase {{{
/**
* Camel-case the given `flag`
*
* @private
* @param {string} flag
* @returns {string}
*/
function _camelcase (flag) {
if (!isSolidString(flag)) return '';
return flag.split('-').reduce(function (str, word) {
return str + word.charAt(0).toUpperCase() + word.slice(1);
});
} // }}}
// _getLongFlagVarName {{{
/**
* Returns option name.
*
* @private
* @param {string} longFlag
* @returns {string}
*/
function _getLongFlagVarName (longFlag) {
if (!isSolidString(longFlag)) return '';
return _camelcase(longFlag.replace(/^--/, '').replace(/^no-/i, ''));
} // }}}
// _showCmdVersionAndExit {{{
/**
* @private
* @param {object} cmdObj
* @param {string} arg
* @returns {void}
*/
function _showCmdVersionAndExit (cmdObj, arg) {
if (!hasContent(cmdObj.version)) return;
var sFlag = cmdObj.version.shortFlag; // Shorthand
var lFlag = cmdObj.version.longFlag;
var shows;
// A short flag (e.g. "-V")
if (isShortFlag(arg)) {
shows = (arg === sFlag);
// Joined short flags (e.g. "-sVc")
} else if (isJoinedShortFlags(arg)) {
shows = includes(arg, sFlag.substring(1));
// A long flags (e.g. "--version")
} else if (isLongFlag(arg)) {
shows = (arg === lFlag);
}
if (shows) {
console.log(cmdObj.version.val);
process.exit(CD.runs.ok);
}
} // }}}
// _creatVersionOptionObj {{{
/**
* @typedef {array} typeVersionSchema
* @example
* ['0.0.1', '-v, --vers', 'Output the current version']
* @property {string} version
* @property {string} flagSchema
* @property {string} [description]
*/
/**
* @typedef {object} typeVersionObject
* @example
* { name: 'vers',
* shortFlag: '-v',
* longFlag: '--ver',
* description: 'Output the current version',
* val: '1.0.0',
* schema: ['0.0.1', '-v, --vers', 'Output the current version'] }
* @property {string} name
* @property {string} shortFlag
* @property {string} longFlag
* @property {string} description
* @property {(string|typeVersionSchema)} schema
*/
/**
* @private
* @example
* // Ex. 1
* _creatVersionOptionObj('0.0.1');
*
* // Ex. 2
* _creatVersionOptionObj([
* '0.0.1', '-v, --vers', 'Output the current version']);
* @param {(string|typeVersionSchema)} [versionSchema={}]
* @returns {typeVersionObject}
*/
function _creatVersionOptionObj (versionSchema) {
var functionName = '_creatVersionOptionObj';
if (!hasContent(versionSchema)) return {};
var verOptObj = {
name: 'version',
shortFlag: '-V',
longFlag: '--version',
description: 'Output the version number',
schema: versionSchema
};
if (isString(versionSchema)) {
verOptObj.val = versionSchema;
return verOptObj;
}
if (!isArray(versionSchema)) throwErrNonArray(functionName, versionSchema);
verOptObj.val = versionSchema[0];
var flagMatches = versionSchema[1].match(reFlagStr);
if (!hasContent(flagMatches)) {
throw new Error('\nError: [Invalid VersionSchema]: ' + insp(versionSchema) + '\n'
+ ' at ' + functionName + ' (' + MODULE_TITLE + ')');
}
verOptObj.shortFlag = flagMatches[1];
verOptObj.longFlag = flagMatches[2];
if (!hasContent(versionSchema[3])) return verOptObj;
verOptObj.description = versionSchema[3];
return verOptObj;
} // }}}
// _createOptionObjs {{{
/**
* @typedef {array} typeOptionSchema
* @example
* // Ex.1
* ['-O, --switch-no', 'Normally opened switch']
*
* // Ex. 2
* ['-I, --increment <Number>', 'Example of processing', function (num, pre) {
* return pre + num;
* }, 3]
* @property {string} flagSchema - Ex. '-O, --switch-no'
* @property {string} [description]
* @property {Function} [process]
* @property {any} [default] - The default/starting value
*/
/**
* @typedef {object} typeOptionObject
* @example
* { name: "gameTitle",
* shortFlag: "-G",
* longFlag: "--game-title",
* valType: "VALUE",
* description: "A game name",
* isRequired: false,
* isPairWithVal: false,
* isSpecified: false,
* isArray: false,
* val: undefined,
* initFunc: undefined,
* schema: ['-G, --game-title [title]', 'A game name']
* valSchema: '[title]' }
* @property {string} name - A option name
* @property {string} shortFlag - A Short Flag. Ex. "-G"
* @property {string} longFlag - A Long Flag. Ex. "--game-title"
* @property {string} valType - "VALUE",
* @property {string} description
* @property {boolean} isRequired
* @property {boolean} isPairWithVal
* @property {boolean} isSpecified
* @property {boolean} isArray
* @property {string|undefined} val
* @property {function|undefined} initFunc
* @property {typeOptionSchema} schema - The source schema. Ex. ['-G, --game-title [title]', 'A game name']
* @property {string} valSchema: '[title]'
*/
/**
* Creates a option object from the option schema. Similar to {@link https://www.npmjs.com/package/commander#options|Commander.js Options}
*
* @example
* _createOptionObjs([
* ["-O, --switch-no", "Normally opened switch"],
* ["-C, --no-switch-nc", "Normally closed switch"],
* ['-o, --opt-word [Bar]', '[Bar] is arbitrary'],
* ['-a, --array-vals <val...>', 'Store as Array']
* ]);
* @private
* @param {typeOptionSchema[]} optsSchemas
* @param {boolean} [isRequired=false]
* @returns {typeOptionObject[]} - See {@link typeOptionObject}
*/
function _createOptionObjs (optsSchemas, isRequired) {
var functionName = '_createOptionObjs';
if (!isArray(optsSchemas)) throwErrNonArray(functionName, optsSchemas);
var options = [];
if (!isSolidArray(optsSchemas)) return options;
optsSchemas.forEach(function (schema) {
var flagMatches = schema[I_FLAG].match(reFlagStr);
if (!isSolidArray(flagMatches)) {
throw new Error('\nError: [Invalid OptionsSchema(flag)]: ' + insp(schema) + '\n'
+ ' at ' + functionName + ' (' + MODULE_TITLE + ')');
}
var shortFlag = flagMatches[1]; // Ex. "-f"
var longFlag = flagMatches[2]; // Ex. "--file"
var optObj = {
name: _getLongFlagVarName(longFlag),
shortFlag: shortFlag,
longFlag: longFlag,
description: schema[I_DESCRIPTION] || '',
isRequired: Boolean(isRequired),
isSpecified: false,
isPairWithVal: false,
schema: schema,
val: undefined
};
var valSchema = flagMatches[3].trim();
if ((/^--no-/i).test(longFlag)) {
optObj.valType = TYPE_SWITCH_NC;
optObj.val = true;
} else if (!hasContent(valSchema)) {
optObj.valType = TYPE_SWITCH_NO;
optObj.val = false;
} else if ((/^<.+>$/i).test(valSchema)) { // Ex. "<Hoo>"
optObj.isPairWithVal = true;
optObj.valSchema = valSchema;
optObj.valType = TYPE_VAL;
if (endsWith(valSchema, '...>')) { // Ex. "<Bar...>"
optObj.isArray = true;
optObj.val = [];
} else {
optObj.isArray = false;
optObj.val = undefined;
}
} else if ((/^\[.+\]$/i).test(valSchema)) { // Ex. "[Hoge]"
optObj.valSchema = valSchema;
optObj.valType = TYPE_VAL;
if (endsWith(valSchema, '...]')) { // Ex. "[Piyo...]"
optObj.isArray = true;
optObj.val = [];
} else {
optObj.isArray = false;
optObj.val = undefined;
}
} else {
throw new Error('\nError: [Invalid OptionsSchema(val)]: ' + insp(schema) + '\n'
+ ' at ' + functionName + ' (' + MODULE_TITLE + ')');
}
/**
* Set pre-function and default-value
* @note If default-value is specified, isPairWithVal to false.
*/
if (schema.length > I_FUNC_OR_DEFVAL) {
if (isFunction(schema[I_FUNC_OR_DEFVAL])) {
optObj.initFunc = schema[I_FUNC_OR_DEFVAL];
if (schema.length > I_DEFVAL) {
if (optObj.isArray) optObj.val = [schema[I_DEFVAL]];
else optObj.val = schema[I_DEFVAL];
optObj.isPairWithVal = false;
}
} else {
if (optObj.isArray) optObj.val = [schema[I_FUNC_OR_DEFVAL]];
else optObj.val = schema[I_FUNC_OR_DEFVAL];
optObj.isPairWithVal = false;
}
}
options.push(optObj);
});
return options;
} // }}}
// _createCmdHelpMsg {{{
/**
* @private
* @param {object} cmdObj
* @returns {string}
*/
function _createCmdHelpMsg (cmdObj) {
// var functionName = '_createCmdHelpMsg';
var helpMsg = 'Usage: ';
if (cmdObj.name === '') {
helpMsg += path.basename(process.argv[1]);
} else {
helpMsg += cmdObj.name;
helpMsg += cmdObj.args.reduce(function (acc, arg) {
return acc + ' ' + arg.schema;
}, '');
}
// Create a temporary array for the help message
var optObjs = cmdObj.options.concat([{
// Add the help object
shortFlag: cmdObj.help.shortFlag,
longFlag: cmdObj.help.longFlag,
description: cmdObj.help.description,
valSchema: undefined,
val: undefined
}]);
if (hasContent(cmdObj.version)) { // Add the version object
optObjs.unshift({
shortFlag: cmdObj.version.shortFlag,
longFlag: cmdObj.version.longFlag,
description: cmdObj.version.description,
valSchema: undefined,
val: undefined
});
}
helpMsg += ' [options]\n\n';
if (hasContent(cmdObj.description)) helpMsg += cmdObj.description + '\n\n';
if (optObjs.length > 0) {
helpMsg += 'Options:\n';
// Get the max length of the flag character to indent
var maxWidth = 0;
optObjs.forEach(function (opt) {
var width = String(opt.longFlag).length;
/*
* @note "--long-flag" or "--long-flag <val>"
* attention the space between Flag and Val
*/
width += opt.valSchema ? (String(opt.valSchema).length + 1) : 0;
if (width > maxWidth) maxWidth = width;
});
helpMsg += optObjs.reduce(function (acc, opt) {
if (!hasContent(opt.shortFlag)) {
acc += ' ,';
} else {
acc += ' ' + opt.shortFlag + ',';
}
acc += ' ' + opt.longFlag;
acc += opt.valSchema ? (' ' + opt.valSchema) : '';
// indent
var baseLen = opt.longFlag.length
+ (opt.valSchema ? (String(opt.valSchema).length + 1) : 0);
for (var i = baseLen; i < maxWidth; i++) acc += ' ';
if (hasContent(opt.description)) acc += ' ' + opt.description;
// In case of `--no-` flag, The default value (true) is not displayed.
if (hasContent(opt.val) && opt.valType !== TYPE_SWITCH_NC) {
acc += ' (default: ' + insp(opt.val) + ')';
}
acc += '\n';
return acc;
}, '');
}
return helpMsg;
} // }}}
// _createHelpOptionObj {{{
/**
* @typedef {array} typeHelpSchema
* @example
* ['-S, --show-help', 'Show the help']
* @property {string} version
* @property {string} flagSchema
* @property {string} [description]
*/
/**
* @typedef {object} typeHelpObject
* @example
* { name: 'showHelp',
* shortFlag: '-S',
* longFlag: '--show-help',
* description: 'Show the help',
* val: '', // Auto create with _createCmdHelpMsg(cmdObj)
* schema: ['-S, --show-help', 'Show the help'] }
* @property {string} name - The option name.
* @property {string} shortFlag - The short flag.
* @property {string} longFlag - The long flag.
* @property {string} description
* @property {string} val - A help message when shows in CLI.
* @property {(string|typeHelpSchema)} schema - The source schema.
*/
/**
* @private
* @param {typeHelpSchema} helpOptSchema
* @returns {typeHelpObject}
*/
function _createHelpOptionObj (helpOptSchema) {
var functionName = '_createHelpOptionObj';
if (!isArray(helpOptSchema)) throwErrNonArray(functionName, helpOptSchema);
var helpOptObj = {
name: 'help',
shortFlag: '-h',
longFlag: '--help',
description: 'Output usage information',
val: undefined,
schema: helpOptSchema
};
if (!isSolidArray(helpOptSchema)) return helpOptObj;
if (!isSolidString(helpOptSchema[I_FLAG])) {
throwErrNonStr(functionName, helpOptSchema);
}
var flagMatches = helpOptSchema[I_FLAG].match(reFlagStr);
if (!hasContent(flagMatches)) {
throw new Error('\nError: [Invalid HelpOptionSchema(flag)]: ' + insp(helpOptSchema) + '\n'
+ ' at ' + functionName + ' (' + MODULE_TITLE + ')');
}
helpOptObj.shortFlag = flagMatches[1];
helpOptObj.longFlag = flagMatches[2];
if (!isSolidString(helpOptSchema[I_DESCRIPTION])) return helpOptObj;
helpOptObj.description = helpOptSchema[I_DESCRIPTION];
return helpOptObj;
} // }}}
// _showCmdHelpAndExit {{{
/**
* @private
* @param {object} cmdObj
* @param {string} arg
* @returns {void}
*/
function _showCmdHelpAndExit (cmdObj, arg) {
var sFlag = cmdObj.help.shortFlag; // Shorthand
var lFlag = cmdObj.help.longFlag;
var shows;
// A short flag (e.g. "-h")
if (isShortFlag(arg)) {
shows = (arg === sFlag);
// Joined short flags (e.g. "-shc")
} else if (isJoinedShortFlags(arg)) {
shows = includes(arg, sFlag.substring(1));
// A long flags (e.g. "--help")
} else if (isLongFlag(arg)) {
shows = (arg === lFlag);
}
if (shows) {
console.log(cmdObj.help.val);
process.exit(CD.runs.ok);
}
} // }}}
// The inner object {{{
/**
* @typedef {object} typeProgramObj
* @example
* { name: 'playgame',
* args: [
* {
* name: 'consoleName',
* isRequired: false,
* isArray: false,
* isSpecified: false,
* val: undefined,
* schema: '[consoleName]'
* },
* {}...
* ],
* description: 'Playing Game Command for CLI',
* version: {
* name: 'vers',
* shortFlag: '-v',
* longFlag: '--ver',
* description: 'Output the current version',
* val: '1.0.0',
* schema: ['0.0.1', '-v, --vers', 'Output the current version']
* },
* help: {
* name: 'showHelp',
* shortFlag: '-S',
* longFlag: '--show-help',
* description: 'Show the help',
* val: 'Usage: ... [options]\n\n ...',
* schema: ['-S, --show-help', 'Show help']
* },
* options: [
* { name: 'gameTitle',
* shortFlag: '-G',
* longFlag: '--game-title',
* valType: 'VALUE',
* description: 'A game name',
* isRequired: false,
* isPairWithVal: false,
* isSpecified: false,
* isArray: false,
* val: undefined,
* initFunc: undefined,
* schema: ['-G, --game-title [title]', 'A game name']
* valSchema: '[title]' },
* { ... },
* ...
* ],
* action: function (console, ..., options) { ... } }
* @property {string} name - The program name.
* @property {typeCommandObj[]} args - The program arguments.
* @property {string} description - The program description.
* @property {typeVersionObject} version - The object of program version.
* @property {typeHelpObject} help - The object of program help.
* @property {typeOptionObject[]} options - The program options.
* @property {Function} action - The function to execute.
*/
/**
* The inner objects defined with Wsh.Commander.addProgram.
*
* @name __commands
* @type {typeProgramObj[]}
*/
var __commands = []; // }}}
// cmd._getCommandObjects {{{
/**
* Gets the inner objects defined with Wsh.Commander.addProgram.
*
* @example
* var cmd = Wsh.Commander;
*
* cmd.addProgram({
* // The program schema
* });
*
* if (debug) console.dir(cmd._getCommandObjects());
*
* cmd.parse(process.argv);
* @function _getCommandObjects
* @memberof Wsh.Commander
* @returns {typeProgramObj[]} - The inner objects defined with .addProgram().
*/
cmd._getCommandObjects = function () {
return __commands;
}; // }}}
// cmd.addProgram {{{
/**
* @typedef {object} typeCommanderSchema
* @example
* {
* command: 'connect <resourceName>',
* description: 'The command to connect my PC to a resource',
* version: '0.5.1',
* requiredOptions: [
* ['-p, --password', 'The password to connect']
* ],
* options: [
* ['-d, --domain-name <name>', 'A domain name of the resource'],
* ['-n, --user-name [name]', 'A user name to log in', 'Tuckn']
* ],
* action: function (resourceName, options) {
* if (options.domainName) {
* connRsrc(processName, options.password,
* options.domainName + '\\' + options.userName);
* } else {
* connRsrc(processName, options.password, options.userName);
* }
* }
* }
* @property {string} [command=''] - The command and arguments. e.g1: 'play' e.g2: 'play \<consoleName\> [gameTitle]'
* @property {string} [description] - The command description.
* @property {string|typeVersionSchema} [version] - The schema of command version.
* @property {typeOptionSchema[]} [options] - An Array of command option schemas.
* @property {typeOptionSchema[]} [requiredOptions] - An Array of required option schemas.
* @property {Function} [action] - The function to execute.
* @property {typeHelpSchema} [helpOption] - The schema of command help.
*/
/**
* Defines the program. Similar to {@link https://www.npmjs.com/package/commander#options|Commander.js Options}
*
* @example
* var cmd = Wsh.Commander;
*
* // Ex. Command and Action
* cmd.addProgram({
* command: 'play <consoleName> [gameTitle]',
* action: function (consoleName, gameTitle) {
* if (typeof gameTitle === 'string') {
* console.log('play ' + gameTitle + ' on ' + consoleName);
* } else {
* console.log('play ' + consoleName);
* }
* }
* });
* cmd.parse(process.argv);
*
* // D:\>cscript Run.wsf
* // ERROR: Set a command.
* // where <command> is one of:
* // play
*
* // D:\>cscript Run.wsf play
* // error: missing required argument "consoleName"
* // Usage: play <consoleName> [gameTitle]
* //
* // Options:
* // -h, --help Output usage information
*
* // D:\>cscript Run.wsf play "PC-Engine"
* // play PC-Engine
* @example
* // Ex. Any-arguments
* cmd.addProgram({
* command: 'createZip <srcDir> <destDir> [excludes...]',
* action: function (srcDir, destDir, excludes) {
* if (excludes) {
* return 'srcDir: "' + srcDir + '", destDir: "' + destDir + '", '
* + 'excludes: ' + excludes.join(', ');
* }
* return 'srcDir: "' + srcDir + '", destDir: "' + destDir + '"';
* }
* });
* var str = cmd.parse(process.argv);
* console.log(str);
*
* // D:\>cscript Run.wsf createZip C:\\Users D:\\BackUp tmp temp cache
* // srcDir: "C:\\Users", destDir: "D:\\BackUp", excludes: tmp,temp,cache
* @example
* // Ex. Commands
* cmd.addProgram({
* command: 'play',
* action: function () { console.log('Your command is `play`'); }
* });
*
* cmd.addProgram({
* command: 'study',
* action: function () { console.log('Your command is `study`'); }
* });
* cmd.parse(process.argv);
*
* // D:\>cscript Run.wsf study
* // Your command is `study`
* @example
* // Ex. Option Switch
* cmd.addProgram({
* options: [
* ['-C, --close1', 'Normally opened switch'], // default: false
* ['-O, --no-close2', 'Normally closed switch'] // default: true
* ]
* });
* cmd.parse(process.argv);
*
* // `D:\>cscript Run.wsf`
* cmd.opt.close1; // false
* cmd.opt.close2; // true
*
* // `D:\>cscript Run.wsf -O -C`
* // or `D:\>cscript Run.wsf --close1 --no-close2`
* cmd.opt.close1; // true
* cmd.opt.close2; // false
* @example
* // Ex. Option Flag value
* cmd.addProgram({
* options: [
* ['-f, --flag [name]', 'Flag name'], // default: undefined
* ['-d, --flag-def [name]', 'Flag name(default: "My Name")', 'My Name'],
* ]
* });
* cmd.parse(process.argv);
*
* // `D:\>cscript Run.wsf`
* cmd.opt.flag; // undefined
* cmd.opt.flagDef; // 'My Name'
*
* // `D:\>cscript Run.wsf -f -d`
* // or `D:\>cscript Run.wsf --flag --flag-def`
* cmd.opt.flag; // true
* cmd.opt.flagDef; // 'My Name'
*
* // `D:\>cscript Run.wsf -f "Flag A" -d "Flag B"`
* // or `D:\>cscript Run.wsf --flag "Flag A" --flag-def "Flag B"`
* cmd.opt.flag; // 'Flag A'
* cmd.opt.flagDef; // 'Flag B'
* @example
* // Ex. Option Pair with a value
* cmd.addProgram({
* options: [
* ['-r, --required <Foo>', 'Empty <Foo> is not allowed'],
* ['-R, --def-word <Bar>', 'default value is "Def Name"', 'Def Name']
* ]
* });
* cmd.parse(process.argv); // No Error
*
* // `D:\>cscript Run.wsf`
* cmd.opt.required; // undefined
* cmd.opt.defWord; // 'Def Name'
*
* // `D:\>cscript Run.wsf -r`
* // or `D:\>cscript Run.wsf --required`
* // Outputs the help
*
* // `D:\>cscript Run.wsf -r "Val A"`
* // or `D:\>cscript Run.wsf --required "Val A"`
* cmd.opt.required; // 'Val A'
* @example
* // Ex. Option Array values
* cmd.addProgram({
* options: [
* ['-f, --flags [name...]', 'Flag name'],
* ['-v, --values <val...>', 'Specify your value', 'Val 0']
* ]
* });
* cmd.parse(process.argv);
*
* // `D:\>cscript Run.wsf`
* cmd.opt.flags; // []
* cmd.opt.values; // ['Val 0']
*
* // `D:\>cscript Run.wsf -f "Name 0" -v "Val 1"`
* // or `D:\>cscript Run.wsf --flags "Name 0" --values "Val 1"`
* cmd.opt.flags; // ['Name 0']
* cmd.opt.values; // ['Val 0', 'Val 1']
*
* // `D:\>cscript Run.wsf -v "Val 1" "Val 2" "Val 3"`
* cmd.opt.values; // ['Val 0', 'Val 1', 'Val 2', 'Val 3']
* @function addProgram
* @memberof Wsh.Commander
* @param {typeCommanderSchema} schemaObj - A schema of the program.
* @returns {void}
*/
cmd.addProgram = function (schemaObj) {
var functionName = 'cmd.addProgram';
if (!isPlainObject(schemaObj)) throwErrNonObject(functionName, schemaObj);
var commandSchema = obtain(schemaObj, 'command', '');
var optionsSchema = obtain(schemaObj, 'options', []);
var requiredOptions = obtain(schemaObj, 'requiredOptions', []);
if (!isString(commandSchema) && !hasContent(optionsSchema) && !hasContent(requiredOptions)) {
throw new Error('\nError: [Invalid Schema]: Empty of command and options\n'
+ ' at ' + functionName + ' (' + MODULE_TITLE + ')');
}
// Set a command
var cmdObj = _createCmdObj(commandSchema);
if (__commands.some(function (o) { return o.name === cmdObj.name; })) {
throw new Error('\nError: [Invalid Command Name] "' + cmdObj.name + '" is already existing\n'
+ ' at ' + functionName + ' (' + MODULE_TITLE + ')');
}
// Set a description
var cmdDescription = obtain(schemaObj, 'description', '');
cmdObj.description = cmdDescription;
// Set a version
var versionOptSchema = obtain(schemaObj, 'version', '');
cmdObj.version = _creatVersionOptionObj(versionOptSchema);
// Set options
cmdObj.options = [].concat(_createOptionObjs(requiredOptions, true));
cmdObj.options = cmdObj.options.concat(_createOptionObjs(optionsSchema, false));
// Set a help option
var helpOptSchema = obtain(schemaObj, 'helpOption', []);
cmdObj.help = _createHelpOptionObj(helpOptSchema);
cmdObj.help.val = _createCmdHelpMsg(cmdObj);
// Set a action
var actionSchema = obtain(schemaObj, 'action', null);
if (isFunction(actionSchema)) {
cmdObj.action = actionSchema;
} else {
cmdObj.action = null;
}
__commands.push(cmdObj);
}; // }}}
// cmd.addPrograms {{{
/**
* Defines the programs.
*
* @function addPrograms
* @memberof Wsh.Commander
* @param {typeCommanderSchema[]} schemaObjs - Schemas of the programs.
* @returns {void}
*/
cmd.addPrograms = function (schemaObjs) {
var functionName = 'cmd.addPrograms';
if (!isSolidArray(schemaObjs)) throwErrNonArray(functionName, schemaObjs);
schemaObjs.forEach(function (schemaObj) {
cmd.addProgram(schemaObj);
});
}; // }}}
cmd.opt = {};
// cmd.parse {{{
/**
* Parses the arguments of a WSH script with the program schemas.
*
* @example
* var cmd = Wsh.Commander;
*
* cmd.addProgram({
* options: [
* ['-c, --console-name <name>', 'The game console name'],
* ['-G, --game-title [title]', 'The game name']
* ]
* });
*
* cmd.parse(process.argv);
*
* // `cscript .\Run.wsf -c "SEGA Saturn" -G "StreetFighter ZERO"`
* console.log(cmd.opt.consoleName); // 'SEGA Saturn'
* console.log(cmd.opt.gameTitle); // 'StreetFighter ZERO'
* @function parse
* @memberof Wsh.Commander
* @param {string[]} processArgv - The arguments of WSH (wscript/cscript).
* @returns {void}
*/
cmd.parse = function (processArgv) {
var functionName = 'cmd.parse';
if (!isArray(processArgv)) throwErrNonArray(functionName, processArgv);
var args = Array.from(processArgv);
var exePath = args.shift();
if (exePath === undefined) {
throw new Error('\nError: [Invalid Arguments(exePath)] ' + insp(processArgv) + '\n'
+ ' at ' + functionName + ' (' + MODULE_TITLE + ')');
}
var wshPath = args.shift();
if (wshPath === undefined) {
throw new Error('\nError: [Invalid Arguments(wshPath)] ' + insp(processArgv) + '\n'
+ ' at ' + functionName + ' (' + MODULE_TITLE + ')');
}
var matchedCmdObj = null;
// Set the command name {{{
// Set the default command object
var defCmdIdx = __commands.findIndex(function (cmd) {
return cmd.name === '';
});
if (defCmdIdx !== -1) matchedCmdObj = __commands[defCmdIdx];
// Read the 1st arg as a command name
if (args.length > 0) {
// Search the arg as a command name
var matchedCmdIdx = __commands.findIndex(function (cmd) {
return (cmd.name === args[0]);
});
// Set the matched command object
if (matchedCmdIdx !== -1) {
matchedCmdObj = __commands[matchedCmdIdx];
args.shift();
}
}
if (matchedCmdObj === null) {
var cmdlist = __commands.reduce(function (acc, cmdObj, i) {
if (i === 0) return cmdObj.name;
return acc + ', ' + cmdObj.name;
}, '');
throw new Error('\n'
+ 'ERROR: Set a command.\n'
+ 'where <command> is one of:\n'
+ ' ' + cmdlist + '\n'
+ '\n'
+ 'process.argv: [' + process.argv.join(', ') + ']');
} // }}}
var matchedArgObjIdx;
var matchedOptIdxs;
var reserveOptIdx = -1;
var arg, opt, argObj;
while (args.length > 0) {
arg = args.shift();
if (arg === undefined) break;
// Set into the reserved option {{{
if (reserveOptIdx !== -1) {
opt = matchedCmdObj.options[reserveOptIdx];
if (startsWith(arg, '-') && !opt.isPairWithVal) {
reserveOptIdx = -1; // Clear the reserved option
} else if (opt.isArray) {
if (startsWith(arg, '-')) {
reserveOptIdx = -1; // Clear the reserved option
} else {
if (isFunction(opt.initFunc)) {
opt.val.push(opt.initFunc(arg, opt.val));
} else {
opt.val.push(arg);
}
}
} else {
// Do the custom option processing
if (isFunction(opt.initFunc)) {
opt.val = opt.initFunc(arg, opt.val);
} else {
opt.val = arg;
}
reserveOptIdx = -1; // Clear the reserved option
continue;
}
} // }}}
// Option
if (startsWith(arg, '-')) {
_showCmdHelpAndExit(matchedCmdObj, arg);
_showCmdVersionAndExit(matchedCmdObj, arg);
matchedOptIdxs = [];
// Get the matching options indexes {{{
if (isShortFlag(arg)) { // A short flag (e.g. "-s")
matchedOptIdxs.push(
matchedCmdObj.options.findIndex(function (opt) {
return opt.shortFlag === arg;
})
);
} else if (isJoinedShortFlags(arg)) { // Joined short flags (e.g. "-sVc")
matchedCmdObj.options.forEach(function (opt, idx) {
if (!includes(opt.shortFlag, arg.substring(1))) return;
matchedOptIdxs.push(idx);
});
} else if (isLongFlag(arg)) { // A long flags (e.g. "--file")
matchedOptIdxs.push(
matchedCmdObj.options.findIndex(function (opt) {
return (opt.longFlag === arg);
})
);
} // }}}
// Set a value into .val from the flag {{{
matchedOptIdxs.forEach(function (idx) {
if (idx === -1) {
throw new Error('\nError: [Invalid Option] "' + arg + '"\n'
+ ' at ' + functionName + ' (' + MODULE_TITLE + ')\n'
+ matchedCmdObj.help.val);
}
opt = matchedCmdObj.options[idx];
opt.isSpecified = true;
if (opt.valType === TYPE_SWITCH_NC) {
if (isFunction(opt.initFunc)) { // the custom option processing
opt.val = opt.initFunc(undefined, opt.val);
} else {
opt.val = false;
}
} else if (opt.valType === TYPE_SWITCH_NO) {
if (isFunction(opt.initFunc)) { // the custom option processing
opt.val = opt.initFunc(undefined, opt.val);
} else {
opt.val = true;
}
} else if (opt.valType === TYPE_VAL) {
if (!opt.isArray && !opt.isPairWithVal) {
if (!opt.val) opt.val = true;
}
reserveOptIdx = idx; // Reserve the next arg
} else {
throw new Error('\nError: Unknown option valType "' + opt.valType + '"\n'
+ ' at ' + functionName + ' (' + MODULE_TITLE + ')\n'
+ matchedCmdObj.help.val);
}
}); // }}}
// Argument
} else {
// Find a empty element in the defined order
matchedArgObjIdx = matchedCmdObj.args.findIndex(function (argObj) {
if (argObj.isArray) return true;
return (argObj.val === undefined);
});
if (matchedArgObjIdx === -1) continue;
argObj = matchedCmdObj.args[matchedArgObjIdx];
argObj.isSpecified = true;
if (argObj.isArray) {
argObj.val.push(arg);
} else {
argObj.val = arg;
}
}
}
// Check if the required value is not empty {{{
matchedCmdObj.args.some(function (arg) {
if (!arg.isRequired) return false;
if (!hasContent(arg.val)) {
throw new Error('\nerror: missing required argument "' + arg.name + '"\n'
+ matchedCmdObj.help.val);
}
});
matchedCmdObj.options.some(function (opt) {
if (opt.isRequired && !hasContent(opt.val)) {
throw new Error('\nerror: option "' + opt.schema[0] + '" argument missing\n'
+ matchedCmdObj.help.val);
}
if (opt.isSpecified && opt.isPairWithVal && opt.val === undefined) {
throw new Error('\nerror: option "' + opt.schema[0] + '" argument missing\n'
+ matchedCmdObj.help.val);
}
}); // }}}
// Store the option name as the cmd property {{{
matchedCmdObj.options.forEach(function (opt) {
cmd.opt[opt.name] = opt.val;
}); // }}}
// Do the action {{{
if (isFunction(matchedCmdObj.action)) {
var cmdArgs = [];
matchedCmdObj.args.forEach(function (argObj) {
if (argObj.isSpecified) cmdArgs.push(argObj.val);
else cmdArgs.push(undefined);
});
if (hasContent(matchedCmdObj.options)) {
var options = {};
matchedCmdObj.options.forEach(function (opt) {
options[opt.name] = opt.val;
});
return matchedCmdObj.action.apply(null, cmdArgs.concat([options]));
}
return matchedCmdObj.action.apply(null, cmdArgs);
} // }}}
}; // }}}
// cmd.help {{{
/**
* Shows the help of programs and exit with returning 1.
*
* @example
* var cmd = Wsh.Commander;
*
* cmd.addProgram({
* options: [
* ['-c, --console-name <name>', 'The game console name'],
* ['-G, --game-title [title]', 'The game name']
* ]
* });
*
* cmd.parse(process.argv);
*
* if (cmd.opt.consoleName === 'DUO') cmd.help();
* @function help
* @memberof Wsh.Commander
* @param {Function} [callback] - A Function to run before showing help.
* @returns {void}
*/
cmd.help = function (callback) {
if (isFunction(callback)) callback();
console.log(__commands.help.val);
process.exit(CD.runs.err);
}; // }}}
// cmd.clearPrograms {{{
/**
* Clears the programs.
*
* @example
* var cmd = Wsh.Commander;
*
* cmd.addProgram({
* // The program schema
* });
*
* if (debug) {
* cmd.clearPrograms();
* cmd.addProgram({
* // The program schema to debug
* });
* }
*
* cmd.parse(process.argv);
* @function clearPrograms
* @memberof Wsh.Commander
* @returns {void}
*/
cmd.clearPrograms = function () {
cmd.opt = {};
__commands = [];
}; // }}}
})();
// vim:set foldmethod=marker commentstring=//%s :