Created
November 5, 2011 01:09
-
-
Save bengillies/1340921 to your computer and use it in GitHub Desktop.
extract.js - extract objects from an argument list
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
A library to parse arguments from functions based on their type. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* A library to parse arguments from functions based on their type | |
* | |
*/ | |
var extract = (function() { | |
var typeList = []; | |
function NoNameError(obj) { | |
return { | |
name: 'NoNameError', | |
msg: 'Object type has no name attribute', | |
value: obj | |
}; | |
} | |
function TypeNotFoundError(obj) { | |
return { | |
name: 'TypeNotFoundError', | |
msg: 'Type "' + obj + '" not found', | |
value: obj | |
}; | |
} | |
// Default to Array.forEach or make our own | |
var forEach = Array.prototype.forEach || function(callback) { | |
var i, l; | |
for (i = 0, l = this.length; i < l; i++) { | |
callback.call(this, this[i], i); | |
} | |
}; | |
// Extend target with extra properties | |
function _extend(target) { | |
forEach.call(Array.prototype.slice.call(arguments, 1), function(obj) { | |
var prop; | |
for (prop in obj) { | |
if (obj.hasOwnProperty(prop)) { | |
target[prop] = obj[prop]; | |
} | |
} | |
}); | |
return target; | |
} | |
// define a base Type type | |
function Type(name, obj) { | |
var title = (typeof name === 'string') ? name : | |
(name && name.name) ? name.name : null, | |
typeObj = (typeof name !== 'string') ? name : | |
(obj || {}); | |
if (!title) { | |
throw NoNameError(typeObj); | |
} | |
_extend(this, typeObj); | |
this.name = title; | |
} | |
_extend(Type.prototype, { | |
parse: function(obj) { | |
return obj; | |
}, | |
test: function() { | |
return true; | |
}, | |
list: false, | |
defaultValue: null, | |
extend: function(name, obj) { | |
var newType = function() {}; | |
newType.prototype = _extend(new Type('default'), this); | |
var extension = new Type(name, obj); | |
return _extend(new newType(), extension); | |
} | |
}); | |
// define a default type (name is mandatory, so not included here) | |
var defaultType = new Type('default'); | |
// add a new type to the type list | |
function addType() { | |
var args = extract( | |
{ name: 'name', test: function(o) { return typeof o === 'string'; } }, | |
{ name: 'type', parse: function(o) { | |
return (o instanceof Type) ? o : new Type(this.name, o); | |
} } | |
).from(arguments); | |
typeList.push(args.type); | |
} | |
function getType(name) { | |
var result = {}; | |
forEach.call(typeList, function(type) { | |
if (type.name === name) { | |
result = type; | |
} | |
}); | |
return result; | |
} | |
function listTypes() { | |
return _extend([], typeList); | |
} | |
function clearTypes() { | |
typeList = []; | |
} | |
/* | |
* from: turn a list of function arguments into a predictable object | |
*/ | |
function from(args) { | |
var result = {}, | |
types = this.types || [], | |
set = function(type, value) { | |
if (type.list) { | |
result[type.name] = result[type.name] || []; | |
result[type.name].push(value); | |
} else { | |
result[type.name] = result[type.name] || value; | |
} | |
}; | |
args = Array.prototype.slice.call(args); // args is likely arguments so not a proper array | |
forEach.call(types, function(obj, pos) { | |
var type = (typeof obj === 'string') ? getType(obj) : new Type(obj), | |
index = 0, | |
arg; | |
if (type.name) { | |
while (index < args.length) { | |
arg = args[index]; | |
if (type.test.call(result, arg)) { | |
set(type, type.parse.call(result, arg)); | |
args.splice(index, 1); | |
if (!type.list) { | |
break; | |
} | |
} else { | |
index++; | |
} | |
} | |
if (!result[type.name]) { | |
set(type, type.defaultValue); | |
} | |
} else { | |
throw (type.get) ? NoNameError(type) : TypeNotFoundError(obj); | |
} | |
}); | |
return result; | |
} | |
/* | |
* Pass in some types that describe objects you would like returned. | |
* You then get a "from" function that you can pass an array into to extract | |
* those objects from. | |
* | |
* e.g.: | |
* | |
* var args = extract( | |
* { name: 'tiddler', parse: function(t) { return store.get(t); }, default: null }, | |
* { name: 'callback', pos: 0, test: function(f) { return typeof f === 'function'; } }, | |
* { name: 'sub', test: function(s) { return s instanceof Sub; } }, | |
* { name: 'msg', pos: 1, test: function(s) { return typeof s === 'string'; } }, | |
* { name: 'renderFlag', pos: 2, parse: function(r) { return (r) ? true : false; } }, | |
* { name: 'rest', list: true, test: function(a) { return typeof a === 'number'; } } | |
* ).from(arguments); | |
* | |
*/ | |
function extract() { | |
var types = Array.prototype.slice.call(arguments); | |
return { | |
types: types, | |
from: from | |
}; | |
} | |
return _extend(extract, { | |
Type: Type, | |
defaultType: defaultType, | |
addType: addType, | |
getType: getType, | |
listTypes: listTypes, | |
clearTypes: clearTypes | |
}); | |
}()); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE HTML> | |
<html lang="en"> | |
<head> | |
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> | |
<title>Test Suite</title> | |
<link rel="stylesheet" type="text/css" href="lib/qunit.css"> | |
</head> | |
<body> | |
<h1 id="qunit-header">Test Suite</h1> | |
<h2 id="qunit-banner"></h2> | |
<h2 id="qunit-userAgent"></h2> | |
<ol id="qunit-tests"></ol> | |
<!--load QUnit and dependencies--> | |
<script src="lib/qunit.js" type="text/javascript"></script> | |
<script src="lib/jquery.js" type="text/javascript"></script> | |
<!--load your scripts in here--> | |
<script src="file:///Users/bengillies/tmp/typeargs/extract.js" type="text/javascript"></script> | |
<script src="fixtures.js" type="text/javascript"></script> | |
<!--load tests--> | |
<script src="test_main.js" type="text/javascript"></script> | |
<script src="test_types.js" type="text/javascript"></script> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.PHONY: test | |
test: | |
qunit test/index.html |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module('extract/from', {}); | |
test('Default Options return args in order', function() { | |
var args = extract({name: 'foo'}, {name: 'bar'}).from(['arg1', 'arg2']); | |
strictEqual(args.foo, 'arg1', 'arg1 is the first argument, so associates with foo'); | |
strictEqual(args.bar, 'arg2', 'arg2 is the second argument, so associates with bar'); | |
}); | |
test('left over args lost', function() { | |
var args = extract({name: 'foo'}, {name: 'bar'}).from(['arg1', 'arg2', 'arg3']); | |
expect(2); | |
$.each(args, function(i, value) { | |
strictEqual(value !== 'arg3', true, 'the left over argument should be lost'); | |
}); | |
}); | |
test('list holds an array', function() { | |
var args = extract({name: 'foo'}, {name: 'rest', list: true}) | |
.from(['arg1', 'arg2', 'arg3']); | |
strictEqual(args.foo, 'arg1', 'args before the list should still exist'); | |
strictEqual(args.rest.length, 2, 'rest should be a list'); | |
strictEqual(args.rest[0], 'arg2', 'the first item should be arg2'); | |
strictEqual(args.rest[1], 'arg3', 'the second item should be arg3'); | |
}); | |
test('"test" function sorts parameters', function() { | |
var args = extract( | |
{ name: 'foo', test: function(o) { return typeof o === 'string'; } }, | |
{ name: 'bar', test: function(o) { return typeof o === 'function'; } }, | |
{ name: 'baz', test: function(o) { return typeof o === 'number'; }, list: true } | |
).from([10, function() { return 'bar'; }, 20, "foo"]); | |
strictEqual(args.foo, 'foo', 'foo should return the string'); | |
strictEqual(args.bar(), 'bar', 'bar should return the function'); | |
strictEqual(args.baz.length, 2, 'bar should be a list of 2 numbers'); | |
strictEqual(args.baz[0], 10, 'baz[0] should be the first number'); | |
strictEqual(args.baz[1], 20, 'baz[1] should be the second number'); | |
}); | |
test('"test" function returns only the first item when list is not set', function() { | |
var args = extract({ name: 'foo', test: function(o) { return typeof o === 'string'; } }) | |
.from([10, 'arg1', 20, 'arg2']); | |
strictEqual(args.foo, 'arg1', 'foo returns the first string. All others are ignored'); | |
}); | |
test('"parse" function modifies arguments', function() { | |
var args = extract({ name: 'foo', parse: function(o) { return o + 'bar'; } }) | |
.from(['foo']); | |
strictEqual(args.foo, 'foobar', 'the original argument should be modified'); | |
}); | |
test('"parse" function allows object decomposition', function() { | |
var args = extract({ | |
name: 'foo', | |
parse: function(o) { this.bar = o.bar; this.baz = o.baz; return o; } | |
}).from([{ bar: 'bar', baz: 'baz'}]); | |
strictEqual(typeof args.foo === 'object', true, 'foo becomes the whole object'); | |
strictEqual(args.foo.bar, 'bar', 'foo contains bar'); | |
strictEqual(args.foo.baz, 'baz', 'foo contains baz'); | |
strictEqual(args.bar, 'bar', 'bar becomes the "bar" part of foo'); | |
strictEqual(args.baz, 'baz', 'baz becomes the "baz" part of foo'); | |
}); | |
test('strings as types', function() { | |
extract.addType('foo', { test: function(o) { return o === 'foo'; }, | |
parse: function(o) { return o + 'bar'; } }); | |
var args = extract('foo').from([10, 'foo', 20]); | |
strictEqual(args.foo, 'foobar', 'the string "foo" should return the "foo" type'); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module('Type', { | |
teardown: function() { | |
extract.clearTypes(); | |
} | |
}); | |
test('New Type', function() { | |
var t = new extract.Type({ | |
name: 'foo', | |
parse: function(o) { | |
return o + 'bar'; | |
} | |
}); | |
strictEqual(t instanceof extract.Type, true, 't is a Type'); | |
strictEqual(t.parse('foo'), 'foobar', 'it gains a new parse method'); | |
strictEqual(t.name, 'foo', 'it is called foo'); | |
strictEqual(t.test('qwerty'), true, 'it uses the default test function'); | |
}); | |
test('extend Type', function() { | |
var t = new extract.Type({ | |
name: 'foo', | |
parse: function(o) { | |
return o + 'bar'; | |
} | |
}); | |
var newT = t.extend('bar', { test: function(o) { return o === 'bar'; } }); | |
strictEqual(newT instanceof extract.Type, true, 'newT is also a Type'); | |
strictEqual(t.test('qwerty'), true, 't still uses the default test'); | |
strictEqual(newT.test('qwerty'), false, 'newT does not pass the default test'); | |
strictEqual(newT.test('bar'), true, 'newT uses its own test method'); | |
}); | |
test('store types', function() { | |
var t = new extract.Type({ | |
name: 'foo', | |
parse: function(o) { | |
return o + 'bar'; | |
} | |
}); | |
var newT = t.extend('bar', { test: function(o) { return o === 'bar'; } }); | |
extract.addType(t); | |
extract.addType(newT); | |
var types = extract.listTypes(); | |
strictEqual(types.length, 2, 'there are two types'); | |
strictEqual(types[0].name, 'foo', 'the first type is foo'); | |
strictEqual(types[1].name, 'bar', 'the second type is bar'); | |
var getType = extract.getType('foo'); | |
strictEqual(getType.name, 'foo', 'We can retrieve the foo type by name'); | |
strictEqual(getType.parse('foo'), 'foobar', 'it is actually the foo type'); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment