Comparing version 0.0.13 to 0.0.15
{ | ||
"name": "sift", | ||
"description": "mongodb query style array filtering", | ||
"version": "0.0.13", | ||
"version": "0.0.15", | ||
"repository": {}, | ||
@@ -6,0 +6,0 @@ "engines": {}, |
## MongoDB inspired array filtering [![Build Status](https://secure.travis-ci.org/crcn/sift.js.png)](https://secure.travis-ci.org/crcn/sift.js) | ||
For extended documentation, checkout http://docs.mongodb.org/manual/reference/operator/query/ | ||
## Features: | ||
- Supported operators: $in, $nin, $exists, $gte, $gt, $lte, $lt, $eq, $neq, $mod, $all, $and, $or, $nor, $not, $size, $type | ||
- Supported operators: [$in](#in), [$nin](#nin), [$exists](#exists), [$gte](#gte), [$gt](#gt), [$lte](#lte), [$lt](#lt), [$eq](#eq), [$neq](#neq), [$mod](#mod), [$all](#all), [$and](#and), [$or](#or), [$nor](#nor), [$not](#not), [$size](#size), [$type](#type), [$regex](#regex) | ||
- Regexp searches | ||
@@ -15,2 +17,3 @@ - Function filtering | ||
## Node.js Examples | ||
@@ -301,2 +304,10 @@ | ||
### $regex | ||
Matches values based on the given regular expression | ||
```javascript | ||
sift({ $regex: /^f/i, $nin: ["frank"] }, ["frank", "fred", "sam", "frost"]); // ["fred", "frost"] | ||
``` | ||
### $not | ||
@@ -303,0 +314,0 @@ |
780
sift.js
@@ -14,556 +14,556 @@ /* | ||
/** | ||
*/ | ||
/** | ||
*/ | ||
var _convertDotToSubObject = function(keyParts, value) { | ||
var _convertDotToSubObject = function(keyParts, value) { | ||
var subObject = {}, | ||
currentValue = subObject; | ||
var subObject = {}, | ||
currentValue = subObject; | ||
for(var i = 0, n = keyParts.length - 1; i < n; i++) { | ||
currentValue = currentValue[keyParts[i]] = {}; | ||
} | ||
for(var i = 0, n = keyParts.length - 1; i < n; i++) { | ||
currentValue = currentValue[keyParts[i]] = {}; | ||
} | ||
currentValue[keyParts[i]] = value; | ||
return subObject; | ||
} | ||
currentValue[keyParts[i]] = value; | ||
return subObject; | ||
} | ||
/** | ||
*/ | ||
/** | ||
*/ | ||
var _queryParser = new (function() { | ||
var _queryParser = new (function() { | ||
/** | ||
* tests against data | ||
*/ | ||
/** | ||
* tests against data | ||
*/ | ||
var priority = this.priority = function(statement, data) { | ||
var priority = this.priority = function(statement, data) { | ||
var exprs = statement.exprs, | ||
priority = 0; | ||
var exprs = statement.exprs, | ||
priority = 0; | ||
//generally, expressions are ordered from least efficient, to most efficient. | ||
for(var i = 0, n = exprs.length; i < n; i++) { | ||
//generally, expressions are ordered from least efficient, to most efficient. | ||
for(var i = 0, n = exprs.length; i < n; i++) { | ||
var expr = exprs[i], | ||
p; | ||
var expr = exprs[i], | ||
p; | ||
if(!~(p = expr.e(expr.v, _comparable(data), data))) return -1; | ||
if(!~(p = expr.e(expr.v, _comparable(data), data))) return -1; | ||
priority += p; | ||
priority += p; | ||
} | ||
} | ||
return priority; | ||
} | ||
return priority; | ||
} | ||
/** | ||
* parses a statement into something evaluable | ||
*/ | ||
/** | ||
* parses a statement into something evaluable | ||
*/ | ||
var parse = this.parse = function(statement, key) { | ||
var parse = this.parse = function(statement, key) { | ||
//fixes sift(null, []) issue | ||
if(!statement) statement = { $eq: statement }; | ||
//fixes sift(null, []) issue | ||
if(!statement) statement = { $eq: statement }; | ||
var testers = []; | ||
//if the statement is an object, then we're looking at something like: { key: match } | ||
if(statement.constructor == Object) { | ||
var testers = []; | ||
//if the statement is an object, then we're looking at something like: { key: match } | ||
if(Object.prototype.toString.call(statement) === "[object Object]") { | ||
for(var k in statement) { | ||
for(var k in statement) { | ||
//find the apropriate operator. If one doesn't exist, then it's a property, which means | ||
//we create a new statement (traversing) | ||
var operator = !!_testers[k] ? k : '$trav', | ||
//find the apropriate operator. If one doesn't exist, then it's a property, which means | ||
//we create a new statement (traversing) | ||
var operator = !!_testers[k] ? k : '$trav', | ||
//value of given statement (the match) | ||
value = statement[k], | ||
//value of given statement (the match) | ||
value = statement[k], | ||
//default = match | ||
exprValue = value; | ||
//default = match | ||
exprValue = value; | ||
//if we're working with a traversable operator, then set the expr value | ||
if(TRAV_OP[operator]) { | ||
//if we're working with a traversable operator, then set the expr value | ||
if(TRAV_OP[operator]) { | ||
//using dot notation? convert into a sub-object | ||
if(~k.indexOf(".")) { | ||
var keyParts = k.split("."); | ||
k = keyParts.shift(); //we're using the first key, so remove it | ||
//using dot notation? convert into a sub-object | ||
if(~k.indexOf(".")) { | ||
var keyParts = k.split("."); | ||
k = keyParts.shift(); //we're using the first key, so remove it | ||
exprValue = value = _convertDotToSubObject(keyParts, value); | ||
} | ||
//*if* the value is an array, then we're dealing with something like: $or, $and | ||
if(value instanceof Array) { | ||
exprValue = []; | ||
exprValue = value = _convertDotToSubObject(keyParts, value); | ||
} | ||
//*if* the value is an array, then we're dealing with something like: $or, $and | ||
if(value instanceof Array) { | ||
exprValue = []; | ||
for(var i = value.length; i--;) { | ||
exprValue.push(parse(value[i])); | ||
} | ||
for(var i = value.length; i--;) { | ||
exprValue.push(parse(value[i])); | ||
} | ||
//otherwise we're dealing with $trav | ||
} else { | ||
exprValue = parse(value, k); | ||
} | ||
} | ||
//otherwise we're dealing with $trav | ||
} else { | ||
exprValue = parse(value, k); | ||
} | ||
} | ||
testers.push(_getExpr(operator, k, exprValue)); | ||
testers.push(_getExpr(operator, k, exprValue)); | ||
} | ||
} | ||
//otherwise we're comparing a particular value, so set to eq | ||
} else { | ||
testers.push(_getExpr('$eq', k, statement)); | ||
} | ||
//otherwise we're comparing a particular value, so set to eq | ||
} else { | ||
testers.push(_getExpr('$eq', k, statement)); | ||
} | ||
var stmt = { | ||
exprs: testers, | ||
k: key, | ||
test: function(value) { | ||
return !!~stmt.priority(value); | ||
}, | ||
priority: function(value) { | ||
return priority(stmt, value); | ||
} | ||
}; | ||
return stmt; | ||
} | ||
var stmt = { | ||
exprs: testers, | ||
k: key, | ||
test: function(value) { | ||
return !!~stmt.priority(value); | ||
}, | ||
priority: function(value) { | ||
return priority(stmt, value); | ||
} | ||
}; | ||
return stmt; | ||
} | ||
//traversable statements | ||
var TRAV_OP = this.traversable = { | ||
$and: true, | ||
$or: true, | ||
$nor: true, | ||
$trav: true, | ||
$not: true | ||
}; | ||
//traversable statements | ||
var TRAV_OP = this.traversable = { | ||
$and: true, | ||
$or: true, | ||
$nor: true, | ||
$trav: true, | ||
$not: true | ||
}; | ||
function _comparable(value) { | ||
if(value instanceof Date) { | ||
return value.getTime(); | ||
} else { | ||
return value; | ||
} | ||
} | ||
function _comparable(value) { | ||
if(value instanceof Date) { | ||
return value.getTime(); | ||
} else { | ||
return value; | ||
} | ||
} | ||
function btop(value) { | ||
return value ? 0 : -1; | ||
} | ||
function btop(value) { | ||
return value ? 0 : -1; | ||
} | ||
var _testers = this.testers = { | ||
var _testers = this.testers = { | ||
/** | ||
*/ | ||
/** | ||
*/ | ||
$eq: function(a, b) { | ||
return btop(a.test(b)); | ||
}, | ||
$eq: function(a, b) { | ||
return btop(a.test(b)); | ||
}, | ||
/** | ||
*/ | ||
/** | ||
*/ | ||
$ne: function(a, b) { | ||
return btop(!a.test(b)); | ||
}, | ||
$ne: function(a, b) { | ||
return btop(!a.test(b)); | ||
}, | ||
/** | ||
*/ | ||
/** | ||
*/ | ||
$lt: function(a, b) { | ||
return btop(a > b); | ||
}, | ||
$lt: function(a, b) { | ||
return btop(a > b); | ||
}, | ||
/** | ||
*/ | ||
/** | ||
*/ | ||
$gt: function(a, b) { | ||
return btop(a < b); | ||
}, | ||
$gt: function(a, b) { | ||
return btop(a < b); | ||
}, | ||
/** | ||
*/ | ||
/** | ||
*/ | ||
$lte: function(a, b) { | ||
return btop(a >= b); | ||
}, | ||
$lte: function(a, b) { | ||
return btop(a >= b); | ||
}, | ||
/** | ||
*/ | ||
/** | ||
*/ | ||
$gte: function(a, b) { | ||
return btop(a <= b); | ||
}, | ||
$gte: function(a, b) { | ||
return btop(a <= b); | ||
}, | ||
/** | ||
*/ | ||
/** | ||
*/ | ||
$exists: function(a, b) { | ||
return btop(a === (b != null)) | ||
}, | ||
$exists: function(a, b) { | ||
return btop(a === (b != null)) | ||
}, | ||
/** | ||
*/ | ||
/** | ||
*/ | ||
$in: function(a, b) { | ||
$in: function(a, b) { | ||
//intersecting an array | ||
if(b instanceof Array) { | ||
//intersecting an array | ||
if(b instanceof Array) { | ||
for(var i = b.length; i--;) { | ||
if(~a.indexOf(b[i])) return i; | ||
} | ||
for(var i = b.length; i--;) { | ||
if(~a.indexOf(b[i])) return i; | ||
} | ||
} else { | ||
return btop(~a.indexOf(b)); | ||
} | ||
} else { | ||
return btop(~a.indexOf(b)); | ||
} | ||
return -1; | ||
}, | ||
return -1; | ||
}, | ||
/** | ||
*/ | ||
/** | ||
*/ | ||
$not: function(a, b) { | ||
if(!a.test) throw new Error("$not test should include an expression, not a value. Use $ne instead."); | ||
return btop(!a.test(b)); | ||
}, | ||
$not: function(a, b) { | ||
if(!a.test) throw new Error("$not test should include an expression, not a value. Use $ne instead."); | ||
return btop(!a.test(b)); | ||
}, | ||
/** | ||
*/ | ||
/** | ||
*/ | ||
$type: function(a, b, org) { | ||
$type: function(a, b, org) { | ||
//instanceof doesn't work for strings / boolean. instanceof works with inheritance | ||
return org ? btop(org instanceof a || org.constructor == a) : -1; | ||
}, | ||
//instanceof doesn't work for strings / boolean. instanceof works with inheritance | ||
return org ? btop(org instanceof a || org.constructor == a) : -1; | ||
}, | ||
/** | ||
*/ | ||
/** | ||
*/ | ||
$nin: function(a, b) { | ||
return ~_testers.$in(a, b) ? -1 : 0; | ||
}, | ||
$nin: function(a, b) { | ||
return ~_testers.$in(a, b) ? -1 : 0; | ||
}, | ||
/** | ||
*/ | ||
/** | ||
*/ | ||
$mod: function(a, b) { | ||
return b % a[0] == a[1] ? 0 : -1; | ||
}, | ||
$mod: function(a, b) { | ||
return b % a[0] == a[1] ? 0 : -1; | ||
}, | ||
/** | ||
*/ | ||
/** | ||
*/ | ||
$all: function(a, b) { | ||
$all: function(a, b) { | ||
for(var i = a.length; i--;) { | ||
for(var i = a.length; i--;) { | ||
var a1 = a[i]; | ||
var indexInB = ~b.indexOf(a1); | ||
if(!indexInB) return -1; | ||
} | ||
if(!indexInB) return -1; | ||
} | ||
return 0; | ||
}, | ||
return 0; | ||
}, | ||
/** | ||
*/ | ||
/** | ||
*/ | ||
$size: function(a, b) { | ||
return b ? btop(a == b.length) : -1; | ||
}, | ||
$size: function(a, b) { | ||
return b ? btop(a == b.length) : -1; | ||
}, | ||
/** | ||
*/ | ||
/** | ||
*/ | ||
$or: function(a, b) { | ||
$or: function(a, b) { | ||
var i = a.length, p, n = i; | ||
var i = a.length, p, n = i; | ||
for(; i--;) { | ||
if(~priority(a[i], b)) { | ||
return i; | ||
} | ||
} | ||
for(; i--;) { | ||
if(~priority(a[i], b)) { | ||
return i; | ||
} | ||
} | ||
return btop(n == 0); | ||
}, | ||
return btop(n == 0); | ||
}, | ||
/** | ||
*/ | ||
/** | ||
*/ | ||
$nor: function(a, b) { | ||
$nor: function(a, b) { | ||
var i = a.length, n = i; | ||
var i = a.length, n = i; | ||
for(; i--;) { | ||
if(~priority(a[i], b)) { | ||
return -1; | ||
} | ||
} | ||
for(; i--;) { | ||
if(~priority(a[i], b)) { | ||
return -1; | ||
} | ||
} | ||
return 0; | ||
}, | ||
return 0; | ||
}, | ||
/** | ||
*/ | ||
/** | ||
*/ | ||
$and: function(a, b) { | ||
$and: function(a, b) { | ||
for(var i = a.length; i--;) { | ||
if(!~priority(a[i], b)) { | ||
return -1; | ||
} | ||
} | ||
for(var i = a.length; i--;) { | ||
if(!~priority(a[i], b)) { | ||
return -1; | ||
} | ||
} | ||
return 0; | ||
}, | ||
return 0; | ||
}, | ||
/** | ||
*/ | ||
/** | ||
*/ | ||
$trav: function(a, b) { | ||
$trav: function(a, b) { | ||
if(b instanceof Array) { | ||
for(var i = b.length; i--;) { | ||
var subb = b[i]; | ||
if(subb[a.k] && ~priority(a, subb[a.k])) return i; | ||
} | ||
if(b instanceof Array) { | ||
for(var i = b.length; i--;) { | ||
var subb = b[i]; | ||
if(subb[a.k] && ~priority(a, subb[a.k])) return i; | ||
} | ||
return -1; | ||
} | ||
return -1; | ||
} | ||
//continue to traverse even if there isn't a value - this is needed for | ||
//something like name:{$exists:false} | ||
return priority(a, b ? b[a.k] : undefined); | ||
}, | ||
//continue to traverse even if there isn't a value - this is needed for | ||
//something like name:{$exists:false} | ||
return priority(a, b ? b[a.k] : undefined); | ||
}, | ||
/** | ||
*/ | ||
/** | ||
*/ | ||
$regex: function(a, b) { | ||
var aRE = new RegExp(a); | ||
return aRE.test(b) ? 0 : -1; | ||
} | ||
$regex: function(a, b) { | ||
var aRE = new RegExp(a); | ||
return aRE.test(b) ? 0 : -1; | ||
} | ||
} | ||
} | ||
var _prepare = { | ||
/** | ||
*/ | ||
var _prepare = { | ||
/** | ||
*/ | ||
$eq: function(a) { | ||
var fn; | ||
$eq: function(a) { | ||
var fn; | ||
if(a instanceof RegExp) { | ||
return a; | ||
} else if (a instanceof Function) { | ||
fn = a; | ||
} else { | ||
fn = function(b) { | ||
if(b instanceof Array) { | ||
return ~b.indexOf(a); | ||
} else { | ||
return a == b; | ||
} | ||
} | ||
} | ||
if(a instanceof RegExp) { | ||
return a; | ||
} else if (a instanceof Function) { | ||
fn = a; | ||
} else { | ||
fn = function(b) { | ||
if(b instanceof Array) { | ||
return ~b.indexOf(a); | ||
} else { | ||
return a == b; | ||
} | ||
} | ||
} | ||
return { | ||
test: fn | ||
} | ||
return { | ||
test: fn | ||
} | ||
}, | ||
/** | ||
*/ | ||
$ne: function(a) { | ||
return _prepare.$eq(a); | ||
} | ||
}; | ||
}, | ||
/** | ||
*/ | ||
$ne: function(a) { | ||
return _prepare.$eq(a); | ||
} | ||
}; | ||
var _getExpr = function(type, key, value) { | ||
var _getExpr = function(type, key, value) { | ||
var v = _comparable(value); | ||
var v = _comparable(value); | ||
return { | ||
return { | ||
//k key | ||
k: key, | ||
//k key | ||
k: key, | ||
//v value | ||
v: _prepare[type] ? _prepare[type](v) : v, | ||
//v value | ||
v: _prepare[type] ? _prepare[type](v) : v, | ||
//e eval | ||
e: _testers[type] | ||
}; | ||
//e eval | ||
e: _testers[type] | ||
}; | ||
} | ||
} | ||
})(); | ||
})(); | ||
var getSelector = function(selector) { | ||
var getSelector = function(selector) { | ||
if(!selector) { | ||
if(!selector) { | ||
return function(value) { | ||
return value; | ||
}; | ||
return function(value) { | ||
return value; | ||
}; | ||
} else | ||
if(typeof selector == 'function') { | ||
return selector; | ||
} | ||
} else | ||
if(typeof selector == 'function') { | ||
return selector; | ||
} | ||
throw new Error("Unknown sift selector " + selector); | ||
} | ||
throw new Error("Unknown sift selector " + selector); | ||
} | ||
var sifter = function(query, selector) { | ||
var sifter = function(query, selector) { | ||
//build the filter for the sifter | ||
var filter = _queryParser.parse( query ); | ||
//the function used to sift through the given array | ||
var self = function(target) { | ||
var sifted = [], results = [], testValue, value, priority; | ||
//build the filter for the sifter | ||
var filter = _queryParser.parse( query ); | ||
//the function used to sift through the given array | ||
var self = function(target) { | ||
var sifted = [], results = [], testValue, value, priority; | ||
//I'll typically start from the end, but in this case we need to keep the order | ||
//of the array the same. | ||
for(var i = 0, n = target.length; i < n; i++) { | ||
//I'll typically start from the end, but in this case we need to keep the order | ||
//of the array the same. | ||
for(var i = 0, n = target.length; i < n; i++) { | ||
value = target[i]; | ||
testValue = selector(value); | ||
value = target[i]; | ||
testValue = selector(value); | ||
//priority = -1? it's not something we can use. | ||
if(!~(priority = filter.priority( testValue ))) continue; | ||
//priority = -1? it's not something we can use. | ||
if(!~(priority = filter.priority( testValue ))) continue; | ||
//push all the sifted values to be sorted later. This is important particularly for statements | ||
//such as $or | ||
sifted.push({ | ||
value: value, | ||
priority: priority | ||
}); | ||
} | ||
//push all the sifted values to be sorted later. This is important particularly for statements | ||
//such as $or | ||
sifted.push({ | ||
value: value, | ||
priority: priority | ||
}); | ||
} | ||
//sort the values | ||
sifted.sort(function(a, b) { | ||
return a.priority > b.priority ? -1 : 1; | ||
}); | ||
//sort the values | ||
sifted.sort(function(a, b) { | ||
return a.priority > b.priority ? -1 : 1; | ||
}); | ||
var values = Array(sifted.length); | ||
var values = Array(sifted.length); | ||
//finally, fetch the values & return them. | ||
for(var i = sifted.length; i--;) { | ||
values[i] = sifted[i].value; | ||
} | ||
//finally, fetch the values & return them. | ||
for(var i = sifted.length; i--;) { | ||
values[i] = sifted[i].value; | ||
} | ||
return values; | ||
} | ||
return values; | ||
} | ||
//set the test function incase the sifter isn't needed | ||
self.test = filter.test; | ||
self.score = filter.priority; | ||
self.query = query; | ||
//set the test function incase the sifter isn't needed | ||
self.test = filter.test; | ||
self.score = filter.priority; | ||
self.query = query; | ||
return self; | ||
} | ||
return self; | ||
} | ||
/** | ||
* sifts the given function | ||
* @param query the mongodb query | ||
* @param target the target array | ||
* @param rawSelector the selector for plucking data from the given target | ||
*/ | ||
/** | ||
* sifts the given function | ||
* @param query the mongodb query | ||
* @param target the target array | ||
* @param rawSelector the selector for plucking data from the given target | ||
*/ | ||
var sift = function(query, target, rawSelector) { | ||
var sift = function(query, target, rawSelector) { | ||
//must be an array | ||
if(typeof target != "object") { | ||
rawSelector = target; | ||
target = undefined; | ||
} | ||
//must be an array | ||
if(typeof target != "object") { | ||
rawSelector = target; | ||
target = undefined; | ||
} | ||
var sft = sifter(query, getSelector(rawSelector)); | ||
var sft = sifter(query, getSelector(rawSelector)); | ||
//target given? sift through it and return the filtered result | ||
if(target) return sft(target); | ||
//target given? sift through it and return the filtered result | ||
if(target) return sft(target); | ||
//otherwise return the sifter func | ||
return sft; | ||
//otherwise return the sifter func | ||
return sft; | ||
} | ||
} | ||
sift.use = function(options) { | ||
if(options.operators) sift.useOperators(options.operators); | ||
} | ||
sift.use = function(options) { | ||
if(options.operators) sift.useOperators(options.operators); | ||
} | ||
sift.useOperators = function(operators) { | ||
for(var key in operators) { | ||
sift.useOperator(key, operators[key]); | ||
} | ||
} | ||
sift.useOperators = function(operators) { | ||
for(var key in operators) { | ||
sift.useOperator(key, operators[key]); | ||
} | ||
} | ||
sift.useOperator = function(operator, optionsOrFn) { | ||
sift.useOperator = function(operator, optionsOrFn) { | ||
var options = {}; | ||
var options = {}; | ||
if(typeof optionsOrFn == "object") { | ||
options = optionsOrFn; | ||
} else { | ||
options = { test: optionsOrFn }; | ||
} | ||
if(typeof optionsOrFn == "object") { | ||
options = optionsOrFn; | ||
} else { | ||
options = { test: optionsOrFn }; | ||
} | ||
var key = "$" + operator; | ||
_queryParser.testers[key] = options.test; | ||
var key = "$" + operator; | ||
_queryParser.testers[key] = options.test; | ||
if(options.traversable || options.traverse) { | ||
_queryParser.traversable[key] = true; | ||
} | ||
} | ||
if(options.traversable || options.traverse) { | ||
_queryParser.traversable[key] = true; | ||
} | ||
} | ||
//node.js? | ||
if((typeof module != 'undefined') && (typeof module.exports != 'undefined')) { | ||
module.exports = sift; | ||
//node.js? | ||
if((typeof module != 'undefined') && (typeof module.exports != 'undefined')) { | ||
module.exports = sift; | ||
} else | ||
} else | ||
//browser? | ||
if(typeof window != 'undefined') { | ||
window.sift = sift; | ||
//browser? | ||
if(typeof window != 'undefined') { | ||
window.sift = sift; | ||
} | ||
} | ||
})(); | ||
48297
366