365 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			365 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
const objFilter = require('./obj-filter')
 | 
						|
const specialKeys = ['$0', '--', '_']
 | 
						|
 | 
						|
// validation-type-stuff, missing params,
 | 
						|
// bad implications, custom checks.
 | 
						|
module.exports = function (yargs, usage, y18n) {
 | 
						|
  const __ = y18n.__
 | 
						|
  const __n = y18n.__n
 | 
						|
  const self = {}
 | 
						|
 | 
						|
  // validate appropriate # of non-option
 | 
						|
  // arguments were provided, i.e., '_'.
 | 
						|
  self.nonOptionCount = function (argv) {
 | 
						|
    const demandedCommands = yargs.getDemandedCommands()
 | 
						|
    // don't count currently executing commands
 | 
						|
    const _s = argv._.length - yargs.getContext().commands.length
 | 
						|
 | 
						|
    if (demandedCommands._ && (_s < demandedCommands._.min || _s > demandedCommands._.max)) {
 | 
						|
      if (_s < demandedCommands._.min) {
 | 
						|
        if (demandedCommands._.minMsg !== undefined) {
 | 
						|
          usage.fail(
 | 
						|
            // replace $0 with observed, $1 with expected.
 | 
						|
            demandedCommands._.minMsg ? demandedCommands._.minMsg.replace(/\$0/g, _s).replace(/\$1/, demandedCommands._.min) : null
 | 
						|
          )
 | 
						|
        } else {
 | 
						|
          usage.fail(
 | 
						|
            __('Not enough non-option arguments: got %s, need at least %s', _s, demandedCommands._.min)
 | 
						|
          )
 | 
						|
        }
 | 
						|
      } else if (_s > demandedCommands._.max) {
 | 
						|
        if (demandedCommands._.maxMsg !== undefined) {
 | 
						|
          usage.fail(
 | 
						|
            // replace $0 with observed, $1 with expected.
 | 
						|
            demandedCommands._.maxMsg ? demandedCommands._.maxMsg.replace(/\$0/g, _s).replace(/\$1/, demandedCommands._.max) : null
 | 
						|
          )
 | 
						|
        } else {
 | 
						|
          usage.fail(
 | 
						|
          __('Too many non-option arguments: got %s, maximum of %s', _s, demandedCommands._.max)
 | 
						|
          )
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // validate the appropriate # of <required>
 | 
						|
  // positional arguments were provided:
 | 
						|
  self.positionalCount = function (required, observed) {
 | 
						|
    if (observed < required) {
 | 
						|
      usage.fail(
 | 
						|
        __('Not enough non-option arguments: got %s, need at least %s', observed, required)
 | 
						|
      )
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // make sure that any args that require an
 | 
						|
  // value (--foo=bar), have a value.
 | 
						|
  self.missingArgumentValue = function (argv) {
 | 
						|
    const defaultValues = [true, false, '']
 | 
						|
    const options = yargs.getOptions()
 | 
						|
 | 
						|
    if (options.requiresArg.length > 0) {
 | 
						|
      const missingRequiredArgs = []
 | 
						|
 | 
						|
      options.requiresArg.forEach(function (key) {
 | 
						|
        const value = argv[key]
 | 
						|
 | 
						|
        // if a value is explicitly requested,
 | 
						|
        // flag argument as missing if it does not
 | 
						|
        // look like foo=bar was entered.
 | 
						|
        if (~defaultValues.indexOf(value) ||
 | 
						|
          (Array.isArray(value) && !value.length)) {
 | 
						|
          missingRequiredArgs.push(key)
 | 
						|
        }
 | 
						|
      })
 | 
						|
 | 
						|
      if (missingRequiredArgs.length > 0) {
 | 
						|
        usage.fail(__n(
 | 
						|
          'Missing argument value: %s',
 | 
						|
          'Missing argument values: %s',
 | 
						|
          missingRequiredArgs.length,
 | 
						|
          missingRequiredArgs.join(', ')
 | 
						|
        ))
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // make sure all the required arguments are present.
 | 
						|
  self.requiredArguments = function (argv) {
 | 
						|
    const demandedOptions = yargs.getDemandedOptions()
 | 
						|
    var missing = null
 | 
						|
 | 
						|
    Object.keys(demandedOptions).forEach(function (key) {
 | 
						|
      if (!argv.hasOwnProperty(key) || typeof argv[key] === 'undefined') {
 | 
						|
        missing = missing || {}
 | 
						|
        missing[key] = demandedOptions[key]
 | 
						|
      }
 | 
						|
    })
 | 
						|
 | 
						|
    if (missing) {
 | 
						|
      const customMsgs = []
 | 
						|
      Object.keys(missing).forEach(function (key) {
 | 
						|
        const msg = missing[key]
 | 
						|
        if (msg && customMsgs.indexOf(msg) < 0) {
 | 
						|
          customMsgs.push(msg)
 | 
						|
        }
 | 
						|
      })
 | 
						|
 | 
						|
      const customMsg = customMsgs.length ? '\n' + customMsgs.join('\n') : ''
 | 
						|
 | 
						|
      usage.fail(__n(
 | 
						|
        'Missing required argument: %s',
 | 
						|
        'Missing required arguments: %s',
 | 
						|
        Object.keys(missing).length,
 | 
						|
        Object.keys(missing).join(', ') + customMsg
 | 
						|
      ))
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // check for unknown arguments (strict-mode).
 | 
						|
  self.unknownArguments = function (argv, aliases, positionalMap) {
 | 
						|
    const aliasLookup = {}
 | 
						|
    const descriptions = usage.getDescriptions()
 | 
						|
    const demandedOptions = yargs.getDemandedOptions()
 | 
						|
    const commandKeys = yargs.getCommandInstance().getCommands()
 | 
						|
    const unknown = []
 | 
						|
    const currentContext = yargs.getContext()
 | 
						|
 | 
						|
    Object.keys(aliases).forEach(function (key) {
 | 
						|
      aliases[key].forEach(function (alias) {
 | 
						|
        aliasLookup[alias] = key
 | 
						|
      })
 | 
						|
    })
 | 
						|
 | 
						|
    Object.keys(argv).forEach(function (key) {
 | 
						|
      if (specialKeys.indexOf(key) === -1 &&
 | 
						|
        !descriptions.hasOwnProperty(key) &&
 | 
						|
        !demandedOptions.hasOwnProperty(key) &&
 | 
						|
        !positionalMap.hasOwnProperty(key) &&
 | 
						|
        !yargs._getParseContext().hasOwnProperty(key) &&
 | 
						|
        !aliasLookup.hasOwnProperty(key)) {
 | 
						|
        unknown.push(key)
 | 
						|
      }
 | 
						|
    })
 | 
						|
 | 
						|
    if (commandKeys.length > 0) {
 | 
						|
      argv._.slice(currentContext.commands.length).forEach(function (key) {
 | 
						|
        if (commandKeys.indexOf(key) === -1) {
 | 
						|
          unknown.push(key)
 | 
						|
        }
 | 
						|
      })
 | 
						|
    }
 | 
						|
 | 
						|
    if (unknown.length > 0) {
 | 
						|
      usage.fail(__n(
 | 
						|
        'Unknown argument: %s',
 | 
						|
        'Unknown arguments: %s',
 | 
						|
        unknown.length,
 | 
						|
        unknown.join(', ')
 | 
						|
      ))
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // validate arguments limited to enumerated choices
 | 
						|
  self.limitedChoices = function (argv) {
 | 
						|
    const options = yargs.getOptions()
 | 
						|
    const invalid = {}
 | 
						|
 | 
						|
    if (!Object.keys(options.choices).length) return
 | 
						|
 | 
						|
    Object.keys(argv).forEach(function (key) {
 | 
						|
      if (specialKeys.indexOf(key) === -1 &&
 | 
						|
        options.choices.hasOwnProperty(key)) {
 | 
						|
        [].concat(argv[key]).forEach(function (value) {
 | 
						|
          // TODO case-insensitive configurability
 | 
						|
          if (options.choices[key].indexOf(value) === -1) {
 | 
						|
            invalid[key] = (invalid[key] || []).concat(value)
 | 
						|
          }
 | 
						|
        })
 | 
						|
      }
 | 
						|
    })
 | 
						|
 | 
						|
    const invalidKeys = Object.keys(invalid)
 | 
						|
 | 
						|
    if (!invalidKeys.length) return
 | 
						|
 | 
						|
    var msg = __('Invalid values:')
 | 
						|
    invalidKeys.forEach(function (key) {
 | 
						|
      msg += '\n  ' + __(
 | 
						|
        'Argument: %s, Given: %s, Choices: %s',
 | 
						|
        key,
 | 
						|
        usage.stringifiedValues(invalid[key]),
 | 
						|
        usage.stringifiedValues(options.choices[key])
 | 
						|
      )
 | 
						|
    })
 | 
						|
    usage.fail(msg)
 | 
						|
  }
 | 
						|
 | 
						|
  // custom checks, added using the `check` option on yargs.
 | 
						|
  var checks = []
 | 
						|
  self.check = function (f, global) {
 | 
						|
    checks.push({
 | 
						|
      func: f,
 | 
						|
      global: global
 | 
						|
    })
 | 
						|
  }
 | 
						|
 | 
						|
  self.customChecks = function (argv, aliases) {
 | 
						|
    for (var i = 0, f; (f = checks[i]) !== undefined; i++) {
 | 
						|
      var func = f.func
 | 
						|
      var result = null
 | 
						|
      try {
 | 
						|
        result = func(argv, aliases)
 | 
						|
      } catch (err) {
 | 
						|
        usage.fail(err.message ? err.message : err, err)
 | 
						|
        continue
 | 
						|
      }
 | 
						|
 | 
						|
      if (!result) {
 | 
						|
        usage.fail(__('Argument check failed: %s', func.toString()))
 | 
						|
      } else if (typeof result === 'string' || result instanceof Error) {
 | 
						|
        usage.fail(result.toString(), result)
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // check implications, argument foo implies => argument bar.
 | 
						|
  var implied = {}
 | 
						|
  self.implies = function (key, value) {
 | 
						|
    if (typeof key === 'object') {
 | 
						|
      Object.keys(key).forEach(function (k) {
 | 
						|
        self.implies(k, key[k])
 | 
						|
      })
 | 
						|
    } else {
 | 
						|
      yargs.global(key)
 | 
						|
      implied[key] = value
 | 
						|
    }
 | 
						|
  }
 | 
						|
  self.getImplied = function () {
 | 
						|
    return implied
 | 
						|
  }
 | 
						|
 | 
						|
  self.implications = function (argv) {
 | 
						|
    const implyFail = []
 | 
						|
 | 
						|
    Object.keys(implied).forEach(function (key) {
 | 
						|
      var num
 | 
						|
      const origKey = key
 | 
						|
      var value = implied[key]
 | 
						|
 | 
						|
      // convert string '1' to number 1
 | 
						|
      num = Number(key)
 | 
						|
      key = isNaN(num) ? key : num
 | 
						|
 | 
						|
      if (typeof key === 'number') {
 | 
						|
        // check length of argv._
 | 
						|
        key = argv._.length >= key
 | 
						|
      } else if (key.match(/^--no-.+/)) {
 | 
						|
        // check if key doesn't exist
 | 
						|
        key = key.match(/^--no-(.+)/)[1]
 | 
						|
        key = !argv[key]
 | 
						|
      } else {
 | 
						|
        // check if key exists
 | 
						|
        key = argv[key]
 | 
						|
      }
 | 
						|
 | 
						|
      num = Number(value)
 | 
						|
      value = isNaN(num) ? value : num
 | 
						|
 | 
						|
      if (typeof value === 'number') {
 | 
						|
        value = argv._.length >= value
 | 
						|
      } else if (value.match(/^--no-.+/)) {
 | 
						|
        value = value.match(/^--no-(.+)/)[1]
 | 
						|
        value = !argv[value]
 | 
						|
      } else {
 | 
						|
        value = argv[value]
 | 
						|
      }
 | 
						|
 | 
						|
      if (key && !value) {
 | 
						|
        implyFail.push(origKey)
 | 
						|
      }
 | 
						|
    })
 | 
						|
 | 
						|
    if (implyFail.length) {
 | 
						|
      var msg = __('Implications failed:') + '\n'
 | 
						|
 | 
						|
      implyFail.forEach(function (key) {
 | 
						|
        msg += ('  ' + key + ' -> ' + implied[key])
 | 
						|
      })
 | 
						|
 | 
						|
      usage.fail(msg)
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  var conflicting = {}
 | 
						|
  self.conflicts = function (key, value) {
 | 
						|
    if (typeof key === 'object') {
 | 
						|
      Object.keys(key).forEach(function (k) {
 | 
						|
        self.conflicts(k, key[k])
 | 
						|
      })
 | 
						|
    } else {
 | 
						|
      yargs.global(key)
 | 
						|
      conflicting[key] = value
 | 
						|
    }
 | 
						|
  }
 | 
						|
  self.getConflicting = function () {
 | 
						|
    return conflicting
 | 
						|
  }
 | 
						|
 | 
						|
  self.conflicting = function (argv) {
 | 
						|
    var args = Object.getOwnPropertyNames(argv)
 | 
						|
 | 
						|
    args.forEach(function (arg) {
 | 
						|
      if (conflicting[arg] && args.indexOf(conflicting[arg]) !== -1) {
 | 
						|
        usage.fail(__('Arguments %s and %s are mutually exclusive', arg, conflicting[arg]))
 | 
						|
      }
 | 
						|
    })
 | 
						|
  }
 | 
						|
 | 
						|
  self.recommendCommands = function (cmd, potentialCommands) {
 | 
						|
    const distance = require('./levenshtein')
 | 
						|
    const threshold = 3 // if it takes more than three edits, let's move on.
 | 
						|
    potentialCommands = potentialCommands.sort(function (a, b) { return b.length - a.length })
 | 
						|
 | 
						|
    var recommended = null
 | 
						|
    var bestDistance = Infinity
 | 
						|
    for (var i = 0, candidate; (candidate = potentialCommands[i]) !== undefined; i++) {
 | 
						|
      var d = distance(cmd, candidate)
 | 
						|
      if (d <= threshold && d < bestDistance) {
 | 
						|
        bestDistance = d
 | 
						|
        recommended = candidate
 | 
						|
      }
 | 
						|
    }
 | 
						|
    if (recommended) usage.fail(__('Did you mean %s?', recommended))
 | 
						|
  }
 | 
						|
 | 
						|
  self.reset = function (localLookup) {
 | 
						|
    implied = objFilter(implied, function (k, v) {
 | 
						|
      return !localLookup[k]
 | 
						|
    })
 | 
						|
    conflicting = objFilter(conflicting, function (k, v) {
 | 
						|
      return !localLookup[k]
 | 
						|
    })
 | 
						|
    checks = checks.filter(function (c) {
 | 
						|
      return c.global
 | 
						|
    })
 | 
						|
    return self
 | 
						|
  }
 | 
						|
 | 
						|
  var frozen
 | 
						|
  self.freeze = function () {
 | 
						|
    frozen = {}
 | 
						|
    frozen.implied = implied
 | 
						|
    frozen.checks = checks
 | 
						|
    frozen.conflicting = conflicting
 | 
						|
  }
 | 
						|
  self.unfreeze = function () {
 | 
						|
    implied = frozen.implied
 | 
						|
    checks = frozen.checks
 | 
						|
    conflicting = frozen.conflicting
 | 
						|
    frozen = undefined
 | 
						|
  }
 | 
						|
 | 
						|
  return self
 | 
						|
}
 |