react / wstein / node_modules / jest-cli / node_modules / istanbul / node_modules / js-yaml / node_modules / argparse / lib / action_container.js
80713 views/** internal1* class ActionContainer2*3* Action container. Parent for [[ArgumentParser]] and [[ArgumentGroup]]4**/56'use strict';78var format = require('util').format;9var _ = require('lodash');1011// Constants12var $$ = require('./const');1314//Actions15var ActionHelp = require('./action/help');16var ActionAppend = require('./action/append');17var ActionAppendConstant = require('./action/append/constant');18var ActionCount = require('./action/count');19var ActionStore = require('./action/store');20var ActionStoreConstant = require('./action/store/constant');21var ActionStoreTrue = require('./action/store/true');22var ActionStoreFalse = require('./action/store/false');23var ActionVersion = require('./action/version');24var ActionSubparsers = require('./action/subparsers');2526// Errors27var argumentErrorHelper = require('./argument/error');28293031/**32* new ActionContainer(options)33*34* Action container. Parent for [[ArgumentParser]] and [[ArgumentGroup]]35*36* ##### Options:37*38* - `description` -- A description of what the program does39* - `prefixChars` -- Characters that prefix optional arguments40* - `argumentDefault` -- The default value for all arguments41* - `conflictHandler` -- The conflict handler to use for duplicate arguments42**/43var ActionContainer = module.exports = function ActionContainer(options) {44options = options || {};4546this.description = options.description;47this.argumentDefault = options.argumentDefault;48this.prefixChars = options.prefixChars || '';49this.conflictHandler = options.conflictHandler;5051// set up registries52this._registries = {};5354// register actions55this.register('action', null, ActionStore);56this.register('action', 'store', ActionStore);57this.register('action', 'storeConst', ActionStoreConstant);58this.register('action', 'storeTrue', ActionStoreTrue);59this.register('action', 'storeFalse', ActionStoreFalse);60this.register('action', 'append', ActionAppend);61this.register('action', 'appendConst', ActionAppendConstant);62this.register('action', 'count', ActionCount);63this.register('action', 'help', ActionHelp);64this.register('action', 'version', ActionVersion);65this.register('action', 'parsers', ActionSubparsers);6667// raise an exception if the conflict handler is invalid68this._getHandler();6970// action storage71this._actions = [];72this._optionStringActions = {};7374// groups75this._actionGroups = [];76this._mutuallyExclusiveGroups = [];7778// defaults storage79this._defaults = {};8081// determines whether an "option" looks like a negative number82// -1, -1.5 -5e+483this._regexpNegativeNumber = new RegExp('^[-]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?$');8485// whether or not there are any optionals that look like negative86// numbers -- uses a list so it can be shared and edited87this._hasNegativeNumberOptionals = [];88};8990// Groups must be required, then ActionContainer already defined91var ArgumentGroup = require('./argument/group');92var MutuallyExclusiveGroup = require('./argument/exclusive');9394//95// Registration methods96//9798/**99* ActionContainer#register(registryName, value, object) -> Void100* - registryName (String) : object type action|type101* - value (string) : keyword102* - object (Object|Function) : handler103*104* Register handlers105**/106ActionContainer.prototype.register = function (registryName, value, object) {107this._registries[registryName] = this._registries[registryName] || {};108this._registries[registryName][value] = object;109};110111ActionContainer.prototype._registryGet = function (registryName, value, defaultValue) {112if (3 > arguments.length) {113defaultValue = null;114}115return this._registries[registryName][value] || defaultValue;116};117118//119// Namespace default accessor methods120//121122/**123* ActionContainer#setDefaults(options) -> Void124* - options (object):hash of options see [[Action.new]]125*126* Set defaults127**/128ActionContainer.prototype.setDefaults = function (options) {129options = options || {};130for (var property in options) {131this._defaults[property] = options[property];132}133134// if these defaults match any existing arguments, replace the previous135// default on the object with the new one136this._actions.forEach(function (action) {137if (action.dest in options) {138action.defaultValue = options[action.dest];139}140});141};142143/**144* ActionContainer#getDefault(dest) -> Mixed145* - dest (string): action destination146*147* Return action default value148**/149ActionContainer.prototype.getDefault = function (dest) {150var result = (_.has(this._defaults, dest)) ? this._defaults[dest] : null;151152this._actions.forEach(function (action) {153if (action.dest === dest && _.has(action, 'defaultValue')) {154result = action.defaultValue;155}156});157158return result;159};160//161// Adding argument actions162//163164/**165* ActionContainer#addArgument(args, options) -> Object166* - args (Array): array of argument keys167* - options (Object): action objects see [[Action.new]]168*169* #### Examples170* - addArgument([-f, --foo], {action:'store', defaultValue=1, ...})171* - addArgument(['bar'], action: 'store', nargs:1, ...})172**/173ActionContainer.prototype.addArgument = function (args, options) {174args = args;175options = options || {};176177if (!_.isArray(args)) {178throw new TypeError('addArgument first argument should be an array');179}180if (!_.isObject(options) || _.isArray(options)) {181throw new TypeError('addArgument second argument should be a hash');182}183184// if no positional args are supplied or only one is supplied and185// it doesn't look like an option string, parse a positional argument186if (!args || args.length === 1 && this.prefixChars.indexOf(args[0][0]) < 0) {187if (args && !!options.dest) {188throw new Error('dest supplied twice for positional argument');189}190options = this._getPositional(args, options);191192// otherwise, we're adding an optional argument193} else {194options = this._getOptional(args, options);195}196197// if no default was supplied, use the parser-level default198if (_.isUndefined(options.defaultValue)) {199var dest = options.dest;200if (_.has(this._defaults, dest)) {201options.defaultValue = this._defaults[dest];202} else if (!_.isUndefined(this.argumentDefault)) {203options.defaultValue = this.argumentDefault;204}205}206207// create the action object, and add it to the parser208var ActionClass = this._popActionClass(options);209if (! _.isFunction(ActionClass)) {210throw new Error(format('Unknown action "%s".', ActionClass));211}212var action = new ActionClass(options);213214// throw an error if the action type is not callable215var typeFunction = this._registryGet('type', action.type, action.type);216if (!_.isFunction(typeFunction)) {217throw new Error(format('"%s" is not callable', typeFunction));218}219220return this._addAction(action);221};222223/**224* ActionContainer#addArgumentGroup(options) -> ArgumentGroup225* - options (Object): hash of options see [[ArgumentGroup.new]]226*227* Create new arguments groups228**/229ActionContainer.prototype.addArgumentGroup = function (options) {230var group = new ArgumentGroup(this, options);231this._actionGroups.push(group);232return group;233};234235/**236* ActionContainer#addMutuallyExclusiveGroup(options) -> ArgumentGroup237* - options (Object): {required: false}238*239* Create new mutual exclusive groups240**/241ActionContainer.prototype.addMutuallyExclusiveGroup = function (options) {242var group = new MutuallyExclusiveGroup(this, options);243this._mutuallyExclusiveGroups.push(group);244return group;245};246247ActionContainer.prototype._addAction = function (action) {248var self = this;249250// resolve any conflicts251this._checkConflict(action);252253// add to actions list254this._actions.push(action);255action.container = this;256257// index the action by any option strings it has258action.optionStrings.forEach(function (optionString) {259self._optionStringActions[optionString] = action;260});261262// set the flag if any option strings look like negative numbers263action.optionStrings.forEach(function (optionString) {264if (optionString.match(self._regexpNegativeNumber)) {265if (!_.any(self._hasNegativeNumberOptionals)) {266self._hasNegativeNumberOptionals.push(true);267}268}269});270271// return the created action272return action;273};274275ActionContainer.prototype._removeAction = function (action) {276var actionIndex = this._actions.indexOf(action);277if (actionIndex >= 0) {278this._actions.splice(actionIndex, 1);279}280};281282ActionContainer.prototype._addContainerActions = function (container) {283// collect groups by titles284var titleGroupMap = {};285this._actionGroups.forEach(function (group) {286if (titleGroupMap[group.title]) {287throw new Error(format('Cannot merge actions - two groups are named "%s".', group.title));288}289titleGroupMap[group.title] = group;290});291292// map each action to its group293var groupMap = {};294function actionHash(action) {295// unique (hopefully?) string suitable as dictionary key296return action.getName();297}298container._actionGroups.forEach(function (group) {299// if a group with the title exists, use that, otherwise300// create a new group matching the container's group301if (!titleGroupMap[group.title]) {302titleGroupMap[group.title] = this.addArgumentGroup({303title: group.title,304description: group.description305});306}307308// map the actions to their new group309group._groupActions.forEach(function (action) {310groupMap[actionHash(action)] = titleGroupMap[group.title];311});312}, this);313314// add container's mutually exclusive groups315// NOTE: if add_mutually_exclusive_group ever gains title= and316// description= then this code will need to be expanded as above317var mutexGroup;318container._mutuallyExclusiveGroups.forEach(function (group) {319mutexGroup = this.addMutuallyExclusiveGroup({320required: group.required321});322// map the actions to their new mutex group323group._groupActions.forEach(function (action) {324groupMap[actionHash(action)] = mutexGroup;325});326}, this); // forEach takes a 'this' argument327328// add all actions to this container or their group329container._actions.forEach(function (action) {330var key = actionHash(action);331if (!!groupMap[key]) {332groupMap[key]._addAction(action);333}334else335{336this._addAction(action);337}338});339};340341ActionContainer.prototype._getPositional = function (dest, options) {342if (_.isArray(dest)) {343dest = _.first(dest);344}345// make sure required is not specified346if (options.required) {347throw new Error('"required" is an invalid argument for positionals.');348}349350// mark positional arguments as required if at least one is351// always required352if (options.nargs !== $$.OPTIONAL && options.nargs !== $$.ZERO_OR_MORE) {353options.required = true;354}355if (options.nargs === $$.ZERO_OR_MORE && options.defaultValue === undefined) {356options.required = true;357}358359// return the keyword arguments with no option strings360options.dest = dest;361options.optionStrings = [];362return options;363};364365ActionContainer.prototype._getOptional = function (args, options) {366var prefixChars = this.prefixChars;367var optionStrings = [];368var optionStringsLong = [];369370// determine short and long option strings371args.forEach(function (optionString) {372// error on strings that don't start with an appropriate prefix373if (prefixChars.indexOf(optionString[0]) < 0) {374throw new Error(format('Invalid option string "%s": must start with a "%s".',375optionString,376prefixChars377));378}379380// strings starting with two prefix characters are long options381optionStrings.push(optionString);382if (optionString.length > 1 && prefixChars.indexOf(optionString[1]) >= 0) {383optionStringsLong.push(optionString);384}385});386387// infer dest, '--foo-bar' -> 'foo_bar' and '-x' -> 'x'388var dest = options.dest || null;389delete options.dest;390391if (!dest) {392var optionStringDest = optionStringsLong.length ? optionStringsLong[0] :optionStrings[0];393dest = _.trim(optionStringDest, this.prefixChars);394395if (dest.length === 0) {396throw new Error(397format('dest= is required for options like "%s"', optionStrings.join(', '))398);399}400dest = dest.replace(/-/g, '_');401}402403// return the updated keyword arguments404options.dest = dest;405options.optionStrings = optionStrings;406407return options;408};409410ActionContainer.prototype._popActionClass = function (options, defaultValue) {411defaultValue = defaultValue || null;412413var action = (options.action || defaultValue);414delete options.action;415416var actionClass = this._registryGet('action', action, action);417return actionClass;418};419420ActionContainer.prototype._getHandler = function () {421var handlerString = this.conflictHandler;422var handlerFuncName = "_handleConflict" + _.capitalize(handlerString);423var func = this[handlerFuncName];424if (typeof func === 'undefined') {425var msg = "invalid conflict resolution value: " + handlerString;426throw new Error(msg);427} else {428return func;429}430};431432ActionContainer.prototype._checkConflict = function (action) {433var optionStringActions = this._optionStringActions;434var conflictOptionals = [];435436// find all options that conflict with this option437// collect pairs, the string, and an existing action that it conflicts with438action.optionStrings.forEach(function (optionString) {439var conflOptional = optionStringActions[optionString];440if (typeof conflOptional !== 'undefined') {441conflictOptionals.push([optionString, conflOptional]);442}443});444445if (conflictOptionals.length > 0) {446var conflictHandler = this._getHandler();447conflictHandler.call(this, action, conflictOptionals);448}449};450451ActionContainer.prototype._handleConflictError = function (action, conflOptionals) {452var conflicts = _.map(conflOptionals, function (pair) {return pair[0]; });453conflicts = conflicts.join(', ');454throw argumentErrorHelper(455action,456format('Conflicting option string(s): %s', conflicts)457);458};459460ActionContainer.prototype._handleConflictResolve = function (action, conflOptionals) {461// remove all conflicting options462var self = this;463conflOptionals.forEach(function (pair) {464var optionString = pair[0];465var conflictingAction = pair[1];466// remove the conflicting option string467var i = conflictingAction.optionStrings.indexOf(optionString);468if (i >= 0) {469conflictingAction.optionStrings.splice(i, 1);470}471delete self._optionStringActions[optionString];472// if the option now has no option string, remove it from the473// container holding it474if (conflictingAction.optionStrings.length === 0) {475conflictingAction.container._removeAction(conflictingAction);476}477});478};479480481