1// validation-type-stuff, missing params,
2// bad implications, custom checks.
3module.exports = function (yargs, usage) {
4  var self = {}
5
6  // validate appropriate # of non-option
7  // arguments were provided, i.e., '_'.
8  self.nonOptionCount = function (argv) {
9    var demanded = yargs.getDemanded()
10
11    if (demanded._ && argv._.length < demanded._.count) {
12      if (demanded._.msg !== undefined) {
13        usage.fail(demanded._.msg)
14      } else {
15        usage.fail('Not enough non-option arguments: got '
16          + argv._.length + ', need at least ' + demanded._.count
17        )
18      }
19    }
20  }
21
22  // make sure that any args that require an
23  // value (--foo=bar), have a value.
24  self.missingArgumentValue = function (argv) {
25    var options = yargs.getOptions(),
26      defaultValues = [true, false, '']
27
28    if (options.requiresArg.length > 0) {
29      var missingRequiredArgs = []
30
31      options.requiresArg.forEach(function (key) {
32        var value = argv[key]
33
34        // if a value is explicitly requested,
35        // flag argument as missing if it does not
36        // look like foo=bar was entered.
37        if (~defaultValues.indexOf(value)
38        || (Array.isArray(value) && !value.length)) {
39          missingRequiredArgs.push(key)
40        }
41      })
42
43      if (missingRequiredArgs.length === 1) {
44        usage.fail('Missing argument value: ' + missingRequiredArgs[0])
45      } else if (missingRequiredArgs.length > 1) {
46        var message = 'Missing argument values: ' + missingRequiredArgs.join(', ')
47        usage.fail(message)
48      }
49    }
50  }
51
52  // make sure all the required arguments are present.
53  self.requiredArguments = function (argv) {
54    var demanded = yargs.getDemanded(),
55      missing = null
56
57    Object.keys(demanded).forEach(function (key) {
58      if (!argv.hasOwnProperty(key)) {
59        missing = missing || {}
60        missing[key] = demanded[key]
61      }
62    })
63
64    if (missing) {
65      var customMsgs = []
66      Object.keys(missing).forEach(function (key) {
67        var msg = missing[key].msg
68        if (msg && customMsgs.indexOf(msg) < 0) {
69          customMsgs.push(msg)
70        }
71      })
72
73      var customMsg = customMsgs.length ? '\n' + customMsgs.join('\n') : ''
74      usage.fail('Missing required arguments: ' + Object.keys(missing).join(', ') + customMsg)
75    }
76  }
77
78  // check for unknown arguments (strict-mode).
79  self.unknownArguments = function (argv, aliases) {
80    var descriptions = usage.getDescriptions(),
81      demanded = yargs.getDemanded(),
82      unknown = [],
83      aliasLookup = {}
84
85    Object.keys(aliases).forEach(function (key) {
86      aliases[key].forEach(function (alias) {
87        aliasLookup[alias] = key
88      })
89    })
90
91    Object.keys(argv).forEach(function (key) {
92      if (key !== '$0' && key !== '_' &&
93        !descriptions.hasOwnProperty(key) &&
94        !demanded.hasOwnProperty(key) &&
95        !aliasLookup.hasOwnProperty(key)) {
96        unknown.push(key)
97      }
98    })
99
100    if (unknown.length === 1) {
101      usage.fail('Unknown argument: ' + unknown[0])
102    } else if (unknown.length > 1) {
103      usage.fail('Unknown arguments: ' + unknown.join(', '))
104    }
105  }
106
107  // custom checks, added using the `check` option on yargs.
108  var checks = []
109  self.check = function (f) {
110    checks.push(f)
111  }
112
113  self.customChecks = function (argv, aliases) {
114    checks.forEach(function (f) {
115      try {
116        var result = f(argv, aliases)
117        if (!result) {
118          usage.fail('Argument check failed: ' + f.toString())
119        } else if (typeof result === 'string') {
120          usage.fail(result)
121        }
122      } catch (err) {
123        usage.fail(err.message ? err.message : err)
124      }
125    })
126  }
127
128  // check implications, argument foo implies => argument bar.
129  var implied = {}
130  self.implies = function (key, value) {
131    if (typeof key === 'object') {
132      Object.keys(key).forEach(function (k) {
133        self.implies(k, key[k])
134      })
135    } else {
136      implied[key] = value
137    }
138  }
139  self.getImplied = function () {
140    return implied
141  }
142
143  self.implications = function (argv) {
144    var implyFail = []
145
146    Object.keys(implied).forEach(function (key) {
147      var num,
148        origKey = key,
149        value = implied[key]
150
151      // convert string '1' to number 1
152      num = Number(key)
153      key = isNaN(num) ? key : num
154
155      if (typeof key === 'number') {
156        // check length of argv._
157        key = argv._.length >= key
158      } else if (key.match(/^--no-.+/)) {
159        // check if key doesn't exist
160        key = key.match(/^--no-(.+)/)[1]
161        key = !argv[key]
162      } else {
163        // check if key exists
164        key = argv[key]
165      }
166
167      num = Number(value)
168      value = isNaN(num) ? value : num
169
170      if (typeof value === 'number') {
171        value = argv._.length >= value
172      } else if (value.match(/^--no-.+/)) {
173        value = value.match(/^--no-(.+)/)[1]
174        value = !argv[value]
175      } else {
176        value = argv[value]
177      }
178
179      if (key && !value) {
180        implyFail.push(origKey)
181      }
182    })
183
184    if (implyFail.length) {
185      var msg = 'Implications failed:\n'
186
187      implyFail.forEach(function (key) {
188        msg += ('  ' + key + ' -> ' + implied[key])
189      })
190
191      usage.fail(msg)
192    }
193  }
194
195  return self
196}
197