capitano
Version:
Powerful, non opitionated command line parser for serious applications
509 lines (413 loc) • 13.9 kB
text/coffeescript
_ = require('lodash')
chai = require('chai')
expect = chai.expect
parse = require('../lib/parse')
state = require('../lib/state')
Option = require('../lib/option')
Signature = require('../lib/signature')
settings = require('../lib/settings')
describe 'Parse:', ->
describe '#normalizeInput()', ->
it 'should handle strings', ->
result = parse.normalizeInput('-x 3 -y 4')
expect(result).to.deep.equal([ '-x', '3', '-y', '4' ])
it 'should handle arrays', ->
result = parse.normalizeInput([ '-x', '3', '-y', '4' ])
expect(result).to.deep.equal([ '-x', '3', '-y', '4' ])
it 'should discard first arguments if process.argv', ->
result = parse.normalizeInput(process.argv)
expect(result).to.deep.equal(process.argv.slice(2))
it 'should throw an error if invalid input', ->
expect ->
parse.normalizeInput({ hello: 'world' })
.to.throw(Error)
describe '#parse()', ->
describe 'options', ->
beforeEach ->
state.globalOptions = []
it 'should be able to parse options', ->
argv = parse.split('-x 3 -y 4')
result = parse.parse(argv)
expect(result).to.deep.equal
global: {}
options:
x: 3
y: 4
it 'should be able to parse boolean options', ->
argv = parse.split('-x --foo')
result = parse.parse(argv)
expect(result).to.deep.equal
global: {}
options:
x: true
foo: true
it 'should be able to compile global options', ->
state.globalOptions.push new Option
signature: new Signature('quiet')
boolean: true
argv = parse.split('foo --quiet')
result = parse.parse(argv)
expect(result).to.deep.equal
global:
quiet: true
options:
quiet: true
command: 'foo'
it 'should be able to compile global options with aliases', ->
state.globalOptions.push new Option
signature: new Signature('quiet')
boolean: true
alias: 'q'
argv = parse.split('foo -q')
result = parse.parse(argv)
expect(result).to.deep.equal
global:
quiet: true
options:
q: true
command: 'foo'
describe 'commands', ->
it 'should be able to parse commands', ->
argv = parse.split('auth login')
result = parse.parse(argv)
expect(result).to.deep.equal
command: 'auth login'
options: {}
global: {}
it 'should be able to parse commands with suffix options', ->
argv = parse.split('auth login -f -n 10')
result = parse.parse(argv)
expect(result).to.deep.equal
command: 'auth login'
global: {}
options:
f: true
n: 10
it 'should be able to parse commands with prefix options', ->
argv = parse.split('-f -n 10 auth login')
result = parse.parse(argv)
expect(result).to.deep.equal
command: 'auth login'
global: {}
options:
f: true
n: 10
it 'should be able to parse commands with infix options', ->
argv = parse.split('auth -f -n 10 login')
result = parse.parse(argv)
expect(result).to.deep.equal
command: 'auth login'
global: {}
options:
f: true
n: 10
it 'should be able to parse commands with arguments', ->
argv = parse.split('auth login <credentials>')
result = parse.parse(argv)
expect(result).to.deep.equal
command: 'auth login <credentials>'
global: {}
options: {}
it 'should be able to parse commands with multiple arguments', ->
argv = parse.split('auth login <credentials> <foo>')
result = parse.parse(argv)
expect(result).to.deep.equal
command: 'auth login <credentials> <foo>'
global: {}
options: {}
it 'should be able to parse commands with optional arguments', ->
argv = parse.split('auth login [foo]')
result = parse.parse(argv)
expect(result).to.deep.equal
command: 'auth login [foo]'
global: {}
options: {}
it 'should be able to parse commands with multiple arguments', ->
argv = parse.split('transfer <from name> <to name>')
result = parse.parse(argv)
expect(result).to.deep.equal
command: 'transfer <from name> <to name>'
global: {}
options: {}
it 'should be able to parse commands with optional arguments', ->
argv = parse.split('greet [their name]')
result = parse.parse(argv)
expect(result).to.deep.equal
command: 'greet [their name]'
global: {}
options: {}
it 'should not discard single quotes when parsing the command', ->
argv = parse.split('hello \'John Doe\'')
result = parse.parse(argv)
expect(result).to.deep.equal
command: 'hello "John Doe"'
global: {}
options: {}
it 'should not discard double quotes when parsing the command', ->
argv = parse.split('hello "John Doe"')
result = parse.parse(argv)
expect(result).to.deep.equal
command: 'hello "John Doe"'
global: {}
options: {}
it 'should not parse numbers in scientific notation automatically', ->
argv = parse.split('hello -x 43e8273')
result = parse.parse(argv)
expect(result).to.deep.equal
command: 'hello'
global: {}
options:
x: '43e8273'
it 'should parse float numbers automatically', ->
argv = parse.split('hello -x 1.5 -y .9')
result = parse.parse(argv)
expect(result).to.deep.equal
command: 'hello'
global: {}
options:
x: 1.5
y: 0.9
it 'should not parse \'.\' as float number', ->
argv = parse.split('hello -x .')
result = parse.parse(argv)
expect(result).to.deep.equal
command: 'hello'
global: {}
options:
x: '.'
describe '#split()', ->
it 'should return an empty array if no signature', ->
signature = undefined
result = []
expect(parse.split(signature)).to.deep.equal(result)
it 'should split a wildcard signature correctly', ->
signature = settings.signatures.wildcard
result = [ settings.signatures.wildcard ]
expect(parse.split(signature)).to.deep.equal(result)
it 'should split signatures correctly', ->
signature = 'foo <bar>'
result = [ 'foo', '<bar>' ]
expect(parse.split(signature)).to.deep.equal(result)
it 'should split multi word required parameters', ->
signature = '<hello world> <foo bar baz>'
result = [ '<hello world>', '<foo bar baz>' ]
expect(parse.split(signature)).to.deep.equal(result)
it 'should split multi word optional parameters', ->
signature = '[hello world] [foo bar baz]'
result = [ '[hello world]', '[foo bar baz]' ]
expect(parse.split(signature)).to.deep.equal(result)
it 'should split multi word variadic required parameters', ->
signature = '<hello world...> <foo bar baz...>'
result = [ '<hello world...>', '<foo bar baz...>' ]
expect(parse.split(signature)).to.deep.equal(result)
it 'should split multi word optional parameters', ->
signature = '[hello world...] [foo bar baz...]'
result = [ '[hello world...]', '[foo bar baz...]' ]
expect(parse.split(signature)).to.deep.equal(result)
it 'should split absolute paths parameters correctly', ->
signature = 'foo /Users/me/foo/bar'
result = [ 'foo', '/Users/me/foo/bar' ]
expect(parse.split(signature)).to.deep.equal(result)
it 'should split absolute paths (win32) parameters correctly', ->
signature = 'foo C:\\Users\\me\\foo\\bar'
result = [ 'foo', 'C:\\Users\\me\\foo\\bar' ]
expect(parse.split(signature)).to.deep.equal(result)
it 'should split relative paths parameters correctly', ->
signature = 'foo ../hello/world'
result = [ 'foo', '../hello/world' ]
expect(parse.split(signature)).to.deep.equal(result)
it 'should split home relative paths parameters correctly', ->
signature = 'foo ~/.ssh/id_rsa.pub'
result = [ 'foo', '~/.ssh/id_rsa.pub' ]
expect(parse.split(signature)).to.deep.equal(result)
it 'should split words surrounded by quotes correctly', ->
signature = 'foo \'hello world\''
result = [ 'foo', 'hello world' ]
expect(parse.split(signature)).to.deep.equal(result)
it 'should split words surrounded by double quotes correctly', ->
signature = 'foo "hello world"'
result = [ 'foo', 'hello world' ]
expect(parse.split(signature)).to.deep.equal(result)
it 'should split words with escaped double quotes and surrounded by double quotes correctly', ->
signature = 'foo "hello \\\"world\\\""'
result = [ 'foo', 'hello \\"world\\"' ]
expect(parse.split(signature)).to.deep.equal(result)
it 'should split words with escaped single quotes and surrounded by single quotes correctly', ->
signature = 'foo \'hello \\\'world\\\'\''
result = [ 'foo', 'hello \\\'world\\\'' ]
expect(parse.split(signature)).to.deep.equal(result)
describe '#parseOptions()', ->
it 'should not throw if options is undefined', ->
definedOptions = []
definedOptions.push new Option
signature: new Signature('foo')
boolean: true
expect ->
parse.parseOptions(definedOptions, undefined)
.to.not.throw(Error)
it 'should return an empty object if defined options is empty', ->
options =
hello: 'world'
quiet: true
expect(parse.parseOptions([], options)).to.deep.equal({})
it 'should return an empty object if options is empty', ->
definedOptions = []
definedOptions.push new Option
signature: new Signature('foo')
parameter: 'bar'
expect(parse.parseOptions(definedOptions, [])).to.deep.equal({})
it 'should parse simple options (without aliases)', ->
definedOptions = []
definedOptions.push new Option
signature: new Signature('foo')
parameter: 'bar'
definedOptions.push new Option
signature: new Signature('quiet')
boolean: true
options =
foo: 'baz'
quiet: true
result = parse.parseOptions(definedOptions, options)
expect(result).to.deep.equal
foo: 'baz'
quiet: true
it 'should parse options starting with a number correctly', ->
definedOptions = []
definedOptions.push new Option
signature: new Signature('foo')
parameter: 'bar'
options =
foo: '10foobar'
result = parse.parseOptions(definedOptions, options)
expect(result).to.deep.equal
foo: '10foobar'
it 'should parse options containing integers as numbers', ->
definedOptions = []
definedOptions.push new Option
signature: new Signature('foo')
parameter: 'bar'
options =
foo: '10'
result = parse.parseOptions(definedOptions, options)
expect(result).to.deep.equal
foo: 10
it 'should discard non matched options', ->
definedOptions = []
definedOptions.push new Option
signature: new Signature('foo')
parameter: 'bar'
definedOptions.push new Option
signature: new Signature('hello')
parameter: 'world'
definedOptions.push new Option
signature: new Signature('quiet')
boolean: true
options =
foo: 'baz'
quiet: 'hello'
hello: true
result = parse.parseOptions(definedOptions, options)
expect(result).to.deep.equal
foo: 'baz'
it 'shoud omit extra defined options', ->
definedOptions = []
definedOptions.push new Option
signature: new Signature('foo')
parameter: 'bar'
definedOptions.push new Option
signature: new Signature('quiet')
boolean: true
options =
foo: 'baz'
result = parse.parseOptions(definedOptions, options)
expect(result).to.deep.equal
foo: 'baz'
it 'should handle string aliases', ->
definedOptions = []
definedOptions.push new Option
signature: new Signature('foo')
parameter: 'bar'
alias: 'f'
options =
f: 'baz'
result = parse.parseOptions(definedOptions, options)
expect(result).to.deep.equal
foo: 'baz'
it 'should handle array aliases', ->
definedOptions = []
definedOptions.push new Option
signature: new Signature('foo')
parameter: 'bar'
alias: [ 'a', 'b', 'c' ]
options =
b: 'baz'
result = parse.parseOptions(definedOptions, options)
expect(result).to.deep.equal
foo: 'baz'
it 'should handle multiletter aliases', ->
definedOptions = []
definedOptions.push new Option
signature: new Signature('foo')
parameter: 'bar'
alias: 'hello'
options =
hello: 'world'
result = parse.parseOptions(definedOptions, options)
expect(result).to.deep.equal
foo: 'world'
it 'should give precedence to long names', ->
definedOptions = []
definedOptions.push new Option
signature: new Signature('foo')
parameter: 'bar'
alias: [ 'a', 'b', 'c' ]
options =
foo: 'bar'
b: 'baz'
result = parse.parseOptions(definedOptions, options)
expect(result).to.deep.equal
foo: 'bar'
it 'should parse numbers automatically', ->
definedOptions = []
definedOptions.push new Option
signature: new Signature('foo')
parameter: 'bar'
options =
foo: '25'
result = parse.parseOptions(definedOptions, options)
expect(result).to.deep.equal
foo: 25
describe 'given an option required key', ->
it 'should throw a generic error if true', ->
definedOptions = []
definedOptions.push new Option
signature: new Signature('foo')
parameter: 'bar'
required: true
options =
hello: 'world'
expect ->
parse.parseOptions(definedOptions, options)
.to.throw('Option foo is required')
it 'should not throw if false', ->
definedOptions = []
definedOptions.push new Option
signature: new Signature('foo')
parameter: 'bar'
required: false
options =
hello: 'world'
expect ->
parse.parseOptions(definedOptions, options)
.to.not.throw('Option foo is required')
it 'should throw a custom error if required is a string', ->
definedOptions = []
definedOptions.push new Option
signature: new Signature('foo')
parameter: 'bar'
required: 'Custom error!'
options =
hello: 'world'
expect ->
parse.parseOptions(definedOptions, options)
.to.throw('Custom error!')