var is = { 'string': function(s){ return typeof s === 'string' }, 'function': function(f){ return typeof f === 'function' }, 'number': function(f){ return typeof f === 'number' }, 'array': Array.isArray || function(a){ return a instanceof Array }, 'object': function(o){ return typeof o === 'object' && o != null }, 'boolean': function(b){ return typeof b === 'boolean' } } function ArgSpec(str){ var ret str = str.trim() var parts = str.split(':') if (parts.length > 1){ ret = { name: parts[0], type: parts[1].split('|') } }else if (parts.length === 1){ ret = { name: str } }else{ throw new Error('Expected arg spec to be format name or name:type but was ' + str) } var m if (m = ret.name.match(/^\[(.+)\]$/)){ ret.name = m[1] ret.optional = true } if (m = ret.name.match(/^\.\.\.(.+)$/)){ ret.name = m[1] ret.spread = true } return ret } function typeMatches(spec, arg) { if (!spec.type) return true var match = false; var type = null; for (var i = 0; i= args.length){ if (argIdx < minExpected){ throw new Error( 'Not enough arguments, expected ' + minExpected + ', got ' + argIdx) } break } if (argIdx >= maxExpected){ throw new Error('Too many arguments, expected ' + maxExpected + ', got ' + (argIdx + 1)) } var arg = args[argIdx] if (typeMatches(sp, arg)){ if (sp.spread){ ret[sp.name] = Array.prototype.slice.call(args, argIdx) break }else{ ret[sp.name] = arg } }else if (sp.optional){ argIdxOffset-- }else{ throw new Error('Expected ' + sp.name + '(pos ' + i + ') to be a ' + sp.type.join(' or ')) } } return ret }