1 | _ = require('lodash')
|
2 | _.str = require('underscore.string')
|
3 | chai = require('chai')
|
4 | expect = chai.expect
|
5 | parse = require('../lib/parse')
|
6 | state = require('../lib/state')
|
7 | Option = require('../lib/option')
|
8 | Signature = require('../lib/signature')
|
9 | settings = require('../lib/settings')
|
10 |
|
11 | describe 'Parse:', ->
|
12 |
|
13 | describe '#normalizeInput()', ->
|
14 |
|
15 | it 'should handle strings', ->
|
16 | result = parse.normalizeInput('-x 3 -y 4')
|
17 | expect(result).to.deep.equal([ '-x', '3', '-y', '4' ])
|
18 |
|
19 | it 'should handle arrays', ->
|
20 | result = parse.normalizeInput([ '-x', '3', '-y', '4' ])
|
21 | expect(result).to.deep.equal([ '-x', '3', '-y', '4' ])
|
22 |
|
23 | it 'should discard first arguments if process.argv', ->
|
24 | result = parse.normalizeInput(process.argv)
|
25 | expect(result).to.deep.equal(process.argv.slice(2))
|
26 |
|
27 | it 'should throw an error if invalid input', ->
|
28 | expect ->
|
29 | parse.normalizeInput({ hello: 'world' })
|
30 | .to.throw(Error)
|
31 |
|
32 | describe '#parse()', ->
|
33 |
|
34 | describe 'options', ->
|
35 |
|
36 | beforeEach ->
|
37 | state.globalOptions = []
|
38 |
|
39 | it 'should be able to parse options', ->
|
40 | argv = parse.split('-x 3 -y 4')
|
41 | result = parse.parse(argv)
|
42 | expect(result).to.deep.equal
|
43 | global: {}
|
44 | options:
|
45 | x: 3
|
46 | y: 4
|
47 |
|
48 | it 'should be able to parse boolean options', ->
|
49 | argv = parse.split('-x --foo')
|
50 | result = parse.parse(argv)
|
51 | expect(result).to.deep.equal
|
52 | global: {}
|
53 | options:
|
54 | x: true
|
55 | foo: true
|
56 |
|
57 | it 'should be able to compile global options', ->
|
58 | state.globalOptions.push new Option
|
59 | signature: new Signature('quiet')
|
60 | boolean: true
|
61 |
|
62 | argv = parse.split('foo --quiet')
|
63 | result = parse.parse(argv)
|
64 | expect(result).to.deep.equal
|
65 | global:
|
66 | quiet: true
|
67 | options:
|
68 | quiet: true
|
69 | command: 'foo'
|
70 |
|
71 | it 'should be able to compile global options with aliases', ->
|
72 | state.globalOptions.push new Option
|
73 | signature: new Signature('quiet')
|
74 | boolean: true
|
75 | alias: 'q'
|
76 |
|
77 | argv = parse.split('foo -q')
|
78 | result = parse.parse(argv)
|
79 | expect(result).to.deep.equal
|
80 | global:
|
81 | quiet: true
|
82 | options:
|
83 | q: true
|
84 | command: 'foo'
|
85 |
|
86 | describe 'commands', ->
|
87 |
|
88 | it 'should be able to parse commands', ->
|
89 | argv = parse.split('auth login')
|
90 | result = parse.parse(argv)
|
91 | expect(result).to.deep.equal
|
92 | command: 'auth login'
|
93 | options: {}
|
94 | global: {}
|
95 |
|
96 | it 'should be able to parse commands with suffix options', ->
|
97 | argv = parse.split('auth login -f -n 10')
|
98 | result = parse.parse(argv)
|
99 | expect(result).to.deep.equal
|
100 | command: 'auth login'
|
101 | global: {}
|
102 | options:
|
103 | f: true
|
104 | n: 10
|
105 |
|
106 | it 'should be able to parse commands with prefix options', ->
|
107 | argv = parse.split('-f -n 10 auth login')
|
108 | result = parse.parse(argv)
|
109 | expect(result).to.deep.equal
|
110 | command: 'auth login'
|
111 | global: {}
|
112 | options:
|
113 | f: true
|
114 | n: 10
|
115 |
|
116 | it 'should be able to parse commands with infix options', ->
|
117 | argv = parse.split('auth -f -n 10 login')
|
118 | result = parse.parse(argv)
|
119 | expect(result).to.deep.equal
|
120 | command: 'auth login'
|
121 | global: {}
|
122 | options:
|
123 | f: true
|
124 | n: 10
|
125 |
|
126 | it 'should be able to parse commands with arguments', ->
|
127 | argv = parse.split('auth login <credentials>')
|
128 | result = parse.parse(argv)
|
129 | expect(result).to.deep.equal
|
130 | command: 'auth login <credentials>'
|
131 | global: {}
|
132 | options: {}
|
133 |
|
134 | it 'should be able to parse commands with multiple arguments', ->
|
135 | argv = parse.split('auth login <credentials> <foo>')
|
136 | result = parse.parse(argv)
|
137 | expect(result).to.deep.equal
|
138 | command: 'auth login <credentials> <foo>'
|
139 | global: {}
|
140 | options: {}
|
141 |
|
142 | it 'should be able to parse commands with optional arguments', ->
|
143 | argv = parse.split('auth login [foo]')
|
144 | result = parse.parse(argv)
|
145 | expect(result).to.deep.equal
|
146 | command: 'auth login [foo]'
|
147 | global: {}
|
148 | options: {}
|
149 |
|
150 | it 'should be able to parse commands with multiple arguments', ->
|
151 | argv = parse.split('transfer <from name> <to name>')
|
152 | result = parse.parse(argv)
|
153 | expect(result).to.deep.equal
|
154 | command: 'transfer <from name> <to name>'
|
155 | global: {}
|
156 | options: {}
|
157 |
|
158 | it 'should be able to parse commands with optional arguments', ->
|
159 | argv = parse.split('greet [their name]')
|
160 | result = parse.parse(argv)
|
161 | expect(result).to.deep.equal
|
162 | command: 'greet [their name]'
|
163 | global: {}
|
164 | options: {}
|
165 |
|
166 | it 'should not discard single quotes when parsing the command', ->
|
167 | argv = parse.split('hello \'John Doe\'')
|
168 | result = parse.parse(argv)
|
169 | expect(result).to.deep.equal
|
170 | command: 'hello "John Doe"'
|
171 | global: {}
|
172 | options: {}
|
173 |
|
174 | it 'should not discard double quotes when parsing the command', ->
|
175 | argv = parse.split('hello "John Doe"')
|
176 | result = parse.parse(argv)
|
177 | expect(result).to.deep.equal
|
178 | command: 'hello "John Doe"'
|
179 | global: {}
|
180 | options: {}
|
181 |
|
182 | it 'should not parse numbers in scientific notation automatically', ->
|
183 | argv = parse.split('hello -x 43e8273')
|
184 | result = parse.parse(argv)
|
185 | expect(result).to.deep.equal
|
186 | command: 'hello'
|
187 | global: {}
|
188 | options:
|
189 | x: '43e8273'
|
190 |
|
191 | it 'should parse float numbers automatically', ->
|
192 | argv = parse.split('hello -x 1.5')
|
193 | result = parse.parse(argv)
|
194 | expect(result).to.deep.equal
|
195 | command: 'hello'
|
196 | global: {}
|
197 | options:
|
198 | x: 1.5
|
199 |
|
200 | describe '#split()', ->
|
201 |
|
202 | it 'should return an empty array if no signature', ->
|
203 | signature = undefined
|
204 | result = []
|
205 | expect(parse.split(signature)).to.deep.equal(result)
|
206 |
|
207 | it 'should split a wildcard signature correctly', ->
|
208 | signature = settings.signatures.wildcard
|
209 | result = [ settings.signatures.wildcard ]
|
210 | expect(parse.split(signature)).to.deep.equal(result)
|
211 |
|
212 | it 'should split signatures correctly', ->
|
213 | signature = 'foo <bar>'
|
214 | result = [ 'foo', '<bar>' ]
|
215 | expect(parse.split(signature)).to.deep.equal(result)
|
216 |
|
217 | it 'should split multi word required parameters', ->
|
218 | signature = '<hello world> <foo bar baz>'
|
219 | result = [ '<hello world>', '<foo bar baz>' ]
|
220 | expect(parse.split(signature)).to.deep.equal(result)
|
221 |
|
222 | it 'should split multi word optional parameters', ->
|
223 | signature = '[hello world] [foo bar baz]'
|
224 | result = [ '[hello world]', '[foo bar baz]' ]
|
225 | expect(parse.split(signature)).to.deep.equal(result)
|
226 |
|
227 | it 'should split multi word variadic required parameters', ->
|
228 | signature = '<hello world...> <foo bar baz...>'
|
229 | result = [ '<hello world...>', '<foo bar baz...>' ]
|
230 | expect(parse.split(signature)).to.deep.equal(result)
|
231 |
|
232 | it 'should split multi word optional parameters', ->
|
233 | signature = '[hello world...] [foo bar baz...]'
|
234 | result = [ '[hello world...]', '[foo bar baz...]' ]
|
235 | expect(parse.split(signature)).to.deep.equal(result)
|
236 |
|
237 | it 'should split absolute paths parameters correctly', ->
|
238 | signature = 'foo /Users/me/foo/bar'
|
239 | result = [ 'foo', '/Users/me/foo/bar' ]
|
240 | expect(parse.split(signature)).to.deep.equal(result)
|
241 |
|
242 | it 'should split absolute paths (win32) parameters correctly', ->
|
243 | signature = 'foo C:\\Users\\me\\foo\\bar'
|
244 | result = [ 'foo', 'C:\\Users\\me\\foo\\bar' ]
|
245 | expect(parse.split(signature)).to.deep.equal(result)
|
246 |
|
247 | it 'should split relative paths parameters correctly', ->
|
248 | signature = 'foo ../hello/world'
|
249 | result = [ 'foo', '../hello/world' ]
|
250 | expect(parse.split(signature)).to.deep.equal(result)
|
251 |
|
252 | it 'should split home relative paths parameters correctly', ->
|
253 | signature = 'foo ~/.ssh/id_rsa.pub'
|
254 | result = [ 'foo', '~/.ssh/id_rsa.pub' ]
|
255 | expect(parse.split(signature)).to.deep.equal(result)
|
256 |
|
257 | it 'should split words surrounded by quotes correctly', ->
|
258 | signature = 'foo \'hello world\''
|
259 | result = [ 'foo', 'hello world' ]
|
260 | expect(parse.split(signature)).to.deep.equal(result)
|
261 |
|
262 | it 'should split words surrounded by double quotes correctly', ->
|
263 | signature = 'foo "hello world"'
|
264 | result = [ 'foo', 'hello world' ]
|
265 | expect(parse.split(signature)).to.deep.equal(result)
|
266 |
|
267 | describe '#parseOptions()', ->
|
268 |
|
269 | it 'should not throw if options is undefined', ->
|
270 | definedOptions = []
|
271 | definedOptions.push new Option
|
272 | signature: new Signature('foo')
|
273 | boolean: true
|
274 |
|
275 | expect ->
|
276 | parse.parseOptions(definedOptions, undefined)
|
277 | .to.not.throw(Error)
|
278 |
|
279 | it 'should return an empty object if defined options is empty', ->
|
280 | options =
|
281 | hello: 'world'
|
282 | quiet: true
|
283 | expect(parse.parseOptions([], options)).to.deep.equal({})
|
284 |
|
285 | it 'should return an empty object if options is empty', ->
|
286 | definedOptions = []
|
287 | definedOptions.push new Option
|
288 | signature: new Signature('foo')
|
289 | parameter: 'bar'
|
290 | expect(parse.parseOptions(definedOptions, [])).to.deep.equal({})
|
291 |
|
292 | it 'should parse simple options (without aliases)', ->
|
293 | definedOptions = []
|
294 | definedOptions.push new Option
|
295 | signature: new Signature('foo')
|
296 | parameter: 'bar'
|
297 |
|
298 | definedOptions.push new Option
|
299 | signature: new Signature('quiet')
|
300 | boolean: true
|
301 |
|
302 | options =
|
303 | foo: 'baz'
|
304 | quiet: true
|
305 |
|
306 | result = parse.parseOptions(definedOptions, options)
|
307 | expect(result).to.deep.equal
|
308 | foo: 'baz'
|
309 | quiet: true
|
310 |
|
311 | it 'should parse options starting with a number correctly', ->
|
312 | definedOptions = []
|
313 | definedOptions.push new Option
|
314 | signature: new Signature('foo')
|
315 | parameter: 'bar'
|
316 |
|
317 | options =
|
318 | foo: '10foobar'
|
319 |
|
320 | result = parse.parseOptions(definedOptions, options)
|
321 | expect(result).to.deep.equal
|
322 | foo: '10foobar'
|
323 |
|
324 | it 'should parse options containing integers as numbers', ->
|
325 | definedOptions = []
|
326 | definedOptions.push new Option
|
327 | signature: new Signature('foo')
|
328 | parameter: 'bar'
|
329 |
|
330 | options =
|
331 | foo: '10'
|
332 |
|
333 | result = parse.parseOptions(definedOptions, options)
|
334 | expect(result).to.deep.equal
|
335 | foo: 10
|
336 |
|
337 | it 'should discard non matched options', ->
|
338 | definedOptions = []
|
339 | definedOptions.push new Option
|
340 | signature: new Signature('foo')
|
341 | parameter: 'bar'
|
342 |
|
343 | definedOptions.push new Option
|
344 | signature: new Signature('hello')
|
345 | parameter: 'world'
|
346 |
|
347 | definedOptions.push new Option
|
348 | signature: new Signature('quiet')
|
349 | boolean: true
|
350 |
|
351 | options =
|
352 | foo: 'baz'
|
353 | quiet: 'hello'
|
354 | hello: true
|
355 |
|
356 | result = parse.parseOptions(definedOptions, options)
|
357 | expect(result).to.deep.equal
|
358 | foo: 'baz'
|
359 |
|
360 | it 'shoud omit extra defined options', ->
|
361 | definedOptions = []
|
362 | definedOptions.push new Option
|
363 | signature: new Signature('foo')
|
364 | parameter: 'bar'
|
365 |
|
366 | definedOptions.push new Option
|
367 | signature: new Signature('quiet')
|
368 | boolean: true
|
369 |
|
370 | options =
|
371 | foo: 'baz'
|
372 |
|
373 | result = parse.parseOptions(definedOptions, options)
|
374 | expect(result).to.deep.equal
|
375 | foo: 'baz'
|
376 |
|
377 | it 'should handle string aliases', ->
|
378 | definedOptions = []
|
379 | definedOptions.push new Option
|
380 | signature: new Signature('foo')
|
381 | parameter: 'bar'
|
382 | alias: 'f'
|
383 |
|
384 | options =
|
385 | f: 'baz'
|
386 |
|
387 | result = parse.parseOptions(definedOptions, options)
|
388 | expect(result).to.deep.equal
|
389 | foo: 'baz'
|
390 |
|
391 | it 'should handle array aliases', ->
|
392 | definedOptions = []
|
393 | definedOptions.push new Option
|
394 | signature: new Signature('foo')
|
395 | parameter: 'bar'
|
396 | alias: [ 'a', 'b', 'c' ]
|
397 |
|
398 | options =
|
399 | b: 'baz'
|
400 |
|
401 | result = parse.parseOptions(definedOptions, options)
|
402 | expect(result).to.deep.equal
|
403 | foo: 'baz'
|
404 |
|
405 | it 'should handle multiletter aliases', ->
|
406 | definedOptions = []
|
407 | definedOptions.push new Option
|
408 | signature: new Signature('foo')
|
409 | parameter: 'bar'
|
410 | alias: 'hello'
|
411 |
|
412 | options =
|
413 | hello: 'world'
|
414 |
|
415 | result = parse.parseOptions(definedOptions, options)
|
416 | expect(result).to.deep.equal
|
417 | foo: 'world'
|
418 |
|
419 | it 'should give precedence to long names', ->
|
420 | definedOptions = []
|
421 | definedOptions.push new Option
|
422 | signature: new Signature('foo')
|
423 | parameter: 'bar'
|
424 | alias: [ 'a', 'b', 'c' ]
|
425 |
|
426 | options =
|
427 | foo: 'bar'
|
428 | b: 'baz'
|
429 |
|
430 | result = parse.parseOptions(definedOptions, options)
|
431 | expect(result).to.deep.equal
|
432 | foo: 'bar'
|
433 |
|
434 | it 'should parse numbers automatically', ->
|
435 | definedOptions = []
|
436 | definedOptions.push new Option
|
437 | signature: new Signature('foo')
|
438 | parameter: 'bar'
|
439 |
|
440 | options =
|
441 | foo: '25'
|
442 |
|
443 | result = parse.parseOptions(definedOptions, options)
|
444 | expect(result).to.deep.equal
|
445 | foo: 25
|
446 |
|
447 | describe 'given an option required key', ->
|
448 |
|
449 | it 'should throw a generic error if true', ->
|
450 | definedOptions = []
|
451 | definedOptions.push new Option
|
452 | signature: new Signature('foo')
|
453 | parameter: 'bar'
|
454 | required: true
|
455 |
|
456 | options =
|
457 | hello: 'world'
|
458 |
|
459 | expect ->
|
460 | parse.parseOptions(definedOptions, options)
|
461 | .to.throw('Option foo is required')
|
462 |
|
463 | it 'should not throw if false', ->
|
464 | definedOptions = []
|
465 | definedOptions.push new Option
|
466 | signature: new Signature('foo')
|
467 | parameter: 'bar'
|
468 | required: false
|
469 |
|
470 | options =
|
471 | hello: 'world'
|
472 |
|
473 | expect ->
|
474 | parse.parseOptions(definedOptions, options)
|
475 | .to.not.throw('Option foo is required')
|
476 |
|
477 | it 'should throw a custom error if required is a string', ->
|
478 | definedOptions = []
|
479 | definedOptions.push new Option
|
480 | signature: new Signature('foo')
|
481 | parameter: 'bar'
|
482 | required: 'Custom error!'
|
483 |
|
484 | options =
|
485 | hello: 'world'
|
486 |
|
487 | expect ->
|
488 | parse.parseOptions(definedOptions, options)
|
489 | .to.throw('Custom error!')
|