UNPKG

luhn-generator

Version:

A generator of numbers that passes the validation of Luhn algorithm or Luhn formula, also known as the 'modulus 10' or 'mod 10' algorithm

342 lines (300 loc) 10.1 kB
'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 }