You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							341 lines
						
					
					
						
							9.8 KiB
						
					
					
				
			
		
		
	
	
							341 lines
						
					
					
						
							9.8 KiB
						
					
					
				'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 | 
						|
}
 | 
						|
 |