Permalink
Browse files

Many more tests, removed -i=foo and added -ifoo

  • Loading branch information...
1 parent 6638e82 commit d6d7a3c716c0d6fd834634ca868d0f5e89b2d9be @frodwith committed Apr 22, 2011
Showing with 279 additions and 51 deletions.
  1. +1 −1 Makefile
  2. +34 −30 getopt.coffee
  3. +244 −0 test/parse.coffee
  4. +0 −20 test/usage.coffee
View
@@ -8,7 +8,7 @@ coffee: $(JSFILES)
all: coffee
test: coffee
- nodeunit test
+ nodeunit test/*.js
clean:
rm $(JSFILES)
View
@@ -1,4 +1,7 @@
exports[t] = t.toUpperCase() for t in ['flag', 'scalar', 'array', 'object']
+exports.list = exports.array
+exports.hash = exports.object
+exports.bool = exports.flag
exports.BaseError = class BaseError extends Error
constructor: (@desc) ->
@@ -28,20 +31,13 @@ exports.UnknownOption = class UnknownOption extends ParseError
exports.NoValue = class NoValue extends ParseError
describe: () -> @desc + " requires a value, but none was given"
-exports.FlagWithValue = class FlagWithArguments extends ParseError
+exports.FlagWithValue = class FlagWithValue extends ParseError
describe: () ->
"#{ @desc.name } was given \"#{ @desc.value }\", but takes no arguments"
exports.ScalarWithValues = class ScalarWithValues extends ParseError
describe: () -> @desc + " was given more than one value"
-exports.ObjectNotPair = class ObjectNotPair extends ParseError
- describe: () ->
- @desc.name + " expects a key=value pair, but got " + @desc.value
-
-exports.ObjectKeyRepeated = class ObjectKeyRepeated extends ParseError
- describe: () -> "#{ @desc.name }.#{ @desc.key } given more than once"
-
exports.RequiredMissing = class RequiredMissing extends ParseError
describe: () -> "Required option #{ @desc } was missing"
@@ -60,10 +56,13 @@ class Result
# Mark a param (scalar, array, or object) by name with value.
param: (name, value) ->
+ throw new NoValue name unless value
+
p = @parser
target = p.index[name]
throw new UnknownOption name unless target
spec = p.spec[target]
+
switch spec.type
when exports.flag
throw new FlagWithValue name: name, value: value
@@ -75,24 +74,20 @@ class Result
o = this[target] or= []
o.push value
when exports.object
- pair = value.split '='
- unless pair.length is 2
- throw new ObjectNotPair name: name, value: value
+ match = /^([^=]*)(?:=(.+))?$/.exec(value)
o = this[target] or= {}
- [k,v] = pair
- if o[k]
- throw new ObjectKeyRepeated name: name, key: k
- o[k] = v
+ o[match[1]] = match[2]
else
throw new UnvalidatedType(name)
# Set defaults, throw errors if required parameters weren't seen.
finalize: () ->
p = @parser
for own target, spec of p.spec
- if (spec.type isnt exports.flag) and this[target]?
+ if (spec.type isnt exports.flag) and not this[target]
this[target] = spec.default
+ value = this[target]
if spec.required and (
(spec.type is exports.scalar and not value) or
(spec.type is exports.array and value.length < 1)
@@ -109,19 +104,19 @@ exports.Parser = class Parser
# upgrade scalars to arrays
longs = subspec.long or [target]
- longs = [longs] unless longs.length?
+ longs = [longs] unless Array.isArray longs
subspec.long = longs
shorts = subspec.short or []
- shorts = [shorts] unless shorts.length?
+ shorts = [shorts] unless Array.isArray shorts
subspec.short = shorts
- alias = longs.slice(0)
+ alias = ('--' + l for l in longs)
for s in shorts
if s.length isnt 1
throw new InvalidShort(s)
- alias.push s
+ alias.push '-' + s
for a in alias
existing = @index[a]
@@ -132,9 +127,9 @@ exports.Parser = class Parser
throw new NoType(target) unless t
if t is exports.array
- spec.default or= []
+ subspec.default or= []
else if t is exports.object
- spec.default or= {}
+ subspec.default or= {}
else if t isnt exports.scalar and t isnt exports.flag
throw new UnknownType name: target, type: t
@@ -159,17 +154,26 @@ exports.Parser = class Parser
result.arg(a) for a in argv.slice(i+1)
break
- # -vax is equivalent to -v -a -x
- else if match = /^-(\w\w+)$/.exec(blob)
- result.flag(f) for f in match[1].split()
+ # -vax is equivalent to -v -a -x if -v is a flag
+ # -Dfoo is equivalent to -D=foo if -D isn't a flag
+ else if match = /^-(\w.+)$/.exec(blob)
+ name = match[1]
+ first = '-' + name.charAt(0)
+
+ if @isFlag first
+ result.flag('-' + f) for f in name.split('')
+ else
+ result.param first, name.slice(1)
- # -a=42 or --answer=42 or even -answer=42 (DWIM a bit)
- else if match = /^--?(\w+)=(.*)$/.exec(blob)
- result.param(match[1], match[2])
+ # --answer=42 but NOT -answer=42. That will be parsed by
+ # the above rule as -a nswer=42. We're also not supporting -a=42,
+ # since that's not standard at all. That will be parsed as -a =42
+ else if match = /^(--\w+)=(.*)$/.exec(blob)
+ result.param match[1], match[2]
# -v or --verbose
- else if match = /^-(\w)$/.exec(blob) or
- /^--(\w+)$/.exec(blob)
+ else if match = /^(-\w)$/.exec(blob) or
+ /^(--\w+)$/.exec(blob)
name = match[1]
if @isFlag name
result.flag name
View
@@ -0,0 +1,244 @@
+getopt = require('getopt')
+
+exports.flag = (t) ->
+ t.expect 6
+
+ p = new getopt.Parser
+ verbose:
+ type: getopt.flag
+ short: 'v'
+
+ o = p.parse ['-v']
+ t.ok o.verbose
+ o = p.parse []
+ t.ok not o.verbose
+ o = p.parse ['--verbose']
+ t.ok o.verbose
+ o = p.parse ['--verbose', '-v', '--verbose', '-v']
+ t.ok o.verbose
+ t.throws (() -> p.parse ['-v', '--frobnicate']), getopt.UnknownOption
+ t.throws (() -> p.parse ['--v']), getopt.UnknownOption
+ t.done()
+
+exports.vax = (t) ->
+ t.expect 5
+ p = new getopt.Parser
+ verbose:
+ type: getopt.flag
+ long: []
+ short: ['v']
+ arcane:
+ type: getopt.flag
+ long: []
+ short: ['a']
+ xenophobic:
+ type: getopt.flag
+ long: []
+ short: ['x']
+ notset:
+ type: getopt.flag
+ long: ['en']
+
+ o = p.parse ['-vax']
+ t.ok o.verbose
+ t.ok o.arcane
+ t.ok o.xenophobic
+ t.ok not o.notset
+
+ t.throws (() -> p.parse ['--verbose']), getopt.UnknownOption
+ t.done()
+
+exports.scalar = (t) ->
+ t.expect 8
+ p = new getopt.Parser
+ foo:
+ type: getopt.scalar
+ required: true
+
+ o = p.parse ['--foo=bar']
+ t.equal o.foo, 'bar'
+
+ o = p.parse ['--foo', 'bar']
+ t.equal o.foo, 'bar'
+ t.deepEqual o.argv, []
+
+ # this would parse as -f -o -o bar
+ t.throws (() -> p.parse ['-foo', 'bar']), getopt.UnknownOption
+
+ # this would parse as -f oo=bar
+ t.throws (() -> p.parse ['-foo=bar']), getopt.UnknownOption
+
+ t.throws (() -> p.parse ['--foo=bar', '--foo=baz']),
+ getopt.ScalarWithValues
+
+ t.throws (() -> p.parse []), getopt.RequiredMissing
+
+ t.throws (() -> p.parse ['--foo']), getopt.NoValue
+
+ t.done()
+
+exports.array = (t) ->
+ t.expect 3
+ p = new getopt.Parser
+ thing:
+ short: ['t']
+ type: getopt.array
+
+ o = p.parse []
+ t.deepEqual o.thing, []
+
+ o = p.parse ['--thing=one']
+ t.deepEqual o.thing, ['one']
+
+ o = p.parse ['--thing', 'one', '--thing=two', '-t', 'three', '-tfour']
+ t.deepEqual o.thing, ['one', 'two', 'three', 'four']
+
+ t.done()
+
+exports.object = (t) ->
+ t.expect 5
+
+ p = new getopt.Parser
+ define:
+ short: ['D']
+ type: getopt.object
+
+ o = p.parse []
+ t.deepEqual o.define, {}
+
+ o = p.parse ['-D', 'foo=bar']
+ t.deepEqual o.define, { foo: 'bar' }
+
+ o = p.parse ['--define', 'foo=bar', '-D', 'bar=baz', '--define=baz=qux']
+ t.deepEqual o.define, { foo: 'bar', bar: 'baz', baz: 'qux' }
+
+ o = p.parse ['--define=foo']
+ t.ok 'foo' of o.define
+ t.equal o.define.foo, undefined
+
+ t.done()
+
+exports.required = (t) ->
+ t.expect 1
+ p = new getopt.Parser
+ define:
+ type: getopt.scalar
+ required: true
+
+ t.throws (() -> p.parse []), getopt.RequiredMissing
+ t.done()
+
+exports.short = (t) ->
+ t.expect 3
+
+ p = new getopt.Parser
+ input:
+ type: getopt.scalar
+ long: []
+ short: 'i'
+
+ o = p.parse ['-i', 'foo.txt']
+ t.equal o.input, 'foo.txt'
+
+ o = p.parse ['-ifoo.txt']
+ t.equal o.input, 'foo.txt'
+
+ o = p.parse ['-i=foo.txt']
+ t.equal o.input, '=foo.txt'
+
+ t.done()
+
+exports.long = (t) ->
+ t.expect 6
+ p = new getopt.Parser
+ foo:
+ type: getopt.bool
+ long: ['not', 'even', 'close']
+ o = p.parse ['--not']
+ t.ok o.foo
+
+ o = p.parse ['--even']
+ t.ok o.foo
+
+ o = p.parse ['--close']
+ t.ok o.foo
+
+ t.throws (() -> p.parse ['--foo']), getopt.UnknownOption
+
+ p = new getopt.Parser
+ foo:
+ type: getopt.bool
+ long: 'bar'
+
+ o = p.parse ['--bar']
+ t.ok o.foo
+
+ t.throws (() -> p.parse ['--foo']), getopt.UnknownOption
+ t.done()
+
+
+exports['aliased types'] = (t) ->
+ t.expect 3
+ t.equal(getopt.list, getopt.array)
+ t.equal(getopt.hash, getopt.object)
+ t.equal(getopt.bool, getopt.flag)
+ t.done()
+
+exports.complicated = (t) ->
+ t.expect 7
+
+ p = new getopt.Parser
+ verbose:
+ type: getopt.bool
+ short: 'v'
+ arcane:
+ type: getopt.flag
+ long: []
+ short: 'a'
+ xenophobic:
+ type: getopt.flag
+ short: 'x'
+ name:
+ type: getopt.list
+ long: ['call', 'sign']
+ define:
+ type: getopt.hash
+ short: ['D', 'd']
+ proxy:
+ type: getopt.scalar
+ default: 'http://www.proxy.com'
+ input:
+ type: getopt.scalar
+ short: 'i'
+ required: true
+
+ o = p.parse [
+ 'split'
+ '-vax'
+ '--input'
+ '-'
+ '--call=bar'
+ '--sign=baz'
+ '-d', 'foo=bar'
+ '-D', 'bar=baz'
+ '-DHAVE_CRYPT'
+ '--define=baz=qux'
+ '-'
+ '--'
+ '--foo'
+ '--bar'
+ '--baz'
+ ]
+ t.deepEqual o.argv, ['split', '-', '--foo', '--bar', '--baz']
+ t.ok o.verbose
+ t.ok o.arcane
+ t.ok o.xenophobic
+ t.equal o.input, '-'
+ t.deepEqual o.name, ['bar', 'baz']
+ t.deepEqual o.define,
+ foo: 'bar'
+ bar: 'baz'
+ baz: 'qux'
+ 'HAVE_CRYPT': undefined
+
+ t.done()
Oops, something went wrong.

0 comments on commit d6d7a3c

Please sign in to comment.