├── .github └── workflows │ ├── cd.yml │ ├── ci.yml │ └── prepublish.yml ├── .gitignore ├── .jshintrc ├── CHANGELOG.md ├── CONTRIBUTING.md ├── DEPLOYING.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── bower.json ├── docs ├── index.md ├── underscore.array.builders.js.md ├── underscore.array.selectors.js.md ├── underscore.collections.walk.js.md ├── underscore.function.arity.js.md ├── underscore.function.combinators.js.md ├── underscore.function.iterators.js.md ├── underscore.function.predicates.js.md ├── underscore.object.builders.js.md ├── underscore.object.selectors.js.md ├── underscore.util.existential.js.md ├── underscore.util.operators.js.md ├── underscore.util.strings.js.md └── underscore.util.trampolines.js.md ├── examples └── walk-examples.js.md ├── index.js ├── package.json ├── test ├── array.builders.js ├── array.selectors.js ├── collections.walk.js ├── dist-concat.html ├── dist-min.html ├── function.arity.js ├── function.combinators.js ├── function.dispatch.js ├── function.iterators.js ├── function.predicates.js ├── index.html ├── object.builders.js ├── object.selectors.js ├── util.existential.js ├── util.operators.js ├── util.strings.js └── util.trampolines.js ├── tocdoc.css ├── underscore.array.builders.js ├── underscore.array.selectors.js ├── underscore.collections.walk.js ├── underscore.function.arity.js ├── underscore.function.combinators.js ├── underscore.function.dispatch.js ├── underscore.function.iterators.js ├── underscore.function.predicates.js ├── underscore.object.builders.js ├── underscore.object.selectors.js ├── underscore.util.existential.js ├── underscore.util.operators.js ├── underscore.util.strings.js ├── underscore.util.trampolines.js └── yarn.lock /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | # This workflow is like CI, but also builds the documentation and deploys to NPM 2 | # and gh-pages. 3 | 4 | name: Continuous Deployment 5 | 6 | on: 7 | push: 8 | branches: [prepublish] 9 | workflow_run: 10 | workflows: [Prepublication staging] 11 | types: [completed] 12 | 13 | jobs: 14 | deploy: 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | with: 21 | ref: prepublish 22 | - name: Use Node.js 14.x 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: 14.x 26 | - name: Configure yarn cache path 27 | run: yarn config set cache-folder ~/.yarn-cache 28 | - name: Restore yarn cache 29 | uses: actions/cache@v2 30 | with: 31 | path: ~/.yarn-cache 32 | key: yarn-cache-14.x 33 | restore-keys: | 34 | yarn-cache- 35 | - name: Restore node_modules 36 | uses: actions/cache@v2 37 | with: 38 | path: node_modules 39 | key: node-modules-14.x-${{ hashFiles('yarn.lock') }} 40 | restore-keys: | 41 | node-modules-14.x- 42 | node-modules- 43 | - name: Install dependencies 44 | run: yarn 45 | - name: Build dist files and docs and run the tests 46 | run: yarn grunt dist docco tocdoc 47 | - name: Commit the build output 48 | uses: EndBug/add-and-commit@v5 49 | with: 50 | add: dist/ index.html 51 | message: Autocommit build output (continuous deployment) 52 | env: 53 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 54 | - name: Publish to NPM 55 | run: yarn publish 56 | env: 57 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 58 | - name: Merge into gh-pages 59 | uses: devmasx/merge-branch@v1.3.0 60 | with: 61 | type: now 62 | target_branch: gh-pages 63 | github_token: ${{ secrets.GITHUB_TOKEN }} 64 | - uses: actions/checkout@v2 65 | with: 66 | ref: gh-pages 67 | - name: Tag the release 68 | uses: Klemensas/action-autotag@stable 69 | with: 70 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 71 | tag_prefix: v 72 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install the node_modules, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Continuous Integration 5 | 6 | on: 7 | push: 8 | branches: [master] 9 | pull_request: 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | node-version: [10.x, 14.x] 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | - name: Configure yarn cache path 27 | run: yarn config set cache-folder ~/.yarn-cache 28 | - name: Restore yarn cache 29 | uses: actions/cache@v2 30 | with: 31 | path: ~/.yarn-cache 32 | key: yarn-cache-${{ matrix.node-version }} 33 | restore-keys: | 34 | yarn-cache- 35 | - name: Restore node_modules 36 | uses: actions/cache@v2 37 | with: 38 | path: node_modules 39 | key: node-modules-${{ matrix.node-version }}-${{ hashFiles('yarn.lock') }} 40 | restore-keys: | 41 | node-modules-${{ matrix.node-version }}- 42 | node-modules- 43 | - name: Install dependencies 44 | run: yarn 45 | - name: Run linter, concat, minifier and tests 46 | run: yarn dist 47 | -------------------------------------------------------------------------------- /.github/workflows/prepublish.yml: -------------------------------------------------------------------------------- 1 | # As an alternative to a manual push to the prepublish branch, this workflow can 2 | # be triggered on a release branch by assigning a special label to its pull 3 | # request in order to set the CD circus in motion. 4 | 5 | name: Prepublication staging 6 | 7 | on: 8 | pull_request: 9 | types: [labeled] 10 | # Would filter by branch here, but GH Actions wrongly decides not to 11 | # trigger the workflow if we do this. 12 | 13 | jobs: 14 | stage: 15 | runs-on: ubuntu-latest 16 | # Filtering by branch here instead. Credit due to @MiguelSavignano. 17 | # https://github.com/devmasx/merge-branch/issues/11 18 | if: contains(github.event.pull_request.head.ref, 'release/') || contains(github.event.pull_request.head.ref, 'hotfix/') 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Merge into prepublish 22 | uses: devmasx/merge-branch@v1.3.0 23 | with: 24 | label_name: ready to launch 25 | target_branch: prepublish 26 | github_token: ${{ secrets.GITHUB_TOKEN }} 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | raw 2 | node_modules 3 | .DS_Store 4 | docs/*.css 5 | docs/*.html 6 | docs/public 7 | examples/*.css 8 | examples/*.html 9 | examples/public 10 | bower_components/* 11 | dist/ 12 | /index.html 13 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "_": true 4 | }, 5 | "es3": true, 6 | "indent": 2, 7 | "camelcase": true, 8 | "eqnull": true, 9 | "forin": true, 10 | "newcap": true, 11 | "-W058": false 12 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### Changelog 2 | 3 | #### 0.3.0 4 | 5 | * Contrib now requires Underscore 1.6.0 or higher. 6 | * Rename `partition` and `partitionAll` to `chunk` and `chunkAll` to avoid name conflicts with Underscore's `partition` - [#115][115] 7 | * Added `toQuery` and `fromQuery` - [#134][134] 8 | * Switch `always` to an alias of Underscore's `constant`. - [#132][132] 9 | * The combinators sub-library now supports method combinators - [#14][14] 10 | 11 | [115]:https://github.com/documentcloud/underscore-contrib/issues/115 12 | [134]:https://github.com/documentcloud/underscore-contrib/issues/134 13 | [132]:https://github.com/documentcloud/underscore-contrib/issues/132 14 | [14]:https://github.com/documentcloud/underscore-contrib/issues/14 -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute to Underscore-contrib 2 | 3 | ## 1. Search the existing issues 4 | 5 | Before you open a ticket or send a pull request, [search](https://github.com/documentcloud/underscore-contrib/issues) for previous discussions about the same feature or issue. Add to the earlier ticket if you find one. 6 | 7 | ## 2. Fork the project 8 | 9 | Create your own fork of contrib where you can make your changes. 10 | 11 | ## 3. Install development dependencies 12 | 13 | Make sure you have [Node.js][node] and [Yarn][yarn] installed. Then install contrib's development dependencies with `yarn`. Note that these dependencies include the [Grunt CLI][cli] which we use for task automation. 14 | 15 | ## 4. Change the code 16 | 17 | Make your code changes, ensuring they adhere to the same [coding style as Underscore][style]. Do **not** edit the files in `dist/`. 18 | 19 | Make any necessary updates to the qUnit tests found in `test/`. Run `yarn test` to catch any test failures or lint issues. You can also use `yarn grunt watch:test` to get instant feedback each time you save a file. 20 | 21 | ## 5. Update the docs 22 | 23 | Make any necessary documentation updates in `docs/`. Do **not** edit `index.html` directly. 24 | 25 | After updating the docs, run `yarn tocdoc` to rebuild the `index.html` file. Visually inspect `index.html` in your browser to ensure the generated docs look nice. 26 | 27 | ## 6. Send a pull request 28 | 29 | Send a pull request to `documentcloud/underscore-contrib` for feedback. 30 | 31 | If modifications are necessary, make the changes and rerun `yarn test` or `yarn tocdoc` as needed. 32 | 33 | And hopefully your pull request will be merged by the core team! :-) 34 | 35 | [style]:https://github.com/documentcloud/underscore/blob/master/underscore.js 36 | [node]:http://nodejs.org/ 37 | [yarn]:https://classic.yarnpkg.com/ 38 | [cli]:http://gruntjs.com/getting-started#installing-the-cli 39 | -------------------------------------------------------------------------------- /DEPLOYING.md: -------------------------------------------------------------------------------- 1 | # How to cut a new release for Underscore-contrib 2 | 3 | This is a checklist for repository maintainers. It covers all the steps involved in releasing a new version, including publishing to NPM and updating the `gh-pages` branch. We have tried to automate as many of these steps as possible using GitHub Actions. The workflows involved are in the (hidden) `.github` directory. 4 | 5 | A "regular" release includes new changes that were introduced to the `master` branch since the previous release. A *hotfix* release instead skips any such changes and only addresses urgent problems with the previous release. 6 | 7 | 8 | ## Steps 9 | 10 | 1. Ensure your local `master` branch descends from the previous release branch and that it is in a state that you want to release. **Exception:** for hotfixes, ensure you have an up-to-date local copy of the previous release branch instead. 11 | 2. Decide on the next version number, respecting [semver][semver]. For the sake of example we'll use `1.2.3` as a placeholder version throughout this checklist. 12 | 3. Create a `release/1.2.3` branch based on `master`. **Exception:** if this is a hotfix, start from the previous release branch instead and call it `hotfix/1.2.3`. 13 | 4. Bump the version number in the `package.json` and the `yarn.lock` by running the command `yarn version --no-git-tag-version`, with the extra flag `--major`, `--minor` or `--patch` depending on which part of the version number you're incrementing (e.g., `--patch` if you're bumping from 1.2.2 to 1.2.3). Note that you can configure `yarn` to always include the `--no-git-tag-version` flag so you don't have to type it every time. 14 | 5. Bump the version number in the source code and in `docs/index.md` accordingly. 15 | 6. Commit the changes from steps 4 and 5 with a commit message of the format `Bump the version to 1.2.3`. 16 | 7. Add more commits to extend the change log and to update any other documentation that might require it. Ensure that all documentation looks good. 17 | 8. Push the release branch and create a pull request against `master` (also if it is a hotfix). 18 | 9. At your option, have the release branch reviewed and add more commits to satisfy any change requests. 19 | 10. The "Continuous Integration" workflow will trigger automatically on the pull request. This runs the test suite against multiple environments. Wait for all checks to complete. 20 | 11. If any checks fail, add more commits to fix and repeat from step 10. 21 | 12. When all reviewers are satisfied and all checks pass, apply the "ready to launch" label to the pull request. This will trigger the "Prepublication staging" workflow, which will merge the release branch into the `prepublish` branch. Merge manually if the workflow fails for whatever reason. **Note:** do *not* merge into `master` yet. 22 | 13. The merging of new changes into `prepublish` will trigger the "Continuous Deployment" workflow, which is documented below. **Pay attention as the deployment workflow progresses.** 23 | 14. If the deployment workflow fails, identify the failing step and address as quickly as possible. Possible solution steps include: 24 | - Adding new commits to the release branch and repeating from step 12 (most likely if the docs fail to build for some reason). 25 | - Manually pushing new commits to the `prepublish` branch and repeating from step 13 (this might be needed in case of merge conflicts, although this is highly unlikely). 26 | - Manually running `yarn publish` (if something is wrong with the NPM registry or the authentication credentials). 27 | - Manually merging `prepublish` into `gh-pages` (in unlikely case of merge conflicts). 28 | - Manually tagging the release on `gh-pages`. 29 | 15. When the deployment workflow has completed, double-check that the new version was published to NPM, that the website was updated and that the repository contains a tag for the new release. 30 | 16. Finally merge the release branch into `master`, but keep the branch around for a few days in case you need to do a hotfix. 31 | 17. Delete the release branch. You can still restore it if necessary, by tracing the history graph two commits back from the release tag. 32 | 33 | 34 | ## Automated continuous deployment workflow 35 | 36 | This workflow is defined in `.github/workflows/cd.yml`. Note that roughly the first half of the steps in that file consists of basic boilerplate to check out the source and install dependencies. 37 | 38 | The publishing to NPM depends on a [secret][secrets] named `NPM_TOKEN`, representing the API token of the NPM account that owns the `underscore-contrib` package. Only admins of the documentcloud organization can access this setting. 39 | 40 | 1. Checkout the source, restore caches and install the dependencies. 41 | 2. Run `yarn grunt dist docco tocdoc`. 42 | 3. Commit the output of the previous step to the `prepublish` branch. 43 | 4. Publish to NPM. 44 | 5. Merge the `prepublish` branch into `gh-pages`. 45 | 6. Tag the tip of `gh-pages` with the version number in the `package.json`. 46 | 47 | 48 | [semver]: https://semver.org 49 | [secrets]: https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets 50 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.loadNpmTasks("grunt-contrib-concat"); 3 | grunt.loadNpmTasks("grunt-contrib-uglify"); 4 | grunt.loadNpmTasks('grunt-contrib-qunit'); 5 | grunt.loadNpmTasks('grunt-contrib-watch'); 6 | grunt.loadNpmTasks('grunt-contrib-jshint'); 7 | grunt.loadNpmTasks("grunt-docco"); 8 | grunt.loadNpmTasks("grunt-tocdoc"); 9 | 10 | grunt.initConfig({ 11 | pkg: grunt.file.readJSON('package.json'), 12 | 13 | contribBanner: 14 | "// <%= 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 | "// > <%= pkg.name %> may be freely distributed under the <%= pkg.license %> license.\n\n", 19 | 20 | concat: { 21 | all: { 22 | src: "underscore.*.js", 23 | dest: "dist/underscore-contrib.js", 24 | options: { banner: "<%= contribBanner %>" } 25 | } 26 | }, 27 | 28 | uglify: { 29 | all: { 30 | files: { "dist/underscore-contrib.min.js": "dist/underscore-contrib.js" }, 31 | options: { banner: "<%= contribBanner %>" } 32 | } 33 | }, 34 | 35 | qunit: { 36 | main: ['test/index.html'], 37 | concat: ['test/dist-concat.html'], 38 | min: ['test/dist-min.html'] 39 | }, 40 | 41 | jshint: { 42 | all: [ 43 | "*.js", 44 | "test/*.js" 45 | ], 46 | options: { 47 | jshintrc: true 48 | } 49 | }, 50 | 51 | watch: { 52 | test: { 53 | files: [ 54 | "underscore.*.js", 55 | "test/*.js" 56 | ], 57 | tasks: ["test"] 58 | } 59 | }, 60 | 61 | tocdoc: { 62 | api: { 63 | files: { 64 | 'index.html': ['docs/*.md', 'CHANGELOG.md'] 65 | }, 66 | options: { 67 | scripts: [ 68 | 'test/vendor/underscore.js', 69 | 'dist/underscore-contrib.js' 70 | ] 71 | } 72 | } 73 | }, 74 | 75 | docco: { 76 | docs: { 77 | src: ['docs/*.md'], 78 | options: { 79 | output: 'docs/' 80 | } 81 | }, 82 | examples: { 83 | src: ['examples/*.md'], 84 | options: { 85 | output: 'examples/' 86 | } 87 | } 88 | } 89 | }); 90 | 91 | grunt.registerTask('test', ['jshint', 'qunit:main']); 92 | grunt.registerTask('default', ['test']); 93 | grunt.registerTask('dist', ['test', 'concat', 'qunit:concat', 'uglify', 'qunit:min']); 94 | grunt.registerTask('doc', ['test', 'tocdoc']); 95 | }; 96 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Jeremy Ashkenas, Michael Fogus, DocumentCloud and Investigative Reporters & Editors 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | underscore-contrib 2 | ================== 3 | 4 | The brass buckles on Underscore's utility belt -- a contributors' library for [Underscore](http://underscorejs.org/). 5 | 6 | Links 7 | ----- 8 | 9 | * [Documentation](http://documentcloud.github.io/underscore-contrib/) 10 | * [Source repository](https://github.com/documentcloud/underscore-contrib) 11 | * [Tickets and bug reports](https://github.com/documentcloud/underscore-contrib/issues?state=open) 12 | * [Maintainer's website](http://www.fogus.me) 13 | 14 | Why underscore-contrib? 15 | ----------------------- 16 | 17 | While Underscore provides a bevy of useful tools to support functional programming in JavaScript, it can't 18 | (and shouldn't) be everything to everyone. Underscore-contrib is intended as a home for functions that, for 19 | various reasons, don't belong in Underscore proper. In particular, it aims to be: 20 | 21 | * a home for functions that are limited in scope, but solve certain point problems, and 22 | * a proving ground for features that belong in Underscore proper, but need some advocacy and/or evolution 23 | (or devolution) to get them there. 24 | 25 | Use 26 | --- 27 | 28 | First, you’ll need Underscore. Then you can grab the relevant underscore-contrib libraries and simply add 29 | something 30 | like the following to your pages: 31 | 32 | 33 | 34 | 35 | At the moment there are no cross-contrib dependencies (i.e. each library can stand by itself), but that may 36 | change in the future. 37 | 38 | Contributing 39 | ------------ 40 | 41 | There is still a lot of work to do around perf, documentation, examples, testing and distribution so any help 42 | in those areas is welcomed. Pull requests are accepted, but please search the [issues](https://github.com/documentcloud/underscore-contrib/issues) 43 | before proposing a new sub-contrib or addition. Additionally, all patches and proposals should have strong 44 | documentation, motivating cases and tests. It would be nice if we could not only provide useful tools built on 45 | Underscore, but also provide an educational experience for why and how one might use them. 46 | 47 | Other (potentially) useful sub-contribs include the following: 48 | 49 | * String utilities 50 | * Date/time utilities 51 | * Validators 52 | * Iterators 53 | * Generators 54 | * Promises 55 | * Monads 56 | * Currying 57 | * Laziness 58 | * Multimethods 59 | 60 | What do these mean? Well, that’s up for discussion. :-) 61 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "underscore-contrib", 3 | "version": "0.3.0", 4 | "main": "dist/underscore-contrib.min.js", 5 | "homepage": "https://github.com/documentcloud/underscore-contrib", 6 | "authors": [ 7 | "fogus " 8 | ], 9 | "description": "The brass buckles on Underscore's utility belt", 10 | "keywords": [ 11 | "underscore", 12 | "underscorejs", 13 | "documentcloud", 14 | "functional", 15 | "javascript" 16 | ], 17 | "license": "MIT", 18 | "ignore": [ 19 | "**/.*", 20 | "node_modules", 21 | "bower_components", 22 | "test", 23 | "tests" 24 | ], 25 | "dependencies" : { 26 | "underscore" : ">=1.6.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Underscore-contrib (0.3.0) 2 | 3 | > The brass buckles on Underscore's utility belt - a contributors' library for [Underscore](http://underscorejs.org/). 4 | 5 | ## Introduction 6 | 7 | ### Places 8 | 9 | * [Documentation](http://documentcloud.github.io/underscore-contrib/) 10 | * [Source repository](https://github.com/documentcloud/underscore-contrib) 11 | * [Tickets and bug reports](https://github.com/documentcloud/underscore-contrib/issues?state=open) 12 | * [Maintainer's website](http://www.fogus.me) 13 | 14 | ### Why underscore-contrib? 15 | 16 | While Underscore provides a bevy of useful tools to support functional programming in JavaScript, it can't 17 | (and shouldn't) be everything to everyone. Underscore-contrib is intended as a home for functions that, for 18 | various reasons, don't belong in Underscore proper. In particular, it aims to be: 19 | 20 | * a home for functions that are limited in scope, but solve certain point problems, and 21 | * a proving ground for features that belong in Underscore proper, but need some advocacy and/or evolution 22 | (or devolution) to get them there. 23 | 24 | ### Use 25 | 26 | #### In the Browser 27 | 28 | First, you'll need Underscore **version 1.6.0 or higher**. Then you can grab the relevant underscore-contrib sub-libraries and simply add something like 29 | the following to your pages: 30 | 31 | ```html 32 | 33 | 34 | ``` 35 | 36 | At the moment there are no cross-contrib dependencies (i.e. each sub-library 37 | can stand by itself), but that may change in the future. 38 | 39 | #### In Node.js 40 | 41 | Using contrib in Node is very simple. Just install it with npm: 42 | 43 | ``` 44 | npm install underscore-contrib --save 45 | ``` 46 | 47 | Then require it within your project like so: 48 | 49 | ```javascript 50 | var _ = require('underscore-contrib'); 51 | ``` 52 | 53 | The `_` variable will be a copy of Underscore with contrib's methods already 54 | mixed in. 55 | 56 | ### License 57 | 58 | _.contrib is open sourced under the [MIT license](https://github.com/documentcloud/underscore-contrib/blob/master/LICENSE). 59 | 60 | ## Sub-libraries 61 | 62 | The _.contrib library currently contains a number of related capabilities, aggregated into the following files. 63 | 64 | - [underscore.array.builders](#array.builders) - functions to build arrays 65 | - [underscore.array.selectors](#array.selectors) - functions to take things from arrays 66 | - [underscore.collections.walk](#collections.walk) - functions to walk and transform nested JavaScript objects 67 | - [underscore.function.arity](#function.arity) - functions to manipulate and fix function argument arity 68 | - [underscore.function.combinators](#function.combinators) - functions to combine functions to make new functions 69 | - [underscore.function.iterators](#function.iterators) - functions to lazily produce, manipulate and consume sequence iterators 70 | - [underscore.function.predicates](#function.predicates) - functions that return `true` or `false` based on some criteria 71 | - [underscore.object.builders](#object.builders) - functions to build JavaScript objects 72 | - [underscore.object.selectors](#object.selectors) - functions to pick things from JavaScript objects 73 | - [underscore.util.existential](#util.existential) - functions that check for the existence or truthiness of JavaScript data types 74 | - [underscore.util.operators](#util.operators) - functions that wrap common (or missing) JavaScript operators 75 | - [underscore.util.strings](#util.strings) - functions to work with strings 76 | - [underscore.util.trampolines](#util.trampolines) - functions to facilitate calling functions recursively without blowing the stack 77 | 78 | The links above are to the annotated source code. Full-blown _.contrib documentation is in the works. Contributors welcomed. 79 | 80 | -------------------------------------------------------------------------------- /docs/underscore.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:** `_.partitionBy(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 | -------------------------------------------------------------------------------- 166 | -------------------------------------------------------------------------------- /docs/underscore.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 | containsAtLeast: function(list, count, value) 15 | containsAtMost: function(list, count, value) 16 | _.walk.collect = _.walk.map; 17 | -------------------------------------------------------------------------------- /docs/underscore.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/underscore.function.iterators.js.md: -------------------------------------------------------------------------------- 1 | ### function.iterators 2 | 3 | > Functions to iterate over collections. 4 | 5 | -------------------------------------------------------------------------------- 6 | 7 | #### iterators.accumulate 8 | 9 | **Signature:** `_.iterators.accumulate(iter:Function, binaryFn:Function, initial:Any)` 10 | 11 | Returns a function that when called will iterate one step with `iter` and return 12 | the value currently accumulated by using `binaryFn`. The function will return `undefined` once all values have been iterated over. 13 | 14 | ```javascript 15 | var generalsIterator = _.iterators.List(["Hannibal", "Scipio"]); 16 | 17 | function countLetters(memo, element) { 18 | return memo + element.length; 19 | } 20 | 21 | var generalsAcc = _.iterators.accumulate(generalsIterator, countLetters, 0); 22 | 23 | generalsAcc(); 24 | // => 8 25 | 26 | generalsAcc(); 27 | // => 14 28 | 29 | generalsAcc(); 30 | // => undefined 31 | ``` 32 | 33 | -------------------------------------------------------------------------------- 34 | 35 | #### iterators.accumulateWithReturn 36 | 37 | **Signature:** `_.iterators.accumulateWithReturn(iter:Function, binaryFn:Function, initial:Any)` 38 | 39 | Acts similarly to accumulate, except that `binaryFn` is expected to return an 40 | array of two elements. The value of the first element is given to the next run 41 | of `binaryFn`. The value of the second element is yielded by the iterator. 42 | 43 | 44 | ```javascript 45 | var fiveIter = _.iterators.List([1, 2, 3, 4, 5]); 46 | 47 | function adderWithMessage (state, element) { 48 | return [state + element, 'Total is ' + (state + element)]; 49 | } 50 | 51 | var i = _.iterators.accumulateWithReturn(fiveIter, adderWithMessage, 0); 52 | 53 | i(); 54 | // => "Total is 1" 55 | 56 | i(); 57 | // => "Total is 3" 58 | 59 | i(); 60 | // => "Total is 6" 61 | ``` 62 | 63 | -------------------------------------------------------------------------------- 64 | 65 | #### iterators.drop 66 | 67 | **Signature:** `_.iterators.drop(iter:Function[, numberToDrop:Number])` 68 | 69 | Given an iterator function `iter`, will return a new iterator which iterates 70 | over the same values as `iter`, except that the first `numberToDrop` values 71 | will be omitted. If `numberToDrop` is not provided, it will default to `1`. 72 | 73 | ```javascript 74 | var deityIter = _.iterators.List(["Zeus", "Apollo", "Athena", "Aphrodite"]); 75 | 76 | var goddessIter = _.iterators.drop(deityIter, 2); 77 | 78 | goddessIter(); 79 | // => "Athena" 80 | 81 | goddessIter(); 82 | // => "Aphrodite" 83 | ``` 84 | 85 | -------------------------------------------------------------------------------- 86 | 87 | #### iterators.foldl 88 | 89 | **Signature:** `_.iterators.foldl(iter:Function, binaryFn:Function[, seed:Any])` 90 | 91 | **Aliases:** `iterators.reduce` 92 | 93 | Boils down the values given by `iter` into a single value. The `seed` is the 94 | initial state. The `binaryFn` is given two arguments: the `seed` and the 95 | current value yielded by `iter`. 96 | 97 | ```javascript 98 | var sybylIter = _.iterators.List(["cumaean", "tiburtine"]); 99 | 100 | function commaString (a, b) { return a + ", " + b; } 101 | 102 | _.iterators.foldl(sybylIter, commaString); 103 | // => "cumaean, tiburtine" 104 | ``` 105 | 106 | -------------------------------------------------------------------------------- 107 | 108 | #### iterators.K 109 | 110 | **Signature:** `_.iterators.K(value:Any)` 111 | 112 | **Aliases:** `iterators.constant` 113 | 114 | Returns a function that when invoked will always return `value`. 115 | 116 | ```javascript 117 | var ceasar = _.iterators.K("Ceasar"); 118 | 119 | ceasar(); 120 | // => "ceasar" 121 | ``` 122 | 123 | -------------------------------------------------------------------------------- 124 | 125 | #### iterators.List 126 | 127 | **Signature:** `_.iterators.List(array:Array)` 128 | 129 | Returns an iterator that when invoked will iterate over the contents of `array`. 130 | 131 | ```javascript 132 | var triumvirIter = _.iterators.List(["Ceasar", "Pompey", "Crassus"]); 133 | 134 | triumvirIter(); 135 | // => "Ceasar" 136 | 137 | triumvirIter(); 138 | // => "Pompey" 139 | 140 | triumvirIter(); 141 | // => "Crassus" 142 | ``` 143 | 144 | -------------------------------------------------------------------------------- 145 | 146 | #### iterators.map 147 | 148 | **Signature:** `_.iterators.map(iter:Function, unaryFn:Function)` 149 | 150 | Returns a new iterator function which on each iteration will return the result 151 | of running `iter`'s current value through `unaryFn`. 152 | 153 | ```javascript 154 | var notablesIter = _.iterators.List(["Socrates", "Plato"]); 155 | 156 | function postfixAthenian (val) { 157 | return val + ", Athenian"; 158 | } 159 | 160 | var notableAtheniansIter = _.iterators.map(notablesIter, postfixAthenian); 161 | 162 | notableAtheniansIter(); 163 | // => "Socrates, Athenian" 164 | 165 | notableAtheniansIter(); 166 | // => "Plato, Athenian" 167 | ``` 168 | 169 | -------------------------------------------------------------------------------- 170 | 171 | #### iterators.mapcat 172 | 173 | **Signature:** `_.iterators.mapcat(iter:Function, unaryFn:Function)` 174 | 175 | Returns an iterator which is the result of flattening the contents of `iter`, 176 | and mapping the results with `unaryFn`. 177 | 178 | ```javascript 179 | function naturalSmallerThan (x) { 180 | return _.iterators.List(_.range(0, x)); 181 | } 182 | 183 | var treeIter = _.iterators.Tree([1, [2]]); 184 | 185 | var smallerThanIter = _.iterators.mapcat(treeIter, naturalSmallerThan); 186 | 187 | smallerThanIter(); 188 | // => 0 189 | 190 | smallerThanIter(); 191 | // => 0 192 | 193 | smallerThanIter(); 194 | // => 1 195 | ``` 196 | 197 | -------------------------------------------------------------------------------- 198 | 199 | #### iterators.numbers 200 | 201 | **Signature:** `_.iterators.numbers([start:Number])` 202 | 203 | Returns an iterator of integers which will begin with `start` and increment by 204 | one for each invocation. If `start` is not provided it will default to `1`. 205 | 206 | ```javascript 207 | var twoAndUp = _.iterators.numbers(2); 208 | 209 | twoAndUp(); 210 | // => 2 211 | 212 | twoAndUp(); 213 | // => 3 214 | 215 | twoAndUp(); 216 | // => 4 217 | ``` 218 | 219 | -------------------------------------------------------------------------------- 220 | 221 | #### iterators.range 222 | 223 | **Signature:** `_.iterators.range([from:Number, to:Number, by:Number])` 224 | 225 | Returns an iterator whose values consist of numbers beginning with `from`, ending with `to`, in steps of size `by`. 226 | 227 | ```javascript 228 | var by5 = _.iterators.range(5, Infinity, 5); 229 | 230 | by5(); 231 | // => 5 232 | 233 | by5(); 234 | // => 10 235 | 236 | by5(); 237 | // => 15 238 | ``` 239 | 240 | -------------------------------------------------------------------------------- 241 | 242 | #### iterators.reject 243 | 244 | **Signature:** `_.iterators.reject(iter:Function, unaryPredicatFn:Function)` 245 | 246 | Returns an iterator consisting of the values of `iter` which are not flagged 247 | `true` by `unaryPredicateFn`. 248 | 249 | ```javascript 250 | var philosophers = ["Anaximander", "Socrates", "Heraclitus"]; 251 | 252 | var philosophersIter = _.iterators.List(philosophers); 253 | 254 | function isSocrates (val) { 255 | return val === "Socrates"; 256 | } 257 | 258 | var preSocraticsIter = _.iterators.reject(philosophersIter, isSocrates); 259 | 260 | preSocraticsIter() 261 | // => "Anaximander" 262 | 263 | preSocraticsIter() 264 | // => "Heraclitus" 265 | ``` 266 | 267 | -------------------------------------------------------------------------------- 268 | 269 | #### iterators.select 270 | 271 | **Signature:** `_.iterators.select(iter:Function, unaryPredicatFn:Function)` 272 | 273 | **Aliases:** `iterators.find`, `iteraters.filter` 274 | 275 | Returns an iterator consisting of the values of `iter` which are flagged 276 | `true` by `unaryPredicateFn`. 277 | 278 | ```javascript 279 | var philosophers = ["Anaximander", "Socrates", "Heraclitus"]; 280 | 281 | var philosophersIter = _.iterators.List(philosophers); 282 | 283 | function isSocrates (val) { 284 | return val === "Socrates"; 285 | } 286 | 287 | var socraticIter = _.iterators.select(philosophersIter, isSocrates); 288 | 289 | socraticIter() 290 | // => "Socrates" 291 | ``` 292 | 293 | -------------------------------------------------------------------------------- 294 | 295 | #### iterators.slice 296 | 297 | **Signature:** `_.iterators.slice(iter:Function, numberToDrop:Number, numberToTake:Number)` 298 | 299 | Returns an iterator whose values consist of `iter`'s after removing 300 | `numberToDrop` from the head, and a maxiumum of `numberToTake` of the remaining. 301 | If `numberToTake` is not specified, all of `iter`'s remaining values will be 302 | used. 303 | 304 | ```javascript 305 | var emperors = ["Augustus", "Tiberius", "Caligula", "Claudius"]; 306 | 307 | var emperorsIter = _.iterators.List(emperors); 308 | 309 | var middleEmperorsIter = _.iterators.slice(emperorsIter, 1, 2); 310 | 311 | middleEmperorsIter(); 312 | // => "Tiberius" 313 | 314 | middleEmperorsIter(); 315 | // => "Caligula" 316 | 317 | middleEmperorsIter(); 318 | // => undefined 319 | ``` 320 | 321 | -------------------------------------------------------------------------------- 322 | 323 | #### iterators.take 324 | 325 | **Signature:** `_.iterators.take(iter:Function[, numberToTake:Number])` 326 | 327 | Returns an iterator consisting of the first `numberToTake` values yielded by 328 | `iter`. If `numberToTake` is not provided, it will default to `1`. 329 | 330 | ```javascript 331 | var byzantineEmperors = ["Constantine", "Constantius", "Constans"]; 332 | 333 | var byzantineEmperorsIter = _.iterators.List(byzantineEmperors); 334 | 335 | var firstTwoEmperorsIter = _.iterators.take(byzantineEmperorsIter, 2); 336 | 337 | firstTwoEmperorsIter(); 338 | // => "Constantine" 339 | 340 | firstTwoEmperorsIter(); 341 | // => "Constantius" 342 | 343 | firstTwoEmperorsIter(); 344 | // => undefined 345 | ``` 346 | 347 | -------------------------------------------------------------------------------- 348 | 349 | #### iterators.Tree 350 | 351 | **Signature:** `_.iterators.Tree(array:Array);` 352 | 353 | Returns an iterator that yields the individual values of a tree `array`. 354 | 355 | ```javascript 356 | var rulers = ["Augustus", ["Constantine"], ["Leo", ["Charlemagne"]]]; 357 | 358 | var rulersIter = _.iterators.Tree(rulers); 359 | 360 | rulersIter(); 361 | // => "Augustus" 362 | 363 | rulersIter(); 364 | // => "Constantine" 365 | 366 | rulersIter(); 367 | // => "Leo" 368 | 369 | rulersIter(); 370 | // => "Charlemagne" 371 | ``` 372 | 373 | -------------------------------------------------------------------------------- 374 | 375 | #### iterators.unfold 376 | 377 | **Signature:** `_.iterators.unfold(seed:Any, unaryFn:Function)` 378 | 379 | ```javascript 380 | function greatify (val) { 381 | return val + " the Great"; 382 | } 383 | 384 | var greatIter = _.iterators.unfold("Constantine", greatify); 385 | 386 | greatIter(); 387 | // => "Constantine the Great" 388 | 389 | greatIter(); 390 | // => "Constantine the Great the Great" 391 | 392 | greatIter(); 393 | // => "Constantine the Great the Great the Great" 394 | ``` 395 | 396 | -------------------------------------------------------------------------------- 397 | 398 | #### iterators.unfoldWithReturn 399 | 400 | **Signature:** `_.iterators.unfold(seed:Any, unaryFn:Function)` 401 | 402 | Acts similarly to unfold, except that `unaryFn` is expected to return an array 403 | with two elements. The value of the first element is given to the next run of 404 | `unaryFn`. The value of the second element is yielded by the iterator. 405 | 406 | ```javascript 407 | var i = _.iterators.unfoldWithReturn(1, function(n) { 408 | return [n + 1, n * n]; 409 | }); 410 | 411 | i(); 412 | // => 1 413 | 414 | i(); 415 | // => 4 416 | 417 | i(); 418 | // => 9 419 | ``` 420 | -------------------------------------------------------------------------------- /docs/underscore.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:** Underscore 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/underscore.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 | Returns a new object resulting from merging the passed objects. Objects 28 | are processed in order, so each will override properties of the same 29 | name occurring in earlier arguments. 30 | 31 | Returns `null` if called without arguments. 32 | 33 | ```javascript 34 | var a = {a: "alpha"}; 35 | var b = {b: "beta"}; 36 | 37 | var threeGreekLetters = _.merge(a, b, {g: "gamma"}); 38 | 39 | a; 40 | // => {a: "alpha"} 41 | 42 | b; 43 | // => {b: "beta"} 44 | 45 | threeGreekLetters; 46 | // => { a: "alpha", b: "beta", g: "gamma" } 47 | ``` 48 | 49 | -------------------------------------------------------------------------------- 50 | 51 | #### renameKeys 52 | 53 | **Signature:** `_.renameKeys(obj:Object, keyMap:Object)` 54 | 55 | Takes an object (`obj`) and a map of keys (`keyMap`) and returns a new object 56 | where the keys of `obj` have been renamed as specified in `keyMap`. 57 | 58 | ```javascript 59 | _.renameKeys({ a: 1, b: 2 }, { a: "alpha", b: "beta" }); 60 | // => { alpha: 1, beta: 2 } 61 | ``` 62 | 63 | -------------------------------------------------------------------------------- 64 | 65 | #### setPath 66 | 67 | **Signature:** `_.setPath(obj:Object, value:Any, ks:Array, defaultValue:Any)` 68 | 69 | Sets the value of a property at any depth in `obj` based on the path described 70 | by the `ks` array. If any of the properties in the `ks` path don't exist, they 71 | will be created with `defaultValue`. 72 | 73 | Note that the original object will *not* be mutated. Instead, `obj` will 74 | be cloned deeply. 75 | 76 | 77 | 78 | ```javascript 79 | 80 | var obj = {}; 81 | 82 | var plotinusObj = _.setPath(obj, "Plotinus", ["Platonism", "Neoplatonism"], {}); 83 | 84 | obj; 85 | // => {} 86 | 87 | plotinusObj; 88 | // => { Platonism: { Neoplatonism: "Plotinus" } } 89 | 90 | obj === plotinusObj; 91 | // => false; 92 | 93 | ``` 94 | 95 | -------------------------------------------------------------------------------- 96 | 97 | #### snapshot 98 | 99 | **Signature:** `_.snapshot(obj:Object)` 100 | 101 | Snapshots/clones an object deeply. 102 | 103 | ```javascript 104 | var schools = { plato: "Academy", aristotle: "Lyceum" }; 105 | 106 | _.snapshot(schools); 107 | // => { plato: "Academy", aristotle: "Lyceum" } 108 | 109 | schools === _.snapshot(schools); 110 | // => false 111 | ``` 112 | 113 | -------------------------------------------------------------------------------- 114 | 115 | #### updatePath 116 | 117 | **Signature:** `_.updatePath(obj:Object, fun:Function, ks:Array, defaultValue:Any)` 118 | 119 | Updates the value at any depth in a nested object based on the path described by 120 | the `ks` array. The function `fun` is called with the current value and is 121 | expected to return a replacement value. If no keys are provided, then the 122 | object itself is presented to `fun`. If a property in the path is missing, then 123 | it will be created with `defaultValue`. 124 | 125 | Note that the original object will *not* be mutated. Instead, `obj` will 126 | be cloned deeply. 127 | 128 | ```javascript 129 | var imperialize = function (val) { 130 | if (val == "Republic") return "Empire"; 131 | else return val; 132 | }; 133 | 134 | _.updatePath({ rome: "Republic" }, imperialize, ["rome"]); 135 | // => { rome: "Empire" } 136 | 137 | var obj = { earth: { rome: "Republic" } }; 138 | var imperialObj = _.updatePath(obj, imperialize, ["earth", "rome"]); 139 | 140 | imperialObj; 141 | // => { earth: { rome: "Empire" }} 142 | 143 | obj; 144 | // => { earth: { rome: "Republic" }} 145 | 146 | obj === imperialObj; 147 | // => false 148 | ``` 149 | 150 | -------------------------------------------------------------------------------- 151 | 152 | #### omitPath 153 | 154 | **Signature:** `_.omitPath(obj:Object, ks:String|Array)` 155 | 156 | Returns a copy of `obj` excluding the value represented by the `ks` path. 157 | Path may be given as an array or as a dot-separated string. 158 | 159 | ```javascript 160 | var test = { 161 | foo: true, 162 | bar: false, 163 | baz: 42, 164 | dada: { 165 | carlos: { 166 | pepe: 9 167 | }, 168 | pedro: 'pedro' 169 | } 170 | }; 171 | 172 | _.omitPath(test, 'dada.carlos.pepe'); 173 | // => {foo: true, bar: false, baz: 42, dada: {carlos: {}, pedro: 'pedro'}} 174 | ``` 175 | -------------------------------------------------------------------------------- /docs/underscore.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 of key names or as a single string 50 | using JavaScript property access notation. 51 | Returns `undefined` if the path cannot be reached. 52 | 53 | ```javascript 54 | var countries = { 55 | greece: { 56 | athens: { 57 | playwright: "Sophocles" 58 | }, 59 | notableFigures: ["Alexander", "Hippocrates", "Thales"] 60 | } 61 | } 62 | }; 63 | 64 | _.getPath(countries, "greece.athens.playwright"); 65 | // => "Sophocles" 66 | 67 | _.getPath(countries, "greece.sparta.playwright"); 68 | // => undefined 69 | 70 | _.getPath(countries, ["greece", "athens", "playwright"]); 71 | // => "Sophocles" 72 | 73 | _.getPath(countries, ["greece", "sparta", "playwright"]); 74 | // => undefined 75 | 76 | _.getPath(countries, ["greece", "notableFigures", 1]); 77 | // => "Hippocrates" 78 | 79 | _.getPath(countries, "greece.notableFigures[2]"); 80 | // => "Thales" 81 | 82 | _.getPath(countries, "greece['notableFigures'][2]") 83 | // => "Thales" 84 | ``` 85 | 86 | -------------------------------------------------------------------------------- 87 | 88 | #### hasPath 89 | 90 | **Signature:** `_.hasPath(obj:Object, ks:String|Array)` 91 | 92 | Returns a boolean indicating whether there is a property at the path described 93 | by the keys given. Keys may be given as an array of key names or as a single string 94 | using JavaScript property access notation. 95 | 96 | ```javascript 97 | var countries = { 98 | greece: { 99 | athens: { 100 | playwright: "Sophocles" 101 | }, 102 | notableFigures: ["Alexander", "Hippocrates", "Thales"] 103 | } 104 | } 105 | }; 106 | 107 | _.hasPath(countries, "greece.athens.playwright"); 108 | // => true 109 | 110 | _.hasPath(countries, "greece.sparta.playwright"); 111 | // => false 112 | 113 | _.hasPath(countries, ["greece", "athens", "playwright"]); 114 | // => true 115 | 116 | _.hasPath(countries, ["greece", "sparta", "playwright"]); 117 | // => false 118 | 119 | _.hasPath(countries, ["greece", "notableFigures", 1]); 120 | // => true 121 | 122 | _.hasPath(countries, "greece.notableFigures[2]"); 123 | // => true 124 | 125 | _.hasPath(countries, "greece['notableFigures'][2]"); 126 | // => true 127 | 128 | _.hasPath(countries, "greece.sparta[2]"); 129 | // => false 130 | ``` 131 | 132 | -------------------------------------------------------------------------------- 133 | 134 | #### keysFromPath 135 | 136 | **Signature:** `_.keysFromPath(path:String)` 137 | 138 | Takes a string in JavaScript object path notation and returns an array of keys. 139 | 140 | ```javascript 141 | _.keysFromPath("rome.emperors[0]['first-name']"); 142 | // => ["rome", "emperors", "0", "first-name"] 143 | ``` 144 | 145 | -------------------------------------------------------------------------------- 146 | 147 | #### kv 148 | 149 | **Signature:** `_.kv(obj:Object, key:String)` 150 | 151 | Returns the key/value pair for a given property in an object, undefined if not found. 152 | 153 | ```javascript 154 | var playAuthor = { 155 | "Medea": "Aeschylus" 156 | }; 157 | 158 | _.kv(playAuthor, "Medea"); 159 | // => ["Medea", "Aeschylus"] 160 | 161 | _.kv(playAuthor, "Hamlet"); 162 | // => undefined 163 | ``` 164 | 165 | -------------------------------------------------------------------------------- 166 | 167 | #### omitWhen 168 | 169 | **Signature:** `_.omitWhen(obj, pred:Function)` 170 | 171 | Returns a copy of `obj` omitting any properties that the predicate (`pred`) 172 | function returns `true` for. The predicat function is invoked with each 173 | property value, like so: `pred(propValue)`. 174 | 175 | ```javascript 176 | var playwrights = { 177 | euripedes: "Greece", 178 | shakespere: "England" 179 | }; 180 | 181 | _.omitWhen(playwrights, function (country) { return country == "England" }); 182 | // => { euripedes: "Greece" } 183 | ``` 184 | 185 | -------------------------------------------------------------------------------- 186 | 187 | #### pickWhen 188 | 189 | **Signature:** `_.pickWhen(obj:Object, pred:Function)` 190 | 191 | Returns a copy of `obj` containing only properties that the predicate (`pred`) 192 | function returns `true` for. The predicate function is invoked with each 193 | property value, like so: `pred(propValue)`. 194 | 195 | ```javascript 196 | var playwrights = { 197 | euripedes: "Greece", 198 | shakespere: "England" 199 | }; 200 | 201 | _.pickWhen(playwrights, function (country) { return country == "England" }); 202 | // => { shakespeare: "England" } 203 | ``` 204 | 205 | -------------------------------------------------------------------------------- 206 | 207 | #### selectKeys 208 | 209 | **Signature:** `_.selectKeys(obj:Object, ks:Array); 210 | 211 | Returns a copy of `obj` containing only the properties listed in the `ks` array. 212 | 213 | ```javascript 214 | var philosopherCities = { 215 | Philo: "Alexandria", 216 | Plato: "Athens", 217 | Plotinus: "Rome" 218 | } 219 | 220 | _.selectKeys(philosopherCities, ["Plato", "Plotinus"]); 221 | // => { Plato: "Athens", Plotinus: "Rome" } 222 | ``` 223 | -------------------------------------------------------------------------------- /docs/underscore.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/underscore.util.operators.js.md: -------------------------------------------------------------------------------- 1 | ### util.operators 2 | 3 | > Functions which wrap JavaScript's operators. 4 | 5 | -------------------------------------------------------------------------------- 6 | 7 | #### add 8 | 9 | **Signature:** `_.add(value:Number, value:Number[, value:Number...])`, `_.add(values:Array)` 10 | 11 | Returns the sum of the arguments. 12 | 13 | ```javascript 14 | _.add(1, 2, 3, 4); 15 | // => 10 16 | 17 | _.add([1, 2, 3, 4]); 18 | // => 10 19 | ``` 20 | 21 | -------------------------------------------------------------------------------- 22 | 23 | #### bitwiseAnd 24 | 25 | **Signature:** `_.bitwiseAnd(value:Any, value:Any[, value:Any...])`, `_.bitwiseAnd(values:Array)` 26 | 27 | Returns the result of using the `&` operator on the arguments. 28 | 29 | ```javascript 30 | _.bitwiseAnd(1, 3); 31 | // => 1 32 | 33 | _.bitwiseAnd(1, 3, 2); 34 | // => 0 35 | 36 | _.bitwiseAnd([1, 3, 2]); 37 | // => 0 38 | ``` 39 | 40 | -------------------------------------------------------------------------------- 41 | 42 | #### bitwiseLeft 43 | 44 | **Signature:** `_.bitwiseLeft(value:Any, value:Any[, value:Any...])`, `_.bitwiseLeft(values:Array)` 45 | 46 | Returns the result of using the `<<` operator on the arguments. 47 | 48 | ```javascript 49 | _.bitwiseLeft(1, 3); 50 | // => 8 51 | 52 | _.bitwiseLeft(1, 3, 2); 53 | // => 32 54 | 55 | _.bitwiseLeft([1, 3, 2]); 56 | // => 32 57 | ``` 58 | 59 | -------------------------------------------------------------------------------- 60 | 61 | #### bitwiseRight 62 | 63 | **Signature:** `_.bitwiseRight(value:Any, value:Any[, value:Any...])`, `_.bitwiseRight(values:Array)` 64 | 65 | Returns the result of using the `>>` operator on the arguments. 66 | 67 | ```javascript 68 | _.bitwiseRight(3, 1); 69 | // => 1 70 | 71 | _.bitwiseRight(3, 1, 3); 72 | // => 0 73 | 74 | _.bitwiseRight([3, 1, 3]); 75 | // => 0 76 | ``` 77 | 78 | -------------------------------------------------------------------------------- 79 | 80 | #### bitwiseNot 81 | 82 | **Signature:** `_.bitwiseNot(value:Any)` 83 | 84 | Returns the result of using the `~` operator on the value. 85 | 86 | ```javascript 87 | _.bitwiseNot(1); 88 | // => -2 89 | 90 | _.bitwiseNot(2); 91 | // => -3 92 | ``` 93 | 94 | -------------------------------------------------------------------------------- 95 | 96 | #### bitwiseOr 97 | 98 | **Signature:** `_.bitwiseOr(value:Any, value:Any[, value:Any...])`, `_.bitwiseOr(values:Array)` 99 | 100 | Returns the result of using the `|` operator on the arguments. 101 | 102 | ```javascript 103 | _.bitwiseOr(1, 3); 104 | // => 3 105 | 106 | _.bitwiseOr(1, 3, 4); 107 | // => 7 108 | 109 | _.bitwiseOr([1, 3, 4]); 110 | // => 7 111 | ``` 112 | 113 | -------------------------------------------------------------------------------- 114 | 115 | #### bitwiseXor 116 | 117 | **Signature:** `_.bitwiseXor(value:Any, value:Any[, value:Any...])`,`_.bitwiseXor(values:Array)` 118 | 119 | Returns the result of using the `^` operator on the arguments. 120 | 121 | ```javascript 122 | _.bitwiseXor(1, 3); 123 | // => 2 124 | 125 | _.bitwiseXor(1, 3, 3); 126 | // => 1 127 | 128 | _.bitwiseXor([1, 3, 3]); 129 | // => 1 130 | ``` 131 | 132 | -------------------------------------------------------------------------------- 133 | 134 | #### bitwiseZ 135 | 136 | **Signature:** `_.bitwiseZ(value:Any, value:Any[, value:Any...])`, `_.bitwiseZ(values:Array)` 137 | 138 | Returns the result of using the `>>>` operator on the arguments. 139 | 140 | ```javascript 141 | _.bitwiseZ(72, 32); 142 | // => 72 143 | 144 | _.bitwiseZ(72, 32, 2); 145 | // => 18 146 | 147 | _.bitwiseZ([72, 32, 2]); 148 | // => 18 149 | ``` 150 | 151 | -------------------------------------------------------------------------------- 152 | 153 | #### dec 154 | 155 | **Signature:** `_.dec(value:Number)` 156 | 157 | Returns the result of decrementing the value by `1`. 158 | 159 | ```javascript 160 | _.dec(2); 161 | // => 1 162 | ``` 163 | 164 | -------------------------------------------------------------------------------- 165 | 166 | #### div 167 | 168 | **Signature:** `_.div(value:Number, value:Number[, value:Number...])`, `_.div(values:Array)` 169 | 170 | Returns the quotient of the arguments. 171 | 172 | ```javascript 173 | _.div(8, 2); 174 | // => 4 175 | 176 | _.div(8, 2, 2); 177 | // => 2 178 | 179 | _.div([8, 2, 2]); 180 | // => 2 181 | ``` 182 | 183 | -------------------------------------------------------------------------------- 184 | 185 | #### eq 186 | 187 | **Signature:** `_.eq(value:Any, value:Any[, value:Any...])`,`_.eq(values:Array)` 188 | 189 | Compares the arguments with loose equality (`==`). 190 | 191 | ```javascript 192 | _.eq(1, "1"); 193 | // => true 194 | 195 | _.eq(1, 15); 196 | // => false 197 | 198 | _.eq(1, true, "1"); 199 | // => true 200 | 201 | _.eq(1, 1, 15); 202 | // => false 203 | 204 | _.eq([1, 1, 15]); 205 | // => false 206 | ``` 207 | 208 | -------------------------------------------------------------------------------- 209 | 210 | #### gt 211 | 212 | **Signature:** `_.gt(value:Any, value:Any[, value:Any...])`, `_.gt(values:Array)` 213 | 214 | Checks whether each argument is greater than the previous argument. 215 | 216 | ```javascript 217 | _.gt(1, 2); 218 | // => true 219 | 220 | _.gt(1, 2, 3); 221 | // => true 222 | 223 | _.gt(1, 6, 2); 224 | // => false 225 | 226 | _.gt([1, 6, 2]); 227 | // => false 228 | ``` 229 | 230 | -------------------------------------------------------------------------------- 231 | 232 | #### gte 233 | 234 | **Signature:** `_.gte(value:Any, value:Any[, value:Any...])`, `_.gte(values:Array)` 235 | 236 | Checks whether each argument is greater than or equal to the previous argument. 237 | 238 | ```javascript 239 | _.gte(1, 2); 240 | // => true 241 | 242 | _.gte(1, 1, 3); 243 | // => true 244 | 245 | _.gte(1, 6, 2); 246 | // => false 247 | 248 | _.gte([1, 6, 2]); 249 | // => false 250 | 251 | ``` 252 | 253 | -------------------------------------------------------------------------------- 254 | 255 | #### inc 256 | 257 | **Signature:** `_.inc(value:Number)` 258 | 259 | Returns the result of incrementing the value by `1`. 260 | 261 | ```javascript 262 | _.inc(2); 263 | // => 3 264 | ``` 265 | 266 | -------------------------------------------------------------------------------- 267 | 268 | #### lt 269 | 270 | **Signature:** `_.lt(value:Any, value:Any[, value:Any...])`, `_.lt(values:Array)` 271 | 272 | Checks whether each argument is less than the previous argument. 273 | 274 | ```javascript 275 | _.lt(2, 1); 276 | // => true 277 | 278 | _.lt(2, 1, 0); 279 | // => true 280 | 281 | _.lt(2, 1, 12); 282 | // => false 283 | 284 | _.lt([2, 1, 12]); 285 | // => false 286 | ``` 287 | 288 | -------------------------------------------------------------------------------- 289 | 290 | #### lte 291 | 292 | **Signature:** `_.lte(value:Any, value:Any[, value:Any...])`, `_.lte(values:Array)` 293 | 294 | Checks whether each argument is less than or equal to the previous argument. 295 | 296 | ```javascript 297 | _.lte(2, 1); 298 | // => true 299 | 300 | _.lte(2, 1, 1); 301 | // => true 302 | 303 | _.lte(2, 1, 12); 304 | // => false 305 | 306 | _.lte([2, 1, 12]); 307 | // => false 308 | 309 | ``` 310 | 311 | -------------------------------------------------------------------------------- 312 | 313 | #### mul 314 | 315 | **Signature:** `_.mul(value:Number, value:Number[, value:Number...])`, `_.mul(values:Array)` 316 | 317 | Returns the product of the arguments. 318 | 319 | ```javascript 320 | _.mul(1, 2, 3, 4); 321 | // => 24 322 | 323 | _.mul([1, 2, 3, 4]); 324 | // => 24 325 | ``` 326 | 327 | -------------------------------------------------------------------------------- 328 | 329 | #### mod 330 | 331 | **Signature:** `_.mod(dividend:Number, divisor:Number)` 332 | 333 | Returns the remainder of dividing `dividend` by `divisor`. 334 | 335 | ```javascript 336 | _.mod(26, 5); 337 | // => 1 338 | 339 | _.mod(14, 3); 340 | // => 2 341 | ``` 342 | 343 | -------------------------------------------------------------------------------- 344 | 345 | #### neg 346 | 347 | **Signature:** `_.neg(num:Number)` 348 | 349 | Returns a new number with the opposite sign value of `num`. 350 | 351 | ```javascript 352 | _.neg(5); 353 | // => -5 354 | 355 | _.neg(-3); 356 | // => 3 357 | ``` 358 | 359 | -------------------------------------------------------------------------------- 360 | 361 | #### neq 362 | 363 | **Signature:** `_.neq(value:Any, value:Any[, value:Any...])`, `_.neq(values:Array)` 364 | 365 | Checks whether each argument is not equal to the previous argument, using loose 366 | inequality (`!=`). 367 | 368 | ```javascript 369 | _.neq(2, 1); 370 | // => true 371 | 372 | _.neq(2, 1, 1); 373 | // => true 374 | 375 | _.neq(1, 1); 376 | // => false 377 | 378 | _.neq([1, 1]); 379 | // => false 380 | 381 | ``` 382 | 383 | -------------------------------------------------------------------------------- 384 | 385 | #### not 386 | 387 | **Signature:** `_.not(value:Any)` 388 | 389 | Returns a boolean which is the opposite of the truthiness of the original value. 390 | 391 | ```javascript 392 | _.not(0); 393 | // => true 394 | 395 | _.not(1); 396 | // => false 397 | 398 | _.not(true); 399 | // => false 400 | 401 | _.not(false); 402 | // => true 403 | 404 | _.not({}); 405 | // => false 406 | 407 | _.not(null); 408 | // => true 409 | ``` 410 | 411 | -------------------------------------------------------------------------------- 412 | 413 | #### seq 414 | 415 | **Signature:** `_.seq(value:Any, value:Any[, value:Any...])`, `_.seq(values:Array)` 416 | 417 | Checks whether the arguments are strictly equal (`===`) to each other. 418 | 419 | ```javascript 420 | _.seq(2, 2); 421 | // => true 422 | 423 | _.seq(2, "2"); 424 | // => false 425 | 426 | _.seq(2, 2, 2); 427 | // => true 428 | 429 | _.seq([2, 2, 2]); 430 | // => true 431 | 432 | ``` 433 | 434 | -------------------------------------------------------------------------------- 435 | 436 | #### sneq 437 | 438 | **Signature:** `_.sneq(value:Any, value:Any[, value:Any...])`, `_.sneq(values:Array)` 439 | 440 | Checks whether the arguments are strictly not equal (`!==`) to each other. 441 | 442 | ```javascript 443 | _.sneq(2, 2); 444 | // => false 445 | 446 | _.sneq(2, "2"); 447 | // => true 448 | 449 | _.sneq(2, 2, 2); 450 | // => false 451 | 452 | _.sneq([2, 2, 2]); 453 | // => false 454 | 455 | ``` 456 | 457 | -------------------------------------------------------------------------------- 458 | 459 | #### sub 460 | 461 | **Signature:** `_.sub(value:Number, value:Number[, value:Number...])`, `_.sub(values:Array)` 462 | 463 | Returns the difference of the arguments. 464 | 465 | ```javascript 466 | _.sub(10, 3); 467 | // => 7 468 | 469 | _.sub(10, 3, 5); 470 | // => 2 471 | 472 | _.sub([10, 3, 5]); 473 | // => 2 474 | ``` -------------------------------------------------------------------------------- /docs/underscore.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 | #### strContains 61 | 62 | **Signature:** `_.strContains(str:String, search:String)` 63 | 64 | Reports whether a string contains a search string. 65 | 66 | ```javascript 67 | _.strContains("Acropolis", "polis"); 68 | // => true 69 | ``` 70 | 71 | -------------------------------------------------------------------------------- 72 | 73 | #### toDash 74 | 75 | **Signature:** `_.toDash(string:String)` 76 | 77 | Converts a camel case string to a dashed string. Opposite of [camelCase](#camelcase). 78 | 79 | ```javascript 80 | _.toDash("thisIsSparta"); 81 | // => "this-is-sparta" 82 | ``` 83 | 84 | -------------------------------------------------------------------------------- 85 | 86 | #### toQuery 87 | 88 | **Signature:** `_.toQuery(obj:Object)` 89 | 90 | Takes an object and converts it into an equivalent URL query string. Opposite 91 | of [fromQuery](#fromquery). 92 | 93 | ```javascript 94 | _.toQuery({ forms: { perfect: "circle", imperfect: "square" } }); 95 | // => "forms%5Bperfect%5D=circle&forms%5Bimperfect%5D=square" 96 | ``` 97 | 98 | -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- /docs/underscore.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(Math.abs(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 | -------------------------------------------------------------------------------- 54 | -------------------------------------------------------------------------------- /examples/walk-examples.js.md: -------------------------------------------------------------------------------- 1 | Examples for _.walk 2 | =================== 3 | 4 | The _.walk module (underscore.collections.walk.js) provides implementations of 5 | the [Underscore collection functions](http://underscorejs.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 [Underscore's 'each' function][each]. For example, take 15 | a simple tree: 16 | 17 | [each]: http://underscorejs.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 | [Underscore collection functions](http://underscorejs.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(_.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 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('./underscore.array.builders'); 2 | require('./underscore.array.selectors'); 3 | require('./underscore.collections.walk'); 4 | require('./underscore.function.arity'); 5 | require('./underscore.function.combinators'); 6 | require('./underscore.function.dispatch'); 7 | require('./underscore.function.iterators'); 8 | require('./underscore.function.predicates'); 9 | require('./underscore.object.builders'); 10 | require('./underscore.object.selectors'); 11 | require('./underscore.util.existential'); 12 | require('./underscore.util.operators'); 13 | require('./underscore.util.strings'); 14 | require('./underscore.util.trampolines'); 15 | 16 | module.exports = require('underscore'); 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "underscore-contrib", 3 | "version": "0.3.0", 4 | "main": "index.js", 5 | "dependencies": { 6 | "underscore": "1.10.2" 7 | }, 8 | "devDependencies": { 9 | "grunt": "^1.5.3", 10 | "grunt-cli": "^1.3.2", 11 | "grunt-contrib-concat": "1.0.1", 12 | "grunt-contrib-jshint": "^2.1.0", 13 | "grunt-contrib-qunit": "^4.0.0", 14 | "grunt-contrib-uglify": "^5.0.0", 15 | "grunt-contrib-watch": "^1.1.0", 16 | "grunt-docco": "~0.5.0", 17 | "grunt-tocdoc": "0.1.1", 18 | "jquery": "3.5.1", 19 | "jslitmus": "^0.1.0", 20 | "qunit": "^2.11.0" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/documentcloud/underscore-contrib.git" 25 | }, 26 | "license": "MIT", 27 | "author": { 28 | "name": "Fogus", 29 | "email": "me@fogus.me", 30 | "url": "http://www.fogus.me" 31 | }, 32 | "scripts": { 33 | "test": "grunt test", 34 | "dist": "grunt dist", 35 | "tocdoc": "grunt tocdoc", 36 | "docco": "grunt docco" 37 | }, 38 | "homepage": "https://github.com/documentcloud/underscore-contrib" 39 | } 40 | -------------------------------------------------------------------------------- /test/array.selectors.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | QUnit.module("underscore.array.selectors"); 4 | 5 | QUnit.test("second", function(assert) { 6 | var a = [1,2,3,4,5]; 7 | 8 | assert.equal(_.second(a), 2, 'should retrieve the 2nd element in an array'); 9 | assert.deepEqual(_.second(a, 5), [2,3,4,5], 'should retrieve all but the first element in an array'); 10 | assert.deepEqual(_.map([a,_.rest(a)], _.second), [2,3], 'should be usable in _.map'); 11 | }); 12 | 13 | QUnit.test("third", function(assert) { 14 | var a = [1,2,3,4,5]; 15 | 16 | assert.equal(_.third(a), 3, 'should retrieve the 3rd element in an array'); 17 | assert.deepEqual(_.third(a, 5), [3,4,5], 'should retrieve all but the first and second element in an array'); 18 | assert.deepEqual(_.map([a,_.rest(a)], _.third), [3,4], 'should be usable in _.map'); 19 | }); 20 | 21 | QUnit.test("takeWhile", function(assert) { 22 | var isNeg = function(n) { return n < 0; }; 23 | 24 | assert.deepEqual(_.takeWhile([-2,-1,0,1,2], isNeg), [-2,-1], 'should take elements until a function goes truthy'); 25 | assert.deepEqual(_.takeWhile([1,-2,-1,0,1,2], isNeg), [], 'should take elements until a function goes truthy'); 26 | }); 27 | 28 | QUnit.test("dropWhile", function(assert) { 29 | var isNeg = function(n) { return n < 0; }; 30 | 31 | assert.deepEqual(_.dropWhile([-2,-1,0,1,2], isNeg), [0,1,2], 'should drop elements until a function goes truthy'); 32 | assert.deepEqual(_.dropWhile([0,1,2], isNeg), [0,1,2], 'should drop elements until a function goes truthy'); 33 | assert.deepEqual(_.dropWhile([-2,-1], isNeg), [], 'should drop elements until a function goes truthy'); 34 | assert.deepEqual(_.dropWhile([1,-2,-1,0,1,2], isNeg), [1,-2,-1,0,1,2], 'should take elements until a function goes truthy'); 35 | assert.deepEqual(_.dropWhile([], isNeg), [], 'should handle empty arrays'); 36 | }); 37 | 38 | QUnit.test("splitWith", function(assert) { 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 | assert.deepEqual(_.splitWith(a, lessEq3p), [[1,2,3], [4,5]], 'should split an array when a function goes false'); 44 | assert.deepEqual(_.splitWith(a, lessEq3p$), [[1,2,3], [4,5]], 'should split an array when a function goes false'); 45 | assert.deepEqual(_.splitWith([], lessEq3p$), [[],[]], 'should split an empty array into two empty arrays'); 46 | }); 47 | 48 | QUnit.test("partitionBy", function(assert) { 49 | var a = [1, 2, null, false, undefined, 3, 4]; 50 | 51 | assert.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 | QUnit.test("best", function(assert) { 55 | var a = [1,2,3,4,5]; 56 | 57 | assert.deepEqual(_.best(a, function(x,y) { return x > y; }), 5, 'should identify the best value based on criteria'); 58 | }); 59 | 60 | QUnit.test("keep", function(assert) { 61 | var a = _.range(10); 62 | var eveny = function(e) { return (_.isEven(e)) ? e : undefined; }; 63 | 64 | assert.deepEqual(_.keep(a, eveny), [0,2,4,6,8], 'should keep only even numbers in a range tagged with null fails'); 65 | assert.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 | QUnit.test("nth", function(assert) { 69 | var a = ['a','b','c']; 70 | var b = [['a'],['b'],[]]; 71 | 72 | assert.equal(_.nth(a,0), 'a', 'should return the element at a given index into an array'); 73 | assert.equal(_.nth(a,100), undefined, 'should return undefined if out of bounds'); 74 | assert.deepEqual(_.map(b,function(e) { return _.nth(e,0); }), ['a','b',undefined], 'should be usable in _.map'); 75 | }); 76 | 77 | QUnit.test("nths", function(assert) { 78 | var a = ['a','b','c', 'd']; 79 | 80 | assert.deepEqual(_.nths(a,1), ['b'], 'should return the element at a given index into an array'); 81 | assert.deepEqual(_.nths(a,1,3), ['b', 'd'], 'should return the elements at given indices into an array'); 82 | assert.deepEqual(_.nths(a,1,5,3), ['b', undefined, 'd'], 'should return undefined if out of bounds'); 83 | 84 | assert.deepEqual(_.nths(a,[1]), ['b'], 'should return the element at a given index into an array'); 85 | assert.deepEqual(_.nths(a,[1,3]), ['b', 'd'], 'should return the elements at given indices into an array'); 86 | assert.deepEqual(_.nths(a,[1,5,3]), ['b', undefined, 'd'], 'should return undefined if out of bounds'); 87 | }); 88 | 89 | QUnit.test("valuesAt", function(assert) { 90 | assert.equal(_.valuesAt, _.nths, 'valuesAt should be alias for nths'); 91 | }); 92 | 93 | QUnit.test("binPick", function(assert) { 94 | var a = ['a','b','c', 'd']; 95 | 96 | assert.deepEqual(_.binPick(a, false, true), ['b'], 'should return the element at a given index into an array'); 97 | assert.deepEqual(_.binPick(a, false, true, false, true), ['b', 'd'], 'should return the elements at given indices into an array'); 98 | assert.deepEqual(_.binPick(a, false, true, false, true, true), ['b', 'd', undefined], 'should return undefined if out of bounds'); 99 | 100 | assert.deepEqual(_.binPick(a, [false, true]), ['b'], 'should return the element at a given index into an array'); 101 | assert.deepEqual(_.binPick(a, [false, true, false, true]), ['b', 'd'], 'should return the elements at given indices into an array'); 102 | assert.deepEqual(_.binPick(a, [false, true, false, true, true]), ['b', 'd', undefined], 'should return undefined if out of bounds'); 103 | }); 104 | }); 105 | 106 | -------------------------------------------------------------------------------- /test/collections.walk.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | QUnit.module("underscore.collections.walk"); 4 | 5 | var getSimpleTestTree = function() { 6 | return { 7 | val: 0, 8 | l: { val: 1, l: { val: 2 }, r: { val: 3 } }, 9 | r: { val: 4, l: { val: 5 }, r: { val: 6 } } 10 | }; 11 | }; 12 | 13 | var getMixedTestTree = function() { 14 | return { 15 | current: 16 | { city: 'Munich', aliases: ['Muenchen'], population: 1378000 }, 17 | previous: [ 18 | { city: 'San Francisco', aliases: ['SF', 'San Fran'], population: 812826 }, 19 | { city: 'Toronto', aliases: ['TO', 'T-dot'], population: 2615000 } 20 | ] 21 | }; 22 | }; 23 | 24 | var getArrayValues = function() { 25 | return ["a", "a", "a", "a", "b", "c", "d", "e" ]; 26 | }; 27 | 28 | QUnit.test("basic", function(assert) { 29 | // Updates the value of `node` to be the sum of the values of its subtrees. 30 | // Ignores leaf nodes. 31 | var visitor = function(node) { 32 | if (node.l && node.r) 33 | node.val = node.l.val + node.r.val; 34 | }; 35 | 36 | var tree = getSimpleTestTree(); 37 | _.walk.postorder(tree, visitor); 38 | assert.equal(tree.val, 16, 'should visit subtrees first'); 39 | 40 | tree = getSimpleTestTree(); 41 | _.walk.preorder(tree, visitor); 42 | assert.equal(tree.val, 5, 'should visit subtrees after the node itself'); 43 | }); 44 | 45 | QUnit.test("circularRefs", function(assert) { 46 | var tree = getSimpleTestTree(); 47 | tree.l.l.r = tree; 48 | assert.throws(function() { _.walk.preorder(tree, _.identity); }, TypeError, 'preorder throws an exception'); 49 | assert.throws(function() { _.walk.postrder(tree, _.identity); }, TypeError, 'postorder throws an exception'); 50 | 51 | tree = getSimpleTestTree(); 52 | tree.r.l = tree.r; 53 | assert.throws(function() { _.walk.preorder(tree, _.identity); }, TypeError, 'exception for a self-referencing node'); 54 | }); 55 | 56 | QUnit.test("simpleMap", function(assert) { 57 | var visitor = function(node, key, parent) { 58 | if (_.has(node, 'val')) return node.val; 59 | if (key !== 'val') throw new Error('Leaf node with incorrect key'); 60 | return this.leafChar || '-'; 61 | }; 62 | var visited = _.walk.map(getSimpleTestTree(), _.walk.preorder, visitor).join(''); 63 | assert.equal(visited, '0-1-2-3-4-5-6-', 'pre-order map'); 64 | 65 | visited = _.walk.map(getSimpleTestTree(), _.walk.postorder, visitor).join(''); 66 | assert.equal(visited, '---2-31--5-640', 'post-order map'); 67 | 68 | var context = { leafChar: '*' }; 69 | visited = _.walk.map(getSimpleTestTree(), _.walk.preorder, visitor, context).join(''); 70 | assert.equal(visited, '0*1*2*3*4*5*6*', 'pre-order with context'); 71 | 72 | visited = _.walk.map(getSimpleTestTree(), _.walk.postorder, visitor, context).join(''); 73 | assert.equal(visited, '***2*31**5*640', 'post-order with context'); 74 | 75 | if (document.querySelector) { 76 | var root = document.querySelector('#map-test'); 77 | var ids = _.walk.map(root, _.walk.preorder, function(el) { return el.id; }); 78 | assert.deepEqual(ids, ['map-test', 'id1', 'id2'], 'preorder map with DOM elements'); 79 | 80 | ids = _.walk.map(root, _.walk.postorder, function(el) { return el.id; }); 81 | assert.deepEqual(ids, ['id1', 'id2', 'map-test'], 'postorder map with DOM elements'); 82 | } 83 | }); 84 | 85 | QUnit.test("mixedMap", function(assert) { 86 | var visitor = function(node, key, parent) { 87 | return _.isString(node) ? node.toLowerCase() : null; 88 | }; 89 | 90 | var tree = getMixedTestTree(); 91 | var preorderResult = _.walk.map(tree, _.walk.preorder, visitor); 92 | assert.equal(preorderResult.length, 19, 'all nodes are visited'); 93 | assert.deepEqual(_.reject(preorderResult, _.isNull), 94 | ['munich', 'muenchen', 'san francisco', 'sf', 'san fran', 'toronto', 'to', 't-dot'], 95 | 'pre-order map on a mixed tree'); 96 | 97 | var postorderResult = _.walk.map(tree, _.walk.postorder, visitor); 98 | assert.deepEqual(preorderResult.sort(), postorderResult.sort(), 'post-order map on a mixed tree'); 99 | 100 | tree = [['foo'], tree]; 101 | var result = _.walk.map(tree, _.walk.postorder, visitor); 102 | assert.deepEqual(_.difference(result, postorderResult), ['foo'], 'map on list of trees'); 103 | }); 104 | 105 | QUnit.test("pluck", function(assert) { 106 | var tree = getSimpleTestTree(); 107 | tree.val = { val: 'z' }; 108 | 109 | var plucked = _.walk.pluckRec(tree, 'val'); 110 | assert.equal(plucked.shift(), tree.val); 111 | assert.equal(plucked.join(''), 'z123456', 'pluckRec is recursive'); 112 | 113 | plucked = _.walk.pluck(tree, 'val'); 114 | assert.equal(plucked.shift(), tree.val); 115 | assert.equal(plucked.join(''), '123456', 'regular pluck is not recursive'); 116 | 117 | tree.l.r.foo = 42; 118 | assert.equal(_.walk.pluck(tree, 'foo'), 42, 'pluck a value from deep in the tree'); 119 | 120 | tree = getMixedTestTree(); 121 | assert.deepEqual(_.walk.pluck(tree, 'city'), ['Munich', 'San Francisco', 'Toronto'], 'pluck from a mixed tree'); 122 | tree = [tree, { city: 'Loserville', population: 'you' }]; 123 | assert.deepEqual(_.walk.pluck(tree, 'population'), [1378000, 812826, 2615000, 'you'], 'pluck from a list of trees'); 124 | }); 125 | 126 | QUnit.test("reduce", function(assert) { 127 | var add = function(a, b) { return a + b; }; 128 | var leafMemo = []; 129 | var sum = function(memo, node) { 130 | if (_.isObject(node)) 131 | return _.reduce(memo, add, 0); 132 | 133 | assert.strictEqual(memo, leafMemo); 134 | return node; 135 | }; 136 | var tree = getSimpleTestTree(); 137 | assert.equal(_.walk.reduce(tree, sum, leafMemo), 21); 138 | 139 | // A more useful example: transforming a tree. 140 | 141 | // Returns a new node where the left and right subtrees are swapped. 142 | var mirror = function(memo, node) { 143 | if (!_.has(node, 'r')) return node; 144 | return _.extend(_.clone(node), { l: memo.r, r: memo.l }); 145 | }; 146 | // Returns the '-' for internal nodes, and the value itself for leaves. 147 | var toString = function(node) { return _.has(node, 'val') ? '-' : node; }; 148 | 149 | tree = _.walk.reduce(getSimpleTestTree(), mirror); 150 | assert.equal(_.walk.reduce(tree, sum, leafMemo), 21); 151 | assert.equal(_.walk.map(tree, _.walk.preorder, toString).join(''), '-0-4-6-5-1-3-2', 'pre-order map'); 152 | }); 153 | 154 | QUnit.test("find", function(assert) { 155 | var tree = getSimpleTestTree(); 156 | 157 | // Returns a visitor function that will succeed when a node with the given 158 | // value is found, and then raise an exception the next time it's called. 159 | var findValue = function(value) { 160 | var found = false; 161 | return function(node) { 162 | if (found) throw 'already found!'; 163 | found = (node.val === value); 164 | return found; 165 | }; 166 | }; 167 | 168 | assert.equal(_.walk.find(tree, findValue(0)).val, 0); 169 | assert.equal(_.walk.find(tree, findValue(6)).val, 6); 170 | assert.deepEqual(_.walk.find(tree, findValue(99)), undefined); 171 | }); 172 | 173 | QUnit.test("filter", function(assert) { 174 | var tree = getSimpleTestTree(); 175 | tree.r.val = '.oOo.'; // Remove one of the numbers. 176 | var isEvenNumber = function(x) { 177 | return _.isNumber(x) && x % 2 === 0; 178 | }; 179 | 180 | assert.equal(_.walk.filter(tree, _.walk.preorder, _.isObject).length, 7, 'filter objects'); 181 | assert.equal(_.walk.filter(tree, _.walk.preorder, _.isNumber).length, 6, 'filter numbers'); 182 | assert.equal(_.walk.filter(tree, _.walk.postorder, _.isNumber).length, 6, 'postorder filter numbers'); 183 | assert.equal(_.walk.filter(tree, _.walk.preorder, isEvenNumber).length, 3, 'filter even numbers'); 184 | 185 | // With the identity function, only the value '0' should be omitted. 186 | assert.equal(_.walk.filter(tree, _.walk.preorder, _.identity).length, 13, 'filter on identity function'); 187 | }); 188 | 189 | QUnit.test("reject", function(assert) { 190 | var tree = getSimpleTestTree(); 191 | tree.r.val = '.oOo.'; // Remove one of the numbers. 192 | 193 | assert.equal(_.walk.reject(tree, _.walk.preorder, _.isObject).length, 7, 'reject objects'); 194 | assert.equal(_.walk.reject(tree, _.walk.preorder, _.isNumber).length, 8, 'reject numbers'); 195 | assert.equal(_.walk.reject(tree, _.walk.postorder, _.isNumber).length, 8, 'postorder reject numbers'); 196 | 197 | // With the identity function, only the value '0' should be kept. 198 | assert.equal(_.walk.reject(tree, _.walk.preorder, _.identity).length, 1, 'reject with identity function'); 199 | }); 200 | 201 | QUnit.test("customTraversal", function(assert) { 202 | var tree = getSimpleTestTree(); 203 | 204 | // Set up a walker that will not traverse the 'val' properties. 205 | var walker = _.walk(function(node) { 206 | return _.omit(node, 'val'); 207 | }); 208 | var visitor = function(node) { 209 | if (!_.isObject(node)) throw new Error("Leaf value visited when it shouldn't be"); 210 | }; 211 | assert.equal(walker.pluck(tree, 'val').length, 7, 'pluck with custom traversal'); 212 | assert.equal(walker.pluckRec(tree, 'val').length, 7, 'pluckRec with custom traversal'); 213 | 214 | assert.equal(walker.map(tree, _.walk.postorder, _.identity).length, 7, 'traversal strategy is dynamically scoped'); 215 | 216 | // Check that the default walker is unaffected. 217 | assert.equal(_.walk.map(tree, _.walk.postorder, _.identity).length, 14, 'default map still works'); 218 | assert.equal(_.walk.pluckRec(tree, 'val').join(''), '0123456', 'default pluckRec still works'); 219 | }); 220 | 221 | QUnit.test("containsAtLeast", function(assert){ 222 | var array = getArrayValues(); 223 | 224 | assert.equal(_.walk.containsAtLeast(array, 3, "a"), true, "list contains at least 3 items"); 225 | assert.equal(_.walk.containsAtLeast(array, 1, "b"), true, "list contains at least 1 items"); 226 | assert.equal(_.walk.containsAtLeast(array, 1, "f"), false, "list doesn't contain item for that value"); 227 | }); 228 | 229 | QUnit.test("containsAtMost", function(assert){ 230 | var array = getArrayValues(); 231 | 232 | assert.equal(_.walk.containsAtMost(array, 4, "a"), true, "list contains at most 4 items"); 233 | assert.equal(_.walk.containsAtMost(array, 1, "b"), true, "list contains at most 1 value"); 234 | assert.equal(_.walk.containsAtMost(array, 1, "f"), true, "list contains at most 1 value"); 235 | }); 236 | }); 237 | -------------------------------------------------------------------------------- /test/dist-concat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Underscore-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 | Underscore-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/function.arity.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | QUnit.module("underscore.function.arity"); 4 | 5 | QUnit.test("fix", function(assert) { 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 | QUnit.test("arity", function(assert) { 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 | QUnit.test("curry", function(assert) { 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 | var failure = false; 85 | try { 86 | curried(1, 2999); 87 | } catch (e) { 88 | failure = true; 89 | } finally { 90 | assert.equal(failure, true, "Curried functions only accept one argument at a time"); 91 | } 92 | }); 93 | 94 | QUnit.test("curry2", function(assert) { 95 | 96 | function echo () { return [].slice.call(arguments, 0); } 97 | 98 | assert.deepEqual(echo(1, 2), [1, 2], "Control test"); 99 | assert.deepEqual(_.curry2(echo)(1)(2), [1, 2], "Accepts curried arguments"); 100 | 101 | }); 102 | 103 | QUnit.test("curryRight2", function(assert) { 104 | 105 | function echo () { return [].slice.call(arguments, 0); } 106 | 107 | assert.deepEqual(echo(1, 2), [1, 2], "Control test"); 108 | assert.deepEqual(_.curryRight2(echo)(1)(2), [2, 1], "Reverses curried arguments"); 109 | assert.equal(_.curryRight2, _.rcurry2, "should have alias 'rcurry2'"); 110 | }); 111 | 112 | QUnit.test("curry3", function(assert) { 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(_.curry3(echo)(1)(2)(3), [1, 2, 3], "Accepts curried arguments"); 118 | 119 | }); 120 | 121 | QUnit.test("curryRight3", function(assert) { 122 | 123 | function echo () { return [].slice.call(arguments, 0); } 124 | 125 | assert.deepEqual(echo(1, 2, 3), [1, 2, 3], "Control test"); 126 | assert.deepEqual(_.curryRight3(echo)(1)(2)(3), [3, 2, 1], "Reverses curried arguments"); 127 | assert.equal(_.curryRight3, _.rcurry3, "should have alias 'rcurry3'"); 128 | }); 129 | 130 | QUnit.test("enforce", function(assert) { 131 | function binary (a, b) { 132 | return a + b; 133 | } 134 | function ternary (a, b, c) { 135 | return a + b + c; 136 | } 137 | function altTernary (a, b, c) { 138 | return a - b - c; 139 | } 140 | var fBinary = _.enforce(binary), 141 | fTernary = _.enforce(ternary), 142 | fAltTernary = _.enforce(altTernary), 143 | failure = false; 144 | try { 145 | fBinary(1); 146 | } catch (e) { 147 | failure = true; 148 | } finally { 149 | assert.equal(failure, true, "Binary must have two arguments."); 150 | } 151 | assert.equal(fBinary(1, 2), 3, "Function returns after proper application"); 152 | 153 | failure = false; 154 | try { 155 | fTernary(1, 3); 156 | } catch (e) { 157 | failure = true; 158 | } finally { 159 | assert.equal(failure, true, "Ternary must have three arguments."); 160 | } 161 | assert.equal(fTernary(1, 2, 3), 6, "Function returns after proper application"); 162 | assert.equal(fAltTernary(1, 2, 3), -4, "Function cache does not collide"); 163 | }); 164 | }); 165 | -------------------------------------------------------------------------------- /test/function.dispatch.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | QUnit.module("underscore.function.dispatch"); 4 | 5 | QUnit.test('attempt', function(assert) { 6 | var obj = {x: '', y: function() { return true; }, z: function() { return _.toArray(arguments).join(''); }}; 7 | assert.strictEqual(_.attempt(obj, 'x'), undefined); 8 | assert.strictEqual(_.attempt(obj, 'y'), true); 9 | assert.strictEqual(_.attempt(obj, 'z', 1, 2, 3), '123'); 10 | assert.strictEqual(_.attempt(null, 'x'), undefined); 11 | assert.strictEqual(_.attempt(undefined, 'x'), undefined); 12 | }); 13 | 14 | }); 15 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Underscore-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 | 42 | 43 | 44 | 45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | 53 | 54 | -------------------------------------------------------------------------------- /test/object.builders.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | QUnit.module("underscore.object.builders"); 4 | 5 | QUnit.test("merge", function(assert) { 6 | var o = {'a': 1, 'b': 2}; 7 | 8 | assert.deepEqual(_.merge(o), {a: 1, b: 2}, 'should return a copy of the object if given only one'); 9 | assert.deepEqual(_.merge({'a': 1, 'b': 2}, {b: 42}), {'a': 1, b: 42}, 'should merge two objects'); 10 | assert.deepEqual(_.merge({a: 1, b: 2}, {b: 42}, {c: 3}), {a: 1, b: 42, c: 3}, 'should merge three or more objects'); 11 | assert.deepEqual(_.merge({a: 1, b: 2}, {b: 42}, {c: 3}, {c: 4}), {a: 1, b: 42, c: 4}, 'should merge three or more objects'); 12 | 13 | var a = {'a': 1, 'b': 2}; 14 | var $ = _.merge(a, {'a': 42}); 15 | 16 | assert.deepEqual(a, {'a': 1, 'b': 2}, 'should not modify the original'); 17 | }); 18 | 19 | QUnit.test("renameKeys", function(assert) { 20 | 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'); 21 | 22 | var a = {'a': 1, 'b': 2}; 23 | var $ = _.renameKeys(a, {'a': 'A'}); 24 | 25 | assert.deepEqual(a, {'a': 1, 'b': 2}, 'should not modify the original'); 26 | }); 27 | 28 | QUnit.test("snapshot", function(assert) { 29 | var o = {'a': 1, 'b': 2}; 30 | var oSnap = _.snapshot(o); 31 | 32 | var a = [1,2,3,4]; 33 | var aSnap = _.snapshot(a); 34 | 35 | var n = [1,{a: 1, b: [1,2,3]},{},4]; 36 | var nSnap = _.snapshot(n); 37 | 38 | var c = [1,{a: 1, b: [1,2,3]},{},4]; 39 | var cSnap = _.snapshot(c); 40 | c[1].b = 42; 41 | 42 | assert.deepEqual(o, oSnap, 'should create a deep copy of an object'); 43 | assert.deepEqual(a, aSnap, 'should create a deep copy of an array'); 44 | assert.deepEqual(n, nSnap, 'should create a deep copy of an array'); 45 | assert.deepEqual(nSnap, [1,{a: 1, b: [1,2,3]},{},4], 'should allow changes to the original to not change copies'); 46 | }); 47 | 48 | QUnit.test("setPath", function(assert) { 49 | var obj = {a: {b: {c: 42, d: 108}}}; 50 | var ary = ['a', ['b', ['c', 'd'], 'e']]; 51 | var nest = [1, {a: 2, b: [3,4], c: 5}, 6]; 52 | 53 | assert.deepEqual(_.setPath(obj, 9, ['a', 'b', 'c']), {a: {b: {c: 9, d: 108}}}, ''); 54 | assert.deepEqual(_.setPath(ary, 9, [1, 1, 0]), ['a', ['b', [9, 'd'], 'e']], ''); 55 | assert.deepEqual(_.setPath(nest, 9, [1, 'b', 1]), [1, {a: 2, b: [3,9], c: 5}, 6], ''); 56 | 57 | assert.deepEqual(_.setPath(obj, 9, 'a'), {a: 9}, ''); 58 | assert.deepEqual(_.setPath(ary, 9, 1), ['a', 9], ''); 59 | 60 | assert.deepEqual(obj, {a: {b: {c: 42, d: 108}}}, 'should not modify the original object'); 61 | assert.deepEqual(ary, ['a', ['b', ['c', 'd'], 'e']], 'should not modify the original array'); 62 | assert.deepEqual(nest, [1, {a: 2, b: [3,4], c: 5}, 6], 'should not modify the original nested structure'); 63 | }); 64 | 65 | QUnit.test("updatePath", function(assert) { 66 | var obj = {a: {b: {c: 42, d: 108}}}; 67 | var ary = ['a', ['b', ['c', 'd'], 'e']]; 68 | var nest = [1, {a: 2, b: [3,4], c: 5}, 6]; 69 | 70 | assert.deepEqual(_.updatePath(obj, _.always(9), ['a', 'b', 'c']), {a: {b: {c: 9, d: 108}}}, ''); 71 | assert.deepEqual(_.updatePath(ary, _.always(9), [1, 1, 0]), ['a', ['b', [9, 'd'], 'e']], ''); 72 | assert.deepEqual(_.updatePath(nest, _.always(9), [1, 'b', 1]), [1, {a: 2, b: [3,9], c: 5}, 6], ''); 73 | 74 | assert.deepEqual(_.updatePath(obj, _.always(9), 'a'), {a: 9}, ''); 75 | assert.deepEqual(_.updatePath(ary, _.always(9), 1), ['a', 9], ''); 76 | 77 | assert.deepEqual(obj, {a: {b: {c: 42, d: 108}}}, 'should not modify the original object'); 78 | assert.deepEqual(ary, ['a', ['b', ['c', 'd'], 'e']], 'should not modify the original array'); 79 | assert.deepEqual(nest, [1, {a: 2, b: [3,4], c: 5}, 6], 'should not modify the original nested structure'); 80 | }); 81 | 82 | QUnit.test("omitPath", function(assert){ 83 | var a = { 84 | foo: true, 85 | bar: false, 86 | baz: 42, 87 | dada: { 88 | carlos: { pepe: 9 }, 89 | pedro: 'pedro', 90 | list: [ 91 | {file: '..', more: {other: { a: 1, b: 2}}, name: 'aa'}, 92 | {file: '..', name: 'bb'} 93 | ] 94 | } 95 | }; 96 | 97 | assert.deepEqual(_.omitPath(a, 'dada.carlos.pepe'), { 98 | foo: true, 99 | bar: false, 100 | baz: 42, 101 | dada: { 102 | carlos: {}, 103 | pedro: 'pedro', 104 | list: [ 105 | {file: '..', more: {other: { a: 1, b: 2}} , name: 'aa'}, 106 | {file: '..', name: 'bb'} 107 | ] 108 | } 109 | }, "should return an object without the value that represent the path"); 110 | 111 | assert.deepEqual(_.omitPath(a, 'dada.carlos'), { 112 | foo: true, 113 | bar: false, 114 | baz: 42, 115 | dada: { 116 | pedro: 'pedro', 117 | list: [ 118 | {file: '..', more: {other: { a: 1, b: 2}} , name: 'aa'}, 119 | {file: '..', name: 'bb'} 120 | ] 121 | } 122 | }, "should return an object without the value that represent the path"); 123 | 124 | assert.deepEqual(_.omitPath(a, ''), { 125 | foo: true, 126 | bar: false, 127 | baz: 42, 128 | dada: { 129 | carlos: { pepe: 9 }, 130 | pedro: 'pedro', 131 | list: [ 132 | {file: '..', more: {other: { a: 1, b: 2}} , name: 'aa'}, 133 | {file: '..', name: 'bb'} 134 | ] 135 | } 136 | }, "should return the whole object because the path is empty"); 137 | 138 | assert.deepEqual(_.omitPath(a, 'dada.list.0.file'), { 139 | foo: true, 140 | bar: false, 141 | baz: 42, 142 | dada: { 143 | carlos: { pepe: 9 }, 144 | pedro: 'pedro', 145 | list: [ 146 | {name: 'aa', more: {other: { a: 1, b: 2}}}, 147 | {file: '..', name: 'bb'} 148 | ] 149 | } 150 | }, "should return an object without the value in an element of the list"); 151 | 152 | assert.deepEqual(_.omitPath(a, 'dada.list.1.name'), { 153 | foo: true, 154 | bar: false, 155 | baz: 42, 156 | dada: { 157 | carlos: { pepe: 9 }, 158 | pedro: 'pedro', 159 | list: [ 160 | {name: 'aa', file: '..', more: {other: { a: 1, b: 2}}}, 161 | {file: '..'} 162 | ] 163 | } 164 | }, "should return an object without the value in each object of the list"); 165 | 166 | assert.deepEqual(_.omitPath(a, 'dada.list'), { 167 | foo: true, 168 | bar: false, 169 | baz: 42, 170 | dada: { 171 | carlos: { pepe: 9 }, 172 | pedro: 'pedro' 173 | } 174 | }, "should return an object without the list"); 175 | 176 | assert.deepEqual(_.omitPath(a, 'dada.list.0.more.other.a'), { 177 | foo: true, 178 | bar: false, 179 | baz: 42, 180 | dada: { 181 | carlos: { pepe: 9 }, 182 | pedro: 'pedro', 183 | list: [ 184 | {file: '..', more: {other: { b: 2}} , name: 'aa'}, 185 | {file: '..', name: 'bb'} 186 | ] 187 | } 188 | }, 189 | "should return an object without the value inside the values of the list" 190 | ); 191 | }); 192 | }); 193 | -------------------------------------------------------------------------------- /test/object.selectors.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | QUnit.module("underscore.object.selectors"); 4 | 5 | QUnit.test("accessor", function(assert) { 6 | var a = [{a: 1, b: 2}, {c: 3}]; 7 | 8 | assert.equal(_.accessor('a')(a[0]), 1, 'should return a function that plucks'); 9 | assert.equal(_.accessor('a')(a[1]), undefined, 'should return a function that plucks, or returns undefined'); 10 | assert.deepEqual(_.map(a, _.accessor('a')), [1, undefined], 'should return a function that plucks'); 11 | }); 12 | 13 | QUnit.test("dictionary", function(assert) { 14 | var a = [{a: 1, b: 2}, {c: 3}]; 15 | 16 | assert.equal(_.dictionary(a[0])('a'), 1, 'should return a function that acts as a dictionary'); 17 | assert.equal(_.dictionary(a[1])('a'), undefined, 'should return a function that acts as a dictionary, or returns undefined'); 18 | }); 19 | 20 | QUnit.test("selectKeys", function(assert) { 21 | assert.deepEqual(_.selectKeys({'a': 1, 'b': 2}, ['a']), {'a': 1}, 'shold return a map of the desired keys'); 22 | assert.deepEqual(_.selectKeys({'a': 1, 'b': 2}, ['z']), {}, 'shold return an empty map if the desired keys are not present'); 23 | }); 24 | 25 | QUnit.test("kv", function(assert) { 26 | assert.deepEqual(_.kv({'a': 1, 'b': 2}, 'a'), ['a', 1], 'should return the key/value pair at the desired key'); 27 | assert.equal(_.kv({'a': 1, 'b': 2}, 'z'), undefined, 'shold return undefined if the desired key is not present'); 28 | }); 29 | 30 | QUnit.test("getPath", function(assert) { 31 | var deepObject = { a: { b: { c: "c" } }, falseVal: false, nullVal: null, undefinedVal: undefined, arrayVal: ["arr"], deepArray: { contents: ["da1", "da2"] } }; 32 | var weirdObject = { "D'artagnan": { "[0].[1]": "myValue" }, element: { "0": { "prop.1": "value1" } } }; 33 | var deepArr = [[["thirdLevel"]]]; 34 | var ks = ["a", "b", "c"]; 35 | 36 | assert.strictEqual(_.getPath(deepObject, ks), "c", "should get a deep property's value from objects"); 37 | assert.deepEqual(ks, ["a", "b", "c"], "should not have mutated ks argument"); 38 | assert.strictEqual(_.getPath(deepArr, [0, 0, 0]), "thirdLevel", "should get a deep property's value from arrays"); 39 | assert.strictEqual(_.getPath(deepObject, ["arrayVal", 0]), "arr", "should get a deep property's value from nested arrays and objects"); 40 | assert.strictEqual(_.getPath(deepObject, ["deepArray", "contents", 1]), "da2", "should get a deep property's value within arrays inside deep objects, from an array"); 41 | assert.strictEqual(_.getPath(deepObject, "deepArray.contents[0]"), "da1", "should get a deep property's value within arrays inside deep objects, from a complete javascript path"); 42 | 43 | assert.strictEqual(_.getPath(deepObject, ["undefinedVal"]), undefined, "should return undefined for undefined properties"); 44 | assert.strictEqual(_.getPath(deepObject, ["a", "notHere"]), undefined, "should return undefined for non-existent properties"); 45 | assert.strictEqual(_.getPath(deepObject, ["nullVal"]), null, "should return null for null properties"); 46 | assert.strictEqual(_.getPath(deepObject, ["nullVal", "notHere", "notHereEither"]), undefined, "should return undefined for non-existent descendents of null properties"); 47 | 48 | assert.strictEqual(_.getPath(weirdObject, ["D'artagnan", "[0].[1]"]), "myValue", "should be able to traverse complex property names, from an array"); 49 | assert.strictEqual(_.getPath(weirdObject, "[\"D'artagnan\"]['[0].[1]']"), "myValue", "should be able to traverse complex property names, from an accessor string"); 50 | assert.strictEqual(_.getPath(weirdObject, "element[0]['prop.1']"), "value1", "should be able to traverse complex property names, from an accessor string"); 51 | 52 | assert.strictEqual(_.getPath(deepObject, "a.b.c"), "c", "should work with keys written in dot notation"); 53 | assert.strictEqual(_.getPath({}, "myPath.deepProperty"), undefined, "should not break with empty objects and deep paths"); 54 | }); 55 | 56 | QUnit.test("hasPath", function(assert) { 57 | var deepObject = { a: { b: { c: "c" } }, falseVal: false, nullVal: null, undefinedVal: undefined, arrayVal: ["arr"], deepArray: { contents: ["da1", "da2"] } }; 58 | var weirdObject = { "D'artagnan": { "[0].[1]": "myValue" }, element: { "0": { "prop.1": "value1" } } }; 59 | var ks = ["a", "b", "c"]; 60 | 61 | assert.strictEqual(_.hasPath(deepObject, ["notHere", "notHereEither"]), false, "should return false if the path doesn't exist"); 62 | assert.strictEqual(_.hasPath(deepObject, ks), true, "should return true if the path exists"); 63 | assert.deepEqual(ks, ["a", "b", "c"], "should not have mutated ks argument"); 64 | 65 | assert.strictEqual(_.hasPath(deepObject, ["deepArray", "contents", 1]), true, "should return true for an existing value within arrays inside deep objects, from an array"); 66 | 67 | assert.strictEqual(_.hasPath(deepObject, ["arrayVal", 0]), true, "should return true for an array's index if it is defined"); 68 | assert.strictEqual(_.hasPath(deepObject, ["arrayVal", 999]), false, "should return false for an array's index if it is not defined"); 69 | 70 | assert.strictEqual(_.hasPath(deepObject, ["nullVal"]), true, "should return true for null properties"); 71 | assert.strictEqual(_.hasPath(deepObject, ["undefinedVal"]), true, "should return true for properties that were explicitly assigned undefined"); 72 | 73 | assert.strictEqual(_.hasPath(weirdObject, ["D'artagnan", "[0].[1]"]), true, "should return true for complex property names, from an array"); 74 | assert.strictEqual(_.hasPath(weirdObject, "[\"D'artagnan\"]['[0].[1]']"), true, "should return true for complex property names, from an accessor string"); 75 | assert.strictEqual(_.hasPath(weirdObject, "element[0]['prop.1']"), true, "should be return true for complex property names, from an accessor string"); 76 | assert.strictEqual(_.hasPath(weirdObject, ["D'artagnan", "[0].[2]"]), false, "should return false for non-existent complex property names, from an array"); 77 | assert.strictEqual(_.hasPath(weirdObject, "[\"D'artagnan\"]['[0].[2]']"), false, "should return true for non-existent complex property names, from an accessor string"); 78 | assert.strictEqual(_.hasPath(weirdObject, "element[0]['prop.2']"), false, "should be return true for non-existent complex property names, from an accessor string"); 79 | 80 | assert.strictEqual(_.hasPath(deepObject, ["nullVal", "notHere"]), false, "should return false for descendants of null properties"); 81 | assert.strictEqual(_.hasPath(deepObject, ["undefinedVal", "notHere"]), false, "should return false for descendants of undefined properties"); 82 | 83 | assert.strictEqual(_.hasPath(deepObject, "a.b.c"), true, "should work with keys written in dot notation."); 84 | 85 | assert.strictEqual(_.hasPath(null, []), true, "should return true for null and undefined when passed no keys"); 86 | assert.strictEqual(_.hasPath(void 0, []), true); 87 | assert.strictEqual(_.hasPath(null, ['']), false, "should return false (not throw) on null/undefined given keys"); 88 | assert.strictEqual(_.hasPath(void 0, ['']), false); 89 | 90 | assert.strictEqual(_.hasPath(deepObject, "a.b.c.d"), false, "should return false for keys which doesn't exist on nested existing objects"); 91 | }); 92 | 93 | QUnit.test("keysFromPath", function(assert) { 94 | assert.deepEqual(_.keysFromPath("a.b.c"), ["a", "b", "c"], "should convert a path into an array of keys"); 95 | assert.deepEqual(_.keysFromPath("a[0].b['c']"), ["a", "0", "b", "c"], "should handle bracket notation"); 96 | assert.deepEqual(_.keysFromPath("[\"D'artagnan\"]['[0].[1]']"), ["D'artagnan", "[0].[1]"], "should handle complex paths"); 97 | }); 98 | 99 | QUnit.test("pickWhen", function(assert) { 100 | var a = {foo: true, bar: false, baz: 42}; 101 | 102 | assert.deepEqual(_.pickWhen(a, _.truthy), {foo: true, baz: 42}, "should return an object with kvs that return a truthy value for the given predicate"); 103 | }); 104 | 105 | QUnit.test("omitWhen", function(assert) { 106 | var a = {foo: [], bar: "", baz: "something", quux: ['a']}; 107 | 108 | assert.deepEqual(_.omitWhen(a, _.isEmpty), {baz: "something", quux: ['a']}, "should return an object with kvs that return a falsey value for the given predicate"); 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /test/util.existential.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | QUnit.module("underscore.util.existential"); 4 | 5 | QUnit.test("exists", function(assert) { 6 | assert.equal(_.exists(null), false, 'should know that null is not existy'); 7 | assert.equal(_.exists(undefined), false, 'should know that undefined is not existy'); 8 | 9 | assert.equal(_.exists(1), true, 'should know that all but null and undefined are existy'); 10 | assert.equal(_.exists(0), true, 'should know that all but null and undefined are existy'); 11 | assert.equal(_.exists(-1), true, 'should know that all but null and undefined are existy'); 12 | assert.equal(_.exists(3.14), true, 'should know that all but null and undefined are existy'); 13 | assert.equal(_.exists('undefined'), true, 'should know that all but null and undefined are existy'); 14 | assert.equal(_.exists(''), true, 'should know that all but null and undefined are existy'); 15 | assert.equal(_.exists(NaN), true, 'should know that all but null and undefined are existy'); 16 | assert.equal(_.exists(Infinity), true, 'should know that all but null and undefined are existy'); 17 | assert.equal(_.exists(true), true, 'should know that all but null and undefined are existy'); 18 | assert.equal(_.exists(false), true, 'should know that all but null and undefined are existy'); 19 | assert.equal(_.exists(function(){}), true, 'should know that all but null and undefined are existy'); 20 | }); 21 | 22 | QUnit.test("truthy", function(assert) { 23 | assert.equal(_.truthy(null), false, 'should know that null, undefined and false are not truthy'); 24 | assert.equal(_.truthy(undefined), false, 'should know that null, undefined and false are not truthy'); 25 | assert.equal(_.truthy(false), false, 'should know that null, undefined and false are not truthy'); 26 | 27 | assert.equal(_.truthy(1), true, 'should know that everything else is truthy'); 28 | assert.equal(_.truthy(0), true, 'should know that everything else is truthy'); 29 | assert.equal(_.truthy(-1), true, 'should know that everything else is truthy'); 30 | assert.equal(_.truthy(3.14), true, 'should know that everything else is truthy'); 31 | assert.equal(_.truthy('undefined'), true, 'should know that everything else is truthy'); 32 | assert.equal(_.truthy(''), true, 'should know that everything else is truthy'); 33 | assert.equal(_.truthy(NaN), true, 'should know that everything else is truthy'); 34 | assert.equal(_.truthy(Infinity), true, 'should know that everything else is truthy'); 35 | assert.equal(_.truthy(true), true, 'should know that everything else is truthy'); 36 | assert.equal(_.truthy(function(){}), true, 'should know that everything else is truthy'); 37 | }); 38 | 39 | QUnit.test("falsey", function(assert) { 40 | assert.equal(_.falsey(null), true, 'should know that null, undefined and false are not falsey'); 41 | assert.equal(_.falsey(undefined), true, 'should know that null, undefined and false are not falsey'); 42 | assert.equal(_.falsey(false), true, 'should know that null, undefined and false are not falsey'); 43 | 44 | assert.equal(_.falsey(1), false, 'should know that everything else is falsey'); 45 | assert.equal(_.falsey(0), false, 'should know that everything else is falsey'); 46 | assert.equal(_.falsey(-1), false, 'should know that everything else is falsey'); 47 | assert.equal(_.falsey(3.14), false, 'should know that everything else is falsey'); 48 | assert.equal(_.falsey('undefined'), false, 'should know that everything else is falsey'); 49 | assert.equal(_.falsey(''), false, 'should know that everything else is falsey'); 50 | assert.equal(_.falsey(NaN), false, 'should know that everything else is falsey'); 51 | assert.equal(_.falsey(Infinity), false, 'should know that everything else is falsey'); 52 | assert.equal(_.falsey(true), false, 'should know that everything else is falsey'); 53 | assert.equal(_.falsey(function(){}), false, 'should know that everything else is falsey'); 54 | }); 55 | 56 | QUnit.test('firstExisting', function(assert) { 57 | assert.equal(_.firstExisting('first', 'second'), 'first', 'should return the first existing value'); 58 | assert.equal(_.firstExisting(null, 'second'), 'second', 'should ignore null'); 59 | assert.equal(_.firstExisting(void 0, 'second'), 'second', 'should ignore undefined'); 60 | assert.equal(_.firstExisting(null, void 0, 'third'), 'third', 'should work with more arguments'); 61 | }); 62 | 63 | }); 64 | -------------------------------------------------------------------------------- /test/util.operators.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | QUnit.module("underscore.util.operators"); 4 | 5 | QUnit.test("add", function(assert) { 6 | assert.equal(_.add(1, 1), 2, '1 + 1 = 2'); 7 | assert.equal(_.add(3, 5), 8, '3 + 5 = 8'); 8 | assert.equal(_.add(1, 2, 3, 4), 10, 'adds multiple operands'); 9 | assert.equal(_.add([1, 2, 3, 4]), 10, 'adds multiple operands, when specified in an array'); 10 | }); 11 | 12 | QUnit.test("sub", function(assert) { 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 | assert.equal(_.sub([10, 9, 8, 7]), -14, 'subtracts multiple operands, when specified in an array'); 17 | }); 18 | 19 | QUnit.test("mul", function(assert) { 20 | assert.equal(_.mul(1, 1), 1, '1 * 1 = 1'); 21 | assert.equal(_.mul(5, 3), 15, '5 * 3 = 15'); 22 | assert.equal(_.mul(1, 2, 3, 4), 24, 'multiplies multiple operands'); 23 | assert.equal(_.mul([1, 2, 3, 4]), 24, 'multiplies multiple operands, when specified in an array'); 24 | }); 25 | 26 | QUnit.test("div", function(assert) { 27 | assert.equal(_.div(1, 1), 1, '1 / 1 = 1'); 28 | assert.equal(_.div(15, 3), 5, '15 / 3 = 5'); 29 | assert.equal(_.div(15, 0), Infinity, '15 / 0 = Infinity'); 30 | assert.equal(_.div(24, 2, 2, 2), 3, 'divides multiple operands'); 31 | assert.equal(_.div([24, 2, 2, 2]), 3, 'divides multiple operands, when specified in an array'); 32 | }); 33 | 34 | QUnit.test("mod", function(assert) { 35 | assert.equal(_.mod(3, 2), 1, '3 / 2 = 1'); 36 | assert.equal(_.mod(15, 3), 0, '15 / 3 = 0'); 37 | }); 38 | 39 | QUnit.test("inc", function(assert) { 40 | assert.equal(_.inc(1), 2, '++1 = 2'); 41 | assert.equal(_.inc(15), 16, '++15 = 16'); 42 | }); 43 | 44 | QUnit.test("dec", function(assert) { 45 | assert.equal(_.dec(2), 1, '--2 = 1'); 46 | assert.equal(_.dec(15), 14, '--15 = 15'); 47 | }); 48 | 49 | QUnit.test("neg", function(assert) { 50 | assert.equal(_.neg(2), -2, 'opposite of 2'); 51 | assert.equal(_.neg(-2), 2, 'opposite of -2'); 52 | assert.equal(_.neg(true), -1, 'opposite of true'); 53 | }); 54 | 55 | QUnit.test("eq", function(assert) { 56 | assert.equal(_.eq(1, 1), true, '1 == 1'); 57 | assert.equal(_.eq(1, true), true, '1 == true'); 58 | assert.equal(_.eq(1, false), false, '1 != false'); 59 | assert.equal(_.eq(1, '1'), true, '1 == "1"'); 60 | assert.equal(_.eq(1, 'one'), false, '1 != "one"'); 61 | assert.equal(_.eq(0, 0), true, '0 == 0'); 62 | assert.equal(_.eq(0, false), true, '0 == false'); 63 | assert.equal(_.eq(0, '0'), true, '0 == "0"'); 64 | assert.equal(_.eq({}, {}), false, '{} == {}'); 65 | assert.equal(false, false, 'failing placeholder'); 66 | assert.equal(_.eq(0, 0, 1), false, 'compares a list of arguments'); 67 | assert.equal(_.eq([0, 0, 1]), false, 'compares a list of arguments, when specified as an array'); 68 | }); 69 | QUnit.test("seq", function(assert) { 70 | assert.equal(_.seq(1, 1), true, '1 === 1'); 71 | assert.equal(_.seq(1, '1'), false, '1 !== "1"'); 72 | assert.equal(_.seq(0, 0, 1), false, 'compares a list of arguments'); 73 | assert.equal(_.seq([0, 0, 1]), false, 'compares a list of arguments, when specified as an array'); 74 | }); 75 | QUnit.test("neq", function(assert) { 76 | assert.equal(_.neq('a', 'b'), true, '"a" != "b"'); 77 | assert.equal(_.neq(1, '1'), false, '1 == "1"'); 78 | assert.equal(_.neq(0, 0, 1), true, 'compares a list of arguments'); 79 | assert.equal(_.neq([0, 0, 1]), true, 'compares a list of arguments, when specified as an array'); 80 | }); 81 | QUnit.test("sneq", function(assert) { 82 | assert.equal(_.sneq('a', 'b'), true, '"a" !== "b"'); 83 | assert.equal(_.sneq(1, '1'), true, '1 !== "1"'); 84 | assert.equal(_.sneq(0, 0, 1), true, 'compares a list of arguments'); 85 | assert.equal(_.sneq([0, 0, 1]), true, 'compares a list of arguments, when specified as an array'); 86 | }); 87 | QUnit.test("not", function(assert) { 88 | assert.equal(_.not(true), false, 'converts true to false'); 89 | assert.equal(_.not(false), true, 'converts false to true'); 90 | assert.equal(_.not('truthy'), false, 'converts truthy values to false'); 91 | assert.equal(_.not(null), true, 'converts falsy values to true'); 92 | }); 93 | QUnit.test("gt", function(assert) { 94 | assert.equal(_.gt(3, 2), true, '3 > 2'); 95 | assert.equal(_.gt(1, 3), false, '1 > 3'); 96 | assert.equal(_.gt(1, 2, 1), false, 'compares a list of arguments'); 97 | assert.equal(_.gt([1, 2, 1]), false, 'compares a list of arguments, when specified as an array'); 98 | }); 99 | QUnit.test("lt", function(assert) { 100 | assert.equal(_.lt(3, 2), false, '3 < 2'); 101 | assert.equal(_.lt(1, 3), true, '1 < 3'); 102 | assert.equal(_.lt(1, 2, 1), false, 'compares a list of arguments'); 103 | assert.equal(_.lt([1, 2, 1]), false, 'compares a list of arguments, when specified as an array'); 104 | }); 105 | QUnit.test("gte", function(assert) { 106 | assert.equal(_.gte(3, 2), true, '3 >= 2'); 107 | assert.equal(_.gte(1, 3), false, '1 >= 3'); 108 | assert.equal(_.gte(3, 3), true, '3 >= 3'); 109 | assert.equal(_.gte(2, 3, 1), false, 'compares a list of arguments'); 110 | assert.equal(_.gte([2, 3, 1]), false, 'compares a list of arguments, when specified as an array'); 111 | }); 112 | QUnit.test("lte", function(assert) { 113 | assert.equal(_.lte(3, 2), false, '3 <= 2'); 114 | assert.equal(_.lte(1, 3), true, '1 <= 3'); 115 | assert.equal(_.lte(3, 3), true, '3 <= 3'); 116 | assert.equal(_.lte(2, 2, 1), false, 'compares a list of arguments'); 117 | assert.equal(_.lte([2, 2, 1]), false, 'compares a list of arguments, when specified as an array'); 118 | }); 119 | QUnit.test("bitwiseAnd", function(assert) { 120 | assert.equal(_.bitwiseAnd(1, 1), 1, '1 & 1'); 121 | assert.equal(_.bitwiseAnd(1, 0), 0, '1 & 0'); 122 | assert.equal(_.bitwiseAnd(1, 1, 0), 0, 'operates on multiple arguments'); 123 | assert.equal(_.bitwiseAnd([1, 1, 0]), 0, 'operates on multiple arguments, when specified as an array'); 124 | }); 125 | QUnit.test("bitwiseOr", function(assert) { 126 | assert.equal(_.bitwiseOr(1, 1), 1, '1 | 1'); 127 | assert.equal(_.bitwiseOr(1, 0), 1, '1 | 0'); 128 | assert.equal(_.bitwiseOr(1, 1, 2), 3, 'operates on multiple arguments'); 129 | assert.equal(_.bitwiseOr([1, 1, 2]), 3, 'operates on multiple arguments, when specified as an array'); 130 | }); 131 | QUnit.test("bitwiseXor", function(assert) { 132 | assert.equal(_.bitwiseXor(1, 1), 0, '1 ^ 1'); 133 | assert.equal(_.bitwiseXor(1, 2), 3, '1 ^ 2'); 134 | assert.equal(_.bitwiseXor(1, 2, 3), 0, 'operates on multiple arguments'); 135 | assert.equal(_.bitwiseXor([1, 2, 3]), 0, 'operates on multiple arguments, when specified as an array'); 136 | }); 137 | QUnit.test("bitwiseNot", function(assert) { 138 | assert.equal(_.bitwiseNot(1), -2, '~1'); 139 | assert.equal(_.bitwiseNot(2), -3, '~2'); 140 | }); 141 | QUnit.test("bitwiseLeft", function(assert) { 142 | assert.equal(_.bitwiseLeft(1, 1), 2, '1 << 1'); 143 | assert.equal(_.bitwiseLeft(1, 0), 1, '1 << 0'); 144 | assert.equal(_.bitwiseLeft(1, 1, 1), 4, 'operates on multiple arguments'); 145 | assert.equal(_.bitwiseLeft([1, 1, 1]), 4, 'operates on multiple arguments, when specified as an array'); 146 | }); 147 | QUnit.test("bitwiseRight", function(assert) { 148 | assert.equal(_.bitwiseRight(1, 1), 0, '1 >> 1'); 149 | assert.equal(_.bitwiseRight(2, 1), 1, '2 >> 1'); 150 | assert.equal(_.bitwiseRight(3, 1, 1), 0, 'operates on multiple arguments'); 151 | assert.equal(_.bitwiseRight([3, 1, 1]), 0, 'operates on multiple arguments, when specified as an array'); 152 | }); 153 | QUnit.test("bitwiseZ", function(assert) { 154 | assert.equal(_.bitwiseZ(-1, 1), 2147483647, '-1 >>> 1'); 155 | assert.equal(_.bitwiseZ(-1, 1, 1), 1073741823, 'operates on multiple arguments'); 156 | assert.equal(_.bitwiseZ([-1, 1, 1]), 1073741823, 'operates on multiple arguments, when specified as an array'); 157 | }); 158 | 159 | }); 160 | -------------------------------------------------------------------------------- /test/util.strings.js: -------------------------------------------------------------------------------- 1 | 2 | $(document).ready(function() { 3 | 4 | QUnit.module('underscore.util.strings'); 5 | 6 | QUnit.test('explode', function(assert) { 7 | assert.deepEqual(_.explode('Virgil'), ['V','i','r','g','i','l'], 'Should explode a string into an array of characters.'); 8 | }); 9 | 10 | QUnit.test('fromQuery', function(assert) { 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 | assert.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 | QUnit.test('implode', function(assert) { 30 | assert.equal(_.implode(['H','o','m','e','r']), 'Homer', 'Should implode an array of characters into a single string.'); 31 | }); 32 | 33 | QUnit.test('camelCase', function(assert) { 34 | assert.equal(_.camelCase('punic-wars'), 'punicWars', 'Should convert a dashed-format string to camelCase.'); 35 | }); 36 | 37 | QUnit.test('toDash', function(assert) { 38 | assert.equal(_.toDash('trojanWar'), 'trojan-war', 'Should convert a camelCase string to dashed-format.'); 39 | assert.equal(_.toDash('PersianWar'), 'persian-war', 'Should convert a PascalCase string to dashed-format.'); 40 | }); 41 | 42 | QUnit.test('toQuery', function(assert) { 43 | var obj = {'foo&bar': 'baz', 'test': 'total success', 'nested': {'works': 'too'}, 'isn\'t': ['that', 'cool?']}; 44 | assert.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'); 45 | assert.equal(_.toQuery(obj), jQuery.param(obj), 'query serialization matchs jQuery.param()'); 46 | assert.equal(_.toQuery({a: []}), '', 'empty array params produce the empty string'); 47 | assert.equal(_.toQuery({a: [], b: []}), '', 'multiple empty array params do not lead to spurious ampersands'); 48 | assert.equal(_.toQuery({a: null, b: undefined}), 'a=null&b=undefined', 'respects null and undefined'); 49 | }); 50 | 51 | QUnit.test('strContains', function(assert) { 52 | assert.equal(_.strContains('Metaphysics', 'physics'), true, 'Should return true if string contains search string.'); 53 | assert.equal(_.strContains('Poetics', 'prose'), false, 'Should return false if string does not contain search string.'); 54 | 55 | var thrower = function() { _.strContains([], ''); }; 56 | assert.throws(thrower, TypeError, 'Throws TypeError if first argument is not a string.'); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/util.trampolines.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | QUnit.module("underscore.util.trampolines"); 4 | 5 | QUnit.test("trampoline", function(assert) { 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 | assert.equal(_.trampoline(evenOline, 55000), true, 'should trampoline two mutually recursive functions'); 21 | assert.equal(_.trampoline(evenOline, 0), true, 'should trampoline two mutually recursive functions'); 22 | assert.equal(_.trampoline(evenOline, 111111), false, 'should trampoline two mutually recursive functions'); 23 | assert.equal(_.trampoline(oddOline, 1), true, 'should trampoline two mutually recursive functions'); 24 | assert.equal(_.trampoline(oddOline, 11111), true, 'should trampoline two mutually recursive functions'); 25 | assert.equal(_.trampoline(oddOline, 22), false, 'should trampoline two mutually recursive functions'); 26 | }); 27 | 28 | }); 29 | -------------------------------------------------------------------------------- /underscore.array.builders.js: -------------------------------------------------------------------------------- 1 | // Underscore-contrib (underscore.array.builders.js 0.3.0) 2 | // (c) 2013 Michael Fogus, DocumentCloud and Investigative Reporters & Editors 3 | // Underscore-contrib may be freely distributed under the MIT license. 4 | 5 | (function() { 6 | 7 | // Baseline setup 8 | // -------------- 9 | 10 | // Establish the root object, `window` in the browser, or `require` it on the server. 11 | if (typeof exports === 'object') { 12 | _ = module.exports = require('underscore'); 13 | } 14 | 15 | // Helpers 16 | // ------- 17 | 18 | // Create quick reference variables for speed access to core prototypes. 19 | var slice = Array.prototype.slice, 20 | sort = Array.prototype.sort; 21 | 22 | var existy = function(x) { return x != null; }; 23 | 24 | // Mixing in the array builders 25 | // ---------------------------- 26 | 27 | _.mixin({ 28 | // Concatenates one or more arrays given as arguments. If given objects and 29 | // scalars as arguments `cat` will plop them down in place in the result 30 | // array. If given an `arguments` object, `cat` will treat it like an array 31 | // and concatenate it likewise. 32 | cat: function() { 33 | return _.flatten(arguments, true); 34 | }, 35 | 36 | // 'Constructs' an array by putting an element at its front 37 | cons: function(head, tail) { 38 | return _.cat([head], tail); 39 | }, 40 | 41 | // Takes an array and chunks it some number of times into 42 | // sub-arrays of size n. Allows and optional padding array as 43 | // the third argument to fill in the tail chunk when n is 44 | // not sufficient to build chunks of the same size. 45 | chunk: function(array, n, pad) { 46 | var p = function(array) { 47 | if (array == null) return []; 48 | 49 | var part = _.take(array, n); 50 | 51 | if (n === _.size(part)) { 52 | return _.cons(part, p(_.drop(array, n))); 53 | } 54 | else { 55 | return pad ? [_.take(_.cat(part, pad), n)] : []; 56 | } 57 | }; 58 | 59 | return p(array); 60 | }, 61 | 62 | // Takes an array and chunks it some number of times into 63 | // sub-arrays of size n. If the array given cannot fill the size 64 | // needs of the final chunk then a smaller chunk is used 65 | // for the last. 66 | chunkAll: function(array, n, step) { 67 | step = (step != null) ? step : n; 68 | 69 | var p = function(array, n, step) { 70 | if (_.isEmpty(array)) return []; 71 | 72 | return _.cons(_.take(array, n), 73 | p(_.drop(array, step), n, step)); 74 | }; 75 | 76 | return p(array, n, step); 77 | }, 78 | 79 | // Maps a function over an array and concatenates all of the results. 80 | mapcat: function(array, fun) { 81 | return _.flatten(_.map(array, fun), true); 82 | }, 83 | 84 | // Returns an array with some item between each element 85 | // of a given array. 86 | interpose: function(array, inter) { 87 | if (!_.isArray(array)) throw new TypeError; 88 | var sz = _.size(array); 89 | if (sz === 0) return array; 90 | if (sz === 1) return array; 91 | 92 | return slice.call(_.mapcat(array, function(elem) { 93 | return _.cons(elem, [inter]); 94 | }), 0, -1); 95 | }, 96 | 97 | // Weaves two or more arrays together 98 | weave: function(/* args */) { 99 | if (!_.some(arguments)) return []; 100 | if (arguments.length == 1) return arguments[0]; 101 | 102 | return _.filter(_.flatten(_.zip.apply(null, arguments), true), function(elem) { 103 | return elem != null; 104 | }); 105 | }, 106 | interleave: _.weave, 107 | 108 | // Returns an array of a value repeated a certain number of 109 | // times. 110 | repeat: function(t, elem) { 111 | return _.times(t, function() { return elem; }); 112 | }, 113 | 114 | // Returns an array built from the contents of a given array repeated 115 | // a certain number of times. 116 | cycle: function(t, elems) { 117 | return _.flatten(_.times(t, function() { return elems; }), true); 118 | }, 119 | 120 | // Returns an array with two internal arrays built from 121 | // taking an original array and spliting it at an index. 122 | splitAt: function(array, index) { 123 | return [_.take(array, index), _.drop(array, index)]; 124 | }, 125 | 126 | // Call a function recursively f(f(f(args))) until a second 127 | // given function goes falsey. Expects a seed value to start. 128 | iterateUntil: function(doit, checkit, seed) { 129 | var ret = []; 130 | var result = doit(seed); 131 | 132 | while (checkit(result)) { 133 | ret.push(result); 134 | result = doit(result); 135 | } 136 | 137 | return ret; 138 | }, 139 | 140 | // Takes every nth item from an array, returning an array of 141 | // the results. 142 | takeSkipping: function(array, n) { 143 | var ret = []; 144 | var sz = _.size(array); 145 | 146 | if (n <= 0) return []; 147 | if (n === 1) return array; 148 | 149 | for(var index = 0; index < sz; index += n) { 150 | ret.push(array[index]); 151 | } 152 | 153 | return ret; 154 | }, 155 | 156 | // Returns an array of each intermediate stage of a call to 157 | // a `reduce`-like function. 158 | reductions: function(array, fun, init) { 159 | var ret = []; 160 | var acc = init; 161 | 162 | _.each(array, function(v,k) { 163 | acc = fun(acc, array[k]); 164 | ret.push(acc); 165 | }); 166 | 167 | return ret; 168 | }, 169 | 170 | // Runs its given function on the index of the elements rather than 171 | // the elements themselves, keeping all of the truthy values in the end. 172 | keepIndexed: function(array, pred) { 173 | return _.filter(_.map(_.range(_.size(array)), function(i) { 174 | return pred(i, array[i]); 175 | }), 176 | existy); 177 | }, 178 | 179 | // Accepts an array-like object (other than strings) as an argument and 180 | // returns an array whose elements are in the reverse order. Unlike the 181 | // built-in `Array.prototype.reverse` method, this does not mutate the 182 | // original object. Note: attempting to use this method on a string will 183 | // result in a `TypeError`, as it cannot properly reverse unicode strings. 184 | reverseOrder: function(obj) { 185 | if (typeof obj == 'string') 186 | throw new TypeError('Strings cannot be reversed by _.reverseOrder'); 187 | return slice.call(obj).reverse(); 188 | }, 189 | 190 | // Returns copy or array sorted according to arbitrary ordering 191 | // order must be an array of values; defines the custom sort 192 | // key must be a valid argument to _.iteratee 193 | collate: function(array, order, key) { 194 | if (typeof array.length != "number") throw new TypeError("expected an array-like first argument"); 195 | if (typeof order.length != "number") throw new TypeError("expected an array-like second argument"); 196 | 197 | var original = slice.call(array); 198 | var valA, valB; 199 | var cb = _.iteratee(key); 200 | return sort.call(original, function (a, b) { 201 | var rankA = _.indexOf(order, cb(a)); 202 | var rankB = _.indexOf(order, cb(b)); 203 | 204 | if(rankA === -1) return 1; 205 | if(rankB === -1) return -1; 206 | 207 | return rankA - rankB; 208 | }); 209 | }, 210 | 211 | // Creates an array with all possible combinations of elements from 212 | // the given arrays 213 | combinations: function(){ 214 | return _.reduce(slice.call(arguments, 1),function(ret,newarr){ 215 | return _.reduce(ret,function(memo,oldi){ 216 | return memo.concat(_.map(newarr,function(newi){ 217 | return oldi.concat([newi]); 218 | })); 219 | },[]); 220 | },_.map(arguments[0],function(i){return [i];})); 221 | } 222 | 223 | }); 224 | 225 | }).call(this); 226 | -------------------------------------------------------------------------------- /underscore.array.selectors.js: -------------------------------------------------------------------------------- 1 | // Underscore-contrib (underscore.array.selectors.js 0.3.0) 2 | // (c) 2013 Michael Fogus, DocumentCloud and Investigative Reporters & Editors 3 | // Underscore-contrib may be freely distributed under the MIT license. 4 | 5 | (function() { 6 | 7 | // Baseline setup 8 | // -------------- 9 | 10 | // Establish the root object, `window` in the browser, or `require` it on the server. 11 | if (typeof exports === 'object') { 12 | _ = module.exports = require('underscore'); 13 | } 14 | 15 | // Helpers 16 | // ------- 17 | 18 | // Create quick reference variables for speed access to core prototypes. 19 | var slice = Array.prototype.slice, 20 | concat = Array.prototype.concat; 21 | 22 | var existy = function(x) { return x != null; }; 23 | var truthy = function(x) { return (x !== false) && existy(x); }; 24 | var isSeq = function(x) { return (_.isArray(x)) || (_.isArguments(x)); }; 25 | 26 | // Mixing in the array selectors 27 | // ---------------------------- 28 | 29 | _.mixin({ 30 | // Returns the second element of an array. Passing **n** will return all but 31 | // the first of the head N values in the array. The **guard** check allows it 32 | // to work with `_.map`. 33 | second: function(array, n, guard) { 34 | if (array == null) return void 0; 35 | return (n != null) && !guard ? slice.call(array, 1, n) : array[1]; 36 | }, 37 | 38 | // Returns the third element of an array. Passing **n** will return all but 39 | // the first two of the head N values in the array. The **guard** check allows it 40 | // to work with `_.map`. 41 | third: function(array, n, guard) { 42 | if (array == null) return void 0; 43 | return (n != null) && !guard ? slice.call(array, 2, n) : array[2]; 44 | }, 45 | 46 | // A function to get at an index into an array 47 | nth: function(array, index, guard) { 48 | if ((index != null) && !guard) return array[index]; 49 | }, 50 | 51 | // A function to get values via indices into an array 52 | nths: nths = function(array, indices) { 53 | if (array == null) return void 0; 54 | 55 | if (isSeq(indices)) 56 | return _(indices).map(function(i){return array[i];}); 57 | else 58 | return nths(array, slice.call(arguments, 1)); 59 | }, 60 | valuesAt: nths, 61 | 62 | // A function to get at "truthily" indexed values 63 | // bin refers to "binary" nature of true/false values in binIndices 64 | // but can also be thought of as putting array values into either "take" or "don't" bins 65 | binPick: function binPick(array, binIndices) { 66 | if (array == null) return void 0; 67 | 68 | if (isSeq(binIndices)) 69 | return _.nths(array, _.range(binIndices.length).filter(function(i){return binIndices[i];})); 70 | else 71 | return binPick(array, slice.call(arguments, 1)); 72 | }, 73 | 74 | // Takes all items in an array while a given predicate returns truthy. 75 | takeWhile: function(array, pred) { 76 | if (!isSeq(array)) throw new TypeError; 77 | 78 | var sz = _.size(array); 79 | 80 | for (var index = 0; index < sz; index++) { 81 | if(!truthy(pred(array[index]))) { 82 | break; 83 | } 84 | } 85 | 86 | return _.take(array, index); 87 | }, 88 | 89 | // Drops all items from an array while a given predicate returns truthy. 90 | dropWhile: function(array, pred) { 91 | if (!isSeq(array)) throw new TypeError; 92 | 93 | var sz = _.size(array); 94 | 95 | for (var index = 0; index < sz; index++) { 96 | if(!truthy(pred(array[index]))) 97 | break; 98 | } 99 | 100 | return _.drop(array, index); 101 | }, 102 | 103 | // Returns an array with two internal arrays built from 104 | // taking an original array and spliting it at the index 105 | // where a given function goes falsey. 106 | splitWith: function(array, pred) { 107 | return [_.takeWhile(array, pred), _.dropWhile(array, pred)]; 108 | }, 109 | 110 | // Takes an array and partitions it as the given predicate changes 111 | // truth sense. 112 | partitionBy: function(array, fun){ 113 | if (_.isEmpty(array) || !existy(array)) return []; 114 | 115 | var fst = _.first(array); 116 | var fstVal = fun(fst); 117 | var run = concat.call([fst], _.takeWhile(_.rest(array), function(e) { 118 | return _.isEqual(fstVal, fun(e)); 119 | })); 120 | 121 | return concat.call([run], _.partitionBy(_.drop(array, _.size(run)), fun)); 122 | }, 123 | 124 | // Returns the 'best' value in an array based on the result of a 125 | // given function. 126 | best: function(array, fun) { 127 | return _.reduce(array, function(x, y) { 128 | return fun(x, y) ? x : y; 129 | }); 130 | }, 131 | 132 | // Returns an array of existy results of a function over an source array. 133 | keep: function(array, fun) { 134 | if (!isSeq(array)) throw new TypeError("expected an array as the first argument"); 135 | 136 | return _.filter(_.map(array, function(e) { 137 | return fun(e); 138 | }), existy); 139 | } 140 | }); 141 | 142 | }).call(this); 143 | -------------------------------------------------------------------------------- /underscore.collections.walk.js: -------------------------------------------------------------------------------- 1 | // Underscore-contrib (underscore.collections.walk.js 0.3.0) 2 | // (c) 2013 Patrick Dubroy 3 | // Underscore-contrib may be freely distributed under the MIT license. 4 | 5 | (function() { 6 | 7 | // Baseline setup 8 | // -------------- 9 | 10 | // Establish the root object, `window` in the browser, or `require` it on the server. 11 | if (typeof exports === 'object') { 12 | _ = module.exports = require('underscore'); 13 | } 14 | 15 | // Helpers 16 | // ------- 17 | 18 | // An internal object that can be returned from a visitor function to 19 | // prevent a top-down walk from walking subtrees of a node. 20 | var stopRecursion = {}; 21 | 22 | // An internal object that can be returned from a visitor function to 23 | // cause the walk to immediately stop. 24 | var stopWalk = {}; 25 | 26 | var notTreeError = 'Not a tree: same object found in two different branches'; 27 | 28 | // Implements the default traversal strategy: if `obj` is a DOM node, walk 29 | // its DOM children; otherwise, walk all the objects it references. 30 | function defaultTraversal(obj) { 31 | return _.isElement(obj) ? obj.children : obj; 32 | } 33 | 34 | // Walk the tree recursively beginning with `root`, calling `beforeFunc` 35 | // before visiting an objects descendents, and `afterFunc` afterwards. 36 | // If `collectResults` is true, the last argument to `afterFunc` will be a 37 | // collection of the results of walking the node's subtrees. 38 | function walkImpl(root, traversalStrategy, beforeFunc, afterFunc, context, collectResults) { 39 | var visited = []; 40 | return (function _walk(value, key, parent) { 41 | // Keep track of objects that have been visited, and throw an exception 42 | // when trying to visit the same object twice. 43 | if (_.isObject(value)) { 44 | if (visited.indexOf(value) >= 0) throw new TypeError(notTreeError); 45 | visited.push(value); 46 | } 47 | 48 | if (beforeFunc) { 49 | var result = beforeFunc.call(context, value, key, parent); 50 | if (result === stopWalk) return stopWalk; 51 | if (result === stopRecursion) return; 52 | } 53 | 54 | var subResults; 55 | var target = traversalStrategy(value); 56 | if (_.isObject(target) && !_.isEmpty(target)) { 57 | // If collecting results from subtrees, collect them in the same shape 58 | // as the parent node. 59 | if (collectResults) subResults = _.isArray(value) ? [] : {}; 60 | 61 | var stop = _.any(target, function(obj, key) { 62 | var result = _walk(obj, key, value); 63 | if (result === stopWalk) return true; 64 | if (subResults) subResults[key] = result; 65 | }); 66 | if (stop) return stopWalk; 67 | } 68 | if (afterFunc) return afterFunc.call(context, value, key, parent, subResults); 69 | })(root); 70 | } 71 | 72 | // Internal helper providing the implementation for `pluck` and `pluckRec`. 73 | function pluck(obj, propertyName, recursive) { 74 | var results = []; 75 | this.preorder(obj, function(value, key) { 76 | if (!recursive && key == propertyName) 77 | return stopRecursion; 78 | if (_.has(value, propertyName)) 79 | results[results.length] = value[propertyName]; 80 | }); 81 | return results; 82 | } 83 | 84 | var exports = { 85 | // Performs a preorder traversal of `obj` and returns the first value 86 | // which passes a truth test. 87 | find: function(obj, visitor, context) { 88 | var result; 89 | this.preorder(obj, function(value, key, parent) { 90 | if (visitor.call(context, value, key, parent)) { 91 | result = value; 92 | return stopWalk; 93 | } 94 | }, context); 95 | return result; 96 | }, 97 | 98 | // Recursively traverses `obj` and returns all the elements that pass a 99 | // truth test. `strategy` is the traversal function to use, e.g. `preorder` 100 | // or `postorder`. 101 | filter: function(obj, strategy, visitor, context) { 102 | var results = []; 103 | if (obj == null) return results; 104 | strategy(obj, function(value, key, parent) { 105 | if (visitor.call(context, value, key, parent)) results.push(value); 106 | }, null, this._traversalStrategy); 107 | return results; 108 | }, 109 | 110 | // Recursively traverses `obj` and returns all the elements for which a 111 | // truth test fails. 112 | reject: function(obj, strategy, visitor, context) { 113 | return this.filter(obj, strategy, function(value, key, parent) { 114 | return !visitor.call(context, value, key, parent); 115 | }); 116 | }, 117 | 118 | // Produces a new array of values by recursively traversing `obj` and 119 | // mapping each value through the transformation function `visitor`. 120 | // `strategy` is the traversal function to use, e.g. `preorder` or 121 | // `postorder`. 122 | map: function(obj, strategy, visitor, context) { 123 | var results = []; 124 | strategy(obj, function(value, key, parent) { 125 | results[results.length] = visitor.call(context, value, key, parent); 126 | }, null, this._traversalStrategy); 127 | return results; 128 | }, 129 | 130 | // Return the value of properties named `propertyName` reachable from the 131 | // tree rooted at `obj`. Results are not recursively searched; use 132 | // `pluckRec` for that. 133 | pluck: function(obj, propertyName) { 134 | return pluck.call(this, obj, propertyName, false); 135 | }, 136 | 137 | // Version of `pluck` which recursively searches results for nested objects 138 | // with a property named `propertyName`. 139 | pluckRec: function(obj, propertyName) { 140 | return pluck.call(this, obj, propertyName, true); 141 | }, 142 | 143 | // Recursively traverses `obj` in a depth-first fashion, invoking the 144 | // `visitor` function for each object only after traversing its children. 145 | // `traversalStrategy` is intended for internal callers, and is not part 146 | // of the public API. 147 | postorder: function(obj, visitor, context, traversalStrategy) { 148 | traversalStrategy = traversalStrategy || this._traversalStrategy; 149 | walkImpl(obj, traversalStrategy, null, visitor, context); 150 | }, 151 | 152 | // Recursively traverses `obj` in a depth-first fashion, invoking the 153 | // `visitor` function for each object before traversing its children. 154 | // `traversalStrategy` is intended for internal callers, and is not part 155 | // of the public API. 156 | preorder: function(obj, visitor, context, traversalStrategy) { 157 | traversalStrategy = traversalStrategy || this._traversalStrategy; 158 | walkImpl(obj, traversalStrategy, visitor, null, context); 159 | }, 160 | 161 | // Builds up a single value by doing a post-order traversal of `obj` and 162 | // calling the `visitor` function on each object in the tree. For leaf 163 | // objects, the `memo` argument to `visitor` is the value of the `leafMemo` 164 | // argument to `reduce`. For non-leaf objects, `memo` is a collection of 165 | // the results of calling `reduce` on the object's children. 166 | reduce: function(obj, visitor, leafMemo, context) { 167 | var reducer = function(value, key, parent, subResults) { 168 | return visitor(subResults || leafMemo, value, key, parent); 169 | }; 170 | return walkImpl(obj, this._traversalStrategy, null, reducer, context, true); 171 | }, 172 | 173 | // Determine if the array contains a number of repated values 174 | containsAtLeast: function(list, count, value) { 175 | var filtered = _.filter(list, function(item) { 176 | return _.isEqual(item, value); 177 | }); 178 | return _.gte(_.size(filtered), count); 179 | }, 180 | 181 | // Determine if the array contains a number of repated values 182 | containsAtMost: function(list, count, value) { 183 | var filtered = _.filter(list, function(item) { 184 | return _.isEqual(item, value); 185 | }); 186 | return _.lte(_.size(filtered), count); 187 | } 188 | }; 189 | 190 | // Set up aliases to match those in underscore.js. 191 | exports.collect = exports.map; 192 | exports.detect = exports.find; 193 | exports.select = exports.filter; 194 | 195 | // Returns an object containing the walk functions. If `traversalStrategy` 196 | // is specified, it is a function determining how objects should be 197 | // traversed. Given an object, it returns the object to be recursively 198 | // walked. The default strategy is equivalent to `_.identity` for regular 199 | // objects, and for DOM nodes it returns the node's DOM children. 200 | _.walk = function(traversalStrategy) { 201 | var walker = _.clone(exports); 202 | 203 | // Bind all of the public functions in the walker to itself. This allows 204 | // the traversal strategy to be dynamically scoped. 205 | _.bindAll.apply(null, [walker].concat(_.keys(walker))); 206 | 207 | walker._traversalStrategy = traversalStrategy || defaultTraversal; 208 | return walker; 209 | }; 210 | 211 | // Use `_.walk` as a namespace to hold versions of the walk functions which 212 | // use the default traversal strategy. 213 | _.extend(_.walk, _.walk()); 214 | }).call(this); 215 | -------------------------------------------------------------------------------- /underscore.function.arity.js: -------------------------------------------------------------------------------- 1 | // Underscore-contrib (underscore.function.arity.js 0.3.0) 2 | // (c) 2013 Michael Fogus, DocumentCloud and Investigative Reporters & Editors 3 | // Underscore-contrib may be freely distributed under the MIT license. 4 | 5 | (function() { 6 | 7 | // Baseline setup 8 | // -------------- 9 | 10 | // Establish the root object, `window` in the browser, or `require` it on the server. 11 | if (typeof exports === 'object') { 12 | _ = module.exports = require('underscore'); 13 | } 14 | 15 | // Helpers 16 | // ------- 17 | 18 | function enforcesUnary (fn) { 19 | return function mustBeUnary () { 20 | if (arguments.length === 1) { 21 | return fn.apply(this, arguments); 22 | } 23 | else throw new RangeError('Only a single argument may be accepted.'); 24 | 25 | }; 26 | } 27 | 28 | // Curry 29 | // ------- 30 | var curry = (function () { 31 | function collectArgs(func, that, argCount, args, newArg, reverse) { 32 | if (reverse === true) { 33 | args.unshift(newArg); 34 | } else { 35 | args.push(newArg); 36 | } 37 | if (args.length == argCount) { 38 | return func.apply(that, args); 39 | } else { 40 | return enforcesUnary(function () { 41 | return collectArgs(func, that, argCount, args.slice(0), arguments[0], reverse); 42 | }); 43 | } 44 | } 45 | return function curry (func, reverse) { 46 | var that = this; 47 | return enforcesUnary(function () { 48 | return collectArgs(func, that, func.length, [], arguments[0], reverse); 49 | }); 50 | }; 51 | }()); 52 | 53 | // Enforce Arity 54 | // -------------------- 55 | var enforce = (function () { 56 | var CACHE = []; 57 | return function enforce (func) { 58 | if (typeof func !== 'function') { 59 | throw new Error('Argument 1 must be a function.'); 60 | } 61 | var funcLength = func.length; 62 | if (CACHE[funcLength] === undefined) { 63 | CACHE[funcLength] = function (enforceFunc) { 64 | return function () { 65 | if (arguments.length !== funcLength) { 66 | throw new RangeError(funcLength + ' arguments must be applied.'); 67 | } 68 | return enforceFunc.apply(this, arguments); 69 | }; 70 | }; 71 | } 72 | return CACHE[funcLength](func); 73 | }; 74 | }()); 75 | 76 | // Right curry variants 77 | // --------------------- 78 | var curryRight = function (func) { 79 | return curry.call(this, func, true); 80 | }; 81 | 82 | var curryRight2 = function (fun) { 83 | return enforcesUnary(function (last) { 84 | return enforcesUnary(function (first) { 85 | return fun.call(this, first, last); 86 | }); 87 | }); 88 | }; 89 | 90 | var curryRight3 = function (fun) { 91 | return enforcesUnary(function (last) { 92 | return enforcesUnary(function (second) { 93 | return enforcesUnary(function (first) { 94 | return fun.call(this, first, second, last); 95 | }); 96 | }); 97 | }); 98 | }; 99 | 100 | // Mixing in the arity functions 101 | // ----------------------------- 102 | 103 | _.mixin({ 104 | // ### Fixed arguments 105 | 106 | // Fixes the arguments to a function based on the parameter template defined by 107 | // the presence of values and the `_` placeholder. 108 | fix: function(fun) { 109 | var fixArgs = _.rest(arguments); 110 | 111 | var f = function() { 112 | var args = fixArgs.slice(); 113 | var arg = 0; 114 | 115 | for ( var i = 0; i < (args.length || arg < arguments.length); i++ ) { 116 | if ( args[i] === _ ) { 117 | args[i] = arguments[arg++]; 118 | } 119 | } 120 | 121 | return fun.apply(null, args); 122 | }; 123 | 124 | f._original = fun; 125 | 126 | return f; 127 | }, 128 | 129 | unary: function (fun) { 130 | return function unary (a) { 131 | return fun.call(this, a); 132 | }; 133 | }, 134 | 135 | binary: function (fun) { 136 | return function binary (a, b) { 137 | return fun.call(this, a, b); 138 | }; 139 | }, 140 | 141 | ternary: function (fun) { 142 | return function ternary (a, b, c) { 143 | return fun.call(this, a, b, c); 144 | }; 145 | }, 146 | 147 | quaternary: function (fun) { 148 | return function quaternary (a, b, c, d) { 149 | return fun.call(this, a, b, c, d); 150 | }; 151 | }, 152 | 153 | // Flexible curry function with strict arity. 154 | // Argument application left to right. 155 | // source: https://github.com/eborden/js-curry 156 | curry: curry, 157 | 158 | // Flexible right to left curry with strict arity. 159 | curryRight: curryRight, 160 | rCurry: curryRight, // alias for backwards compatibility 161 | 162 | 163 | curry2: function (fun) { 164 | return enforcesUnary(function curried (first) { 165 | return enforcesUnary(function (last) { 166 | return fun.call(this, first, last); 167 | }); 168 | }); 169 | }, 170 | 171 | curry3: function (fun) { 172 | return enforcesUnary(function (first) { 173 | return enforcesUnary(function (second) { 174 | return enforcesUnary(function (last) { 175 | return fun.call(this, first, second, last); 176 | }); 177 | }); 178 | }); 179 | }, 180 | 181 | // reverse currying for functions taking two arguments. 182 | curryRight2: curryRight2, 183 | rcurry2: curryRight2, // alias for backwards compatibility 184 | 185 | curryRight3: curryRight3, 186 | rcurry3: curryRight3, // alias for backwards compatibility 187 | 188 | // Dynamic decorator to enforce function arity and defeat varargs. 189 | enforce: enforce 190 | }); 191 | 192 | _.arity = (function () { 193 | // Allow 'new Function', as that is currently the only reliable way 194 | // to manipulate function.length 195 | /* jshint -W054 */ 196 | var FUNCTIONS = {}; 197 | return function arity (numberOfArgs, fun) { 198 | if (FUNCTIONS[numberOfArgs] == null) { 199 | var parameters = new Array(numberOfArgs); 200 | for (var i = 0; i < numberOfArgs; ++i) { 201 | parameters[i] = "__" + i; 202 | } 203 | var pstr = parameters.join(); 204 | var code = "return function ("+pstr+") { return fun.apply(this, arguments); };"; 205 | FUNCTIONS[numberOfArgs] = new Function(['fun'], code); 206 | } 207 | if (fun == null) { 208 | return function (fun) { return arity(numberOfArgs, fun); }; 209 | } 210 | else return FUNCTIONS[numberOfArgs](fun); 211 | }; 212 | })(); 213 | 214 | }).call(this); 215 | -------------------------------------------------------------------------------- /underscore.function.combinators.js: -------------------------------------------------------------------------------- 1 | // Underscore-contrib (underscore.function.combinators.js 0.3.0) 2 | // (c) 2013 Michael Fogus, DocumentCloud and Investigative Reporters & Editors 3 | // Underscore-contrib may be freely distributed under the MIT license. 4 | 5 | (function() { 6 | 7 | // Baseline setup 8 | // -------------- 9 | 10 | // Establish the root object, `window` in the browser, or `require` it on the server. 11 | if (typeof exports === 'object') { 12 | _ = module.exports = require('underscore'); 13 | } 14 | 15 | // Helpers 16 | // ------- 17 | 18 | var existy = function(x) { return x != null; }; 19 | var truthy = function(x) { return (x !== false) && existy(x); }; 20 | var __reverse = [].reverse; 21 | var __slice = [].slice; 22 | var __map = [].map; 23 | var curry2 = function (fun) { 24 | return function curried (first, optionalLast) { 25 | if (arguments.length === 1) { 26 | return function (last) { 27 | return fun(first, last); 28 | }; 29 | } 30 | else return fun(first, optionalLast); 31 | }; 32 | }; 33 | 34 | var createPredicateApplicator = function (funcToInvoke /*, preds */) { 35 | var preds = _(arguments).tail(); 36 | 37 | return function (objToCheck) { 38 | var array = _(objToCheck).cat(); 39 | 40 | return _[funcToInvoke](array, function (e) { 41 | return _[funcToInvoke](preds, function (p) { 42 | return p(e); 43 | }); 44 | }); 45 | }; 46 | }; 47 | 48 | // n.b. depends on underscore.function.arity.js 49 | // n.b. depends on underscore.array.builders.js 50 | 51 | // Takes a target function and a mapping function. Returns a function 52 | // that applies the mapper to its arguments before evaluating the body. 53 | function baseMapArgs (fun, mapFun) { 54 | return _.arity(fun.length, function () { 55 | return fun.apply(this, __map.call(arguments, mapFun)); 56 | }); 57 | } 58 | 59 | // Mixing in the combinator functions 60 | // ---------------------------------- 61 | 62 | _.mixin({ 63 | // Provide "always" alias for backwards compatibility 64 | always: _.constant, 65 | 66 | // Takes some number of functions, either as an array or variadically 67 | // and returns a function that takes some value as its first argument 68 | // and runs it through a pipeline of the original functions given. 69 | pipeline: function(/*, funs */){ 70 | var funs = (_.isArray(arguments[0])) ? arguments[0] : arguments; 71 | 72 | return function(seed) { 73 | return _.reduce(funs, 74 | function(l,r) { return r(l); }, 75 | seed); 76 | }; 77 | }, 78 | 79 | // Composes a bunch of predicates into a single predicate that 80 | // checks all elements of an array for conformance to all of the 81 | // original predicates. 82 | conjoin: _.partial(createPredicateApplicator, ('every')), 83 | 84 | // Composes a bunch of predicates into a single predicate that 85 | // checks all elements of an array for conformance to any of the 86 | // original predicates. 87 | disjoin: _.partial(createPredicateApplicator, 'some'), 88 | 89 | // Takes a predicate-like and returns a comparator (-1,0,1). 90 | comparator: function(fun) { 91 | return function(x, y) { 92 | if (truthy(fun(x, y))) 93 | return -1; 94 | else if (truthy(fun(y, x))) 95 | return 1; 96 | else 97 | return 0; 98 | }; 99 | }, 100 | 101 | // Returns a function that reverses the sense of a given predicate-like. 102 | complement: function(pred) { 103 | return function() { 104 | return !pred.apply(this, arguments); 105 | }; 106 | }, 107 | 108 | // Takes a function expecting varargs and 109 | // returns a function that takes an array and 110 | // uses its elements as the args to the original 111 | // function 112 | splat: function(fun) { 113 | return function(array) { 114 | return fun.apply(this, array); 115 | }; 116 | }, 117 | 118 | // Takes a function expecting an array and returns 119 | // a function that takes varargs and wraps all 120 | // in an array that is passed to the original function. 121 | unsplat: function(fun) { 122 | var funLength = fun.length; 123 | 124 | if (funLength < 1) { 125 | return fun; 126 | } 127 | else if (funLength === 1) { 128 | return function () { 129 | return fun.call(this, __slice.call(arguments, 0)); 130 | }; 131 | } 132 | else { 133 | return function () { 134 | var numberOfArgs = arguments.length, 135 | namedArgs = __slice.call(arguments, 0, funLength - 1), 136 | numberOfMissingNamedArgs = Math.max(funLength - numberOfArgs - 1, 0), 137 | argPadding = new Array(numberOfMissingNamedArgs), 138 | variadicArgs = __slice.call(arguments, fun.length - 1); 139 | 140 | return fun.apply(this, namedArgs.concat(argPadding).concat([variadicArgs])); 141 | }; 142 | } 143 | }, 144 | 145 | // Same as unsplat, but the rest of the arguments are collected in the 146 | // first parameter, e.g. unsplatl( function (args, callback) { ... ]}) 147 | unsplatl: function(fun) { 148 | var funLength = fun.length; 149 | 150 | if (funLength < 1) { 151 | return fun; 152 | } 153 | else if (funLength === 1) { 154 | return function () { 155 | return fun.call(this, __slice.call(arguments, 0)); 156 | }; 157 | } 158 | else { 159 | return function () { 160 | var numberOfArgs = arguments.length, 161 | namedArgs = __slice.call(arguments, Math.max(numberOfArgs - funLength + 1, 0)), 162 | variadicArgs = __slice.call(arguments, 0, Math.max(numberOfArgs - funLength + 1, 0)); 163 | 164 | return fun.apply(this, [variadicArgs].concat(namedArgs)); 165 | }; 166 | } 167 | }, 168 | 169 | // map the arguments of a function 170 | mapArgs: curry2(baseMapArgs), 171 | 172 | // Returns a function that returns an array of the calls to each 173 | // given function for some arguments. 174 | juxt: function(/* funs */) { 175 | var funs = arguments; 176 | 177 | return function(/* args */) { 178 | var args = arguments; 179 | return _.map(funs, function(f) { 180 | return f.apply(this, args); 181 | }, this); 182 | }; 183 | }, 184 | 185 | // Returns a function that protects a given function from receiving 186 | // non-existy values. Each subsequent value provided to `fnull` acts 187 | // as the default to the original function should a call receive non-existy 188 | // values in the defaulted arg slots. 189 | fnull: function(fun /*, defaults */) { 190 | var defaults = _.rest(arguments); 191 | 192 | return function(/*args*/) { 193 | var args = _.toArray(arguments); 194 | var sz = _.size(defaults); 195 | 196 | for(var i = 0; i < sz; i++) { 197 | if (!existy(args[i])) 198 | args[i] = defaults[i]; 199 | } 200 | 201 | return fun.apply(this, args); 202 | }; 203 | }, 204 | 205 | // Flips the first two args of a function 206 | flip2: function(fun) { 207 | return function(/* args */) { 208 | var flipped = __slice.call(arguments); 209 | flipped[0] = arguments[1]; 210 | flipped[1] = arguments[0]; 211 | 212 | return fun.apply(this, flipped); 213 | }; 214 | }, 215 | 216 | // Flips an arbitrary number of args of a function 217 | flip: function(fun) { 218 | return function(/* args */) { 219 | var reversed = __reverse.call(arguments); 220 | 221 | return fun.apply(this, reversed); 222 | }; 223 | }, 224 | 225 | // Takes a method-style function (one which uses `this`) and pushes 226 | // `this` into the argument list. The returned function uses its first 227 | // argument as the receiver/context of the original function, and the rest 228 | // of the arguments are used as the original's entire argument list. 229 | functionalize: function(method) { 230 | return function(ctx /*, args */) { 231 | return method.apply(ctx, _.rest(arguments)); 232 | }; 233 | }, 234 | 235 | // Takes a function and pulls the first argument out of the argument 236 | // list and into `this` position. The returned function calls the original 237 | // with its receiver (`this`) prepending the argument list. The original 238 | // is called with a receiver of `null`. 239 | methodize: function(func) { 240 | return function(/* args */) { 241 | return func.apply(null, _.cons(this, arguments)); 242 | }; 243 | }, 244 | 245 | k: _.always, 246 | t: _.pipeline 247 | }); 248 | 249 | _.unsplatr = _.unsplat; 250 | 251 | // map the arguments of a function, takes the mapping function 252 | // first so it can be used as a combinator 253 | _.mapArgsWith = curry2(_.flip(baseMapArgs)); 254 | 255 | // Returns function property of object by name, bound to object 256 | _.bound = function(obj, fname) { 257 | var fn = obj[fname]; 258 | if (!_.isFunction(fn)) 259 | throw new TypeError("Expected property to be a function"); 260 | return _.bind(fn, obj); 261 | }; 262 | 263 | }).call(this); 264 | -------------------------------------------------------------------------------- /underscore.function.dispatch.js: -------------------------------------------------------------------------------- 1 | // Underscore-contrib (underscore.function.dispatch.js 0.3.0) 2 | // (c) 2013 Justin Ridgewell 3 | // Underscore-contrib may be freely distributed under the MIT license. 4 | 5 | (function() { 6 | 7 | // Baseline setup 8 | // -------------- 9 | 10 | // Establish the root object, `window` in the browser, or `require` it on the server. 11 | if (typeof exports === 'object') { 12 | _ = module.exports = require('underscore'); 13 | } 14 | 15 | // Helpers 16 | // ------- 17 | 18 | // Create quick reference variable for speed. 19 | var slice = Array.prototype.slice; 20 | 21 | // Mixing in the attempt function 22 | // ------------------------ 23 | 24 | _.mixin({ 25 | // If object is not undefined or null then invoke the named `method` function 26 | // with `object` as context and arguments; otherwise, return undefined. 27 | attempt: function(object, method) { 28 | if (object == null) return void 0; 29 | var func = object[method]; 30 | var args = slice.call(arguments, 2); 31 | return _.isFunction(func) ? func.apply(object, args) : void 0; 32 | } 33 | }); 34 | 35 | }).call(this); 36 | -------------------------------------------------------------------------------- /underscore.function.iterators.js: -------------------------------------------------------------------------------- 1 | // Underscore-contrib (underscore.function.iterators.js 0.3.0) 2 | // (c) 2013 Michael Fogus and DocumentCloud Inc. 3 | // Underscore-contrib may be freely distributed under the MIT license. 4 | 5 | (function() { 6 | 7 | // Baseline setup 8 | // -------------- 9 | 10 | // Establish the root object, `window` in the browser, or `require` it on the server. 11 | if (typeof exports === 'object') { 12 | _ = module.exports = require('underscore'); 13 | } 14 | 15 | // Helpers 16 | // ------- 17 | 18 | var HASNTBEENRUN = {}; 19 | 20 | function unary (fun) { 21 | return function (first) { 22 | return fun.call(this, first); 23 | }; 24 | } 25 | 26 | function binary (fun) { 27 | return function (first, second) { 28 | return fun.call(this, first, second); 29 | }; 30 | } 31 | 32 | // Mixing in the iterator functions 33 | // -------------------------------- 34 | 35 | function foldl (iter, binaryFn, seed) { 36 | var state, element; 37 | if (seed !== void 0) { 38 | state = seed; 39 | } 40 | else { 41 | state = iter(); 42 | } 43 | element = iter(); 44 | while (element != null) { 45 | state = binaryFn.call(element, state, element); 46 | element = iter(); 47 | } 48 | return state; 49 | } 50 | 51 | function unfold (seed, unaryFn) { 52 | var state = HASNTBEENRUN; 53 | return function () { 54 | if (state === HASNTBEENRUN) { 55 | state = seed; 56 | } else if (state != null) { 57 | state = unaryFn.call(state, state); 58 | } 59 | 60 | return state; 61 | }; 62 | } 63 | 64 | // note that the unfoldWithReturn behaves differently than 65 | // unfold with respect to the first value returned 66 | function unfoldWithReturn (seed, unaryFn) { 67 | var state = seed, 68 | pair, 69 | value; 70 | return function () { 71 | if (state != null) { 72 | pair = unaryFn.call(state, state); 73 | value = pair[1]; 74 | state = value != null ? pair[0] : void 0; 75 | return value; 76 | } 77 | else return void 0; 78 | }; 79 | } 80 | 81 | function accumulate (iter, binaryFn, initial) { 82 | var state = initial; 83 | return function () { 84 | var element = iter(); 85 | if (element == null) { 86 | return element; 87 | } 88 | else { 89 | if (state === void 0) { 90 | state = element; 91 | } else { 92 | state = binaryFn.call(element, state, element); 93 | } 94 | 95 | return state; 96 | } 97 | }; 98 | } 99 | 100 | function accumulateWithReturn (iter, binaryFn, initial) { 101 | var state = initial, 102 | stateAndReturnValue, 103 | element; 104 | return function () { 105 | element = iter(); 106 | if (element == null) { 107 | return element; 108 | } 109 | else { 110 | if (state === void 0) { 111 | state = element; 112 | return state; 113 | } 114 | else { 115 | stateAndReturnValue = binaryFn.call(element, state, element); 116 | state = stateAndReturnValue[0]; 117 | return stateAndReturnValue[1]; 118 | } 119 | } 120 | }; 121 | } 122 | 123 | function map (iter, unaryFn) { 124 | return function() { 125 | var element; 126 | element = iter(); 127 | if (element != null) { 128 | return unaryFn.call(element, element); 129 | } else { 130 | return void 0; 131 | } 132 | }; 133 | } 134 | 135 | function mapcat(iter, unaryFn) { 136 | var lastIter = null; 137 | return function() { 138 | var element; 139 | var gen; 140 | if (lastIter == null) { 141 | gen = iter(); 142 | if (gen == null) { 143 | lastIter = null; 144 | return void 0; 145 | } 146 | lastIter = unaryFn.call(gen, gen); 147 | } 148 | while (element == null) { 149 | element = lastIter(); 150 | if (element == null) { 151 | gen = iter(); 152 | if (gen == null) { 153 | lastIter = null; 154 | return void 0; 155 | } 156 | else { 157 | lastIter = unaryFn.call(gen, gen); 158 | } 159 | } 160 | } 161 | return element; 162 | }; 163 | } 164 | 165 | function select (iter, unaryPredicateFn) { 166 | return function() { 167 | var element; 168 | element = iter(); 169 | while (element != null) { 170 | if (unaryPredicateFn.call(element, element)) { 171 | return element; 172 | } 173 | element = iter(); 174 | } 175 | return void 0; 176 | }; 177 | } 178 | 179 | function reject (iter, unaryPredicateFn) { 180 | return select(iter, function (something) { 181 | return !unaryPredicateFn(something); 182 | }); 183 | } 184 | 185 | function find (iter, unaryPredicateFn) { 186 | return select(iter, unaryPredicateFn)(); 187 | } 188 | 189 | function slice (iter, numberToDrop, numberToTake) { 190 | var count = 0; 191 | while (numberToDrop-- > 0) { 192 | iter(); 193 | } 194 | if (numberToTake != null) { 195 | return function() { 196 | if (++count <= numberToTake) { 197 | return iter(); 198 | } else { 199 | return void 0; 200 | } 201 | }; 202 | } 203 | else return iter; 204 | } 205 | 206 | function drop (iter, numberToDrop) { 207 | return slice(iter, numberToDrop == null ? 1 : numberToDrop); 208 | } 209 | 210 | function take (iter, numberToTake) { 211 | return slice(iter, 0, numberToTake == null ? 1 : numberToTake); 212 | } 213 | 214 | function List (array) { 215 | var index = 0; 216 | return function() { 217 | return array[index++]; 218 | }; 219 | } 220 | 221 | function cycle(array) { 222 | var index = 0, 223 | length = array.length; 224 | return function() { 225 | return array[index++ % length]; 226 | }; 227 | } 228 | 229 | function Tree (array) { 230 | var index, myself, state; 231 | index = 0; 232 | state = []; 233 | myself = function() { 234 | var element, tempState; 235 | element = array[index++]; 236 | if (element instanceof Array) { 237 | state.push({ 238 | array: array, 239 | index: index 240 | }); 241 | array = element; 242 | index = 0; 243 | return myself(); 244 | } else if (element === void 0) { 245 | if (state.length > 0) { 246 | tempState = state.pop(); 247 | array = tempState.array; 248 | index = tempState.index; 249 | return myself(); 250 | } else { 251 | return void 0; 252 | } 253 | } else { 254 | return element; 255 | } 256 | }; 257 | return myself; 258 | } 259 | 260 | function K (value) { 261 | return function () { 262 | return value; 263 | }; 264 | } 265 | 266 | function upRange (from, to, by) { 267 | return function () { 268 | var was; 269 | 270 | if (from > to) { 271 | return void 0; 272 | } 273 | else { 274 | was = from; 275 | from = from + by; 276 | return was; 277 | } 278 | }; 279 | } 280 | 281 | function downRange (from, to, by) { 282 | return function () { 283 | var was; 284 | 285 | if (from < to) { 286 | return void 0; 287 | } 288 | else { 289 | was = from; 290 | from = from - by; 291 | return was; 292 | } 293 | }; 294 | } 295 | 296 | function range (from, to, by) { 297 | if (from == null) { 298 | return upRange(1, Infinity, 1); 299 | } 300 | else if (to == null) { 301 | return upRange(from, Infinity, 1); 302 | } 303 | else if (by == null) { 304 | if (from <= to) { 305 | return upRange(from, to, 1); 306 | } 307 | else return downRange(from, to, 1); 308 | } 309 | else if (by > 0) { 310 | return upRange(from, to, by); 311 | } 312 | else if (by < 0) { 313 | return downRange(from, to, Math.abs(by)); 314 | } 315 | else return k(from); 316 | } 317 | 318 | var numbers = unary(range); 319 | 320 | _.iterators = { 321 | accumulate: accumulate, 322 | accumulateWithReturn: accumulateWithReturn, 323 | foldl: foldl, 324 | reduce: foldl, 325 | unfold: unfold, 326 | unfoldWithReturn: unfoldWithReturn, 327 | map: map, 328 | mapcat: mapcat, 329 | select: select, 330 | reject: reject, 331 | filter: select, 332 | find: find, 333 | slice: slice, 334 | drop: drop, 335 | take: take, 336 | List: List, 337 | Tree: Tree, 338 | constant: K, 339 | K: K, 340 | numbers: numbers, 341 | range: range, 342 | cycle: cycle 343 | }; 344 | 345 | }).call(this, void 0); 346 | -------------------------------------------------------------------------------- /underscore.function.predicates.js: -------------------------------------------------------------------------------- 1 | // Underscore-contrib (underscore.function.predicates.js 0.3.0) 2 | // (c) 2013 Michael Fogus, DocumentCloud and Investigative Reporters & Editors 3 | // Underscore-contrib may be freely distributed under the MIT license. 4 | 5 | // Establish the root object, `window` in the browser, or `require` it on the server. 6 | if (typeof exports === 'object') { 7 | _ = module.exports = require('underscore'); 8 | } 9 | 10 | // Helpers 11 | // ------- 12 | 13 | 14 | // Mixing in the predicate functions 15 | // --------------------------------- 16 | 17 | _.mixin({ 18 | // A wrapper around instanceof 19 | isInstanceOf: function(x, t) { return (x instanceof t); }, 20 | 21 | // An associative object is one where its elements are 22 | // accessed via a key or index. (i.e. array and object) 23 | isAssociative: function(x) { return _.isArray(x) || _.isObject(x) || _.isArguments(x); }, 24 | 25 | // An indexed object is anything that allows numerical index for 26 | // accessing its elements (e.g. arrays and strings). NOTE: Underscore 27 | // does not support cross-browser consistent use of strings as array-like 28 | // objects, so be wary in IE 8 when using String objects and IE<8. 29 | // on string literals & objects. 30 | isIndexed: function(x) { return _.isArray(x) || _.isString(x) || _.isArguments(x); }, 31 | 32 | // A seq is something considered a sequential composite type (i.e. arrays and `arguments`). 33 | isSequential: function(x) { return (_.isArray(x)) || (_.isArguments(x)); }, 34 | 35 | // Check if an object is an object literal, since _.isObject(function() {}) === _.isObject([]) === true 36 | isPlainObject: function(x) { return _.isObject(x) && x.constructor === Object; }, 37 | 38 | // These do what you think that they do 39 | isZero: function(x) { return 0 === x; }, 40 | isEven: function(x) { return _.isFinite(x) && (x & 1) === 0; }, 41 | isOdd: function(x) { return _.isFinite(x) && !_.isEven(x); }, 42 | isPositive: function(x) { return x > 0; }, 43 | isNegative: function(x) { return x < 0; }, 44 | isValidDate: function(x) { return _.isDate(x) && !_.isNaN(x.getTime()); }, 45 | 46 | // A numeric is a variable that contains a numeric value, regardless its type 47 | // It can be a String containing a numeric value, exponential notation, or a Number object 48 | // See here for more discussion: http://stackoverflow.com/questions/18082/validate-numbers-in-javascript-isnumeric/1830844#1830844 49 | isNumeric: function(n) { 50 | return !isNaN(parseFloat(n)) && isFinite(n); 51 | }, 52 | 53 | // An integer contains an optional minus sign to begin and only the digits 0-9 54 | // Objects that can be parsed that way are also considered ints, e.g. "123" 55 | // Floats that are mathematically equal to integers are considered integers, e.g. 1.0 56 | // See here for more discussion: http://stackoverflow.com/questions/1019515/javascript-test-for-an-integer 57 | isInteger: function(i) { 58 | return _.isNumeric(i) && i % 1 === 0; 59 | }, 60 | 61 | // A float is a numbr that is not an integer. 62 | isFloat: function(n) { 63 | return _.isNumeric(n) && !_.isInteger(n); 64 | }, 65 | 66 | // checks if a string is a valid JSON 67 | isJSON: function(str) { 68 | try { 69 | JSON.parse(str); 70 | } catch (e) { 71 | return false; 72 | } 73 | return true; 74 | }, 75 | 76 | // Returns true if its arguments are monotonically 77 | // increaing values; false otherwise. 78 | isIncreasing: function() { 79 | var count = _.size(arguments); 80 | if (count === 1) return true; 81 | if (count === 2) return arguments[0] < arguments[1]; 82 | 83 | for (var i = 1; i < count; i++) { 84 | if (arguments[i-1] >= arguments[i]) { 85 | return false; 86 | } 87 | } 88 | 89 | return true; 90 | }, 91 | 92 | // Returns true if its arguments are monotonically 93 | // decreaing values; false otherwise. 94 | isDecreasing: function() { 95 | var count = _.size(arguments); 96 | if (count === 1) return true; 97 | if (count === 2) return arguments[0] > arguments[1]; 98 | 99 | for (var i = 1; i < count; i++) { 100 | if (arguments[i-1] <= arguments[i]) { 101 | return false; 102 | } 103 | } 104 | 105 | return true; 106 | } 107 | }); 108 | -------------------------------------------------------------------------------- /underscore.object.builders.js: -------------------------------------------------------------------------------- 1 | // Underscore-contrib (underscore.object.builders.js 0.3.0) 2 | // (c) 2013 Michael Fogus, DocumentCloud and Investigative Reporters & Editors 3 | // Underscore-contrib may be freely distributed under the MIT license. 4 | 5 | (function() { 6 | 7 | // Baseline setup 8 | // -------------- 9 | 10 | // Establish the root object, `window` in the browser, or `require` it on the server. 11 | if (typeof exports === 'object') { 12 | _ = module.exports = require('underscore'); 13 | } 14 | 15 | // Helpers 16 | // ------- 17 | 18 | // Create quick reference variables for speed access to core prototypes. 19 | var slice = Array.prototype.slice, 20 | concat = Array.prototype.concat; 21 | 22 | var existy = function(x) { return x != null; }; 23 | var truthy = function(x) { return (x !== false) && existy(x); }; 24 | var isAssociative = function(x) { return _.isArray(x) || _.isObject(x); }; 25 | var curry2 = function(fun) { 26 | return function(last) { 27 | return function(first) { 28 | return fun(first, last); 29 | }; 30 | }; 31 | }; 32 | 33 | // Mixing in the object builders 34 | // ---------------------------- 35 | 36 | _.mixin({ 37 | // Merges two or more objects starting with the left-most and 38 | // applying the keys right-word 39 | // {any:any}* -> {any:any} 40 | merge: function(/* objs */){ 41 | var dest = _.some(arguments) ? {} : null; 42 | 43 | if (truthy(dest)) { 44 | _.extend.apply(null, concat.call([dest], _.toArray(arguments))); 45 | } 46 | 47 | return dest; 48 | }, 49 | 50 | // Takes an object and another object of strings to strings where the second 51 | // object describes the key renaming to occur in the first object. 52 | renameKeys: function(obj, kobj) { 53 | return _.reduce(kobj, function(o, nu, old) { 54 | if (existy(obj[old])) { 55 | o[nu] = obj[old]; 56 | return o; 57 | } 58 | else 59 | return o; 60 | }, 61 | _.omit.apply(null, concat.call([obj], _.keys(kobj)))); 62 | }, 63 | 64 | // Snapshots an object deeply. Based on the version by 65 | // [Keith Devens](http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone) 66 | // until we can find a more efficient and robust way to do it. 67 | snapshot: function(obj) { 68 | if(obj == null || typeof(obj) != 'object') { 69 | return obj; 70 | } 71 | 72 | var temp = new obj.constructor(); 73 | 74 | for(var key in obj) { 75 | if (obj.hasOwnProperty(key)) { 76 | temp[key] = _.snapshot(obj[key]); 77 | } 78 | } 79 | 80 | return temp; 81 | }, 82 | 83 | // Updates the value at any depth in a nested object based on the 84 | // path described by the keys given. The function provided is supplied 85 | // the current value and is expected to return a value for use as the 86 | // new value. If no keys are provided, then the object itself is presented 87 | // to the given function. 88 | updatePath: function(obj, fun, ks, defaultValue) { 89 | if (!isAssociative(obj)) throw new TypeError("Attempted to update a non-associative object."); 90 | if (!existy(ks)) return fun(obj); 91 | 92 | var deepness = _.isArray(ks); 93 | var keys = deepness ? ks : [ks]; 94 | var ret = deepness ? _.snapshot(obj) : _.clone(obj); 95 | var lastKey = _.last(keys); 96 | var target = ret; 97 | 98 | _.each(_.initial(keys), function(key) { 99 | if (defaultValue && !_.has(target, key)) { 100 | target[key] = _.clone(defaultValue); 101 | } 102 | target = target[key]; 103 | }); 104 | 105 | target[lastKey] = fun(target[lastKey]); 106 | return ret; 107 | }, 108 | 109 | // Returns an object excluding the value represented by the path 110 | omitPath: function(obj, ks, copy){ 111 | if (!obj) return copy; 112 | if (typeof ks == "string") ks = ks.split("."); 113 | if (!copy) copy = obj = _.snapshot(obj); 114 | if (ks.length > 1) return _.omitPath(obj[ks[0]], _.tail(ks), copy); 115 | delete obj[ks[0]]; 116 | return copy; 117 | }, 118 | 119 | // Sets the value at any depth in a nested object based on the 120 | // path described by the keys given. 121 | setPath: function(obj, value, ks, defaultValue) { 122 | if (!existy(ks)) throw new TypeError("Attempted to set a property at a null path."); 123 | 124 | return _.updatePath(obj, function() { return value; }, ks, defaultValue); 125 | }, 126 | 127 | // Returns an object where each element of an array is keyed to 128 | // the number of times that it occurred in said array. 129 | frequencies: curry2(_.countBy)(_.identity) 130 | }); 131 | 132 | }).call(this); 133 | -------------------------------------------------------------------------------- /underscore.object.selectors.js: -------------------------------------------------------------------------------- 1 | // Underscore-contrib (underscore.object.selectors.js 0.3.0) 2 | // (c) 2013 Michael Fogus, DocumentCloud and Investigative Reporters & Editors 3 | // Underscore-contrib may be freely distributed under the MIT license. 4 | 5 | (function() { 6 | 7 | // Baseline setup 8 | // -------------- 9 | 10 | // Establish the root object, `window` in the browser, or `require` it on the server. 11 | if (typeof exports === 'object') { 12 | _ = module.exports = require('underscore'); 13 | } 14 | 15 | // Helpers 16 | // ------- 17 | 18 | // Create quick reference variables for speed access to core prototypes. 19 | var concat = Array.prototype.concat; 20 | var ArrayProto = Array.prototype; 21 | var slice = ArrayProto.slice; 22 | 23 | // Will take a path like 'element[0][1].subElement["Hey!.What?"]["[hey]"]' 24 | // and return ["element", "0", "1", "subElement", "Hey!.What?", "[hey]"] 25 | function keysFromPath(path) { 26 | // from http://codereview.stackexchange.com/a/63010/8176 27 | /** 28 | * Repeatedly capture either: 29 | * - a bracketed expression, discarding optional matching quotes inside, or 30 | * - an unbracketed expression, delimited by a dot or a bracket. 31 | */ 32 | var re = /\[("|')(.+)\1\]|([^.\[\]]+)/g; 33 | 34 | var elements = []; 35 | var result; 36 | while ((result = re.exec(path)) !== null) { 37 | elements.push(result[2] || result[3]); 38 | } 39 | return elements; 40 | } 41 | 42 | // Mixing in the object selectors 43 | // ------------------------------ 44 | 45 | _.mixin({ 46 | // Returns a function that will attempt to look up a named field 47 | // in any object that it's given. 48 | accessor: function(field) { 49 | return function(obj) { 50 | return (obj && obj[field]); 51 | }; 52 | }, 53 | 54 | // Given an object, returns a function that will attempt to look up a field 55 | // that it's given. 56 | dictionary: function (obj) { 57 | return function(field) { 58 | return (obj && field && obj[field]); 59 | }; 60 | }, 61 | 62 | // Like `_.pick` except that it takes an array of keys to pick. 63 | selectKeys: function (obj, ks) { 64 | return _.pick.apply(null, concat.call([obj], ks)); 65 | }, 66 | 67 | // Returns the key/value pair for a given property in an object, undefined if not found. 68 | kv: function(obj, key) { 69 | if (_.has(obj, key)) { 70 | return [key, obj[key]]; 71 | } 72 | 73 | return void 0; 74 | }, 75 | 76 | // Gets the value at any depth in a nested object based on the 77 | // path described by the keys given. Keys may be given as an array 78 | // or as a dot-separated string. 79 | getPath: function getPath (obj, ks) { 80 | ks = typeof ks == "string" ? keysFromPath(ks) : ks; 81 | 82 | var i = -1, length = ks.length; 83 | 84 | // If the obj is null or undefined we have to break as 85 | // a TypeError will result trying to access any property 86 | // Otherwise keep incrementally access the next property in 87 | // ks until complete 88 | while (++i < length && obj != null) { 89 | obj = obj[ks[i]]; 90 | } 91 | return i === length ? obj : void 0; 92 | }, 93 | 94 | // Returns a boolean indicating whether there is a property 95 | // at the path described by the keys given 96 | hasPath: function hasPath (obj, ks) { 97 | ks = typeof ks == "string" ? keysFromPath(ks) : ks; 98 | 99 | var i = -1, length = ks.length; 100 | while (++i < length && _.isObject(obj)) { 101 | if (ks[i] in obj) { 102 | obj = obj[ks[i]]; 103 | } else { 104 | return false; 105 | } 106 | } 107 | return i === length; 108 | }, 109 | 110 | keysFromPath: keysFromPath, 111 | 112 | pickWhen: function(obj, pred) { 113 | var copy = {}; 114 | 115 | _.each(obj, function(value, key) { 116 | if (pred(obj[key])) copy[key] = obj[key]; 117 | }); 118 | 119 | return copy; 120 | }, 121 | 122 | omitWhen: function(obj, pred) { 123 | return _.pickWhen(obj, function(e) { return !pred(e); }); 124 | } 125 | 126 | }); 127 | 128 | }).call(this); 129 | -------------------------------------------------------------------------------- /underscore.util.existential.js: -------------------------------------------------------------------------------- 1 | // Underscore-contrib (underscore.util.existential.js 0.3.0) 2 | // (c) 2013 Michael Fogus, DocumentCloud and Investigative Reporters & Editors 3 | // Underscore-contrib may be freely distributed under the MIT license. 4 | 5 | // Establish the root object, `window` in the browser, or `require` it on the server. 6 | if (typeof exports === 'object') { 7 | _ = module.exports = require('underscore'); 8 | } 9 | 10 | // Mixing in the truthiness 11 | // ------------------------ 12 | 13 | _.mixin({ 14 | exists: function(x) { return x != null; }, 15 | truthy: function(x) { return (x !== false) && _.exists(x); }, 16 | falsey: function(x) { return !_.truthy(x); }, 17 | not: function(b) { return !b; }, 18 | firstExisting: function() { 19 | for (var i = 0; i < arguments.length; i++) { 20 | if (arguments[i] != null) return arguments[i]; 21 | } 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /underscore.util.operators.js: -------------------------------------------------------------------------------- 1 | // Underscore-contrib (underscore.function.arity.js 0.3.0) 2 | // (c) 2013 Michael Fogus, DocumentCloud and Investigative Reporters & Editors 3 | // Underscore-contrib may be freely distributed under the MIT license. 4 | 5 | (function() { 6 | 7 | // Baseline setup 8 | // -------------- 9 | 10 | // Establish the root object, `window` in the browser, or `require` it on the server. 11 | if (typeof exports === 'object') { 12 | _ = module.exports = require('underscore'); 13 | } 14 | 15 | // Setup for variadic operators 16 | // ---------------------------- 17 | 18 | // Turn a binary math operator into a variadic operator 19 | function variadicMath(operator) { 20 | return variaderize(function(numbersToOperateOn) { 21 | return _.reduce(numbersToOperateOn, operator); 22 | }); 23 | } 24 | 25 | // Turn a binary comparator into a variadic comparator 26 | function variadicComparator(comparator) { 27 | return variaderize(function(itemsToCompare) { 28 | var result; 29 | 30 | for (var i = 0; i < itemsToCompare.length - 1; i++) { 31 | result = comparator(itemsToCompare[i], itemsToCompare[i + 1]); 32 | if (result === false) return result; 33 | } 34 | 35 | return result; 36 | }); 37 | } 38 | 39 | // Converts a unary function that operates on an array into one that also works with a variable number of arguments 40 | function variaderize(func) { 41 | return function (args) { 42 | var allArgs = isArrayLike(args) ? args : arguments; 43 | return func(allArgs); 44 | }; 45 | } 46 | 47 | function isArrayLike(obj) { 48 | return obj != null && typeof obj.length === "number"; 49 | } 50 | 51 | // Turn a boolean-returning function into one with the opposite meaning 52 | function invert(fn) { 53 | return function() { 54 | return !fn.apply(this, arguments); 55 | }; 56 | } 57 | 58 | // Basic math operators 59 | function add(x, y) { 60 | return x + y; 61 | } 62 | 63 | function sub(x, y) { 64 | return x - y; 65 | } 66 | 67 | function mul(x, y) { 68 | return x * y; 69 | } 70 | 71 | function div(x, y) { 72 | return x / y; 73 | } 74 | 75 | function mod(x, y) { 76 | return x % y; 77 | } 78 | 79 | function inc(x) { 80 | return ++x; 81 | } 82 | 83 | function dec(x) { 84 | return --x; 85 | } 86 | 87 | function neg(x) { 88 | return -x; 89 | } 90 | 91 | // Bitwise operators 92 | function bitwiseAnd(x, y) { 93 | return x & y; 94 | } 95 | 96 | function bitwiseOr(x, y) { 97 | return x | y; 98 | } 99 | 100 | function bitwiseXor(x, y) { 101 | return x ^ y; 102 | } 103 | 104 | function bitwiseLeft(x, y) { 105 | return x << y; 106 | } 107 | 108 | function bitwiseRight(x, y) { 109 | return x >> y; 110 | } 111 | 112 | function bitwiseZ(x, y) { 113 | return x >>> y; 114 | } 115 | 116 | function bitwiseNot(x) { 117 | return ~x; 118 | } 119 | 120 | // Basic comparators 121 | function eq(x, y) { 122 | return x == y; 123 | } 124 | 125 | function seq(x, y) { 126 | return x === y; 127 | } 128 | 129 | // Not 130 | function not(x) { 131 | return !x; 132 | } 133 | 134 | // Relative comparators 135 | function gt(x, y) { 136 | return x > y; 137 | } 138 | 139 | function lt(x, y) { 140 | return x < y; 141 | } 142 | 143 | function gte(x, y) { 144 | return x >= y; 145 | } 146 | 147 | function lte(x, y) { 148 | return x <= y; 149 | } 150 | 151 | // Mixing in the operator functions 152 | // ----------------------------- 153 | 154 | _.mixin({ 155 | add: variadicMath(add), 156 | sub: variadicMath(sub), 157 | mul: variadicMath(mul), 158 | div: variadicMath(div), 159 | mod: mod, 160 | inc: inc, 161 | dec: dec, 162 | neg: neg, 163 | eq: variadicComparator(eq), 164 | seq: variadicComparator(seq), 165 | neq: invert(variadicComparator(eq)), 166 | sneq: invert(variadicComparator(seq)), 167 | not: not, 168 | gt: variadicComparator(gt), 169 | lt: variadicComparator(lt), 170 | gte: variadicComparator(gte), 171 | lte: variadicComparator(lte), 172 | bitwiseAnd: variadicMath(bitwiseAnd), 173 | bitwiseOr: variadicMath(bitwiseOr), 174 | bitwiseXor: variadicMath(bitwiseXor), 175 | bitwiseNot: bitwiseNot, 176 | bitwiseLeft: variadicMath(bitwiseLeft), 177 | bitwiseRight: variadicMath(bitwiseRight), 178 | bitwiseZ: variadicMath(bitwiseZ) 179 | }); 180 | }).call(this); 181 | -------------------------------------------------------------------------------- /underscore.util.strings.js: -------------------------------------------------------------------------------- 1 | // Underscore-contrib (underscore.util.strings.js 0.3.0) 2 | // (c) 2013 Michael Fogus, DocumentCloud and Investigative Reporters & Editors 3 | // Underscore-contrib may be freely distributed under the MIT license. 4 | 5 | (function() { 6 | 7 | // Baseline setup 8 | // -------------- 9 | 10 | // Establish the root object, `window` in the browser, or `require` it on the server. 11 | if (typeof exports === 'object') { 12 | _ = module.exports = require('underscore'); 13 | } 14 | 15 | // Helpers 16 | // ------- 17 | 18 | // No reason to create regex more than once 19 | var plusRegex = /\+/g; 20 | var bracketRegex = /(?:([^\[]+))|(?:\[(.*?)\])/g; 21 | 22 | var urlDecode = function(s) { 23 | return decodeURIComponent(s.replace(plusRegex, '%20')); 24 | }; 25 | var urlEncode = function(s) { 26 | return encodeURIComponent(s); 27 | }; 28 | 29 | var buildParams = function(prefix, val, top) { 30 | if (_.isUndefined(top)) top = true; 31 | if (_.isArray(val)) { 32 | return _.compact(_.map(val, function(value, key) { 33 | return buildParams(top ? key : prefix + '[]', value, false); 34 | })).join('&'); 35 | } else if (_.isObject(val)) { 36 | return _.compact(_.map(val, function(value, key) { 37 | return buildParams(top ? key : prefix + '[' + key + ']', value, false); 38 | })).join('&'); 39 | } else { 40 | return urlEncode(prefix) + '=' + urlEncode(val); 41 | } 42 | }; 43 | 44 | // Mixing in the string utils 45 | // ---------------------------- 46 | 47 | _.mixin({ 48 | // Explodes a string into an array of chars 49 | explode: function(s) { 50 | return s.split(''); 51 | }, 52 | 53 | // Parses a query string into a hash 54 | fromQuery: function(str) { 55 | var parameters = str.split('&'), 56 | obj = {}, 57 | parameter, 58 | key, 59 | match, 60 | lastKey, 61 | subKey, 62 | depth; 63 | 64 | // Iterate over key/value pairs 65 | _.each(parameters, function(parameter) { 66 | parameter = parameter.split('='); 67 | key = urlDecode(parameter[0]); 68 | lastKey = key; 69 | depth = obj; 70 | 71 | // Reset so we don't have issues when matching the same string 72 | bracketRegex.lastIndex = 0; 73 | 74 | // Attempt to extract nested values 75 | while ((match = bracketRegex.exec(key)) !== null) { 76 | if (!_.isUndefined(match[1])) { 77 | 78 | // If we're at the top nested level, no new object needed 79 | subKey = match[1]; 80 | 81 | } else { 82 | 83 | // If we're at a lower nested level, we need to step down, and make 84 | // sure that there is an object to place the value into 85 | subKey = match[2]; 86 | depth[lastKey] = depth[lastKey] || (subKey ? {} : []); 87 | depth = depth[lastKey]; 88 | } 89 | 90 | // Save the correct key as a hash or an array 91 | lastKey = subKey || _.size(depth); 92 | } 93 | 94 | // Assign value to nested object 95 | depth[lastKey] = urlDecode(parameter[1]); 96 | }); 97 | 98 | return obj; 99 | }, 100 | 101 | // Implodes and array of chars into a string 102 | implode: function(a) { 103 | return a.join(''); 104 | }, 105 | 106 | // Converts a string to camel case 107 | camelCase : function( string ){ 108 | return string.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); }); 109 | }, 110 | 111 | // Converts camel case to dashed (opposite of _.camelCase) 112 | toDash : function( string ){ 113 | string = string.replace(/([A-Z])/g, function($1){return "-"+$1.toLowerCase();}); 114 | // remove first dash 115 | return ( string.charAt( 0 ) == '-' ) ? string.substr(1) : string; 116 | }, 117 | 118 | // Creates a query string from a hash 119 | toQuery: function(obj) { 120 | return buildParams('', obj); 121 | }, 122 | 123 | // Reports whether a string contains a search string. 124 | strContains: function(str, search) { 125 | if (typeof str != 'string') throw new TypeError; 126 | return (str.indexOf(search) != -1); 127 | } 128 | 129 | }); 130 | }).call(this); 131 | -------------------------------------------------------------------------------- /underscore.util.trampolines.js: -------------------------------------------------------------------------------- 1 | // Underscore-contrib (underscore.util.trampolines.js 0.3.0) 2 | // (c) 2013 Michael Fogus, DocumentCloud and Investigative Reporters & Editors 3 | // Underscore-contrib may be freely distributed under the MIT license. 4 | 5 | // Establish the root object, `window` in the browser, or `require` it on the server. 6 | if (typeof exports === 'object') { 7 | _ = module.exports = require('underscore'); 8 | } 9 | 10 | // Mixing in the truthiness 11 | // ------------------------ 12 | 13 | _.mixin({ 14 | done: function(value) { 15 | var ret = _(value); 16 | ret.stopTrampoline = true; 17 | return ret; 18 | }, 19 | 20 | trampoline: function(fun /*, args */) { 21 | var result = fun.apply(fun, _.rest(arguments)); 22 | 23 | while (_.isFunction(result)) { 24 | result = result(); 25 | if ((result instanceof _) && (result.stopTrampoline)) break; 26 | } 27 | 28 | return result.value(); 29 | } 30 | }); 31 | --------------------------------------------------------------------------------