├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── common-js ├── _.array.builders.js ├── _.array.selectors.js ├── _.collections.walk.js ├── _.function.arity.js ├── _.function.combinators.js ├── _.function.iterators.js ├── _.function.predicates.js ├── _.object.builders.js ├── _.object.selectors.js ├── _.util.existential.js ├── _.util.operators.js ├── _.util.strings.js └── _.util.trampolines.js ├── component.json ├── dist ├── lodash-contrib.commonjs.js ├── lodash-contrib.js └── lodash-contrib.min.js ├── docs ├── _.array.builders.js.md ├── _.array.selectors.js.md ├── _.collections.walk.js.md ├── _.function.arity.js.md ├── _.function.combinators.js.md ├── _.function.iterators.js.md ├── _.function.predicates.js.md ├── _.object.builders.js.md ├── _.object.selectors.js.md ├── _.util.existential.js.md ├── _.util.operators.js.md ├── _.util.strings.js.md ├── _.util.trampolines.js.md └── index.md ├── examples ├── builders-examples.js.md ├── combinators-examples.js.md └── walk-examples.js.md ├── gh-pages ├── _.array.builders.js.html ├── _.array.selectors.js.html ├── _.collections.walk.js.html ├── _.function.arity.js.html ├── _.function.combinators.js.html ├── _.function.iterators.js.html ├── _.function.predicates.js.html ├── _.object.builders.js.html ├── _.object.selectors.js.html ├── _.util.existential.js.html ├── _.util.operators.js.html ├── _.util.strings.js.html ├── _.util.trampolines.js.html ├── docco.css ├── index.html └── public │ ├── fonts │ ├── aller-bold.eot │ ├── aller-bold.ttf │ ├── aller-bold.woff │ ├── aller-light.eot │ ├── aller-light.ttf │ ├── aller-light.woff │ ├── novecento-bold.eot │ ├── novecento-bold.ttf │ └── novecento-bold.woff │ └── stylesheets │ └── normalize.css ├── package.json ├── test ├── array.selectors.js ├── browserified.html ├── collections.walk.js ├── dist-min.html ├── function.combinators.js ├── function.iterators.js ├── function.predicates.js ├── index.html ├── mocha │ ├── array.builders.js │ ├── function.arity.js │ ├── object.builders.js │ ├── util.operators.js │ ├── util.strings.js │ └── vanilla.js ├── object.selectors.js ├── util.existential.js ├── util.strings.js ├── util.trampolines.js └── vendor │ ├── jquery.js │ ├── jslitmus.js │ ├── lodash.js │ ├── qunit.css │ ├── qunit.js │ └── runner.js └── tocdoc.css /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /gen/ 3 | raw 4 | node_modules 5 | .DS_Store 6 | docs/*.css 7 | docs/*.html 8 | docs/public 9 | examples/*.css 10 | examples/*.html 11 | examples/public 12 | *.log 13 | bower_components/ 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '6' 5 | notifications: 6 | slack: empeeric:Hsm2xE5SNniMiUAEgM0Ngcnb 7 | deploy: 8 | provider: npm 9 | email: me@refack.com 10 | api_key: 11 | secure: eLMBTF/VA5zprsHZY16xnUHf7wbnr9a/xVk6UsMxYg87K6EnT/2/jwfQ28cFhEn5DFiEnyAQjLG63ERxhKIIcXb4Z37XQJrjLoVKg0yaM/pIGiEyZ5WJw2sIwtzU43d2D/us0aCcLvJILYWajQglc1F61fTUxA3Fg555UUtDQU0= 12 | on: 13 | tags: true 14 | repo: node4good/lodash-contrib 15 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How to contribute to lodash-contrib 2 | 3 | * Before you open a ticket or send a pull request, please [search](https://github.com/empeeric/lodash-contrib/issues) for previous discussions about the same feature or issue. Add to the earlier ticket if you find one. 4 | 5 | * Before sending a pull request for a feature, be sure to have [tests like found in lodash](http://lodashjs.org/test/). Tests may be run in a browser by opening `test/index.html`. Tests and linting can be run in the terminal by using the `grunt test` command, or `grunt watch:test` to automatically rerun after file save. 6 | 7 | * Use the same coding [style as lodash](https://github.com/documentcloud/lodash/blob/master/lodash.js). 8 | 9 | * In your pull request, do not add documentation, edit the files in `dist/` or use grunt to re-build the files in `dist/`. We'll do those things before cutting a new release. 10 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | var _ = require('lodash'); 3 | 4 | grunt.loadNpmTasks('grunt-browserify'); 5 | grunt.loadNpmTasks('grunt-contrib-copy'); 6 | grunt.loadNpmTasks("grunt-contrib-uglify"); 7 | grunt.loadNpmTasks('grunt-contrib-qunit'); 8 | grunt.loadNpmTasks('grunt-contrib-jshint'); 9 | grunt.loadNpmTasks("grunt-mocha-test"); 10 | 11 | grunt.initConfig({ 12 | pkg: grunt.file.readJSON('package.json'), 13 | 14 | contribBanner: "// <%= pkg.name %> v<%= pkg.version %>\n" + 15 | "// =========================\n\n" + 16 | "// > <%= pkg.homepage %>\n" + 17 | "// > (c) 2013 Michael Fogus, DocumentCloud and Investigative Reporters & Editors\n" + 18 | "// > (c) 2016 Refael Ackermann & node4good.org\n" + 19 | "// > <%= pkg.name %> may be freely distributed under the <%= pkg.license %> license.\n\n", 20 | 21 | 22 | copy: { 23 | main: { 24 | expand: true, 25 | flatten: true, 26 | src: 'gen/lodash*.js', 27 | dest: 'dist/' 28 | } 29 | }, 30 | 31 | 32 | uglify: { 33 | all: { 34 | files: { "gen/lodash-contrib.min.js": "gen/lodash-contrib.js" } 35 | } 36 | }, 37 | 38 | 39 | mochaTest: { 40 | test: { 41 | src: ['test/mocha/*.*'], 42 | options: { 43 | reporter: "spec" 44 | } 45 | } 46 | }, 47 | 48 | 49 | qunit: { 50 | main: ['test/index.html'], 51 | minified: ['test/dist-min.html'], 52 | browserified: ['test/browserified.html'] 53 | }, 54 | 55 | 56 | jshint: { 57 | all: [ 58 | "*.js", 59 | "test/*.js" 60 | ], 61 | options: { 62 | es3: true, // Enforce ES3 compatibility 63 | indent: 2, // Indent by 2 spaces 64 | camelcase: true, // All vars must be camelCase or UPPER_WITH_UNDERSCORES 65 | eqnull: true, // Allow 'x == null' convention 66 | forin: true, // Require `for x in y` to filter with `hasOwnProperty` 67 | newcap: true, // Require constructor names to be capitalized 68 | "-W058": false // Allow 'new Constructor' without parens 69 | } 70 | }, 71 | 72 | 73 | browserWrap: { 74 | all: { 75 | files: [ 76 | { 77 | src: ['common-js/*.*'], 78 | dst: 'gen/temp-scaffold.js' 79 | } 80 | ] 81 | } 82 | }, 83 | 84 | 85 | commonjsWrap: { 86 | all: { 87 | files: [ 88 | { 89 | src: ['gen/temp-scaffold.js'], 90 | dst: 'gen/lodash-contrib.commonjs.js' 91 | } 92 | ] 93 | } 94 | }, 95 | 96 | 97 | browserify: { 98 | dist: { 99 | files: { 100 | 'gen/lodash-contrib.js': 'gen/temp-scaffold.js' 101 | } 102 | }, 103 | test: { 104 | files: { 105 | 'gen/double.browserified.js': 'gen/lodash-contrib.js' 106 | }, 107 | browserifyOptions: { debug: true } 108 | } 109 | } 110 | }); 111 | 112 | 113 | grunt.registerMultiTask('browserWrap', 'index.js scaffolding task.', function () { 114 | grunt.log.writeln('Generating first pass browserWrap.js'); 115 | var setup = this.files.pop(); 116 | var code = setup.src.reduce(function (seed, val) { return seed + 'require("../' + val + '")(_);\n'; }, ''); 117 | grunt.file.write(setup.dst, code); 118 | }); 119 | 120 | 121 | grunt.registerMultiTask('commonjsWrap', 'index.js scaffolding task.', function () { 122 | grunt.log.writeln('Generating first pass commonjsWrap.js'); 123 | var code = 'var _ = module.exports = require("lodash").runInContext();\n\n'; 124 | 125 | var setup = this.files.pop(); 126 | code += setup.src.reduce(function (seed, val) { return seed + grunt.file.read(val); }, ''); 127 | grunt.file.write(setup.dst, code); 128 | 129 | code += '\n //Adding explicit method names for static analysis\n'; 130 | var ctrb1 = require('./' + setup.dst); 131 | _(ctrb1).keys().sortBy().forEach(function (name) { 132 | var len = Math.max(20 - name.length, 2); 133 | var arr = new Array(len); 134 | var aligner = arr.join(' '); 135 | code += 'module.exports.' + name + aligner + ' = _.' + name + ';\n'; 136 | }); 137 | grunt.file.write(setup.dst, code); 138 | }); 139 | 140 | 141 | grunt.registerTask('gen', ['browserWrap', 'commonjsWrap', 'browserify:dist', 'uglify', 'browserify:test', 'copy']); 142 | grunt.registerTask('test', ['jshint', 'mochaTest', 'qunit:main', 'qunit:browserified', 'qunit:minified']); 143 | grunt.registerTask('default', ['gen', 'test']); 144 | }; 145 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Jeremy Ashkenas, Michael Fogus, DocumentCloud and Investigative Reporters & Editors 2 | Copyright (c) 2013 Refael Ackermann & Empeeric 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without 7 | restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | The brass buckles on lodash's utility belt 2 | 3 | Basically a [`lodash`](http://lodash.com/) compatible fork of [`underscore-contrib`](https://github.com/documentcloud/underscore-contrib) 4 | 5 | lodash-contrib 6 | ============== 7 | [![Build Status](https://travis-ci.org/node4good/lodash-contrib.png?branch=master)](https://travis-ci.org/node4good/lodash-contrib) 8 | 9 | Links 10 | ----- 11 | 12 | * [Documentation](https://github.com/node4good/lodash-contrib/blob/master/docs/index.md) 13 | * [Source repository](https://github.com/Empeeric/lodash-contrib) 14 | * [Tickets and bug reports](https://github.com/Empeeric/lodash-contrib/issues?state=open) 15 | 16 | Why lodash-contrib? 17 | ----------------------- 18 | 19 | While lodash provides a bevy of useful tools to support functional programming in JavaScript, it can't 20 | (and shouldn't) be everything to everyone. lodash-contrib is intended as a home for functions that, for 21 | various reasons, don't belong in lodash proper. In particular, it aims to be: 22 | 23 | * a home for functions that are limited in scope, but solve certain point problems, and 24 | * a proving ground for features that belong in lodash proper, but need some advocacy and/or evolution 25 | (or devolution) to get them there. 26 | 27 | Use 28 | --- 29 | 30 | ####Web 31 | 32 | First, you’ll need lodash. Then you can grab the relevant lodash-contrib libraries and simply add something like the following to your pages: 33 | ```html 34 | 35 | 36 | ``` 37 | 38 | You could also use [browserify](http://browserify.org/) to bundle your code into a JavaScript file that you can include in a web page. 39 | Require `lodash-contrib` in your main script file (e.g. `test.js`) like so: 40 | 41 | ```javascript 42 | var _ = require('lodash-contrib'); 43 | 44 | // YOUR CODE COMES HERE 45 | console.log(_.truthyAll(0, 1, 2, 'lodash-contrib!')); 46 | ``` 47 | 48 | then you could run `browserify test.js -o browserified.js` to get `lodash`, `lodash-contrib` and your code into `browserified.js`. 49 | 50 | ####Node 51 | 52 | Just run `npm install lodash-contrib`, you don't need to have lodash as it will be grabbed as a dependency. 53 | 54 | Contributing 55 | ------------ 56 | 57 | **We need some docs sync** since rebasing to version 3 (some methods renamed xxxContrib) 58 | 59 | There is still a lot of work to do around perf, documentation, examples, testing and distribution so any help 60 | in those areas is welcomed. Pull requests are accepted, but please search the [issues](https://github.com/empeeric/lodash-contrib/issues) 61 | before proposing a new sub-contrib or addition. Additionally, all patches and proposals should have strong 62 | documentation, motivating cases and tests. It would be nice if we could not only provide useful tools built on 63 | lodash, but also provide an educational experience for why and how one might use them. 64 | 65 | Other (potentially) useful sub-contribs include the following: 66 | 67 | * String utilities 68 | * Date/time utilities 69 | * Validators 70 | * Iterators 71 | * Generators 72 | * Promises 73 | * Monads 74 | * Currying 75 | * Laziness 76 | * Multimethods 77 | 78 | What do these mean? Well, that’s up for discussion. :-) 79 | -------------------------------------------------------------------------------- /common-js/_.array.builders.js: -------------------------------------------------------------------------------- 1 | module.exports = function(_) { 2 | // Create quick reference variables for speed access to core prototypes. 3 | var slice = Array.prototype.slice, 4 | concat = Array.prototype.concat, 5 | sort = Array.prototype.sort; 6 | 7 | var existy = function(x) { return x != null; }; 8 | 9 | // Mixing in the array builders 10 | // ---------------------------- 11 | 12 | _.mixin({ 13 | // Concatenates one or more arrays given as arguments. If given objects and 14 | // scalars as arguments `cat` will plop them down in place in the result 15 | // array. If given an `arguments` object, `cat` will treat it like an array 16 | // and concatenate it likewise. 17 | cat: function() { 18 | return _.reduce(arguments, function(acc, elem) { 19 | if (_.isArguments(elem)) { 20 | return concat.call(acc, slice.call(elem)); 21 | } 22 | else { 23 | return concat.call(acc, elem); 24 | } 25 | }, []); 26 | }, 27 | 28 | // 'Constructs' an array by putting an element at its front 29 | cons: function(head, tail) { 30 | return _.cat([head], tail); 31 | }, 32 | 33 | // Takes an array and chunks it some number of times into 34 | // sub-arrays of size n. Allows and optional padding array as 35 | // the third argument to fill in the tail chunk when n is 36 | // not sufficient to build chunks of the same size. 37 | chunkContrib: function(array, n, pad) { 38 | var args = arguments; 39 | var p = function(array) { 40 | if (array == null) return []; 41 | 42 | var part = _.take(array, n); 43 | 44 | if (n === _.size(part)) { 45 | return _.cons(part, p(_.drop(array, n))); 46 | } 47 | else if (args.length === 3) { 48 | pad = _.isArray(pad) ? pad : _.repeatContrib(n, pad); 49 | return [_.take(_.cat(part, pad), n)]; 50 | } 51 | else { 52 | return []; 53 | } 54 | }; 55 | 56 | return p(array); 57 | }, 58 | 59 | // Takes an array and chunks it some number of times into 60 | // sub-arrays of size n. If the array given cannot fill the size 61 | // needs of the final chunk then a smaller chunk is used 62 | // for the last. 63 | chunkAll: function(array, n, step) { 64 | step = (step != null) ? step : n; 65 | 66 | var p = function(array, n, step) { 67 | if (_.isEmpty(array)) return []; 68 | 69 | return _.cons(_.take(array, n), 70 | p(_.drop(array, step), n, step)); 71 | }; 72 | 73 | return p(array, n, step); 74 | }, 75 | 76 | // Maps a function over an array and concatenates all of the results. 77 | mapcat: function(array, fun) { 78 | return _.cat.apply(null, _.map(array, fun)); 79 | }, 80 | 81 | // Returns an array with some item between each element 82 | // of a given array. 83 | interpose: function(array, inter) { 84 | if (!_.isArray(array)) throw new TypeError; 85 | var sz = _.size(array); 86 | if (sz === 0) return array; 87 | if (sz === 1) return array; 88 | 89 | return slice.call(_.mapcat(array, function(elem) { 90 | return _.cons(elem, [inter]); 91 | }), 0, -1); 92 | }, 93 | 94 | // Weaves two or more arrays together 95 | weave: function(/* args */) { 96 | if (!_.some(arguments)) return []; 97 | if (arguments.length == 1) return arguments[0]; 98 | 99 | return _.filter(_.flatten(_.zip.apply(null, arguments), false), function(elem) { 100 | return elem != null; 101 | }); 102 | }, 103 | interleave: _.weave, 104 | 105 | // Returns an array of a value repeated a certain number of 106 | // times. 107 | repeatContrib: function(t, elem) { 108 | return _.times(t, function() { return elem; }); 109 | }, 110 | 111 | // Returns an array built from the contents of a given array repeated 112 | // a certain number of times. 113 | cycle: function(t, elems) { 114 | return _.flatten(_.times(t, function() { return elems; }), true); 115 | }, 116 | 117 | // Returns an array with two internal arrays built from 118 | // taking an original array and spliting it at an index. 119 | splitAt: function(array, index) { 120 | return [_.take(array, index), _.drop(array, index)]; 121 | }, 122 | 123 | // Call a function recursively f(f(f(args))) until a second 124 | // given function goes falsey. Expects a seed value to start. 125 | iterateUntil: function(doit, checkit, seed) { 126 | var ret = []; 127 | var result = doit(seed); 128 | 129 | while (checkit(result)) { 130 | ret.push(result); 131 | result = doit(result); 132 | } 133 | 134 | return ret; 135 | }, 136 | 137 | // Takes every nth item from an array, returning an array of 138 | // the results. 139 | takeSkipping: function(array, n) { 140 | var ret = []; 141 | var sz = _.size(array); 142 | 143 | if (n <= 0) return []; 144 | if (n === 1) return array; 145 | 146 | for(var index = 0; index < sz; index += n) { 147 | ret.push(array[index]); 148 | } 149 | 150 | return ret; 151 | }, 152 | 153 | // Returns an array of each intermediate stage of a call to 154 | // a `reduce`-like function. 155 | reductions: function(array, fun, init) { 156 | var ret = []; 157 | var acc = init; 158 | 159 | _.each(array, function(v,k) { 160 | acc = fun(acc, array[k]); 161 | ret.push(acc); 162 | }); 163 | 164 | return ret; 165 | }, 166 | 167 | // Runs its given function on the index of the elements rather than 168 | // the elements themselves, keeping all of the truthy values in the end. 169 | keepIndexed: function(array, pred) { 170 | return _.filter(_.map(_.range(_.size(array)), function(i) { 171 | return pred(i, array[i]); 172 | }), 173 | existy); 174 | }, 175 | 176 | // Accepts an array-like object (other than strings) as an argument and 177 | // returns an array whose elements are in the reverse order. Unlike the 178 | // built-in `Array.prototype.reverse` method, this does not mutate the 179 | // original object. Note: attempting to use this method on a string will 180 | // result in a `TypeError`, as it cannot properly reverse unicode strings. 181 | 182 | reverseOrder: function(obj) { 183 | if (typeof obj == 'string') 184 | throw new TypeError('Strings cannot be reversed by _.reverseOrder'); 185 | return slice.call(obj).reverse(); 186 | }, 187 | 188 | 189 | // Returns copy or array sorted according to arbitrary ordering 190 | // order must be an array of values; defines the custom sort 191 | // key must be one of: missing/null, a string, or a function; 192 | collate: function(array, order, key) { 193 | if (!_.isArray(array)) throw new TypeError("expected an array as the first argument"); 194 | if (!_.isArray(order)) throw new TypeError("expected an array as the second argument"); 195 | 196 | return sort.call(array, function (a, b) { 197 | if(_.isFunction(key)) { 198 | valA = key.call(a); 199 | valB = key.call(b); 200 | } else if(existy(key)) { 201 | valA = a[key]; 202 | valB = b[key]; 203 | } else { 204 | valA = a; 205 | valB = b; 206 | } 207 | 208 | var rankA = _.indexOf(order, valA); 209 | var rankB = _.indexOf(order, valB); 210 | 211 | if(rankA === -1) return 1; 212 | if(rankB === -1) return -1; 213 | 214 | return rankA - rankB; 215 | }); 216 | } 217 | }); 218 | 219 | }; 220 | -------------------------------------------------------------------------------- /common-js/_.array.selectors.js: -------------------------------------------------------------------------------- 1 | module.exports = function(_) { 2 | // Create quick reference variables for speed access to core prototypes. 3 | var slice = Array.prototype.slice, 4 | concat = Array.prototype.concat; 5 | 6 | var existy = function(x) { return x != null; }; 7 | var truthy = function(x) { return (x !== false) && existy(x); }; 8 | var isSeq = function(x) { return (_.isArray(x)) || (_.isArguments(x)); }; 9 | 10 | function nths(array, indices) { 11 | if (array == null) return void 0; 12 | 13 | if (isSeq(indices)) 14 | return _(indices).map(function(i){return array[i];}).valueOf(); 15 | else 16 | return nths(array, slice.call(arguments, 1)); 17 | } 18 | 19 | // Mixing in the array selectors 20 | // ---------------------------- 21 | 22 | _.mixin({ 23 | // Returns the second element of an array. Passing **n** will return all but 24 | // the first of the head N values in the array. The **guard** check allows it 25 | // to work with `_.map`. 26 | second: function(array, n, guard) { 27 | if (array == null) return void 0; 28 | return (n != null) && !guard ? slice.call(array, 1, n) : array[1]; 29 | }, 30 | 31 | // Returns the third element of an array. Passing **n** will return all but 32 | // the first two of the head N values in the array. The **guard** check allows it 33 | // to work with `_.map`. 34 | third: function(array, n, guard) { 35 | if (array == null) return void 0; 36 | return (n != null) && !guard ? slice.call(array, 2, n) : array[2]; 37 | }, 38 | 39 | // A function to get at an index into an array 40 | nth: function(array, index, guard) { 41 | if ((index != null) && !guard) return array[index]; 42 | }, 43 | 44 | // A function to get values via indices into an array 45 | nths: nths, 46 | valuesAt: nths, 47 | 48 | // A function to get at "truthily" indexed values 49 | // bin refers to "binary" nature of true/false values in binIndices 50 | // but can also be thought of as putting array values into either "take" or "don't" bins 51 | binPick: function binPick(array, binIndices) { 52 | if (array == null) return void 0; 53 | 54 | if (isSeq(binIndices)) 55 | return _.nths(array, _.range(binIndices.length).filter(function(i){return binIndices[i];})); 56 | else 57 | return binPick(array, slice.call(arguments, 1)); 58 | }, 59 | 60 | // Returns an array with two internal arrays built from 61 | // taking an original array and spliting it at the index 62 | // where a given function goes falsey. 63 | splitWith: function(array, pred) { 64 | return [_.takeWhile(array, pred), _.dropWhile(array, pred)]; 65 | }, 66 | 67 | // Takes an array and partitions it as the given predicate changes 68 | // truth sense. 69 | partitionBy: function(array, fun){ 70 | if (_.isEmpty(array) || !existy(array)) return []; 71 | 72 | var fst = _.first(array); 73 | var fstVal = fun(fst); 74 | var run = concat.call([fst], _.takeWhile(_.rest(array), function(e) { 75 | return _.isEqual(fstVal, fun(e)); 76 | })); 77 | 78 | return concat.call([run], _.partitionBy(_.drop(array, _.size(run)), fun)); 79 | }, 80 | 81 | // Returns the 'best' value in an array based on the result of a 82 | // given function. 83 | best: function(array, fun) { 84 | return _.reduce(array, function(x, y) { 85 | return fun(x, y) ? x : y; 86 | }); 87 | }, 88 | 89 | // Returns an array of existy results of a function over an source array. 90 | keep: function(array, fun) { 91 | if (!isSeq(array)) throw new TypeError("expected an array as the first argument"); 92 | 93 | return _.filter(_.map(array, function(e) { 94 | return fun(e); 95 | }), existy); 96 | } 97 | }); 98 | 99 | }; 100 | -------------------------------------------------------------------------------- /common-js/_.collections.walk.js: -------------------------------------------------------------------------------- 1 | module.exports = function (_) { 2 | // Helpers 3 | // ------- 4 | 5 | // An internal object that can be returned from a visitor function to 6 | // prevent a top-down walk from walking subtrees of a node. 7 | var stopRecursion = {}; 8 | 9 | // An internal object that can be returned from a visitor function to 10 | // cause the walk to immediately stop. 11 | var stopWalk = {}; 12 | 13 | var notTreeError = 'Not a tree: same object found in two different branches'; 14 | 15 | // Implements the default traversal strategy: if `obj` is a DOM node, walk 16 | // its DOM children; otherwise, walk all the objects it references. 17 | function defaultTraversal(obj) { 18 | return _.isElement(obj) ? obj.children : obj; 19 | } 20 | 21 | // Walk the tree recursively beginning with `root`, calling `beforeFunc` 22 | // before visiting an objects descendents, and `afterFunc` afterwards. 23 | // If `collectResults` is true, the last argument to `afterFunc` will be a 24 | // collection of the results of walking the node's subtrees. 25 | function walkImpl(root, traversalStrategy, beforeFunc, afterFunc, context, collectResults) { 26 | var visited = []; 27 | return (function _walk(value, key, parent) { 28 | // Keep track of objects that have been visited, and throw an exception 29 | // when trying to visit the same object twice. 30 | if (_.isObject(value)) { 31 | if (visited.indexOf(value) >= 0) throw new TypeError(notTreeError); 32 | visited.push(value); 33 | } 34 | 35 | if (beforeFunc) { 36 | var result = beforeFunc.call(context, value, key, parent); 37 | if (result === stopWalk) return stopWalk; 38 | if (result === stopRecursion) return; 39 | } 40 | 41 | var subResults; 42 | var target = traversalStrategy(value); 43 | if (_.isObject(target) && !_.isEmpty(target)) { 44 | // If collecting results from subtrees, collect them in the same shape 45 | // as the parent node. 46 | if (collectResults) subResults = _.isArray(value) ? [] : {}; 47 | 48 | var stop = _.any(target, function(obj, key) { 49 | var result = _walk(obj, key, value); 50 | if (result === stopWalk) return true; 51 | if (subResults) subResults[key] = result; 52 | }); 53 | if (stop) return stopWalk; 54 | } 55 | if (afterFunc) return afterFunc.call(context, value, key, parent, subResults); 56 | })(root); 57 | } 58 | 59 | // Internal helper providing the implementation for `pluck` and `pluckRec`. 60 | function pluck(obj, propertyName, recursive) { 61 | var results = []; 62 | this.preorder(obj, function(value, key) { 63 | if (!recursive && key == propertyName) 64 | return stopRecursion; 65 | if (_.has(value, propertyName)) 66 | results[results.length] = value[propertyName]; 67 | }); 68 | return results; 69 | } 70 | 71 | var exports = { 72 | // Performs a preorder traversal of `obj` and returns the first value 73 | // which passes a truth test. 74 | find: function(obj, visitor, context) { 75 | var result; 76 | this.preorder(obj, function(value, key, parent) { 77 | if (visitor.call(context, value, key, parent)) { 78 | result = value; 79 | return stopWalk; 80 | } 81 | }, context); 82 | return result; 83 | }, 84 | 85 | // Recursively traverses `obj` and returns all the elements that pass a 86 | // truth test. `strategy` is the traversal function to use, e.g. `preorder` 87 | // or `postorder`. 88 | filter: function(obj, strategy, visitor, context) { 89 | var results = []; 90 | if (obj == null) return results; 91 | strategy(obj, function(value, key, parent) { 92 | if (visitor.call(context, value, key, parent)) results.push(value); 93 | }, null, this._traversalStrategy); 94 | return results; 95 | }, 96 | 97 | // Recursively traverses `obj` and returns all the elements for which a 98 | // truth test fails. 99 | reject: function(obj, strategy, visitor, context) { 100 | return this.filter(obj, strategy, function(value, key, parent) { 101 | return !visitor.call(context, value, key, parent); 102 | }); 103 | }, 104 | 105 | // Produces a new array of values by recursively traversing `obj` and 106 | // mapping each value through the transformation function `visitor`. 107 | // `strategy` is the traversal function to use, e.g. `preorder` or 108 | // `postorder`. 109 | map: function(obj, strategy, visitor, context) { 110 | var results = []; 111 | strategy(obj, function(value, key, parent) { 112 | results[results.length] = visitor.call(context, value, key, parent); 113 | }, null, this._traversalStrategy); 114 | return results; 115 | }, 116 | 117 | // Return the value of properties named `propertyName` reachable from the 118 | // tree rooted at `obj`. Results are not recursively searched; use 119 | // `pluckRec` for that. 120 | pluck: function(obj, propertyName) { 121 | return pluck.call(this, obj, propertyName, false); 122 | }, 123 | 124 | // Version of `pluck` which recursively searches results for nested objects 125 | // with a property named `propertyName`. 126 | pluckRec: function(obj, propertyName) { 127 | return pluck.call(this, obj, propertyName, true); 128 | }, 129 | 130 | // Recursively traverses `obj` in a depth-first fashion, invoking the 131 | // `visitor` function for each object only after traversing its children. 132 | // `traversalStrategy` is intended for internal callers, and is not part 133 | // of the public API. 134 | postorder: function(obj, visitor, context, traversalStrategy) { 135 | traversalStrategy = traversalStrategy || this._traversalStrategy; 136 | walkImpl(obj, traversalStrategy, null, visitor, context); 137 | }, 138 | 139 | // Recursively traverses `obj` in a depth-first fashion, invoking the 140 | // `visitor` function for each object before traversing its children. 141 | // `traversalStrategy` is intended for internal callers, and is not part 142 | // of the public API. 143 | preorder: function(obj, visitor, context, traversalStrategy) { 144 | traversalStrategy = traversalStrategy || this._traversalStrategy; 145 | walkImpl(obj, traversalStrategy, visitor, null, context); 146 | }, 147 | 148 | // Builds up a single value by doing a post-order traversal of `obj` and 149 | // calling the `visitor` function on each object in the tree. For leaf 150 | // objects, the `memo` argument to `visitor` is the value of the `leafMemo` 151 | // argument to `reduce`. For non-leaf objects, `memo` is a collection of 152 | // the results of calling `reduce` on the object's children. 153 | reduce: function(obj, visitor, leafMemo, context) { 154 | var reducer = function(value, key, parent, subResults) { 155 | return visitor(subResults || leafMemo, value, key, parent); 156 | }; 157 | return walkImpl(obj, this._traversalStrategy, null, reducer, context, true); 158 | } 159 | }; 160 | 161 | // Set up aliases to match those in lodash.js. 162 | exports.collect = exports.map; 163 | exports.detect = exports.find; 164 | exports.select = exports.filter; 165 | 166 | // Returns an object containing the walk functions. If `traversalStrategy` 167 | // is specified, it is a function determining how objects should be 168 | // traversed. Given an object, it returns the object to be recursively 169 | // walked. The default strategy is equivalent to `_.identity` for regular 170 | // objects, and for DOM nodes it returns the node's DOM children. 171 | function walk(traversalStrategy) { 172 | var walker = _.clone(exports); 173 | 174 | // Bind all of the public functions in the walker to itself. This allows 175 | // the traversal strategy to be dynamically scoped. 176 | _.bindAll.apply(null, [walker].concat(_.keys(walker))); 177 | 178 | walker._traversalStrategy = traversalStrategy || defaultTraversal; 179 | return walker; 180 | } 181 | 182 | // Use `_.walk` as a namespace to hold versions of the walk functions which 183 | // use the default traversal strategy. 184 | _.extend(walk, walk()); 185 | 186 | _.mixin({walk: walk}); 187 | }; 188 | -------------------------------------------------------------------------------- /common-js/_.function.arity.js: -------------------------------------------------------------------------------- 1 | module.exports = function (_) { 2 | 3 | // Helpers 4 | // ------- 5 | 6 | function enforcesUnary(fn) { 7 | return function mustBeUnary() { 8 | if (arguments.length === 1) { 9 | return fn.apply(this, arguments); 10 | } 11 | else throw new RangeError('Only a single argument may be accepted.'); 12 | 13 | }; 14 | } 15 | 16 | // Curry 17 | // ------- 18 | var curry = (function () { 19 | function collectArgs(func, that, argCount, args, newArg, reverse) { 20 | if (reverse === true) { 21 | args.unshift(newArg); 22 | } else { 23 | args.push(newArg); 24 | } 25 | if (args.length == argCount) { 26 | return func.apply(that, args); 27 | } else { 28 | return enforcesUnary(function () { 29 | return collectArgs(func, that, argCount, args.slice(0), arguments[0], reverse); 30 | }); 31 | } 32 | } 33 | 34 | return function curry(func, reverse) { 35 | var that = this; 36 | return enforcesUnary(function () { 37 | return collectArgs(func, that, func.length, [], arguments[0], reverse); 38 | }); 39 | }; 40 | }()); 41 | 42 | // Enforce Arity 43 | // -------------------- 44 | var enforce = (function () { 45 | var CACHE = []; 46 | return function enforce(func) { 47 | if (typeof func !== 'function') { 48 | throw new Error('Argument 1 must be a function.'); 49 | } 50 | var funcLength = func.length; 51 | if (CACHE[funcLength] === undefined) { 52 | CACHE[funcLength] = function (enforceFunc) { 53 | return function () { 54 | if (arguments.length !== funcLength) { 55 | throw new RangeError(funcLength + ' arguments must be applied.'); 56 | } 57 | return enforceFunc.apply(this, arguments); 58 | }; 59 | }; 60 | } 61 | return CACHE[funcLength](func); 62 | }; 63 | }()); 64 | 65 | // Right curry variants 66 | // --------------------- 67 | var curryRight = function (func) { 68 | return curry.call(this, func, true); 69 | }; 70 | 71 | var curryRight2 = function (fun) { 72 | return enforcesUnary(function (last) { 73 | return enforcesUnary(function (first) { 74 | return fun.call(this, first, last); 75 | }); 76 | }); 77 | }; 78 | 79 | var curryRight3 = function (fun) { 80 | return enforcesUnary(function (last) { 81 | return enforcesUnary(function (second) { 82 | return enforcesUnary(function (first) { 83 | return fun.call(this, first, second, last); 84 | }); 85 | }); 86 | }); 87 | }; 88 | 89 | // Mixing in the arity functions 90 | // ----------------------------- 91 | 92 | _.mixin({ 93 | // ### Fixed arguments 94 | 95 | // Fixes the arguments to a function based on the parameter template defined by 96 | // the presence of values and the `_` placeholder. 97 | fix: function (fun) { 98 | var fixArgs = _.tail(arguments); 99 | 100 | var f = function () { 101 | var args = fixArgs.slice(); 102 | var arg = 0; 103 | 104 | for (var i = 0; i < (args.length || arg < arguments.length); i++) { 105 | if (args[i] === _) { 106 | args[i] = arguments[arg++]; 107 | } 108 | } 109 | 110 | return fun.apply(null, args); 111 | }; 112 | 113 | f._original = fun; 114 | 115 | return f; 116 | }, 117 | 118 | unary: function (fun) { 119 | return function unary(a) { 120 | return fun.call(this, a); 121 | }; 122 | }, 123 | 124 | binary: function (fun) { 125 | return function binary(a, b) { 126 | return fun.call(this, a, b); 127 | }; 128 | }, 129 | 130 | ternary: function (fun) { 131 | return function ternary(a, b, c) { 132 | return fun.call(this, a, b, c); 133 | }; 134 | }, 135 | 136 | quaternary: function (fun) { 137 | return function quaternary(a, b, c, d) { 138 | return fun.call(this, a, b, c, d); 139 | }; 140 | }, 141 | 142 | rCurry: curryRight, // alias for backwards compatibility 143 | 144 | curry2: function (fun) { 145 | return enforcesUnary(function curried(first) { 146 | return enforcesUnary(function (last) { 147 | return fun.call(this, first, last); 148 | }); 149 | }); 150 | }, 151 | 152 | curry3: function (fun) { 153 | return enforcesUnary(function (first) { 154 | return enforcesUnary(function (second) { 155 | return enforcesUnary(function (last) { 156 | return fun.call(this, first, second, last); 157 | }); 158 | }); 159 | }); 160 | }, 161 | 162 | // reverse currying for functions taking two arguments. 163 | curryRight2: curryRight2, 164 | rcurry2: curryRight2, // alias for backwards compatibility 165 | 166 | curryRight3: curryRight3, 167 | rcurry3: curryRight3, // alias for backwards compatibility 168 | 169 | // Dynamic decorator to enforce function arity and defeat varargs. 170 | enforce: enforce, 171 | 172 | arity: (function () { 173 | // Allow 'new Function', as that is currently the only reliable way 174 | // to manipulate function.length 175 | /* jshint -W054 */ 176 | var FUNCTIONS = {}; 177 | return function arity(numberOfArgs, fun) { 178 | if (FUNCTIONS[numberOfArgs] == null) { 179 | var parameters = new Array(numberOfArgs); 180 | for (var i = 0; i < numberOfArgs; ++i) { 181 | parameters[i] = "__" + i; 182 | } 183 | var pstr = parameters.join(); 184 | var code = "return function (" + pstr + ") { return fun.apply(this, arguments); };"; 185 | FUNCTIONS[numberOfArgs] = new Function(['fun'], code); 186 | } 187 | if (fun == null) { 188 | return function (fun) { return arity(numberOfArgs, fun); }; 189 | } 190 | else return FUNCTIONS[numberOfArgs](fun); 191 | }; 192 | })() 193 | }); 194 | 195 | 196 | }; 197 | -------------------------------------------------------------------------------- /common-js/_.function.combinators.js: -------------------------------------------------------------------------------- 1 | module.exports = function (_) { 2 | 3 | // Helpers 4 | // ------- 5 | 6 | var existy = function(x) { return x != null; }; 7 | var truthy = function(x) { return (x !== false) && existy(x); }; 8 | var __reverse = [].reverse; 9 | var __slice = [].slice; 10 | var __map = [].map; 11 | var curry2 = function (fun) { 12 | return function curried (first, optionalLast) { 13 | if (arguments.length === 1) { 14 | return function (last) { 15 | return fun(first, last); 16 | }; 17 | } 18 | else return fun(first, optionalLast); 19 | }; 20 | }; 21 | 22 | // n.b. depends on lodash.function.arity.js 23 | 24 | // Takes a target function and a mapping function. Returns a function 25 | // that applies the mapper to its arguments before evaluating the body. 26 | function baseMapArgs (fun, mapFun) { 27 | return _.arity(fun.length, function () { 28 | return fun.apply(this, __map.call(arguments, mapFun)); 29 | }); 30 | } 31 | 32 | // Mixing in the combinator functions 33 | // ---------------------------------- 34 | 35 | _.mixin({ 36 | // Provide "always" alias for backwards compatibility 37 | always: _.constant, 38 | 39 | // Takes some number of functions, either as an array or variadically 40 | // and returns a function that takes some value as its first argument 41 | // and runs it through a pipeline of the original functions given. 42 | pipeline: function(/*, funs */){ 43 | var funs = (_.isArray(arguments[0])) ? arguments[0] : arguments; 44 | 45 | return function(seed) { 46 | return _.reduce(funs, 47 | function(l,r) { return r(l); }, 48 | seed); 49 | }; 50 | }, 51 | composeRight: _.pipeline, 52 | 53 | // Composes a bunch of predicates into a single predicate that 54 | // checks all elements of an array for conformance to all of the 55 | // original predicates. 56 | conjoin: function(/* preds */) { 57 | var preds = arguments; 58 | 59 | return function(array) { 60 | return _.every(array, function(e) { 61 | return _.every(preds, function(p) { 62 | return p(e); 63 | }); 64 | }); 65 | }; 66 | }, 67 | 68 | // Composes a bunch of predicates into a single predicate that 69 | // checks all elements of an array for conformance to any of the 70 | // original predicates. 71 | disjoin: function(/* preds */) { 72 | var preds = arguments; 73 | 74 | return function(array) { 75 | return _.some(array, function(e) { 76 | return _.some(preds, function(p) { 77 | return p(e); 78 | }); 79 | }); 80 | }; 81 | }, 82 | 83 | // Takes a predicate-like and returns a comparator (-1,0,1). 84 | comparator: function(fun) { 85 | return function(x, y) { 86 | if (truthy(fun(x, y))) 87 | return -1; 88 | else if (truthy(fun(y, x))) 89 | return 1; 90 | else 91 | return 0; 92 | }; 93 | }, 94 | 95 | // Returns a function that reverses the sense of a given predicate-like. 96 | complement: function(pred) { 97 | return function() { 98 | return !pred.apply(this, arguments); 99 | }; 100 | }, 101 | 102 | // Takes a function expecting varargs and 103 | // returns a function that takes an array and 104 | // uses its elements as the args to the original 105 | // function 106 | splat: function(fun) { 107 | return function(array) { 108 | return fun.apply(this, array); 109 | }; 110 | }, 111 | 112 | // Takes a function expecting an array and returns 113 | // a function that takes varargs and wraps all 114 | // in an array that is passed to the original function. 115 | unsplat: function(fun) { 116 | var funLength = fun.length; 117 | 118 | if (funLength < 1) { 119 | return fun; 120 | } 121 | else if (funLength === 1) { 122 | return function () { 123 | return fun.call(this, __slice.call(arguments, 0)); 124 | }; 125 | } 126 | else { 127 | return function () { 128 | var numberOfArgs = arguments.length, 129 | namedArgs = __slice.call(arguments, 0, funLength - 1), 130 | numberOfMissingNamedArgs = Math.max(funLength - numberOfArgs - 1, 0), 131 | argPadding = new Array(numberOfMissingNamedArgs), 132 | variadicArgs = __slice.call(arguments, fun.length - 1); 133 | 134 | return fun.apply(this, namedArgs.concat(argPadding).concat([variadicArgs])); 135 | }; 136 | } 137 | }, 138 | 139 | // Same as unsplat, but the rest of the arguments are collected in the 140 | // first parameter, e.g. unsplatl( function (args, callback) { ... ]}) 141 | unsplatl: function(fun) { 142 | var funLength = fun.length; 143 | 144 | if (funLength < 1) { 145 | return fun; 146 | } 147 | else if (funLength === 1) { 148 | return function () { 149 | return fun.call(this, __slice.call(arguments, 0)); 150 | }; 151 | } 152 | else { 153 | return function () { 154 | var numberOfArgs = arguments.length, 155 | namedArgs = __slice.call(arguments, Math.max(numberOfArgs - funLength + 1, 0)), 156 | variadicArgs = __slice.call(arguments, 0, Math.max(numberOfArgs - funLength + 1, 0)); 157 | 158 | return fun.apply(this, [variadicArgs].concat(namedArgs)); 159 | }; 160 | } 161 | }, 162 | 163 | // map the arguments of a function 164 | mapArgs: curry2(baseMapArgs), 165 | 166 | // Returns a function that returns an array of the calls to each 167 | // given function for some arguments. 168 | juxt: function(/* funs */) { 169 | var funs = arguments; 170 | 171 | return function(/* args */) { 172 | var args = arguments; 173 | return _.map(funs, function(f) { 174 | return f.apply(this, args); 175 | }, this); 176 | }; 177 | }, 178 | 179 | // Returns a function that protects a given function from receiving 180 | // non-existy values. Each subsequent value provided to `fnull` acts 181 | // as the default to the original function should a call receive non-existy 182 | // values in the defaulted arg slots. 183 | fnull: function(fun /*, defaults */) { 184 | var defaults = _.rest(arguments); 185 | 186 | return function(/*args*/) { 187 | var args = _.toArray(arguments); 188 | var sz = _.size(defaults); 189 | 190 | for(var i = 0; i < sz; i++) { 191 | if (!existy(args[i])) 192 | args[i] = defaults[i]; 193 | } 194 | 195 | return fun.apply(this, args); 196 | }; 197 | }, 198 | 199 | // Flips the first two args of a function 200 | flip2: function(fun) { 201 | return function(/* args */) { 202 | var flipped = __slice.call(arguments); 203 | flipped[0] = arguments[1]; 204 | flipped[1] = arguments[0]; 205 | 206 | return fun.apply(this, flipped); 207 | }; 208 | }, 209 | 210 | // Flips an arbitrary number of args of a function 211 | flip: function(fun) { 212 | return function(/* args */) { 213 | var reversed = __reverse.call(arguments); 214 | 215 | return fun.apply(this, reversed); 216 | }; 217 | }, 218 | 219 | // Takes a method-style function (one which uses `this`) and pushes 220 | // `this` into the argument list. The returned function uses its first 221 | // argument as the receiver/context of the original function, and the rest 222 | // of the arguments are used as the original's entire argument list. 223 | functionalize: function(method) { 224 | return function(ctx /*, args */) { 225 | return method.apply(ctx, _.rest(arguments)); 226 | }; 227 | }, 228 | 229 | // Takes a function and pulls the first argument out of the argument 230 | // list and into `this` position. The returned function calls the original 231 | // with its receiver (`this`) prepending the argument list. The original 232 | // is called with a receiver of `null`. 233 | methodize: function(func) { 234 | return function(/* args */) { 235 | return func.apply(null, _.cons(this, arguments)); 236 | }; 237 | }, 238 | 239 | k: _.always, 240 | t: _.pipeline 241 | }); 242 | 243 | _.unsplatr = _.unsplat; 244 | 245 | // map the arguments of a function, takes the mapping function 246 | // first so it can be used as a combinator 247 | _.mapArgsWith = curry2(_.flip(baseMapArgs)); 248 | 249 | // Returns function property of object by name, bound to object 250 | _.bound = function(obj, fname) { 251 | var fn = obj[fname]; 252 | if (!_.isFunction(fn)) 253 | throw new TypeError("Expected property to be a function"); 254 | return _.bind(fn, obj); 255 | }; 256 | 257 | }; 258 | -------------------------------------------------------------------------------- /common-js/_.function.iterators.js: -------------------------------------------------------------------------------- 1 | module.exports = function (_) { 2 | 3 | // Helpers 4 | // ------- 5 | 6 | var HASNTBEENRUN = {}; 7 | 8 | function unary (fun) { 9 | return function (first) { 10 | return fun.call(this, first); 11 | }; 12 | } 13 | 14 | function binary (fun) { 15 | return function (first, second) { 16 | return fun.call(this, first, second); 17 | }; 18 | } 19 | 20 | // Mixing in the iterator functions 21 | // -------------------------------- 22 | 23 | function foldl (iter, binaryFn, seed) { 24 | var state, element; 25 | if (seed !== void 0) { 26 | state = seed; 27 | } 28 | else { 29 | state = iter(); 30 | } 31 | element = iter(); 32 | while (element != null) { 33 | state = binaryFn.call(element, state, element); 34 | element = iter(); 35 | } 36 | return state; 37 | } 38 | 39 | function unfold (seed, unaryFn) { 40 | var state = HASNTBEENRUN; 41 | return function () { 42 | if (state === HASNTBEENRUN) { 43 | state = seed; 44 | } else if (state != null) { 45 | state = unaryFn.call(state, state); 46 | } 47 | 48 | return state; 49 | }; 50 | } 51 | 52 | // note that the unfoldWithReturn behaves differently than 53 | // unfold with respect to the first value returned 54 | function unfoldWithReturn (seed, unaryFn) { 55 | var state = seed, 56 | pair, 57 | value; 58 | return function () { 59 | if (state != null) { 60 | pair = unaryFn.call(state, state); 61 | value = pair[1]; 62 | state = value != null ? pair[0] : void 0; 63 | return value; 64 | } 65 | else return void 0; 66 | }; 67 | } 68 | 69 | function accumulate (iter, binaryFn, initial) { 70 | var state = initial; 71 | return function () { 72 | var element = iter(); 73 | if (element == null) { 74 | return element; 75 | } 76 | else { 77 | if (state === void 0) { 78 | state = element; 79 | } else { 80 | state = binaryFn.call(element, state, element); 81 | } 82 | 83 | return state; 84 | } 85 | }; 86 | } 87 | 88 | function accumulateWithReturn (iter, binaryFn, initial) { 89 | var state = initial, 90 | stateAndReturnValue, 91 | element; 92 | return function () { 93 | element = iter(); 94 | if (element == null) { 95 | return element; 96 | } 97 | else { 98 | if (state === void 0) { 99 | state = element; 100 | return state; 101 | } 102 | else { 103 | stateAndReturnValue = binaryFn.call(element, state, element); 104 | state = stateAndReturnValue[0]; 105 | return stateAndReturnValue[1]; 106 | } 107 | } 108 | }; 109 | } 110 | 111 | function map (iter, unaryFn) { 112 | return function() { 113 | var element; 114 | element = iter(); 115 | if (element != null) { 116 | return unaryFn.call(element, element); 117 | } else { 118 | return void 0; 119 | } 120 | }; 121 | } 122 | 123 | function mapcat(iter, unaryFn) { 124 | var lastIter = null; 125 | return function() { 126 | var element; 127 | var gen; 128 | if (lastIter == null) { 129 | gen = iter(); 130 | if (gen == null) { 131 | lastIter = null; 132 | return void 0; 133 | } 134 | lastIter = unaryFn.call(gen, gen); 135 | } 136 | while (element == null) { 137 | element = lastIter(); 138 | if (element == null) { 139 | gen = iter(); 140 | if (gen == null) { 141 | lastIter = null; 142 | return void 0; 143 | } 144 | else { 145 | lastIter = unaryFn.call(gen, gen); 146 | } 147 | } 148 | } 149 | return element; 150 | }; 151 | } 152 | 153 | function select (iter, unaryPredicateFn) { 154 | return function() { 155 | var element; 156 | element = iter(); 157 | while (element != null) { 158 | if (unaryPredicateFn.call(element, element)) { 159 | return element; 160 | } 161 | element = iter(); 162 | } 163 | return void 0; 164 | }; 165 | } 166 | 167 | function reject (iter, unaryPredicateFn) { 168 | return select(iter, function (something) { 169 | return !unaryPredicateFn(something); 170 | }); 171 | } 172 | 173 | function find (iter, unaryPredicateFn) { 174 | return select(iter, unaryPredicateFn)(); 175 | } 176 | 177 | function slice (iter, numberToDrop, numberToTake) { 178 | var count = 0; 179 | while (numberToDrop-- > 0) { 180 | iter(); 181 | } 182 | if (numberToTake != null) { 183 | return function() { 184 | if (++count <= numberToTake) { 185 | return iter(); 186 | } else { 187 | return void 0; 188 | } 189 | }; 190 | } 191 | else return iter; 192 | } 193 | 194 | function drop (iter, numberToDrop) { 195 | return slice(iter, numberToDrop == null ? 1 : numberToDrop); 196 | } 197 | 198 | function take (iter, numberToTake) { 199 | return slice(iter, 0, numberToTake == null ? 1 : numberToTake); 200 | } 201 | 202 | function List (array) { 203 | var index = 0; 204 | return function() { 205 | return array[index++]; 206 | }; 207 | } 208 | 209 | function Tree (array) { 210 | var index, myself, state; 211 | index = 0; 212 | state = []; 213 | myself = function() { 214 | var element, tempState; 215 | element = array[index++]; 216 | if (element instanceof Array) { 217 | state.push({ 218 | array: array, 219 | index: index 220 | }); 221 | array = element; 222 | index = 0; 223 | return myself(); 224 | } else if (element === void 0) { 225 | if (state.length > 0) { 226 | tempState = state.pop(); 227 | array = tempState.array; 228 | index = tempState.index; 229 | return myself(); 230 | } else { 231 | return void 0; 232 | } 233 | } else { 234 | return element; 235 | } 236 | }; 237 | return myself; 238 | } 239 | 240 | function K (value) { 241 | return function () { 242 | return value; 243 | }; 244 | } 245 | 246 | function upRange (from, to, by) { 247 | return function () { 248 | var was; 249 | 250 | if (from > to) { 251 | return void 0; 252 | } 253 | else { 254 | was = from; 255 | from = from + by; 256 | return was; 257 | } 258 | }; 259 | } 260 | 261 | function downRange (from, to, by) { 262 | return function () { 263 | var was; 264 | 265 | if (from < to) { 266 | return void 0; 267 | } 268 | else { 269 | was = from; 270 | from = from - by; 271 | return was; 272 | } 273 | }; 274 | } 275 | 276 | function range (from, to, by) { 277 | if (from == null) { 278 | return upRange(1, Infinity, 1); 279 | } 280 | else if (to == null) { 281 | return upRange(from, Infinity, 1); 282 | } 283 | else if (by == null) { 284 | if (from <= to) { 285 | return upRange(from, to, 1); 286 | } 287 | else return downRange(from, to, 1); 288 | } 289 | else if (by > 0) { 290 | return upRange(from, to, by); 291 | } 292 | else if (by < 0) { 293 | return downRange(from, to, Math.abs(by)); 294 | } 295 | else return k(from); 296 | } 297 | 298 | var numbers = unary(range); 299 | 300 | _.iterators = { 301 | accumulate: accumulate, 302 | accumulateWithReturn: accumulateWithReturn, 303 | foldl: foldl, 304 | reduce: foldl, 305 | unfold: unfold, 306 | unfoldWithReturn: unfoldWithReturn, 307 | map: map, 308 | mapcat: mapcat, 309 | select: select, 310 | reject: reject, 311 | filter: select, 312 | find: find, 313 | slice: slice, 314 | drop: drop, 315 | take: take, 316 | List: List, 317 | Tree: Tree, 318 | constant: K, 319 | K: K, 320 | numbers: numbers, 321 | range: range 322 | }; 323 | 324 | }; 325 | -------------------------------------------------------------------------------- /common-js/_.function.predicates.js: -------------------------------------------------------------------------------- 1 | module.exports = function (_) { 2 | 3 | // Mixing in the predicate functions 4 | // --------------------------------- 5 | 6 | _.mixin({ 7 | // A wrapper around instanceof 8 | isInstanceOf: function(x, t) { return (x instanceof t); }, 9 | 10 | // An associative object is one where its elements are 11 | // accessed via a key or index. (i.e. array and object) 12 | isAssociative: function(x) { return _.isArray(x) || _.isObject(x) || _.isArguments(x); }, 13 | 14 | // An indexed object is anything that allows numerical index for 15 | // accessing its elements (e.g. arrays and strings). NOTE: lodash 16 | // does not support cross-browser consistent use of strings as array-like 17 | // objects, so be wary in IE 8 when using String objects and IE<8. 18 | // on string literals & objects. 19 | isIndexed: function(x) { return _.isArray(x) || _.isString(x) || _.isArguments(x); }, 20 | 21 | // A seq is something considered a sequential composite type (i.e. arrays and `arguments`). 22 | isSequential: function(x) { return (_.isArray(x)) || (_.isArguments(x)); }, 23 | 24 | // These do what you think that they do 25 | isZero: function(x) { return 0 === x; }, 26 | isEven: function(x) { return _.isFinite(x) && (x & 1) === 0; }, 27 | isOdd: function(x) { return _.isFinite(x) && !_.isEven(x); }, 28 | isPositive: function(x) { return x > 0; }, 29 | isNegative: function(x) { return x < 0; }, 30 | isValidDate: function(x) { return _.isDate(x) && !_.isNaN(x.getTime()); }, 31 | 32 | // A numeric is a variable that contains a numeric value, regardless its type 33 | // It can be a String containing a numeric value, exponential notation, or a Number object 34 | // See here for more discussion: http://stackoverflow.com/questions/18082/validate-numbers-in-javascript-isnumeric/1830844#1830844 35 | isNumeric: function(n) { 36 | return !isNaN(parseFloat(n)) && isFinite(n); 37 | }, 38 | 39 | // An integer contains an optional minus sign to begin and only the digits 0-9 40 | // Objects that can be parsed that way are also considered ints, e.g. "123" 41 | // Floats that are mathematically equal to integers are considered integers, e.g. 1.0 42 | // See here for more discussion: http://stackoverflow.com/questions/1019515/javascript-test-for-an-integer 43 | isInteger: function(i) { 44 | return _.isNumeric(i) && i % 1 === 0; 45 | }, 46 | 47 | // A float is a numbr that is not an integer. 48 | isFloat: function(n) { 49 | return _.isNumeric(n) && !_.isInteger(n); 50 | }, 51 | 52 | // checks if a string is a valid JSON 53 | isJSON: function(str) { 54 | try { 55 | JSON.parse(str); 56 | } catch (e) { 57 | return false; 58 | } 59 | return true; 60 | }, 61 | 62 | // Returns true if its arguments are monotonically 63 | // increaing values; false otherwise. 64 | isIncreasing: function() { 65 | var count = _.size(arguments); 66 | if (count === 1) return true; 67 | if (count === 2) return arguments[0] < arguments[1]; 68 | 69 | for (var i = 1; i < count; i++) { 70 | if (arguments[i-1] >= arguments[i]) { 71 | return false; 72 | } 73 | } 74 | 75 | return true; 76 | }, 77 | 78 | // Returns true if its arguments are monotonically 79 | // decreaing values; false otherwise. 80 | isDecreasing: function() { 81 | var count = _.size(arguments); 82 | if (count === 1) return true; 83 | if (count === 2) return arguments[0] > arguments[1]; 84 | 85 | for (var i = 1; i < count; i++) { 86 | if (arguments[i-1] <= arguments[i]) { 87 | return false; 88 | } 89 | } 90 | 91 | return true; 92 | } 93 | }); 94 | 95 | }; 96 | -------------------------------------------------------------------------------- /common-js/_.object.builders.js: -------------------------------------------------------------------------------- 1 | module.exports = function (_) { 2 | 3 | // Helpers 4 | // ------- 5 | 6 | // Create quick reference variables for speed access to core prototypes. 7 | var slice = Array.prototype.slice, 8 | concat = Array.prototype.concat; 9 | 10 | var existy = function(x) { return x != null; }; 11 | var truthy = function(x) { return (x !== false) && existy(x); }; 12 | var isAssociative = function(x) { return _.isArray(x) || _.isObject(x); }; 13 | var curry2 = function(fun) { 14 | return function(last) { 15 | return function(first) { 16 | return fun(first, last); 17 | }; 18 | }; 19 | }; 20 | 21 | // Mixing in the object builders 22 | // ---------------------------- 23 | 24 | _.mixin({ 25 | // Takes an object and another object of strings to strings where the second 26 | // object describes the key renaming to occur in the first object. 27 | renameKeys: function(obj, kobj) { 28 | return _.reduce(kobj, function(o, nu, old) { 29 | if (existy(obj[old])) { 30 | o[nu] = obj[old]; 31 | return o; 32 | } 33 | else 34 | return o; 35 | }, 36 | _.omit.apply(null, concat.call([obj], _.keys(kobj)))); 37 | }, 38 | 39 | // Snapshots an object deeply. Based on the version by 40 | // [Keith Devens](http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone) 41 | // until we can find a more efficient and robust way to do it. 42 | snapshot: function(obj) { 43 | if(obj == null || typeof(obj) != 'object') { 44 | return obj; 45 | } 46 | 47 | var temp = new obj.constructor(); 48 | 49 | for(var key in obj) { 50 | if (obj.hasOwnProperty(key)) { 51 | temp[key] = _.snapshot(obj[key]); 52 | } 53 | } 54 | 55 | return temp; 56 | }, 57 | 58 | // Updates the value at any depth in a nested object based on the 59 | // path described by the keys given. The function provided is supplied 60 | // the current value and is expected to return a value for use as the 61 | // new value. If no keys are provided, then the object itself is presented 62 | // to the given function. 63 | updatePath: function(obj, fun, ks, defaultValue) { 64 | if (!isAssociative(obj)) throw new TypeError("Attempted to update a non-associative object."); 65 | if (!existy(ks)) return fun(obj); 66 | 67 | var deepness = _.isArray(ks); 68 | var keys = deepness ? ks : [ks]; 69 | var ret = deepness ? _.snapshot(obj) : _.clone(obj); 70 | var lastKey = _.last(keys); 71 | var target = ret; 72 | 73 | _.each(_.initial(keys), function(key) { 74 | if (defaultValue && !_.has(target, key)) { 75 | target[key] = _.clone(defaultValue); 76 | } 77 | target = target[key]; 78 | }); 79 | 80 | target[lastKey] = fun(target[lastKey]); 81 | return ret; 82 | }, 83 | 84 | // Sets the value at any depth in a nested object based on the 85 | // path described by the keys given. 86 | setPath: function(obj, value, ks, defaultValue) { 87 | if (!existy(ks)) throw new TypeError("Attempted to set a property at a null path."); 88 | 89 | return _.updatePath(obj, function() { return value; }, ks, defaultValue); 90 | }, 91 | 92 | // Returns an object where each element of an array is keyed to 93 | // the number of times that it occurred in said array. 94 | frequencies: curry2(_.countBy)(_.identity) 95 | }); 96 | 97 | }; 98 | -------------------------------------------------------------------------------- /common-js/_.object.selectors.js: -------------------------------------------------------------------------------- 1 | module.exports = function (_) { 2 | 3 | // Helpers 4 | // ------- 5 | 6 | // Create quick reference variables for speed access to core prototypes. 7 | var concat = Array.prototype.concat; 8 | var ArrayProto = Array.prototype; 9 | var slice = ArrayProto.slice; 10 | 11 | // Mixing in the object selectors 12 | // ------------------------------ 13 | 14 | _.mixin({ 15 | // Returns a function that will attempt to look up a named field 16 | // in any object that it's given. 17 | accessor: function(field) { 18 | return function(obj) { 19 | return (obj && obj[field]); 20 | }; 21 | }, 22 | 23 | // Given an object, returns a function that will attempt to look up a field 24 | // that it's given. 25 | dictionary: function (obj) { 26 | return function(field) { 27 | return (obj && field && obj[field]); 28 | }; 29 | }, 30 | 31 | // Like `_.pick` except that it takes an array of keys to pick. 32 | selectKeys: function (obj, ks) { 33 | return _.pick.apply(null, concat.call([obj], ks)); 34 | }, 35 | 36 | // Returns the key/value pair for a given property in an object, undefined if not found. 37 | kv: function(obj, key) { 38 | if (_.has(obj, key)) { 39 | return [key, obj[key]]; 40 | } 41 | 42 | return void 0; 43 | }, 44 | 45 | // Gets the value at any depth in a nested object based on the 46 | // path described by the keys given. Keys may be given as an array 47 | // or as a dot-separated string. 48 | getPath: function getPath (obj, ks) { 49 | if (typeof ks == "string") ks = ks.split("."); 50 | 51 | // If we have reached an undefined property 52 | // then stop executing and return undefined 53 | if (obj === undefined) return void 0; 54 | 55 | // If the path array has no more elements, we've reached 56 | // the intended property and return its value 57 | if (ks.length === 0) return obj; 58 | 59 | // If we still have elements in the path array and the current 60 | // value is null, stop executing and return undefined 61 | if (obj === null) return void 0; 62 | 63 | return getPath(obj[_.first(ks)], _.rest(ks)); 64 | }, 65 | 66 | // Returns a boolean indicating whether there is a property 67 | // at the path described by the keys given 68 | hasPath: function hasPath (obj, ks) { 69 | if (typeof ks == "string") ks = ks.split("."); 70 | 71 | var numKeys = ks.length; 72 | 73 | if (obj == null && numKeys > 0) return false; 74 | 75 | if (_.contains(['boolean', 'string', 'number'], typeof obj)) return false; 76 | 77 | if (!(ks[0] in obj)) return false; 78 | 79 | if (numKeys === 1) return true; 80 | 81 | return hasPath(obj[_.first(ks)], _.rest(ks)); 82 | }, 83 | 84 | pickWhen: function(obj, pred) { 85 | var copy = {}; 86 | 87 | _.each(obj, function(value, key) { 88 | if (pred(obj[key])) copy[key] = obj[key]; 89 | }); 90 | 91 | return copy; 92 | }, 93 | 94 | omitWhen: function(obj, pred) { 95 | return _.pickWhen(obj, function(e) { return !pred(e); }); 96 | } 97 | 98 | }); 99 | 100 | }; 101 | -------------------------------------------------------------------------------- /common-js/_.util.existential.js: -------------------------------------------------------------------------------- 1 | module.exports = function(_) { 2 | 3 | // Mixing in the truthiness 4 | // ------------------------ 5 | 6 | _.mixin({ 7 | exists: function(x) { return x != null; }, 8 | truthy: function(x) { return (x !== false) && _.exists(x); }, 9 | falsey: function(x) { return !_.truthy(x); }, 10 | not: function(b) { return !b; }, 11 | 12 | existsAll: function() { return _.every(arguments, _.exists); }, 13 | truthyAll: function() { return _.every(arguments, _.truthy); }, 14 | falseyAll: function() { return _.every(arguments, _.falsey); }, 15 | firstExisting: function() { 16 | for (var i = 0; i < arguments.length; i++) { 17 | if (_.exists(arguments[i])) return arguments[i]; 18 | } 19 | } 20 | }); 21 | 22 | }; 23 | 24 | -------------------------------------------------------------------------------- /common-js/_.util.operators.js: -------------------------------------------------------------------------------- 1 | module.exports = function (_) { 2 | 3 | // Setup for variadic operators 4 | // ---------------------------- 5 | 6 | // Turn a binary math operator into a variadic operator 7 | function variadicMath(operator) { 8 | return function() { 9 | return _.reduce(arguments, operator); 10 | }; 11 | } 12 | 13 | // Turn a binary comparator into a variadic comparator 14 | function variadicComparator(comparator) { 15 | return function() { 16 | var result; 17 | for (var i = 0; i < arguments.length - 1; i++) { 18 | result = comparator(arguments[i], arguments[i + 1]); 19 | if (result === false) return result; 20 | } 21 | return result; 22 | }; 23 | } 24 | 25 | // Turn a boolean-returning function into one with the opposite meaning 26 | function invert(fn) { 27 | return function() { 28 | return !fn.apply(this, arguments); 29 | }; 30 | } 31 | 32 | // Basic math operators 33 | function add(x, y) { 34 | return x + y; 35 | } 36 | 37 | function sub(x, y) { 38 | return x - y; 39 | } 40 | 41 | function mul(x, y) { 42 | return x * y; 43 | } 44 | 45 | function div(x, y) { 46 | return x / y; 47 | } 48 | 49 | function mod(x, y) { 50 | return x % y; 51 | } 52 | 53 | function inc(x) { 54 | return ++x; 55 | } 56 | 57 | function dec(x) { 58 | return --x; 59 | } 60 | 61 | function neg(x) { 62 | return -x; 63 | } 64 | 65 | // Bitwise operators 66 | function bitwiseAnd(x, y) { 67 | return x & y; 68 | } 69 | 70 | function bitwiseOr(x, y) { 71 | return x | y; 72 | } 73 | 74 | function bitwiseXor(x, y) { 75 | return x ^ y; 76 | } 77 | 78 | function bitwiseLeft(x, y) { 79 | return x << y; 80 | } 81 | 82 | function bitwiseRight(x, y) { 83 | return x >> y; 84 | } 85 | 86 | function bitwiseZ(x, y) { 87 | return x >>> y; 88 | } 89 | 90 | function bitwiseNot(x) { 91 | return ~x; 92 | } 93 | 94 | // Basic comparators 95 | function eq(x, y) { 96 | return x == y; 97 | } 98 | 99 | function seq(x, y) { 100 | return x === y; 101 | } 102 | 103 | // Not 104 | function not(x) { 105 | return !x; 106 | } 107 | 108 | // Relative comparators 109 | function gt(x, y) { 110 | return x > y; 111 | } 112 | 113 | function lt(x, y) { 114 | return x < y; 115 | } 116 | 117 | function gte(x, y) { 118 | return x >= y; 119 | } 120 | 121 | function lte(x, y) { 122 | return x <= y; 123 | } 124 | 125 | // Mixing in the operator functions 126 | // ----------------------------- 127 | 128 | _.mixin({ 129 | addContrib: variadicMath(add), 130 | sub: variadicMath(sub), 131 | mul: variadicMath(mul), 132 | div: variadicMath(div), 133 | mod: mod, 134 | inc: inc, 135 | dec: dec, 136 | neg: neg, 137 | eqContrib: variadicComparator(eq), 138 | seq: variadicComparator(seq), 139 | neq: invert(variadicComparator(eq)), 140 | sneq: invert(variadicComparator(seq)), 141 | not: not, 142 | gtContrib: variadicComparator(gt), 143 | ltContrib: variadicComparator(lt), 144 | gteContrib: variadicComparator(gte), 145 | lteContrib: variadicComparator(lte), 146 | bitwiseAnd: variadicMath(bitwiseAnd), 147 | bitwiseOr: variadicMath(bitwiseOr), 148 | bitwiseXor: variadicMath(bitwiseXor), 149 | bitwiseNot: bitwiseNot, 150 | bitwiseLeft: variadicMath(bitwiseLeft), 151 | bitwiseRight: variadicMath(bitwiseRight), 152 | bitwiseZ: variadicMath(bitwiseZ) 153 | }); 154 | }; 155 | -------------------------------------------------------------------------------- /common-js/_.util.strings.js: -------------------------------------------------------------------------------- 1 | module.exports = function (_) { 2 | 3 | // Helpers 4 | // ------- 5 | 6 | // No reason to create regex more than once 7 | var REGEX = { 8 | boundary: /(\b.)/g, 9 | bracket: /(?:([^\[]+))|(?:\[(.*?)\])/g, 10 | capitalLetters: /([A-Z])/g, 11 | dot: /\./g, 12 | htmlTags: /<\/?[^<>]*>/gi, 13 | lowerThenUpper: /([a-z])([A-Z])/g, 14 | nonCamelCase: /[-_\s](\w)/g, 15 | plus: /\+/g, 16 | regex: /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, 17 | space: / /g, 18 | underscore: /_/g, 19 | upperThenLower: /\b([A-Z]+)([A-Z])([a-z])/g 20 | }; 21 | 22 | var urlDecode = function (s) { 23 | return decodeURIComponent(s.replace(REGEX.plus, '%20')); 24 | }; 25 | 26 | var buildParams = function (prefix, val, top) { 27 | if (_.isUndefined(top)) top = true; 28 | if (_.isArray(val)) { 29 | return _.map(val, function (value, key) { 30 | return buildParams(top ? key : prefix + '[]', value, false); 31 | }).join('&'); 32 | } else if (_.isObject(val)) { 33 | return _.map(val, function (value, key) { 34 | return buildParams(top ? key : prefix + '[' + key + ']', value, false); 35 | }).join('&'); 36 | } else { 37 | return encodeURIComponent(prefix) + '=' + encodeURIComponent(val); 38 | } 39 | }; 40 | 41 | // Mixing in the string utils 42 | // ---------------------------- 43 | 44 | _.mixin({ 45 | // Explodes a string into an array of chars 46 | explode: function (s) { 47 | return s.split(''); 48 | }, 49 | 50 | // Parses a query string into a hash 51 | fromQuery: function (str) { 52 | var parameters = str.split('&'), 53 | obj = {}, 54 | parameter, 55 | key, 56 | match, 57 | lastKey, 58 | subKey, 59 | depth; 60 | 61 | // Iterate over key/value pairs 62 | _.each(parameters, function (parameter) { 63 | parameter = parameter.split('='); 64 | key = urlDecode(parameter[0]); 65 | lastKey = key; 66 | depth = obj; 67 | 68 | // Reset so we don't have issues when matching the same string 69 | REGEX.bracket.lastIndex = 0; 70 | 71 | // Attempt to extract nested values 72 | while ((match = REGEX.bracket.exec(key)) !== null) { 73 | if (!_.isUndefined(match[1])) { 74 | 75 | // If we're at the top nested level, no new object needed 76 | subKey = match[1]; 77 | 78 | } else { 79 | 80 | // If we're at a lower nested level, we need to step down, and make 81 | // sure that there is an object to place the value into 82 | subKey = match[2]; 83 | depth[lastKey] = depth[lastKey] || (subKey ? {} : []); 84 | depth = depth[lastKey]; 85 | } 86 | 87 | // Save the correct key as a hash or an array 88 | lastKey = subKey || _.size(depth); 89 | } 90 | 91 | // Assign value to nested object 92 | depth[lastKey] = urlDecode(parameter[1]); 93 | }); 94 | 95 | return obj; 96 | }, 97 | 98 | // Implodes and array of chars into a string 99 | implode: function (a) { 100 | return a.join(''); 101 | }, 102 | 103 | // Converts camel case to dashed (opposite of _.camelCase) 104 | toDash: function (string) { 105 | string = string.replace(REGEX.capitalLetters, function ($1) {return "-" + $1.toLowerCase();}); 106 | // remove first dash 107 | return ( string.charAt(0) == '-' ) ? string.substr(1) : string; 108 | }, 109 | 110 | // Creates a query string from a hash 111 | toQuery: function (obj) { 112 | return buildParams('', obj); 113 | }, 114 | 115 | // Reports whether a string contains a search string. 116 | strContains: function (str, search) { 117 | if (typeof str != 'string') throw new TypeError( 'First argument to strContains must be a string' ); 118 | return (str.indexOf(search) != -1); 119 | }, 120 | 121 | // Upper case first letter in every word. 122 | titleCase: function capitalize(string) { 123 | return string.replace(REGEX.boundary, function ($1) {return $1.toUpperCase();}); 124 | }, 125 | 126 | // Slugify a string. Makes lowercase, and converts dots and spaces to dashes. 127 | slugify: function (urlString) { 128 | return urlString.replace(REGEX.lowerThenUpper, '$1-$2') 129 | .replace(REGEX.space, '-') 130 | .replace(REGEX.dot, '-') 131 | .toLowerCase(); 132 | }, 133 | 134 | // Humanize a slug by adding spaces in place of underscores and between words 135 | humanize: function (slugish) { 136 | return _.capitalize(slugish 137 | // Replace _ with a space 138 | .replace(REGEX.underscore, ' ') 139 | // insert a space between lower & upper 140 | .replace(REGEX.lowerThenUpper, '$1 $2') 141 | // space before last upper in a sequence followed by lower 142 | .replace(REGEX.upperThenLower, '$1 $2$3') 143 | ); 144 | }, 145 | 146 | // Strip HTML-ish tags from string 147 | stripTags: function (suspectString) { 148 | var str = suspectString.replace(REGEX.htmlTags, ''); 149 | return str; 150 | } 151 | 152 | }); 153 | }; 154 | -------------------------------------------------------------------------------- /common-js/_.util.trampolines.js: -------------------------------------------------------------------------------- 1 | module.exports = function (_) { 2 | 3 | // Helpers 4 | // ------- 5 | 6 | 7 | // Mixing in the truthiness 8 | // ------------------------ 9 | 10 | _.mixin({ 11 | done: function(value) { 12 | var ret = _(value); 13 | ret.stopTrampoline = true; 14 | return ret; 15 | }, 16 | 17 | trampoline: function(fun /*, args */) { 18 | var result = fun.apply(fun, _.rest(arguments)); 19 | 20 | while (_.isFunction(result)) { 21 | result = result(); 22 | if ((result instanceof _) && (result.stopTrampoline)) break; 23 | } 24 | 25 | return result.value(); 26 | } 27 | }); 28 | 29 | }; 30 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lodash-contrib", 3 | "description": "The brass buckles on lodash's utility belt", 4 | "repo": "node4good/lodash-contrib", 5 | "version": "41200.0.0", 6 | "dependencies": { 7 | "lodash/lodash": "4.12.0" 8 | }, 9 | "main": "dist/lodash-contrib.js", 10 | "scripts": [ 11 | "dist/lodash-contrib.js" 12 | ], 13 | "license": "MIT" 14 | } 15 | -------------------------------------------------------------------------------- /docs/_.array.selectors.js.md: -------------------------------------------------------------------------------- 1 | ### array.selectors 2 | 3 | > Functions to take things from arrays. View Annotated Source 4 | 5 | -------------------------------------------------------------------------------- 6 | 7 | #### best 8 | 9 | **Signature:** `_.best(array:Array, fun:Function)` 10 | 11 | Returns the "best" value in an array based on the result of a given function. 12 | 13 | ```javascript 14 | _.best([1, 2, 3, 4, 5], function(x, y) { 15 | return x > y; 16 | }); 17 | //=> 5 18 | ``` 19 | 20 | -------------------------------------------------------------------------------- 21 | 22 | #### dropWhile 23 | 24 | **Signature:** `_.dropWhile(array:Array, pred:Function)` 25 | 26 | Drops elements for which the given function returns a truthy value. 27 | 28 | ```javascript 29 | _.dropWhile([-2,-1,0,1,2], isNeg); 30 | //=> [0,1,2] 31 | ``` 32 | 33 | -------------------------------------------------------------------------------- 34 | 35 | #### keep 36 | 37 | **Signature:** `_.keep(array:Array, fun:Function)` 38 | 39 | Returns an array of existy results of a function over a source array. 40 | 41 | ```javascript 42 | _.keep([1, 2, 3, 4, 5], function(val) { 43 | if (val % 3 === 0) { 44 | return val; 45 | } 46 | }); 47 | // => [3] 48 | ``` 49 | 50 | -------------------------------------------------------------------------------- 51 | 52 | #### nth 53 | 54 | **Signature:** `_.nth(array:Array, index:Number[, guard:Any])` 55 | 56 | The `_.nth` function is a convenience for the equivalent `array[n]`. The 57 | optional `guard` value allows `_.nth` to work correctly as a callback for 58 | `_.map`. 59 | 60 | ```javascript 61 | _.nth(['a','b','c'], 2); 62 | //=> 'c' 63 | ``` 64 | 65 | If given an index out of bounds then `_.nth` will return `undefined`: 66 | 67 | ```javascript 68 | _.nth(['a','b','c'], 2000); 69 | //=> undefined 70 | ``` 71 | 72 | The `_.nth` function can also be used in conjunction with `_.map` and `_.compact` like so: 73 | 74 | ```javascript 75 | var b = [['a'],['b'],[]]; 76 | 77 | _.compact(_.map(b, function(e) { return _.nth(e,0) })); 78 | //=> ['a','b'] 79 | ``` 80 | 81 | If wrapping a function around `_.nth` is too tedious or you'd like to partially apply the index then Underscore-contrib offers any of `_.flip2`, `_.fix` or `_.curryRight2` to solve this. 82 | 83 | -------------------------------------------------------------------------------- 84 | 85 | #### partitionBy 86 | 87 | **Signature:** `_.keep(array:Array, fun:Function)` 88 | 89 | Takes an array and partitions it into sub-arrays as the given predicate changes 90 | truth sense. 91 | 92 | ```javascript 93 | _.partitionBy([1,2,2,3,1,1,5], _.isEven); 94 | // => [[1],[2,2],[3,1,1,5]] 95 | 96 | _.partitionBy([1,2,2,3,1,1,5], _.identity); 97 | // => [[1],[2,2],[3],[1,1],[5]] 98 | ``` 99 | 100 | -------------------------------------------------------------------------------- 101 | 102 | #### second 103 | 104 | **Signature:** `_.second(array:Array, index:Number[, guard:Any])` 105 | 106 | The `_.second` function is a convenience for the equivalent `array[1]`. The 107 | optional `guard` value allows `_.second` to work correctly as a callback for 108 | `_.map`. 109 | 110 | ```javascript 111 | _.second(['a','b']); 112 | //=> 'b' 113 | 114 | _.map([['a','b'], _.range(10,20)], _.second); 115 | //=> ['b',11] 116 | ``` 117 | 118 | You can also pass an optional number to the `_.second` function to take a number of elements from an array starting with the second element and ending at the given index: 119 | 120 | ```javascript 121 | _.second(_.range(10), 5) 122 | //=> [1, 2, 3, 4] 123 | ``` 124 | 125 | -------------------------------------------------------------------------------- 126 | 127 | #### takeWhile 128 | 129 | **Signature:** `_.takeWhile(array:Array, pred:Function)` 130 | 131 | The `_.takeWhile` function takes an array and a function and returns a new array containing the first n elements in the original array for which the given function returns a truthy value: 132 | 133 | ```javascript 134 | var isNeg = function(n) { return n < 0; }; 135 | 136 | _.takeWhile([-2,-1,0,1,2], isNeg); 137 | //=> [-2,-1] 138 | ``` 139 | 140 | -------------------------------------------------------------------------------- 141 | 142 | #### third 143 | 144 | **Signature:** `_.third(array:Array, index:Number[, guard:Any])` 145 | 146 | The `_.third` function is a convenience for the equivalent `array[2]`. The 147 | optional `guard` value allows `_.third` to work correctly as a callback for 148 | `_.map`. 149 | 150 | ```javascript 151 | _.third(['a','b','c']); 152 | //=> 'c' 153 | 154 | _.map([['a','b','c'], _.range(10,20)], _.third); 155 | //=> ['c',12] 156 | ``` 157 | 158 | You can also pass an optional number to the `_.third` function to take a number of elements from an array starting with the third element and ending at the given index: 159 | 160 | ```javascript 161 | _.third(_.range(10), 5) 162 | //=> [2, 3, 4] 163 | ``` 164 | 165 | -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- /docs/_.collections.walk.js.md: -------------------------------------------------------------------------------- 1 | ### collections.walk 2 | 3 | > Functions to recursively walk collections which are trees. 4 | 5 | Documentation should use [Journo](https://github.com/jashkenas/journo) formats and standards. 6 | 7 | _.walk = walk; 8 | postorder: function(obj, visitor, context) 9 | preorder: function(obj, visitor, context) 10 | walk(obj, visitor, null, context) 11 | map: function(obj, strategy, visitor, context) 12 | pluck: function(obj, propertyName) 13 | pluckRec: function(obj, propertyName) 14 | _.walk.collect = _.walk.map; 15 | 16 | -------------------------------------------------------------------------------- /docs/_.function.arity.js.md: -------------------------------------------------------------------------------- 1 | ### function.arity 2 | 3 | > Functions which manipulate the way functions work with their arguments. 4 | 5 | -------------------------------------------------------------------------------- 6 | 7 | #### arity 8 | 9 | **Signature:** `_.arity(numberOfArgs:Number, fun:Function)` 10 | 11 | Returns a new function which is equivalent to `fun`, except that the new 12 | function's `length` property is equal to `numberOfArgs`. This does **not** limit 13 | the function to using that number of arguments. It's only effect is on the 14 | reported length. 15 | 16 | ```javascript 17 | function addAll() { 18 | var sum = 0; 19 | for (var i = 0; i < arguments.length; i++) { 20 | sum = sum + arguments[i]; 21 | } 22 | return sum; 23 | } 24 | 25 | addAll.length 26 | // => 0 27 | 28 | var addAllWithFixedLength = _.arity(2, addAll); 29 | 30 | addAllWithFixedLength.length 31 | // => 2 32 | 33 | addAllWithFixedLength(1, 1, 1, 1); 34 | // => 4 35 | ``` 36 | 37 | -------------------------------------------------------------------------------- 38 | 39 | #### binary 40 | 41 | **Signature:** `_.binary(fun:Function)` 42 | 43 | Returns a new function which accepts only two arguments and passes these 44 | arguments to `fun`. Additional arguments are discarded. 45 | 46 | ```javascript 47 | function addAll() { 48 | var sum = 0; 49 | for (var i = 0; i < arguments.length; i++) { 50 | sum = sum + arguments[i]; 51 | } 52 | return sum; 53 | } 54 | 55 | var add2 = _.binary(addAll); 56 | 57 | add2(1, 1); 58 | // => 2 59 | 60 | add2(1, 1, 1, 1); 61 | // => 2 62 | ``` 63 | 64 | -------------------------------------------------------------------------------- 65 | 66 | #### curry 67 | 68 | **Signature:** `_.curry(func:Function[, reverse:Boolean])` 69 | 70 | Returns a curried version of `func`. If `reverse` is true, arguments will be 71 | processed from right to left. 72 | 73 | ```javascript 74 | function add3 (x, y, z) { 75 | return x + y + z; 76 | } 77 | 78 | var curried = _.curry(add3); 79 | // => function 80 | 81 | curried(1); 82 | // => function 83 | 84 | curried(1)(2); 85 | // => function 86 | 87 | curried(1)(2)(3); 88 | // => 6 89 | ``` 90 | 91 | -------------------------------------------------------------------------------- 92 | 93 | #### curry2 94 | 95 | **Signature:** `_.curry2(fun:Function)` 96 | 97 | Returns a curried version of `func`, but will curry exactly two arguments, no 98 | more or less. 99 | 100 | ```javascript 101 | function add2 (a, b) { 102 | return a + b; 103 | } 104 | 105 | var curried = _.curry2(add2); 106 | // => function 107 | 108 | curried(1); 109 | // => function 110 | 111 | curried(1)(2); 112 | // => 3 113 | ``` 114 | 115 | -------------------------------------------------------------------------------- 116 | 117 | #### curry3 118 | 119 | **Signature:** `_.curry3(fun:Function)` 120 | 121 | Returns a curried version of `func`, but will curry exactly three arguments, no 122 | more or less. 123 | 124 | ```javascript 125 | function add3 (a, b, c) { 126 | return a + b + c; 127 | } 128 | 129 | var curried = _.curry3(add3); 130 | // => function 131 | 132 | curried(1); 133 | // => function 134 | 135 | curried(1)(2); 136 | // => function 137 | 138 | curried(1)(2)(3); 139 | // => 6 140 | ``` 141 | 142 | -------------------------------------------------------------------------------- 143 | 144 | #### curryRight 145 | 146 | **Signature:** `_.curryRight(func:Function)` 147 | 148 | **Aliases:** `_.rCurry` 149 | 150 | Returns a curried version of `func` where arguments are processed from right 151 | to left. 152 | 153 | ```javascript 154 | function divide (a, b) { 155 | return a / b; 156 | } 157 | 158 | var curried = _.curryRight(divide); 159 | // => function 160 | 161 | curried(1); 162 | // => function 163 | 164 | curried(1)(2); 165 | // => 2 166 | 167 | curried(2)(1); 168 | // => 0.5 169 | ``` 170 | 171 | -------------------------------------------------------------------------------- 172 | 173 | #### curryRight2 174 | 175 | **Signature:** `_.curryRight2(func:Function)` 176 | 177 | **Aliases:** `_.rcurry2` 178 | 179 | Returns a curried version of `func` where a maxium of two arguments are 180 | processed from right to left. 181 | 182 | ```javascript 183 | function concat () { 184 | var str = ""; 185 | 186 | for (var i = 0; i < arguments.length; i++) { 187 | str = str + arguments[i]; 188 | } 189 | 190 | return str; 191 | } 192 | 193 | var curried = _.curryRight2(concat); 194 | // => function 195 | 196 | curried("a"); 197 | // => function 198 | 199 | curried("a")("b"); 200 | // => "ba" 201 | 202 | ``` 203 | 204 | -------------------------------------------------------------------------------- 205 | 206 | #### curryRight3 207 | 208 | **Signature:** `_.curryRight3(func:Function)` 209 | 210 | **Aliases:** `_.rcurry3` 211 | 212 | Returns a curried version of `func` where a maxium of three arguments are 213 | processed from right to left. 214 | 215 | ```javascript 216 | function concat () { 217 | var str = ""; 218 | 219 | for (var i = 0; i < arguments.length; i++) { 220 | str = str + arguments[i]; 221 | } 222 | 223 | return str; 224 | } 225 | 226 | var curried = _.curryRight3(concat); 227 | // => function 228 | 229 | curried("a"); 230 | // => function 231 | 232 | curried("a")("b"); 233 | // => function 234 | 235 | curried("a")("b")("c"); 236 | // => "cba" 237 | 238 | ``` 239 | 240 | 241 | -------------------------------------------------------------------------------- 242 | 243 | #### fix 244 | 245 | **Signature:** `_.fix(fun:Function[, value:Any...])` 246 | 247 | Fixes the arguments to a function based on the parameter template defined by 248 | the presence of values and the `_` placeholder. 249 | 250 | ```javascript 251 | function add3 (a, b, c) { 252 | return a + b + c; 253 | } 254 | 255 | var fixedFirstAndLast = _.fix(add3, 1, _, 3); 256 | // => function 257 | 258 | fixedFirstAndLast(2); 259 | // => 6 260 | 261 | fixedFirstAndLast(10); 262 | // => 14 263 | ``` 264 | 265 | -------------------------------------------------------------------------------- 266 | 267 | #### quaternary 268 | 269 | **Signature:** `_.quaternary(fun:Function)` 270 | 271 | Returns a new function which accepts only four arguments and passes these 272 | arguments to `fun`. Additional arguments are discarded. 273 | 274 | ```javascript 275 | function addAll() { 276 | var sum = 0; 277 | for (var i = 0; i < arguments.length; i++) { 278 | sum = sum + arguments[i]; 279 | } 280 | return sum; 281 | } 282 | 283 | var add4 = _.quaternary(addAll); 284 | 285 | add4(1, 1, 1, 1); 286 | // => 4 287 | 288 | add4(1, 1, 1, 1, 1, 1); 289 | // => 4 290 | ``` 291 | 292 | -------------------------------------------------------------------------------- 293 | 294 | #### ternary 295 | 296 | **Signature:** `_.ternary(fun:Function)` 297 | 298 | Returns a new function which accepts only three arguments and passes these 299 | arguments to `fun`. Additional arguments are discarded. 300 | 301 | ```javascript 302 | function addAll() { 303 | var sum = 0; 304 | for (var i = 0; i < arguments.length; i++) { 305 | sum = sum + arguments[i]; 306 | } 307 | return sum; 308 | } 309 | 310 | var add3 = _.ternary(addAll); 311 | 312 | add3(1, 1, 1); 313 | // => 3 314 | 315 | add3(1, 1, 1, 1, 1, 1); 316 | // => 3 317 | ``` 318 | 319 | -------------------------------------------------------------------------------- 320 | 321 | #### unary 322 | 323 | **Signature:** `_.unary(fun:Function)` 324 | 325 | Returns a new function which accepts only one argument and passes this 326 | argument to `fun`. Additional arguments are discarded. 327 | 328 | ```javascript 329 | function logArgs() { 330 | console.log(arguments); 331 | } 332 | 333 | var logOneArg = _.unary(logArgs); 334 | 335 | logOneArg("first"); 336 | // => ["first"] 337 | 338 | logOneArg("first", "second"); 339 | // => ["first"] 340 | ``` 341 | 342 | -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- /docs/_.function.predicates.js.md: -------------------------------------------------------------------------------- 1 | ### function.predicates 2 | 3 | > Functions which return whether the input meets a condition. 4 | 5 | -------------------------------------------------------------------------------- 6 | 7 | #### isAssociative 8 | 9 | **Signature:** `isAssociative(value:Any)` 10 | 11 | Returns a boolean indicating whether or not the value is an associative object. 12 | An associative object is one where its elements can be accessed via a key or 13 | index (e.g. arrays, `arguments`, objects). 14 | 15 | ```javascript 16 | _.isAssociative(["Athens", "Sparta"]); 17 | // => true 18 | 19 | _.isAssociative(42); 20 | // => false 21 | ``` 22 | -------------------------------------------------------------------------------- 23 | 24 | #### isDecreasing 25 | 26 | **Signature:** `_.isDecreasing(values:Any...)` 27 | 28 | Checks whether the arguments are monotonically decreasing values (i.e. whether 29 | each argument is less than the previous argument.) 30 | 31 | ```javascript 32 | _.isDecreasing(3, 2, 1); 33 | // => true 34 | 35 | _.isDecreasing(15, 12, 2); 36 | // => true 37 | 38 | _.isDecreasing(2, 3); 39 | // => false 40 | ``` 41 | 42 | -------------------------------------------------------------------------------- 43 | 44 | #### isEven 45 | 46 | **Signature:** `_.isEven(value:Any)` 47 | 48 | Checks whether the value is an even number. 49 | 50 | ```javascript 51 | _.isEven(12); 52 | // => true 53 | 54 | _.isEven(3); 55 | // => false 56 | 57 | _.isEven({}); 58 | // => false 59 | ``` 60 | 61 | -------------------------------------------------------------------------------- 62 | 63 | #### isFloat 64 | 65 | **Signature:** `_.isFloat(value:Any)` 66 | 67 | Checks whether the value is a "float." For the purposes of this function, a float 68 | is a numeric value that is not an integer. A numeric value may be a number, a 69 | string containing a number, a `Number` object, etc. 70 | 71 | **NOTE:** JavaScript itself makes no distinction between integers and floats. For 72 | the purposes of this function both `1` and `1.0` are considered integers. 73 | 74 | ```javascript 75 | _.isFloat(1.1); 76 | // => true 77 | 78 | _.isFloat(1); 79 | // => false 80 | 81 | _.isFloat(1.0); 82 | // => false 83 | 84 | _.isFloat("2.15"); 85 | // => true 86 | ``` 87 | 88 | -------------------------------------------------------------------------------- 89 | 90 | #### isIncreasing 91 | 92 | **Signature:** `_.isIncreasing(value:Any...)` 93 | 94 | Checks whether the arguments are monotonically increasing values (i.e. each 95 | argument is greater than the previous argument.) 96 | 97 | ```javascript 98 | _.isIncreasing(1, 12, 15); 99 | // => true 100 | 101 | _.isIncreasing(1); 102 | // => true 103 | 104 | _.isIncreasing(5, 4); 105 | // => false 106 | ``` 107 | 108 | -------------------------------------------------------------------------------- 109 | 110 | #### isIndexed 111 | 112 | **Signature:** `_.isIndexed(value:Any)` 113 | 114 | Checks whether the value is "indexed." An indexed value is one which accepts a 115 | numerical index to access its elements. (e.g. arrays and strings) 116 | 117 | **NOTE:** lodash does not support cross-browser consistent use of strings as 118 | array-like values, so be wary in IE 8 when using string objects and in IE7 and 119 | earlier when using string literals & objects. 120 | 121 | ```javascript 122 | _.isIndexed("Socrates"); 123 | // => true 124 | 125 | _.isIndexed({poison: "hemlock"}); 126 | // => false 127 | ``` 128 | 129 | -------------------------------------------------------------------------------- 130 | 131 | #### isInstanceOf 132 | 133 | **Signature:** `_.isInstanceOf(value:Any, constructor:Function)` 134 | 135 | Checks whether the value is an instance of the constructor. 136 | 137 | ```javascript 138 | _.isInstanceOf(new Date(), Date); 139 | // => true 140 | 141 | _.isInstanceOf("Hippocrates", RegExp); 142 | // => false 143 | ``` 144 | 145 | -------------------------------------------------------------------------------- 146 | 147 | #### isInteger 148 | 149 | **Signature:** `_.isInteger(value:Any)` 150 | 151 | Checks whether the value is a numeric integer. A numeric value can be a string 152 | containing a number, a `Number` object, etc. 153 | 154 | ```javascript 155 | _.isInteger(18); 156 | // => true 157 | 158 | _.isInteger("18"); 159 | // => true 160 | 161 | _.isInteger(2.5); 162 | // => false 163 | 164 | _.isInteger(-1); 165 | // => true 166 | ``` 167 | 168 | -------------------------------------------------------------------------------- 169 | 170 | #### isJSON 171 | 172 | **Signature:** `_.isJSON(value:Any)` 173 | 174 | Checks whether the value is valid JSON. [See the spec](http://www.json.org/) for 175 | more information on what constitutes valid JSON. 176 | 177 | **NOTE:** This function relies on `JSON.parse` which is not available in IE7 and 178 | earlier. 179 | 180 | ```javascript 181 | _.isJSON('{ "name": "Crockford" }'); 182 | // => true 183 | 184 | _.isJSON({ name: "Crocket" }); 185 | // => false 186 | ``` 187 | 188 | -------------------------------------------------------------------------------- 189 | 190 | #### isNegative 191 | 192 | **Signature:** `_.isNegative(value:Any)` 193 | 194 | Checks whether the value is a negative number. 195 | 196 | ```javascript 197 | _.isNegative(-2); 198 | // => true 199 | 200 | _.isNegative(5); 201 | // => false 202 | ``` 203 | 204 | -------------------------------------------------------------------------------- 205 | 206 | #### isNumeric 207 | 208 | **Signature:** `_.isNumeric(value:Any)` 209 | 210 | A numeric is something that contains a numeric value, regardless of its type. It 211 | can be a string containing a numeric value, exponential notation, a `Number` 212 | object, etc. 213 | 214 | ```javascript 215 | _.isNumeric("14"); 216 | // => true 217 | 218 | _.isNumeric("fourteen"); 219 | // => false 220 | ``` 221 | 222 | -------------------------------------------------------------------------------- 223 | 224 | #### isOdd 225 | 226 | **Signature:** `_.isOdd(value:Any)` 227 | 228 | Checks whether the value is an odd number. 229 | 230 | ```javascript 231 | _.isOdd(3); 232 | // => true 233 | 234 | _.isOdd(2); 235 | // => false 236 | 237 | _.isOdd({}); 238 | // => false 239 | ``` 240 | 241 | -------------------------------------------------------------------------------- 242 | 243 | #### isPlainObject 244 | 245 | **Signature:** `_.isPlainObject(value:Any);` 246 | 247 | Checks whether the value is a "plain" object created as an object literal (`{}`) 248 | or explicitly constructed with `new Object()`. Instances of other constructors 249 | are **not** plain objects. 250 | 251 | ```javascript 252 | _.isPlainObject({}); 253 | // => true 254 | 255 | _.isPlainObject(new Date()); 256 | // => false 257 | 258 | _.isPlainObject([]); 259 | // => false 260 | ``` 261 | 262 | -------------------------------------------------------------------------------- 263 | 264 | #### isPositive 265 | 266 | **Signature:** `_.isPositive(value:Any)` 267 | 268 | Checks whether the value is a positive number. 269 | 270 | ```javascript 271 | _.isPositive(21); 272 | // => true 273 | 274 | _.isPositive(-3); 275 | // => false 276 | ``` 277 | 278 | -------------------------------------------------------------------------------- 279 | 280 | #### isSequential 281 | 282 | **Signature:** `_.isSequential(value:Any)` 283 | 284 | Checks whether the value is a sequential composite type (i.e. arrays and 285 | `arguments`). 286 | 287 | ```javascript 288 | _.isSequential(["Herodotus", "Thucidydes"); 289 | // => true 290 | 291 | _.isSequential(new Date); 292 | // => false 293 | ``` 294 | 295 | -------------------------------------------------------------------------------- 296 | 297 | #### isValidDate 298 | 299 | **Signature:** `_.isValidDate(value:Any)` 300 | 301 | Checks whether the value is a valid date. That is, the value is both an instance 302 | of `Date` and it represents an actual date. 303 | 304 | Warning: This function does not verify 305 | whether the original input to `Date` is a real date. For instance, 306 | `new Date("02/30/2014")` is considered a valid date because `Date` coerces that 307 | into a representation of 03/02/2014. To validate strings representing a date, 308 | consider using a date/time library like [Moment.js.][momentjs] 309 | 310 | ```javascript 311 | _.isValidDate(new Date("January 1, 1900")); 312 | // => true 313 | 314 | _.isValidDate(new Date("The Last Great Time War")); 315 | // => false 316 | ``` 317 | 318 | -------------------------------------------------------------------------------- 319 | 320 | #### isZero 321 | 322 | **Signature:** `_.isZero(value:Any)` 323 | 324 | Checks whether the value is `0`. 325 | 326 | ```javascript 327 | _.isZero(0); 328 | // => true 329 | 330 | _.isZero("Pythagoras"); 331 | // => false 332 | ``` 333 | 334 | -------------------------------------------------------------------------------- 335 | 336 | [momentjs]:http://momentjs.com/ -------------------------------------------------------------------------------- /docs/_.object.builders.js.md: -------------------------------------------------------------------------------- 1 | ### object.builders 2 | 3 | > Functions to build objects. 4 | 5 | -------------------------------------------------------------------------------- 6 | 7 | #### frequencies 8 | 9 | **Signature:** `_.frequencies(arr:Array)` 10 | 11 | Returns an object whose property keys are the values of `arr`'s elements. The 12 | property values are a count of how many times that value appeared in `arr`. 13 | 14 | ```javascript 15 | var citations = ["Plato", "Aristotle", "Plotinus", "Plato"]; 16 | 17 | _.frequencies(citations); 18 | // => { Plato: 2, Aristotle: 1, Plotinus: 1 } 19 | ``` 20 | 21 | -------------------------------------------------------------------------------- 22 | 23 | #### merge 24 | 25 | **Signature:** `_.merge(obj1:Object[, obj:Object...])` 26 | 27 | Merges two or more objects starting with the left-most and applying the keys 28 | rightward. 29 | 30 | ```javascript 31 | _.merge({ a: "alpha" }, { b: "beta" }); 32 | // => { a: "alpha", b: "beta" } 33 | ``` 34 | 35 | -------------------------------------------------------------------------------- 36 | 37 | #### renameKeys 38 | 39 | **Signature:** `_.renameKeys(obj:Object, keyMap:Object)` 40 | 41 | Takes an object (`obj`) and a map of keys (`keyMap`) and returns a new object 42 | where the keys of `obj` have been renamed as specified in `keyMap`. 43 | 44 | ```javascript 45 | _.renameKeys({ a: 1, b: 2 }, { a: "alpha", b: "beta" }); 46 | // => { alpha: 1, beta: 2 } 47 | ``` 48 | 49 | -------------------------------------------------------------------------------- 50 | 51 | #### setPath 52 | 53 | **Signature:** `_.setPath(obj:Object, value:Any, ks:Array, defaultValue:Any)` 54 | 55 | Sets the value of a property at any depth in `obj` based on the path described 56 | by the `ks` array. If any of the properties in the `ks` path don't exist, they 57 | will be created with `defaultValue`. 58 | 59 | See `_.updatePath` about `obj` not being mutated in the process by cloning it. 60 | 61 | ```javascript 62 | _.setPath({}, "Plotinus", ["Platonism", "Neoplatonism"], {}); 63 | // => { Platonism: { Neoplatonism: "Plotinus" } } 64 | ``` 65 | 66 | -------------------------------------------------------------------------------- 67 | 68 | #### snapshot 69 | 70 | **Signature:** `_.snapshot(obj:Object)` 71 | 72 | Snapshots/clones an object deeply. 73 | 74 | ```javascript 75 | var schools = { plato: "Academy", aristotle: "Lyceum" }; 76 | 77 | _.snapshot(schools); 78 | // => { plato: "Academy", aristotle: "Lyceum" } 79 | 80 | schools === _.snapshot(schools); 81 | // => false 82 | ``` 83 | 84 | -------------------------------------------------------------------------------- 85 | 86 | **Signature:** `_.updatePath(obj:Object, fun:Function, ks:Array, defaultValue:Any)` 87 | 88 | Updates the value at any depth in a nested object based on the path described by 89 | the `ks` array. The function `fun` is called with the current value and is 90 | expected to return a replacement value. If no keys are provided, then the 91 | object itself is presented to `fun`. If a property in the path is missing, then 92 | it will be created with `defaultValue`. 93 | 94 | Note that the original object will *not* be mutated. Instead, `obj` will 95 | be cloned deeply. 96 | 97 | ```javascript 98 | var imperialize = function (val) { 99 | if (val == "Republic") return "Empire"; 100 | else return val; 101 | }; 102 | 103 | _.updatePath({ rome: "Republic" }, imperialize, ["rome"]); 104 | // => { rome: "Empire" } 105 | 106 | var obj = { earth: { rome: "Republic" } }; 107 | var imperialObj = _.updatePath(obj, imperialize, ["earth", "rome"]); 108 | 109 | imperialObj; 110 | // => { earth: { rome: "Empire" }} 111 | 112 | obj; 113 | // => { earth: { rome: "Republic" }} 114 | 115 | obj === imperialObj; 116 | // => false 117 | ``` 118 | -------------------------------------------------------------------------------- /docs/_.object.selectors.js.md: -------------------------------------------------------------------------------- 1 | ### object.selectors 2 | 3 | > Functions to select values from an object. 4 | 5 | -------------------------------------------------------------------------------- 6 | 7 | #### accessor 8 | 9 | **Signature:** `_.accessor(field:String)` 10 | 11 | Returns a function that will attempt to look up a named field in any object 12 | that it is given. 13 | 14 | ```javascript 15 | var getName = _.accessor('name'); 16 | 17 | getName({ name: 'Seneca' }); 18 | // => 'Seneca' 19 | ``` 20 | 21 | -------------------------------------------------------------------------------- 22 | 23 | #### dictionary 24 | 25 | **Signature:** `_.dictionary(obj:Object)` 26 | 27 | Given an object, returns a function that will attempt to look up a field that 28 | it is given. 29 | 30 | ```javascript 31 | var generals = { 32 | rome: "Scipio", 33 | carthage: "Hannibal" 34 | }; 35 | 36 | var getGeneralOf = _.dictionary(generals); 37 | 38 | getGeneralOf("rome"); 39 | // => "Scipio" 40 | ``` 41 | 42 | -------------------------------------------------------------------------------- 43 | 44 | #### getPath 45 | 46 | **Signature:** `_.getPath(obj:Object, ks:String|Array)` 47 | 48 | Gets the value at any depth in a nested object based on the path described by 49 | the keys given. Keys may be given as an array or as a dot-separated string. 50 | Returns `undefined` if the path cannot be reached. 51 | 52 | ```javascript 53 | var countries = { 54 | greece: { 55 | athens: { 56 | playwright: "Sophocles" 57 | } 58 | } 59 | } 60 | }; 61 | 62 | _.getPath(countries, "greece.athens.playwright"); 63 | // => "Sophocles" 64 | 65 | _.getPath(countries, "greece.sparta.playwright"); 66 | // => undefined 67 | 68 | _.getPath(countries, ["greece", "athens", "playwright"]); 69 | // => "Sophocles" 70 | 71 | _.getPath(countries, ["greece", "sparta", "playwright"]); 72 | // => undefined 73 | ``` 74 | 75 | -------------------------------------------------------------------------------- 76 | 77 | #### hasPath 78 | 79 | **Signature:** `_.hasPath(obj:Object, ks:String|Array)` 80 | 81 | Returns a boolean indicating whether there is a property at the path described 82 | by the keys given. Keys may be given as an array or as a dot-separated string. 83 | 84 | ```javascript 85 | var countries = { 86 | greece: { 87 | athens: { 88 | playwright: "Sophocles" 89 | } 90 | } 91 | } 92 | }; 93 | 94 | _.hasPath(countries, "greece.athens.playwright"); 95 | // => true 96 | 97 | _.hasPath(countries, "greece.sparta.playwright"); 98 | // => false 99 | 100 | _.hasPath(countries, ["greece", "athens", "playwright"]); 101 | // => true 102 | 103 | _.hasPath(countries, ["greece", "sparta", "playwright"]); 104 | // => false 105 | ``` 106 | 107 | -------------------------------------------------------------------------------- 108 | 109 | #### kv 110 | 111 | **Signature:** `_.kv(obj:Object, key:String)` 112 | 113 | Returns the key/value pair for a given property in an object, undefined if not found. 114 | 115 | ```javascript 116 | var playAuthor = { 117 | "Medea": "Aeschylus" 118 | }; 119 | 120 | _.kv(playAuthor, "Medea"); 121 | // => ["Medea", "Aeschylus"] 122 | 123 | _.kv(playAuthor, "Hamlet"); 124 | // => undefined 125 | ``` 126 | 127 | -------------------------------------------------------------------------------- 128 | 129 | #### omitWhen 130 | 131 | **Signature:** `_.omitWhen(obj, pred:Function)` 132 | 133 | Returns a copy of `obj` omitting any properties that the predicate (`pred`) 134 | function returns `true` for. The predicat function is invoked with each 135 | property value, like so: `pred(propValue)`. 136 | 137 | ```javascript 138 | var playwrights = { 139 | euripedes: "Greece", 140 | shakespere: "England" 141 | }; 142 | 143 | _.omitWhen(obj, function (country) { return country == "England" }); 144 | // => { euripedes: "Greece" } 145 | ``` 146 | 147 | -------------------------------------------------------------------------------- 148 | 149 | #### pickWhen 150 | 151 | **Signature:** `_.pickWhen(obj:Object, pred:Function)` 152 | 153 | Returns a copy of `obj` containing only properties that the predicate (`pred`) 154 | function returns `true` for. The predicate function is invoked with each 155 | property value, like so: `pred(propValue)`. 156 | 157 | ```javascript 158 | var playwrights = { 159 | euripedes: "Greece", 160 | shakespere: "England" 161 | }; 162 | 163 | _.pickWhen(obj, function (country) { return country == "England" }); 164 | // => { shakespeare: "England" } 165 | ``` 166 | 167 | -------------------------------------------------------------------------------- 168 | 169 | #### selectKeys 170 | 171 | **Signature:** `_.selectKeys(obj:Object, ks:Array); 172 | 173 | Returns a copy of `obj` containing only the properties listed in the `ks` array. 174 | 175 | ```javascript 176 | var philosopherCities = { 177 | Philo: "Alexandria", 178 | Plato: "Athens", 179 | Plotinus: "Rome" 180 | } 181 | 182 | _.selectKeys(philosopherCities, ["Plato", "Plotinus"]); 183 | // => { Plato: "Athens", Plotinus: "Rome" } 184 | ``` 185 | -------------------------------------------------------------------------------- /docs/_.util.existential.js.md: -------------------------------------------------------------------------------- 1 | ### util.existential 2 | 3 | > Functions which deal with whether a value "exists." 4 | 5 | -------------------------------------------------------------------------------- 6 | 7 | #### exists 8 | 9 | **Signature:** `_.exists(value:Any)` 10 | 11 | Checks whether or not the value is "existy." Both `null` and `undefined` are 12 | considered non-existy values. All other values are existy. 13 | 14 | ```javascript 15 | _.exists(null); 16 | // => false 17 | 18 | _.exists(undefined); 19 | // => false 20 | 21 | _.exists({}); 22 | // = > true 23 | 24 | _.exists("Sparta"); 25 | // => true 26 | ``` 27 | 28 | -------------------------------------------------------------------------------- 29 | 30 | #### falsey 31 | 32 | **Signature:** `_.falsey(value:Any)` 33 | 34 | Checks whether the value is falsey. A falsey value is one which coerces to 35 | `false` in a boolean context. 36 | 37 | ```javascript 38 | _.falsey(0); 39 | // => true 40 | 41 | _.falsey(""); 42 | // => true 43 | 44 | _.falsey({}); 45 | // => false 46 | 47 | _.falsey("Corinth"); 48 | // => false 49 | ``` 50 | 51 | -------------------------------------------------------------------------------- 52 | 53 | #### firstExisting 54 | 55 | **Signature:** `_.firstExisting(value:Any[, value:Any...])` 56 | 57 | Returns the first existy argument from the argument list. 58 | 59 | ```javascript 60 | _.firstExisting("Socrates", "Plato"); 61 | // => "Socrates" 62 | 63 | _.firstExisting(null, undefined, "Heraclitus"); 64 | // => "Heraclitus" 65 | ``` 66 | 67 | -------------------------------------------------------------------------------- 68 | 69 | #### not 70 | 71 | **Signature:** `_.not(value:Any)` 72 | 73 | Returns a boolean which is the opposite of the truthiness of the original value. 74 | 75 | ```javascript 76 | _.not(0); 77 | // => true 78 | 79 | _.not(1); 80 | // => false 81 | 82 | _.not(true); 83 | // => false 84 | 85 | _.not(false); 86 | // => true 87 | 88 | _.not({}); 89 | // => false 90 | 91 | _.not(null); 92 | // => true 93 | ``` 94 | 95 | -------------------------------------------------------------------------------- 96 | 97 | #### truthy 98 | 99 | **Signature:** `_.truthy(value:Any)` 100 | 101 | Checks whether the value is truthy. A truthy value is one which coerces to 102 | `true` in a boolean context. 103 | 104 | ```javascript 105 | _.truthy({}); 106 | // => true 107 | 108 | _.truthy("Athens"); 109 | // => true 110 | 111 | _.truthy(0); 112 | // => false 113 | 114 | _.truthy(""); 115 | // => false 116 | ``` 117 | 118 | -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- /docs/_.util.strings.js.md: -------------------------------------------------------------------------------- 1 | ### util.strings 2 | 3 | > Functions for working with strings. 4 | 5 | -------------------------------------------------------------------------------- 6 | 7 | #### camelCase 8 | 9 | **Signature:** `_.camelCase(string:String)` 10 | 11 | Converts a dash-separated string to camel case. Opposite of [toDash](#todash). 12 | 13 | ```javascript 14 | _.camelCase("ancient-greece"); 15 | // => "ancientGreece" 16 | ``` 17 | 18 | -------------------------------------------------------------------------------- 19 | 20 | #### explode 21 | 22 | **Signature:** `_.explode(s:String)` 23 | 24 | Explodes a string into an array of characters. Opposite of [implode](#implode). 25 | 26 | ```javascript 27 | _.explode("Plato"); 28 | // => ["P", "l", "a", "t", "o"] 29 | ``` 30 | 31 | -------------------------------------------------------------------------------- 32 | 33 | #### fromQuery 34 | 35 | **Signature:** `_.fromQuery(str:String)` 36 | 37 | Takes a URL query string and converts it into an equivalent JavaScript object. 38 | Opposite of [toQuery](#toquery) 39 | 40 | ```javascript 41 | _.fromQuery("forms%5Bperfect%5D=circle&forms%5Bimperfect%5D=square"); 42 | // => { forms: { perfect: "circle", imperfect: "square" } } 43 | ``` 44 | 45 | -------------------------------------------------------------------------------- 46 | 47 | #### implode 48 | 49 | **Signature:** `_.implode(a:Array)` 50 | 51 | Implodes an array of strings into a single string. Opposite of [explode](#explode). 52 | 53 | ```javascript 54 | _.implode(["H", "o", "m", "e", "r"]); 55 | // => "Homer" 56 | ``` 57 | 58 | -------------------------------------------------------------------------------- 59 | 60 | #### slugify 61 | 62 | **Signature:** `_.slugify(str:String)` 63 | 64 | Slugifies a string, converting spaces and dots to dashes and inserting dashes between words. 65 | 66 | ```javascript 67 | _.slugify("ExampleString.that-covers-it.all"); 68 | // => "example-string-that-covers-it-all" 69 | ``` 70 | 71 | -------------------------------------------------------------------------------- 72 | 73 | #### strContains 74 | 75 | **Signature:** `_.strContains(str:String, search:String)` 76 | 77 | Reports whether a string contains a search string. 78 | 79 | ```javascript 80 | _.strContains("Acropolis", "polis"); 81 | // => true 82 | ``` 83 | 84 | -------------------------------------------------------------------------------- 85 | 86 | #### toDash 87 | 88 | **Signature:** `_.toDash(string:String)` 89 | 90 | Converts a camel case string to a dashed string. Opposite of [camelCase](#camelcase). 91 | 92 | ```javascript 93 | _.toDash("thisIsSparta"); 94 | // => "this-is-sparta" 95 | ``` 96 | 97 | -------------------------------------------------------------------------------- 98 | 99 | #### toQuery 100 | 101 | **Signature:** `_.toQuery(obj:Object)` 102 | 103 | Takes an object and converts it into an equivalent URL query string. Opposite 104 | of [fromQuery](#fromquery). 105 | 106 | ```javascript 107 | _.toQuery({ forms: { perfect: "circle", imperfect: "square" } }); 108 | // => "forms%5Bperfect%5D=circle&forms%5Bimperfect%5D=square" 109 | ``` 110 | 111 | -------------------------------------------------------------------------------- 112 | -------------------------------------------------------------------------------- /docs/_.util.trampolines.js.md: -------------------------------------------------------------------------------- 1 | ### util.trampolines 2 | 3 | > Trampoline functions. 4 | 5 | -------------------------------------------------------------------------------- 6 | 7 | #### done 8 | 9 | **Signature:** `_.done(value:Any)` 10 | 11 | A utility for wrapping a function's return values so they can be used by 12 | `_.trampoline`. [See below](#trampoline). 13 | 14 | -------------------------------------------------------------------------------- 15 | 16 | #### trampoline 17 | 18 | **Signature:** `_.trampoline(fun:Function[, args:Any...])` 19 | 20 | Provides a way of creating recursive functions that won't exceed a JavaScript 21 | engine's maximum recursion depth. Rather than writing a naive recursive 22 | function, the function's base cases must return `_.done(someValue)`, and 23 | recursive calls must be wrapped in a returned function. 24 | 25 | In order to create a trampolined function that can be used in the same way as 26 | a naive recursive function, use `_.partial` as illustrated below. 27 | 28 | ```javascript 29 | function isEvenNaive (num) { 30 | if (num === 0) return true; 31 | if (num === 1) return false; 32 | return isEvenNaive(num - 2); 33 | } 34 | 35 | isEvenNaive(99999); 36 | // => InternalError: too much recursion 37 | 38 | function isEvenInner (num) { 39 | if (num === 0) return _.done(true); 40 | if (num === 1) return _.done(false); 41 | return function () { return isEvenInner(Math.abs(num) - 2); }; 42 | } 43 | 44 | _.trampoline(isEvenInner, 99999); 45 | // => false 46 | 47 | var isEven = _.partial(_.trampoline, isEvenInner); 48 | 49 | isEven(99999); 50 | // => false 51 | ``` 52 | 53 | -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # lodash-contrib 2 | 3 | > The brass buckles on lodash's utility belt - a contributors' library for [lodash](http://lodash.com/). 4 | 5 | ## Introduction 6 | 7 | ### Places 8 | 9 | * [Documentation](#sub-libraries) 10 | * [Source repository](https://github.com/empeeric/lodash-contrib) 11 | * [Tickets and bug reports](https://github.com/empeeric/lodash-contrib/issues?state=open) 12 | 13 | ### Why lodash-contrib? 14 | 15 | While lodash provides a bevy of useful tools to support functional programming in JavaScript, it can't 16 | (and shouldn't) be everything to everyone. lodash-contrib is intended as a home for functions that, for 17 | various reasons, don't belong in lodash proper. In particular, it aims to be: 18 | 19 | * a home for functions that are limited in scope, but solve certain point problems, and 20 | * a proving ground for features that belong in lodash proper, but need some advocacy and/or evolution 21 | (or devolution) to get them there. 22 | 23 | ### Use 24 | 25 | #### In the Browser 26 | 27 | First, you’ll need lodash. Then you can grab the relevant lodash-contrib libraries and simply add 28 | the following to your pages: 29 | 30 | ```html 31 | 32 | 33 | ``` 34 | 35 | At the moment there are no cross-contrib dependencies (i.e. each sub-library 36 | can stand by itself), but that may change in the future. 37 | 38 | ### License 39 | 40 | lodash-contrib is open sourced under the [MIT license](https://github.com/Empeeric/lodash-contrib/blob/master/LICENSE). 41 | 42 | ## Sub-libraries 43 | 44 | The lodash-contrib library currently contains a number of related capabilities, aggregated into the following files. 45 | 46 | - [_.array.builders](_.array.builders.js.md#arraybuilders) - functions to build arrays 47 | - [_.array.selectors](_.array.selectors.js.md#arrayselectors) - functions to take things from arrays 48 | - [_.collections.walk](_.collections.walk.js.md#collectionswalk) - functions to walk and transform nested JavaScript objects 49 | - [_.function.arity](_.function.arity.js.md#functionarity) - functions to manipulate and fix function argument arity 50 | - [_.function.combinators](_.function.combinators.js.md#functioncombinators) - functions to combine functions to make new functions 51 | - [_.function.iterators](_.function.iterators.js.md#functioniterators) - functions to lazily produce, manipulate and consume sequence iterators 52 | - [_.function.predicates](_.function.predicates.js.md#functionpredicates) - functions that return `true` or `false` based on some criteria 53 | - [_.object.builders](_.object.builders.js.md#objectbuilders) - functions to build JavaScript objects 54 | - [_.object.selectors](_.object.selectors.js.md#objectselectors) - functions to pick things from JavaScript objects 55 | - [_.util.existential](_.util.existential.js.md#utilexistential) - functions that check for the existence or truthiness of JavaScript data types 56 | - [_.util.operators](_.util.operators.js.md#utiloperators) - functions that wrap common (or missing) JavaScript operators 57 | - [_.util.strings](_.util.strings.js.md#utilstrings) - functions to work with strings 58 | - [_.util.trampolines](_.util.trampolines.js.md#utiltrampolines) - functions to facilitate calling functions recursively without blowing the stack 59 | 60 | The links above are to the annotated source code. Full-blown _.contrib documentation is in the works. Contributors welcomed. 61 | 62 | 63 | -------------------------------------------------------------------------------- /examples/builders-examples.js.md: -------------------------------------------------------------------------------- 1 | Examples for builder functions 2 | =================== 3 | 4 | `_.object.builders.js` contains functions that are useful when small changes to JavaScript 5 | objects are needed. 6 | 7 | Each section gives use cases showing how a given function could be used: 8 | 9 | * [_.merge](#_merge-objs-) 10 | * [_.renameKeys](#_renamekeysobj-kobj) 11 | * [_.snapshot](#_snapshotobj) 12 | * [_.updatePath](#_updatepathobj-fun-ks-defaultvalue) 13 | * [_.setPath](#_setpathobj-value-ks-defaultvalue) 14 | * [_.frequencies](#_frequenciesobj) 15 | 16 | For some more insights have a look at [the tests](https://github.com/node4good/lodash-contrib/blob/master/test/object.builders.js). 17 | 18 | _.merge(/* objs */) 19 | ------------------- 20 | 21 | Merges two or more objects starting with the left-most and applying the keys right-word. 22 | 23 | **Arguments** 24 | 25 | 1. an arbitrary number of parameters consisting of JavaScript objects, all of which remain unchanged. 26 | 27 | **Returns** 28 | 29 | (Object): the merged object. 30 | 31 | **Example** 32 | 33 | ```javascript 34 | var merged = _.merge({a: 1, b: 1}, {a:2, c: 2}, {a: 3}); 35 | merged.a; // → 3 36 | merged.b; // → 1 37 | merged.c; // → 2 38 | ``` 39 | 40 | _.renameKeys(obj, kobj) 41 | ----------------------- 42 | 43 | Takes an object and another object of strings to strings where the second object describes 44 | the key renaming to occur in the first object. 45 | 46 | **Arguments** 47 | 48 | 1. `obj` that will be used as starting point, it remains unchanged. 49 | 2. `kobj` string to string mapping defining the renaming. 50 | 51 | **Returns** 52 | 53 | (Object): `obj` with renamed keys. 54 | 55 | **Example** 56 | 57 | ```javascript 58 | var obj = { 59 | id: 'tester@test.com', 60 | name: 'tester' 61 | }; 62 | 63 | var user = _.renameKeys(obj, { 'id': 'email'}); 64 | obj.id; // → 'tester@test.com' 65 | user.id; // → undefined 66 | user.email; // → 'tester@test.com' 67 | ``` 68 | 69 | _.snapshot(obj) 70 | --------------- 71 | 72 | Snapshots an object deeply. Based on the [version by Keith Devens](http://archive.today/FVuq2). 73 | 74 | **Arguments** 75 | 76 | 1. `obj` that will be snapshotted. 77 | 78 | **Returns** 79 | 80 | (Object): copy of `obj`. 81 | 82 | **Example** 83 | 84 | ```javascript 85 | var coordsFirstTick = {x: 1, y: 1}; 86 | var coordsSecondTick = _.snapshot(coordsFirstTick); 87 | coordsSecondTick.x = 2; 88 | console.log(coordsFirstTick.x); // → 1 89 | ``` 90 | 91 | _.updatePath(obj, fun, ks, defaultValue) 92 | ---------------------------------------- 93 | 94 | Updates the value at any depth in a nested object based on the path described by 95 | the keys given. The function provided is supplied the current value and is expected 96 | to return a value for use as the new value. If no keys are provided, then the object 97 | itself is presented to the given function. 98 | 99 | **Arguments** 100 | 101 | 1. `obj` that will be updated. 102 | 2. `fun` that will be applied on the last element of `ks`. 103 | 3. `ks` as a list of steps specifying the path to the attribute that will be updated. 104 | 4. `defaultValue` that will be used if the key does not exist in `obj`. 105 | 106 | **Returns** 107 | 108 | (Objct): `obj` with updated keys. 109 | 110 | **Example** 111 | 112 | ```javascript 113 | // we could either give a path to a value in a nested array 114 | var nested = [0, 1, [3, 3], 4]; 115 | _.updatePath(nested, _.always(2), [2, 0]); // → [0, 1, [2, 3], 4] 116 | nested; // → [0, 1, [3, 3], 4] 117 | 118 | // ...or we could pass an array of keys to traverse 119 | var nested = { 120 | one: { 121 | two: { 122 | three: 4 123 | } 124 | } 125 | }; 126 | _.updatePath(nested, _.always(3), ['one', 'two', 'three']).one.two.three; // → 3 127 | nested.one.two.three; // → 4 128 | ``` 129 | 130 | _.setPath(obj, value, ks, defaultValue) 131 | ---------------------------------------- 132 | 133 | Sets the value at any depth in a nested object based on the path described by the keys given. 134 | 135 | See [_.updatePath](#_updatepathobj-fun-ks-defaultvalue), this is just syntactic sugar that allows you to pass a value as second 136 | parameter, without having to wrap it into a function by yourself. 137 | 138 | 139 | _.frequencies(obj) 140 | ------------------------------ 141 | 142 | Returns an object where each element of an array is keyed to the number of times that 143 | it occurred in said array. 144 | 145 | **Arguments** 146 | 147 | 1. `obj` as array or object, as long as the object is not nested the expected object 148 | gets returned. 149 | 150 | **Returns** 151 | 152 | (Object): with the values as keys and the occurrencies of these as values. 153 | 154 | **Example** 155 | 156 | ```javascript 157 | _.frequencies([0, 2, 2, 7, 7, 7]); // → { 0: 1, 2: 2, 7: 3} 158 | _.frequencies({a: 1, b: 1, c: 2, d: 1}) // → {1: 3, 2: 1} 159 | ``` 160 | -------------------------------------------------------------------------------- /examples/walk-examples.js.md: -------------------------------------------------------------------------------- 1 | Examples for _.walk 2 | =================== 3 | 4 | The _.walk module (lodash.collections.walk.js) provides implementations of 5 | the [lodash collection functions](http://lodashjs.org/#collections) 6 | that are specialized for operating on nested JavaScript objects that form 7 | trees. 8 | 9 | Basic Traversal 10 | --------------- 11 | 12 | The most basic operation on a tree is to iterate through all its nodes, which 13 | is provided by `_.walk.preorder` and `_.walk.postorder`. They can be used in 14 | much the same way as [lodash's 'each' function][each]. For example, take 15 | a simple tree: 16 | 17 | [each]: http://lodashjs.org/#each 18 | 19 | var tree = { 20 | 'name': { 'first': 'Bucky', 'last': 'Fuller' }, 21 | 'occupations': ['designer', 'inventor'] 22 | }; 23 | 24 | We can do a preorder traversal of the tree: 25 | 26 | _.walk.preorder(tree, function(value, key, parent) { 27 | console.log(key + ': ' + value); 28 | }); 29 | 30 | which produces the following output: 31 | 32 | undefined: [object Object] 33 | name: [object Object] 34 | first: Bucky 35 | last: Fuller 36 | occupations: designer,inventor 37 | 0: designer 38 | 1: inventor 39 | 40 | A preorder traversal visits the nodes in the tree in a top-down fashion: first 41 | the root node is visited, then all of its child nodes are recursively visited. 42 | `_.walk.postorder` does the opposite, calling the visitor function for a node 43 | only after visiting all of its child nodes. 44 | 45 | Collection Functions 46 | -------------------- 47 | 48 | The \_.walk module provides versions of most of the 49 | [lodash collection functions](http://lodashjs.org/#collections), with 50 | some small differences that make them better suited for operating on trees. For 51 | example, you can use `_.walk.filter` to get a list of all the strings in a tree: 52 | 53 | _.walk.filter(tree, _.walk.preorder, _.isString); 54 | 55 | Like many other functions in _.walk, the argument to `filter` is a function 56 | indicating in what order the nodes should be visited. Currently, only 57 | `preorder` and `postorder` are supported. 58 | 59 | Custom Walkers 60 | -------------- 61 | 62 | Sometimes, you have a tree structure that can't be naively traversed. A good 63 | example of this is a DOM tree: because each element has a reference to its 64 | parent, a naive walk would encounter circular references. To handle such cases, 65 | you can create a custom walker by invoking `_.walk` as a function, and passing 66 | it a function which returns the descendants of a given node. E.g.: 67 | 68 | var domWalker = _.walk(function(el) { 69 | return el.children; 70 | }); 71 | 72 | The resulting object has the same functions as `_.walk`, but parameterized 73 | to use the custom walking behavior: 74 | 75 | var buttons = domWalker.filter(_.walk.preorder, function(el) { 76 | return el.tagName === 'BUTTON'; 77 | }); 78 | 79 | However, it's not actually necessary to create custom walkers for DOM nodes -- 80 | _.walk handles DOM nodes specially by default. 81 | 82 | Parse Trees 83 | ----------- 84 | 85 | A _parse tree_ is tree that represents the syntactic structure of a formal 86 | language. For example, the arithmetic expression `1 + (4 + 2) * 7` might have the 87 | following parse tree: 88 | 89 | var tree = { 90 | 'type': 'Addition', 91 | 'left': { 'type': 'Value', 'value': 1 }, 92 | 'right': { 93 | 'type': 'Multiplication', 94 | 'left': { 95 | 'type': 'Addition', 96 | 'left': { 'type': 'Value', 'value': 4 }, 97 | 'right': { 'type': 'Value', 'value': 2 } 98 | }, 99 | 'right': { 'type': 'Value', 'value': 7 } 100 | } 101 | }; 102 | 103 | We can create a custom walker for this parse tree: 104 | 105 | var parseTreeWalker = _.walk(function(node) { 106 | return _.pick(node, 'left', 'right'); 107 | }); 108 | 109 | Using the `find` function, we could find the first occurrence of the addition 110 | operator. It uses a pre-order traversal of the tree, so the following code 111 | will produce the root node (`tree`): 112 | 113 | parseTreeWalker.find(tree, function(node) { 114 | return node.type === 'Addition'; 115 | }); 116 | 117 | We could use the `reduce` function to evaluate the arithmetic expression 118 | represented by the tree. The following code will produce `43`: 119 | 120 | parseTreeWalker.reduce(tree, function(memo, node) { 121 | if (node.type === 'Value') return node.value; 122 | if (node.type === 'Addition') return memo.left + memo.right; 123 | if (node.type === 'Multiplication') return memo.left * memo.right; 124 | }); 125 | 126 | When the visitor function is called on a node, the `memo` argument contains 127 | the results of calling `reduce` on each of the node's subtrees. To evaluate a 128 | node, we just need to add or multiply the results of the left and right 129 | subtrees of the node. 130 | -------------------------------------------------------------------------------- /gh-pages/_.collections.walk.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _.collections.walk.js.md 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 | 96 | 97 | 136 |
137 | 138 | 139 | -------------------------------------------------------------------------------- /gh-pages/_.util.strings.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _.util.strings.js.md 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 | 96 | 97 | 182 |
183 | 184 | 185 | -------------------------------------------------------------------------------- /gh-pages/_.util.trampolines.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _.util.trampolines.js.md 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 | 96 | 97 | 197 |
198 | 199 | 200 | -------------------------------------------------------------------------------- /gh-pages/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | lodash-contrib 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 | 96 | 97 | 162 |
163 | 164 | 165 | -------------------------------------------------------------------------------- /gh-pages/public/fonts/aller-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node4good/lodash-contrib/91dded5d52f6dca50a4c74782740b02478c2c548/gh-pages/public/fonts/aller-bold.eot -------------------------------------------------------------------------------- /gh-pages/public/fonts/aller-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node4good/lodash-contrib/91dded5d52f6dca50a4c74782740b02478c2c548/gh-pages/public/fonts/aller-bold.ttf -------------------------------------------------------------------------------- /gh-pages/public/fonts/aller-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node4good/lodash-contrib/91dded5d52f6dca50a4c74782740b02478c2c548/gh-pages/public/fonts/aller-bold.woff -------------------------------------------------------------------------------- /gh-pages/public/fonts/aller-light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node4good/lodash-contrib/91dded5d52f6dca50a4c74782740b02478c2c548/gh-pages/public/fonts/aller-light.eot -------------------------------------------------------------------------------- /gh-pages/public/fonts/aller-light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node4good/lodash-contrib/91dded5d52f6dca50a4c74782740b02478c2c548/gh-pages/public/fonts/aller-light.ttf -------------------------------------------------------------------------------- /gh-pages/public/fonts/aller-light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node4good/lodash-contrib/91dded5d52f6dca50a4c74782740b02478c2c548/gh-pages/public/fonts/aller-light.woff -------------------------------------------------------------------------------- /gh-pages/public/fonts/novecento-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node4good/lodash-contrib/91dded5d52f6dca50a4c74782740b02478c2c548/gh-pages/public/fonts/novecento-bold.eot -------------------------------------------------------------------------------- /gh-pages/public/fonts/novecento-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node4good/lodash-contrib/91dded5d52f6dca50a4c74782740b02478c2c548/gh-pages/public/fonts/novecento-bold.ttf -------------------------------------------------------------------------------- /gh-pages/public/fonts/novecento-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node4good/lodash-contrib/91dded5d52f6dca50a4c74782740b02478c2c548/gh-pages/public/fonts/novecento-bold.woff -------------------------------------------------------------------------------- /gh-pages/public/stylesheets/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v2.0.1 | MIT License | git.io/normalize */ 2 | 3 | /* ========================================================================== 4 | HTML5 display definitions 5 | ========================================================================== */ 6 | 7 | /* 8 | * Corrects `block` display not defined in IE 8/9. 9 | */ 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | hgroup, 19 | nav, 20 | section, 21 | summary { 22 | display: block; 23 | } 24 | 25 | /* 26 | * Corrects `inline-block` display not defined in IE 8/9. 27 | */ 28 | 29 | audio, 30 | canvas, 31 | video { 32 | display: inline-block; 33 | } 34 | 35 | /* 36 | * Prevents modern browsers from displaying `audio` without controls. 37 | * Remove excess height in iOS 5 devices. 38 | */ 39 | 40 | audio:not([controls]) { 41 | display: none; 42 | height: 0; 43 | } 44 | 45 | /* 46 | * Addresses styling for `hidden` attribute not present in IE 8/9. 47 | */ 48 | 49 | [hidden] { 50 | display: none; 51 | } 52 | 53 | /* ========================================================================== 54 | Base 55 | ========================================================================== */ 56 | 57 | /* 58 | * 1. Sets default font family to sans-serif. 59 | * 2. Prevents iOS text size adjust after orientation change, without disabling 60 | * user zoom. 61 | */ 62 | 63 | html { 64 | font-family: sans-serif; /* 1 */ 65 | -webkit-text-size-adjust: 100%; /* 2 */ 66 | -ms-text-size-adjust: 100%; /* 2 */ 67 | } 68 | 69 | /* 70 | * Removes default margin. 71 | */ 72 | 73 | body { 74 | margin: 0; 75 | } 76 | 77 | /* ========================================================================== 78 | Links 79 | ========================================================================== */ 80 | 81 | /* 82 | * Addresses `outline` inconsistency between Chrome and other browsers. 83 | */ 84 | 85 | a:focus { 86 | outline: thin dotted; 87 | } 88 | 89 | /* 90 | * Improves readability when focused and also mouse hovered in all browsers. 91 | */ 92 | 93 | a:active, 94 | a:hover { 95 | outline: 0; 96 | } 97 | 98 | /* ========================================================================== 99 | Typography 100 | ========================================================================== */ 101 | 102 | /* 103 | * Addresses `h1` font sizes within `section` and `article` in Firefox 4+, 104 | * Safari 5, and Chrome. 105 | */ 106 | 107 | h1 { 108 | font-size: 2em; 109 | } 110 | 111 | /* 112 | * Addresses styling not present in IE 8/9, Safari 5, and Chrome. 113 | */ 114 | 115 | abbr[title] { 116 | border-bottom: 1px dotted; 117 | } 118 | 119 | /* 120 | * Addresses style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 121 | */ 122 | 123 | b, 124 | strong { 125 | font-weight: bold; 126 | } 127 | 128 | /* 129 | * Addresses styling not present in Safari 5 and Chrome. 130 | */ 131 | 132 | dfn { 133 | font-style: italic; 134 | } 135 | 136 | /* 137 | * Addresses styling not present in IE 8/9. 138 | */ 139 | 140 | mark { 141 | background: #ff0; 142 | color: #000; 143 | } 144 | 145 | 146 | /* 147 | * Corrects font family set oddly in Safari 5 and Chrome. 148 | */ 149 | 150 | code, 151 | kbd, 152 | pre, 153 | samp { 154 | font-family: monospace, serif; 155 | font-size: 1em; 156 | } 157 | 158 | /* 159 | * Improves readability of pre-formatted text in all browsers. 160 | */ 161 | 162 | pre { 163 | white-space: pre; 164 | white-space: pre-wrap; 165 | word-wrap: break-word; 166 | } 167 | 168 | /* 169 | * Sets consistent quote types. 170 | */ 171 | 172 | q { 173 | quotes: "\201C" "\201D" "\2018" "\2019"; 174 | } 175 | 176 | /* 177 | * Addresses inconsistent and variable font size in all browsers. 178 | */ 179 | 180 | small { 181 | font-size: 80%; 182 | } 183 | 184 | /* 185 | * Prevents `sub` and `sup` affecting `line-height` in all browsers. 186 | */ 187 | 188 | sub, 189 | sup { 190 | font-size: 75%; 191 | line-height: 0; 192 | position: relative; 193 | vertical-align: baseline; 194 | } 195 | 196 | sup { 197 | top: -0.5em; 198 | } 199 | 200 | sub { 201 | bottom: -0.25em; 202 | } 203 | 204 | /* ========================================================================== 205 | Embedded content 206 | ========================================================================== */ 207 | 208 | /* 209 | * Removes border when inside `a` element in IE 8/9. 210 | */ 211 | 212 | img { 213 | border: 0; 214 | } 215 | 216 | /* 217 | * Corrects overflow displayed oddly in IE 9. 218 | */ 219 | 220 | svg:not(:root) { 221 | overflow: hidden; 222 | } 223 | 224 | /* ========================================================================== 225 | Figures 226 | ========================================================================== */ 227 | 228 | /* 229 | * Addresses margin not present in IE 8/9 and Safari 5. 230 | */ 231 | 232 | figure { 233 | margin: 0; 234 | } 235 | 236 | /* ========================================================================== 237 | Forms 238 | ========================================================================== */ 239 | 240 | /* 241 | * Define consistent border, margin, and padding. 242 | */ 243 | 244 | fieldset { 245 | border: 1px solid #c0c0c0; 246 | margin: 0 2px; 247 | padding: 0.35em 0.625em 0.75em; 248 | } 249 | 250 | /* 251 | * 1. Corrects color not being inherited in IE 8/9. 252 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 253 | */ 254 | 255 | legend { 256 | border: 0; /* 1 */ 257 | padding: 0; /* 2 */ 258 | } 259 | 260 | /* 261 | * 1. Corrects font family not being inherited in all browsers. 262 | * 2. Corrects font size not being inherited in all browsers. 263 | * 3. Addresses margins set differently in Firefox 4+, Safari 5, and Chrome 264 | */ 265 | 266 | button, 267 | input, 268 | select, 269 | textarea { 270 | font-family: inherit; /* 1 */ 271 | font-size: 100%; /* 2 */ 272 | margin: 0; /* 3 */ 273 | } 274 | 275 | /* 276 | * Addresses Firefox 4+ setting `line-height` on `input` using `!important` in 277 | * the UA stylesheet. 278 | */ 279 | 280 | button, 281 | input { 282 | line-height: normal; 283 | } 284 | 285 | /* 286 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 287 | * and `video` controls. 288 | * 2. Corrects inability to style clickable `input` types in iOS. 289 | * 3. Improves usability and consistency of cursor style between image-type 290 | * `input` and others. 291 | */ 292 | 293 | button, 294 | html input[type="button"], /* 1 */ 295 | input[type="reset"], 296 | input[type="submit"] { 297 | -webkit-appearance: button; /* 2 */ 298 | cursor: pointer; /* 3 */ 299 | } 300 | 301 | /* 302 | * Re-set default cursor for disabled elements. 303 | */ 304 | 305 | button[disabled], 306 | input[disabled] { 307 | cursor: default; 308 | } 309 | 310 | /* 311 | * 1. Addresses box sizing set to `content-box` in IE 8/9. 312 | * 2. Removes excess padding in IE 8/9. 313 | */ 314 | 315 | input[type="checkbox"], 316 | input[type="radio"] { 317 | box-sizing: border-box; /* 1 */ 318 | padding: 0; /* 2 */ 319 | } 320 | 321 | /* 322 | * 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome. 323 | * 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome 324 | * (include `-moz` to future-proof). 325 | */ 326 | 327 | input[type="search"] { 328 | -webkit-appearance: textfield; /* 1 */ 329 | -moz-box-sizing: content-box; 330 | -webkit-box-sizing: content-box; /* 2 */ 331 | box-sizing: content-box; 332 | } 333 | 334 | /* 335 | * Removes inner padding and search cancel button in Safari 5 and Chrome 336 | * on OS X. 337 | */ 338 | 339 | input[type="search"]::-webkit-search-cancel-button, 340 | input[type="search"]::-webkit-search-decoration { 341 | -webkit-appearance: none; 342 | } 343 | 344 | /* 345 | * Removes inner padding and border in Firefox 4+. 346 | */ 347 | 348 | button::-moz-focus-inner, 349 | input::-moz-focus-inner { 350 | border: 0; 351 | padding: 0; 352 | } 353 | 354 | /* 355 | * 1. Removes default vertical scrollbar in IE 8/9. 356 | * 2. Improves readability and alignment in all browsers. 357 | */ 358 | 359 | textarea { 360 | overflow: auto; /* 1 */ 361 | vertical-align: top; /* 2 */ 362 | } 363 | 364 | /* ========================================================================== 365 | Tables 366 | ========================================================================== */ 367 | 368 | /* 369 | * Remove most spacing between table cells. 370 | */ 371 | 372 | table { 373 | border-collapse: collapse; 374 | border-spacing: 0; 375 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lodash-contrib", 3 | "description": "The brass buckles on lodash's utility belt", 4 | "version": "4.1200.1", 5 | "main": "dist/lodash-contrib.commonjs.js", 6 | "dependencies": { 7 | "lodash": "4.12.x" 8 | }, 9 | "devDependencies": { 10 | "chai": "", 11 | "grunt": "", 12 | "grunt-browserify": "", 13 | "grunt-cli": "", 14 | "grunt-contrib-concat": "", 15 | "grunt-contrib-copy": "", 16 | "grunt-contrib-jshint": "", 17 | "grunt-contrib-qunit": "", 18 | "grunt-contrib-uglify": "", 19 | "grunt-mocha-test": "", 20 | "mocha": "" 21 | }, 22 | "repository": "node4good/lodash-contrib", 23 | "license": "MIT", 24 | "author": "Refael Ackermann (http://refack.com)", 25 | "contributors": [ 26 | "Fogus (http://www.fogus.me)", 27 | "John-David Dalton (http://allyoucanleet.com/)" 28 | ], 29 | "scripts": { 30 | "dist": "grunt gen", 31 | "mypublish": "npm run dist && npm version patch && git push --follow-tags", 32 | "test": "grunt default" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/array.selectors.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | module("lodash.array.selectors"); 4 | 5 | test("second", function() { 6 | var a = [1,2,3,4,5]; 7 | 8 | equal(_.second(a), 2, 'should retrieve the 2nd element in an array'); 9 | deepEqual(_.second(a, 5), [2,3,4,5], 'should retrieve all but the first element in an array'); 10 | deepEqual(_.map([a,_.rest(a)], _.second), [2,3], 'should be usable in _.map'); 11 | }); 12 | 13 | test("third", function() { 14 | var a = [1,2,3,4,5]; 15 | 16 | equal(_.third(a), 3, 'should retrieve the 3rd element in an array'); 17 | deepEqual(_.third(a, 5), [3,4,5], 'should retrieve all but the first and second element in an array'); 18 | deepEqual(_.map([a,_.rest(a)], _.third), [3,4], 'should be usable in _.map'); 19 | }); 20 | 21 | test("takeWhile", function() { 22 | var isNeg = function(n) { return n < 0; }; 23 | 24 | deepEqual(_.takeWhile([-2,-1,0,1,2], isNeg), [-2,-1], 'should take elements until a function goes truthy'); 25 | deepEqual(_.takeWhile([1,-2,-1,0,1,2], isNeg), [], 'should take elements until a function goes truthy'); 26 | }); 27 | 28 | test("dropWhile", function() { 29 | var isNeg = function(n) { return n < 0; }; 30 | 31 | deepEqual(_.dropWhile([-2,-1,0,1,2], isNeg), [0,1,2], 'should drop elements until a function goes truthy'); 32 | deepEqual(_.dropWhile([0,1,2], isNeg), [0,1,2], 'should drop elements until a function goes truthy'); 33 | deepEqual(_.dropWhile([-2,-1], isNeg), [], 'should drop elements until a function goes truthy'); 34 | deepEqual(_.dropWhile([1,-2,-1,0,1,2], isNeg), [1,-2,-1,0,1,2], 'should take elements until a function goes truthy'); 35 | deepEqual(_.dropWhile([], isNeg), [], 'should handle empty arrays'); 36 | }); 37 | 38 | test("splitWith", function() { 39 | var a = [1,2,3,4,5]; 40 | var lessEq3p = function(n) { return n <= 3; }; 41 | var lessEq3p$ = function(n) { return (n <= 3) ? true : null; }; 42 | 43 | deepEqual(_.splitWith(a, lessEq3p), [[1,2,3], [4,5]], 'should split an array when a function goes false'); 44 | deepEqual(_.splitWith(a, lessEq3p$), [[1,2,3], [4,5]], 'should split an array when a function goes false'); 45 | deepEqual(_.splitWith([], lessEq3p$), [[],[]], 'should split an empty array into two empty arrays'); 46 | }); 47 | 48 | test("partitionBy", function() { 49 | var a = [1, 2, null, false, undefined, 3, 4]; 50 | 51 | deepEqual(_.partitionBy(a, _.truthy), [[1,2], [null, false, undefined], [3,4]], 'should partition an array as a given predicate changes truth sense'); 52 | }); 53 | 54 | test("best", function() { 55 | var a = [1,2,3,4,5]; 56 | 57 | deepEqual(_.best(a, function(x,y) { return x > y; }), 5, 'should identify the best value based on criteria'); 58 | }); 59 | 60 | test("keep", function() { 61 | var a = _.range(10); 62 | var eveny = function(e) { return (_.isEven(e)) ? e : undefined; }; 63 | 64 | deepEqual(_.keep(a, eveny), [0,2,4,6,8], 'should keep only even numbers in a range tagged with null fails'); 65 | deepEqual(_.keep(a, _.isEven), [true, false, true, false, true, false, true, false, true, false], 'should keep all existy values corresponding to a predicate over a range'); 66 | }); 67 | 68 | test("nth", function() { 69 | var a = ['a','b','c']; 70 | var b = [['a'],['b'],[]]; 71 | 72 | equal(_.nth(a,0), 'a', 'should return the element at a given index into an array'); 73 | equal(_.nth(a,100), undefined, 'should return undefined if out of bounds'); 74 | deepEqual(_.map(b,function(e) { return _.nth(e,0); }), ['a','b',undefined], 'should be usable in _.map'); 75 | }); 76 | 77 | test("nths", function() { 78 | var a = ['a','b','c', 'd']; 79 | 80 | deepEqual(_.nths(a,1), ['b'], 'should return the element at a given index into an array'); 81 | deepEqual(_.nths(a,1,3), ['b', 'd'], 'should return the elements at given indices into an array'); 82 | deepEqual(_.nths(a,1,5,3), ['b', undefined, 'd'], 'should return undefined if out of bounds'); 83 | 84 | deepEqual(_.nths(a,[1]), ['b'], 'should return the element at a given index into an array'); 85 | deepEqual(_.nths(a,[1,3]), ['b', 'd'], 'should return the elements at given indices into an array'); 86 | deepEqual(_.nths(a,[1,5,3]), ['b', undefined, 'd'], 'should return undefined if out of bounds'); 87 | }); 88 | 89 | test("valuesAt", function() { 90 | equal(_.valuesAt, _.nths, 'valuesAt should be alias for nths'); 91 | }); 92 | 93 | test("binPick", function() { 94 | var a = ['a','b','c', 'd']; 95 | 96 | deepEqual(_.binPick(a, false, true), ['b'], 'should return the element at a given index into an array'); 97 | deepEqual(_.binPick(a, false, true, false, true), ['b', 'd'], 'should return the elements at given indices into an array'); 98 | deepEqual(_.binPick(a, false, true, false, true, true), ['b', 'd', undefined], 'should return undefined if out of bounds'); 99 | 100 | deepEqual(_.binPick(a, [false, true]), ['b'], 'should return the element at a given index into an array'); 101 | deepEqual(_.binPick(a, [false, true, false, true]), ['b', 'd'], 'should return the elements at given indices into an array'); 102 | deepEqual(_.binPick(a, [false, true, false, true, true]), ['b', 'd', undefined], 'should return undefined if out of bounds'); 103 | }); 104 | }); 105 | 106 | -------------------------------------------------------------------------------- /test/browserified.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | lodash-contrib Test Suite 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | 40 | 41 | -------------------------------------------------------------------------------- /test/dist-min.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | lodash-contrib Test Suite 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | 40 | 41 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | lodash-contrib Test Suite 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | 40 | 41 | -------------------------------------------------------------------------------- /test/mocha/function.arity.js: -------------------------------------------------------------------------------- 1 | var _ = require('../..'); 2 | var assert = require('assert'); 3 | 4 | describe("lodash.function.arity", function () { 5 | describe("fix", function() { 6 | var over = function(t, m, b) { return t / m / b; }; 7 | var t = _.fix(over, 10, _, _); 8 | assert.equal(t(5, 2), 1, 'should return a function partially applied for some number of arbitrary args marked by _'); 9 | assert.equal(t(10, 2), 0.5, 'should return a function partially applied for some number of arbitrary args marked by _'); 10 | assert.equal(t(10, 5), 0.2, 'should return a function partially applied for some number of arbitrary args marked by _'); 11 | 12 | var f = function () { 13 | return _.map(arguments, function (arg) { 14 | return typeof arg; 15 | }).join(', '); 16 | }; 17 | var g = _.fix(f, _, _, 3); 18 | assert.equal(g(1), 'number, undefined, number', 'should fill "undefined" if argument not given'); 19 | g(1, 2); 20 | assert.equal(g(1), 'number, undefined, number', 'should not remember arguments between calls'); 21 | 22 | assert.equal(_.fix(parseInt, _, 10)('11'), 11, 'should "fix" common js foibles'); 23 | 24 | assert.equal(_.fix(f, _, 3)(1,'a'), 'number, number', 'should ignore extra parameters'); 25 | 26 | }); 27 | 28 | describe("arity", function () { 29 | function variadic () { return arguments.length; } 30 | function unvariadic (a, b, c) { return arguments.length; } 31 | 32 | assert.equal( _.arity(unvariadic.length, variadic).length, unvariadic.length, "should set the length"); 33 | assert.equal( _.arity(3, variadic)(1, 2, 3, 4, 5), unvariadic(1, 2, 3, 4, 5), "shouldn't trim arguments"); 34 | assert.equal( _.arity(3, variadic)(1), unvariadic(1), "shouldn't pad arguments"); 35 | 36 | // this is the big use case for _.arity: 37 | 38 | function reverse (list) { 39 | return [].reduce.call(list, function (acc, element) { 40 | acc.unshift(element); 41 | return acc; 42 | }, []); 43 | } 44 | 45 | function naiveFlip (fun) { 46 | return function () { 47 | return fun.apply(this, reverse(arguments)); 48 | }; 49 | } 50 | 51 | function echo (a, b, c) { return [a, b, c]; } 52 | 53 | assert.deepEqual(naiveFlip(echo)(1, 2, 3), [3, 2, 1], "naive flip flips its arguments"); 54 | assert.notEqual(naiveFlip(echo).length, echo.length, "naiveFlip gets its arity wrong"); 55 | 56 | function flipWithArity (fun) { 57 | return _.arity(fun.length, naiveFlip(fun)); 58 | } 59 | 60 | assert.deepEqual(flipWithArity(echo)(1, 2, 3), [3, 2, 1], "flipWithArity flips its arguments"); 61 | assert.equal(flipWithArity(echo).length, echo.length, "flipWithArity gets its arity correct"); 62 | 63 | }); 64 | 65 | describe("curry", function() { 66 | var func = function (x, y, z) { 67 | return x + y + z; 68 | }, 69 | curried = _.curry(func), 70 | rCurried = _.curryRight(func); 71 | 72 | assert.equal(func(1, 2, 3), 6, "Test pure function"); 73 | assert.equal(typeof curried, 'function', "Curry returns a function"); 74 | assert.equal(typeof curried(1), 'function', "Curry returns a function after partial application"); 75 | assert.equal(curried(1)(2)(3), 6, "Curry returns a value after total application"); 76 | assert.equal(curried(1)(2)(3), 6, "Curry invocations have no side effects and do not interact with each other"); 77 | assert.equal(curried(2)(4)(8), 14, "Curry invocations have no side effects and do not interact with each other"); 78 | assert.equal(rCurried('a')('b')('c'), 'cba', "Flipped curry applies arguments in reverse."); 79 | 80 | var addyz = curried(1); 81 | assert.equal(addyz(2)(3), 6, "Partial applications can be used multiple times"); 82 | assert.equal(addyz(2)(4), 7, "Partial applications can be used multiple times"); 83 | }); 84 | 85 | describe("curry2", function () { 86 | 87 | function echo () { return [].slice.call(arguments, 0); } 88 | 89 | assert.deepEqual(echo(1, 2), [1, 2], "Control test"); 90 | assert.deepEqual(_.curry2(echo)(1)(2), [1, 2], "Accepts curried arguments"); 91 | 92 | }); 93 | 94 | describe("curryRight2", function () { 95 | 96 | function echo () { return [].slice.call(arguments, 0); } 97 | 98 | assert.deepEqual(echo(1, 2), [1, 2], "Control test"); 99 | assert.deepEqual(_.curryRight2(echo)(1)(2), [2, 1], "Reverses curried arguments"); 100 | assert.equal(_.curryRight2, _.rcurry2, "should have alias 'rcurry2'"); 101 | }); 102 | 103 | describe("curry3", function () { 104 | 105 | function echo () { return [].slice.call(arguments, 0); } 106 | 107 | assert.deepEqual(echo(1, 2, 3), [1, 2, 3], "Control test"); 108 | assert.deepEqual(_.curry3(echo)(1)(2)(3), [1, 2, 3], "Accepts curried arguments"); 109 | 110 | }); 111 | 112 | describe("curryRight3", function () { 113 | 114 | function echo () { return [].slice.call(arguments, 0); } 115 | 116 | assert.deepEqual(echo(1, 2, 3), [1, 2, 3], "Control test"); 117 | assert.deepEqual(_.curryRight3(echo)(1)(2)(3), [3, 2, 1], "Reverses curried arguments"); 118 | assert.equal(_.curryRight3, _.rcurry3, "should have alias 'rcurry3'"); 119 | }); 120 | 121 | describe("enforce", function () { 122 | function binary (a, b) { 123 | return a + b; 124 | } 125 | function ternary (a, b, c) { 126 | return a + b + c; 127 | } 128 | function altTernary (a, b, c) { 129 | return a - b - c; 130 | } 131 | var fBinary = _.enforce(binary), 132 | fTernary = _.enforce(ternary), 133 | fAltTernary = _.enforce(altTernary), 134 | failure = false; 135 | try { 136 | fBinary(1); 137 | } catch (e) { 138 | failure = true; 139 | } finally { 140 | assert.equal(failure, true, "Binary must have two arguments."); 141 | } 142 | assert.equal(fBinary(1, 2), 3, "Function returns after proper application"); 143 | 144 | failure = false; 145 | try { 146 | fTernary(1, 3); 147 | } catch (e) { 148 | failure = true; 149 | } finally { 150 | assert.equal(failure, true, "Ternary must have three arguments."); 151 | } 152 | assert.equal(fTernary(1, 2, 3), 6, "Function returns after proper application"); 153 | assert.equal(fAltTernary(1, 2, 3), -4, "Function cache does not collide"); 154 | }); 155 | }); 156 | -------------------------------------------------------------------------------- /test/mocha/object.builders.js: -------------------------------------------------------------------------------- 1 | var _ = require('../..'); 2 | var assert = require('assert'); 3 | 4 | describe("lodash.object.builders", function () { 5 | 6 | it("renameKeys", function() { 7 | assert.deepEqual(_.renameKeys({'a': 1, 'b': 2}, {'a': 'A'}), {'b': 2, 'A': 1}, 'should rename the keys in the first object to the mapping in the second object'); 8 | 9 | var a = {'a': 1, 'b': 2}; 10 | var $ = _.renameKeys(a, {'a': 'A'}); 11 | 12 | assert.deepEqual(a, {'a': 1, 'b': 2}, 'should not modify the original'); 13 | }); 14 | 15 | it("snapshot", function() { 16 | var o = {'a': 1, 'b': 2}; 17 | var oSnap = _.snapshot(o); 18 | 19 | var a = [1,2,3,4]; 20 | var aSnap = _.snapshot(a); 21 | 22 | var n = [1,{a: 1, b: [1,2,3]},{},4]; 23 | var nSnap = _.snapshot(n); 24 | 25 | var c = [1,{a: 1, b: [1,2,3]},{},4]; 26 | var cSnap = _.snapshot(c); 27 | c[1].b = 42; 28 | 29 | assert.deepEqual(o, oSnap, 'should create a deep copy of an object'); 30 | assert.deepEqual(a, aSnap, 'should create a deep copy of an array'); 31 | assert.deepEqual(n, nSnap, 'should create a deep copy of an array'); 32 | assert.deepEqual(nSnap, [1,{a: 1, b: [1,2,3]},{},4], 'should allow changes to the original to not change copies'); 33 | }); 34 | 35 | it("setPath", function() { 36 | var obj = {a: {b: {c: 42, d: 108}}}; 37 | var ary = ['a', ['b', ['c', 'd'], 'e']]; 38 | var nest = [1, {a: 2, b: [3,4], c: 5}, 6]; 39 | 40 | assert.deepEqual(_.setPath(obj, 9, ['a', 'b', 'c']), {a: {b: {c: 9, d: 108}}}, ''); 41 | assert.deepEqual(_.setPath(ary, 9, [1, 1, 0]), ['a', ['b', [9, 'd'], 'e']], ''); 42 | assert.deepEqual(_.setPath(nest, 9, [1, 'b', 1]), [1, {a: 2, b: [3,9], c: 5}, 6], ''); 43 | 44 | assert.deepEqual(_.setPath(obj, 9, 'a'), {a: 9}, ''); 45 | assert.deepEqual(_.setPath(ary, 9, 1), ['a', 9], ''); 46 | 47 | assert.deepEqual(obj, {a: {b: {c: 42, d: 108}}}, 'should not modify the original object'); 48 | assert.deepEqual(ary, ['a', ['b', ['c', 'd'], 'e']], 'should not modify the original array'); 49 | assert.deepEqual(nest, [1, {a: 2, b: [3,4], c: 5}, 6], 'should not modify the original nested structure'); 50 | }); 51 | 52 | it("updatePath", function() { 53 | var obj = {a: {b: {c: 42, d: 108}}}; 54 | var ary = ['a', ['b', ['c', 'd'], 'e']]; 55 | var nest = [1, {a: 2, b: [3,4], c: 5}, 6]; 56 | 57 | assert.deepEqual(_.updatePath(obj, _.always(9), ['a', 'b', 'c']), {a: {b: {c: 9, d: 108}}}, ''); 58 | assert.deepEqual(_.updatePath(ary, _.always(9), [1, 1, 0]), ['a', ['b', [9, 'd'], 'e']], ''); 59 | assert.deepEqual(_.updatePath(nest, _.always(9), [1, 'b', 1]), [1, {a: 2, b: [3,9], c: 5}, 6], ''); 60 | 61 | assert.deepEqual(_.updatePath(obj, _.always(9), 'a'), {a: 9}, ''); 62 | assert.deepEqual(_.updatePath(ary, _.always(9), 1), ['a', 9], ''); 63 | 64 | assert.deepEqual(obj, {a: {b: {c: 42, d: 108}}}, 'should not modify the original object'); 65 | assert.deepEqual(ary, ['a', ['b', ['c', 'd'], 'e']], 'should not modify the original array'); 66 | assert.deepEqual(nest, [1, {a: 2, b: [3,4], c: 5}, 6], 'should not modify the original nested structure'); 67 | }); 68 | 69 | }); 70 | -------------------------------------------------------------------------------- /test/mocha/util.operators.js: -------------------------------------------------------------------------------- 1 | var _ = require('../..'); 2 | var assert = require('assert'); 3 | 4 | describe("lodash.util.operators", function () { 5 | 6 | it("addContrib", function() { 7 | assert.equal(_.addContrib(1, 1), 2, '1 + 1 = 2'); 8 | assert.equal(_.addContrib(3, 5), 8, '3 + 5 = 8'); 9 | assert.equal(_.addContrib(1, 2, 3, 4), 10, 'adds multiple operands'); 10 | }); 11 | 12 | it("sub", function() { 13 | assert.equal(_.sub(1, 1), 0, '1 - 1 = 0'); 14 | assert.equal(_.sub(5, 3), 2, '5 - 3 = 2'); 15 | assert.equal(_.sub(10, 9, 8, 7), -14, 'subtracts multiple operands'); 16 | }); 17 | 18 | it("mul", function() { 19 | assert.equal(_.mul(1, 1), 1, '1 * 1 = 1'); 20 | assert.equal(_.mul(5, 3), 15, '5 * 3 = 15'); 21 | assert.equal(_.mul(1, 2, 3, 4), 24, 'multiplies multiple operands'); 22 | }); 23 | 24 | it("div", function() { 25 | assert.equal(_.div(1, 1), 1, '1 / 1 = 1'); 26 | assert.equal(_.div(15, 3), 5, '15 / 3 = 5'); 27 | assert.equal(_.div(15, 0), Infinity, '15 / 0 = Infinity'); 28 | assert.equal(_.div(24, 2, 2, 2), 3, 'divides multiple operands'); 29 | }); 30 | 31 | it("mod", function() { 32 | assert.equal(_.mod(3, 2), 1, '3 / 2 = 1'); 33 | assert.equal(_.mod(15, 3), 0, '15 / 3 = 0'); 34 | }); 35 | 36 | it("inc", function() { 37 | assert.equal(_.inc(1), 2, '++1 = 2'); 38 | assert.equal(_.inc(15), 16, '++15 = 16'); 39 | }); 40 | 41 | it("dec", function() { 42 | assert.equal(_.dec(2), 1, '--2 = 1'); 43 | assert.equal(_.dec(15), 14, '--15 = 15'); 44 | }); 45 | 46 | it("neg", function() { 47 | assert.equal(_.neg(2), -2, 'opposite of 2'); 48 | assert.equal(_.neg(-2), 2, 'opposite of -2'); 49 | assert.equal(_.neg(true), -1, 'opposite of true'); 50 | }); 51 | 52 | it("eqContrib", function() { 53 | assert.equal(_.eqContrib(1, 1), true, '1 == 1'); 54 | assert.equal(_.eqContrib(1, true), true, '1 == true'); 55 | assert.equal(_.eqContrib(1, false), false, '1 != false'); 56 | assert.equal(_.eqContrib(1, '1'), true, '1 == "1"'); 57 | assert.equal(_.eqContrib(1, 'one'), false, '1 != "one"'); 58 | assert.equal(_.eqContrib(0, 0), true, '0 == 0'); 59 | assert.equal(_.eqContrib(0, false), true, '0 == false'); 60 | assert.equal(_.eqContrib(0, '0'), true, '0 == "0"'); 61 | assert.equal(_.eqContrib({}, {}), false, '{} == {}'); 62 | assert.equal(_.eqContrib(0, 0, 1), false, 'compares a list of arguments'); 63 | }); 64 | it("seq", function() { 65 | assert.equal(_.seq(1, 1), true, '1 === 1'); 66 | assert.equal(_.seq(1, '1'), false, '1 !== "1"'); 67 | assert.equal(_.seq(0, 0, 1), false, 'compares a list of arguments'); 68 | }); 69 | it("neq", function() { 70 | assert.equal(_.neq('a', 'b'), true, '"a" != "b"'); 71 | assert.equal(_.neq(1, '1'), false, '1 == "1"'); 72 | assert.equal(_.neq(0, 0, 1), true, 'compares a list of arguments'); 73 | }); 74 | it("sneq", function() { 75 | assert.equal(_.sneq('a', 'b'), true, '"a" !== "b"'); 76 | assert.equal(_.sneq(1, '1'), true, '1 !== "1"'); 77 | assert.equal(_.sneq(0, 0, 1), true, 'compares a list of arguments'); 78 | }); 79 | it("not", function() { 80 | assert.equal(_.not(true), false, 'converts true to false'); 81 | assert.equal(_.not(false), true, 'converts false to true'); 82 | assert.equal(_.not('truthy'), false, 'converts truthy values to false'); 83 | assert.equal(_.not(null), true, 'converts falsy values to true'); 84 | }); 85 | it("gtContrib", function() { 86 | assert.equal(_.gtContrib(3, 2), true, '3 > 2'); 87 | assert.equal(_.gtContrib(1, 3), false, '1 > 3'); 88 | assert.equal(_.gtContrib(1, 2, 1), false, 'compares a list of arguments'); 89 | }); 90 | it("ltContrib", function() { 91 | assert.equal(_.ltContrib(3, 2), false, '3 < 2'); 92 | assert.equal(_.ltContrib(1, 3), true, '1 < 3'); 93 | assert.equal(_.ltContrib(1, 2, 1), false, 'compares a list of arguments'); 94 | }); 95 | it("gteContrib", function() { 96 | assert.equal(_.gteContrib(3, 2), true, '3 >= 2'); 97 | assert.equal(_.gteContrib(1, 3), false, '1 >= 3'); 98 | assert.equal(_.gteContrib(3, 3), true, '3 >= 3'); 99 | assert.equal(_.gteContrib(2, 3, 1), false, 'compares a list of arguments'); 100 | }); 101 | it("lteContrib", function() { 102 | assert.equal(_.lteContrib(3, 2), false, '3 <= 2'); 103 | assert.equal(_.lteContrib(1, 3), true, '1 <= 3'); 104 | assert.equal(_.lteContrib(3, 3), true, '3 <= 3'); 105 | assert.equal(_.lteContrib(2, 2, 1), false, 'compares a list of arguments'); 106 | }); 107 | it("bitwiseAnd", function() { 108 | assert.equal(_.bitwiseAnd(1, 1), 1, '1 & 1'); 109 | assert.equal(_.bitwiseAnd(1, 0), 0, '1 & 0'); 110 | assert.equal(_.bitwiseAnd(1, 1, 0), 0, 'operates on multiple arguments'); 111 | }); 112 | it("bitwiseOr", function() { 113 | assert.equal(_.bitwiseOr(1, 1), 1, '1 | 1'); 114 | assert.equal(_.bitwiseOr(1, 0), 1, '1 | 0'); 115 | assert.equal(_.bitwiseOr(1, 1, 2), 3, 'operates on multiple arguments'); 116 | }); 117 | it("bitwiseXor", function() { 118 | assert.equal(_.bitwiseXor(1, 1), 0, '1 ^ 1'); 119 | assert.equal(_.bitwiseXor(1, 2), 3, '1 ^ 2'); 120 | assert.equal(_.bitwiseXor(1, 2, 3), 0, 'operates on multiple arguments'); 121 | }); 122 | it("bitwiseNot", function() { 123 | assert.equal(_.bitwiseNot(1), -2, '~1'); 124 | assert.equal(_.bitwiseNot(2), -3, '~2'); 125 | }); 126 | it("bitwiseLeft", function() { 127 | assert.equal(_.bitwiseLeft(1, 1), 2, '1 << 1'); 128 | assert.equal(_.bitwiseLeft(1, 0), 1, '1 << 0'); 129 | assert.equal(_.bitwiseLeft(1, 1, 1), 4, 'operates on multiple arguments'); 130 | }); 131 | it("bitwiseRight", function() { 132 | assert.equal(_.bitwiseRight(1, 1), 0, '1 >> 1'); 133 | assert.equal(_.bitwiseRight(2, 1), 1, '2 >> 1'); 134 | assert.equal(_.bitwiseRight(3, 1, 1), 0, 'operates on multiple arguments'); 135 | }); 136 | it("bitwiseZ", function() { 137 | assert.equal(_.bitwiseZ(-1, 1), 2147483647, '-1 >>> 1'); 138 | assert.equal(_.bitwiseZ(-1, 1, 1), 1073741823, 'operates on multiple arguments'); 139 | }); 140 | 141 | }); 142 | -------------------------------------------------------------------------------- /test/mocha/util.strings.js: -------------------------------------------------------------------------------- 1 | var _ = require('../..'); 2 | var assert = require('assert'); 3 | var expect = require('chai').expect; 4 | 5 | describe("util.string", function () { 6 | describe("fromQuery", function () { 7 | it("can convert a query string to a hash", function (done) { 8 | var query = 'foo%26bar=baz&test=total+utter+success'; 9 | assert(_.isEqual(_.fromQuery(query), {'foo&bar': 'baz', 'test': 'total utter success'})); 10 | done(); 11 | }); 12 | }); 13 | 14 | describe("toQuery", function () { 15 | it('can convert a hash to a query string', function (done) { 16 | var obj = {'foo&bar': 'baz', 'test': 'total success'}; 17 | assert.equal(_.toQuery(obj), 'foo%26bar=baz&test=total%20success', 'can convert a hash to a query string'); 18 | done(); 19 | }); 20 | }); 21 | 22 | describe("Title Case", function () { 23 | it('can convert a sentance to a Title Case', function (done) { 24 | assert.equal(_.titleCase('Hey, dudes!'), 'Hey, Dudes!'); 25 | assert.equal(_.titleCase('(Where are you?)'), '(Where Are You?)'); 26 | assert.equal(_.titleCase('Boogaloo dudes (Stand up, come on!)'), 'Boogaloo Dudes (Stand Up, Come On!)'); 27 | done(); 28 | }); 29 | }); 30 | 31 | describe('slugify', function () { 32 | 33 | it('lower-cases strings for slugs', function () { 34 | expect(_.slugify('String')).to.equal('string'); 35 | }); 36 | 37 | it('converts a string with spaces into a slug', function () { 38 | expect(_.slugify('string with spaces')).to.equal('string-with-spaces'); 39 | }); 40 | 41 | it('converts a string with dots into a slug', function () { 42 | expect(_.slugify('string.with.dots')).to.equal('string-with-dots'); 43 | }); 44 | 45 | it('converts TitleCase strings into slugs', function () { 46 | expect(_.slugify('TitleCase')).to.equal('title-case'); 47 | }); 48 | 49 | it('leaves strings that are already slugs alone', function () { 50 | expect(_.slugify('i-am-a-slug')).to.equal('i-am-a-slug'); 51 | }); 52 | 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /test/mocha/vanilla.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | describe("vanilla", function () { 4 | it("should install the extensions", function (done) { 5 | var contrib = require('../..'); 6 | assert('walk' in contrib); 7 | done(); 8 | }); 9 | 10 | it("but should leave 'lodash' alone", function (done) { 11 | var lodash = require('lodash'); 12 | assert(!('walk' in lodash)); 13 | done(); 14 | }); 15 | 16 | it("but should not override methods", function (done) { 17 | var lodash = require('lodash'); 18 | var contrib = require('../..'); 19 | var methods = lodash.remove(lodash.keys(lodash), '_'); 20 | lodash.forEach(methods, function(m) { 21 | assert.equal(contrib[m].toString(), lodash[m].toString(), m + ' should be the same'); 22 | }); 23 | done(); 24 | }); 25 | }); 26 | 27 | -------------------------------------------------------------------------------- /test/object.selectors.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | module("lodash.object.selectors"); 4 | 5 | test("accessor", function() { 6 | var a = [{a: 1, b: 2}, {c: 3}]; 7 | 8 | equal(_.accessor('a')(a[0]), 1, 'should return a function that plucks'); 9 | equal(_.accessor('a')(a[1]), undefined, 'should return a function that plucks, or returns undefined'); 10 | deepEqual(_.map(a, _.accessor('a')), [1, undefined], 'should return a function that plucks'); 11 | }); 12 | 13 | test("dictionary", function() { 14 | var a = [{a: 1, b: 2}, {c: 3}]; 15 | 16 | equal(_.dictionary(a[0])('a'), 1, 'should return a function that acts as a dictionary'); 17 | equal(_.dictionary(a[1])('a'), undefined, 'should return a function that acts as a dictionary, or returns undefined'); 18 | }); 19 | 20 | test("selectKeys", function() { 21 | deepEqual(_.selectKeys({'a': 1, 'b': 2}, ['a']), {'a': 1}, 'shold return a map of the desired keys'); 22 | deepEqual(_.selectKeys({'a': 1, 'b': 2}, ['z']), {}, 'shold return an empty map if the desired keys are not present'); 23 | }); 24 | 25 | test("kv", function() { 26 | deepEqual(_.kv({'a': 1, 'b': 2}, 'a'), ['a', 1], 'should return the key/value pair at the desired key'); 27 | equal(_.kv({'a': 1, 'b': 2}, 'z'), undefined, 'shold return undefined if the desired key is not present'); 28 | }); 29 | 30 | test("getPath", function() { 31 | var deepObject = { a: { b: { c: "c" } }, falseVal: false, nullVal: null, undefinedVal: undefined, arrayVal: ["arr"] }; 32 | var deepArr = [[["thirdLevel"]]]; 33 | var ks = ["a", "b", "c"]; 34 | 35 | strictEqual(_.getPath(deepObject, ks), "c", "should get a deep property's value from objects"); 36 | deepEqual(ks, ["a", "b", "c"], "should not have mutated ks argument"); 37 | strictEqual(_.getPath(deepArr, [0, 0, 0]), "thirdLevel", "should get a deep property's value from arrays"); 38 | strictEqual(_.getPath(deepObject, ["arrayVal", 0]), "arr", "should get a deep property's value from nested arrays and objects"); 39 | 40 | strictEqual(_.getPath(deepObject, ["undefinedVal"]), undefined, "should return undefined for undefined properties"); 41 | strictEqual(_.getPath(deepObject, ["a", "notHere"]), undefined, "should return undefined for non-existent properties"); 42 | strictEqual(_.getPath(deepObject, ["nullVal"]), null, "should return null for null properties"); 43 | strictEqual(_.getPath(deepObject, ["nullVal", "notHere", "notHereEither"]), undefined, "should return undefined for non-existent descendents of null properties"); 44 | 45 | strictEqual(_.getPath(deepObject, "a.b.c"), "c", "should work with keys written in dot notation"); 46 | }); 47 | 48 | test("hasPath", function() { 49 | var deepObject = { a: { b: { c: "c" } }, falseVal: false, numberVal: 123, stringVal: 'str', nullVal: null, undefinedVal: undefined, arrayVal: ["arr"] }; 50 | var ks = ["a", "b", "c"]; 51 | 52 | strictEqual(_.hasPath(deepObject, ["notHere", "notHereEither"]), false, "should return false if the path doesn't exist"); 53 | strictEqual(_.hasPath(deepObject, ks), true, "should return true if the path exists"); 54 | deepEqual(ks, ["a", "b", "c"], "should not have mutated ks argument"); 55 | 56 | strictEqual(_.hasPath(deepObject, ["arrayVal", 0]), true, "should return true for an array's index if it is defined"); 57 | strictEqual(_.hasPath(deepObject, ["arrayVal", 999]), false, "should return false for an array's index if it is not defined"); 58 | 59 | strictEqual(_.hasPath(deepObject, ["nullVal"]), true, "should return true for null properties"); 60 | strictEqual(_.hasPath(deepObject, ["undefinedVal"]), true, "should return true for properties that were explicitly assigned undefined"); 61 | 62 | strictEqual(_.hasPath(deepObject, ["nullVal", "notHere"]), false, "should return false for descendants of null properties"); 63 | strictEqual(_.hasPath(deepObject, ["undefinedVal", "notHere"]), false, "should return false for descendants of undefined properties"); 64 | strictEqual(_.hasPath(deepObject, ["falseVal", "notHere"]), false, "should return false for descendants of a boolean literal"); 65 | strictEqual(_.hasPath(deepObject, ["stringVal", "notHere"]), false, "should return false for descendants of a string literal"); 66 | strictEqual(_.hasPath(deepObject, ["numberVal", "notHere"]), false, "should return false for descendants of a number literal"); 67 | 68 | strictEqual(_.hasPath(deepObject, "a.b.c"), true, "should work with keys written in dot notation."); 69 | }); 70 | 71 | test("pickWhen", function() { 72 | var a = {foo: true, bar: false, baz: 42}; 73 | 74 | deepEqual(_.pickWhen(a, _.truthy), {foo: true, baz: 42}, "should return an object with kvs that return a truthy value for the given predicate"); 75 | }); 76 | 77 | test("omitWhen", function() { 78 | var a = {foo: [], bar: "", baz: "something", quux: ['a']}; 79 | 80 | deepEqual(_.omitWhen(a, _.isEmpty), {baz: "something", quux: ['a']}, "should return an object with kvs that return a falsey value for the given predicate"); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /test/util.existential.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | module("lodash.util.existential"); 4 | 5 | test("exists", function() { 6 | equal(_.exists(null), false, 'should know that null is not existy'); 7 | equal(_.exists(undefined), false, 'should know that undefined is not existy'); 8 | 9 | equal(_.exists(1), true, 'should know that all but null and undefined are existy'); 10 | equal(_.exists(0), true, 'should know that all but null and undefined are existy'); 11 | equal(_.exists(-1), true, 'should know that all but null and undefined are existy'); 12 | equal(_.exists(3.14), true, 'should know that all but null and undefined are existy'); 13 | equal(_.exists('undefined'), true, 'should know that all but null and undefined are existy'); 14 | equal(_.exists(''), true, 'should know that all but null and undefined are existy'); 15 | equal(_.exists(NaN), true, 'should know that all but null and undefined are existy'); 16 | equal(_.exists(Infinity), true, 'should know that all but null and undefined are existy'); 17 | equal(_.exists(true), true, 'should know that all but null and undefined are existy'); 18 | equal(_.exists(false), true, 'should know that all but null and undefined are existy'); 19 | equal(_.exists(function(){}), true, 'should know that all but null and undefined are existy'); 20 | 21 | equal(_.existsAll(0, null, '2'), false, 'should know that null is not existy'); 22 | equal(_.existsAll(0, undefined, '2'), false, 'should know that undefined is not existy'); 23 | equal(_.existsAll(0, 'undefined', NaN, Infinity, true, false, function(){}), true, 'should know that all but null and undefined are existy'); 24 | }); 25 | 26 | test("truthy", function() { 27 | equal(_.truthy(null), false, 'should know that null, undefined and false are not truthy'); 28 | equal(_.truthy(undefined), false, 'should know that null, undefined and false are not truthy'); 29 | equal(_.truthy(false), false, 'should know that null, undefined and false are not truthy'); 30 | equal(_.truthy(1), true, 'should know that everything else is truthy'); 31 | equal(_.truthy(0), true, 'should know that everything else is truthy'); 32 | equal(_.truthy(-1), true, 'should know that everything else is truthy'); 33 | equal(_.truthy(3.14), true, 'should know that everything else is truthy'); 34 | equal(_.truthy('undefined'), true, 'should know that everything else is truthy'); 35 | equal(_.truthy(''), true, 'should know that everything else is truthy'); 36 | equal(_.truthy(NaN), true, 'should know that everything else is truthy'); 37 | equal(_.truthy(Infinity), true, 'should know that everything else is truthy'); 38 | equal(_.truthy(true), true, 'should know that everything else is truthy'); 39 | equal(_.truthy(function(){}), true, 'should know that everything else is truthy'); 40 | 41 | equal(_.truthyAll(0, null, '2'), false, 'should know that null is not truthy'); 42 | equal(_.truthyAll(0, undefined, '2'), false, 'should know that undefined is not truthy'); 43 | equal(_.truthyAll(0, undefined, '2'), false, 'should know that false is not truthy'); 44 | equal(_.truthyAll(0, 'undefined', NaN, Infinity, true, function(){}), true, 'should know that all but null and undefined are truthy'); 45 | }); 46 | 47 | test("falsey", function() { 48 | equal(_.falsey(null), true, 'should know that null, undefined and false are falsey'); 49 | equal(_.falsey(undefined), true, 'should know that null, undefined and false are falsey'); 50 | equal(_.falsey(false), true, 'should know that null, undefined and false are falsey'); 51 | 52 | equal(_.falsey(1), false, 'should know that everything else is not falsey'); 53 | equal(_.falsey(0), false, 'should know that everything else is not falsey'); 54 | equal(_.falsey(-1), false, 'should know that everything else is not falsey'); 55 | equal(_.falsey(3.14), false, 'should know that everything else is not falsey'); 56 | equal(_.falsey('undefined'), false, 'should know that everything else is not falsey'); 57 | equal(_.falsey(''), false, 'should know that everything else is not falsey'); 58 | equal(_.falsey(NaN), false, 'should know that everything else is not falsey'); 59 | equal(_.falsey(Infinity), false, 'should know that everything else is not falsey'); 60 | equal(_.falsey(true), false, 'should know that everything else is not falsey'); 61 | equal(_.falsey(function(){}), false, 'should know that everything else is not falsey'); 62 | 63 | equal(!_.truthyAll(1, null, '2'), true, '!_.truthyAll should know that null is falsey even when veriadic'); 64 | equal(!_.truthyAll(1, undefined, '2'), true, '!_.truthyAll should know that undefined is falsey even when veriadic'); 65 | equal(!_.truthyAll(1, false, '2'), true, '!_.truthyAll should know that false is falsey even when veriadic'); 66 | equal(!_.truthyAll(1, 'undefined', NaN, Infinity, true, function(){}), false, '!_.truthyAll should know that only null undefined and false are falsey even when veriadic'); 67 | 68 | equal(_.falseyAll(false, null), true, '_.falseyAll should know that null is falsey even when veriadic'); 69 | equal(_.falseyAll(false, undefined), true, '_.falseyAll should know that undefined is falsey even when veriadic'); 70 | equal(_.falseyAll(false, false), true, '_.falseyAll should know that false is falsey even when veriadic'); 71 | equal(_.falseyAll(false, null, undefined), true, '_.falseyAll should know that only null undefined and false are falsey even when veriadic'); 72 | }); 73 | 74 | test('firstExisting', function() { 75 | equal(_.firstExisting('first', 'second'), 'first', 'should return the first existing value'); 76 | equal(_.firstExisting(null, 'second'), 'second', 'should ignore null'); 77 | equal(_.firstExisting(void 0, 'second'), 'second', 'should ignore undefined'); 78 | equal(_.firstExisting(null, void 0, 'third'), 'third', 'should work with more arguments'); 79 | }); 80 | 81 | }); 82 | -------------------------------------------------------------------------------- /test/util.strings.js: -------------------------------------------------------------------------------- 1 | 2 | $(document).ready(function() { 3 | 4 | module('lodash.util.strings'); 5 | 6 | test('explode', function() { 7 | deepEqual(_.explode('Virgil'), ['V','i','r','g','i','l'], 'Should explode a string into an array of characters.'); 8 | }); 9 | 10 | test('fromQuery', function() { 11 | var query = 'foo%5Bbar%5D%5Bbaz%5D%5Bblargl%5D=blah&foo%5Bbar%5D%5Bbaz%5D%5Bblargr%5D=woop&blar=bluh&abc[]=123&abc[]=234'; 12 | ok(_.isEqual(_.fromQuery(query), { 13 | 'foo': { 14 | 'bar': { 15 | 'baz': { 16 | 'blargl': 'blah', 17 | 'blargr': 'woop' 18 | } 19 | } 20 | }, 21 | 'blar': 'bluh', 22 | 'abc': [ 23 | '123', 24 | '234' 25 | ] 26 | }), 'can convert a query string to a hash'); 27 | }); 28 | 29 | test('implode', function() { 30 | equal(_.implode(['H','o','m','e','r']), 'Homer', 'Should implode an array of characters into a single string.'); 31 | }); 32 | 33 | test('toDash', function() { 34 | equal(_.toDash('trojanWar'), 'trojan-war', 'Should convert a camelCase string to dashed-format.'); 35 | equal(_.toDash('PersianWar'), 'persian-war', 'Should convert a PascalCase string to dashed-format.'); 36 | }); 37 | 38 | test('toQuery', function() { 39 | var obj = {'foo&bar': 'baz', 'test': 'total success', 'nested': {'works': 'too'}, 'isn\'t': ['that', 'cool?']}; 40 | equal(_.toQuery(obj), 'foo%26bar=baz&test=total%20success&nested%5Bworks%5D=too&isn\'t%5B%5D=that&isn\'t%5B%5D=cool%3F', 'can convert a hash to a query string'); 41 | }); 42 | 43 | test('strContains', function() { 44 | equal(_.strContains('Metaphysics', 'physics'), true, 'Should return true if string contains search string.'); 45 | equal(_.strContains('Poetics', 'prose'), false, 'Should return false if string does not contain search string.'); 46 | 47 | var thrower = function() { _.strContains([], ''); }; 48 | throws(thrower, TypeError, 'Throws TypeError if first argument is not a string.'); 49 | }); 50 | 51 | test('humanize', function() { 52 | equal(_.humanize("lowercase"), "Lowercase"); 53 | equal(_.humanize("Class"), "Class"); 54 | equal(_.humanize("MyClass"), "My Class"); 55 | equal(_.humanize("HTML"), "HTML"); 56 | equal(_.humanize("PDFLoader"), "PDF Loader"); 57 | equal(_.humanize("AString"), "A String"); 58 | equal(_.humanize("SimpleXMLParser"), "Simple XML Parser"); 59 | equal(_.humanize("LastUpdateDateInt"), "Last Update Date Int"); 60 | equal(_.humanize("LastUpdate_date_IntHTML"), "Last Update date Int HTML"); 61 | }); 62 | 63 | test( 'slugify', function() { 64 | equal(_.slugify('String'), 'string', 'lower-cases strings for slugs'); 65 | equal(_.slugify('string with spaces'), 'string-with-spaces', 'converts a string with spaces into a slug'); 66 | equal(_.slugify('string.with.dots'), 'string-with-dots', 'converts a string with dots into a slug'); 67 | equal(_.slugify('TitleCase'), 'title-case', 'converts TitleCase strings into slugs'); 68 | equal(_.slugify('i-am-a-slug'), 'i-am-a-slug', 'leaves strings that are already slugs alone'); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /test/util.trampolines.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | module("lodash.util.trampolines"); 4 | 5 | test("trampoline", function() { 6 | var oddOline = function(n) { 7 | if (n === 0) 8 | return _.done(false); 9 | else 10 | return _.partial(evenOline, Math.abs(n) - 1); 11 | }; 12 | 13 | var evenOline = function(n) { 14 | if (n === 0) 15 | return _.done(true); 16 | else 17 | return _.partial(oddOline, Math.abs(n) - 1); 18 | }; 19 | 20 | equal(_.trampoline(evenOline, 55000), true, 'should trampoline two mutually recursive functions'); 21 | equal(_.trampoline(evenOline, 0), true, 'should trampoline two mutually recursive functions'); 22 | equal(_.trampoline(evenOline, 111111), false, 'should trampoline two mutually recursive functions'); 23 | equal(_.trampoline(oddOline, 1), true, 'should trampoline two mutually recursive functions'); 24 | equal(_.trampoline(oddOline, 11111), true, 'should trampoline two mutually recursive functions'); 25 | equal(_.trampoline(oddOline, 22), false, 'should trampoline two mutually recursive functions'); 26 | }); 27 | 28 | }); 29 | -------------------------------------------------------------------------------- /test/vendor/qunit.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * QUnit 1.18.0 3 | * http://qunitjs.com/ 4 | * 5 | * Copyright jQuery Foundation and other contributors 6 | * Released under the MIT license 7 | * http://jquery.org/license 8 | * 9 | * Date: 2015-04-03T10:23Z 10 | */ 11 | 12 | /** Font Family and Sizes */ 13 | 14 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 15 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 16 | } 17 | 18 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 19 | #qunit-tests { font-size: smaller; } 20 | 21 | 22 | /** Resets */ 23 | 24 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { 25 | margin: 0; 26 | padding: 0; 27 | } 28 | 29 | 30 | /** Header */ 31 | 32 | #qunit-header { 33 | padding: 0.5em 0 0.5em 1em; 34 | 35 | color: #8699A4; 36 | background-color: #0D3349; 37 | 38 | font-size: 1.5em; 39 | line-height: 1em; 40 | font-weight: 400; 41 | 42 | border-radius: 5px 5px 0 0; 43 | } 44 | 45 | #qunit-header a { 46 | text-decoration: none; 47 | color: #C2CCD1; 48 | } 49 | 50 | #qunit-header a:hover, 51 | #qunit-header a:focus { 52 | color: #FFF; 53 | } 54 | 55 | #qunit-testrunner-toolbar label { 56 | display: inline-block; 57 | padding: 0 0.5em 0 0.1em; 58 | } 59 | 60 | #qunit-banner { 61 | height: 5px; 62 | } 63 | 64 | #qunit-testrunner-toolbar { 65 | padding: 0.5em 1em 0.5em 1em; 66 | color: #5E740B; 67 | background-color: #EEE; 68 | overflow: hidden; 69 | } 70 | 71 | #qunit-userAgent { 72 | padding: 0.5em 1em 0.5em 1em; 73 | background-color: #2B81AF; 74 | color: #FFF; 75 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 76 | } 77 | 78 | #qunit-modulefilter-container { 79 | float: right; 80 | padding: 0.2em; 81 | } 82 | 83 | .qunit-url-config { 84 | display: inline-block; 85 | padding: 0.1em; 86 | } 87 | 88 | .qunit-filter { 89 | display: block; 90 | float: right; 91 | margin-left: 1em; 92 | } 93 | 94 | /** Tests: Pass/Fail */ 95 | 96 | #qunit-tests { 97 | list-style-position: inside; 98 | } 99 | 100 | #qunit-tests li { 101 | padding: 0.4em 1em 0.4em 1em; 102 | border-bottom: 1px solid #FFF; 103 | list-style-position: inside; 104 | } 105 | 106 | #qunit-tests > li { 107 | display: none; 108 | } 109 | 110 | #qunit-tests li.running, 111 | #qunit-tests li.pass, 112 | #qunit-tests li.fail, 113 | #qunit-tests li.skipped { 114 | display: list-item; 115 | } 116 | 117 | #qunit-tests.hidepass li.running, 118 | #qunit-tests.hidepass li.pass { 119 | visibility: hidden; 120 | position: absolute; 121 | width: 0px; 122 | height: 0px; 123 | padding: 0; 124 | border: 0; 125 | margin: 0; 126 | } 127 | 128 | #qunit-tests li strong { 129 | cursor: pointer; 130 | } 131 | 132 | #qunit-tests li.skipped strong { 133 | cursor: default; 134 | } 135 | 136 | #qunit-tests li a { 137 | padding: 0.5em; 138 | color: #C2CCD1; 139 | text-decoration: none; 140 | } 141 | 142 | #qunit-tests li p a { 143 | padding: 0.25em; 144 | color: #6B6464; 145 | } 146 | #qunit-tests li a:hover, 147 | #qunit-tests li a:focus { 148 | color: #000; 149 | } 150 | 151 | #qunit-tests li .runtime { 152 | float: right; 153 | font-size: smaller; 154 | } 155 | 156 | .qunit-assert-list { 157 | margin-top: 0.5em; 158 | padding: 0.5em; 159 | 160 | background-color: #FFF; 161 | 162 | border-radius: 5px; 163 | } 164 | 165 | .qunit-collapsed { 166 | display: none; 167 | } 168 | 169 | #qunit-tests table { 170 | border-collapse: collapse; 171 | margin-top: 0.2em; 172 | } 173 | 174 | #qunit-tests th { 175 | text-align: right; 176 | vertical-align: top; 177 | padding: 0 0.5em 0 0; 178 | } 179 | 180 | #qunit-tests td { 181 | vertical-align: top; 182 | } 183 | 184 | #qunit-tests pre { 185 | margin: 0; 186 | white-space: pre-wrap; 187 | word-wrap: break-word; 188 | } 189 | 190 | #qunit-tests del { 191 | background-color: #E0F2BE; 192 | color: #374E0C; 193 | text-decoration: none; 194 | } 195 | 196 | #qunit-tests ins { 197 | background-color: #FFCACA; 198 | color: #500; 199 | text-decoration: none; 200 | } 201 | 202 | /*** Test Counts */ 203 | 204 | #qunit-tests b.counts { color: #000; } 205 | #qunit-tests b.passed { color: #5E740B; } 206 | #qunit-tests b.failed { color: #710909; } 207 | 208 | #qunit-tests li li { 209 | padding: 5px; 210 | background-color: #FFF; 211 | border-bottom: none; 212 | list-style-position: inside; 213 | } 214 | 215 | /*** Passing Styles */ 216 | 217 | #qunit-tests li li.pass { 218 | color: #3C510C; 219 | background-color: #FFF; 220 | border-left: 10px solid #C6E746; 221 | } 222 | 223 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 224 | #qunit-tests .pass .test-name { color: #366097; } 225 | 226 | #qunit-tests .pass .test-actual, 227 | #qunit-tests .pass .test-expected { color: #999; } 228 | 229 | #qunit-banner.qunit-pass { background-color: #C6E746; } 230 | 231 | /*** Failing Styles */ 232 | 233 | #qunit-tests li li.fail { 234 | color: #710909; 235 | background-color: #FFF; 236 | border-left: 10px solid #EE5757; 237 | white-space: pre; 238 | } 239 | 240 | #qunit-tests > li:last-child { 241 | border-radius: 0 0 5px 5px; 242 | } 243 | 244 | #qunit-tests .fail { color: #000; background-color: #EE5757; } 245 | #qunit-tests .fail .test-name, 246 | #qunit-tests .fail .module-name { color: #000; } 247 | 248 | #qunit-tests .fail .test-actual { color: #EE5757; } 249 | #qunit-tests .fail .test-expected { color: #008000; } 250 | 251 | #qunit-banner.qunit-fail { background-color: #EE5757; } 252 | 253 | /*** Skipped tests */ 254 | 255 | #qunit-tests .skipped { 256 | background-color: #EBECE9; 257 | } 258 | 259 | #qunit-tests .qunit-skipped-label { 260 | background-color: #F4FF77; 261 | display: inline-block; 262 | font-style: normal; 263 | color: #366097; 264 | line-height: 1.8em; 265 | padding: 0 0.5em; 266 | margin: -0.4em 0.4em -0.4em 0; 267 | } 268 | 269 | /** Result */ 270 | 271 | #qunit-testresult { 272 | padding: 0.5em 1em 0.5em 1em; 273 | 274 | color: #2B81AF; 275 | background-color: #D2E0E6; 276 | 277 | border-bottom: 1px solid #FFF; 278 | } 279 | #qunit-testresult .module-name { 280 | font-weight: 700; 281 | } 282 | 283 | /** Fixture */ 284 | 285 | #qunit-fixture { 286 | position: absolute; 287 | top: -10000px; 288 | left: -10000px; 289 | width: 1000px; 290 | height: 1000px; 291 | } 292 | -------------------------------------------------------------------------------- /test/vendor/runner.js: -------------------------------------------------------------------------------- 1 | //https://github.com/jonkemp/qunit-phantomjs-runner 2 | /* 3 | * QtWebKit-powered headless test runner using PhantomJS 4 | * 5 | * PhantomJS binaries: http://phantomjs.org/download.html 6 | * Requires PhantomJS 1.6+ (1.7+ recommended) 7 | * 8 | * Run with: 9 | * phantomjs runner.js [url-of-your-qunit-testsuite] 10 | * 11 | * e.g. 12 | * phantomjs runner.js http://localhost/qunit/test/index.html 13 | */ 14 | 15 | /*jshint latedef:false */ 16 | /*global phantom:false, require:false, console:false, window:false, QUnit:false */ 17 | 18 | (function () { 19 | 'use strict'; 20 | 21 | var url, page, timeout, 22 | args = require('system').args; 23 | 24 | // arg[0]: scriptName, args[1...]: arguments 25 | if (args.length < 2) { 26 | console.error('Usage:\n phantomjs [phantom arguments] runner.js [url-of-your-qunit-testsuite] [timeout-in-seconds]'); 27 | exit(1); 28 | } 29 | 30 | url = args[1]; 31 | 32 | if (args[2] !== undefined) { 33 | timeout = parseInt(args[2], 10); 34 | } 35 | 36 | page = require('webpage').create(); 37 | 38 | // Route `console.log()` calls from within the Page context to the main Phantom context (i.e. current `this`) 39 | page.onConsoleMessage = function (msg) { 40 | console.log(msg); 41 | }; 42 | 43 | page.onInitialized = function () { 44 | page.evaluate(addLogging); 45 | }; 46 | 47 | page.onCallback = function (message) { 48 | var result, 49 | failed; 50 | 51 | if (message) { 52 | if (message.name === 'QUnit.done') { 53 | result = message.data; 54 | failed = !result || !result.total || result.failed; 55 | 56 | if (!result.total) { 57 | console.error('No tests were executed. Are you loading tests asynchronously?'); 58 | } 59 | 60 | exit(failed ? 1 : 0); 61 | } 62 | } 63 | }; 64 | 65 | page.open(url, function (status) { 66 | if (status !== 'success') { 67 | console.error('Unable to access network: ' + status); 68 | exit(1); 69 | } else { 70 | // Cannot do this verification with the 'DOMContentLoaded' handler because it 71 | // will be too late to attach it if a page does not have any script tags. 72 | var qunitMissing = page.evaluate(function () { 73 | return (typeof QUnit === 'undefined' || !QUnit); 74 | }); 75 | if (qunitMissing) { 76 | console.error('The `QUnit` object is not present on this page.'); 77 | exit(1); 78 | } 79 | 80 | // Set a default timeout value if the user does not provide one 81 | if (typeof timeout === 'undefined') { 82 | timeout = 5; 83 | } 84 | 85 | // Set a timeout on the test running, otherwise tests with async problems will hang forever 86 | setTimeout(function () { 87 | console.error('The specified timeout of ' + timeout + ' seconds has expired. Aborting...'); 88 | exit(1); 89 | }, timeout * 1000); 90 | 91 | // Do nothing... the callback mechanism will handle everything! 92 | } 93 | }); 94 | 95 | function addLogging() { 96 | window.document.addEventListener('DOMContentLoaded', function () { 97 | var currentTestAssertions = []; 98 | 99 | QUnit.log(function (details) { 100 | var response; 101 | 102 | // Ignore passing assertions 103 | if (details.result) { 104 | return; 105 | } 106 | 107 | response = details.message || ''; 108 | 109 | if (typeof details.expected !== 'undefined') { 110 | if (response) { 111 | response += ', '; 112 | } 113 | 114 | response += 'expected: ' + details.expected + ', but was: ' + details.actual; 115 | } 116 | 117 | if (details.source) { 118 | response += '\n' + details.source; 119 | } 120 | 121 | currentTestAssertions.push('Failed assertion: ' + response); 122 | }); 123 | 124 | QUnit.testDone(function (result) { 125 | var i, 126 | len, 127 | name = ''; 128 | 129 | if (result.module) { 130 | name += result.module + ': '; 131 | } 132 | name += result.name; 133 | 134 | if (result.failed) { 135 | console.log('\n' + 'Test failed: ' + name); 136 | 137 | for (i = 0, len = currentTestAssertions.length; i < len; i++) { 138 | console.log(' ' + currentTestAssertions[i]); 139 | } 140 | } 141 | 142 | currentTestAssertions.length = 0; 143 | }); 144 | 145 | QUnit.done(function (result) { 146 | console.log('\n' + 'Took ' + result.runtime + 'ms to run ' + result.total + ' tests. ' + result.passed + ' passed, ' + result.failed + ' failed.'); 147 | 148 | if (typeof window.callPhantom === 'function') { 149 | window.callPhantom({ 150 | 'name': 'QUnit.done', 151 | 'data': result 152 | }); 153 | } 154 | }); 155 | }, false); 156 | } 157 | 158 | function exit(code) { 159 | if (page) { 160 | page.close(); 161 | } 162 | setTimeout(function () { 163 | phantom.exit(code); 164 | }, 0); 165 | } 166 | })(); 167 | --------------------------------------------------------------------------------