├── .gitignore ├── .jshintrc ├── History.md ├── Makefile ├── Readme.md ├── component.json ├── examples ├── dot.js ├── equality.js ├── expr.js ├── grep.js ├── nesting.js └── object.js ├── index.js ├── package.json └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | components 2 | build 3 | node_modules 4 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "evil": true, 3 | "laxbreak": true 4 | } 5 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 2.0.6 / 2015-01-29 3 | ================== 4 | 5 | * package: fix "browserify" usage 6 | * Remove unused dependency (#16, @Raynos) 7 | 8 | 2.0.5 / 2014-05-27 9 | ================== 10 | 11 | * package: "remove-try-require" should be a dependency 12 | 13 | 2.0.4 / 2014-05-16 14 | ================== 15 | 16 | * index: fix lint 17 | * add .jshintrc file 18 | * test: whitespace 19 | * index: properly handle nested properties (#11) 20 | 21 | 2.0.3 / 2014-04-04 22 | ================== 23 | 24 | * use remove-try-require to correct browserify support. 25 | * add package.json test script. 26 | 27 | 2.0.2 / 2014-04-02 28 | ================== 29 | 30 | * really fix npm/browserify package 31 | 32 | 2.0.1 / 2014-03-26 33 | ================== 34 | 35 | * fix npm package 36 | 37 | 2.0.0 / 2014-02-27 38 | ================== 39 | 40 | * cmp; bump version 2.0.0 41 | * core: remove dual requiring dependency 42 | 43 | 1.2.1 / 2014-02-05 44 | ================== 45 | 46 | * add support for complex expressions and implicit getter-style functions 47 | * better-assert should be a dev-Dependency 48 | 49 | 1.1.1 / 2012-11-20 50 | ================== 51 | 52 | * add immediate support for expressions 53 | 54 | 1.1.0 / 2012-11-20 55 | ================== 56 | 57 | * add js expression support 58 | * add strict equality default 59 | * add regexp support 60 | 61 | 1.0.0 / 2012-11-08 62 | ================== 63 | 64 | * add getter-style function invocation support 65 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | build: components index.js 3 | @component build --dev 4 | 5 | components: 6 | @component install --dev 7 | 8 | clean: 9 | rm -fr build components template.js 10 | 11 | test: 12 | @./node_modules/.bin/mocha \ 13 | --require should \ 14 | --reporter spec 15 | 16 | .PHONY: clean test 17 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # to-function 2 | 3 | Convert property access strings into functions 4 | 5 | ## Installation 6 | 7 | $ component install component/to-function 8 | 9 | ## Examples 10 | 11 | ```js 12 | var toFunction = require('to-function'); 13 | var fn = toFunction('name.first'); 14 | var user = { name: { first: 'Tobi' }}; 15 | fn(user); 16 | // => "Tobi" 17 | ``` 18 | 19 | ### Dot access 20 | 21 | ```js 22 | 23 | var _ = require('..'); 24 | 25 | var users = [ 26 | { name: { first: 'Tobi' }}, 27 | { name: { first: 'Loki' }}, 28 | { name: { first: 'Jane' }}, 29 | { name: { first: 'Manny' }} 30 | ]; 31 | 32 | var short = users.map(_('name.first')); 33 | console.log(short); 34 | // => [ 'Tobi', 'Loki', 'Jane', 'Manny' ] 35 | ``` 36 | 37 | ### Equality 38 | 39 | ```js 40 | var _ = require('..'); 41 | 42 | var tobi = { name: { first: 'Tobi' }, age: 2 }; 43 | var loki = { name: { first: 'Loki' }, age: 2 }; 44 | var jane = { name: { first: 'Jane' }, age: 6 }; 45 | 46 | var users = [tobi, loki, jane]; 47 | 48 | var user = users.filter(_(loki)).pop(); 49 | console.log(user); 50 | // => { name: { first: 'Loki' }, age: 2 } 51 | ``` 52 | 53 | ### Expressions 54 | 55 | ```js 56 | var _ = require('..'); 57 | 58 | var users = [ 59 | { name: { first: 'Tobi' }, age: 2 }, 60 | { name: { first: 'Loki' }, age: 2 }, 61 | { name: { first: 'Jane' }, age: 6 } 62 | ]; 63 | 64 | var oldPets = users.filter(_('age > 2 && age < 10')); 65 | console.log(oldPets); 66 | // => [ { name: { first: 'Jane' }, age: 6 } ] 67 | ``` 68 | 69 | ### Regular expressions 70 | 71 | ```js 72 | var _ = require('..'); 73 | 74 | var users = [ 75 | 'Tobi', 76 | 'Loki', 77 | 'Jane' 78 | ]; 79 | 80 | var t = users.filter(_(/^T/)); 81 | 82 | console.log(t); 83 | // => [ 'Tobi' ] 84 | ``` 85 | 86 | ### Nesting 87 | 88 | ```js 89 | var _ = require('..'); 90 | 91 | var users = [ 92 | { name: { first: 'Tobi', last: 'Ferret' }, age: 2 }, 93 | { name: { first: 'Loki', last: 'Ferret' }, age: 2 }, 94 | { name: { first: 'Luna', last: 'Cat' }, age: 2 }, 95 | { name: { first: 'Manny', last: 'Cat' }, age: 3 } 96 | ]; 97 | 98 | // single-key 99 | 100 | var query = _({ 101 | name: { 102 | last: 'Cat' 103 | } 104 | }); 105 | 106 | console.log(users.filter(query)); 107 | // => [ { name: { first: 'Luna', last: 'Cat' }, age: 2 }, 108 | // { name: { first: 'Manny', last: 'Cat' }, age: 3 } ] 109 | 110 | // multi-key 111 | 112 | var query = _({ 113 | name: { 114 | first: /^L/, 115 | last: 'Cat' 116 | } 117 | }); 118 | 119 | console.log(users.filter(query)); 120 | // => [ { name: { first: 'Luna', last: 'Cat' }, age: 2 } ] 121 | 122 | // multi-level 123 | 124 | var query = _({ 125 | name: { last: 'Cat' }, 126 | age: 3 127 | }); 128 | 129 | console.log(users.filter(query)); 130 | // => [ { name: { first: 'Manny', last: 'Cat' }, age: 3 } ] 131 | ``` 132 | 133 | ## License 134 | 135 | MIT 136 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "to-function", 3 | "repo": "component/to-function", 4 | "description": "Convert property access strings into functions", 5 | "version": "2.0.6", 6 | "keywords": [ 7 | "utility", 8 | "function" 9 | ], 10 | "dependencies": { 11 | "component/props": "*" 12 | }, 13 | "development": {}, 14 | "scripts": [ 15 | "index.js" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /examples/dot.js: -------------------------------------------------------------------------------- 1 | 2 | var _ = require('..'); 3 | 4 | var users = [ 5 | { name: { first: 'Tobi' }}, 6 | { name: { first: 'Loki' }}, 7 | { name: { first: 'Jane' }}, 8 | { name: { first: 'Manny' }} 9 | ]; 10 | 11 | var short = users.map(_('name.first')); 12 | console.log(short); 13 | 14 | -------------------------------------------------------------------------------- /examples/equality.js: -------------------------------------------------------------------------------- 1 | var _ = require('..'); 2 | 3 | var tobi = { name: { first: 'Tobi' }, age: 2 }; 4 | var loki = { name: { first: 'Loki' }, age: 2 }; 5 | var jane = { name: { first: 'Jane' }, age: 6 }; 6 | 7 | var users = [tobi, loki, jane]; 8 | 9 | var user = users.filter(_(loki)).pop(); 10 | console.log(user); 11 | -------------------------------------------------------------------------------- /examples/expr.js: -------------------------------------------------------------------------------- 1 | var _ = require('..'); 2 | 3 | var users = [ 4 | { name: { first: 'Tobi' }, age: 2 }, 5 | { name: { first: 'Loki' }, age: 2 }, 6 | { name: { first: 'Jane' }, age: 6 } 7 | ]; 8 | 9 | var oldPets = users.filter(_('age > 2')); 10 | console.log(oldPets); 11 | -------------------------------------------------------------------------------- /examples/grep.js: -------------------------------------------------------------------------------- 1 | var _ = require('..'); 2 | 3 | var users = [ 4 | 'Tobi', 5 | 'Loki', 6 | 'Jane' 7 | ]; 8 | 9 | var t = users.filter(_(/^T/)); 10 | 11 | console.log(t); 12 | -------------------------------------------------------------------------------- /examples/nesting.js: -------------------------------------------------------------------------------- 1 | var _ = require('..'); 2 | 3 | var users = [ 4 | { name: { first: 'Tobi', last: 'Ferret' }, age: 2 }, 5 | { name: { first: 'Loki', last: 'Ferret' }, age: 2 }, 6 | { name: { first: 'Luna', last: 'Cat' }, age: 2 }, 7 | { name: { first: 'Manny', last: 'Cat' }, age: 3 } 8 | ]; 9 | 10 | // single-key 11 | 12 | var query = _({ 13 | name: { 14 | last: 'Cat' 15 | } 16 | }); 17 | 18 | console.log(users.filter(query)); 19 | 20 | // multi-key 21 | 22 | var query = _({ 23 | name: { 24 | first: /^L/, 25 | last: 'Cat' 26 | } 27 | }); 28 | 29 | console.log(users.filter(query)); 30 | 31 | // multi-level 32 | 33 | var query = _({ 34 | name: { last: 'Cat' }, 35 | age: 3 36 | }); 37 | 38 | console.log(users.filter(query)); 39 | -------------------------------------------------------------------------------- /examples/object.js: -------------------------------------------------------------------------------- 1 | var _ = require('..'); 2 | 3 | var users = [ 4 | { name: 'Tobi' }, 5 | { name: 'Loki' }, 6 | { name: 'Jane' } 7 | ]; 8 | 9 | users = users.filter(_({ name: /^To/ })); 10 | 11 | console.log(users); 12 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module Dependencies 4 | */ 5 | 6 | var expr; 7 | try { 8 | expr = require('props'); 9 | } catch(e) { 10 | expr = require('component-props'); 11 | } 12 | 13 | /** 14 | * Expose `toFunction()`. 15 | */ 16 | 17 | module.exports = toFunction; 18 | 19 | /** 20 | * Convert `obj` to a `Function`. 21 | * 22 | * @param {Mixed} obj 23 | * @return {Function} 24 | * @api private 25 | */ 26 | 27 | function toFunction(obj) { 28 | switch ({}.toString.call(obj)) { 29 | case '[object Object]': 30 | return objectToFunction(obj); 31 | case '[object Function]': 32 | return obj; 33 | case '[object String]': 34 | return stringToFunction(obj); 35 | case '[object RegExp]': 36 | return regexpToFunction(obj); 37 | default: 38 | return defaultToFunction(obj); 39 | } 40 | } 41 | 42 | /** 43 | * Default to strict equality. 44 | * 45 | * @param {Mixed} val 46 | * @return {Function} 47 | * @api private 48 | */ 49 | 50 | function defaultToFunction(val) { 51 | return function(obj){ 52 | return val === obj; 53 | }; 54 | } 55 | 56 | /** 57 | * Convert `re` to a function. 58 | * 59 | * @param {RegExp} re 60 | * @return {Function} 61 | * @api private 62 | */ 63 | 64 | function regexpToFunction(re) { 65 | return function(obj){ 66 | return re.test(obj); 67 | }; 68 | } 69 | 70 | /** 71 | * Convert property `str` to a function. 72 | * 73 | * @param {String} str 74 | * @return {Function} 75 | * @api private 76 | */ 77 | 78 | function stringToFunction(str) { 79 | // immediate such as "> 20" 80 | if (/^ *\W+/.test(str)) return new Function('_', 'return _ ' + str); 81 | 82 | // properties such as "name.first" or "age > 18" or "age > 18 && age < 36" 83 | return new Function('_', 'return ' + get(str)); 84 | } 85 | 86 | /** 87 | * Convert `object` to a function. 88 | * 89 | * @param {Object} object 90 | * @return {Function} 91 | * @api private 92 | */ 93 | 94 | function objectToFunction(obj) { 95 | var match = {}; 96 | for (var key in obj) { 97 | match[key] = typeof obj[key] === 'string' 98 | ? defaultToFunction(obj[key]) 99 | : toFunction(obj[key]); 100 | } 101 | return function(val){ 102 | if (typeof val !== 'object') return false; 103 | for (var key in match) { 104 | if (!(key in val)) return false; 105 | if (!match[key](val[key])) return false; 106 | } 107 | return true; 108 | }; 109 | } 110 | 111 | /** 112 | * Built the getter function. Supports getter style functions 113 | * 114 | * @param {String} str 115 | * @return {String} 116 | * @api private 117 | */ 118 | 119 | function get(str) { 120 | var props = expr(str); 121 | if (!props.length) return '_.' + str; 122 | 123 | var val, i, prop; 124 | for (i = 0; i < props.length; i++) { 125 | prop = props[i]; 126 | val = '_.' + prop; 127 | val = "('function' == typeof " + val + " ? " + val + "() : " + val + ")"; 128 | 129 | // mimic negative lookbehind to avoid problems with nested properties 130 | str = stripNested(prop, str, val); 131 | } 132 | 133 | return str; 134 | } 135 | 136 | /** 137 | * Mimic negative lookbehind to avoid problems with nested properties. 138 | * 139 | * See: http://blog.stevenlevithan.com/archives/mimic-lookbehind-javascript 140 | * 141 | * @param {String} prop 142 | * @param {String} str 143 | * @param {String} val 144 | * @return {String} 145 | * @api private 146 | */ 147 | 148 | function stripNested (prop, str, val) { 149 | return str.replace(new RegExp('(\\.)?' + prop, 'g'), function($0, $1) { 150 | return $1 ? $0 : val; 151 | }); 152 | } 153 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "to-function", 3 | "description": "Convert property access strings into functions", 4 | "version": "2.0.6", 5 | "keywords": [ 6 | "utility" 7 | ], 8 | "dependencies": { 9 | "component-props": "*" 10 | }, 11 | "scripts": { 12 | "test": "make test" 13 | }, 14 | "devDependencies": { 15 | "better-assert": "*", 16 | "mocha": "*", 17 | "should": "*" 18 | }, 19 | "browser": { 20 | "props": "component-props" 21 | }, 22 | "component": { 23 | "scripts": { 24 | "to-function/index.js": "index.js" 25 | } 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/component/to-function.git" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 2 | var toFunction = require('..') 3 | , assert = require('better-assert'); 4 | 5 | describe('toFunction(str)', function(){ 6 | it('should access properties', function(){ 7 | var fn = toFunction('name'); 8 | fn({ name: 'Tobi' }).should.equal('Tobi'); 9 | }) 10 | 11 | it('should access nested properties', function(){ 12 | var fn = toFunction('name.first'); 13 | fn({ name: { first: 'Tobi' } }).should.equal('Tobi'); 14 | }) 15 | 16 | it('should access nested properties with the same name', function(){ 17 | var fn = toFunction('name.name'); 18 | fn({ name: { name: 'Tobi' } }).should.equal('Tobi'); 19 | }) 20 | 21 | it('should access deeply nested properties with the same name', function(){ 22 | var fn = toFunction('name.name.name'); 23 | fn({ name: { name: { name: 'Tobi' } } }).should.equal('Tobi'); 24 | }) 25 | 26 | it('should access nested properties with the same name with levels in between', function(){ 27 | var fn = toFunction('name.foo.name'); 28 | fn({ name: { foo: { name: 'Tobi' } } }).should.equal('Tobi'); 29 | }) 30 | 31 | it('should invoke getter-style functions', function(){ 32 | var user = { attrs: { name: 'tj' }}; 33 | user.name = function(){ return this.attrs.name }; 34 | var fn = toFunction('name()'); 35 | assert('tj' == fn(user)); 36 | }) 37 | 38 | it('should support js expressions', function(){ 39 | var fn = toFunction('age > 18'); 40 | assert(true === fn({ age: 20 })); 41 | assert(false === fn({ age: 18 })); 42 | }) 43 | 44 | it('should support complex js expressions', function(){ 45 | var fn = toFunction('age > 18 && age < 35'); 46 | assert(true === fn({ age: 20 })); 47 | assert(false === fn({ age: 18 })); 48 | }) 49 | 50 | it('should support js with immediate value', function(){ 51 | var fn = toFunction('> 18'); 52 | assert(true === fn(20)); 53 | assert(false === fn(18)); 54 | }) 55 | 56 | it('should support js with getter-style functions', function(){ 57 | var user = { attrs: { age: 24 }}; 58 | user.age = function(){ return this.attrs.age }; 59 | var fn = toFunction('age > 25'); 60 | assert(false == fn(user)); 61 | var fn = toFunction('age > 20'); 62 | assert(true == fn(user)); 63 | }) 64 | 65 | it('should support complex js expressions with getter-style functions', function(){ 66 | var user = { attrs: { age: 24 }}; 67 | user.age = function(){ return this.attrs.age }; 68 | var fn = toFunction('age > 30 || age < 20'); 69 | assert(false == fn(user)); 70 | var fn = toFunction('age > 20 && age < 35'); 71 | assert(true == fn(user)); 72 | }) 73 | }) 74 | 75 | describe('toFunction(fn)', function(){ 76 | it('should return the function', function(){ 77 | var fn = function(){}; 78 | assert(fn == toFunction(fn)); 79 | }) 80 | }) 81 | 82 | describe('toFunction(regexp)', function(){ 83 | it('should .test the value', function(){ 84 | var fn = toFunction(/^tob/); 85 | assert(false === fn({})); 86 | assert(false === fn('luna')); 87 | assert(true === fn('tobi')); 88 | }) 89 | }) 90 | 91 | describe('toFunction(object)', function(){ 92 | it('should support ==', function(){ 93 | var fn = toFunction({ 94 | name: 'tobi', 95 | age: /\d+/ 96 | }); 97 | 98 | assert(false === fn({})); 99 | assert(false === fn({ name: 'luna' })); 100 | assert(false === fn('tobi')); 101 | assert(false === fn({ name: 'luna', age: 2 })); 102 | assert(false === fn({ name: 'tobi' })); 103 | assert(true === fn({ name: 'tobi', age: 3, type: 'ferret' })); 104 | }) 105 | 106 | it('should support nesting', function(){ 107 | var user = { 108 | name: { 109 | first: 'Tobi', 110 | last: 'Ferret' 111 | } 112 | }; 113 | 114 | var fn = toFunction({ 115 | name: { 116 | first: 'Tobi', 117 | last: 'Ferret' 118 | } 119 | }); 120 | 121 | assert(true == fn(user)); 122 | 123 | var fn = toFunction({ 124 | name: { 125 | first: 'Loki', 126 | last: 'Ferret' 127 | } 128 | }); 129 | 130 | assert(false == fn(user)); 131 | }) 132 | 133 | it('should support regexps', function(){ 134 | var user = { 135 | name: { 136 | first: 'Tobi', 137 | last: 'Ferret' 138 | } 139 | }; 140 | 141 | var fn = toFunction({ 142 | name: { last: /^Fer/ } 143 | }); 144 | 145 | assert(true == fn(user)); 146 | 147 | var fn = toFunction({ 148 | name: { 149 | last: /^ferret$/ 150 | } 151 | }); 152 | 153 | assert(false == fn(user)); 154 | }) 155 | }); 156 | 157 | describe('toFunction(other)', function(){ 158 | it('should default to === equality', function(){ 159 | var fn = toFunction(null); 160 | assert(true == fn(null)); 161 | assert(false == fn(0)); 162 | assert(false == fn()); 163 | }) 164 | }) 165 | --------------------------------------------------------------------------------