| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341 | 'use strict'const argsert = require('./argsert')const objFilter = require('./obj-filter')const specialKeys = ['$0', '--', '_']// validation-type-stuff, missing params,// bad implications, custom checks.module.exports = function validation (yargs, usage, y18n) {  const __ = y18n.__  const __n = y18n.__n  const self = {}  // validate appropriate # of non-option  // arguments were provided, i.e., '_'.  self.nonOptionCount = function nonOptionCount (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 positionalCount (required, observed) {    if (observed < required) {      usage.fail(        __('Not enough non-option arguments: got %s, need at least %s', observed, required)      )    }  }  // make sure all the required arguments are present.  self.requiredArguments = function requiredArguments (argv) {    const demandedOptions = yargs.getDemandedOptions()    let missing = null    Object.keys(demandedOptions).forEach((key) => {      if (!argv.hasOwnProperty(key) || typeof argv[key] === 'undefined') {        missing = missing || {}        missing[key] = demandedOptions[key]      }    })    if (missing) {      const customMsgs = []      Object.keys(missing).forEach((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 unknownArguments (argv, aliases, positionalMap) {    const commandKeys = yargs.getCommandInstance().getCommands()    const unknown = []    const currentContext = yargs.getContext()    Object.keys(argv).forEach((key) => {      if (specialKeys.indexOf(key) === -1 &&        !positionalMap.hasOwnProperty(key) &&        !yargs._getParseContext().hasOwnProperty(key) &&        !aliases.hasOwnProperty(key)      ) {        unknown.push(key)      }    })    if (commandKeys.length > 0) {      argv._.slice(currentContext.commands.length).forEach((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 limitedChoices (argv) {    const options = yargs.getOptions()    const invalid = {}    if (!Object.keys(options.choices).length) return    Object.keys(argv).forEach((key) => {      if (specialKeys.indexOf(key) === -1 &&        options.choices.hasOwnProperty(key)) {        [].concat(argv[key]).forEach((value) => {          // TODO case-insensitive configurability          if (options.choices[key].indexOf(value) === -1 &&              value !== undefined) {            invalid[key] = (invalid[key] || []).concat(value)          }        })      }    })    const invalidKeys = Object.keys(invalid)    if (!invalidKeys.length) return    let msg = __('Invalid values:')    invalidKeys.forEach((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.  let checks = []  self.check = function check (f, global) {    checks.push({      func: f,      global    })  }  self.customChecks = function customChecks (argv, aliases) {    for (let i = 0, f; (f = checks[i]) !== undefined; i++) {      const func = f.func      let 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.  let implied = {}  self.implies = function implies (key, value) {    argsert('<string|object> [array|number|string]', [key, value], arguments.length)    if (typeof key === 'object') {      Object.keys(key).forEach((k) => {        self.implies(k, key[k])      })    } else {      yargs.global(key)      if (!implied[key]) {        implied[key] = []      }      if (Array.isArray(value)) {        value.forEach((i) => self.implies(key, i))      } else {        implied[key].push(value)      }    }  }  self.getImplied = function getImplied () {    return implied  }  self.implications = function implications (argv) {    const implyFail = []    Object.keys(implied).forEach((key) => {      const origKey = key      ;(implied[key] || []).forEach((value) => {        let num        let key = origKey        const origValue = value        // 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} -> ${origValue}`)        }      })    })    if (implyFail.length) {      let msg = `${__('Implications failed:')}\n`      implyFail.forEach((value) => {        msg += (value)      })      usage.fail(msg)    }  }  let conflicting = {}  self.conflicts = function conflicts (key, value) {    argsert('<string|object> [array|string]', [key, value], arguments.length)    if (typeof key === 'object') {      Object.keys(key).forEach((k) => {        self.conflicts(k, key[k])      })    } else {      yargs.global(key)      if (!conflicting[key]) {        conflicting[key] = []      }      if (Array.isArray(value)) {        value.forEach((i) => self.conflicts(key, i))      } else {        conflicting[key].push(value)      }    }  }  self.getConflicting = () => conflicting  self.conflicting = function conflictingFn (argv) {    Object.keys(argv).forEach((key) => {      if (conflicting[key]) {        conflicting[key].forEach((value) => {          // we default keys to 'undefined' that have been configured, we should not          // apply conflicting check unless they are a value other than 'undefined'.          if (value && argv[key] !== undefined && argv[value] !== undefined) {            usage.fail(__('Arguments %s and %s are mutually exclusive', key, value))          }        })      }    })  }  self.recommendCommands = function recommendCommands (cmd, potentialCommands) {    const distance = require('./levenshtein')    const threshold = 3 // if it takes more than three edits, let's move on.    potentialCommands = potentialCommands.sort((a, b) => b.length - a.length)    let recommended = null    let bestDistance = Infinity    for (let i = 0, candidate; (candidate = potentialCommands[i]) !== undefined; i++) {      const 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 reset (localLookup) {    implied = objFilter(implied, (k, v) => !localLookup[k])    conflicting = objFilter(conflicting, (k, v) => !localLookup[k])    checks = checks.filter(c => c.global)    return self  }  let frozen  self.freeze = function freeze () {    frozen = {}    frozen.implied = implied    frozen.checks = checks    frozen.conflicting = conflicting  }  self.unfreeze = function unfreeze () {    implied = frozen.implied    checks = frozen.checks    conflicting = frozen.conflicting    frozen = undefined  }  return self}
 |