Compare commits

..

121 Commits
0.7.0 ... 0.8.2

Author SHA1 Message Date
John-David Dalton
219138ade6 Bump to v0.8.2.
Former-commit-id: edbb9d995a63ec8fd5a3b1a47ccda7b30c353b35
2012-10-10 00:03:39 -07:00
John-David Dalton
9086d189b9 Update vendors.
Former-commit-id: e88301bddf23f177a2d14f2c729877eaede022e5
2012-10-09 20:11:57 -07:00
Kit Cambridge
d1178defe0 Add a note about npm link. See #88.
Former-commit-id: 11c4a9bce02e5f143b09d0edff80362e2e824b10
2012-10-09 15:22:26 -07:00
John-David Dalton
691a561a95 Merge pull request #87 from danheberden/typo_fix
Fix typo in `_.indexOf` docs.

Former-commit-id: 25305344f272cb875a612e37a93ae252a53e1b4b
2012-10-09 13:56:59 -07:00
Dan Heberden
aaaac93bd0 fix tiny baby super small almost didn't need to worry about fixing typo from isSorted to fromIndex for indexOf method
Former-commit-id: 6f2a7b3e11c4830ec47c174f24badca3ef2e6530
2012-10-09 16:53:56 -04:00
John-David Dalton
fd790566b2 Ensure _.throttle clears the timeout when func is called. [closes #86]
Former-commit-id: 6f3b777aa247f059d97f965c02323d4ee6ab8464
2012-10-09 02:15:06 -07:00
John-David Dalton
db3b429784 Update _.min, _.max, _.shuffle build dependencies.
Former-commit-id: 21c4c99f8ead92b90b46c299fee59098131758b1
2012-10-09 01:43:24 -07:00
John-David Dalton
40ed3278d4 Rebuild minified files and docs.
Former-commit-id: b14af8d9fcb9046c2faf4374fe2e6e83c4f4f835
2012-10-09 01:02:14 -07:00
John-David Dalton
d174b13111 Add object iteration unit tests for _.max, _.min, _.shuffle.
Former-commit-id: 45129100fbbfa14610cacb055c1fa393ae6ce153
2012-10-09 00:59:55 -07:00
John-David Dalton
1708663b32 Move _.max, _.min, _.shuffle back to "Collections" methods for compat but still optimized for arrays.
Former-commit-id: 28cd9298915ad123445400a5d061ac8e9432eb6b
2012-10-09 00:48:02 -07:00
John-David Dalton
6fdce4ad0d Re-optimize _.max, _.min, and _.sortedIndex.
Former-commit-id: 7f449a4fde6777f14a1def0d767f2926bdea07c9
2012-10-09 00:17:10 -07:00
John-David Dalton
fff8d5f07d Cleanup var names in lodash.js and continue to optimize for gzip.
Former-commit-id: 00d76bd7ab8b35d2b45237224662849e42d00bac
2012-10-07 22:26:23 -07:00
John-David Dalton
9c8e1f4706 Reduce _.compact and revert _.random use in _.shuffle.
Former-commit-id: 17d153cb09c262830f1497f93c0f3d9b279c8f8a
2012-10-07 19:32:56 -07:00
John-David Dalton
0a1036c78f Add unit tests to check "Collections" methods return values.
Former-commit-id: 6ac6cd97414035f74a102a51e913099e744d9a93
2012-10-07 18:18:20 -07:00
John-David Dalton
0e881a7972 Simplify _.shuffle and iterator options.
Former-commit-id: 341fd577d65725d47b26172d25b46dec2ac8e67f
2012-10-07 17:43:38 -07:00
John-David Dalton
839e52ba30 Organize docs by category. [closes #84]
Former-commit-id: f4ebda7c32a0ce9c5a86cdb0fd1e689f76557e42
2012-10-07 12:21:01 -07:00
John-David Dalton
8f7d5dcb4d Reduce lodash file size.
Former-commit-id: c6c309cbbc5f93bffb852726e831ba9f90c332a0
2012-10-06 23:39:41 -07:00
John-David Dalton
1104b28bc1 Update README.md to remove fixed Underscore issue.
Former-commit-id: 16fc177c33c2f2522fd9080c0974091ef8e97850
2012-10-06 23:38:25 -07:00
John-David Dalton
48521cb1e4 Update DocDown and Underscore.
Former-commit-id: 7744ef274a9d8b47975dc9f3e3283bd778bc5403
2012-10-06 22:31:08 -07:00
John-David Dalton
17e113dafb Update _.map to return an empty array when falsey values are passed.
Former-commit-id: 2f091fbeb140cbc0b8f3bd2df7a449a06239be0b
2012-10-06 21:17:50 -07:00
John-David Dalton
c33825a904 Reduce Underscore build and update Underscore version number.
Former-commit-id: fd631cd5525fa287c2af493bfe4a93668678977d
2012-10-06 20:58:21 -07:00
John-David Dalton
b07ef98c8f Update Backbone/Underscore vendor folders.
Former-commit-id: 3317d501fe535d91eefab8a5dcb3a88a791e20ac
2012-10-06 20:56:42 -07:00
John-David Dalton
b3d68893df Reduce deep _.clone array iteration and add _.each dependency.
Former-commit-id: 3cb599d294a693974483b892748e6f60186d0c50
2012-10-06 18:15:30 -07:00
John-David Dalton
1329599987 Simplify how deep _.clone handles booleans.
Former-commit-id: adf1d03677336131da2f62bd2fb6e2900c9889a4
2012-10-06 17:32:18 -07:00
John-David Dalton
2adf3f364c Add _.isPlainObject to the list of features in README.md. [ci skip]
Former-commit-id: ec81cdc9e7fef775871cc1c4a497e4d17e7716aa
2012-10-06 15:45:48 -07:00
John-David Dalton
436ee34a02 Simplify createIterator.
Former-commit-id: 0530a9db49488900843c6312cc0d30b1dc641120
2012-10-06 14:43:34 -07:00
John-David Dalton
4a6e17b214 Reduce lodash builds and cleanup README.md.
Former-commit-id: 3c6bbc236a35687c843a8cb27c29f71ed89d0ab0
2012-10-04 23:35:36 -07:00
John-David Dalton
6e9cbccde6 Bump to v0.8.1.
Former-commit-id: 1b63f03d4a7ca0cdc66e44cd987fddecaf88f9ce
2012-10-04 01:40:17 -07:00
John-David Dalton
a0cb8ec124 Cleanup lodash.js.
Former-commit-id: 7a2443719a96b36ae53b2f7d0fe2a1867d650f02
2012-10-04 00:28:45 -07:00
John-David Dalton
21217dfda3 Reduce createIterator.
Former-commit-id: 8c27ca8e4d1f71b2727dd988bc62194510a850dc
2012-10-04 00:02:43 -07:00
John-David Dalton
25ba18e570 Update vendors.
Former-commit-id: 94bb6b8541c223d3ef6eb8aad5fb5925f2d3be48
2012-10-03 23:21:51 -07:00
John-David Dalton
a210377f35 Add unit tests for passing falsey values to _.initial and _.rest.
Former-commit-id: 9d5d4960c175a3dd90af977b605ce309bc6446d3
2012-10-03 20:12:03 -07:00
John-David Dalton
07b9ca457f Cleanup build.js regexes.
Former-commit-id: 0d23053cd8ae20fa2268ba24b15db72c6cd7a85e
2012-10-03 09:09:40 -07:00
John-David Dalton
5f1372d39c Rebuild minified files and docs.
Former-commit-id: d8d8453fa79ab026be0acd44a1af967bdb0bc4cc
2012-10-03 09:09:20 -07:00
John-David Dalton
01b84c79f0 Revert removing falsey guards.
Former-commit-id: b5eeb5d4a0896eb030f20e7e91e54bf101535abc
2012-10-03 09:08:51 -07:00
John-David Dalton
4017443b1e Allow deep clone if requested via include or plus with the underscore build.
Former-commit-id: e86dba41f7265700330e57346a112b578873b390
2012-10-02 21:39:18 -07:00
John-David Dalton
fd2a17d244 Reduce lodash.underscore.min.js.
Former-commit-id: a5032bc542e1166fab6acfd7313c305dd8236d36
2012-10-02 01:43:49 -07:00
John-David Dalton
126804f7c3 Cleanup _.difference, _.intersecton, _.without.
Former-commit-id: 1ca8edb52a1c403fc2d8a8e1b3fd113ced9ff39e
2012-10-02 01:43:30 -07:00
John-David Dalton
5167bbf59e Fix _.lateBind doc typo.
Former-commit-id: 3284bee699837ab380a3e8fd2853f8bdcf0684b6
2012-10-01 23:33:35 -07:00
John-David Dalton
8bcdfa2793 Add identitydependencies to build dependency map.
Former-commit-id: e466c8547888755b9e3d645d555298b21b5a6849
2012-10-01 08:31:23 -07:00
John-David Dalton
2f9cb6a91e Add more to .jamignore.
Former-commit-id: aaa51092bb28995478e4cf8cf1b5ff249880a99c
2012-09-30 23:29:43 -07:00
John-David Dalton
662be14535 Tweak whitespace in README.md and update QUnit-CLIB.
Former-commit-id: b8fed819580bf7db926b8a4cfb794aa7666c5f58
2012-09-30 23:06:40 -07:00
John-David Dalton
d6d065cd61 Cleanup settings=.. build option usage.
Former-commit-id: 33506d9cfc2101cba8d160169c5d27861f8c7064
2012-09-30 22:52:34 -07:00
John-David Dalton
ac7f045708 Correct bullets in README.md.
Former-commit-id: fb75befff853071b7ad5dde3f3a575c707930cc4
2012-09-30 22:47:29 -07:00
John-David Dalton
ffe02ad7bf Reduce the size of lodash.underscore.min.js.
Former-commit-id: fd7d512e104c6325a38a7d0e09015235ca69b1da
2012-09-30 22:36:57 -07:00
John-David Dalton
db1a87d10c Bump to v0.8.0.
Former-commit-id: b5bed986eed052cab3f927a928d92d58044a4798
2012-09-30 21:49:12 -07:00
John-David Dalton
4bae41ffd8 Use a better strict mode detect for tests.js so older versions of Node.js pass.
Former-commit-id: fd9f6ea35c71a8183ce0dcf4a6ec6e6afe13c39e
2012-09-30 21:45:50 -07:00
John-David Dalton
be36fb979f Make .jamignore ignore test-build.js and test/template/.
Former-commit-id: cc6e5e744e81cfb92ae46a4991a39b5c925a0727
2012-09-30 21:39:00 -07:00
John-David Dalton
0f66d763e0 No longer create/use the dist/ folder during the build process.
Former-commit-id: d8d298d5ce21032542d21c4d4fbc7e0112f6ad65
2012-09-30 15:05:17 -07:00
John-David Dalton
06f4743f51 Add lodash.underscore.min.js.
Former-commit-id: 15592a33a6f6979a1d60632a6ade3c341f13d0e7
2012-09-30 02:49:48 -07:00
John-David Dalton
9cc11b8774 Cleanup build files.
Former-commit-id: d19939a34688a8a63979f84eb1a5c5f9c926897b
2012-09-30 01:55:26 -07:00
John-David Dalton
de821e55ae Add template and settings build options unit tests and tweak _.template docs.
Former-commit-id: c814799c82e5a1dde60e5eda4dda5cb192d437f9
2012-09-30 01:03:43 -07:00
John-David Dalton
463b5c6e49 Tweak build option internals and add test template files.
Former-commit-id: ed5ec6ed7886a066c8c727de19dc0fe6548a276d
2012-09-30 00:57:09 -07:00
John-David Dalton
65ab5fdb34 Ensured failed tests trigger travis fails.
Former-commit-id: d513b5b1bae77ab1f4aa9e98ec3622e143048def
2012-09-29 19:54:39 -07:00
John-David Dalton
d2f7a035b3 Add test-build.js to npm test.
Former-commit-id: c915ba8401c1c1b11aa69d155cebe2a0a81eb2d1
2012-09-29 17:20:51 -07:00
John-David Dalton
bc0b924283 Reorganize tests.
Former-commit-id: 5293cdc1206af20824e8aec86b892afd4badf639
2012-09-29 16:42:13 -07:00
John-David Dalton
56ad6af5b2 Add travis integration.
Former-commit-id: 293478e5175ff94dab92bc340034d8d83e3e4773
2012-09-29 16:05:17 -07:00
John-David Dalton
c50bb3a5a9 Remove component.json and add index.js.
Former-commit-id: 16718a8ee6c4b6c834fe96feb58404311b82e3a0
2012-09-29 15:31:47 -07:00
John-David Dalton
d993a62263 Use invert to assign htmlUnescapes.
Former-commit-id: 20f9f29d622643e2f59bbc4a57fd2456c09ef49e
2012-09-29 14:36:23 -07:00
John-David Dalton
82bc52b909 Reduce underscore build size.
Former-commit-id: 207d4ab49063483245dc951d4646413d6d4a1903
2012-09-29 12:21:34 -07:00
John-David Dalton
f31598f916 Update README.md and rebuild minified file.
Former-commit-id: 0b37eebda0d1a82d0bb62cf2ba6e5b190e176547
2012-09-29 03:48:15 -07:00
John-David Dalton
6a9efd8ac6 Ensure build tests pass.
Former-commit-id: 9b91f0d884fe96dce1df34a6c0b659619276b83e
2012-09-29 03:41:00 -07:00
John-David Dalton
a9dddb6066 Update vendors.
Former-commit-id: 31e8de8842ed9ea020f54ca06cdb87b1478e3b08
2012-09-29 03:14:03 -07:00
John-David Dalton
40cf5c99ef Update reduce repeated code.
Former-commit-id: 3412cde47a136dab5c241c67d1c29f2e676c38d1
2012-09-29 03:13:45 -07:00
John-David Dalton
42f58cbbb3 Fix perf.js.
Former-commit-id: 4eef40ddbcb851aca3a87813a17dc329f9ecb071
2012-09-29 00:17:27 -07:00
John-David Dalton
fd9c780015 Reduce file size further.
Former-commit-id: 009540db12d86f0fb79ccd493b61c4fa2cdd9b1f
2012-09-28 07:58:16 -07:00
John-David Dalton
383b92b207 Update build.js, test-build.js and rebuild docs.
Former-commit-id: 385c6b4cb127ad8089622416758021556e413a0a
2012-09-28 02:14:59 -07:00
John-David Dalton
7036ed5e2f Remove falsey checks and reduce file size.
Former-commit-id: 5263a0beaffe2a987eb65fd3631ea4aff8d9f000
2012-09-28 00:46:57 -07:00
John-David Dalton
30666aa111 Update vendor.
Former-commit-id: 9f433cc31e3c70dddba332346c7d053539f54ab5
2012-09-27 20:53:49 -07:00
John-David Dalton
9614d68baa Cleanup test-build.js.
Former-commit-id: d6588b8cc7cebe0ce44392269cdda1ebd851f1ae
2012-09-26 23:08:19 -07:00
John-David Dalton
c25fb4c743 Merge branch 'master' of github.com:bestiejs/lodash
Former-commit-id: a8dbad27cb89e403dbdafcc7cc69a397ae1e5bbd
2012-09-26 22:31:18 -07:00
John-David Dalton
09d5222b1f Allow _.sortedIndex to accept a string value.
Former-commit-id: 7ac17a6bb620ad16ecce17718a8110d422d49118
2012-09-26 22:30:15 -07:00
John-David Dalton
426ca78bf7 Update vendors.
Former-commit-id: 48e14b4b41c9b26382b09294127e552a794e49be
2012-09-26 20:42:52 -07:00
Kit Cambridge
a77a0945fe Add tests for the -d and -m options.
Former-commit-id: def5bcf323aaed96c037fea3e15c4a5c8c72a977
2012-09-26 09:26:40 -06:00
Kit Cambridge
5311a0f903 Allow the -d option to be used independently.
Former-commit-id: cb838b158edb8360d6d7c98ee18f2a7fbb4e9bb4
2012-09-26 08:59:33 -06:00
John-David Dalton
d421656be8 Cleanup build exports options.
Former-commit-id: 75a5f57c0c9f71067cf6d55006f59fa0296a82e2
2012-09-25 23:06:30 -07:00
John-David Dalton
04459eaa50 Remove internal skipArgsCheck from _.isPlainObject and add unit tests.
Former-commit-id: 213c1e95f61368eb8912850248a97f44664384d8
2012-09-25 09:05:09 -07:00
John-David Dalton
3beda8eea0 Add _ references to precompiled templates.
Former-commit-id: 4a6f38ec03790d647de4923262bba8d73378ce14
2012-09-25 02:10:35 -07:00
Mathias Bynens
5e894be06b Fix typo
Former-commit-id: b9fa32da8453a1a928b16bc712a9f2ec53722341
2012-09-24 15:22:55 +03:00
John-David Dalton
bc8f93b596 Remove internal params from built docs.
Former-commit-id: 8b0be9d888ef88dae74554101463f4fa8d268dbc
2012-09-24 01:15:46 -07:00
John-David Dalton
2fb93bac99 Use a different private indicator for _.merge now that isPlainObject is exposed.
Former-commit-id: 5c75de935eb3e5e9e035bd6520398c7fbd811ea6
2012-09-24 01:06:58 -07:00
John-David Dalton
d0c94c1aef Cleanup template build option.
Former-commit-id: 38b94dad822dd9030a6a71f66e65ff7aec0726cc
2012-09-24 00:03:41 -07:00
John-David Dalton
77242bfb95 Expose _.isPlainObject.
Former-commit-id: 884bc87df7773ef3cb5e52725d5a37f07812385a
2012-09-23 21:30:14 -07:00
John-David Dalton
5d2d86bffc Initial lodash template=… build support.
Former-commit-id: 9d13021463380556a997cb53f5ae89eb22a7b98b
2012-09-23 21:29:20 -07:00
John-David Dalton
8492c13e72 Remove DRY modifications for smaller gzip size.
Former-commit-id: 69391d792d76c6592e7d48aec44165a8db388f81
2012-09-22 20:50:59 -07:00
John-David Dalton
e4eb5dadda Modify "underscore" build methods in prep for Underscore.next.
Former-commit-id: 22503a046256915aa6667e32f6efb992c6ddbce9
2012-09-22 20:03:43 -07:00
John-David Dalton
8532dc4b75 Refactor reduceRight and modify a _.difference benchmark.
Former-commit-id: b70272ac5316fe1bee52b9611a1a5ea4d761dd3c
2012-09-22 16:11:23 -07:00
John-David Dalton
1ca26ce676 Ensure methods accepting a thisArg argument allow null values.
Former-commit-id: 368b943687291f0d7ed02304284ac076ef86e02b
2012-09-22 15:39:37 -07:00
John-David Dalton
d8e3e823a7 Removed Underscore's fixed issues from README.md and rebuild docs.
Former-commit-id: 08db54926a791ab32b7e003143ae0f70a251068e
2012-09-20 21:13:36 -07:00
John-David Dalton
473dd7660b Reduce _.reduceRight and update vendors.
Former-commit-id: f7250ccb4b8f15052c1f1420947c2ac68963a92c
2012-09-20 21:06:32 -07:00
John-David Dalton
5afeed56ef Cleanup _.difference and _.union.
Former-commit-id: 1fcaab3989caaaacd9fe73de071d8f360f52b715
2012-09-19 22:15:15 -07:00
John-David Dalton
b91b04f652 Update vendors and add _.reduce, _.reduceRight, and _.where benchmarks.
Former-commit-id: c1b4bc7f8aaf08c429ae918f5d528401f1a66255
2012-09-19 21:40:39 -07:00
John-David Dalton
25de44a23d Cleanup _.template.
Former-commit-id: dc3fa2d02a9a4a2d4034136d2ce7f03d0b67224a
2012-09-19 01:06:45 -07:00
John-David Dalton
00cfb95971 Update vendors.
Former-commit-id: fddaef2be532e30f197f8bdff70dc6ec9bfa3cfc
2012-09-18 23:42:52 -07:00
John-David Dalton
f4ba0e1191 Simplify _.template and remove superfluous caching.
Former-commit-id: f9f18a63a77376471e69c95d5046dfe0146b9887
2012-09-18 23:42:26 -07:00
John-David Dalton
6499643769 Make _.random return 0 or 1 when no arguments are passed.
Former-commit-id: 7d20f5240d534f0091757f613c664b08e0d45759
2012-09-18 20:29:36 -07:00
John-David Dalton
f85287a444 Add "category" options unit tests to test-build.js.
Former-commit-id: 0499317babeb422e88700edd0f1e46c1fa6196fd
2012-09-18 01:22:10 -07:00
John-David Dalton
483bc9ad87 Add unit test to confirm correctly parsing delimiters.
Former-commit-id: 71aa7a240d2becb8082e36f9aa4874eb0aed09d3
2012-09-17 23:49:03 -07:00
John-David Dalton
93b89a93c1 Make _.times return an array of the results of each callback execution. [Closes #73]
Former-commit-id: 808af6b9eb07bf1fada8221ca659558d82e6eb57
2012-09-17 21:48:26 -07:00
John-David Dalton
10064c046c Allow build "plus", "minus", and "include" to accept case-sensitive category names.
Former-commit-id: f74d4cda73195854b3471ddce0afaab099dcfe77
2012-09-16 18:11:53 -07:00
John-David Dalton
a5aecb9d0e Add minus and plus commands to build.js.
Former-commit-id: fcdd2e829de5a6a29ebc342e694b7986d9a5eade
2012-09-16 13:22:26 -07:00
John-David Dalton
de1e889f1d Make lodash underscore build smaller and rebuild docs.
Former-commit-id: 19405bc4e490588d7a55379974d2d7d81d1cf5f7
2012-09-15 19:24:26 -07:00
John-David Dalton
16f89b40c3 Remove issues resolved by Underscore from README.md.
Former-commit-id: 27510827a1e60dd4cf81f5306991e3f63e8cedb5
2012-09-15 08:47:22 -07:00
John-David Dalton
0e387d2cda Remove custom toArray checks in _.toArray and simplify array-like object checks in "Collections" methods.
Former-commit-id: 6b7678de16907f44c1b079a22a6a2e091a638d4b
2012-09-15 08:46:49 -07:00
John-David Dalton
837c15e4f9 Update vendors.
Former-commit-id: 1e93a793891e5f4e2c0d3927f3f38b25bd182263
2012-09-15 08:21:44 -07:00
John-David Dalton
ae7e353169 DRY more code in the "underscore" build.
Former-commit-id: c1dbc3daccb1efba371c38f20b9a4ff96a7171a4
2012-09-14 01:09:02 -07:00
John-David Dalton
6c1bbd2344 Tweak iteratorTemplate formatting and remove more code from "underscore" builds.
Former-commit-id: cbaa8e41a9a60b5828d2c0da7188d483702c55e1
2012-09-13 23:51:52 -07:00
John-David Dalton
27247e8e56 Remove freeExports var when not used via exports build option.
Former-commit-id: 66950af3a18b35412fbe1092e19c5d7ef0c9a029
2012-09-13 19:28:56 -07:00
John-David Dalton
08249d78ea Add RequireJS "shim" test and a build test to ensure the AMD snippet is maintained for r.js.
Former-commit-id: 708c07877cfca0022d6d56c16c36d8bae79e4796
2012-09-13 19:11:48 -07:00
John-David Dalton
827091e522 Remove "lazy" bind from README.md until next version bump.
Former-commit-id: f132182f32670aa7df7ac418f58db0a1fb0f8547
2012-09-13 02:10:23 -07:00
John-David Dalton
13d901bf8d Make the IIFE Closure Compiler regexp in post process more restrictive to work better with the "iife" build option.
Former-commit-id: 999602451e50850eb82680b9c377d97605be8af4
2012-09-13 01:40:28 -07:00
John-David Dalton
e69bdabc99 Rename makeBound to createBound for naming consistency.
Former-commit-id: ec6845badba06231af0b1341b1f1efedb8adbc88
2012-09-13 00:46:47 -07:00
John-David Dalton
e8d19265e4 Cleanup component.json.
Former-commit-id: 0761bb592e5c643223848e2cd7fca0efc5b9c725
2012-09-13 00:10:16 -07:00
John-David Dalton
49e3a49dc5 Update docs, minified, build, and vendors.
Former-commit-id: 8b1425b8c4a5238a42185dfa974bb3b2468eebea
2012-09-13 00:08:52 -07:00
John-David Dalton
82a7c01898 Simplify "underscore" build of _.clone.
Former-commit-id: 32975bb5966f1ded4f007eb76dcf2d4677478e7d
2012-09-13 00:04:53 -07:00
John-David Dalton
c0d7dbf639 Reduce code around _.bind and _.partial, and add _.lateBind.
Former-commit-id: 4c962d066ecfa54882cee2216a7310ab34b3b5a3
2012-09-13 00:04:00 -07:00
John-David Dalton
569caa0bf2 Remove custom isEqual checks from _.isEqual and custom clone checks from _.clone and simply _.clone, _.isEqual, and _.merge.
Former-commit-id: 45e90ab1494e46e281265f660c87e27f59581308
2012-09-12 22:00:23 -07:00
John-David Dalton
f88ea1ee7d Re-order test-build unit tests and make the iife test a bit more thorough.
Former-commit-id: f798639cfa58241b052d16b9ca6fbf4537482349
2012-09-12 08:40:01 -07:00
John-David Dalton
d5a8fa0b97 Update underscore unit tests.
Former-commit-id: ae00a864c7cb3bff9970289917df681ad5e295d9
2012-09-12 08:39:18 -07:00
John-David Dalton
3f8f96edea Add "mobile" build unit test.
Former-commit-id: f067b42618abe6a9f747fea000522de6a9117b3c
2012-09-11 22:30:23 -07:00
John-David Dalton
04fb4aff28 Update underscore.
Former-commit-id: 7041883ef258e3dc80d3c3751a5e4beecf0b4767
2012-09-11 20:15:13 -07:00
47 changed files with 6088 additions and 5292 deletions

1
.gitignore vendored
View File

@@ -1,4 +1,3 @@
*.custom.* *.custom.*
.DS_Store .DS_Store
dist/
node_modules/ node_modules/

View File

@@ -1,7 +1,14 @@
*.custom.* *.custom.*
.* .*
dist/ build.js
index.js
build/
doc/*.php doc/*.php
node_modules/ node_modules/
perf/*.sh perf/*.sh
test/*.sh test/*.sh
test/test-build.js
test/template/
vendor/closure-compiler
vendor/docdown
vendor/uglifyjs

View File

@@ -1,6 +1,5 @@
*.custom.* *.custom.*
.* .*
dist/
doc/*.php doc/*.php
node_modules/ node_modules/
perf/*.html perf/*.html

4
.travis.yml Normal file
View File

@@ -0,0 +1,4 @@
language: node_js
node_js:
- 0.6
- 0.8

165
README.md
View File

@@ -1,15 +1,17 @@
# Lo-Dash <sup>v0.7.0</sup> # Lo-Dash <sup>v0.8.2</sup>
[![build status](https://secure.travis-ci.org/bestiejs/lodash.png)](http://travis-ci.org/bestiejs/lodash)
A drop-in replacement<sup>[*](https://github.com/bestiejs/lodash/wiki/Drop-in-Disclaimer)</sup> for Underscore.js, from the devs behind [jsPerf.com](http://jsperf.com), delivering [performance](http://lodash.com/benchmarks), [bug fixes](https://github.com/bestiejs/lodash#resolved-underscorejs-issues-20), and [additional features](https://github.com/bestiejs/lodash#features). A drop-in replacement<sup>[*](https://github.com/bestiejs/lodash/wiki/Drop-in-Disclaimer)</sup> for Underscore.js, from the devs behind [jsPerf.com](http://jsperf.com), delivering [performance](http://lodash.com/benchmarks), [bug fixes](https://github.com/bestiejs/lodash#resolved-underscorejs-issues), and [additional features](http://lodash.com/#features).
Lo-Dashs performance is gained by avoiding slower native methods, instead opting for simplified non-ES5 compliant methods optimized for common usage, and by leveraging function compilation to reduce the number of overall function calls. Lo-Dashs performance is gained by avoiding slower native methods, instead opting for simplified non-ES5 compliant methods optimized for common usage, and by leveraging function compilation to reduce the number of overall function calls.
## Download ## Download
* [Development source](https://raw.github.com/bestiejs/lodash/v0.7.0/lodash.js) * [Development build](https://raw.github.com/bestiejs/lodash/v0.8.2/lodash.js)
* [Production source](https://raw.github.com/bestiejs/lodash/v0.7.0/lodash.min.js) * [Production build](https://raw.github.com/bestiejs/lodash/v0.8.2/lodash.min.js)
* CDN copies of ≤ [v0.7.0](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/0.7.0/lodash.min.js) are available on [cdnjs](http://cdnjs.com/) thanks to [CloudFlare](http://www.cloudflare.com/) * [Underscore build](https://raw.github.com/bestiejs/lodash/v0.8.2/lodash.underscore.min.js) tailored for projects already using Underscore
* For optimal performance, [create a custom build](https://github.com/bestiejs/lodash#custom-builds) with only the features you need * CDN copies of ≤ [v0.8.2](http://cdnjs.cloudflare.com/ajax/libs/lodash.js/0.8.2/lodash.min.js) are available on [cdnjs](http://cdnjs.com/) thanks to [CloudFlare](http://www.cloudflare.com/)
* For optimal file size, [create a custom build](https://github.com/bestiejs/lodash#custom-builds) with only the features you need
## Dive in ## Dive in
@@ -32,32 +34,22 @@ For more information check out these screencasts over Lo-Dash:
## Features ## Features
* AMD loader support ([RequireJS](http://requirejs.org/), [curl.js](https://github.com/cujojs/curl), etc.) * AMD loader support ([RequireJS](http://requirejs.org/), [curl.js](https://github.com/cujojs/curl), etc.)
* [_.bind](http://lodash.com/docs#bind) supports *“lazy”* binding
* [_.clone](http://lodash.com/docs#clone) supports *“deep”* cloning * [_.clone](http://lodash.com/docs#clone) supports *“deep”* cloning
* [_.countBy](http://lodash.com/docs#countBy) as a companion function for [_.groupBy](http://lodash.com/docs#groupBy) and [_.sortBy](http://lodash.com/docs#sortBy)
* [_.debounce](http://lodash.com/docs#debounce)ed functions match [_.throttle](http://lodash.com/docs#throttle)ed functions return value behavior
* [_.forEach](http://lodash.com/docs#forEach) is chainable and supports exiting iteration early * [_.forEach](http://lodash.com/docs#forEach) is chainable and supports exiting iteration early
* [_.forIn](http://lodash.com/docs#forIn) for iterating over an objects own and inherited properties * [_.forIn](http://lodash.com/docs#forIn) for iterating over an objects own and inherited properties
* [_.forOwn](http://lodash.com/docs#forOwn) for iterating over an objects own properties * [_.forOwn](http://lodash.com/docs#forOwn) for iterating over an objects own properties
* [_.indexOf](http://lodash.com/docs#indexOf) and [_.lastIndexOf](http://lodash.com/docs#lastIndexOf) accept a `fromIndex` argument * [_.isPlainObject](http://lodash.com/docs#isPlainObject) checks if values are created by the `Object` constructor
* [_.invert](http://lodash.com/docs#invert) to create inverted objects * [_.lateBind](http://lodash.com/docs#lateBind) for late binding
* [_.merge](http://lodash.com/docs#merge) for a *“deep”* [_.extend](http://lodash.com/docs#extend) * [_.merge](http://lodash.com/docs#merge) for a *“deep”* [_.extend](http://lodash.com/docs#extend)
* [_.object](http://lodash.com/docs#object) and [_.pairs](http://lodash.com/docs#pairs) for composing objects
* [_.omit](http://lodash.com/docs#omit) for the inverse functionality of [_.pick](http://lodash.com/docs#pick)
* [_.partial](http://lodash.com/docs#partial) for partial application without `this` binding * [_.partial](http://lodash.com/docs#partial) for partial application without `this` binding
* [_.pick](http://lodash.com/docs#pick) and [_.omit](http://lodash.com/docs#omit) accept `callback` and `thisArg` arguments * [_.pick](http://lodash.com/docs#pick) and [_.omit](http://lodash.com/docs#omit) accept `callback` and `thisArg` arguments
* [_.random](http://lodash.com/docs#random) for generating random numbers within a given range
* [_.sortBy](http://lodash.com/docs#sortBy) performs a [stable](http://en.wikipedia.org/wiki/Sorting_algorithm#Stability) sort
* [_.template](http://lodash.com/docs#template) utilizes [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl) for easier debugging * [_.template](http://lodash.com/docs#template) utilizes [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl) for easier debugging
* [_.unescape](http://lodash.com/docs#unescape) to unescape strings escaped by [_.escape](http://lodash.com/docs#escape)
* [_.where](http://lodash.com/docs#where) for filtering collections by contained properties
* [_.countBy](http://lodash.com/docs#countBy), [_.groupBy](http://lodash.com/docs#groupBy), [_.sortedIndex](http://lodash.com/docs#sortedIndex), and [_.uniq](http://lodash.com/docs#uniq) accept a `thisArg` argument
* [_.contains](http://lodash.com/docs#contains), [_.size](http://lodash.com/docs#size), [_.toArray](http://lodash.com/docs#toArray), * [_.contains](http://lodash.com/docs#contains), [_.size](http://lodash.com/docs#size), [_.toArray](http://lodash.com/docs#toArray),
[and more…](http://lodash.com/docs "_.countBy, _.every, _.filter, _.find, _.forEach, _.groupBy, _.invoke, _.map, _.pluck, _.reduce, _.reduceRight, _.reject, _.some, _.sortBy, _.where") accept strings [and more…](http://lodash.com/docs "_.countBy, _.every, _.filter, _.find, _.forEach, _.groupBy, _.invoke, _.map, _.pluck, _.reduce, _.reduceRight, _.reject, _.some, _.sortBy, _.where") accept strings
## Support ## Support
Lo-Dash has been tested in at least Chrome 5-21, Firefox 1-15, IE 6-9, Opera 9.25-12, Safari 3-6, Node.js 0.4.8-0.8.8, Narwhal 0.3.2, RingoJS 0.8, and Rhino 1.7RC5. Lo-Dash has been tested in at least Chrome 5~22, Firefox 1~16, IE 6-10, Opera 9.25-12, Safari 3-6, Node.js 0.4.8-0.8.11, Narwhal 0.3.2, RingoJS 0.8, and Rhino 1.7RC5.
## Custom builds ## Custom builds
@@ -89,7 +81,7 @@ lodash mobile
lodash strict lodash strict
``` ```
* Underscore builds, with iteration fixes removed and only Underscores API, may be created using the `underscore` modifier argument. * Underscore builds, tailored for projects already using Underscore, may be created using the `underscore` modifier argument.
```bash ```bash
lodash underscore lodash underscore
``` ```
@@ -97,23 +89,17 @@ lodash underscore
Custom builds may be created using the following commands: Custom builds may be created using the following commands:
* Use the `category` argument to pass comma separated categories of methods to include in the build.<br> * Use the `category` argument to pass comma separated categories of methods to include in the build.<br>
Valid categories are *“arrays”*, *“chaining”*, *“collections”*, *“functions”*, *“objects”*, and *“utilities”*. Valid categories (case-insensitive) are *“arrays”*, *“chaining”*, *“collections”*, *“functions”*, *“objects”*, and *“utilities”*.
```bash ```bash
lodash category=collections,functions lodash category=collections,functions
lodash category="collections, functions" lodash category="collections, functions"
```
* Use the `exclude` argument to pass comma separated names of methods to exclude from the build.
```bash
lodash exclude=union,uniq,zip
lodash exclude="union, uniq, zip"
``` ```
* Use the `exports` argument to pass comma separated names of ways to export the `LoDash` function.<br> * Use the `exports` argument to pass comma separated names of ways to export the `LoDash` function.<br>
Valid exports are *“amd”*, *“commonjs”*, *“global”*, *“node”*, and *“none”*. Valid exports are *“amd”*, *“commonjs”*, *“global”*, *“node”*, and *“none”*.
```bash ```bash
lodash exports=amd,commonjs,node lodash exports=amd,commonjs,node
lodash include="amd, commonjs, node" lodash exports="amd, commonjs, node"
``` ```
* Use the `iife` argument to specify code to replace the immediately-invoked function expression that wraps Lo-Dash. * Use the `iife` argument to specify code to replace the immediately-invoked function expression that wraps Lo-Dash.
@@ -121,31 +107,49 @@ lodash include="amd, commonjs, node"
lodash iife="!function(window,undefined){%output%}(this)" lodash iife="!function(window,undefined){%output%}(this)"
``` ```
* Use the `include` argument to pass comma separated names of methods to include in the build. * Use the `include` argument to pass comma separated method/category names to include in the build.
```bash ```bash
lodash include=each,filter,map lodash include=each,filter,map
lodash include="each, filter, map" lodash include="each, filter, map"
``` ```
All arguments, except `exclude` with `include` and `legacy` with `csp`/`mobile`, may be combined. * Use the `minus` argument to pass comma separated method/category names to remove from those included in the build.
```bash ```bash
lodash backbone legacy exports=global category=utilities exclude=first,last lodash underscore minus=result,shuffle
lodash -s underscore mobile strict exports=amd category=functions include=pick,uniq lodash underscore minus="result, shuffle"
``` ```
* Use the `plus` argument to pass comma separated method/category names to add to those included in the build.
```bash
lodash backbone plus=random,template
lodash backbone plus="random, template"
```
* Use the `template` argument to pass the file path pattern used to match template files to precompile
```bash
lodash template="./*.jst"
```
* Use the `settings` argument to pass the template settings used when precompiling templates
```bash
lodash settings="{interpolate:/\\{\\{([\\s\\S]+?)\\}\\}/g}"
```
All arguments, except `legacy` with `csp` or `mobile`, may be combined.<br>
Unless specified by `-o` or `--output`, all files created are saved to the current working directory.
The following options are also supported: The following options are also supported:
* `-c`, `--stdout` Write output to standard output * `-c`, `--stdout`&nbsp;&nbsp;&nbsp;&nbsp; Write output to standard output
* `-h`, `--help` Display help information * `-d`, `--debug`&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Write only the debug output
* `-o`, `--output` Write output to a given path/filename * `-h`, `--help`&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Display help information
* `-s`, `--silent` Skip status updates normally logged to the console * `-m`, `--minify`&nbsp;&nbsp;&nbsp;&nbsp; Write only the minified output
* `-V`, `--version` Output current version of Lo-Dash * `-o`, `--output`&nbsp;&nbsp;&nbsp;&nbsp; Write output to a given path/filename
* `-s`, `--silent`&nbsp;&nbsp;&nbsp;&nbsp; Skip status updates normally logged to the console
* `-V`, `--version`&nbsp;&nbsp; Output current version of Lo-Dash
The `lodash` command-line utility is available when Lo-Dash is installed as a global package (i.e. `npm install -g lodash`). The `lodash` command-line utility is available when Lo-Dash is installed as a global package (i.e. `npm install -g lodash`).
Custom builds are saved to `lodash.custom.js` and `lodash.custom.min.js`.
## Installation and usage ## Installation and usage
In browsers: In browsers:
@@ -158,7 +162,9 @@ Using [npm](http://npmjs.org/):
```bash ```bash
npm install lodash npm install lodash
npm install -g lodash npm install -g lodash
npm link lodash
``` ```
In [Node.js](http://nodejs.org/) and [RingoJS v0.8.0+](http://ringojs.org/): In [Node.js](http://nodejs.org/) and [RingoJS v0.8.0+](http://ringojs.org/):
@@ -167,6 +173,8 @@ In [Node.js](http://nodejs.org/) and [RingoJS v0.8.0+](http://ringojs.org/):
var _ = require('lodash'); var _ = require('lodash');
``` ```
**Note:** If Lo-Dash is installed globally, [run `npm link lodash`](http://blog.nodejs.org/2011/03/23/npm-1-0-global-vs-local-installation/) in your projects root directory before attempting to `require` it.
In [RingoJS v0.7.0-](http://ringojs.org/): In [RingoJS v0.7.0-](http://ringojs.org/):
```js ```js
@@ -192,43 +200,30 @@ require({
}); });
``` ```
## Resolved Underscore.js issues <sup>(20+)</sup> ## Resolved Underscore.js issues
* Allow iteration of objects with a `length` property [[#148](https://github.com/documentcloud/underscore/issues/148), [#154](https://github.com/documentcloud/underscore/issues/154), [#252](https://github.com/documentcloud/underscore/issues/252), [#448](https://github.com/documentcloud/underscore/issues/448), [#659](https://github.com/documentcloud/underscore/issues/659), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L543-549)] * Add AMD loader support [[#431](https://github.com/documentcloud/underscore/pull/431), [test](https://github.com/bestiejs/lodash/blob/v0.8.2/test/test.js#L118-140)]
* Ensure array-like objects with invalid `length` properties are treated like regular objects [[#741](https://github.com/documentcloud/underscore/issues/741), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L491-501)] * Allow iteration of objects with a `length` property [[#799](https://github.com/documentcloud/underscore/pull/799), [test](https://github.com/bestiejs/lodash/blob/v0.8.2/test/test.js#L510-516)]
* Ensure *Arrays”*, *“Collections”*, and *“Objects”* methods dont error when passed falsey arguments [[#650](https://github.com/documentcloud/underscore/pull/650), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L1719-1754)] * Ensure *Collections”* methods allow string `collection` arguments [[#247](https://github.com/documentcloud/underscore/issues/247), [#276](https://github.com/documentcloud/underscore/issues/276), [#561](https://github.com/documentcloud/underscore/pull/561), [test](https://github.com/bestiejs/lodash/blob/v0.8.2/test/test.js#L470-487)]
* Ensure *“Collections”* methods allow string `collection` arguments [[#247](https://github.com/documentcloud/underscore/issues/247), [#276](https://github.com/documentcloud/underscore/issues/276), [#561](https://github.com/documentcloud/underscore/pull/561), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L503-520)] * Fix cross-browser object iteration bugs [[#60](https://github.com/documentcloud/underscore/issues/60), [#376](https://github.com/documentcloud/underscore/issues/376), [test](https://github.com/bestiejs/lodash/blob/v0.8.2/test/test.js#L523-547)]
* Fix cross-browser object iteration bugs [[#60](https://github.com/documentcloud/underscore/issues/60), [#376](https://github.com/documentcloud/underscore/issues/376), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L554-579)] * Methods should work on pages with incorrectly shimmed native methods [[#7](https://github.com/documentcloud/underscore/issues/7), [#742](https://github.com/documentcloud/underscore/issues/742), [test](https://github.com/bestiejs/lodash/blob/v0.8.2/test/test.js#L142-148)]
* Methods should work on pages with incorrectly shimmed native methods [[#7](https://github.com/documentcloud/underscore/issues/7), [#742](https://github.com/documentcloud/underscore/issues/742), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L134-140)] * `_.clone` should allow `deep` cloning [[#595](https://github.com/documentcloud/underscore/pull/595), [test](https://github.com/bestiejs/lodash/blob/v0.8.2/test/test.js#L214-225)]
* Register as an AMD module, but still export to global [[#431](https://github.com/documentcloud/underscore/pull/431), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L118-132)] * `_.contains` should work with strings [[#667](https://github.com/documentcloud/underscore/pull/667), [test](https://github.com/bestiejs/lodash/blob/v0.8.2/test/test.js#L267-276)]
* `_.clone` should allow `deep` cloning [[#595](https://github.com/documentcloud/underscore/pull/595), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L223-234)] * `_.extend` should recursively extend objects [[#379](https://github.com/documentcloud/underscore/pull/379), [#718](https://github.com/documentcloud/underscore/issues/718), [test](https://github.com/bestiejs/lodash/blob/v0.8.2/test/test.js#L968-990)]
* `_.contains` should work with strings [[#667](https://github.com/documentcloud/underscore/pull/667), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L289-298)] * `_.forEach` should be chainable [[#142](https://github.com/documentcloud/underscore/issues/142), [test](https://github.com/bestiejs/lodash/blob/v0.8.2/test/test.js#L465-468)]
* `_.countBy` and `_.groupBy` should only add values to own, not inherited, properties [[#736](https://github.com/documentcloud/underscore/issues/736), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L306-313)] * `_.forEach` should allow exiting iteration early [[#211](https://github.com/documentcloud/underscore/issues/211), [test](https://github.com/bestiejs/lodash/blob/v0.8.2/test/test.js#L553-571)]
* `_.extend` should recursively extend objects [[#379](https://github.com/documentcloud/underscore/pull/379), [#718](https://github.com/documentcloud/underscore/issues/718), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L946-968)] * `_.isEmpty` should support jQuery/MooTools DOM query collections [[#690](https://github.com/documentcloud/underscore/pull/690), [test](https://github.com/bestiejs/lodash/blob/v0.8.2/test/test.js#L712-717)]
* `_.forEach` should be chainable [[#142](https://github.com/documentcloud/underscore/issues/142), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L486-489)] * `_.isObject` should avoid V8 bug [#2291](http://code.google.com/p/v8/issues/detail?id=2291) [[#605](https://github.com/documentcloud/underscore/issues/605), [test](https://github.com/bestiejs/lodash/blob/v0.8.2/test/test.js#L772-784)]
* `_.forEach` should allow exiting iteration early [[#211](https://github.com/documentcloud/underscore/issues/211), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L581-600)] * `_.keys` should work with `arguments` objects cross-browser [[#396](https://github.com/documentcloud/underscore/issues/396), [test](https://github.com/bestiejs/lodash/blob/v0.8.2/test/test.js#L865-867)]
* `_.isElement` should use strict equality for its duck type check [[#734](https://github.com/documentcloud/underscore/issues/734), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L696-705)] * `_.range` should coerce arguments to numbers [[#634](https://github.com/documentcloud/underscore/issues/634), [#683](https://github.com/documentcloud/underscore/issues/683), [test](https://github.com/bestiejs/lodash/blob/v0.8.2/test/test.js#L1243-1246)]
* `_.isEmpty` should support jQuery/MooTools DOM query collections [[#690](https://github.com/documentcloud/underscore/pull/690), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L732-737)]
* `_.isEqual` should return `true` for like-objects from different documents [[#733](https://github.com/documentcloud/underscore/issues/733), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L784-795)]
* `_.isEqual` should use custom `isEqual` methods before checking strict equality [[#748](https://github.com/documentcloud/underscore/issues/748), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L758-761)]
* `_.isObject` should avoid V8 bug [#2291](http://code.google.com/p/v8/issues/detail?id=2291) [[#605](https://github.com/documentcloud/underscore/issues/605), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L803-815)]
* `_.isNaN(new Number(NaN))` should return `true` [[#749](https://github.com/documentcloud/underscore/issues/749), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L823-825)]
* `_.keys` should work with `arguments` objects cross-browser [[#396](https://github.com/documentcloud/underscore/issues/396), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L879-881)]
* `_.range` should coerce arguments to numbers [[#634](https://github.com/documentcloud/underscore/issues/634), [#683](https://github.com/documentcloud/underscore/issues/683), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L1222-1225)]
* `_.reduceRight` should pass correct callback arguments when iterating objects [[test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L1257-1271)]
* `_.sortedIndex` should support arrays with high `length` values [[#735](https://github.com/documentcloud/underscore/issues/735), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L1404-1413)]
* `_.throttle` should work when called in a loop [[#502](https://github.com/documentcloud/underscore/issues/502), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L1534-1544)]
* `_.toArray` uses custom `toArray` methods of arrays and strings [[#747](https://github.com/documentcloud/underscore/issues/747), [test](https://github.com/bestiejs/lodash/blob/v0.7.0/test/test.js#L1571-1579)]
## Optimized methods <sup>(50+)</sup> ## Optimized methods <sup>(50+)</sup>
* `_.bind` * `_.bind`
* `_.bindAll` * `_.bindAll`
* `_.clone`
* `_.compact` * `_.compact`
* `_.contains`, `_.include` * `_.contains`, `_.include`
* `_.defaults` * `_.defaults`
* `_.defer`
* `_.difference` * `_.difference`
* `_.each` * `_.each`
* `_.every`, `_.all` * `_.every`, `_.all`
@@ -245,7 +240,6 @@ require({
* `_.invoke` * `_.invoke`
* `_.isArguments` * `_.isArguments`
* `_.isDate` * `_.isDate`
* `_.isEmpty`
* `_.isFinite` * `_.isFinite`
* `_.isFunction` * `_.isFunction`
* `_.isObject` * `_.isObject`
@@ -272,11 +266,11 @@ require({
* `_.sortedIndex` * `_.sortedIndex`
* `_.template` * `_.template`
* `_.throttle` * `_.throttle`
* `_.times`
* `_.toArray` * `_.toArray`
* `_.union` * `_.union`
* `_.uniq`, `_.unique` * `_.uniq`, `_.unique`
* `_.values` * `_.values`
* `_.where`
* `_.without` * `_.without`
* `_.wrap` * `_.wrap`
* `_.zip` * `_.zip`
@@ -284,30 +278,13 @@ require({
## Release Notes ## Release Notes
### <sup>v0.7.0</sup> ### <sup>v0.8.2</sup>
#### Compatibility Warnings #### * Ensured `_.map` returns an array when passed a falsey collection
* Ensured `_.throttle` clears its timeout when `func` is called
* Renamed `_.zipObject` to `_.object` * Made `_.max`, `_.min`, `_.shuffle` support iterating objects
* Replaced `_.drop` with `_.omit` * Reduced `createIterator`, `_.clone`, and `_.compact`
* Made `_.drop` alias `_.rest` * Re-optimized `_.max`, `_.min`, and `_.sortedIndex`
#### Changes ####
* Added [_.invert](http://lodash.com/docs#invert), [_.pairs](http://lodash.com/docs#pairs), and [_.random](http://lodash.com/docs#random)
* Added `_.result` to the `backbone` build
* Added `exports`, `iife`, `-c`/`--stdout`, `-o`/`--output`, and `-s`/`--silent` build options
* Ensured `isPlainObject` works with objects from other documements
* Ensured `_.isEqual` compares values with circular references correctly
* Ensured `_.merge` work with four or more arguments
* Ensured `_.sortBy` performs a stable sort for `undefined` values
* Ensured `_.template` works with "interpolate" delimiters containing ternary operators
* Ensured the production build works in Node.js
* Ensured template delimiters are tokenized correctly
* Made pseudo private properties `_chain` and `_wrapped` double-underscored to avoid conflicts
* Made `minify.js` support `underscore.js`
* Reduced the size of `mobile` and `underscore` builds
* Simplified `_.isEqual` and `_.size`
The full changelog is available [here](https://github.com/bestiejs/lodash/wiki/Changelog). The full changelog is available [here](https://github.com/bestiejs/lodash/wiki/Changelog).

1295
build.js

File diff suppressed because it is too large Load Diff

View File

@@ -9,18 +9,15 @@
spawn = require('child_process').spawn; spawn = require('child_process').spawn;
/** The directory that is the base of the repository */ /** The directory that is the base of the repository */
var basePath = path.join(__dirname, '../'); var basePath = fs.realpathSync(path.join(__dirname, '..'));
/** The directory where the Closure Compiler is located */ /** The directory where the Closure Compiler is located */
var closurePath = path.join(basePath, 'vendor', 'closure-compiler', 'compiler.jar'); var closurePath = path.join(basePath, 'vendor', 'closure-compiler', 'compiler.jar');
/** The distribution directory */
var distPath = path.join(basePath, 'dist');
/** Load other modules */ /** Load other modules */
var preprocess = require(path.join(__dirname, 'pre-compile')), var preprocess = require('./pre-compile'),
postprocess = require(path.join(__dirname, 'post-compile')), postprocess = require('./post-compile'),
uglifyJS = require(path.join(basePath, 'vendor', 'uglifyjs', 'uglify-js')); uglifyJS = require('../vendor/uglifyjs/uglify-js');
/** Closure Compiler command-line options */ /** Closure Compiler command-line options */
var closureOptions = [ var closureOptions = [
@@ -37,31 +34,38 @@
* The exposed `minify` function minifies a given Lo-Dash `source` and invokes * The exposed `minify` function minifies a given Lo-Dash `source` and invokes
* the `onComplete` callback when finished. * the `onComplete` callback when finished.
* *
* @param {Array|String} source The array of command-line arguments or the * @param {Array|String} [source=''] The source to minify or array of commands.
* source to minify. * @param {Object} [options={}] The options object.
* @param {Object} options The options object containing `onComplete`,
* `silent`, and `workingName`.
*/ */
function minify(source, options) { function minify(source, options) {
source || (source = '');
options || (options = {}); options || (options = {});
// juggle arguments
if (Array.isArray(source)) { if (Array.isArray(source)) {
// convert the command-line arguments to an options object // convert commands to an options object
options = source; options = source;
var filePath = options[options.length - 1], var filePath = options[options.length - 1],
dirPath = path.dirname(filePath), isSilent = options.indexOf('-s') > -1 || options.indexOf('--silent') > -1,
workingName = path.basename(filePath, '.js') + '.min', isTemplate = options.indexOf('-t') > -1 || options.indexOf('--template') > -1,
outputPath = path.join(dirPath, workingName + '.js'), outputPath = path.join(path.dirname(filePath), path.basename(filePath, '.js') + '.min.js');
isSilent = options.indexOf('-s') > -1 || options.indexOf('--silent') > -1;
outputPath = options.reduce(function(result, value, index) {
if (/-o|--output/.test(value)) {
result = options[index + 1];
result = path.join(fs.realpathSync(path.dirname(result)), path.basename(result));
}
return result;
}, outputPath);
options = {
'isSilent': isSilent,
'isTemplate': isTemplate,
'outputPath': outputPath
};
source = fs.readFileSync(filePath, 'utf8'); source = fs.readFileSync(filePath, 'utf8');
options = {
'silent': isSilent,
'workingName': workingName,
'onComplete': function(source) {
fs.writeFileSync(outputPath, source, 'utf8');
}
};
} }
new Minify(source, options); new Minify(source, options);
} }
@@ -72,35 +76,28 @@
* @private * @private
* @constructor * @constructor
* @param {String} source The source to minify. * @param {String} source The source to minify.
* @param {Object} options The options object containing `onComplete`, * @param {Object} options The options object.
* `silent`, and `workingName`.
*/ */
function Minify(source, options) { function Minify(source, options) {
source || (source = ''); // juggle arguments
options || (options = {}); if (typeof source == 'object' && source) {
if (typeof source != 'string') {
options = source || options; options = source || options;
source = options.source || ''; source = options.source || '';
} }
// create the destination directory if it doesn't exist
if (!fs.existsSync(distPath)) {
// avoid errors when called as a npm executable
try {
fs.mkdirSync(distPath);
} catch(e) { }
}
this.compiled = {}; this.compiled = {};
this.hybrid = {}; this.hybrid = {};
this.uglified = {}; this.uglified = {};
this.isSilent = !!options.silent; this.isSilent = !!options.isSilent;
this.onComplete = options.onComplete || function() {}; this.isTemplate = !!options.isTemplate;
this.workingName = options.workingName || 'temp'; this.outputPath = options.outputPath;
source = preprocess(source); source = preprocess(source, options);
this.source = source; this.source = source;
this.onComplete = options.onComplete || function(source) {
fs.writeFileSync(this.outputPath, source, 'utf8');
};
// begin the minification process // begin the minification process
closureCompile.call(this, source, onClosureCompile.bind(this)); closureCompile.call(this, source, onClosureCompile.bind(this));
} }
@@ -117,10 +114,19 @@
* @param {Function} callback The function to call once the process completes. * @param {Function} callback The function to call once the process completes.
*/ */
function closureCompile(source, message, callback) { function closureCompile(source, message, callback) {
var options = closureOptions.slice();
// use simple optimizations when minifying template files
if (this.isTemplate) {
options = options.map(function(value) {
return value.replace(/^(compilation_level)=.+$/, '$1=SIMPLE_OPTIMIZATIONS');
});
}
// the standard error stream, standard output stream, and Closure Compiler process // the standard error stream, standard output stream, and Closure Compiler process
var error = '', var error = '',
output = '', output = '',
compiler = spawn('java', ['-jar', closurePath].concat(closureOptions)); compiler = spawn('java', ['-jar', closurePath].concat(options));
// juggle arguments // juggle arguments
if (typeof message == 'function') { if (typeof message == 'function') {
@@ -130,7 +136,7 @@
if (!this.isSilent) { if (!this.isSilent) {
console.log(message == null console.log(message == null
? 'Compressing ' + this.workingName + ' using the Closure Compiler...' ? 'Compressing ' + path.basename(this.outputPath, '.js') + ' using the Closure Compiler...'
: message : message
); );
} }
@@ -183,7 +189,7 @@
if (!this.isSilent) { if (!this.isSilent) {
console.log(message == null console.log(message == null
? 'Compressing ' + this.workingName + ' using UglifyJS...' ? 'Compressing ' + path.basename(this.outputPath, '.js') + ' using UglifyJS...'
: message : message
); );
} }
@@ -278,7 +284,7 @@
if (!this.isSilent) { if (!this.isSilent) {
console.log('Done. Size: %d bytes.', result.length); console.log('Done. Size: %d bytes.', result.length);
} }
var message = 'Compressing ' + this.workingName + ' using hybrid minification...'; var message = 'Compressing ' + path.basename(this.outputPath, '.js') + ' using hybrid minification...';
// store the gzipped result and report the size // store the gzipped result and report the size
this.uglified.gzip = result; this.uglified.gzip = result;
@@ -332,24 +338,8 @@
function onComplete() { function onComplete() {
var compiled = this.compiled, var compiled = this.compiled,
hybrid = this.hybrid, hybrid = this.hybrid,
name = this.workingName,
uglified = this.uglified; uglified = this.uglified;
// avoid errors when called as a npm executable
try {
// save the Closure Compiled version to disk
fs.writeFileSync(path.join(distPath, name + '.compiler.js'), compiled.source);
fs.writeFileSync(path.join(distPath, name + '.compiler.js.gz'), compiled.gzip);
// save the Uglified version to disk
fs.writeFileSync(path.join(distPath, name + '.uglify.js'), uglified.source);
fs.writeFileSync(path.join(distPath, name + '.uglify.js.gz'), uglified.gzip);
// save the hybrid minified version to disk
fs.writeFileSync(path.join(distPath, name + '.hybrid.js'), hybrid.source);
fs.writeFileSync(path.join(distPath, name + '.hybrid.js.gz'), hybrid.gzip);
} catch(e) { }
// select the smallest gzipped file and use its minified counterpart as the // select the smallest gzipped file and use its minified counterpart as the
// official minified release (ties go to Closure Compiler) // official minified release (ties go to Closure Compiler)
var min = Math.min(compiled.gzip.length, hybrid.gzip.length, uglified.gzip.length); var min = Math.min(compiled.gzip.length, hybrid.gzip.length, uglified.gzip.length);

View File

@@ -10,10 +10,10 @@
'lodash': 'lodash':
'/*!\n' + '/*!\n' +
' Lo-Dash @VERSION lodash.com/license\n' + ' Lo-Dash @VERSION lodash.com/license\n' +
' Underscore.js 1.3.3 github.com/documentcloud/underscore/blob/master/LICENSE\n' + ' Underscore.js 1.4.2 underscorejs.org/LICENSE\n' +
'*/', '*/',
'underscore': 'underscore':
'/*! Underscore.js @VERSION github.com/documentcloud/underscore/blob/master/LICENSE */' '/*! Underscore.js @VERSION underscorejs.org/LICENSE */'
}; };
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
@@ -27,7 +27,7 @@
*/ */
function postprocess(source) { function postprocess(source) {
// move vars exposed by Closure Compiler into the IIFE // move vars exposed by Closure Compiler into the IIFE
source = source.replace(/^([^(\n]+)\s*(\(function[^)]+\){)/, '$2$1'); source = source.replace(/^((?:(['"])use strict\2;)?(?:var (?:[a-z]+=(?:!0|!1|null)[,;])+)?)([\s\S]*?function[^)]+\){)/, '$3$1');
// unescape properties (i.e. foo["bar"] => foo.bar) // unescape properties (i.e. foo["bar"] => foo.bar)
source = source.replace(/(\w)\["([^."]+)"\]/g, function(match, left, right) { source = source.replace(/(\w)\["([^."]+)"\]/g, function(match, left, right) {
@@ -56,13 +56,20 @@
// expose `postprocess` // expose `postprocess`
if (module != require.main) { if (module != require.main) {
module.exports = postprocess; module.exports = postprocess;
} else { }
else {
// read the Lo-Dash source file from the first argument if the script // read the Lo-Dash source file from the first argument if the script
// was invoked directly (e.g. `node post-compile.js source.js`) and write to // was invoked directly (e.g. `node post-compile.js source.js`) and write to
// the same file // the same file
(function() { (function() {
var source = fs.readFileSync(process.argv[2], 'utf8'); var options = process.argv;
fs.writeFileSync(process.argv[2], postprocess(source), 'utf8'); if (options.length < 3) {
return;
}
var filePath = options[options.length - 1],
source = fs.readFileSync(filePath, 'utf8');
fs.writeFileSync(filePath, postprocess(source), 'utf8');
}()); }());
} }
}()); }());

View File

@@ -12,12 +12,11 @@
'callback', 'callback',
'collection', 'collection',
'concat', 'concat',
'createCallback',
'ctor', 'ctor',
'hasOwnProperty', 'hasOwnProperty',
'identity',
'index', 'index',
'iteratee', 'iteratee',
'iteratorBind',
'length', 'length',
'nativeKeys', 'nativeKeys',
'object', 'object',
@@ -33,6 +32,7 @@
'stringClass', 'stringClass',
'thisArg', 'thisArg',
'toString', 'toString',
'undefined',
'value', 'value',
// lesser used variables // lesser used variables
@@ -44,7 +44,6 @@
'callee', 'callee',
'className', 'className',
'compareAscending', 'compareAscending',
'data',
'forIn', 'forIn',
'found', 'found',
'funcs', 'funcs',
@@ -58,19 +57,18 @@
'isPlainObject', 'isPlainObject',
'methodName', 'methodName',
'noaccum', 'noaccum',
'noop',
'objectClass', 'objectClass',
'objectTypes', 'objectTypes',
'pass', 'pass',
'properties', 'properties',
'property', 'property',
'propsLength', 'propsLength',
'recursive',
'source', 'source',
'sources', 'stackA',
'stackB',
'stackLength', 'stackLength',
'target', 'target'
'valueProp',
'values'
]; ];
/** Used to minify `compileIterator` option properties */ /** Used to minify `compileIterator` option properties */
@@ -80,7 +78,6 @@
'arrayBranch', 'arrayBranch',
'beforeLoop', 'beforeLoop',
'bottom', 'bottom',
'exit',
'firstArg', 'firstArg',
'hasDontEnumBug', 'hasDontEnumBug',
'inLoop', 'inLoop',
@@ -106,7 +103,6 @@
var propWhitelist = [ var propWhitelist = [
'_', '_',
'__chain__', '__chain__',
'__proto__',
'__wrapped__', '__wrapped__',
'after', 'after',
'all', 'all',
@@ -118,7 +114,6 @@
'chain', 'chain',
'clearTimeout', 'clearTimeout',
'clone', 'clone',
'clones',
'collect', 'collect',
'compact', 'compact',
'compose', 'compose',
@@ -177,12 +172,14 @@
'isNull', 'isNull',
'isNumber', 'isNumber',
'isObject', 'isObject',
'isPlainObject',
'isRegExp', 'isRegExp',
'isString', 'isString',
'isUndefined', 'isUndefined',
'keys', 'keys',
'last', 'last',
'lastIndexOf', 'lastIndexOf',
'lateBind',
'map', 'map',
'max', 'max',
'memoize', 'memoize',
@@ -214,13 +211,11 @@
'sortBy', 'sortBy',
'sortedIndex', 'sortedIndex',
'source', 'source',
'sources',
'stackA',
'stackB',
'tail', 'tail',
'take', 'take',
'tap', 'tap',
'template', 'template',
'templates',
'templateSettings', 'templateSettings',
'throttle', 'throttle',
'times', 'times',
@@ -249,16 +244,24 @@
/** /**
* Pre-process a given Lo-Dash `source`, preparing it for minification. * Pre-process a given Lo-Dash `source`, preparing it for minification.
* *
* @param {String} source The source to process. * @param {String} [source=''] The source to process.
* @param {Object} [options={}] The options object.
* @returns {String} Returns the processed source. * @returns {String} Returns the processed source.
*/ */
function preprocess(source) { function preprocess(source, options) {
// remove copyright to add later in post-compile.js source || (source = '');
source = source.replace(/\/\*![\s\S]+?\*\//, ''); options || (options = {});
// remove unrecognized JSDoc tags so Closure Compiler won't complain // remove unrecognized JSDoc tags so Closure Compiler won't complain
source = source.replace(/@(?:alias|category)\b.*/g, ''); source = source.replace(/@(?:alias|category)\b.*/g, '');
if (options.isTemplate) {
return source;
}
// remove copyright to add later in post-compile.js
source = source.replace(/\/\*![\s\S]+?\*\//, '');
// add brackets to whitelisted properties so Closure Compiler won't mung them // add brackets to whitelisted properties so Closure Compiler won't mung them
// http://code.google.com/closure/compiler/docs/api-tutorial3.html#export // http://code.google.com/closure/compiler/docs/api-tutorial3.html#export
source = source.replace(RegExp('\\.(' + propWhitelist.join('|') + ')\\b', 'g'), "['$1']"); source = source.replace(RegExp('\\.(' + propWhitelist.join('|') + ')\\b', 'g'), "['$1']");
@@ -278,7 +281,7 @@
// remove whitespace from string literals // remove whitespace from string literals
source = source.replace(/'(?:(?=(\\?))\1.)*?'/g, function(string) { source = source.replace(/'(?:(?=(\\?))\1.)*?'/g, function(string) {
// avoids removing the '\n' of the `stringEscapes` object // avoids removing the '\n' of the `stringEscapes` object
return string.replace(/\[object |delete |else if|function | in |return\s+[\w']|throw |typeof |use strict|var |@ |'\\n'|\\\\n|\\n|\s+/g, function(match) { return string.replace(/\[object |delete |else |function | in |return\s+[\w']|throw |typeof |use strict|var |@ |'\\n'|\\\\n|\\n|\s+/g, function(match) {
return match == false || match == '\\n' ? '' : match; return match == false || match == '\\n' ? '' : match;
}); });
}); });
@@ -287,7 +290,7 @@
source = source.replace(/\+"__p\+='"/g, '+"\\n__p+=\'"'); source = source.replace(/\+"__p\+='"/g, '+"\\n__p+=\'"');
// remove whitespace from `_.template` related regexes // remove whitespace from `_.template` related regexes
source = source.replace(/(?:reDelimiterCode\w+|reEmptyString\w+|reInsertVariable) *=.+/g, function(match) { source = source.replace(/(?:reEmptyString\w+|reInsertVariable) *=.+/g, function(match) {
return match.replace(/ |\\n/g, ''); return match.replace(/ |\\n/g, '');
}); });
@@ -303,12 +306,12 @@
source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *try *\{(?:\s*\/\/.*)*\n *var useSourceURL[\s\S]+?catch[^}]+}\n/, ''); source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *try *\{(?:\s*\/\/.*)*\n *var useSourceURL[\s\S]+?catch[^}]+}\n/, '');
// remove debug sourceURL use in `_.template` // remove debug sourceURL use in `_.template`
source = source.replace(/(?:\s*\/\/.*\n)* *if *\(useSourceURL[^}]+}/, ''); source = source.replace(/(?:\s*\/\/.*\n)* *var sourceURL[^;]+;|\+ *sourceURL/g, '');
// minify internal properties used by 'compareAscending', `_.clone`, `_.isEqual`, `_.merge`, and `_.sortBy` // minify internal properties used by 'compareAscending', `_.merge`, and `_.sortBy`
(function() { (function() {
var properties = ['clones', 'criteria', 'index', 'sources', 'thorough', 'value', 'values'], var properties = ['criteria', 'index', 'value'],
snippets = source.match(/( +)(?:function (?:clone|compareAscending|isEqual)|var merge|var sortBy)\b[\s\S]+?\n\1}/g); snippets = source.match(/( +)(?:function compareAscending|var merge|var sortBy)\b[\s\S]+?\n\1}/g);
if (!snippets) { if (!snippets) {
return; return;
@@ -443,8 +446,17 @@
// was invoked directly (e.g. `node pre-compile.js source.js`) and write to // was invoked directly (e.g. `node pre-compile.js source.js`) and write to
// the same file // the same file
(function() { (function() {
var source = fs.readFileSync(process.argv[2], 'utf8'); var options = process.argv;
fs.writeFileSync(process.argv[2], preprocess(source), 'utf8'); if (options.length < 3) {
return;
}
var filePath = options[options.length - 1],
isTemplate = options.indexOf('-t') > -1 || options.indexOf('--template') > -1,
source = fs.readFileSync(filePath, 'utf8');
fs.writeFileSync(filePath, preprocess(source, {
'isTemplate': isTemplate
}), 'utf8');
}()); }());
} }
}()); }());

View File

@@ -1,34 +0,0 @@
{
"name": "lodash",
"version": "0.7.0",
"description": "A drop-in replacement for Underscore.js delivering performance, bug fixes, and additional features.",
"homepage": "http://lodash.com",
"main": [
"./lodash.js",
"./lodash.min.js"
],
"keywords": [
"browser",
"client",
"functional",
"performance",
"server",
"speed",
"util"
],
"licenses": [
{
"type": "MIT",
"url": "http://lodash.com/license"
}
],
"author": {
"name": "John-David Dalton",
"email": "john.david.dalton@gmail.com",
"web": "http://allyoucanleet.com/"
},
"repository": {
"type": "git",
"url": "https://github.com/bestiejs/lodash.git"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -20,9 +20,10 @@
// generate Markdown // generate Markdown
$markdown = docdown(array( $markdown = docdown(array(
'path' => '../' . $file, 'path' => '../' . $file,
'title' => 'Lo-Dash <sup>v0.7.0</sup>', 'title' => 'Lo-Dash <sup>v0.8.2</sup>',
'url' => 'https://github.com/bestiejs/lodash/blob/master/lodash.js' 'toc' => 'categories',
'url' => 'https://github.com/bestiejs/lodash/blob/master/lodash.js'
)); ));
// save to a .md file // save to a .md file

1
index.js Executable file
View File

@@ -0,0 +1 @@
module.exports = require('./lodash');

1767
lodash.js

File diff suppressed because it is too large Load Diff

77
lodash.min.js vendored
View File

@@ -1,42 +1,39 @@
/*! /*!
Lo-Dash 0.7.0 lodash.com/license Lo-Dash 0.8.2 lodash.com/license
Underscore.js 1.3.3 github.com/documentcloud/underscore/blob/master/LICENSE Underscore.js 1.4.2 underscorejs.org/LICENSE
*/ */
;(function(e,t){function s(e){return new o(e)}function o(e){if(e&&e.__wrapped__)return e;this.__wrapped__=e}function u(e,t,n){t||(t=0);var r=e.length,i=r-t>=(n||V),s=i?{}:e;if(i)for(var o=t-1;++o<r;)n=e[o]+"",(lt.call(s,n)?s[n]:s[n]=[]).push(e[o]);return function(e){if(i){var n=e+"";return lt.call(s,n)&&-1<k(s[n],e)}return-1<k(s,e,t)}}function a(){for(var e,t,n,s=-1,o=arguments.length,u={e:"",f:"",j:"",q:"",c:{d:""},m:{d:""}};++s<o;)for(t in e=arguments[s],e)n=(n=e[t])==r?"":n,/d|i/.test(t)?("string"==typeof ;(function(e,t){function s(e){if(e&&e.__wrapped__)return e;if(!(this instanceof s))return new s(e);this.__wrapped__=e}function o(e,t,n){t||(t=0);var r=e.length,i=r-t>=(n||H),s=i?{}:e;if(i)for(n=t-1;++n<r;){var o=e[n]+"";(Z.call(s,o)?s[o]:s[o]=[]).push(e[n])}return function(e){if(i){var n=e+"";return Z.call(s,n)&&-1<T(s[n],e)}return-1<T(s,e,t)}}function u(e,n){var r=e.b,i=n.b,e=e.a,n=n.a;if(e!==n){if(e>n||e===t)return 1;if(e<n||n===t)return-1}return r<i?-1:1}function a(e,t,n){function r(){var u=arguments
n&&(n={b:n,l:n}),u.c[t]=n.b||"",u.m[t]=n.l||""):u[t]=n;e=u.a,t=/^[^,]+/.exec(e)[0],n=u.s,u.g=t,u.h=Dt,u.k=zt,u.n=Bt,u.p=st,u.r=u.r!==i,u.s=n==r?Wt:n,u.o==r&&(u.o=It),u.f||(u.f="if(!"+t+")return u");if("d"!=t||!u.c.i)u.c=r;t="",u.s&&(t+="'use strict';"),t+="var i,A,j="+u.g+",u",u.j&&(t+="="+u.j),t+=";"+u.f+";"+u.q+";",u.c&&(t+="var l=j.length;i=-1;",u.m&&(t+="if(l>-1&&l===l>>>0){"),u.o&&(t+="if(z.call(j)==x){j=j.split('')}"),t+=u.c.d+";while(++i<l){A=j[i];"+u.c.i+"}",u.m&&(t+="}"));if(u.m){u.c?t+="else{" ,a=s?this:t;return i||(e=t[o]),n.length&&(u=u.length?n.concat(nt.call(u)):n),this instanceof r?(p.prototype=e.prototype,a=new p,(u=e.apply(a,u))&&Ht[typeof u]?u:a):e.apply(a,u)}var i=m(e),s=!n,o=e;return s&&(n=t),r}function f(e,n){return e?"function"!=typeof e?function(t){return t[e]}:n!==t?function(t,r,i){return e.call(n,t,r,i)}:e:A}function l(){for(var e={e:"",g:Et,i:"",j:Mt,m:Tt,n:kt,o:J,p:"",q:n,r:_t,c:{d:""},l:{d:""}},t,i=-1;t=arguments[++i];)for(var s in t){var o=t[s];/d|h/.test(s)?("string"==typeof
:u.n&&(t+="var l=j.length;i=-1;if(l&&P(j)){while(++i<l){A=j[i+=''];"+u.m.i+"}}else{"),u.h||(t+="var v=typeof j=='function'&&r.call(j,'prototype');");if(u.k&&u.r)t+="var o=-1,p=Y[typeof j]?m(j):[],l=p.length;"+u.m.d+";while(++o<l){i=p[o];",u.h||(t+="if(!(v&&i=='prototype')){"),t+="A=j[i];"+u.m.i+"",u.h||(t+="}");else{t+=u.m.d+";for(i in j){";if(!u.h||u.r)t+="if(",u.h||(t+="!(v&&i=='prototype')"),!u.h&&u.r&&(t+="&&"),u.r&&(t+="g.call(j,i)"),t+="){";t+="A=j[i];"+u.m.i+";";if(!u.h||u.r)t+="}"}t+="}"; o&&(o={b:o,k:o}),e.c[s]=o.b,e.l[s]=o.k):e[s]=o}t=e.a;if("d"!=(e.f=/^[^,]+/.exec(t)[0])||!e.c.h)e.c=r;i="",e.r&&(i+="'use strict';"),i+="var i,A,j="+e.f+",t="+(e.i||e.f)+";if(!"+e.f+")return t;"+e.p+";",e.c&&(i+="var k=j.length;i=-1;",e.l&&(i+="if(k===+k){"),e.n&&(i+="if(y.call(j)==w){j=j.split('')}"),i+=e.c.d+";while(++i<k){A=j[i];"+e.c.h+"}",e.l&&(i+="}"));if(e.l){e.c?i+="else {":e.m&&(i+="var k=j.length;i=-1;if(k&&O(j)){while(++i<k){A=j[i+=''];"+e.l.h+"}}else {"),e.g||(i+="var u=typeof j=='function'&&q.call(j,'prototype');"
if(u.h){t+="var f=j.constructor;";for(n=0;7>n;n++)t+="i='"+u.p[n]+"';if(","constructor"==u.p[n]&&(t+="!(f&&f.prototype===j)&&"),t+="g.call(j,i)){A=j[i];"+u.m.i+"}"}if(u.c||u.n)t+="}"}return t+=u.e+";return u",Function("D,E,F,I,e,K,g,h,N,P,R,T,U,k,X,Y,m,r,w,x,z","var G=function("+e+"){"+t+"};return G")(Xt,q,_,f,ft,hn,lt,D,k,b,un,w,an,p,Lt,Kt,bt,ht,pt,Ot,dt)}function f(e,n){var r=e.c,i=n.c,e=e.b,n=n.b;if(e!==n){if(e>n||e===t)return 1;if(e<n||n===t)return-1}return r<i?-1:1}function l(e,t){return at[ );if(e.j&&e.q)i+="var n=-1,o=Y[typeof j]?l(j):[],k=o.length;"+e.l.d+";while(++n<k){i=o[n];",e.g||(i+="if(!(u&&i=='prototype')){"),i+="A=j[i];"+e.l.h+"",e.g||(i+="}");else{i+=e.l.d+";for(i in j){";if(!e.g||e.q)i+="if(",e.g||(i+="!(u&&i=='prototype')"),!e.g&&e.q&&(i+="&&"),e.q&&(i+="h.call(j,i)"),i+="){";i+="A=j[i];"+e.l.h+";";if(!e.g||e.q)i+="}"}i+="}";if(e.g){i+="var g=j.constructor;";for(s=0;7>s;s++)i+="i='"+e.o[s]+"';if(","constructor"==e.o[s]&&(i+="!(g&&g.prototype===j)&&"),i+="h.call(j,i)){A=j[i];"+
t]}function c(e){return"\\"+Qt[e]}function h(e){return $t[e]}function p(e,t){return function(n,r,i){return e.call(t,n,r,i)}}function d(){}function v(e,t){if(e&&J.test(t))return"<e%-"+t+"%>";var n=at.length;return at[n]="'+__e("+t+")+'",ot+n+ut}function m(e,t,n,i){return i?(e=at.length,at[e]="';"+i+";__p+='",ot+e+ut):t?v(r,t):g(r,n)}function g(e,t){if(e&&J.test(t))return"<e%="+t+"%>";var n=at.length;return at[n]="'+((__t=("+t+"))==null?'':__t)+'",ot+n+ut}function y(e){return Jt[e]}function b(e){return dt e.l.h+"}"}if(e.c||e.m)i+="}"}return i+=e.e+";return t",Function("D,E,F,I,e,f,J,h,M,O,Q,S,T,X,Y,l,q,v,w,y,z","var G=function("+t+"){"+i+"};return G")(Dt,_,L,u,Q,f,en,Z,T,v,$t,m,Jt,mt,Ht,ut,tt,nt,yt,rt)}function c(e){return"\\"+Bt[e]}function h(e){return Qt[e]}function p(){}function d(e){return Gt[e]}function v(e){return rt.call(e)==ct}function m(e){return"function"==typeof e}function g(e){var t=i;if(!e||"object"!=typeof e||v(e))return t;var n=e.constructor;return(!Lt||"function"==typeof e.toString||"string"!=typeof
.call(e)==xt}function w(e){return"function"==typeof e}function E(e,t){var n=i;if(!e||"object"!=typeof e||!t&&b(e))return n;var r=e.constructor;return(!qt||"function"==typeof e.toString||"string"!=typeof (e+""))&&(!w(r)||r instanceof r)?Ht?(hn(e,function(e,t,r){return n=!lt.call(r,t),i}),n===i):(hn(e,function(e,t){n=t}),n===i||lt.call(e,n)):n}function S(e,t,s,o){if(e==r)return e;s&&(t=i),o||(o={e:r}),o.t==r&&(o.t=!(!R.clone&&!z.clone&&!W.clone));if(((s=Kt[typeof e])||o.t)&&e.clone&&w(e.clone))return o (e+""))&&(!m(n)||n instanceof n)?xt?(en(e,function(e,n,r){return t=!Z.call(r,n),i}),t===i):(en(e,function(e,n){t=n}),t===i||Z.call(e,t)):t}function y(e,t,n,s,o){if(e==r)return e;n&&(t=i);if(n=Ht[typeof e]){var u=rt.call(e);if(!Pt[u]||Nt&&v(e))return e;var a=u==ht,n=a||(u==mt?Jt(e):n)}if(!n||!t)return n?a?nt.call(e):Zt({},e):e;n=e.constructor;switch(u){case pt:case dt:return new n(+e);case vt:case yt:return new n(e);case gt:return n(e.source,U.exec(e))}s||(s=[]),o||(o=[]);for(u=s.length;u--;)if(s[
.t=r,e.clone(t);if(s){var u=dt.call(e);if(!Vt[u]||jt&&b(e))return e;var a=u==Tt,s=a||(u==Lt?an(e,n):s)}if(!s||!t)return s?a?pt.call(e):cn({},e):e;s=e.constructor;switch(u){case Nt:return new s(e==n);case Ct:return new s(+e);case kt:case Ot:return new s(e);case At:return s(e.source,Z.exec(e))}for(var f=o.a||(o.a=[]),l=o.d||(o.d=[]),u=f.length;u--;)if(l[u]==e)return f[u];var c=a?s(u=e.length):{};f.push(c),l.push(e);if(a)for(a=-1;++a<u;)c[a]=S(e[a],t,r,o);else pn(e,function(e,n){c[n]=S(e,t,r,o)});return c u]==e)return o[u];var f=a?n(e.length):{};return s.push(e),o.push(f),(a?mn:tn)(e,function(e,n){f[n]=y(e,t,r,s,o)}),f}function b(e,t,s,o){if(e==r||t==r)return e===t;if(e===t)return 0!==e||1/e==1/t;if(Ht[typeof e]||Ht[typeof t])e=e.__wrapped__||e,t=t.__wrapped__||t;var u=rt.call(e);if(u!=rt.call(t))return i;switch(u){case pt:case dt:return+e==+t;case vt:return e!=+e?t!=+t:0==e?1/e==1/t:e==+t;case gt:case yt:return e==t+""}var a=Dt[u];if(Nt&&!a&&(a=v(e))&&!v(t)||!a&&(u!=mt||Lt&&("function"!=typeof e.
}function x(e,t,s){if(e==r||t==r)return e===t;s||(s={e:r}),s.t==r&&(s.t=!(!R.isEqual&&!z.isEqual&&!W.isEqual));if(Kt[typeof e]||Kt[typeof t]||s.t){e=e.__wrapped__||e,t=t.__wrapped__||t;if(e.isEqual&&w(e.isEqual))return s.t=r,e.isEqual(t);if(t.isEqual&&w(t.isEqual))return s.t=r,t.isEqual(e)}if(e===t)return 0!==e||1/e==1/t;var o=dt.call(e);if(o!=dt.call(t))return i;switch(o){case Nt:case Ct:return+e==+t;case kt:return e!=+e?t!=+t:0==e?1/e==1/t:e==+t;case At:case Ot:return e==t+""}var u=Xt[o];if(jt&&! toString&&"string"==typeof (e+"")||"function"!=typeof t.toString&&"string"==typeof (t+""))))return i;s||(s=[]),o||(o=[]);for(u=s.length;u--;)if(s[u]==e)return o[u]==t;var u=-1,f=n,l=0;s.push(e),o.push(t);if(a){l=e.length;if(f=l==t.length)for(;l--&&(f=b(e[l],t[l],s,o)););return f}a=e.constructor,f=t.constructor;if(a!=f&&(!m(a)||!(a instanceof a&&m(f)&&f instanceof f)))return i;for(var c in e)if(Z.call(e,c)&&(l++,!Z.call(t,c)||!b(e[c],t[c],s,o)))return i;for(c in t)if(Z.call(t,c)&&!(l--))return i;if(
u&&(u=b(e))&&!b(t)||!u&&(o!=Lt||qt&&("function"!=typeof e.toString&&"string"==typeof (e+"")||"function"!=typeof t.toString&&"string"==typeof (t+""))))return i;for(var a=s.stackA||(s.stackA=[]),f=s.stackB||(s.stackB=[]),o=a.length;o--;)if(a[o]==e)return f[o]==t;var o=-1,l=n,c=0;a.push(e),f.push(t);if(u){c=e.length;if(l=c==t.length)for(;c--&&(l=x(e[c],t[c],s)););return l}u=e.constructor,a=t.constructor;if(u!=a&&(!w(u)||!(u instanceof u&&w(a)&&a instanceof a)))return i;for(var h in e)if(lt.call(e,h)&& Et)for(;7>++u;)if(c=J[u],Z.call(e,c)&&(!Z.call(t,c)||!b(e[c],t[c],s,o)))return i;return n}function w(e,t,n){var r=-Infinity,i=-1,s=e?e.length:0,o=r;if(t||s!==+s)t=f(t,n),mn(e,function(e,n,i){n=t(e,n,i),n>r&&(r=n,o=e)});else for(;++i<s;)e[i]>o&&(o=e[i]);return o}function E(e,t,n,r){var s=e,o=e?e.length:0,u=3>arguments.length;if(o!==+o)var a=sn(e),o=a.length;else kt&&rt.call(e)==yt&&(s=e.split(""));return mn(e,function(e,f,l){f=a?a[--o]:--o,n=u?(u=i,s[f]):t.call(r,n,s[f],f,l)}),n}function S(e,t,n){
(c++,!lt.call(t,h)||!x(e[h],t[h],s)))return i;for(h in t)if(lt.call(t,h)&&!(c--))return i;if(Dt)for(;7>++o;)if(h=st[o],lt.call(e,h)&&(!lt.call(t,h)||!x(e[h],t[h],s)))return i;return n}function T(e,t,n,r){if(!e)return n;var i=e.length,s=3>arguments.length;r&&(t=p(t,r));if(-1<i&&i===i>>>0){var o=It&&dt.call(e)==Ot?e.split(""):e;for(i&&s&&(n=o[--i]);i--;)n=t(n,o[i],i,e);return n}o=gn(e);for((i=o.length)&&s&&(n=e[o[--i]]);i--;)s=o[i],n=t(n,e[s],s,e);return n}function N(e,t,n){if(e)return t==r||n?e[0] if(e)return t==r||n?e[0]:nt.call(e,0,t)}function x(e,t){for(var n=-1,r=e?e.length:0,i=[];++n<r;){var s=e[n];$t(s)?et.apply(i,t?s:x(s)):i.push(s)}return i}function T(e,t,n){var r=-1,i=e?e.length:0;if("number"==typeof n)r=(0>n?at(0,i+n):n||0)-1;else if(n)return r=C(e,t),e[r]===t?r:-1;for(;++r<i;)if(e[r]===t)return r;return-1}function N(e,t,n){return e?nt.call(e,t==r||n?1:t):[]}function C(e,t,n,r){var i=0,s=e?e.length:i;if(n){n=f(n,r);for(t=n(t);i<s;)r=i+s>>>1,n(e[r])<t?i=r+1:s=r}else for(;i<s;)r=i+
:pt.call(e,0,t)}function C(e,t){var n=[];if(!e)return n;for(var r,i=-1,s=e.length;++i<s;)r=e[i],un(r)?ct.apply(n,t?r:C(r)):n.push(r);return n}function k(e,t,n){if(!e)return-1;var r=-1,i=e.length;if(n){if("number"!=typeof n)return r=O(e,t),e[r]===t?r:-1;r=(0>n?wt(0,i+n):n)-1}for(;++r<i;)if(e[r]===t)return r;return-1}function L(e,t,n){var r=-Infinity,i=r;if(!e)return i;var s=-1,o=e.length;if(!t){for(;++s<o;)e[s]>i&&(i=e[s]);return i}for(n&&(t=p(t,n));++s<o;)n=t(e[s],s,e),n>r&&(r=n,i=e[s]);return i} s>>>1,e[r]<t?i=r+1:s=r;return i}function k(e,t,n,r){var s=-1,o=e?e.length:0,u=[],a=[];"function"==typeof t&&(r=n,n=t,t=i);for(n=f(n,r);++s<o;)if(r=n(e[s],s,e),t?!s||a[a.length-1]!==r:0>T(a,r))a.push(r),u.push(e[s]);return u}function L(e,t){return Ot||it&&2<arguments.length?it.call.apply(it,arguments):a(e,t,nt.call(arguments,2))}function A(e){return e}function O(e){mn(nn(e),function(t){var r=s[t]=e[t];s.prototype[t]=function(){var e=[this.__wrapped__];return arguments.length&&et.apply(e,arguments)
function A(e,t,n){return e?pt.call(e,t==r||n?1:t):[]}function O(e,t,n,r){if(!e)return 0;var i=0,s=e.length;if(n){r&&(n=_(n,r));for(t=n(t);i<s;)r=i+s>>>1,n(e[r])<t?i=r+1:s=r}else for(;i<s;)r=i+s>>>1,e[r]<t?i=r+1:s=r;return i}function M(e,t,n,r){var s=[];if(!e)return s;var o=-1,u=e.length,a=[];"function"==typeof t&&(r=n,n=t,t=i);for(n?r&&(n=p(n,r)):n=D;++o<u;)if(r=n(e[o],o,e),t?!o||a[a.length-1]!==r:0>k(a,r))a.push(r),s.push(e[o]);return s}function _(e,t){function n(){var o=arguments,u=t;return i|| ,e=r.apply(s,e),this.__chain__&&(e=new s(e),e.__chain__=n),e}})}var n=!0,r=null,i=!1,M="object"==typeof exports&&exports&&("object"==typeof global&&global&&global==global.global&&(e=global),exports),_=Array.prototype,D=Object.prototype,P=0,H=30,B=e._,j=/[-?+=!~*%&^<>|{(\/]|\[\D|\b(?:delete|in|instanceof|new|typeof|void)\b/,F=/&(?:amp|lt|gt|quot|#x27);/g,I=/\b__p\+='';/g,q=/\b(__p\+=)''\+/g,R=/(__e\(.*?\)|\b__t\))\+'';/g,U=/\w*$/,z=/(?:__e|__t=)\(\s*(?![\d\s"']|this\.)/g,W=RegExp("^"+(D.valueOf+""
(e=t[r]),s.length&&(o=o.length?s.concat(pt.call(o)):s),this instanceof n?(d.prototype=e.prototype,u=new d,(o=e.apply(u,o))&&Kt[typeof o]?o:u):e.apply(u,o)}var r,i=w(e);if(i){if(Ut||vt&&2<arguments.length)return vt.call.apply(vt,arguments)}else r=t,t=e;var s=pt.call(arguments,2);return n}function D(e){return e}function P(e){Ln(dn(e),function(t){var r=s[t]=e[t];o.prototype[t]=function(){var e=[this.__wrapped__];return arguments.length&&ct.apply(e,arguments),e=r.apply(s,e),this.__chain__&&(e=new o(e ).replace(/[.*+?^=!:${}()|[\]\/\\]/g,"\\$&").replace(/valueOf|for [^\]]+/g,".+?")+"$"),X=/($^)/,V=/[&<>"']/g,$=/['\n\r\t\u2028\u2029\\]/g,J="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" "),K=Math.ceil,Q=_.concat,G=Math.floor,Y=W.test(Y=Object.getPrototypeOf)&&Y,Z=D.hasOwnProperty,et=_.push,tt=D.propertyIsEnumerable,nt=_.slice,rt=D.toString,it=W.test(it=nt.bind)&&it,st=W.test(st=Array.isArray)&&st,ot=e.isFinite,ut=W.test(ut=Object.keys)&&ut
),e.__chain__=n),e}})}var n=!0,r=null,i=!1,H,B,j,F,I="object"==typeof exports&&exports&&("object"==typeof global&&global&&global==global.global&&(e=global),exports),q=Array.prototype,R=Boolean.prototype,U=Object.prototype,z=Number.prototype,W=String.prototype,X=0,V=30,$=e._,J=/[-?+=!~*%&^<>|{(\/]|\[\D|\b(?:delete|in|instanceof|new|typeof|void)\b/,K=/&(?:amp|lt|gt|quot|#x27);/g,Q=/\b__p\+='';/g,G=/\b(__p\+=)''\+/g,Y=/(__e\(.*?\)|\b__t\))\+'';/g,Z=/\w*$/,et=/(?:__e|__t=)\(\s*(?![\d\s"']|this\.)/g,tt= ,at=Math.max,ft=Math.min,lt=Math.random,ct="[object Arguments]",ht="[object Array]",pt="[object Boolean]",dt="[object Date]",vt="[object Number]",mt="[object Object]",gt="[object RegExp]",yt="[object String]",bt=e.clearTimeout,wt=e.setTimeout,Et,St,xt,Tt=n;(function(){function e(){this.x=1}var t={0:1,length:1},n=[];e.prototype={valueOf:1,y:1};for(var r in new e)n.push(r);for(r in arguments)Tt=!r;Et=4>(n+"").length,xt="x"!=n[0],St=(n.splice.call(t,0,1),t[0])})(1);var Nt=!v(arguments),Ct="x"!=nt.call("x"
RegExp("^"+(U.valueOf+"").replace(/[.*+?^=!:${}()|[\]\/\\]/g,"\\$&").replace(/valueOf|for [^\]]+/g,".+?")+"$"),nt=/__token(\d+)__/g,rt=/[&<>"']/g,it=/['\n\r\t\u2028\u2029\\]/g,st="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" "),ot="__token",ut="__",at=[],ft=q.concat,lt=U.hasOwnProperty,ct=q.push,ht=U.propertyIsEnumerable,pt=q.slice,dt=U.toString,vt=tt.test(vt=pt.bind)&&vt,mt=Math.floor,gt=tt.test(gt=Array.isArray)&&gt,yt=e.isFinite,bt=tt.test )[0],kt="xx"!="x"[0]+Object("x")[0];try{var Lt=("[object Object]",rt.call(e.document||0)==mt)}catch(At){}var Ot=it&&/\n|Opera/.test(it+rt.call(e.opera)),Mt=ut&&/^.+$|true/.test(ut+!!e.attachEvent),_t=!Ot,Dt={};Dt[pt]=Dt[dt]=Dt["[object Function]"]=Dt[vt]=Dt[mt]=Dt[gt]=i,Dt[ct]=Dt[ht]=Dt[yt]=n;var Pt={};Pt[ct]=Pt["[object Function]"]=i,Pt[ht]=Pt[pt]=Pt[dt]=Pt[vt]=Pt[mt]=Pt[gt]=Pt[yt]=n;var Ht={"boolean":i,"function":n,object:n,number:i,string:i,"undefined":i,unknown:n},Bt={"\\":"\\","'":"'","\n":"n"
(bt=Object.keys)&&bt,wt=Math.max,Et=Math.min,St=Math.random,xt="[object Arguments]",Tt="[object Array]",Nt="[object Boolean]",Ct="[object Date]",kt="[object Number]",Lt="[object Object]",At="[object RegExp]",Ot="[object String]",Mt=e.clearTimeout,_t=e.setTimeout,Dt,Pt,Ht,Bt=n;(function(){function e(){this.x=1}var t={0:1,length:1},n=[];e.prototype={valueOf:1,y:1};for(var r in new e)n.push(r);for(r in arguments)Bt=!r;Dt=4>(n+"").length,Ht="x"!=n[0],Pt=(n.splice.call(t,0,1),t[0])})(1);var jt=!b(arguments ,"\r":"r"," ":"t","\u2028":"u2028","\u2029":"u2029"};s.templateSettings={escape:/<%-([\s\S]+?)%>/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,variable:""};var jt={a:"d,c,x",p:"c=f(c,x)",h:"if(c(A,i,d)===false)return t"},Ft={i:"{}",p:"c=f(c,x)",h:"var p=c(A,i,d);(h.call(t,p)?t[p]++:t[p]=1)"},It={i:"true",h:"if(!c(A,i,d))return!t"},qt={q:i,r:i,a:"m",p:"for(var a=1,b=arguments.length;a<b;a++){if(j=arguments[a]){",h:"t[i]=A",e:"}}"},Rt={i:"[]",h:"c(A,i,d)&&t.push(A)"},Ut={p:"c=f(c,x)"}
),Ft="x"!=pt.call("x")[0],It="xx"!="x"[0]+Object("x")[0];try{var qt=("[object Object]",dt.call(e.document||0)==Lt)}catch(Rt){}var Ut=vt&&/\n|Opera/.test(vt+dt.call(e.opera)),zt=bt&&/^.+$|true/.test(bt+!!e.attachEvent),Wt=!Ut,Xt={};Xt[Nt]=Xt[Ct]=Xt["[object Function]"]=Xt[kt]=Xt[Lt]=Xt[At]=i,Xt[xt]=Xt[Tt]=Xt[Ot]=n;var Vt={};Vt[xt]=Vt["[object Function]"]=i,Vt[Tt]=Vt[Nt]=Vt[Ct]=Vt[kt]=Vt[Lt]=Vt[At]=Vt[Ot]=n;var $t={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;"},Jt={"&amp;":"&","&lt;":"<" ,zt={h:{k:jt.h}},Wt={i:"d||[]",d:{b:"t=Array(k)",k:"t="+(Mt?"Array(k)":"[]")},h:{b:"t[i]=c(A,i,d)",k:"t"+(Mt?"[n]=":".push")+"(c(A,i,d))"}},Xt={q:i,a:"m,c,x",i:"{}",p:"var R=typeof c=='function';if(R)c=f(c,x);else var s=e.apply(E,arguments)",h:"if(R?!c(A,i,m):M(s,i)<0)t[i]=A"},Vt=l({a:"m",i:"{}",h:"t[A]=i"});Nt&&(v=function(e){return e?Z.call(e,"callee"):i});var $t=st||function(e){return rt.call(e)==ht};m(/x/)&&(m=function(e){return"[object Function]"==rt.call(e)});var Jt=Y?function(e){if(!e||"object"!=typeof
,"&gt;":">","&quot;":'"',"&#x27;":"'"},Kt={"boolean":i,"function":n,object:n,number:i,string:i,"undefined":i,unknown:n},Qt={"\\":"\\","'":"'","\n":"n","\r":"r"," ":"t","\u2028":"u2028","\u2029":"u2029"};s.templateSettings={escape:/<%-([\s\S]+?)%>/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,variable:""};var Gt={a:"d,c,y",j:"d",q:"if(!c)c=h;else if(y)c=k(c,y)",i:"if(c(A,i,d)===false)return u"},Yt={j:"{}",q:"var q;if(typeof c!='function'){var ii=c;c=function(A){return A[ii]}}else if(y)c=k(c,y)" e)return i;var t=e.valueOf,n="function"==typeof t&&(n=Y(t))&&Y(n);return n?e==n||Y(e)==n&&!v(e):g(e)}:g,Kt=l({a:"m",i:"[]",h:"t.push(i)"}),Qt={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;"},Gt=Vt(Qt),Yt=l(qt,{h:"if(t[i]==null)"+qt.h}),Zt=l(qt),en=l(jt,Ut,zt,{q:i}),tn=l(jt,Ut,zt),nn=l({q:i,a:"m",i:"[]",h:"S(A)&&t.push(i)",e:"t.sort()"}),rn=l({a:"A",i:"true",p:"var H=y.call(A),k=A.length;if(D[H]"+(Nt?"||O(A)":"")+"||(H==X&&k===+k&&S(A.splice)))return!k",h:{k:"return false"}}),sn=ut?function(
,i:"q=c(A,i,d);(g.call(u,q)?u[q]++:u[q]=1)"},Zt={j:"true",i:"if(!c(A,i,d))return!u"},en={r:i,s:i,a:"n",j:"n",q:"for(var a=1,b=arguments.length;a<b;a++){if(j=arguments[a]){",i:"u[i]=A",e:"}}"},tn={j:"[]",i:"c(A,i,d)&&u.push(A)"},nn={q:"if(y)c=k(c,y)"},rn={i:{l:Gt.i}},sn={j:"",f:"if(!d)return[]",d:{b:"u=Array(l)",l:"u="+(zt?"Array(l)":"[]")},i:{b:"u[i]=c(A,i,d)",l:"u"+(zt?"[o]=":".push")+"(c(A,i,d))"}},on={r:i,a:"n,c,y",j:"{}",q:"var S=typeof c=='function';if(!S){var t=e.apply(E,arguments)}else if(y)c=k(c,y)" e){var t=typeof e;return"function"==t&&tt.call(e,"prototype")?Kt(e):e&&Ht[t]?ut(e):[]}:Kt,on=l(qt,{a:"m,dd,N",p:"var P,C=arguments,a=0;if(N==I){var b=2,ee=C[3],ff=C[4]}else var b=C.length,ee=[],ff=[];while(++a<b){if(j=C[a]){",h:"if((dd=A)&&((P=Q(dd))||T(dd))){var K=false,gg=ee.length;while(gg--)if(K=ee[gg]==dd)break;if(K){t[i]=ff[gg]}else {ee.push(dd);ff.push(A=(A=t[i],P)?(Q(A)?A:[]):(T(A)?A:{}));t[i]=G(A,dd,I,ee,ff)}}else if(dd!=null)t[i]=dd"}),un=l(Xt),an=l({a:"m",i:"[]",h:"t"+(Mt?"[n]=":".push"
,i:"if(S?!c(A,i,n):N(t,i)<0)u[i]=A"};jt&&(b=function(e){return!!e&&!!lt.call(e,"callee")});var un=gt||function(e){return dt.call(e)==Tt};w(/x/)&&(w=function(e){return"[object Function]"==dt.call(e)});var an=Kt.__proto__!=U?E:function(e,t){if(!e)return i;var n=e.valueOf,r="function"==typeof n&&(r=n.__proto__)&&r.__proto__;return r?e==r||e.__proto__==r&&(t||!b(e)):E(e)},fn=a({a:"n",j:"[]",i:"u.push(i)"}),ln=a(en,{i:"if(u[i]==null)"+en.i}),cn=a(en),hn=a(Gt,nn,rn,{r:i}),pn=a(Gt,nn,rn),dn=a({r:i,a:"n" )+"([i,A])"}),fn=l(Xt,{p:"if(typeof c!='function'){var i=0,s=e.apply(E,arguments),k=s.length;while(++i<k){var p=s[i];if(p in m)t[p]=m[p]}}else {c=f(c,x)",h:"if(c(A,i,m))t[i]=A",e:"}"}),ln=l({a:"m",i:"[]",h:"t.push(A)"}),cn=l({a:"d,hh",i:"false",n:i,d:{b:"if(y.call(d)==w)return d.indexOf(hh)>-1"},h:"if(A===hh)return true"}),hn=l(jt,Ft),pn=l(jt,It),dn=l(jt,Rt),vn=l(jt,Ut,{i:"z",h:"if(c(A,i,d))return A"}),mn=l(jt,Ut),gn=l(jt,Ft,{h:"var p=c(A,i,d);(h.call(t,p)?t[p]:t[p]=[]).push(A)"}),yn=l(Wt,{a:"d,U"
,j:"[]",i:"if(T(A))u.push(i)",e:"u.sort()"}),vn=a({a:"n",j:"{}",i:"u[A]=i"}),mn=a({a:"A",j:"true",q:"var H=z.call(A),l=A.length;if(D[H]"+(jt?"||P(A)":"")+"||(H==X&&l>-1&&l===l>>>0&&T(A.splice)))return!l",i:{l:"return false"}}),gn=bt?function(e){var t=typeof e;return"function"==t&&ht.call(e,"prototype")?fn(e):e&&Kt[t]?bt(e):[]}:fn,yn=a(en,{a:"n,ee,O",q:"var Q,dd=O==U,J=dd?arguments[3]:{g:[],d:[]};for(var a=1,b=dd?2:arguments.length;a<b;a++){if(j=arguments[a]){",i:"if((ee=A)&&((Q=R(ee))||U(ee))){var L=false,jj=J.g,ff=J.d,gg=ff.length;while(gg--)if(L=ff[gg]==ee)break;if(L){u[i]=jj[gg]}else{jj.push(A=(A=u[i])&&Q?(R(A)?A:[]):(U(A)?A:{}));ff.push(ee);u[i]=G(A,ee,U,J)}}else if(ee!=null)u[i]=ee" ,p:"var C=v.call(arguments,2),R=typeof U=='function'",h:{b:"t[i]=(R?U:A[U]).apply(A,C)",k:"t"+(Mt?"[n]=":".push")+"((R?U:A[U]).apply(A,C))"}}),bn=l(jt,Wt),wn=l(Wt,{a:"d,bb",h:{b:"t[i]=A[bb]",k:"t"+(Mt?"[n]=":".push")+"(A[bb])"}}),En=l({a:"d,c,B,x",i:"B",p:"var V=arguments.length<3;c=f(c,x)",d:{b:"if(V)t=j[++i]"},h:{b:"t=c(t,A,i,d)",k:"t=V?(V=false,A):c(t,A,i,d)"}}),Sn=l(jt,Rt,{h:"!"+Rt.h}),xn=l(jt,It,{i:"false",h:It.h.replace("!","")}),Tn=l(jt,Ft,Wt,{h:{b:"t[i]={a:c(A,i,d),b:i,c:A}",k:"t"+(Mt?"[n]="
}),bn=a(on),wn=a({a:"n",j:"[]",i:"u"+(zt?"[o]=":".push")+"([i,A])"}),En=a(on,{q:"if(typeof c!='function'){var q,t=e.apply(E,arguments),l=t.length;for(i=1;i<l;i++){q=t[i];if(q in n)u[q]=n[q]}}else{if(y)c=k(c,y)",i:"if(c(A,i,n))u[i]=A",e:"}"}),Sn=a({a:"n",j:"[]",i:"u.push(A)"}),xn=a({a:"d,hh",j:"false",o:i,d:{b:"if(z.call(d)==x)return d.indexOf(hh)>-1"},i:"if(A===hh)return true"}),Tn=a(Gt,Yt),Nn=a(Gt,Zt),Cn=a(Gt,tn),kn=a(Gt,nn,{j:"",i:"if(c(A,i,d))return A"}),Ln=a(Gt,nn),An=a(Gt,Yt,{i:"q=c(A,i,d);(g.call(u,q)?u[q]:u[q]=[]).push(A)" :".push")+"({a:c(A,i,d),b:i,c:A})"},e:"t.sort(I);k=t.length;while(k--)t[k]=t[k].c"}),Nn=l(Rt,{a:"d,aa",p:"var s=[];J(aa,function(A,p){s.push(p)});var cc=s.length",h:"for(var Z=true,r=0;r<cc;r++){var p=s[r];if(!(Z=A[p]===aa[p]))break}Z&&t.push(A)"}),Cn=l({q:i,r:i,a:"m",p:"var L=arguments,i=0,k=L.length;if(k>1){while(++i<k)t[L[i]]=F(t[L[i]],t);return t}",h:"if(S(A))t[i]=F(A,t)"});s.VERSION="0.8.2",s.after=function(e,t){return 1>e?t():function(){if(1>--e)return t.apply(this,arguments)}},s.bind=L,s.bindAll=
}),On=a(sn,{a:"d,V",q:"var C=w.call(arguments,2),S=typeof V=='function'",i:{b:"u[i]=(S?V:A[V]).apply(A,C)",l:"u"+(zt?"[o]=":".push")+"((S?V:A[V]).apply(A,C))"}}),Mn=a(Gt,sn),_n=a(sn,{a:"d,bb",i:{b:"u[i]=A[bb]",l:"u"+(zt?"[o]=":".push")+"(A[bb])"}}),Dn=a({a:"d,c,B,y",j:"B",q:"var W=arguments.length<3;if(y)c=k(c,y)",d:{b:"if(W)u=j[++i]"},i:{b:"u=c(u,A,i,d)",l:"u=W?(W=false,A):c(u,A,i,d)"}}),Pn=a(Gt,tn,{i:"!"+tn.i}),Hn=a(Gt,Zt,{j:"false",i:Zt.i.replace("!","")}),Bn=a(Gt,Yt,sn,{i:{b:"u[i]={b:c(A,i,d),c:i,f:A}" Cn,s.chain=function(e){return e=new s(e),e.__chain__=n,e},s.clone=y,s.compact=function(e){for(var t=-1,n=e?e.length:0,r=[];++t<n;){var i=e[t];i&&r.push(i)}return r},s.compose=function(){var e=arguments;return function(){for(var t=arguments,n=e.length;n--;)t=[e[n].apply(this,t)];return t[0]}},s.contains=cn,s.countBy=hn,s.debounce=function(e,t,n){function i(){a=r,n||(o=e.apply(u,s))}var s,o,u,a;return function(){var r=n&&!a;return s=arguments,u=this,bt(a),a=wt(i,t),r&&(o=e.apply(u,s)),o}},s.defaults=
,l:"u"+(zt?"[o]=":".push")+"({b:c(A,i,d),c:i,f:A})"},e:"u.sort(I);l=u.length;while(l--)u[l]=u[l].f"}),jn=a(tn,{a:"d,aa",q:"var t=[];K(aa,function(A,q){t.push(q)});var cc=t.length",i:"for(var q,Z=true,s=0;s<cc;s++){q=t[s];if(!(Z=A[q]===aa[q]))break}Z&&u.push(A)"}),Fn=a({r:i,s:i,a:"n",j:"n",q:"var M=arguments,l=M.length;if(l>1){for(var i=1;i<l;i++)u[M[i]]=F(u[M[i]],u);return u}",i:"if(T(u[i]))u[i]=F(u[i],u)"});s.VERSION="0.7.0",s.after=function(e,t){return 1>e?t():function(){if(1>--e)return t.apply Yt,s.defer=function(e){var n=nt.call(arguments,1);return wt(function(){return e.apply(t,n)},1)},s.delay=function(e,n){var r=nt.call(arguments,2);return wt(function(){return e.apply(t,r)},n)},s.difference=function(e){var t=[];if(!e)return t;for(var n=-1,r=e.length,i=Q.apply(_,arguments),i=o(i,r);++n<r;){var s=e[n];i(s)||t.push(s)}return t},s.escape=function(e){return e==r?"":(e+"").replace(V,h)},s.every=pn,s.extend=Zt,s.filter=dn,s.find=vn,s.first=S,s.flatten=x,s.forEach=mn,s.forIn=en,s.forOwn=tn,
(this,arguments)}},s.bind=_,s.bindAll=Fn,s.chain=function(e){return e=new o(e),e.__chain__=n,e},s.clone=S,s.compact=function(e){var t=[];if(!e)return t;for(var n=-1,r=e.length;++n<r;)e[n]&&t.push(e[n]);return t},s.compose=function(){var e=arguments;return function(){for(var t=arguments,n=e.length;n--;)t=[e[n].apply(this,t)];return t[0]}},s.contains=xn,s.countBy=Tn,s.debounce=function(e,t,n){function i(){a=r,n||(o=e.apply(u,s))}var s,o,u,a;return function(){var r=n&&!a;return s=arguments,u=this,Mt s.functions=nn,s.groupBy=gn,s.has=function(e,t){return e?Z.call(e,t):i},s.identity=A,s.indexOf=T,s.initial=function(e,t,n){return e?nt.call(e,0,-(t==r||n?1:t)):[]},s.intersection=function(e){var t=arguments.length,n=[],r=-1,i=e?e.length:0,s=[];e:for(;++r<i;){var u=e[r];if(0>T(s,u)){for(var a=1;a<t;a++)if(!(n[a]||(n[a]=o(arguments[a])))(u))continue e;s.push(u)}}return s},s.invert=Vt,s.invoke=yn,s.isArguments=v,s.isArray=$t,s.isBoolean=function(e){return e===n||e===i||rt.call(e)==pt},s.isDate=function(
(a),a=_t(i,t),r&&(o=e.apply(u,s)),o}},s.defaults=ln,s.defer=function(e){var n=pt.call(arguments,1);return _t(function(){return e.apply(t,n)},1)},s.delay=function(e,n){var r=pt.call(arguments,2);return _t(function(){return e.apply(t,r)},n)},s.difference=function(e){var t=[];if(!e)return t;for(var n=-1,r=e.length,i=ft.apply(t,arguments),i=u(i,r);++n<r;)i(e[n])||t.push(e[n]);return t},s.escape=function(e){return e==r?"":(e+"").replace(rt,h)},s.every=Nn,s.extend=cn,s.filter=Cn,s.find=kn,s.first=N,s.flatten= e){return rt.call(e)==dt},s.isElement=function(e){return e?1===e.nodeType:i},s.isEmpty=rn,s.isEqual=b,s.isFinite=function(e){return ot(e)&&rt.call(e)==vt},s.isFunction=m,s.isNaN=function(e){return rt.call(e)==vt&&e!=+e},s.isNull=function(e){return e===r},s.isNumber=function(e){return rt.call(e)==vt},s.isObject=function(e){return e?Ht[typeof e]:i},s.isPlainObject=Jt,s.isRegExp=function(e){return rt.call(e)==gt},s.isString=function(e){return rt.call(e)==yt},s.isUndefined=function(e){return e===t},s
C,s.forEach=Ln,s.forIn=hn,s.forOwn=pn,s.functions=dn,s.groupBy=An,s.has=function(e,t){return e?lt.call(e,t):i},s.identity=D,s.indexOf=k,s.initial=function(e,t,n){return e?pt.call(e,0,-(t==r||n?1:t)):[]},s.intersection=function(e){var t=[];if(!e)return t;var n,r=arguments.length,i=[],s=-1,o=e.length;e:for(;++s<o;)if(n=e[s],0>k(t,n)){for(var a=1;a<r;a++)if(!(i[a]||(i[a]=u(arguments[a])))(n))continue e;t.push(n)}return t},s.invert=vn,s.invoke=On,s.isArguments=b,s.isArray=un,s.isBoolean=function(e){return e=== .keys=sn,s.last=function(e,t,n){if(e){var i=e.length;return t==r||n?e[i-1]:nt.call(e,-t||i)}},s.lastIndexOf=function(e,t,n){var r=e?e.length:0;for("number"==typeof n&&(r=(0>n?at(0,r+n):ft(n,r-1))+1);r--;)if(e[r]===t)return r;return-1},s.lateBind=function(e,t){return a(t,e,nt.call(arguments,2))},s.map=bn,s.max=w,s.memoize=function(e,t){var n={};return function(){var r=t?t.apply(this,arguments):arguments[0];return Z.call(n,r)?n[r]:n[r]=e.apply(this,arguments)}},s.merge=on,s.min=function(e,t,n){var r=
n||e===i||dt.call(e)==Nt},s.isElement=function(e){return e?1===e.nodeType:i},s.isEmpty=mn,s.isEqual=x,s.isFinite=function(e){return yt(e)&&dt.call(e)==kt},s.isFunction=w,s.isNaN=function(e){return dt.call(e)==kt&&e!=+e},s.isNull=function(e){return e===r},s.isObject=function(e){return e?Kt[typeof e]:i},s.isUndefined=function(e){return e===t},s.keys=gn,s.last=function(e,t,n){if(e){var i=e.length;return t==r||n?e[i-1]:pt.call(e,-t||i)}},s.lastIndexOf=function(e,t,n){if(!e)return-1;var r=e.length;for( Infinity,i=-1,s=e?e.length:0,o=r;if(t||s!==+s)t=f(t,n),mn(e,function(e,n,i){n=t(e,n,i),n<r&&(r=n,o=e)});else for(;++i<s;)e[i]<o&&(o=e[i]);return o},s.mixin=O,s.noConflict=function(){return e._=B,this},s.object=function(e,t){for(var n=-1,r=e?e.length:0,i={};++n<r;){var s=e[n];t?i[s]=t[n]:i[s[0]]=s[1]}return i},s.omit=un,s.once=function(e){var t,s=i;return function(){return s?t:(s=n,t=e.apply(this,arguments),e=r,t)}},s.pairs=an,s.partial=function(e){return a(e,nt.call(arguments,1))},s.pick=fn,s.pluck=
n&&"number"==typeof n&&(r=(0>n?wt(0,r+n):Et(n,r-1))+1);r--;)if(e[r]===t)return r;return-1},s.map=Mn,s.max=L,s.memoize=function(e,t){var n={};return function(){var r=t?t.apply(this,arguments):arguments[0];return lt.call(n,r)?n[r]:n[r]=e.apply(this,arguments)}},s.merge=yn,s.min=function(e,t,n){var r=Infinity,i=r;if(!e)return i;var s=-1,o=e.length;if(!t){for(;++s<o;)e[s]<i&&(i=e[s]);return i}for(n&&(t=p(t,n));++s<o;)n=t(e[s],s,e),n<r&&(r=n,i=e[s]);return i},s.mixin=P,s.noConflict=function(){return e wn,s.random=function(e,t){return e==r&&t==r&&(t=1),e=+e||0,t==r&&(t=e,e=0),e+G(lt()*((+t||0)-e+1))},s.range=function(e,t,n){e=+e||0,n=+n||1,t==r&&(t=e,e=0);for(var i=-1,t=at(0,K((t-e)/n)),s=Array(t);++i<t;)s[i]=e,e+=n;return s},s.reduce=En,s.reduceRight=E,s.reject=Sn,s.rest=N,s.result=function(e,t){var n=e?e[t]:r;return m(n)?e[t]():n},s.shuffle=function(e){var t=-1,n=Array(e?e.length:0);return mn(e,function(e){var r=G(lt()*(++t+1));n[t]=n[r],n[r]=e}),n},s.size=function(e){var t=e?e.length:0;return t===+
._=$,this},s.object=function(e,t){if(!e)return{};for(var n=-1,r=e.length,i={};++n<r;)t?i[e[n]]=t[n]:i[e[n][0]]=e[n][1];return i},s.omit=bn,s.once=function(e){var t,s=i;return function(){return s?t:(s=n,t=e.apply(this,arguments),e=r,t)}},s.pairs=wn,s.partial=function(e){var t=pt.call(arguments,1),n=t.length;return function(){var r;return r=arguments,r.length&&(t.length=n,ct.apply(t,r)),r=1==t.length?e.call(this,t[0]):e.apply(this,t),t.length=n,r}},s.pick=En,s.pluck=_n,s.random=function(e,t){return e== t?t:sn(e).length},s.some=xn,s.sortBy=Tn,s.sortedIndex=C,s.tap=function(e,t){return t(e),e},s.template=function(e,t,n){e||(e=""),n||(n={});var r,i,o=0,u=s.templateSettings,a="__p += '",f=n.variable||u.variable,l=f;e.replace(RegExp((n.escape||u.escape||X).source+"|"+(n.interpolate||u.interpolate||X).source+"|"+(n.evaluate||u.evaluate||X).source+"|$","g"),function(t,n,i,s,u){a+=e.slice(o,u).replace($,c),a+=n?"'+__e("+n+")+'":s?"';"+s+";__p+='":i?"'+((__t=("+i+"))==null?'':__t)+'":"",r||(r=s||j.test(
r&&t==r?St():(e=+e||0,t==r&&(t=e,e=0),e+mt(St()*((+t||0)-e+1)))},s.range=function(e,t,n){e=+e||0,n=+n||1,t==r&&(t=e,e=0);for(var i=-1,t=wt(0,Math.ceil((t-e)/n)),s=Array(t);++i<t;)s[i]=e,e+=n;return s},s.reduce=Dn,s.reduceRight=T,s.reject=Pn,s.rest=A,s.result=function(e,t){if(!e)return r;var n=e[t];return w(n)?e[t]():n},s.shuffle=function(e){if(!e)return[];for(var t,n=-1,r=e.length,i=Array(r);++n<r;)t=mt(St()*(n+1)),i[n]=i[t],i[t]=e[n];return i},s.size=function(e){if(!e)return 0;var t=e.length;return-1< n||i)),o=u+t.length}),a+="';",l||(f="obj",r?a="with("+f+"){"+a+"}":(n=RegExp("(\\(\\s*)"+f+"\\."+f+"\\b","g"),a=a.replace(z,"$&"+f+".").replace(n,"$1__d"))),a=(r?a.replace(I,""):a).replace(q,"$1").replace(R,"$1;"),a="function("+f+"){"+(l?"":f+"||("+f+"={});")+"var __t,__p='',__e=_.escape"+(r?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":(l?"":",__d="+f+"."+f+"||"+f)+";")+a+"return __p}";try{i=Function("_","return "+a)(s)}catch(h){throw h.source=a,h}return t?i(t):(i.source=
t&&t===t>>>0?t:gn(e).length},s.some=Hn,s.sortBy=Bn,s.sortedIndex=O,s.tap=function(e,t){return t(e),e},s.template=function(e,t,n){n||(n={});var e=e+"",o,u;o=n.escape;var a=n.evaluate,f=n.interpolate,h=s.templateSettings,p=n=n.variable||h.variable;o==r&&(o=h.escape),a==r&&(a=h.evaluate||i),f==r&&(f=h.interpolate),o&&(e=e.replace(o,v)),f&&(e=e.replace(f,g)),a!=H&&(H=a,F=RegExp("<e%-([\\s\\S]+?)%>|<e%=([\\s\\S]+?)%>"+(a?"|"+a.source:""),"g")),o=at.length,e=e.replace(F,m),o=o!=at.length,e="__p += '"+e a,i)},s.throttle=function(e,t){function n(){a=new Date,u=r,s=e.apply(o,i)}var i,s,o,u,a=0;return function(){var r=new Date,f=t-(r-a);return i=arguments,o=this,0>=f?(bt(u),a=r,s=e.apply(o,i)):u||(u=wt(n,f)),s}},s.times=function(e,t,n){for(var e=+e||0,r=-1,i=Array(e);++r<e;)i[r]=t.call(n,r);return i},s.toArray=function(e){if(!e)return[];var t=e.length;return t===+t?(Ct?rt.call(e)==yt:"string"==typeof e)?e.split(""):nt.call(e):ln(e)},s.unescape=function(e){return e==r?"":(e+"").replace(F,d)},s.union=
.replace(it,c).replace(nt,l)+"';",at.length=0,p||(n=B||"obj",o?e="with("+n+"){"+e+"}":(n!=B&&(B=n,j=RegExp("(\\(\\s*)"+n+"\\."+n+"\\b","g")),e=e.replace(et,"$&"+n+".").replace(j,"$1__d"))),e=(o?e.replace(Q,""):e).replace(G,"$1").replace(Y,"$1;"),e="function("+n+"){"+(p?"":n+"||("+n+"={});")+"var __t,__p='',__e=_.escape"+(o?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":(p?"":",__d="+n+"."+n+"||"+n)+";")+e+"return __p}";try{u=Function("_","return "+e)(s)}catch(d){throw d function(){for(var e=-1,t=Q.apply(_,arguments),n=t.length,r=[];++e<n;){var i=t[e];0>T(r,i)&&r.push(i)}return r},s.uniq=k,s.uniqueId=function(e){var t=P++;return e?e+t:t},s.values=ln,s.where=Nn,s.without=function(e){for(var t=-1,n=e?e.length:0,r=o(arguments,1,20),i=[];++t<n;){var s=e[t];r(s)||i.push(s)}return i},s.wrap=function(e,t){return function(){var n=[e];return arguments.length&&et.apply(n,arguments),t.apply(this,n)}},s.zip=function(e){for(var t=-1,n=e?w(wn(arguments,"length")):0,r=Array(n);++
.source=e,d}return t?u(t):(u.source=e,u)},s.throttle=function(e,t){function n(){a=new Date,u=r,s=e.apply(o,i)}var i,s,o,u,a=0;return function(){var r=new Date,f=t-(r-a);return i=arguments,o=this,0>=f?(a=r,s=e.apply(o,i)):u||(u=_t(n,f)),s}},s.times=function(e,t,n){var r=-1;if(n)for(;++r<e;)t.call(n,r);else for(;++r<e;)t(r)},s.toArray=function(e){if(!e)return[];if(e.toArray&&w(e.toArray))return e.toArray();var t=e.length;return-1<t&&t===t>>>0?(Ft?dt.call(e)==Ot:"string"==typeof e)?e.split(""):pt.call t<n;)r[t]=wn(arguments,t);return r},s.all=pn,s.any=xn,s.collect=bn,s.detect=vn,s.drop=N,s.each=mn,s.foldl=En,s.foldr=E,s.head=S,s.include=cn,s.inject=En,s.methods=nn,s.select=dn,s.tail=N,s.take=S,s.unique=k,O(s),s.prototype.chain=function(){return this.__chain__=n,this},s.prototype.value=function(){return this.__wrapped__},mn("pop push reverse shift sort splice unshift".split(" "),function(e){var t=_[e];s.prototype[e]=function(){var e=this.__wrapped__;return t.apply(e,arguments),St&&e.length===0&&delete
(e):Sn(e)},s.unescape=function(e){return e==r?"":(e+"").replace(K,y)},s.union=function(){for(var e=-1,t=[],n=ft.apply(t,arguments),r=n.length;++e<r;)0>k(t,n[e])&&t.push(n[e]);return t},s.uniq=M,s.uniqueId=function(e){var t=X++;return e?e+t:t},s.values=Sn,s.where=jn,s.without=function(e){var t=[];if(!e)return t;for(var n=-1,r=e.length,i=u(arguments,1,20);++n<r;)i(e[n])||t.push(e[n]);return t},s.wrap=function(e,t){return function(){var n=[e];return arguments.length&&ct.apply(n,arguments),t.apply(this e[0],this.__chain__&&(e=new s(e),e.__chain__=n),e}}),mn(["concat","join","slice"],function(e){var t=_[e];s.prototype[e]=function(){var e=t.apply(this.__wrapped__,arguments);return this.__chain__&&(e=new s(e),e.__chain__=n),e}}),typeof define=="function"&&typeof define.amd=="object"&&define.amd?(e._=s,define(function(){return s})):M?"object"==typeof module&&module&&module.exports==M?(module.exports=s)._=s:M._=s:e._=s})(this);
,n)}},s.zip=function(e){if(!e)return[];for(var t=-1,n=L(_n(arguments,"length")),r=Array(n);++t<n;)r[t]=_n(arguments,t);return r},s.all=Nn,s.any=Hn,s.collect=Mn,s.detect=kn,s.drop=A,s.each=Ln,s.foldl=Dn,s.foldr=T,s.head=N,s.include=xn,s.inject=Dn,s.methods=dn,s.select=Cn,s.tail=A,s.take=N,s.unique=M,Ln({Date:Ct,Number:kt,RegExp:At,String:Ot},function(e,t){s["is"+t]=function(t){return dt.call(t)==e}}),o.prototype=s.prototype,P(s),o.prototype.chain=function(){return this.__chain__=n,this},o.prototype
.value=function(){return this.__wrapped__},Ln("pop push reverse shift sort splice unshift".split(" "),function(e){var t=q[e];o.prototype[e]=function(){var e=this.__wrapped__;return t.apply(e,arguments),Pt&&e.length===0&&delete e[0],this.__chain__&&(e=new o(e),e.__chain__=n),e}}),Ln(["concat","join","slice"],function(e){var t=q[e];o.prototype[e]=function(){var e=t.apply(this.__wrapped__,arguments);return this.__chain__&&(e=new o(e),e.__chain__=n),e}}),typeof define=="function"&&typeof define.amd=="object"&&
define.amd?(e._=s,define(function(){return s})):I?"object"==typeof module&&module&&module.exports==I?(module.exports=s)._=s:I._=s:e._=s})(this);

36
lodash.underscore.min.js vendored Normal file
View File

@@ -0,0 +1,36 @@
/*!
Lo-Dash 0.8.2 lodash.com/license
Underscore.js 1.4.2 underscorejs.org/LICENSE
*/
;(function(e,t){function s(e,t,r){var s;if(!e)return i;var t=x(t,r),o=e.length,r=-1;if(o===+o){for(;++r<o;)if(s=e[r],t(s,r,e))return n}else for(r in e)if(it.call(e,r)&&(s=e[r],t(s,r,e)))return n;return i}function o(e,t,n,r){var s,o,u=n;if(!e)return u;var a=3>arguments.length,t=x(t,r),f=e.length;s=-1;if(f===+f)for(a&&(u=e[++s]);++s<f;)o=e[s],u=t(u,o,s,e);else for(s in e)it.call(e,s)&&(o=e[s],u=a?(a=i,o):t(u,o,s,e));return u}function u(e,t){var n,r,i=e||[];if(!e)return i;var s=e.length;n=-1;if(s===+
s)for(i=Array(s);++n<s;)r=e[n],i[n]=r[t];else for(n in i=[],e)it.call(e,n)&&(r=e[n],i.push(r[t]));return i}function a(e,t,n){var r,i=e||[];if(!e)return i;var t=x(t,n),s=e.length,n=-1;if(s===+s)for(i=Array(s);++n<s;)r=e[n],i[n]=t(r,n,e);else for(n in i=[],e)it.call(e,n)&&(r=e[n],i.push(t(r,n,e)));return i}function f(e,t,n){var r;if(!e)return e;var t=x(t,n),i=e.length,n=-1;if(i===+i)for(;++n<i;)r=e[n],t(r,n,e);else for(n in e)it.call(e,n)&&(r=e[n],t(r,n,e));return e}function l(e,n,r){var i,s=t;if(!
e)return s;var n=x(n,r),o=e.length,r=-1;if(o===+o){for(;++r<o;)if(i=e[r],n(i,r,e))return i}else for(r in e)if(it.call(e,r)&&(i=e[r],n(i,r,e)))return i;return s}function c(e,t,n){var r,i=[];if(!e)return i;var t=x(t,n),s=e.length,n=-1;if(s===+s)for(;++n<s;)r=e[n],t(r,n,e)&&i.push(r);else for(n in e)it.call(e,n)&&(r=e[n],t(r,n,e)&&i.push(r));return i}function h(e,t,r){var s;if(!e)return n;var t=x(t,r),o=e.length,r=-1;if(o===+o){for(;++r<o;)if(s=e[r],!t(s,r,e))return i}else for(r in e)if(it.call(e,r)&&
(s=e[r],!t(s,r,e)))return i;return n}function p(e,t){var r,s;if(!e)return i;var o=e.length;r=-1;if(o===+o){if(ut.call(e)==wt)return-1<e.indexOf(t);for(;++r<o;)if(s=e[r],s===t)return n}else for(r in e)if(it.call(e,r)&&(s=e[r],s===t))return n;return i}function d(e){var t,n,r=[];if(!e)return r;for(t in e)it.call(e,t)&&(n=e[t],r.push(n));return r}function v(e){var t,n,r=[];if(!e)return r;for(t in e)n=e[t],L(n)&&r.push(t);return r.sort(),r}function m(e,t,n){var r;if(!e)return e;t=x(t,n);for(r in e)n=e
[r],t(n,r,e);return e}function g(e){var t,n,r=e;if(!e)return e;for(var i=1,s=arguments.length;i<s;i++)if(r=arguments[i])for(t in r)n=r[t],e[t]=n;return e}function y(e){var t,n=[];if(!e)return n;for(t in e)it.call(e,t)&&n.push(t);return n}function b(e){var t,n,r={};if(!e)return r;for(t in e)it.call(e,t)&&(n=e[t],r[n]=t);return r}function w(e){if(e&&e.__wrapped__)return e;if(!(this instanceof w))return new w(e);this.__wrapped__=e}function E(e,n){var r=e.b,i=n.b,e=e.a,n=n.a;if(e!==n){if(e>n||e===t)return 1
;if(e<n||n===t)return-1}return r<i?-1:1}function S(e,t,n){function r(){var i=arguments,s=t;return n.length&&(i=i.length?n.concat(ot.call(i)):n),this instanceof r?(C.prototype=e.prototype,s=new C,(i=e.apply(s,i))&&Tt[typeof i]?i:s):e.apply(s,i)}return r}function x(e,n){return e?"function"!=typeof e?function(t){return t[e]}:n!==t?function(t,r,i){return e.call(n,t,r,i)}:e:q}function T(e){return"\\"+Nt[e]}function N(e){return kt[e]}function C(){}function k(e){return Lt[e]}function L(e){return"function"==typeof
e}function A(e){var t=i;if(!e||"object"!=typeof e||isArguments(e))return t;var n=e.constructor;return!L(n)||n instanceof n?(m(e,function(e,n){t=n}),t===i||it.call(e,t)):t}function O(e,t,s,o){if(e==r||t==r)return e===t;if(e===t)return 0!==e||1/e==1/t;if(Tt[typeof e]||Tt[typeof t])e=e.__wrapped__||e,t=t.__wrapped__||t;var u=ut.call(e);if(u!=ut.call(t))return i;switch(u){case vt:case mt:return+e==+t;case gt:return e!=+e?t!=+t:0==e?1/e==1/t:e==+t;case bt:case wt:return e==t+""}var a=Ct(e);if(!a&&u!=yt
)return i;s||(s=[]),o||(o=[]);for(u=s.length;u--;)if(s[u]==e)return o[u]==t;var f=n,u=0;s.push(e),o.push(t);if(a){u=e.length;if(f=u==t.length)for(;u--&&(f=O(e[u],t[u],s,o)););return f}a=e.constructor,f=t.constructor;if(a!=f&&(!L(a)||!(a instanceof a&&L(f)&&f instanceof f)))return i;for(var l in e)if(it.call(e,l)&&(u++,!it.call(t,l)||!O(e[l],t[l],s,o)))return i;for(l in t)if(it.call(t,l)&&!(u--))return i;return n}function M(e,t,n){var r=-Infinity,i=-1,s=e?e.length:0,o=r;if(t||s!==+s)t=x(t,n),f(e,function(
e,n,i){n=t(e,n,i),n>r&&(r=n,o=e)});else for(;++i<s;)e[i]>o&&(o=e[i]);return o}function _(e,t,n,r){var s=e?e.length:0,o=3>arguments.length;if(s!==+s)var u=At(e),s=u.length;return f(e,function(a,f,l){f=u?u[--s]:--s,n=o?(o=i,e[f]):t.call(r,n,e[f],f,l)}),n}function D(e,t,n){if(e)return t==r||n?e[0]:ot.call(e,0,t)}function P(e,t){for(var n=-1,r=e?e.length:0,i=[];++n<r;){var s=e[n];Ct(s)?st.apply(i,t?s:P(s)):i.push(s)}return i}function H(e,t,n){var r=-1,i=e?e.length:0;if("number"==typeof n)r=(0>n?ht(0,
i+n):n||0)-1;else if(n)return r=j(e,t),e[r]===t?r:-1;for(;++r<i;)if(e[r]===t)return r;return-1}function B(e,t,n){return e?ot.call(e,t==r||n?1:t):[]}function j(e,t,n,r){var i=0,s=e?e.length:i;if(n){n=x(n,r);for(t=n(t);i<s;)r=i+s>>>1,n(e[r])<t?i=r+1:s=r}else for(;i<s;)r=i+s>>>1,e[r]<t?i=r+1:s=r;return i}function F(e,t,n,r){var s=-1,o=e?e.length:0,u=[],a=[];"function"==typeof t&&(r=n,n=t,t=i);for(n=x(n,r);++s<o;)if(r=n(e[s],s,e),t?!s||a[a.length-1]!==r:0>H(a,r))a.push(r),u.push(e[s]);return u}function I
(e,t){return xt||at&&2<arguments.length?at.call.apply(at,arguments):S(e,t,ot.call(arguments,2))}function q(e){return e}function R(e){f(v(e),function(t){var r=w[t]=e[t];w.prototype[t]=function(){var e=[this.__wrapped__];return arguments.length&&st.apply(e,arguments),e=r.apply(w,e),this.__chain__&&(e=new w(e),e.__chain__=n),e}})}var n=!0,r=null,i=!1,U="object"==typeof exports&&exports&&("object"==typeof global&&global&&global==global.global&&(e=global),exports),z=Array.prototype,W=Object.prototype,
X=0,V=e._,$=/[-?+=!~*%&^<>|{(\/]|\[\D|\b(?:delete|in|instanceof|new|typeof|void)\b/,J=/&(?:amp|lt|gt|quot|#x27);/g,K=/(?:__e|__t=)\(\s*(?![\d\s"']|this\.)/g,Q=RegExp("^"+(W.valueOf+"").replace(/[.*+?^=!:${}()|[\]\/\\]/g,"\\$&").replace(/valueOf|for [^\]]+/g,".+?")+"$"),G=/($^)/,Y=/[&<>"']/g,Z=/['\n\r\t\u2028\u2029\\]/g,et=Math.ceil,tt=z.concat,nt=Math.floor,rt=Q.test(rt=Object.getPrototypeOf)&&rt,it=W.hasOwnProperty,st=z.push,ot=z.slice,ut=W.toString,at=Q.test(at=ot.bind)&&at,ft=Q.test(ft=Array.isArray
)&&ft,lt=e.isFinite,ct=Q.test(ct=Object.keys)&&ct,ht=Math.max,pt=Math.min,dt=Math.random,vt="[object Boolean]",mt="[object Date]",gt="[object Number]",yt="[object Object]",bt="[object RegExp]",wt="[object String]",Et=e.clearTimeout,St=e.setTimeout,xt=at&&/\n|Opera/.test(at+ut.call(e.opera)),Tt={"boolean":i,"function":n,object:n,number:i,string:i,"undefined":i,unknown:n},Nt={"\\":"\\","'":"'","\n":"n","\r":"r"," ":"t","\u2028":"u2028","\u2029":"u2029"};w.templateSettings={escape:/<%-([\s\S]+?)%>/g
,evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,variable:""},w.isArguments=function(e){return"[object Arguments]"==ut.call(e)},w.isArguments(arguments)||(w.isArguments=function(e){return e?it.call(e,"callee"):i});var Ct=ft||function(e){return"[object Array]"==ut.call(e)};L(/x/)&&(L=function(e){return"[object Function]"==ut.call(e)});var W=rt?function(e){if(!e||"object"!=typeof e)return i;var t=e.valueOf,n="function"==typeof t&&(n=rt(t))&&rt(n);return n?e==n||rt(e)==n&&!isArguments(e):A
(e)}:A,kt={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;"},Lt=b(kt),At=ct?function(e){return e&&Tt[typeof e]?ct(e):[]}:y;w.VERSION="0.8.2",w.after=function(e,t){return 1>e?t():function(){if(1>--e)return t.apply(this,arguments)}},w.bind=I,w.bindAll=function(e){var t,n,r=e,i=e;if(!e)return i;n=arguments,t=0;var s=n.length;if(1<s){for(;++t<s;)i[n[t]]=I(i[n[t]],i);return i}for(t in r)n=r[t],L(n)&&(i[t]=I(n,i));return i},w.chain=function(e){return e=new w(e),e.__chain__=n,e},w.clone=function(
e){return e&&Tt[typeof e]?Ct(e)?ot.call(e):g({},e):e},w.compact=function(e){for(var t=-1,n=e?e.length:0,r=[];++t<n;){var i=e[t];i&&r.push(i)}return r},w.compose=function(){var e=arguments;return function(){for(var t=arguments,n=e.length;n--;)t=[e[n].apply(this,t)];return t[0]}},w.contains=p,w.countBy=function(e,t,n){var r,i={};if(!e)return i;var t=x(t,n),s=e.length,n=-1;if(s===+s)for(;++n<s;)r=e[n],r=t(r,n,e),it.call(i,r)?i[r]++:i[r]=1;else for(n in e)it.call(e,n)&&(r=e[n],r=t(r,n,e),it.call(i,r)?
i[r]++:i[r]=1);return i},w.debounce=function(e,t,n){function i(){a=r,n||(o=e.apply(u,s))}var s,o,u,a;return function(){var r=n&&!a;return s=arguments,u=this,Et(a),a=St(i,t),r&&(o=e.apply(u,s)),o}},w.defaults=function(e){var t,n,i=e;if(!e)return e;for(var s=1,o=arguments.length;s<o;s++)if(i=arguments[s])for(t in i)n=i[t],e[t]==r&&(e[t]=n);return e},w.defer=function(e){var n=ot.call(arguments,1);return St(function(){return e.apply(t,n)},1)},w.delay=function(e,n){var r=ot.call(arguments,2);return St
(function(){return e.apply(t,r)},n)},w.difference=function(e){for(var t=-1,n=e.length,r=tt.apply(z,arguments),i=[];++t<n;){var s=e[t];0>H(r,s,n)&&i.push(s)}return i},w.escape=function(e){return e==r?"":(e+"").replace(Y,N)},w.every=h,w.extend=g,w.filter=c,w.find=l,w.first=D,w.flatten=P,w.forEach=f,w.forIn=m,w.forOwn=function(e,t,n){var r;if(!e)return e;t=x(t,n);for(r in e)it.call(e,r)&&(n=e[r],t(n,r,e));return e},w.functions=v,w.groupBy=function(e,t,n){var r,i={};if(!e)return i;var t=x(t,n),s=e.length
,n=-1;if(s===+s)for(;++n<s;){r=e[n];var o=t(r,n,e);(it.call(i,o)?i[o]:i[o]=[]).push(r)}else for(n in e)it.call(e,n)&&(r=e[n],o=t(r,n,e),(it.call(i,o)?i[o]:i[o]=[]).push(r));return i},w.has=function(e,t){return e?it.call(e,t):i},w.identity=q,w.indexOf=H,w.initial=function(e,t,n){return e?ot.call(e,0,-(t==r||n?1:t)):[]},w.intersection=function(e){var t=arguments.length,n=-1,r=e.length,i=[];e:for(;++n<r;){var s=e[n];if(0>H(i,s)){for(var o=1;o<t;o++)if(0>H(arguments[o],s))continue e;i.push(s)}}return i
},w.invert=b,w.invoke=function(e,t){var n,r,i=e,s=e||[];if(!e)return s;var o=ot.call(arguments,2),u="function"==typeof t,a=i.length;n=-1;if(a===+a)for(s=Array(a);++n<a;)r=i[n],s[n]=(u?t:r[t]).apply(r,o);else for(n in s=[],i)it.call(i,n)&&(r=i[n],s.push((u?t:r[t]).apply(r,o)));return s},w.isArray=Ct,w.isBoolean=function(e){return e===n||e===i||ut.call(e)==vt},w.isDate=function(e){return ut.call(e)==mt},w.isElement=function(e){return e?1===e.nodeType:i},w.isEmpty=function(e){var t;if(!e)return n;var r=
ut.call(e),s=e.length;if(Ct(e)||r==wt||r==yt&&s===+s&&L(e.splice))return!s;for(t in e)if(it.call(e,t))return i;return n},w.isEqual=O,w.isFinite=function(e){return lt(e)&&ut.call(e)==gt},w.isFunction=L,w.isNaN=function(e){return ut.call(e)==gt&&e!=+e},w.isNull=function(e){return e===r},w.isNumber=function(e){return ut.call(e)==gt},w.isObject=function(e){return e?Tt[typeof e]:i},w.isPlainObject=W,w.isRegExp=function(e){return ut.call(e)==bt},w.isString=function(e){return ut.call(e)==wt},w.isUndefined=
function(e){return e===t},w.keys=At,w.last=function(e,t,n){if(e){var i=e.length;return t==r||n?e[i-1]:ot.call(e,-t||i)}},w.lastIndexOf=function(e,t,n){var r=e?e.length:0;for("number"==typeof n&&(r=(0>n?ht(0,r+n):pt(n,r-1))+1);r--;)if(e[r]===t)return r;return-1},w.map=a,w.max=M,w.memoize=function(e,t){var n={};return function(){var r=t?t.apply(this,arguments):arguments[0];return it.call(n,r)?n[r]:n[r]=e.apply(this,arguments)}},w.min=function(e,t,n){var r=Infinity,i=-1,s=e?e.length:0,o=r;if(t||s!==+
s)t=x(t,n),f(e,function(e,n,i){n=t(e,n,i),n<r&&(r=n,o=e)});else for(;++i<s;)e[i]<o&&(o=e[i]);return o},w.mixin=R,w.noConflict=function(){return e._=V,this},w.object=function(e,t){for(var n=-1,r=e?e.length:0,i={};++n<r;){var s=e[n];t?i[s]=t[n]:i[s[0]]=s[1]}return i},w.omit=function(e,t,n){var r,i,s=e,o={};if(!e)return o;var u="function"==typeof t;if(u)t=x(t,n);else var a=tt.apply(z,arguments);for(r in s)if(i=s[r],u?!t(i,r,e):0>H(a,r))o[r]=i;return o},w.once=function(e){var t,s=i;return function(){
return s?t:(s=n,t=e.apply(this,arguments),e=r,t)}},w.pairs=function(e){var t,n,r=[];if(!e)return r;for(t in e)it.call(e,t)&&(n=e[t],r.push([t,n]));return r},w.pick=function(e,t,n){var r,i,s=e,o={};if(!e)return o;if("function"!=typeof t){r=0,i=tt.apply(z,arguments);for(s=i.length;++r<s;){var u=i[r];u in e&&(o[u]=e[u])}}else for(r in t=x(t,n),s)i=s[r],t(i,r,e)&&(o[r]=i);return o},w.pluck=u,w.random=function(e,t){return e==r&&t==r&&(t=1),e=+e||0,t==r&&(t=e,e=0),e+nt(dt()*((+t||0)-e+1))},w.range=function(
e,t,n){e=+e||0,n=+n||1,t==r&&(t=e,e=0);for(var i=-1,t=ht(0,et((t-e)/n)),s=Array(t);++i<t;)s[i]=e,e+=n;return s},w.reduce=o,w.reduceRight=_,w.reject=function(e,t,n){var r,i=[];if(!e)return i;var t=x(t,n),s=e.length,n=-1;if(s===+s)for(;++n<s;)r=e[n],!t(r,n,e)&&i.push(r);else for(n in e)it.call(e,n)&&(r=e[n],!t(r,n,e)&&i.push(r));return i},w.rest=B,w.result=function(e,t){var n=e?e[t]:r;return L(n)?e[t]():n},w.shuffle=function(e){var t=-1,n=Array(e?e.length:0);return f(e,function(e){var r=nt(dt()*(++
t+1));n[t]=n[r],n[r]=e}),n},w.size=function(e){var t=e?e.length:0;return t===+t?t:At(e).length},w.some=s,w.sortBy=function(e,t,n){var r,i=e||[];if(!e)return i;var t=x(t,n),s=e.length,n=-1;if(s===+s)for(i=Array(s);++n<s;)r=e[n],i[n]={a:t(r,n,e),b:n,c:r};else for(n in i=[],e)it.call(e,n)&&(r=e[n],i.push({a:t(r,n,e),b:n,c:r}));i.sort(E);for(s=i.length;s--;)i[s]=i[s].c;return i},w.sortedIndex=j,w.tap=function(e,t){return t(e),e},w.template=function(e,t,n){e||(e=""),n||(n={});var r,i,s=0,o=w.templateSettings
,u="__p += '",a=n.variable||o.variable,f=a;e.replace(RegExp((n.escape||o.escape||G).source+"|"+(n.interpolate||o.interpolate||G).source+"|"+(n.evaluate||o.evaluate||G).source+"|$","g"),function(t,n,i,o,a){u+=e.slice(s,a).replace(Z,T),u+=n?"'+__e("+n+")+'":o?"';"+o+";__p+='":i?"'+((__t=("+i+"))==null?'':__t)+'":"",r||(r=o||$.test(n||i)),s=a+t.length}),u+="';",f||(a="obj",r?u="with("+a+"){"+u+"}":(n=RegExp("(\\(\\s*)"+a+"\\."+a+"\\b","g"),u=u.replace(K,"$&"+a+".").replace(n,"$1__d"))),u="function("+
a+"){"+(f?"":a+"||("+a+"={});")+"var __t,__p='',__e=_.escape"+(r?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":(f?"":",__d="+a+"."+a+"||"+a)+";")+u+"return __p}";try{i=Function("_","return "+u)(w)}catch(l){throw l.source=u,l}return t?i(t):(i.source=u,i)},w.throttle=function(e,t){function n(){a=new Date,u=r,s=e.apply(o,i)}var i,s,o,u,a=0;return function(){var r=new Date,f=t-(r-a);return i=arguments,o=this,0>=f?(Et(u),a=r,s=e.apply(o,i)):u||(u=St(n,f)),s}},w.times=function(
e,t,n){for(var e=+e||0,r=-1,i=Array(e);++r<e;)i[r]=t.call(n,r);return i},w.toArray=function(e){if(!e)return[];var t=e.length;return t===+t?"string"==typeof e?e.split(""):ot.call(e):d(e)},w.unescape=function(e){return e==r?"":(e+"").replace(J,k)},w.union=function(){for(var e=-1,t=tt.apply(z,arguments),n=t.length,r=[];++e<n;){var i=t[e];0>H(r,i)&&r.push(i)}return r},w.uniq=F,w.uniqueId=function(e){var t=X++;return e?e+t:t},w.values=d,w.where=function(e,t){var r,i,s=[];if(!e)return s;var o=[];m(t,function(
e,t){o.push(t)});var u=o.length,a=e.length;r=-1;if(a===+a)for(;++r<a;){i=e[r];for(var f=n,l=0;l<u&&(f=o[l],f=i[f]===t[f]);l++);f&&s.push(i)}else for(r in e)if(it.call(e,r)){i=e[r],f=n;for(l=0;l<u&&(f=o[l],f=i[f]===t[f]);l++);f&&s.push(i)}return s},w.without=function(e){for(var t=-1,n=e.length,r=[];++t<n;){var i=e[t];0>H(arguments,i,1)&&r.push(i)}return r},w.wrap=function(e,t){return function(){var n=[e];return arguments.length&&st.apply(n,arguments),t.apply(this,n)}},w.zip=function(e){for(var t=-1
,n=e?M(u(arguments,"length")):0,r=Array(n);++t<n;)r[t]=u(arguments,t);return r},w.all=h,w.any=s,w.collect=a,w.detect=l,w.drop=B,w.each=f,w.foldl=o,w.foldr=_,w.head=D,w.include=p,w.inject=o,w.methods=v,w.select=c,w.tail=B,w.take=D,w.unique=F,R(w),w.prototype.chain=function(){return this.__chain__=n,this},w.prototype.value=function(){return this.__wrapped__},f("pop push reverse shift sort splice unshift".split(" "),function(e){var t=z[e];w.prototype[e]=function(){var e=this.__wrapped__;return t.apply
(e,arguments),this.__chain__&&(e=new w(e),e.__chain__=n),e}}),f(["concat","join","slice"],function(e){var t=z[e];w.prototype[e]=function(){var e=t.apply(this.__wrapped__,arguments);return this.__chain__&&(e=new w(e),e.__chain__=n),e}}),U?"object"==typeof module&&module&&module.exports==U?(module.exports=w)._=w:U._=w:e._=w})(this);

View File

@@ -1,9 +1,9 @@
{ {
"name": "lodash", "name": "lodash",
"version": "0.7.0", "version": "0.8.2",
"description": "A drop-in replacement for Underscore.js delivering performance, bug fixes, and additional features.", "description": "A drop-in replacement for Underscore.js delivering performance, bug fixes, and additional features.",
"homepage": "http://lodash.com", "homepage": "http://lodash.com",
"main": "lodash", "main": "./lodash",
"keywords": [ "keywords": [
"browser", "browser",
"client", "client",
@@ -43,10 +43,10 @@
"rhino" "rhino"
], ],
"jam": { "jam": {
"main": "./lodash.min.js" "main": "./lodash.js"
}, },
"scripts": { "scripts": {
"build": "node build", "build": "node build",
"test": "node test/test" "test": "node test/test && node test/test-build"
} }
} }

View File

@@ -276,7 +276,6 @@
var twentyFiveValues = Array(25),\ var twentyFiveValues = Array(25),\
twentyFiveValues2 = Array(25),\ twentyFiveValues2 = Array(25),\
fiftyValues = Array(50),\ fiftyValues = Array(50),\
fiftyValues2 = Array(50),\
seventyFiveValues = Array(75),\ seventyFiveValues = Array(75),\
seventyFiveValues2 = Array(75),\ seventyFiveValues2 = Array(75),\
lowerChars = "abcdefghijklmnopqrstuvwxyz".split(""),\ lowerChars = "abcdefghijklmnopqrstuvwxyz".split(""),\
@@ -294,14 +293,11 @@
}\ }\
fiftyValues[index] =\ fiftyValues[index] =\
seventyFiveValues[index] = lowerChars[index];\ seventyFiveValues[index] = lowerChars[index];\
\
fiftyValues2[index] =\
seventyFiveValues2[index] = upperChars[index];\ seventyFiveValues2[index] = upperChars[index];\
}\ }\
else {\ else {\
if (index < 50) {\ if (index < 50) {\
fiftyValues[index] = index;\ fiftyValues[index] = index;\
fiftyValues2[index] = index + (index < 40 ? 75 : 0);\
}\ }\
seventyFiveValues[index] = index;\ seventyFiveValues[index] = index;\
seventyFiveValues2[index] = index + (index < 60 ? 75 : 0);\ seventyFiveValues2[index] = index + (index < 60 ? 75 : 0);\
@@ -575,13 +571,13 @@
); );
suites.push( suites.push(
Benchmark.Suite('`_.difference` iterating 50 elements') Benchmark.Suite('`_.difference` iterating 50 and 75 elements')
.add('Lo-Dash', { .add('Lo-Dash', {
'fn': 'lodash.difference(fiftyValues, fiftyValues2)', 'fn': 'lodash.difference(fiftyValues, seventyFiveValues2)',
'teardown': 'function multiArrays(){}' 'teardown': 'function multiArrays(){}'
}) })
.add('Underscore', { .add('Underscore', {
'fn': '_.difference(fiftyValues, fiftyValues2)', 'fn': '_.difference(fiftyValues, seventyFiveValues2)',
'teardown': 'function multiArrays(){}' 'teardown': 'function multiArrays(){}'
}) })
); );
@@ -839,13 +835,13 @@
); );
suites.push( suites.push(
Benchmark.Suite('`_.intersection` iterating 50 elements') Benchmark.Suite('`_.intersection` iterating 50 and 75 elements')
.add('Lo-Dash', { .add('Lo-Dash', {
'fn': 'lodash.intersection(fiftyValues, fiftyValues2)', 'fn': 'lodash.intersection(fiftyValues, seventyFiveValues2)',
'teardown': 'function multiArrays(){}' 'teardown': 'function multiArrays(){}'
}) })
.add('Underscore', { .add('Underscore', {
'fn': '_.intersection(fiftyValues, fiftyValues2)', 'fn': '_.intersection(fiftyValues, seventyFiveValues2)',
'teardown': 'function multiArrays(){}' 'teardown': 'function multiArrays(){}'
}) })
); );
@@ -1152,6 +1148,74 @@
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.reduce` iterating an array')
.add('Lo-Dash', '\
lodash.reduce(numbers, function(result, value, index) {\
result[index] = value;\
return result;\
}, {});'
)
.add('Underscore', '\
_.reduce(numbers, function(result, value, index) {\
result[index] = value;\
return result;\
}, {});'
)
);
suites.push(
Benchmark.Suite('`_.reduce` iterating an object')
.add('Lo-Dash', '\
lodash.reduce(object, function(result, value, key) {\
result.push([key, value]);\
return result;\
}, []);'
)
.add('Underscore', '\
_.reduce(object, function(result, value, key) {\
result.push([key, value]);\
return result;\
}, []);'
)
);
/*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.reduceRight` iterating an array')
.add('Lo-Dash', '\
lodash.reduceRight(numbers, function(result, value, index) {\
result[index] = value;\
return result;\
}, {});'
)
.add('Underscore', '\
_.reduceRight(numbers, function(result, value, index) {\
result[index] = value;\
return result;\
}, {});'
)
);
suites.push(
Benchmark.Suite('`_.reduceRight` iterating an object')
.add('Lo-Dash', '\
lodash.reduceRight(object, function(result, value, key) {\
result.push([key, value]);\
return result;\
}, []);'
)
.add('Underscore', '\
_.reduceRight(object, function(result, value, key) {\
result.push([key, value]);\
return result;\
}, []);'
)
);
/*--------------------------------------------------------------------------*/
suites.push( suites.push(
Benchmark.Suite('`_.shuffle`') Benchmark.Suite('`_.shuffle`')
.add('Lo-Dash', '\ .add('Lo-Dash', '\
@@ -1412,6 +1476,18 @@
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
suites.push(
Benchmark.Suite('`_.where`')
.add('Lo-Dash', '\
lodash.where(objects, { "num": 9 });'
)
.add('Underscore', '\
_.where(objects, { "num": 9 });'
)
);
/*--------------------------------------------------------------------------*/
suites.push( suites.push(
Benchmark.Suite('`_.without`') Benchmark.Suite('`_.without`')
.add('Lo-Dash', '\ .add('Lo-Dash', '\
@@ -1435,13 +1511,13 @@
); );
suites.push( suites.push(
Benchmark.Suite('`_.without` iterating an array of 50 elements') Benchmark.Suite('`_.without` iterating an array of 75 and 50 elements')
.add('Lo-Dash', { .add('Lo-Dash', {
'fn': 'lodash.without.apply(lodash, [fiftyValues].concat(fiftyValues2));', 'fn': 'lodash.without.apply(lodash, [seventyFiveValues2].concat(fiftyValues));',
'teardown': 'function multiArrays(){}' 'teardown': 'function multiArrays(){}'
}) })
.add('Underscore', { .add('Underscore', {
'fn': '_.without.apply(_, [fiftyValues].concat(fiftyValues2));', 'fn': '_.without.apply(_, [seventyFiveValues2].concat(fiftyValues));',
'teardown': 'function multiArrays(){}' 'teardown': 'function multiArrays(){}'
}) })
); );

View File

@@ -44,6 +44,7 @@
<script> <script>
// load Lo-Dash as a module // load Lo-Dash as a module
var lodashModule, var lodashModule,
shimmedModule,
underscoreModule; underscoreModule;
window.require && require({ window.require && require({
@@ -51,15 +52,25 @@
'urlArgs': 't=' + (+new Date), 'urlArgs': 't=' + (+new Date),
'paths': { 'paths': {
'lodash': '../../' + QUnit.config.lodashFilename, 'lodash': '../../' + QUnit.config.lodashFilename,
'underscore': './../../' + QUnit.config.lodashFilename 'shimmed': './../../' + QUnit.config.lodashFilename,
'underscore': '../underscore/../../' + QUnit.config.lodashFilename
},
'shim': {
'shimmed': {
'exports': '_'
}
} }
}, },
['lodash', 'underscore'], function(lodash, underscore) { ['lodash', 'shimmed', 'underscore'], function(lodash, shimmed, underscore) {
if (lodash.noConflict) { if (lodash && lodash.noConflict) {
lodashModule = lodash.noConflict(); lodashModule = lodash.noConflict();
lodashModule.moduleName = 'lodash'; lodashModule.moduleName = 'lodash';
} }
if (underscore.noConflict) { if (shimmed.noConflict) {
shimmedModule = shimmed.noConflict();
shimmedModule.moduleName = 'shimmed';
}
if (underscore && underscore.noConflict) {
underscoreModule = underscore.noConflict(); underscoreModule = underscore.noConflict();
underscoreModule.moduleName = 'underscore'; underscoreModule.moduleName = 'underscore';
} }

3
test/template/a.jst Normal file
View File

@@ -0,0 +1,3 @@
<ul>
<% _.forEach(people, function(name) { %><li><%= name %></li><% }); %>
</ul>

1
test/template/b.jst Normal file
View File

@@ -0,0 +1 @@
<% print("Hello " + epithet); %>.

1
test/template/c.tpl Normal file
View File

@@ -0,0 +1 @@
Hello {{ name }}!

View File

@@ -69,12 +69,9 @@
'intersection', 'intersection',
'last', 'last',
'lastIndexOf', 'lastIndexOf',
'max',
'min',
'object', 'object',
'range', 'range',
'rest', 'rest',
'shuffle',
'sortedIndex', 'sortedIndex',
'tail', 'tail',
'take', 'take',
@@ -113,11 +110,14 @@
'inject', 'inject',
'invoke', 'invoke',
'map', 'map',
'max',
'min',
'pluck', 'pluck',
'reduce', 'reduce',
'reduceRight', 'reduceRight',
'reject', 'reject',
'select', 'select',
'shuffle',
'size', 'size',
'some', 'some',
'sortBy', 'sortBy',
@@ -134,6 +134,7 @@
'debounce', 'debounce',
'defer', 'defer',
'delay', 'delay',
'lateBind',
'memoize', 'memoize',
'once', 'once',
'partial', 'partial',
@@ -164,6 +165,7 @@
'isNull', 'isNull',
'isNumber', 'isNumber',
'isObject', 'isObject',
'isPlainObject',
'isRegExp', 'isRegExp',
'isString', 'isString',
'isUndefined', 'isUndefined',
@@ -237,18 +239,12 @@
/** List of methods used by Underscore */ /** List of methods used by Underscore */
var underscoreMethods = _.without.apply(_, [allMethods].concat([ var underscoreMethods = _.without.apply(_, [allMethods].concat([
'countBy',
'forIn', 'forIn',
'forOwn', 'forOwn',
'invert', 'isPlainObject',
'lateBind',
'merge', 'merge',
'object', 'partial'
'omit',
'pairs',
'partial',
'random',
'unescape',
'where'
])); ]));
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
@@ -292,6 +288,31 @@
return realToAliasMap[funcName] || []; return realToAliasMap[funcName] || [];
} }
/**
* Gets the names of methods belonging to the given `category`.
*
* @private
* @param {String} category The category to filter by.
* @returns {Array} Returns a new array of method names belonging to the given category.
*/
function getMethodsByCategory(category) {
switch (category) {
case 'Arrays':
return arraysMethods.slice();
case 'Chaining':
return chainingMethods.slice();
case 'Collections':
return collectionsMethods.slice();
case 'Functions':
return functionsMethods.slice();
case 'Objects':
return objectsMethods.slice();
case 'Utilities':
return utilityMethods.slice();
}
return [];
}
/** /**
* Gets the real name, not alias, of a given function name. * Gets the real name, not alias, of a given function name.
* *
@@ -370,14 +391,16 @@
else if (functionsMethods.indexOf(methodName) > -1) { else if (functionsMethods.indexOf(methodName) > -1) {
if (methodName == 'after') { if (methodName == 'after') {
func(1, noop); func(1, noop);
} else if (methodName == 'bindAll') {
func({ 'noop': noop });
} else if (methodName == 'lateBind') {
func(lodash, 'identity', array, string);
} else if (/^(?:bind|partial)$/.test(methodName)) { } else if (/^(?:bind|partial)$/.test(methodName)) {
func(noop, object, array, string); func(noop, object, array, string);
} else if (/^(?:compose|memoize|wrap)$/.test(methodName)) { } else if (/^(?:compose|memoize|wrap)$/.test(methodName)) {
func(noop, noop); func(noop, noop);
} else if (/^(?:debounce|throttle)$/.test(methodName)) { } else if (/^(?:debounce|throttle)$/.test(methodName)) {
func(noop, 100); func(noop, 100);
} else if (methodName == 'bindAll') {
func({ 'noop': noop });
} else { } else {
func(noop); func(noop);
} }
@@ -413,102 +436,112 @@
console.log(e); console.log(e);
pass = false; pass = false;
} }
equal(pass, true, methodName + ': ' + message); equal(pass, true, '_.' + methodName + ': ' + message);
} }
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
QUnit.module('lodash build'); QUnit.module('minified AMD snippet');
(function() { (function() {
var commands = [ var start = _.once(QUnit.start);
'backbone',
'csp',
'legacy',
'mobile',
'strict',
'underscore',
'category=arrays',
'category=chaining',
'category=collections',
'category=functions',
'category=objects',
'category=utilities',
'exclude=union,uniq,zip',
'include=each,filter,map',
'category=collections,functions',
'underscore backbone',
'backbone legacy category=utilities exclude=first,last',
'underscore mobile strict category=functions exports=amd,global include=pick,uniq',
]
.concat(
allMethods.map(function(methodName) {
return 'include=' + methodName;
})
);
commands.forEach(function(command) { asyncTest('`lodash`', function() {
build(['-s'], function(source, filePath) {
// used by r.js build optimizer
var defineHasRegExp = /typeof\s+define\s*==(=)?\s*['"]function['"]\s*&&\s*typeof\s+define\.amd\s*==(=)?\s*['"]object['"]\s*&&\s*define\.amd/g,
basename = path.basename(filePath, '.js');
ok(!!defineHasRegExp.exec(source), basename);
start();
});
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('template builds');
(function() {
var templatePath = __dirname + '/template';
asyncTest('`lodash template=*.jst`', function() {
var start = _.after(2, _.once(QUnit.start)); var start = _.after(2, _.once(QUnit.start));
asyncTest('`lodash ' + command +'`', function() { build(['-s', 'template=' + templatePath + '/*.jst'], function(source, filePath) {
build(['--silent'].concat(command.split(' ')), function(source, filepath) { var basename = path.basename(filePath, '.js'),
var basename = path.basename(filepath, '.js'), context = createContext();
context = createContext(),
methodNames = [];
try { var data = {
vm.runInContext(source, context); 'a': { 'people': ['moe', 'larry', 'curly'] },
} catch(e) { } 'b': { 'epithet': 'stooge' }
};
if (/underscore/.test(command)) { context._ = _;
methodNames = underscoreMethods; vm.runInContext(source, context);
} var templates = context._.templates;
if (/backbone/.test(command)) {
methodNames = backboneDependencies;
}
if (/include/.test(command)) {
methodNames = methodNames.concat(command.match(/include=(\S*)/)[1].split(/, */));
}
if (/category/.test(command)) {
methodNames = command.match(/category=(\S*)/)[1].split(/, */).reduce(function(result, category) {
switch (category) {
case 'arrays':
return result.concat(arraysMethods);
case 'chaining':
return result.concat(chainingMethods);
case 'collections':
return result.concat(collectionsMethods);
case 'functions':
return result.concat(functionsMethods);
case 'objects':
return result.concat(objectsMethods);
case 'utilities':
return result.concat(utilityMethods);
}
return result;
}, methodNames);
}
if (!methodNames.length) {
methodNames = allMethods;
}
if (/exclude/.test(command)) { equal(templates.a(data.a).replace(/[\r\n]+/g, ''), '<ul><li>moe</li><li>larry</li><li>curly</li></ul>', basename);
methodNames = _.without.apply(_, [methodNames].concat( equal(templates.b(data.b), 'Hello stooge.', basename);
expandMethodNames(command.match(/exclude=(\S*)/)[1].split(/, */)) start();
)); });
} else { });
methodNames = expandMethodNames(methodNames);
}
var lodash = context._ || {}; asyncTest('`lodash settings=...`', function() {
methodNames = _.unique(methodNames); var start = _.after(2, _.once(QUnit.start));
methodNames.forEach(function(methodName) { build(['-s', 'template=' + templatePath + '/*.tpl', 'settings={interpolate:/\\{\\{([\\s\\S]+?)\\}\\}/}'], function(source, filePath) {
testMethod(lodash, methodName, basename); var basename = path.basename(filePath, '.js'),
}); context = createContext();
start(); var data = {
}); 'c': { 'name': 'Mustache' }
};
context._ = _;
vm.runInContext(source, context);
var templates = context._.templates;
equal(templates.c(data.c), 'Hello Mustache!', basename);
start();
});
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('independent builds');
(function() {
asyncTest('debug only', function() {
var start = _.once(QUnit.start);
build(['-d', '-s'], function(source, filePath) {
equal(path.basename(filePath, '.js'), 'lodash');
start();
});
});
asyncTest('debug custom', function () {
var start = _.once(QUnit.start);
build(['-d', '-s', 'backbone'], function(source, filePath) {
equal(path.basename(filePath, '.js'), 'lodash.custom');
start();
});
});
asyncTest('minified only', function() {
var start = _.once(QUnit.start);
build(['-m', '-s'], function(source, filePath) {
equal(path.basename(filePath, '.js'), 'lodash.min');
start();
});
});
asyncTest('minified custom', function () {
var start = _.once(QUnit.start);
build(['-m', '-s', 'backbone'], function(source, filePath) {
equal(path.basename(filePath, '.js'), 'lodash.custom.min');
start();
}); });
}); });
}()); }());
@@ -524,16 +557,15 @@
}); });
['non-strict', 'strict'].forEach(function(strictMode, index) { ['non-strict', 'strict'].forEach(function(strictMode, index) {
var start = _.after(2, _.once(QUnit.start));
asyncTest(strictMode + ' should ' + (index ? 'error': 'silently fail') + ' attempting to overwrite read-only properties', function() { asyncTest(strictMode + ' should ' + (index ? 'error': 'silently fail') + ' attempting to overwrite read-only properties', function() {
var commands = ['-s', 'include=bindAll,defaults,extend']; var commands = ['-s', 'include=bindAll,defaults,extend'],
start = _.after(2, _.once(QUnit.start));
if (index) { if (index) {
commands.push('strict'); commands.push('strict');
} }
build(commands, function(source, filePath) {
build(commands, function(source, filepath) { var basename = path.basename(filePath, '.js'),
var basename = path.basename(filepath, '.js'),
context = createContext(), context = createContext(),
pass = !index; pass = !index;
@@ -559,18 +591,63 @@
QUnit.module('underscore modifier'); QUnit.module('underscore modifier');
(function() { (function() {
var start = _.once(QUnit.start); asyncTest('modified methods should work correctly', function() {
var start = _.after(2, _.once(QUnit.start));
asyncTest('should not have deep clone', function() { build(['-s', 'underscore'], function(source, filePath) {
build(['-s', 'underscore'], function(source, filepath) { var last,
var array = [{ 'a': 1 }], array = [{ 'value': 1 }, { 'value': 2 }],
basename = path.basename(filepath, '.js'), basename = path.basename(filePath, '.js'),
context = createContext(); context = createContext();
vm.runInContext(source, context); vm.runInContext(source, context);
var lodash = context._; var lodash = context._;
ok(lodash.clone(array, true)[0] === array[0], basename); lodash.each(array, function(value) {
last = value;
return false;
});
equal(last.value, 2, '_.each: ' + basename);
equal(lodash.isEmpty('moe'), false, '_.isEmpty: ' + basename);
var object = { 'fn': lodash.bind(function(x) { return this.x + x; }, { 'x': 1 }, 1) };
equal(object.fn(), 2, '_.bind: ' + basename);
ok(lodash.clone(array, true)[0] === array[0], '_.clone: ' + basename);
start();
});
});
asyncTest('`lodash underscore include=partial`', function() {
var start = _.after(2, _.once(QUnit.start));
build(['-s', 'underscore', 'include=partial'], function(source, filePath) {
var basename = path.basename(filePath, '.js'),
context = createContext();
vm.runInContext(source, context);
var lodash = context._;
equal(lodash.partial(_.identity, 2)(), 2, '_.partial: ' + basename);
start();
});
});
asyncTest('`lodash underscore plus=clone`', function() {
var start = _.after(2, _.once(QUnit.start));
build(['-s', 'underscore', 'plus=clone'], function(source, filePath) {
var array = [{ 'value': 1 }],
basename = path.basename(filePath, '.js'),
context = createContext();
vm.runInContext(source, context);
var lodash = context._,
clone = lodash.clone(array, true);
deepEqual(array, clone, basename);
notEqual(array, clone, basename);
start(); start();
}); });
}); });
@@ -590,11 +667,11 @@
]; ];
commands.forEach(function(command, index) { commands.forEach(function(command, index) {
var start = _.after(2, _.once(QUnit.start));
asyncTest('`lodash ' + command +'`', function() { asyncTest('`lodash ' + command +'`', function() {
build(['-s', command], function(source, filepath) { var start = _.after(2, _.once(QUnit.start));
var basename = path.basename(filepath, '.js'),
build(['-s', command], function(source, filePath) {
var basename = path.basename(filePath, '.js'),
context = createContext(), context = createContext(),
pass = false; pass = false;
@@ -644,21 +721,33 @@
QUnit.module('iife command'); QUnit.module('iife command');
(function() { (function() {
var start = _.after(2, _.once(QUnit.start)); var commands = [
'iife=this["lodash"]=(function(window,undefined){%output%;return lodash}(this))',
'iife=define(function(window,undefined){return function(){%output%;return lodash}}(this));'
];
asyncTest('`lodash iife=...`', function() { commands.forEach(function(command) {
build(['-s', 'iife=!function(window,undefined){%output%}(this)'], function(source, filepath) { asyncTest('`lodash ' + command +'`', function() {
var basename = path.basename(filepath, '.js'), var start = _.after(2, _.once(QUnit.start));
context = createContext();
try { build(['-s', 'exports=none', command], function(source, filePath) {
vm.runInContext(source, context); var basename = path.basename(filePath, '.js'),
} catch(e) { } context = createContext();
var lodash = context._ || {}; context.define = function(func) {
ok(_.isString(lodash.VERSION), basename); context.lodash = func();
ok(/!function/.test(source), basename); };
start();
try {
vm.runInContext(source, context);
} catch(e) {
console.log(e);
}
var lodash = context.lodash || {};
ok(_.isString(lodash.VERSION), basename);
start();
});
}); });
}); });
}()); }());
@@ -669,11 +758,11 @@
(function() { (function() {
['-o a.js', '--output a.js'].forEach(function(command, index) { ['-o a.js', '--output a.js'].forEach(function(command, index) {
var start = _.once(QUnit.start);
asyncTest('`lodash ' + command +'`', function() { asyncTest('`lodash ' + command +'`', function() {
build(['-s'].concat(command.split(' ')), function(source, filepath) { var start = _.once(QUnit.start);
equal(filepath, 'a.js', command);
build(['-s'].concat(command.split(' ')), function(source, filePath) {
equal(path.basename(filePath, '.js'), 'a', command);
start(); start();
}); });
}); });
@@ -686,12 +775,18 @@
(function() { (function() {
['-c', '--stdout'].forEach(function(command, index) { ['-c', '--stdout'].forEach(function(command, index) {
var descriptor = Object.getOwnPropertyDescriptor(global, 'console'),
start = _.once(QUnit.start);
asyncTest('`lodash ' + command +'`', function() { asyncTest('`lodash ' + command +'`', function() {
build([command, 'exports=', 'include='], function(source, filepath) { var written,
equal(source, ''); start = _.once(QUnit.start),
write = process.stdout.write;
process.stdout.write = function(string) {
written = string;
};
build([command, 'exports=', 'include='], function(source) {
process.stdout.write = write;
equal(written, source);
equal(arguments.length, 1); equal(arguments.length, 1);
start(); start();
}); });
@@ -704,27 +799,170 @@
QUnit.module('minify underscore'); QUnit.module('minify underscore');
(function() { (function() {
var start = _.once(QUnit.start);
asyncTest('`node minify underscore.js`', function() { asyncTest('`node minify underscore.js`', function() {
var source = fs.readFileSync(path.join(__dirname, '..', 'vendor', 'underscore', 'underscore.js'), 'utf8'); var source = fs.readFileSync(path.join(__dirname, '..', 'vendor', 'underscore', 'underscore.js'), 'utf8'),
start = _.once(QUnit.start);
minify(source, { minify(source, {
'silent': true, 'isSilent': true,
'workingName': 'underscore.min', 'workingName': 'underscore.min',
'onComplete': function(result) { 'onComplete': function(result) {
var context = createContext(); var context = createContext();
try { try {
vm.runInContext(result, context); vm.runInContext(result, context);
} catch(e) { } } catch(e) {
console.log(e);
}
var underscore = context._ || {}; var underscore = context._ || {};
ok(_.isString(underscore.VERSION)); ok(_.isString(underscore.VERSION));
ok(result.match(/\n/g).length < source.match(/\n/g).length); ok(!/Lo-Dash/.test(result) && result.match(/\n/g).length < source.match(/\n/g).length);
start(); start();
} }
}); });
}); });
}()); }());
/*--------------------------------------------------------------------------*/
QUnit.module('mobile build');
(function() {
asyncTest('`lodash mobile`', function() {
var start = _.after(2, _.once(QUnit.start));
build(['-s', 'mobile'], function(source, filePath) {
var basename = path.basename(filePath, '.js'),
context = createContext();
try {
vm.runInContext(source, context);
} catch(e) {
console.log(e);
}
var array = [1, 2, 3],
object1 = [{ 'a': 1 }],
object2 = [{ 'b': 2 }],
object3 = [{ 'a': 1, 'b': 2 }],
circular1 = { 'a': 1 },
circular2 = { 'a': 1 },
lodash = context._;
circular1.b = circular1;
circular2.b = circular2;
deepEqual(lodash.merge(object1, object2), object3, basename);
deepEqual(lodash.sortBy([3, 2, 1], _.identity), array, basename);
equal(lodash.isEqual(circular1, circular2), true, basename);
var actual = lodash.clone(circular1, true);
ok(actual != circular1 && actual.b == actual, basename);
start();
});
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('lodash build');
(function() {
var commands = [
'backbone',
'csp',
'legacy',
'mobile',
'strict',
'underscore',
'category=arrays',
'category=chaining',
'category=collections',
'category=functions',
'category=objects',
'category=utilities',
'exclude=union,uniq,zip',
'include=each,filter,map',
'include=once plus=bind,Chaining',
'category=collections,functions',
'underscore backbone',
'backbone legacy category=utilities minus=first,last',
'underscore include=debounce,throttle plus=after minus=throttle',
'underscore mobile strict category=functions exports=amd,global plus=pick,uniq',
]
.concat(
allMethods.map(function(methodName) {
return 'include=' + methodName;
})
);
commands.forEach(function(command) {
asyncTest('`lodash ' + command +'`', function() {
var start = _.after(2, _.once(QUnit.start));
build(['--silent'].concat(command.split(' ')), function(source, filePath) {
var methodNames,
basename = path.basename(filePath, '.js'),
context = createContext();
try {
vm.runInContext(source, context);
} catch(e) {
console.log(e);
}
// add method names explicitly
if (/include/.test(command)) {
methodNames = command.match(/include=(\S*)/)[1].split(/, */);
}
// add method names required by Backbone and Underscore builds
if (/backbone/.test(command) && !methodNames) {
methodNames = backboneDependencies.slice();
}
if (/underscore/.test(command) && !methodNames) {
methodNames = underscoreMethods.slice();
}
// add method names explicitly by category
if (/category/.test(command)) {
// resolve method names belonging to each category (case-insensitive)
methodNames = command.match(/category=(\S*)/)[1].split(/, */).reduce(function(result, category) {
var capitalized = category[0].toUpperCase() + category.toLowerCase().slice(1);
return result.concat(getMethodsByCategory(capitalized));
}, methodNames || []);
}
// init `methodNames` if it hasn't been inited
if (!methodNames) {
methodNames = allMethods.slice();
}
if (/plus/.test(command)) {
methodNames = methodNames.concat(command.match(/plus=(\S*)/)[1].split(/, */));
}
if (/minus/.test(command)) {
methodNames = _.without.apply(_, [methodNames]
.concat(expandMethodNames(command.match(/minus=(\S*)/)[1].split(/, */))));
}
if (/exclude/.test(command)) {
methodNames = _.without.apply(_, [methodNames]
.concat(expandMethodNames(command.match(/exclude=(\S*)/)[1].split(/, */))));
}
// expand aliases and categories to real method names
methodNames = expandMethodNames(methodNames).reduce(function(result, methodName) {
return result.concat(methodName, getMethodsByCategory(methodName));
}, []);
// remove nonexistent and duplicate method names
methodNames = _.uniq(_.intersection(allMethods, expandMethodNames(methodNames)));
var lodash = context._ || {};
methodNames.forEach(function(methodName) {
testMethod(lodash, methodName, basename);
});
start();
});
});
});
}());
}()); }());

View File

@@ -106,7 +106,7 @@
(function() { (function() {
// ensure this test is executed before any other template tests to avoid false positives // ensure this test is executed before any other template tests to avoid false positives
test('should initialize `reEvaluateDelimiter` correctly (test with production build)', function() { test('should initialize `reEvaluateDelimiter` (test with production build)', function() {
var data = { 'a': [1, 2] }, var data = { 'a': [1, 2] },
settings = _.templateSettings; settings = _.templateSettings;
@@ -123,6 +123,14 @@
} }
}); });
test('supports loading lodash.js with the Require.js "shim" configuration option', function() {
if (window.document && window.require) {
equal((shimmedModule || {}).moduleName, 'shimmed');
} else {
skipTest();
}
});
test('supports loading lodash.js as the "underscore" module', function() { test('supports loading lodash.js as the "underscore" module', function() {
if (window.document && window.require) { if (window.document && window.require) {
equal((underscoreModule || {}).moduleName, 'underscore'); equal((underscoreModule || {}).moduleName, 'underscore');
@@ -160,30 +168,13 @@
QUnit.module('lodash.bind'); QUnit.module('lodash.bind');
(function() { (function() {
test('should correctly append array arguments to partially applied arguments (test in IE < 9)', function() { test('should append array arguments to partially applied arguments (test in IE < 9)', function() {
var args, var args,
bound = _.bind(function() { args = slice.call(arguments); }, {}, 'a'); bound = _.bind(function() { args = slice.call(arguments); }, {}, 'a');
bound(['b'], 'c'); bound(['b'], 'c');
deepEqual(args, ['a', ['b'], 'c']); deepEqual(args, ['a', ['b'], 'c']);
}); });
test('supports lazy bind', function() {
var object = {
'name': 'moe',
'greet': function(greeting) {
return greeting + ': ' + this.name;
}
};
var func = _.bind(object, 'greet', 'hi');
equal(func(), 'hi: moe');
object.greet = function(greeting) {
return greeting + ' ' + this.name + '!';
};
equal(func(), 'hi moe!');
});
}()); }());
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
@@ -221,7 +212,7 @@
objects['an array'].length = 5; objects['an array'].length = 5;
_.forOwn(objects, function(object, key) { _.forOwn(objects, function(object, key) {
test('should deep clone ' + key + ' correctly', function() { test('should deep clone ' + key, function() {
var clone = _.clone(object, true); var clone = _.clone(object, true);
ok(_.isEqual(object, clone)); ok(_.isEqual(object, clone));
@@ -260,19 +251,6 @@
ok(clone.bar.b === clone.foo.b && clone === clone.foo.b.foo.c && clone !== object); ok(clone.bar.b === clone.foo.b && clone === clone.foo.b.foo.c && clone !== object);
}); });
test('should clone using Klass#clone', function() {
var object = new Klass;
Klass.prototype.clone = function() { return new Klass; };
var clone = _.clone(object);
ok(clone !== object && clone instanceof Klass);
clone = _.clone(object, true);
ok(clone !== object && clone instanceof Klass);
delete Klass.prototype.clone;
});
test('should clone problem JScript properties (test in IE < 9)', function() { test('should clone problem JScript properties (test in IE < 9)', function() {
deepEqual(_.clone(shadowed), shadowed); deepEqual(_.clone(shadowed), shadowed);
ok(_.clone(shadowed) != shadowed); ok(_.clone(shadowed) != shadowed);
@@ -341,7 +319,7 @@
QUnit.module('lodash.difference'); QUnit.module('lodash.difference');
(function() { (function() {
test('should work correctly when using `cachedContains`', function() { test('should work when using `cachedContains`', function() {
var array1 = _.range(27), var array1 = _.range(27),
array2 = array1.slice(), array2 = array1.slice(),
a = {}, a = {},
@@ -400,11 +378,12 @@
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
QUnit.module('strict mode checks');
_.each(['bindAll', 'defaults', 'extend'], function(methodName) { _.each(['bindAll', 'defaults', 'extend'], function(methodName) {
var func = _[methodName]; var func = _[methodName];
QUnit.module('lodash.' + methodName + ' strict mode checks');
test('should not throw strict mode errors', function() { test('lodash.' + methodName + ' should not throw strict mode errors', function() {
var object = { 'a': null, 'b': function(){} }, var object = { 'a': null, 'b': function(){} },
pass = true; pass = true;
@@ -488,18 +467,6 @@
equal(_.forEach(collection, Boolean), collection); equal(_.forEach(collection, Boolean), collection);
}); });
test('should treat array-like object with invalid `length` as a regular object', function() {
var keys = [],
object = { 'length': -1 };
_.forEach(object, function(value, key) { keys.push(key); });
deepEqual(keys, ['length']);
keys = []; object.length = Math.pow(2, 32);
_.forEach(object, function(value, key) { keys.push(key); });
deepEqual(keys, ['length']);
});
_.each({ _.each({
'literal': 'abc', 'literal': 'abc',
'object': Object('abc') 'object': Object('abc')
@@ -551,17 +518,18 @@
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
QUnit.module('object iteration bugs');
_.each(['forEach', 'forIn', 'forOwn'], function(methodName) { _.each(['forEach', 'forIn', 'forOwn'], function(methodName) {
var func = _[methodName]; var func = _[methodName];
QUnit.module('lodash.' + methodName + ' iteration bugs');
test('fixes the JScript [[DontEnum]] bug (test in IE < 9)', function() { test('lodash.' + methodName + ' fixes the JScript [[DontEnum]] bug (test in IE < 9)', function() {
var keys = []; var keys = [];
func(shadowed, function(value, key) { keys.push(key); }); func(shadowed, function(value, key) { keys.push(key); });
deepEqual(keys.sort(), shadowedKeys); deepEqual(keys.sort(), shadowedKeys);
}); });
test('skips the prototype property of functions (test in Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1)', function() { test('lodash.' + methodName + ' skips the prototype property of functions (test in Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1)', function() {
function Foo() {} function Foo() {}
Foo.prototype.a = 1; Foo.prototype.a = 1;
@@ -578,11 +546,14 @@
}); });
}); });
/*--------------------------------------------------------------------------*/
QUnit.module('exit early');
_.each(['forEach', 'forIn', 'forOwn'], function(methodName) { _.each(['forEach', 'forIn', 'forOwn'], function(methodName) {
var func = _[methodName]; var func = _[methodName];
QUnit.module('lodash.' + methodName + ' can exit early');
test('can exit early when iterating arrays', function() { test('lodash.' + methodName + ' can exit early when iterating arrays', function() {
var array = [1, 2, 3], var array = [1, 2, 3],
values = []; values = [];
@@ -590,7 +561,7 @@
deepEqual(values, [1]); deepEqual(values, [1]);
}); });
test('can exit early when iterating objects', function() { test('lodash.' + methodName + ' can exit early when iterating objects', function() {
var object = { 'a': 1, 'b': 2, 'c': 3 }, var object = { 'a': 1, 'b': 2, 'c': 3 },
values = []; values = [];
@@ -675,6 +646,15 @@
var array = [1, 2, 3]; var array = [1, 2, 3];
deepEqual(_.initial(array, 0), []); deepEqual(_.initial(array, 0), []);
}); });
test('should allow a falsey `array` argument', function() {
_.each(falsey, function(index, value) {
try {
var actual = index ? _.initial(value) : _.initial();
} catch(e) { }
deepEqual(actual, []);
})
});
}()); }());
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
@@ -755,17 +735,6 @@
equal(_.isEqual(args1, args3), false); equal(_.isEqual(args1, args3), false);
}); });
test('should respect custom `isEqual` result despite objects strict equaling each other', function() {
var object = { 'isEqual': function() { return false; } };
equal(_.isEqual(object, object), false);
});
test('should use custom `isEqual` methods on primitives', function() {
Boolean.prototype.isEqual = function() { return true; };
equal(_.isEqual(true, false), true);
delete Boolean.prototype.isEqual;
});
test('fixes the JScript [[DontEnum]] bug (test in IE < 9)', function() { test('fixes the JScript [[DontEnum]] bug (test in IE < 9)', function() {
equal(_.isEqual(shadowed, {}), false); equal(_.isEqual(shadowed, {}), false);
}); });
@@ -827,6 +796,24 @@
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
QUnit.module('lodash.isPlainObject');
(function() {
test('should detect plain objects', function() {
function Foo(a) {
this.a = 1;
}
equal(_.isPlainObject(new Foo(1)), false);
equal(_.isPlainObject([1, 2, 3]), false);
equal(_.isPlainObject({ 'a': 1 }), true);
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('isType checks');
_.each([ _.each([
'isArguments', 'isArguments',
'isArray', 'isArray',
@@ -846,9 +833,8 @@
'isUndefined' 'isUndefined'
], function(methodName) { ], function(methodName) {
var func = _[methodName]; var func = _[methodName];
QUnit.module('lodash.' + methodName + ' result');
test('should return a boolean', function() { test('lodash.' + methodName + ' should return a boolean', function() {
var expected = 'boolean'; var expected = 'boolean';
equal(typeof func(arguments), expected); equal(typeof func(arguments), expected);
@@ -938,6 +924,42 @@
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
QUnit.module('lodash.lateBind');
(function() {
test('should work when the target function is overwritten', function() {
var object = {
'name': 'moe',
'greet': function(greeting) {
return greeting + ': ' + this.name;
}
};
var func = _.lateBind(object, 'greet', 'hi');
equal(func(), 'hi: moe');
object.greet = function(greeting) {
return greeting + ' ' + this.name + '!';
};
equal(func(), 'hi moe!');
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.max and lodash.min object iteration');
_.each(['max', 'min'], function(methodName) {
var func = _[methodName];
test('lodash.' + methodName + ' should iterate an object', function() {
var actual = func({ 'a': 1, 'b': 2, 'c': 3 });
equal(actual, methodName == 'max' ? 3 : 1);
});
});
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.merge'); QUnit.module('lodash.merge');
(function() { (function() {
@@ -982,7 +1004,6 @@
source.bar.b = source.foo.b; source.bar.b = source.foo.b;
var actual = _.merge(object, source); var actual = _.merge(object, source);
ok(actual.bar.b === actual.foo.b && actual.foo.b.foo.c === actual.foo.b.foo.c.foo.b.foo.c); ok(actual.bar.b === actual.foo.b && actual.foo.b.foo.c === actual.foo.b.foo.c.foo.b.foo.c);
}); });
@@ -1185,9 +1206,9 @@
QUnit.module('lodash.random'); QUnit.module('lodash.random');
(function() { (function() {
test('should work like `Math.random` if no arguments are passed', function() { test('should return `0` or `1` when no arguments are passed', function() {
var actual = _.random(); var actual = _.random();
ok(actual >= 0 && actual < 1); ok(actual === 0 || actual === 1);
}); });
test('supports not passing a `max` argument', function() { test('supports not passing a `max` argument', function() {
@@ -1270,22 +1291,6 @@
deepEqual(args, expected); deepEqual(args, expected);
}); });
test('should treat array-like object with invalid `length` as a regular object', function() {
var args,
object = { 'a': 1, 'length': -1 },
lastKey = _.keys(object).pop();
var expected = lastKey == 'length'
? [-1, 1, 'a', object]
: [1, -1, 'length', object];
_.reduceRight(object, function() {
args || (args = slice.call(arguments));
});
deepEqual(args, expected);
});
_.each({ _.each({
'literal': 'abc', 'literal': 'abc',
'object': Object('abc') 'object': Object('abc')
@@ -1307,6 +1312,32 @@
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
QUnit.module('lodash.rest');
(function() {
test('should allow a falsey `array` argument', function() {
_.each(falsey, function(index, value) {
try {
var actual = index ? _.rest(value) : _.rest();
} catch(e) { }
deepEqual(actual, []);
})
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.shuffle');
(function() {
test('should shuffle an object', function() {
var actual = _.shuffle({ 'a': 1, 'b': 2, 'c': 3 });
deepEqual(actual.sort(), [1, 2, 3]);
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.size'); QUnit.module('lodash.size');
(function() { (function() {
@@ -1508,7 +1539,7 @@
ok(pass); ok(pass);
}); });
test('should tokenize delimiters correctly', function() { test('should tokenize delimiters', function() {
var compiled = _.template('<span class="icon-<%= type %>2"></span>'); var compiled = _.template('<span class="icon-<%= type %>2"></span>');
equal(compiled({ 'type': 1 }), '<span class="icon-12"></span>'); equal(compiled({ 'type': 1 }), '<span class="icon-12"></span>');
}); });
@@ -1517,6 +1548,13 @@
var compiled = _.template('<%= value ? value : "b" %>'); var compiled = _.template('<%= value ? value : "b" %>');
equal(compiled({ 'value': 'a' }), 'a'); equal(compiled({ 'value': 'a' }), 'a');
}); });
test('should parse delimiters with newlines', function() {
var expected = '<<\nprint("<p>" + (value ? "yes" : "no") + "</p>")\n>>',
compiled = _.template(expected, null, { 'evaluate': /<<(.+?)>>/g });
equal(compiled({ 'value': true }), expected);
});
}()); }());
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
@@ -1559,6 +1597,31 @@
throttled(); throttled();
}); });
asyncTest('should clear timeout when `func` is called', function() {
var now = new Date,
times = [];
var throttled = _.throttle(function() {
times.push(new Date - now);
}, 20);
setTimeout(throttled, 20);
setTimeout(throttled, 20);
setTimeout(throttled, 40);
setTimeout(throttled, 40);
setTimeout(function() {
var actual = _.every(times, function(value, index) {
return index
? (value - times[index - 1]) > 2
: true;
});
ok(actual);
QUnit.start();
}, 120);
});
}()); }());
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
@@ -1568,27 +1631,12 @@
(function() { (function() {
var args = arguments; var args = arguments;
_.each({
'an array': ['a', 'b', 'c'],
'a string': Object('abc')
}, function(collection, key) {
test('should call custom `toArray` method of ' + key, function() {
collection.toArray = function() { return [3, 2, 1]; };
deepEqual(_.toArray(collection), [3, 2, 1]);
});
});
test('should treat array-like objects like arrays', function() { test('should treat array-like objects like arrays', function() {
var object = { '0': 'a', '1': 'b', '2': 'c', 'length': 3 }; var object = { '0': 'a', '1': 'b', '2': 'c', 'length': 3 };
deepEqual(_.toArray(object), ['a', 'b', 'c']); deepEqual(_.toArray(object), ['a', 'b', 'c']);
deepEqual(_.toArray(args), [1, 2, 3]); deepEqual(_.toArray(args), [1, 2, 3]);
}); });
test('should treat array-like object with invalid `length` as a regular object', function() {
var object = { 'length': -1 };
deepEqual(_.toArray(object), [-1]);
});
test('should work with a string for `collection` (test in Opera < 10.52)', function() { test('should work with a string for `collection` (test in Opera < 10.52)', function() {
deepEqual(_.toArray('abc'), ['a', 'b', 'c']); deepEqual(_.toArray('abc'), ['a', 'b', 'c']);
deepEqual(_.toArray(Object('abc')), ['a', 'b', 'c']); deepEqual(_.toArray(Object('abc')), ['a', 'b', 'c']);
@@ -1597,6 +1645,16 @@
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
QUnit.module('lodash.times');
(function() {
test('should return an array of the results of each `callback` execution', function() {
deepEqual(_.times(3, function(n) { return n * 2; }), [0, 2, 4]);
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.unescape'); QUnit.module('lodash.unescape');
(function() { (function() {
@@ -1717,6 +1775,18 @@
(function() { (function() {
test('should allow falsey arguments', function() { test('should allow falsey arguments', function() {
var returnArrays = [
'filter',
'invoke',
'map',
'pluck',
'reject',
'shuffle',
'sortBy',
'toArray',
'where'
];
var funcs = _.without.apply(_, [_.functions(_)].concat([ var funcs = _.without.apply(_, [_.functions(_)].concat([
'_', '_',
'_iteratorTemplate', '_iteratorTemplate',
@@ -1738,18 +1808,79 @@
])); ]));
_.each(funcs, function(methodName) { _.each(funcs, function(methodName) {
var func = _[methodName], var actual = [],
expected = _.times(falsey.length, function() { return []; }),
func = _[methodName],
pass = true; pass = true;
_.each(falsey, function(value, index) { _.each(falsey, function(value, index) {
try { try {
index ? func(value) : func(); actual.push(index ? func(value) : func());
} catch(e) { } catch(e) {
pass = false; pass = false;
} }
}); });
ok(pass, methodName + ' allows falsey arguments'); if (_.indexOf(returnArrays, methodName) > -1) {
deepEqual(actual, expected, '_.' + methodName + ' returns an array');
} else {
skipTest(falsey.length);
}
ok(pass, '_.' + methodName + ' allows falsey arguments');
});
});
test('should handle `null` `thisArg` arguments', function() {
var thisArg,
array = ['a'],
callback = function() { thisArg = this; },
expected = (function() { return this; }).call(null);
var funcs = [
'countBy',
'every',
'filter',
'find',
'forEach',
'forIn',
'forOwn',
'groupBy',
'map',
'max',
'min',
'omit',
'pick',
'reduce',
'reduceRight',
'reject',
'some',
'sortBy',
'sortedIndex',
'times',
'uniq'
];
_.each(funcs, function(methodName) {
var func = _[methodName],
message = '_.' + methodName + ' handles `null` `thisArg` arguments';
thisArg = undefined;
if (/^reduce/.test(methodName)) {
func(array, callback, 0, null);
} else if (methodName == 'sortedIndex') {
func(array, 'a', callback, null);
} else if (methodName == 'times') {
func(1, callback, null);
} else {
func(array, callback, null);
}
if (expected === null) {
deepEqual(thisArg, null, message);
} else {
equal(thisArg, expected, message);
}
}); });
}); });
}()); }());

View File

@@ -20,6 +20,7 @@
// Create a local reference to array methods. // Create a local reference to array methods.
var ArrayProto = Array.prototype; var ArrayProto = Array.prototype;
var push = ArrayProto.push;
var slice = ArrayProto.slice; var slice = ArrayProto.slice;
var splice = ArrayProto.splice; var splice = ArrayProto.splice;
@@ -182,22 +183,22 @@
// is automatically generated and assigned for you. // is automatically generated and assigned for you.
var Model = Backbone.Model = function(attributes, options) { var Model = Backbone.Model = function(attributes, options) {
var defaults; var defaults;
attributes || (attributes = {}); var attrs = attributes || {};
if (options && options.collection) this.collection = options.collection; if (options && options.collection) this.collection = options.collection;
if (options && options.parse) attributes = this.parse(attributes); if (options && options.parse) attributes = this.parse(attributes);
if (defaults = _.result(this, 'defaults')) { if (defaults = _.result(this, 'defaults')) {
attributes = _.extend({}, defaults, attributes); attrs = _.extend({}, defaults, attrs);
} }
this.attributes = {}; this.attributes = {};
this._escapedAttributes = {}; this._escapedAttributes = {};
this.cid = _.uniqueId('c'); this.cid = _.uniqueId('c');
this.changed = {}; this.changed = {};
this._silent = {}; this._changes = {};
this._pending = {}; this._pending = {};
this.set(attributes, {silent: true}); this.set(attrs, {silent: true});
// Reset change tracking. // Reset change tracking.
this.changed = {}; this.changed = {};
this._silent = {}; this._changes = {};
this._pending = {}; this._pending = {};
this._previousAttributes = _.clone(this.attributes); this._previousAttributes = _.clone(this.attributes);
this.initialize.apply(this, arguments); this.initialize.apply(this, arguments);
@@ -209,14 +210,18 @@
// A hash of attributes whose current and previous value differ. // A hash of attributes whose current and previous value differ.
changed: null, changed: null,
// A hash of attributes that have silently changed since the last time // A hash of attributes that have changed since the last time `change`
// `change` was called. Will become pending attributes on the next call. // was called.
_silent: null, _changes: null,
// A hash of attributes that have changed since the last `'change'` event // A hash of attributes that have changed since the last `change` event
// began. // began.
_pending: null, _pending: null,
// A hash of attributes with the current model state to determine if
// a `change` should be recorded within a nested `change` block.
_changing : null,
// The default name for the JSON `id` attribute is `"id"`. MongoDB and // The default name for the JSON `id` attribute is `"id"`. MongoDB and
// CouchDB users may want to set this to `"_id"`. // CouchDB users may want to set this to `"_id"`.
idAttribute: 'id', idAttribute: 'id',
@@ -256,23 +261,22 @@
// Set a hash of model attributes on the object, firing `"change"` unless // Set a hash of model attributes on the object, firing `"change"` unless
// you choose to silence it. // you choose to silence it.
set: function(key, value, options) { set: function(attrs, options) {
var attrs, attr, val; var attr, key, val;
if (attrs == null) return this;
// Handle both `"key", value` and `{key: value}` -style arguments. // Handle both `"key", value` and `{key: value}` -style arguments.
if (_.isObject(key) || key == null) { if (!_.isObject(attrs)) {
attrs = key; key = attrs;
options = value; (attrs = {})[key] = options;
} else { options = arguments[2];
attrs = {};
attrs[key] = value;
} }
// Extract attributes and options. // Extract attributes and options.
options || (options = {}); var silent = options && options.silent;
if (!attrs) return this; var unset = options && options.unset;
if (attrs instanceof Model) attrs = attrs.attributes; if (attrs instanceof Model) attrs = attrs.attributes;
if (options.unset) for (attr in attrs) attrs[attr] = void 0; if (unset) for (attr in attrs) attrs[attr] = void 0;
// Run validation. // Run validation.
if (!this._validate(attrs, options)) return false; if (!this._validate(attrs, options)) return false;
@@ -280,7 +284,7 @@
// Check for changes of `id`. // Check for changes of `id`.
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute]; if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
var changes = options.changes = {}; var changing = this._changing;
var now = this.attributes; var now = this.attributes;
var escaped = this._escapedAttributes; var escaped = this._escapedAttributes;
var prev = this._previousAttributes || {}; var prev = this._previousAttributes || {};
@@ -290,27 +294,30 @@
val = attrs[attr]; val = attrs[attr];
// If the new and current value differ, record the change. // If the new and current value differ, record the change.
if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) { if (!_.isEqual(now[attr], val) || (unset && _.has(now, attr))) {
delete escaped[attr]; delete escaped[attr];
(options.silent ? this._silent : changes)[attr] = true; this._changes[attr] = true;
} }
// Update or delete the current value. // Update or delete the current value.
options.unset ? delete now[attr] : now[attr] = val; unset ? delete now[attr] : now[attr] = val;
// If the new and previous value differ, record the change. If not, // If the new and previous value differ, record the change. If not,
// then remove changes for this attribute. // then remove changes for this attribute.
if (!_.isEqual(prev[attr], val) || (_.has(now, attr) !== _.has(prev, attr))) { if (!_.isEqual(prev[attr], val) || (_.has(now, attr) !== _.has(prev, attr))) {
this.changed[attr] = val; this.changed[attr] = val;
if (!options.silent) this._pending[attr] = true; if (!silent) this._pending[attr] = true;
} else { } else {
delete this.changed[attr]; delete this.changed[attr];
delete this._pending[attr]; delete this._pending[attr];
if (!changing) delete this._changes[attr];
} }
if (changing && _.isEqual(now[attr], changing[attr])) delete this._changes[attr];
} }
// Fire the `"change"` events. // Fire the `"change"` events.
if (!options.silent) this.change(options); if (!silent) this.change(options);
return this; return this;
}, },
@@ -345,16 +352,14 @@
// Set a hash of model attributes, and sync the model to the server. // Set a hash of model attributes, and sync the model to the server.
// If the server returns an attributes hash that differs, the model's // If the server returns an attributes hash that differs, the model's
// state will be `set` again. // state will be `set` again.
save: function(key, value, options) { save: function(attrs, options) {
var attrs, current, done; var key, current, done;
// Handle both `("key", value)` and `({key: value})` -style calls. // Handle both `"key", value` and `{key: value}` -style arguments.
if (_.isObject(key) || key == null) { if (attrs != null && !_.isObject(attrs)) {
attrs = key; key = attrs;
options = value; (attrs = {})[key] = options;
} else { options = arguments[2];
attrs = {};
attrs[key] = value;
} }
options = options ? _.clone(options) : {}; options = options ? _.clone(options) : {};
@@ -371,7 +376,7 @@
} }
// Do not persist invalid models. // Do not persist invalid models.
if (!attrs && !this.isValid()) return false; if (!attrs && !this._validate(null, options)) return false;
// After a successful server-side save, the client is (optionally) // After a successful server-side save, the client is (optionally)
// updated with the server-side state. // updated with the server-side state.
@@ -454,18 +459,25 @@
// a `"change:attribute"` event for each changed attribute. // a `"change:attribute"` event for each changed attribute.
// Calling this will cause all objects observing the model to update. // Calling this will cause all objects observing the model to update.
change: function(options) { change: function(options) {
options || (options = {});
var changing = this._changing; var changing = this._changing;
this._changing = true; var current = this._changing = {};
// Silent changes become pending changes. // Silent changes become pending changes.
for (var attr in this._silent) this._pending[attr] = true; for (var attr in this._changes) this._pending[attr] = true;
// Silent changes are triggered. // Trigger 'change:attr' for any new or silent changes.
var changes = _.extend({}, options.changes, this._silent); var changes = this._changes;
this._silent = {}; this._changes = {};
// Set the correct state for this._changing values
var triggers = [];
for (var attr in changes) { for (var attr in changes) {
this.trigger('change:' + attr, this, this.get(attr), options); current[attr] = this.get(attr);
triggers.push(attr);
}
for (var i=0, l=triggers.length; i < l; i++) {
this.trigger('change:' + triggers[i], this, current[triggers[i]], options);
} }
if (changing) return this; if (changing) return this;
@@ -475,13 +487,13 @@
this.trigger('change', this, options); this.trigger('change', this, options);
// Pending and silent changes still remain. // Pending and silent changes still remain.
for (var attr in this.changed) { for (var attr in this.changed) {
if (this._pending[attr] || this._silent[attr]) continue; if (this._pending[attr] || this._changes[attr]) continue;
delete this.changed[attr]; delete this.changed[attr];
} }
this._previousAttributes = _.clone(this.attributes); this._previousAttributes = _.clone(this.attributes);
} }
this._changing = false; this._changing = null;
return this; return this;
}, },
@@ -531,7 +543,7 @@
// returning `true` if all is well. If a specific `error` callback has // returning `true` if all is well. If a specific `error` callback has
// been passed, call that instead of firing the general `"error"` event. // been passed, call that instead of firing the general `"error"` event.
_validate: function(attrs, options) { _validate: function(attrs, options) {
if (options.silent || !this.validate) return true; if (options && options.silent || !this.validate) return true;
attrs = _.extend({}, this.attributes, attrs); attrs = _.extend({}, this.attributes, attrs);
var error = this.validate(attrs, options); var error = this.validate(attrs, options);
if (!error) return true; if (!error) return true;
@@ -585,59 +597,51 @@
// Add a model, or list of models to the set. Pass **silent** to avoid // Add a model, or list of models to the set. Pass **silent** to avoid
// firing the `add` event for every new model. // firing the `add` event for every new model.
add: function(models, options) { add: function(models, options) {
var i, index, length, model, cid, id, cids = {}, ids = {}, dups = []; var i, args, length, model, existing;
options || (options = {}); var at = options && options.at;
models = _.isArray(models) ? models.slice() : [models]; models = _.isArray(models) ? models.slice() : [models];
// Begin by turning bare objects into model references, and preventing // Begin by turning bare objects into model references, and preventing
// invalid models or duplicate models from being added. // invalid models from being added.
for (i = 0, length = models.length; i < length; i++) { for (i = 0, length = models.length; i < length; i++) {
if (!(model = models[i] = this._prepareModel(models[i], options))) { if (models[i] = this._prepareModel(models[i], options)) continue;
throw new Error("Can't add an invalid model to a collection"); throw new Error("Can't add an invalid model to a collection");
} }
cid = model.cid;
id = model.id; for (i = models.length - 1; i >= 0; i--) {
if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) { model = models[i];
dups.push(i); existing = model.id != null && this._byId[model.id];
// If a duplicate is found, splice it out and optionally merge it into
// the existing model.
if (existing || this._byCid[model.cid]) {
if (options && options.merge && existing) {
existing.set(model, options);
}
models.splice(i, 1);
continue; continue;
} }
cids[cid] = ids[id] = model;
}
// Remove duplicates. // Listen to added models' events, and index models for lookup by
i = dups.length; // `id` and by `cid`.
while (i--) { model.on('all', this._onModelEvent, this);
dups[i] = models.splice(dups[i], 1)[0];
}
// Listen to added models' events, and index models for lookup by
// `id` and by `cid`.
for (i = 0, length = models.length; i < length; i++) {
(model = models[i]).on('all', this._onModelEvent, this);
this._byCid[model.cid] = model; this._byCid[model.cid] = model;
if (model.id != null) this._byId[model.id] = model; if (model.id != null) this._byId[model.id] = model;
} }
// Insert models into the collection, re-sorting if needed, and triggering // Update `length` and splice in new models.
// `add` events unless silenced. this.length += models.length;
this.length += length; args = [at != null ? at : this.models.length, 0];
index = options.at != null ? options.at : this.models.length; push.apply(args, models);
splice.apply(this.models, [index, 0].concat(models)); splice.apply(this.models, args);
// Merge in duplicate models.
if (options.merge) {
for (i = 0, length = dups.length; i < length; i++) {
if (model = this._byId[dups[i].id]) model.set(dups[i], options);
}
}
// Sort the collection if appropriate. // Sort the collection if appropriate.
if (this.comparator && options.at == null) this.sort({silent: true}); if (this.comparator && at == null) this.sort({silent: true});
if (options.silent) return this; if (options && options.silent) return this;
for (i = 0, length = this.models.length; i < length; i++) {
if (!cids[(model = this.models[i]).cid]) continue; // Trigger `add` events.
options.index = i; while (model = models.shift()) {
model.trigger('add', model, this, options); model.trigger('add', model, this, options);
} }
@@ -731,35 +735,35 @@
// normal circumstances, as the set will maintain sort order as each item // normal circumstances, as the set will maintain sort order as each item
// is added. // is added.
sort: function(options) { sort: function(options) {
options || (options = {}); if (!this.comparator) {
if (!this.comparator) throw new Error('Cannot sort a set without a comparator'); throw new Error('Cannot sort a set without a comparator');
var boundComparator = _.bind(this.comparator, this);
if (this.comparator.length === 1) {
this.models = this.sortBy(boundComparator);
} else {
this.models.sort(boundComparator);
} }
if (!options.silent) this.trigger('reset', this, options);
if (_.isString(this.comparator) || this.comparator.length === 1) {
this.models = this.sortBy(this.comparator, this);
} else {
this.models.sort(_.bind(this.comparator, this));
}
if (!options || !options.silent) this.trigger('reset', this, options);
return this; return this;
}, },
// Pluck an attribute from each model in the collection. // Pluck an attribute from each model in the collection.
pluck: function(attr) { pluck: function(attr) {
return _.map(this.models, function(model){ return model.get(attr); }); return _.invoke(this.models, 'get', attr);
}, },
// When you have more items than you want to add or remove individually, // When you have more items than you want to add or remove individually,
// you can reset the entire set with a new list of models, without firing // you can reset the entire set with a new list of models, without firing
// any `add` or `remove` events. Fires `reset` when finished. // any `add` or `remove` events. Fires `reset` when finished.
reset: function(models, options) { reset: function(models, options) {
models || (models = []);
options || (options = {});
for (var i = 0, l = this.models.length; i < l; i++) { for (var i = 0, l = this.models.length; i < l; i++) {
this._removeReference(this.models[i]); this._removeReference(this.models[i]);
} }
this._reset(); this._reset();
this.add(models, _.extend({silent: true}, options)); if (models) this.add(models, _.extend({silent: true}, options));
if (!options.silent) this.trigger('reset', this, options); if (!options || !options.silent) this.trigger('reset', this, options);
return this; return this;
}, },
@@ -861,9 +865,9 @@
var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl', var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select', 'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke', 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
'max', 'min', 'sortBy', 'sortedIndex', 'toArray', 'size', 'first', 'head', 'max', 'min', 'sortedIndex', 'toArray', 'size', 'first', 'head', 'take',
'take', 'initial', 'rest', 'tail', 'last', 'without', 'indexOf', 'shuffle', 'initial', 'rest', 'tail', 'last', 'without', 'indexOf', 'shuffle',
'lastIndexOf', 'isEmpty', 'groupBy']; 'lastIndexOf', 'isEmpty'];
// Mix in each Underscore method as a proxy to `Collection#models`. // Mix in each Underscore method as a proxy to `Collection#models`.
_.each(methods, function(method) { _.each(methods, function(method) {
@@ -874,6 +878,19 @@
}; };
}); });
// Underscore methods that take a property name as an argument.
var attributeMethods = ['groupBy', 'countBy', 'sortBy'];
// Use attributes instead of properties.
_.each(attributeMethods, function(method) {
Collection.prototype[method] = function(value, context) {
var iterator = _.isFunction(value) ? value : function(model) {
return model.get(value);
};
return _[method](this.models, iterator, context);
};
});
// Backbone.Router // Backbone.Router
// ------------------- // -------------------
@@ -888,9 +905,10 @@
// Cached regular expressions for matching named param parts and splatted // Cached regular expressions for matching named param parts and splatted
// parts of route strings. // parts of route strings.
var optionalParam = /\((.*?)\)/g;
var namedParam = /:\w+/g; var namedParam = /:\w+/g;
var splatParam = /\*\w+/g; var splatParam = /\*\w+/g;
var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g; var escapeRegExp = /[-{}[\]+?.,\\^$|#\s]/g;
// Set up all inheritable **Backbone.Router** properties and methods. // Set up all inheritable **Backbone.Router** properties and methods.
_.extend(Router.prototype, Events, { _.extend(Router.prototype, Events, {
@@ -920,6 +938,7 @@
// Simple proxy to `Backbone.history` to save a fragment into the history. // Simple proxy to `Backbone.history` to save a fragment into the history.
navigate: function(fragment, options) { navigate: function(fragment, options) {
Backbone.history.navigate(fragment, options); Backbone.history.navigate(fragment, options);
return this;
}, },
// Bind all defined routes to `Backbone.history`. We have to reverse the // Bind all defined routes to `Backbone.history`. We have to reverse the
@@ -940,6 +959,7 @@
// against the current location hash. // against the current location hash.
_routeToRegExp: function(route) { _routeToRegExp: function(route) {
route = route.replace(escapeRegExp, '\\$&') route = route.replace(escapeRegExp, '\\$&')
.replace(optionalParam, '(?:$1)?')
.replace(namedParam, '([^\/]+)') .replace(namedParam, '([^\/]+)')
.replace(splatParam, '(.*?)'); .replace(splatParam, '(.*?)');
return new RegExp('^' + route + '$'); return new RegExp('^' + route + '$');
@@ -958,11 +978,15 @@
// Handles cross-browser history management, based on URL fragments. If the // Handles cross-browser history management, based on URL fragments. If the
// browser does not support `onhashchange`, falls back to polling. // browser does not support `onhashchange`, falls back to polling.
var History = Backbone.History = function(options) { var History = Backbone.History = function() {
this.handlers = []; this.handlers = [];
_.bindAll(this, 'checkUrl'); _.bindAll(this, 'checkUrl');
this.location = options && options.location || root.location;
this.history = options && options.history || root.history; // #1653 - Ensure that `History` can be used outside of the browser.
if (typeof window !== 'undefined') {
this.location = window.location;
this.history = window.history;
}
}; };
// Cached regex for cleaning leading hashes and slashes. // Cached regex for cleaning leading hashes and slashes.
@@ -1048,7 +1072,7 @@
// opened by a non-pushState browser. // opened by a non-pushState browser.
this.fragment = fragment; this.fragment = fragment;
var loc = this.location; var loc = this.location;
var atRoot = (loc.pathname.replace(/[^/]$/, '$&/') === this.root) && !loc.search; var atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === this.root;
// If we've started off with a route from a `pushState`-enabled browser, // If we've started off with a route from a `pushState`-enabled browser,
// but we're currently in a browser that doesn't support it... // but we're currently in a browser that doesn't support it...
@@ -1062,7 +1086,7 @@
// in a browser where it could be `pushState`-based instead... // in a browser where it could be `pushState`-based instead...
} else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) { } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
this.fragment = this.getHash().replace(routeStripper, ''); this.fragment = this.getHash().replace(routeStripper, '');
this.history.replaceState({}, document.title, this.root + this.fragment); this.history.replaceState({}, document.title, this.root + this.fragment + loc.search);
} }
if (!this.options.silent) return this.loadUrl(); if (!this.options.silent) return this.loadUrl();
@@ -1121,7 +1145,7 @@
fragment = this.getFragment(fragment || ''); fragment = this.getFragment(fragment || '');
if (this.fragment === fragment) return; if (this.fragment === fragment) return;
this.fragment = fragment; this.fragment = fragment;
var url = (fragment.indexOf(this.root) !== 0 ? this.root : '') + fragment; var url = this.root + fragment;
// If pushState is available, we use it to set the fragment as a real URL. // If pushState is available, we use it to set the fragment as a real URL.
if (this._hasPushState) { if (this._hasPushState) {
@@ -1151,9 +1175,11 @@
// a new one to the browser history. // a new one to the browser history.
_updateHash: function(location, fragment, replace) { _updateHash: function(location, fragment, replace) {
if (replace) { if (replace) {
location.replace(location.href.replace(/(javascript:|#).*$/, '') + '#' + fragment); var href = location.href.replace(/(javascript:|#).*$/, '');
location.replace(href + '#' + fragment);
} else { } else {
location.hash = fragment; // #1649 - Some browsers require that `hash` contains a leading #.
location.hash = '#' + fragment;
} }
} }
@@ -1208,8 +1234,8 @@
// memory leaks. // memory leaks.
dispose: function() { dispose: function() {
this.undelegateEvents(); this.undelegateEvents();
if (this.model) this.model.off(null, null, this); if (this.model && this.model.off) this.model.off(null, null, this);
if (this.collection) this.collection.off(null, null, this); if (this.collection && this.collection.off) this.collection.off(null, null, this);
return this; return this;
}, },
@@ -1307,7 +1333,7 @@
if (this.className) attrs['class'] = _.result(this, 'className'); if (this.className) attrs['class'] = _.result(this, 'className');
this.setElement(this.make(_.result(this, 'tagName'), attrs), false); this.setElement(this.make(_.result(this, 'tagName'), attrs), false);
} else { } else {
this.setElement(this.el, false); this.setElement(_.result(this, 'el'), false);
} }
} }
@@ -1422,9 +1448,12 @@
child = function(){ parent.apply(this, arguments); }; child = function(){ parent.apply(this, arguments); };
} }
// Add static properties to the constructor function, if supplied.
_.extend(child, parent, staticProps);
// Set the prototype chain to inherit from `parent`, without calling // Set the prototype chain to inherit from `parent`, without calling
// `parent`'s constructor function. // `parent`'s constructor function.
function Surrogate(){ this.constructor = child; }; var Surrogate = function(){ this.constructor = child; };
Surrogate.prototype = parent.prototype; Surrogate.prototype = parent.prototype;
child.prototype = new Surrogate; child.prototype = new Surrogate;
@@ -1432,9 +1461,6 @@
// if supplied. // if supplied.
if (protoProps) _.extend(child.prototype, protoProps); if (protoProps) _.extend(child.prototype, protoProps);
// Add static properties to the constructor function, if supplied.
_.extend(child, parent, staticProps);
// Set a convenience property in case the parent's prototype is needed // Set a convenience property in case the parent's prototype is needed
// later. // later.
child.__super__ = parent.prototype; child.__super__ = parent.prototype;

View File

@@ -34,6 +34,15 @@ $(document).ready(function() {
equal(col.length, 4); equal(col.length, 4);
}); });
test("String comparator.", 1, function() {
var collection = new Backbone.Collection([
{id: 3},
{id: 1},
{id: 2}
], {comparator: 'id'});
deepEqual(collection.pluck('id'), [1, 2, 3]);
});
test("new and parse", 3, function() { test("new and parse", 3, function() {
var Collection = Backbone.Collection.extend({ var Collection = Backbone.Collection.extend({
parse : function(data) { parse : function(data) {
@@ -88,7 +97,7 @@ $(document).ready(function() {
equal(col.pluck('label').join(' '), 'a b c d'); equal(col.pluck('label').join(' '), 'a b c d');
}); });
test("add", 11, function() { test("add", 10, function() {
var added, opts, secondAdded; var added, opts, secondAdded;
added = opts = secondAdded = null; added = opts = secondAdded = null;
e = new Backbone.Model({id: 10, label : 'e'}); e = new Backbone.Model({id: 10, label : 'e'});
@@ -98,7 +107,6 @@ $(document).ready(function() {
}); });
col.on('add', function(model, collection, options){ col.on('add', function(model, collection, options){
added = model.get('label'); added = model.get('label');
equal(options.index, 4);
opts = options; opts = options;
}); });
col.add(e, {amazing: true}); col.add(e, {amazing: true});
@@ -222,7 +230,7 @@ $(document).ready(function() {
equal(col.indexOf(tom), 2); equal(col.indexOf(tom), 2);
}); });
test("comparator that depends on `this`", 1, function() { test("comparator that depends on `this`", 2, function() {
var col = new Backbone.Collection; var col = new Backbone.Collection;
col.negative = function(num) { col.negative = function(num) {
return -num; return -num;
@@ -231,7 +239,12 @@ $(document).ready(function() {
return this.negative(a.id); return this.negative(a.id);
}; };
col.add([{id: 1}, {id: 2}, {id: 3}]); col.add([{id: 1}, {id: 2}, {id: 3}]);
equal(col.pluck('id').join(' '), '3 2 1'); deepEqual(col.pluck('id'), [3, 2, 1]);
col.comparator = function(a, b) {
return this.negative(b.id) - this.negative(a.id);
};
col.sort();
deepEqual(col.pluck('id'), [1, 2, 3]);
}); });
test("remove", 5, function() { test("remove", 5, function() {
@@ -549,23 +562,6 @@ $(document).ready(function() {
}); });
}); });
test("index with comparator", 4, function() {
var counter = 0;
var col = new Backbone.Collection([{id: 2}, {id: 4}], {
comparator: function(model){ return model.id; }
}).on('add', function(model, colleciton, options){
if (model.id == 1) {
equal(options.index, 0);
equal(counter++, 0);
}
if (model.id == 3) {
equal(options.index, 2);
equal(counter++, 1);
}
});
col.add([{id: 3}, {id: 1}]);
});
test("throwing during add leaves consistent state", 4, function() { test("throwing during add leaves consistent state", 4, function() {
var col = new Backbone.Collection(); var col = new Backbone.Collection();
col.on('test', function() { ok(false); }); col.on('test', function() { ok(false); });
@@ -651,7 +647,7 @@ $(document).ready(function() {
collection.create({id: 1}); collection.create({id: 1});
}); });
test("#1447 - create with wait adds model.", function() { test("#1447 - create with wait adds model.", 1, function() {
var collection = new Backbone.Collection; var collection = new Backbone.Collection;
var model = new Backbone.Model; var model = new Backbone.Model;
model.sync = function(method, model, options){ options.success(); }; model.sync = function(method, model, options){ options.success(); };
@@ -659,7 +655,7 @@ $(document).ready(function() {
collection.create(model, {wait: true}); collection.create(model, {wait: true});
}); });
test("#1448 - add sorts collection after merge.", function() { test("#1448 - add sorts collection after merge.", 1, function() {
var collection = new Backbone.Collection([ var collection = new Backbone.Collection([
{id: 1, x: 1}, {id: 1, x: 1},
{id: 2, x: 2} {id: 2, x: 2}
@@ -668,4 +664,41 @@ $(document).ready(function() {
collection.add({id: 1, x: 3}, {merge: true}); collection.add({id: 1, x: 3}, {merge: true});
deepEqual(collection.pluck('id'), [2, 1]); deepEqual(collection.pluck('id'), [2, 1]);
}); });
test("#1655 - groupBy can be used with a string argument.", 3, function() {
var collection = new Backbone.Collection([{x: 1}, {x: 2}]);
var grouped = collection.groupBy('x');
strictEqual(_.keys(grouped).length, 2);
strictEqual(grouped[1][0].get('x'), 1);
strictEqual(grouped[2][0].get('x'), 2);
});
test("#1655 - sortBy can be used with a string argument.", 1, function() {
var collection = new Backbone.Collection([{x: 3}, {x: 1}, {x: 2}]);
var values = _.map(collection.sortBy('x'), function(model) {
return model.get('x');
});
deepEqual(values, [1, 2, 3]);
});
test("#1604 - Removal during iteration.", 0, function() {
var collection = new Backbone.Collection([{}, {}]);
collection.on('add', function() {
collection.at(0).destroy();
});
collection.add({}, {at: 0});
});
test("#1638 - `sort` during `add` triggers correctly.", function() {
var collection = new Backbone.Collection;
collection.comparator = function(model) { return model.get('x'); };
var added = [];
collection.on('add', function(model) {
model.set({x: 3});
collection.sort();
added.push(model.id);
});
collection.add([{id: 1, x: 1}, {id: 2, x: 2}]);
deepEqual(added, [1, 2]);
});
}); });

View File

@@ -740,9 +740,9 @@ $(document).ready(function() {
model.set({b: 2}, {silent: true}); model.set({b: 2}, {silent: true});
}); });
model.set({b: 0}); model.set({b: 0});
deepEqual(changes, [0, 1, 1]); deepEqual(changes, [0, 1]);
model.change(); model.change();
deepEqual(changes, [0, 1, 1, 2, 1]); deepEqual(changes, [0, 1, 2, 1]);
}); });
test("nested set multiple times", 1, function() { test("nested set multiple times", 1, function() {
@@ -816,4 +816,66 @@ $(document).ready(function() {
strictEqual(model.save(), false); strictEqual(model.save(), false);
}); });
test("#1377 - Save without attrs triggers 'error'.", 1, function() {
var Model = Backbone.Model.extend({
url: '/test/',
sync: function(method, model, options){ options.success(); },
validate: function(){ return 'invalid'; }
});
var model = new Model({id: 1});
model.on('error', function(){ ok(true); });
model.save();
});
test("#1545 - `undefined` can be passed to a model constructor without coersion", function() {
var Model = Backbone.Model.extend({
defaults: { one: 1 },
initialize : function(attrs, opts) {
equal(attrs, undefined);
}
});
var emptyattrs = new Model();
var undefinedattrs = new Model(undefined);
});
asyncTest("#1478 - Model `save` does not trigger change on unchanged attributes", 0, function() {
var Model = Backbone.Model.extend({
sync: function(method, model, options) {
setTimeout(function(){
options.success();
start();
}, 0);
}
});
new Model({x: true})
.on('change:x', function(){ ok(false); })
.save(null, {wait: true});
});
test("#1664 - Changing from one value, silently to another, back to original does not trigger change.", 0, function() {
var model = new Backbone.Model({x:1});
model.on('change:x', function() { ok(false); });
model.set({x:2},{silent:true});
model.set({x:3},{silent:true});
model.set({x:1});
});
test("#1664 - multiple silent changes nested inside a change event", 2, function() {
var changes = [];
var model = new Backbone.Model();
model.on('change', function() {
model.set({a:'c'}, {silent:true});
model.set({b:2}, {silent:true});
model.unset('c', {silent:true});
model.set({a:'a'}, {silent:true});
model.set({b:1}, {silent:true});
model.set({c:'item'}, {silent:true});
});
model.on('change:a change:b change:c', function(model, val) { changes.push(val); });
model.set({a:'a', b:1, c:'item'});
deepEqual(changes, ['a',1,'item']);
model.change();
deepEqual(changes, ['a',1,'item']);
});
}); });

View File

@@ -41,7 +41,7 @@ $(document).ready(function() {
setup: function() { setup: function() {
location = new Location('http://example.com'); location = new Location('http://example.com');
Backbone.history = new Backbone.History({location: location}); Backbone.history = _.extend(new Backbone.History, {location: location});
router = new Router({testing: 101}); router = new Router({testing: 101});
Backbone.history.interval = 9; Backbone.history.interval = 9;
Backbone.history.start({pushState: false}); Backbone.history.start({pushState: false});
@@ -69,6 +69,7 @@ $(document).ready(function() {
"contacts": "contacts", "contacts": "contacts",
"contacts/new": "newContact", "contacts/new": "newContact",
"contacts/:id": "loadContact", "contacts/:id": "loadContact",
"optional(/:item)": "optionalItem",
"splat/*args/end": "splat", "splat/*args/end": "splat",
"*first/complex-:part/*rest": "complex", "*first/complex-:part/*rest": "complex",
":entity?*args": "query", ":entity?*args": "query",
@@ -105,6 +106,10 @@ $(document).ready(function() {
this.contact = 'load'; this.contact = 'load';
}, },
optionalItem: function(arg){
this.arg = arg !== undefined ? arg : null;
},
splat : function(args) { splat : function(args) {
this.args = args; this.args = args;
}, },
@@ -199,6 +204,15 @@ $(document).ready(function() {
equal(router.args, 'long-list/of/splatted_99args'); equal(router.args, 'long-list/of/splatted_99args');
}); });
test("routes (optional)", 2, function() {
location.replace('http://example.com#optional');
Backbone.history.checkUrl();
equal(router.arg, null);
location.replace('http://example.com#optional/thing');
Backbone.history.checkUrl();
equal(router.arg, 'thing');
});
test("routes (complex)", 3, function() { test("routes (complex)", 3, function() {
location.replace('http://example.com#one/two/three/complex-part/four/five/six/seven'); location.replace('http://example.com#one/two/three/complex-part/four/five/six/seven');
Backbone.history.checkUrl(); Backbone.history.checkUrl();
@@ -233,12 +247,12 @@ $(document).ready(function() {
location.replace('http://example.com/root/foo'); location.replace('http://example.com/root/foo');
Backbone.history.stop(); Backbone.history.stop();
Backbone.history = new Backbone.History({location: location}); Backbone.history = _.extend(new Backbone.History, {location: location});
Backbone.history.start({root: '/root', hashChange: false, silent: true}); Backbone.history.start({root: '/root', hashChange: false, silent: true});
strictEqual(Backbone.history.getFragment(), 'foo'); strictEqual(Backbone.history.getFragment(), 'foo');
Backbone.history.stop(); Backbone.history.stop();
Backbone.history = new Backbone.History({location: location}); Backbone.history = _.extend(new Backbone.History, {location: location});
Backbone.history.start({root: '/root/', hashChange: false, silent: true}); Backbone.history.start({root: '/root/', hashChange: false, silent: true});
strictEqual(Backbone.history.getFragment(), 'foo'); strictEqual(Backbone.history.getFragment(), 'foo');
}); });
@@ -272,7 +286,7 @@ $(document).ready(function() {
test("#1185 - Use pathname when hashChange is not wanted.", 1, function() { test("#1185 - Use pathname when hashChange is not wanted.", 1, function() {
Backbone.history.stop(); Backbone.history.stop();
location.replace('http://example.com/path/name#hash'); location.replace('http://example.com/path/name#hash');
Backbone.history = new Backbone.History({location: location}); Backbone.history = _.extend(new Backbone.History, {location: location});
Backbone.history.start({hashChange: false}); Backbone.history.start({hashChange: false});
var fragment = Backbone.history.getFragment(); var fragment = Backbone.history.getFragment();
strictEqual(fragment, location.pathname.replace(/^\//, '')); strictEqual(fragment, location.pathname.replace(/^\//, ''));
@@ -281,7 +295,7 @@ $(document).ready(function() {
test("#1206 - Strip leading slash before location.assign.", 1, function() { test("#1206 - Strip leading slash before location.assign.", 1, function() {
Backbone.history.stop(); Backbone.history.stop();
location.replace('http://example.com/root/'); location.replace('http://example.com/root/');
Backbone.history = new Backbone.History({location: location}); Backbone.history = _.extend(new Backbone.History, {location: location});
Backbone.history.start({hashChange: false, root: '/root/'}); Backbone.history.start({hashChange: false, root: '/root/'});
location.assign = function(pathname) { location.assign = function(pathname) {
strictEqual(pathname, '/root/fragment'); strictEqual(pathname, '/root/fragment');
@@ -292,7 +306,7 @@ $(document).ready(function() {
test("#1387 - Root fragment without trailing slash.", 1, function() { test("#1387 - Root fragment without trailing slash.", 1, function() {
Backbone.history.stop(); Backbone.history.stop();
location.replace('http://example.com/root'); location.replace('http://example.com/root');
Backbone.history = new Backbone.History({location: location}); Backbone.history = _.extend(new Backbone.History, {location: location});
Backbone.history.start({hashChange: false, root: '/root/', silent: true}); Backbone.history.start({hashChange: false, root: '/root/', silent: true});
strictEqual(Backbone.history.getFragment(), ''); strictEqual(Backbone.history.getFragment(), '');
}); });
@@ -300,7 +314,7 @@ $(document).ready(function() {
test("#1366 - History does not prepend root to fragment.", 2, function() { test("#1366 - History does not prepend root to fragment.", 2, function() {
Backbone.history.stop(); Backbone.history.stop();
location.replace('http://example.com/root/'); location.replace('http://example.com/root/');
Backbone.history = new Backbone.History({ Backbone.history = _.extend(new Backbone.History, {
location: location, location: location,
history: { history: {
pushState: function(state, title, url) { pushState: function(state, title, url) {
@@ -320,7 +334,7 @@ $(document).ready(function() {
test("Normalize root.", 1, function() { test("Normalize root.", 1, function() {
Backbone.history.stop(); Backbone.history.stop();
location.replace('http://example.com/root'); location.replace('http://example.com/root');
Backbone.history = new Backbone.History({ Backbone.history = _.extend(new Backbone.History, {
location: location, location: location,
history: { history: {
pushState: function(state, title, url) { pushState: function(state, title, url) {
@@ -339,7 +353,7 @@ $(document).ready(function() {
test("Normalize root.", 1, function() { test("Normalize root.", 1, function() {
Backbone.history.stop(); Backbone.history.stop();
location.replace('http://example.com/root#fragment'); location.replace('http://example.com/root#fragment');
Backbone.history = new Backbone.History({ Backbone.history = _.extend(new Backbone.History, {
location: location, location: location,
history: { history: {
pushState: function(state, title, url) {}, pushState: function(state, title, url) {},
@@ -357,7 +371,7 @@ $(document).ready(function() {
test("Normalize root.", 1, function() { test("Normalize root.", 1, function() {
Backbone.history.stop(); Backbone.history.stop();
location.replace('http://example.com/root'); location.replace('http://example.com/root');
Backbone.history = new Backbone.History({location: location}); Backbone.history = _.extend(new Backbone.History, {location: location});
Backbone.history.loadUrl = function() { ok(true); }; Backbone.history.loadUrl = function() { ok(true); };
Backbone.history.start({ Backbone.history.start({
pushState: true, pushState: true,
@@ -368,7 +382,7 @@ $(document).ready(function() {
test("Normalize root - leading slash.", 1, function() { test("Normalize root - leading slash.", 1, function() {
Backbone.history.stop(); Backbone.history.stop();
location.replace('http://example.com/root'); location.replace('http://example.com/root');
Backbone.history = new Backbone.History({ Backbone.history = _.extend(new Backbone.History, {
location: location, location: location,
history: { history: {
pushState: function(){}, pushState: function(){},
@@ -382,7 +396,7 @@ $(document).ready(function() {
test("Transition from hashChange to pushState.", 1, function() { test("Transition from hashChange to pushState.", 1, function() {
Backbone.history.stop(); Backbone.history.stop();
location.replace('http://example.com/root#x/y'); location.replace('http://example.com/root#x/y');
Backbone.history = new Backbone.History({ Backbone.history = _.extend(new Backbone.History, {
location: location, location: location,
history: { history: {
pushState: function(){}, pushState: function(){},
@@ -400,7 +414,7 @@ $(document).ready(function() {
test("#1619: Router: Normalize empty root", 1, function() { test("#1619: Router: Normalize empty root", 1, function() {
Backbone.history.stop(); Backbone.history.stop();
location.replace('http://example.com/'); location.replace('http://example.com/');
Backbone.history = new Backbone.History({ Backbone.history = _.extend(new Backbone.History, {
location: location, location: location,
history: { history: {
pushState: function(){}, pushState: function(){},
@@ -414,7 +428,7 @@ $(document).ready(function() {
test("#1619: Router: nagivate with empty root", 1, function() { test("#1619: Router: nagivate with empty root", 1, function() {
Backbone.history.stop(); Backbone.history.stop();
location.replace('http://example.com/'); location.replace('http://example.com/');
Backbone.history = new Backbone.History({ Backbone.history = _.extend(new Backbone.History, {
location: location, location: location,
history: { history: {
pushState: function(state, title, url) { pushState: function(state, title, url) {
@@ -436,7 +450,7 @@ $(document).ready(function() {
location.replace = function(url) { location.replace = function(url) {
strictEqual(url, '/root/?a=b#x/y'); strictEqual(url, '/root/?a=b#x/y');
}; };
Backbone.history = new Backbone.History({ Backbone.history = _.extend(new Backbone.History, {
location: location, location: location,
history: { history: {
pushState: null, pushState: null,
@@ -449,4 +463,22 @@ $(document).ready(function() {
}); });
}); });
test("#1695 - hashChange to pushState with search.", 1, function() {
Backbone.history.stop();
location.replace('http://example.com/root?a=b#x/y');
Backbone.history = _.extend(new Backbone.History, {
location: location,
history: {
pushState: function(){},
replaceState: function(state, title, url){
strictEqual(url, '/root/x/y?a=b');
}
}
});
Backbone.history.start({
root: 'root',
pushState: true
});
});
}); });

View File

@@ -305,6 +305,11 @@ $(document).ready(function() {
view.$el.click(); view.$el.click();
}); });
test("dispose with non Backbone objects", 0, function() {
var view = new Backbone.View({model: {}, collection: {}});
view.dispose();
});
test("view#remove calls dispose.", 1, function() { test("view#remove calls dispose.", 1, function() {
var view = new Backbone.View(); var view = new Backbone.View();
@@ -312,4 +317,15 @@ $(document).ready(function() {
view.remove(); view.remove();
}); });
test("Provide function for el.", 1, function() {
var View = Backbone.View.extend({
el: function() {
return "<p><a></a></p>";
}
});
var view = new View;
ok(view.$el.is('p:has(a)'));
});
}); });

View File

@@ -1,4 +1,5 @@
# Benchmark.js <sup>v1.0.0</sup> # Benchmark.js <sup>v1.0.0</sup>
[![build status](https://secure.travis-ci.org/bestiejs/benchmark.js.png)](http://travis-ci.org/bestiejs/benchmark.js)
A [robust](http://calendar.perfplanet.com/2010/bulletproof-javascript-benchmarks/ "Bulletproof JavaScript benchmarks") benchmarking library that works on nearly all JavaScript platforms<sup><a name="fnref1" href="#fn1">1</a></sup>, supports high-resolution timers, and returns statistically significant results. As seen on [jsPerf](http://jsperf.com/). A [robust](http://calendar.perfplanet.com/2010/bulletproof-javascript-benchmarks/ "Bulletproof JavaScript benchmarks") benchmarking library that works on nearly all JavaScript platforms<sup><a name="fnref1" href="#fn1">1</a></sup>, supports high-resolution timers, and returns statistically significant results. As seen on [jsPerf](http://jsperf.com/).

View File

@@ -22,10 +22,11 @@ class Alias {
* @param {String} $name The alias name. * @param {String} $name The alias name.
* @param {Object} $owner The alias owner. * @param {Object} $owner The alias owner.
*/ */
public function __construct($name, $owner) { public function __construct( $name, $owner ) {
$this->owner = $owner; $this->owner = $owner;
$this->_name = $name; $this->_name = $name;
$this->_call = $owner->getCall(); $this->_call = $owner->getCall();
$this->_category = $owner->getCategory();
$this->_desc = $owner->getDesc(); $this->_desc = $owner->getDesc();
$this->_example = $owner->getExample(); $this->_example = $owner->getExample();
$this->_lineNumber = $owner->getLineNumber(); $this->_lineNumber = $owner->getLineNumber();
@@ -65,6 +66,16 @@ class Alias {
return $this->_call; return $this->_call;
} }
/**
* Extracts the owner entry's `category` data.
*
* @memberOf Alias
* @returns {String} The owner entry's `category` data.
*/
public function getCategory() {
return $this->_category;
}
/** /**
* Extracts the owner entry's description. * Extracts the owner entry's description.
* *

View File

@@ -152,6 +152,27 @@ class Entry {
return $this->_call; return $this->_call;
} }
/**
* Extracts the entry's `category` data.
*
* @memberOf Entry
* @returns {String} The entry's `category` data.
*/
public function getCategory() {
if (isset($this->_category)) {
return $this->_category;
}
preg_match('#\* *@category\s+([^\n]+)#', $this->entry, $result);
if (count($result)) {
$result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
} else {
$result = $this->getType() == 'Function' ? 'Methods' : 'Properties';
}
$this->_category = $result;
return $result;
}
/** /**
* Extracts the entry's description. * Extracts the entry's description.
* *

View File

@@ -7,6 +7,15 @@ require(dirname(__FILE__) . "/Entry.php");
*/ */
class Generator { class Generator {
/**
* The HTML for the close tag.
*
* @static
* @memberOf Generator
* @type String
*/
public $closeTag = "\n<!-- /div -->\n";
/** /**
* An array of JSDoc entries. * An array of JSDoc entries.
* *
@@ -15,6 +24,15 @@ class Generator {
*/ */
public $entries = array(); public $entries = array();
/**
* The HTML for the open tag.
*
* @static
* @memberOf Generator
* @type String
*/
public $openTag = "\n<!-- div -->\n";
/** /**
* An options array used to configure the generator. * An options array used to configure the generator.
* *
@@ -24,7 +42,7 @@ class Generator {
public $options = array(); public $options = array();
/** /**
* The entire file's source code. * The file's source code.
* *
* @memberOf Generator * @memberOf Generator
* @type String * @type String
@@ -65,6 +83,9 @@ class Generator {
if (!isset($options['lang'])) { if (!isset($options['lang'])) {
$options['lang'] = 'js'; $options['lang'] = 'js';
} }
if (!isset($options['toc'])) {
$options['toc'] = 'properties';
}
$this->options = $options; $this->options = $options;
$this->source = str_replace(PHP_EOL, "\n", $options['source']); $this->source = str_replace(PHP_EOL, "\n", $options['source']);
@@ -86,7 +107,7 @@ class Generator {
* @param {String} $string The string to format. * @param {String} $string The string to format.
* @returns {String} The formatted string. * @returns {String} The formatted string.
*/ */
private static function format($string) { private static function format( $string ) {
$counter = 0; $counter = 0;
// tokenize inline code snippets // tokenize inline code snippets
@@ -121,7 +142,7 @@ class Generator {
* @param {Array|Object} $object The template object. * @param {Array|Object} $object The template object.
* @returns {String} The modified string. * @returns {String} The modified string.
*/ */
private static function interpolate($string, $object) { private static function interpolate( $string, $object ) {
preg_match_all('/#\{([^}]+)\}/', $string, $tokens); preg_match_all('/#\{([^}]+)\}/', $string, $tokens);
$tokens = array_unique(array_pop($tokens)); $tokens = array_unique(array_pop($tokens));
@@ -149,6 +170,63 @@ class Generator {
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
/**
* Adds the given `$entries` to the `$result` array.
*
* @private
* @memberOf Generator
* @param {Array} $result The result array to modify.
* @param {Array} $entries The entries to add to the `$result`.
*/
private function addEntries( &$result, $entries ) {
foreach ($entries as $entry) {
// skip aliases
if ($entry->isAlias()) {
continue;
}
// name and description
array_push(
$result,
$this->openTag,
Generator::interpolate("### <a id=\"#{hash}\"></a>`#{member}#{separator}#{call}`\n<a href=\"##{hash}\">#</a> [&#x24C8;](#{href} \"View in source\") [&#x24C9;][1]\n\n#{desc}", $entry)
);
// @alias
if (count($aliases = $entry->getAliases())) {
array_push($result, '', '#### Aliases');
foreach ($aliases as $index => $alias) {
$aliases[$index] = $alias->getName();
}
$result[] = '*' . implode(', ', $aliases) . '*';
}
// @param
if (count($params = $entry->getParams())) {
array_push($result, '', '#### Arguments');
foreach ($params as $index => $param) {
$result[] = Generator::interpolate('#{num}. `#{name}` (#{type}): #{desc}', array(
'desc' => $param[2],
'name' => $param[1],
'num' => $index + 1,
'type' => $param[0]
));
}
}
// @returns
if (count($returns = $entry->getReturns())) {
array_push(
$result, '',
'#### Returns',
Generator::interpolate('(#{type}): #{desc}', array('desc' => $returns[1], 'type' => $returns[0]))
);
}
// @example
if ($example = $entry->getExample()) {
array_push($result, '', '#### Example', $example);
}
array_push($result, "\n* * *", $this->closeTag);
}
}
/** /**
* Resolves the entry's hash used to navigate the documentation. * Resolves the entry's hash used to navigate the documentation.
* *
@@ -204,9 +282,11 @@ class Generator {
*/ */
public function generate() { public function generate() {
$api = array(); $api = array();
$byCategory = $this->options['toc'] == 'categories';
$categories = array();
$closeTag = $this->closeTag;
$compiling = false; $compiling = false;
$openTag = "\n<!-- div -->\n"; $openTag = $this->openTag;
$closeTag = "\n<!-- /div -->\n";
$result = array('# ' . $this->options['title']); $result = array('# ' . $this->options['title']);
$toc = 'toc'; $toc = 'toc';
@@ -223,14 +303,14 @@ class Generator {
foreach ($members as $member) { foreach ($members as $member) {
// create api category arrays // create api category arrays
if (!isset($api[$member]) && $member) { if ($member && !isset($api[$member])) {
// create temporary entry to be replaced later // create temporary entry to be replaced later
$api[$member] = new Entry('', '', $entry->lang); $api[$member] = new stdClass;
$api[$member]->static = array(); $api[$member]->static = array();
$api[$member]->plugin = array(); $api[$member]->plugin = array();
} }
// append entry to api category // append entry to api member
if (!$member || $entry->isCtor() || ($entry->getType() == 'Object' && if (!$member || $entry->isCtor() || ($entry->getType() == 'Object' &&
!preg_match('/[=:]\s*(?:null|undefined)\s*[,;]?$/', $entry->entry))) { !preg_match('/[=:]\s*(?:null|undefined)\s*[,;]?$/', $entry->entry))) {
@@ -261,6 +341,26 @@ class Generator {
} }
} }
// add properties to each entry
foreach ($api as $entry) {
$entry->hash = $this->getHash($entry);
$entry->href = $this->getLineUrl($entry);
$member = $entry->getMembers(0);
$member = ($member ? $member . ($entry->isPlugin() ? '.prototype.' : '.') : '') . $entry->getName();
$entry->member = preg_replace('/' . $entry->getName() . '$/', '', $member);
// add properties to static and plugin sub-entries
foreach (array('static', 'plugin') as $kind) {
foreach ($entry->{$kind} as $subentry) {
$subentry->hash = $this->getHash($subentry);
$subentry->href = $this->getLineUrl($subentry);
$subentry->member = $member;
$subentry->separator = $this->getSeparator($subentry);
}
}
}
/*------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/
// custom sort for root level entries // custom sort for root level entries
@@ -268,19 +368,19 @@ class Generator {
function sortCompare($a, $b) { function sortCompare($a, $b) {
$score = array( 'a' => 0, 'b' => 0); $score = array( 'a' => 0, 'b' => 0);
foreach (array( 'a' => $a, 'b' => $b) as $key => $value) { foreach (array( 'a' => $a, 'b' => $b) as $key => $value) {
// capitalized keys that represent constructor properties are last // capitalized properties are last
if (preg_match('/[#.][A-Z]/', $value)) { if (preg_match('/[#.][A-Z]/', $value)) {
$score[$key] = 0; $score[$key] = 0;
} }
// lowercase keys with prototype properties are next to last // lowercase prototype properties are next to last
else if (preg_match('/#[a-z]/', $value)) { else if (preg_match('/#[a-z]/', $value)) {
$score[$key] = 1; $score[$key] = 1;
} }
// lowercase keys with static properties next to first // lowercase static properties next to first
else if (preg_match('/\.[a-z]/', $value)) { else if (preg_match('/\.[a-z]/', $value)) {
$score[$key] = 2; $score[$key] = 2;
} }
// lowercase keys with no properties are first // root properties are first
else if (preg_match('/^[^#.]+$/', $value)) { else if (preg_match('/^[^#.]+$/', $value)) {
$score[$key] = 3; $score[$key] = 3;
} }
@@ -310,48 +410,90 @@ class Generator {
/*------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/
// add categories
foreach ($api as $entry) {
$categories[$entry->getCategory()][] = $entry;
foreach (array('static', 'plugin') as $kind) {
foreach ($entry->{$kind} as $subentry) {
$categories[$subentry->getCategory()][] = $subentry;
}
}
}
// sort categories
ksort($categories);
foreach(array('Methods', 'Properties') as $category) {
if (isset($categories[$category])) {
$entries = $categories[$category];
unset($categories[$category]);
$categories[$category] = $entries;
}
}
/*------------------------------------------------------------------------*/
// compile TOC // compile TOC
$result[] = $openTag; $result[] = $openTag;
foreach ($api as $key => $entry) { // compile TOC by categories
$entry->hash = $this->getHash($entry); if ($byCategory) {
$entry->href = $this->getLineUrl($entry); foreach ($categories as $category => $entries) {
if ($compiling) {
$member = $entry->getMembers(0); $result[] = $closeTag;
$member = ($member ? $member . ($entry->isPlugin() ? '.prototype.' : '.') : '') . $entry->getName(); } else {
$compiling = true;
$entry->member = preg_replace('/' . $entry->getName() . '$/', '', $member);
$compiling = $compiling ? ($result[] = $closeTag) : true;
// assign TOC hash
if (count($result) == 2) {
$toc = $member;
}
// add root entry
array_push(
$result,
$openTag, '## ' . (count($result) == 2 ? '<a id="' . $toc . '"></a>' : '') . '`' . $member . '`',
Generator::interpolate('* [`' . $member . '`](##{hash})', $entry)
);
// add static and plugin sub-entries
foreach (array('static', 'plugin') as $kind) {
if ($kind == 'plugin' && count($entry->plugin)) {
array_push(
$result,
$closeTag,
$openTag,
'## `' . $member . ($entry->isCtor() ? '.prototype`' : '`')
);
} }
foreach ($entry->{$kind} as $subentry) { // assign TOC hash
$subentry->hash = $this->getHash($subentry); if (count($result) == 2) {
$subentry->href = $this->getLineUrl($subentry); $toc = $category;
$subentry->member = $member; }
$subentry->separator = $this->getSeparator($subentry); // add category
$result[] = Generator::interpolate('* [`#{member}#{separator}#{name}`](##{hash})', $subentry); array_push(
$result,
$openTag, '## ' . (count($result) == 2 ? '<a id="' . $toc . '"></a>' : '') . '`' . $category . '`'
);
// add entries
foreach ($entries as $entry) {
$result[] = Generator::interpolate('* [`#{member}#{separator}#{name}`](##{hash})', $entry);
}
}
}
// compile TOC by namespace
else {
foreach ($api as $entry) {
if ($compiling) {
$result[] = $closeTag;
} else {
$compiling = true;
}
$member = $entry->member . $entry->getName();
// assign TOC hash
if (count($result) == 2) {
$toc = $member;
}
// add root entry
array_push(
$result,
$openTag, '## ' . (count($result) == 2 ? '<a id="' . $toc . '"></a>' : '') . '`' . $member . '`',
Generator::interpolate('* [`' . $member . '`](##{hash})', $entry)
);
// add static and plugin sub-entries
foreach (array('static', 'plugin') as $kind) {
if ($kind == 'plugin' && count($entry->plugin)) {
array_push(
$result,
$closeTag,
$openTag,
'## `' . $member . ($entry->isCtor() ? '.prototype`' : '`')
);
}
foreach ($entry->{$kind} as $subentry) {
$subentry->member = $member;
$result[] = Generator::interpolate('* [`#{member}#{separator}#{name}`](##{hash})', $subentry);
}
} }
} }
} }
@@ -364,81 +506,51 @@ class Generator {
$compiling = false; $compiling = false;
$result[] = $openTag; $result[] = $openTag;
foreach ($api as $entry) { if ($byCategory) {
// skip aliases foreach ($categories as $category => $entries) {
if ($entry->isAlias()) { if ($compiling) {
continue; $result[] = $closeTag;
} } else {
$compiling = true;
// add root entry
$member = $entry->member . $entry->getName();
$compiling = $compiling ? ($result[] = $closeTag) : true;
array_push($result, $openTag, '## `' . $member . '`');
foreach (array($entry, 'static', 'plugin') as $kind) {
$subentries = is_string($kind) ? $entry->{$kind} : array($kind);
// title
if ($kind != 'static' && $entry->getType() != 'Object' &&
count($subentries) && $subentries[0] != $kind) {
if ($kind == 'plugin') {
$result[] = $closeTag;
}
array_push(
$result,
$openTag,
'## `' . $member . ($kind == 'plugin' ? '.prototype`' : '`')
);
} }
if ($category != 'Methods' && $category != 'Properties') {
$category = '“' . $category . '” Methods';
}
array_push($result, $openTag, '## `' . $category . '`');
$this->addEntries($result, $entries);
}
}
else {
foreach ($api as $entry) {
// skip aliases
if ($entry->isAlias()) {
continue;
}
if ($compiling) {
$result[] = $closeTag;
} else {
$compiling = true;
}
// add root entry name
$member = $entry->member . $entry->getName();
array_push($result, $openTag, '## `' . $member . '`');
// body foreach (array($entry, 'static', 'plugin') as $kind) {
foreach ($subentries as $subentry) { $subentries = is_string($kind) ? $entry->{$kind} : array($kind);
// skip aliases
if ($subentry->isAlias()) {
continue;
}
// description // add sub-entry name
array_push( if ($kind != 'static' && $entry->getType() != 'Object' &&
$result, count($subentries) && $subentries[0] != $kind) {
$openTag, if ($kind == 'plugin') {
Generator::interpolate("### <a id=\"#{hash}\"></a>`#{member}#{separator}#{call}`\n<a href=\"##{hash}\">#</a> [&#x24C8;](#{href} \"View in source\") [&#x24C9;][1]\n\n#{desc}", $subentry) $result[] = $closeTag;
);
// @alias
if (count($aliases = $subentry->getAliases())) {
array_push($result, '', '#### Aliases');
foreach ($aliases as $index => $alias) {
$aliases[$index] = $alias->getName();
} }
$result[] = '*' . implode(', ', $aliases) . '*';
}
// @param
if (count($params = $subentry->getParams())) {
array_push($result, '', '#### Arguments');
foreach ($params as $index => $param) {
$result[] = Generator::interpolate('#{num}. `#{name}` (#{type}): #{desc}', array(
'desc' => $param[2],
'name' => $param[1],
'num' => $index + 1,
'type' => $param[0]
));
}
}
// @returns
if (count($returns = $subentry->getReturns())) {
array_push( array_push(
$result, '', $result,
'#### Returns', $openTag,
Generator::interpolate('(#{type}): #{desc}', array('desc' => $returns[1], 'type' => $returns[0])) '## `' . $member . ($kind == 'plugin' ? '.prototype`' : '`')
); );
} }
// @example $this->addEntries($result, $subentries);
if ($example = $subentry->getExample()) {
array_push($result, '', '#### Example', $example);
}
array_push($result, "\n* * *", $closeTag);
} }
} }
} }

View File

@@ -830,10 +830,10 @@
}; };
} }
// add browser/OS architecture // add browser/OS architecture
if ((data = / (?:AMD|IA|Win|WOW|x86_|x)64\b/i.exec(arch)) && !/\bi686\b/i.test(arch)) { if ((data = /\b(?:AMD|IA|Win|WOW|x86_|x)64\b/i.exec(arch)) && !/\bi686\b/i.test(arch)) {
if (os) { if (os) {
os.architecture = 64; os.architecture = 64;
os.family = os.family.replace(data, ''); os.family = os.family.replace(RegExp(' *' + data), '');
} }
if (name && (/WOW64/i.test(ua) || if (name && (/WOW64/i.test(ua) ||
(useFeatures && /\w(?:86|32)$/.test(nav.cpuClass || nav.platform)))) { (useFeatures && /\w(?:86|32)$/.test(nav.cpuClass || nav.platform)))) {

View File

@@ -171,7 +171,12 @@
// exit out of Node.js // exit out of Node.js
try { try {
process.exit(); if (details.failed) {
console.error('Error: ' + details.failed + ' of ' + details.total + ' tests failed.');
process.exit(1);
} else {
process.exit(0);
}
} catch(e) { } } catch(e) { }
} }

View File

@@ -1,4 +1,4 @@
[QUnit](http://qunitjs.com) - A JavaScript Unit Testing framework. [QUnit](http://docs.jquery.com/QUnit) - A JavaScript Unit Testing framework.
================================ ================================
QUnit is a powerful, easy-to-use, JavaScript test suite. It's used by the jQuery QUnit is a powerful, easy-to-use, JavaScript test suite. It's used by the jQuery
@@ -35,8 +35,7 @@ the change, run `grunt` to lint and test it, then commit, push and create a pull
Include some background for the change in the commit message and `Fixes #nnn`, referring Include some background for the change in the commit message and `Fixes #nnn`, referring
to the issue number you're addressing. to the issue number you're addressing.
To run `grunt`, you need `node` and `npm`, then `npm install grunt -g`. That gives you a global To run `grunt`, you need `node` and `npm`, then `npm install grunt -g`.
grunt binary. For additional grunt tasks, also run `npm install`.
Releases Releases
-------- --------
@@ -48,12 +47,3 @@ tag, update them again to the next version, commit and push commits and tags
Put the 'v' in front of the tag, e.g. `v1.8.0`. Clean up the changelog, removing merge commits Put the 'v' in front of the tag, e.g. `v1.8.0`. Clean up the changelog, removing merge commits
or whitespace cleanups. or whitespace cleanups.
To upload to code.jquery.com (replace $version accordingly):
scp -q qunit/qunit.js jqadmin@code.origin.jquery.com:/var/www/html/code.jquery.com/qunit/qunit-$version.js
scp -q qunit/qunit.css jqadmin@code.origin.jquery.com:/var/www/html/code.jquery.com/qunit/qunit-$version.css
Then update /var/www/html/code.jquery.com/index.html and purge it with:
curl -s http://code.origin.jquery.com/?reload

View File

@@ -1,11 +1,11 @@
/** /**
* QUnit v1.10.0 - A JavaScript Unit Testing Framework * QUnit v1.9.0 - A JavaScript Unit Testing Framework
* *
* http://qunitjs.com * http://docs.jquery.com/QUnit
* *
* Copyright 2012 jQuery Foundation and other contributors * Copyright (c) 2012 John Resig, Jörn Zaefferer
* Released under the MIT license. * Dual licensed under the MIT (MIT-LICENSE.txt)
* http://jquery.org/license * or GPL (GPL-LICENSE.txt) licenses.
*/ */
/** Font Family and Sizes */ /** Font Family and Sizes */
@@ -20,7 +20,7 @@
/** Resets */ /** Resets */
#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
@@ -67,7 +67,6 @@
padding: 0.5em 0 0.5em 2em; padding: 0.5em 0 0.5em 2em;
color: #5E740B; color: #5E740B;
background-color: #eee; background-color: #eee;
overflow: hidden;
} }
#qunit-userAgent { #qunit-userAgent {
@@ -77,9 +76,6 @@
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
} }
#qunit-modulefilter-container {
float: right;
}
/** Tests: Pass/Fail */ /** Tests: Pass/Fail */

View File

@@ -1,11 +1,11 @@
/** /**
* QUnit v1.10.0 - A JavaScript Unit Testing Framework * QUnit v1.9.0 - A JavaScript Unit Testing Framework
* *
* http://qunitjs.com * http://docs.jquery.com/QUnit
* *
* Copyright 2012 jQuery Foundation and other contributors * Copyright (c) 2012 John Resig, Jörn Zaefferer
* Released under the MIT license. * Dual licensed under the MIT (MIT-LICENSE.txt)
* http://jquery.org/license * or GPL (GPL-LICENSE.txt) licenses.
*/ */
(function( window ) { (function( window ) {
@@ -17,8 +17,6 @@ var QUnit,
fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""), fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""),
toString = Object.prototype.toString, toString = Object.prototype.toString,
hasOwn = Object.prototype.hasOwnProperty, hasOwn = Object.prototype.hasOwnProperty,
// Keep a local reference to Date (GH-283)
Date = window.Date,
defined = { defined = {
setTimeout: typeof window.setTimeout !== "undefined", setTimeout: typeof window.setTimeout !== "undefined",
sessionStorage: (function() { sessionStorage: (function() {
@@ -306,8 +304,7 @@ QUnit = {
// call on start of module test to prepend name to all tests // call on start of module test to prepend name to all tests
module: function( name, testEnvironment ) { module: function( name, testEnvironment ) {
config.currentModule = name; config.currentModule = name;
config.currentModuleTestEnvironment = testEnvironment; config.currentModuleTestEnviroment = testEnvironment;
config.modules[name] = true;
}, },
asyncTest: function( testName, expected, callback ) { asyncTest: function( testName, expected, callback ) {
@@ -339,7 +336,7 @@ QUnit = {
async: async, async: async,
callback: callback, callback: callback,
module: config.currentModule, module: config.currentModule,
moduleTestEnvironment: config.currentModuleTestEnvironment, moduleTestEnvironment: config.currentModuleTestEnviroment,
stack: sourceFromStacktrace( 2 ) stack: sourceFromStacktrace( 2 )
}); });
@@ -352,11 +349,7 @@ QUnit = {
// Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
expect: function( asserts ) { expect: function( asserts ) {
if (arguments.length === 1) { config.current.expected = asserts;
config.current.expected = asserts;
} else {
return config.current.expected;
}
}, },
start: function( count ) { start: function( count ) {
@@ -422,8 +415,6 @@ QUnit.assert = {
var source, var source,
details = { details = {
module: config.current.module,
name: config.current.testName,
result: result, result: result,
message: msg message: msg
}; };
@@ -609,9 +600,6 @@ config = {
} }
], ],
// Set of all modules.
modules: {},
// logging callback queues // logging callback queues
begin: [], begin: [],
done: [], done: [],
@@ -722,10 +710,17 @@ extend( QUnit, {
}, },
// Resets the test setup. Useful for tests that modify the DOM. // Resets the test setup. Useful for tests that modify the DOM.
// If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
reset: function() { reset: function() {
var fixture = id( "qunit-fixture" ); var fixture;
if ( fixture ) {
fixture.innerHTML = config.fixture; if ( window.jQuery ) {
jQuery( "#qunit-fixture" ).html( config.fixture );
} else {
fixture = id( "qunit-fixture" );
if ( fixture ) {
fixture.innerHTML = config.fixture;
}
} }
}, },
@@ -786,8 +781,6 @@ extend( QUnit, {
var output, source, var output, source,
details = { details = {
module: config.current.module,
name: config.current.testName,
result: result, result: result,
message: message, message: message,
actual: actual, actual: actual,
@@ -833,8 +826,6 @@ extend( QUnit, {
var output, var output,
details = { details = {
module: config.current.module,
name: config.current.testName,
result: false, result: false,
message: message message: message
}; };
@@ -925,9 +916,7 @@ QUnit.load = function() {
runLoggingCallbacks( "begin", QUnit, {} ); runLoggingCallbacks( "begin", QUnit, {} );
// Initialize the config, saving the execution queue // Initialize the config, saving the execution queue
var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, urlConfigCheckboxes, moduleFilter, var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, urlConfigCheckboxes,
numModules = 0,
moduleFilterHtml = "",
urlConfigHtml = "", urlConfigHtml = "",
oldconfig = extend( {}, config ); oldconfig = extend( {}, config );
@@ -951,15 +940,6 @@ QUnit.load = function() {
urlConfigHtml += "<input id='qunit-urlconfig-" + val.id + "' name='" + val.id + "' type='checkbox'" + ( config[ val.id ] ? " checked='checked'" : "" ) + " title='" + val.tooltip + "'><label for='qunit-urlconfig-" + val.id + "' title='" + val.tooltip + "'>" + val.label + "</label>"; urlConfigHtml += "<input id='qunit-urlconfig-" + val.id + "' name='" + val.id + "' type='checkbox'" + ( config[ val.id ] ? " checked='checked'" : "" ) + " title='" + val.tooltip + "'><label for='qunit-urlconfig-" + val.id + "' title='" + val.tooltip + "'>" + val.label + "</label>";
} }
moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label><select id='qunit-modulefilter' name='modulefilter'><option value='' " + ( config.module === undefined ? "selected" : "" ) + ">< All Modules ></option>";
for ( i in config.modules ) {
if ( config.modules.hasOwnProperty( i ) ) {
numModules += 1;
moduleFilterHtml += "<option value='" + encodeURIComponent(i) + "' " + ( config.module === i ? "selected" : "" ) + ">" + i + "</option>";
}
}
moduleFilterHtml += "</select>";
// `userAgent` initialized at top of scope // `userAgent` initialized at top of scope
userAgent = id( "qunit-userAgent" ); userAgent = id( "qunit-userAgent" );
if ( userAgent ) { if ( userAgent ) {
@@ -1022,19 +1002,6 @@ QUnit.load = function() {
window.location = QUnit.url( params ); window.location = QUnit.url( params );
}); });
toolbar.appendChild( urlConfigCheckboxes ); toolbar.appendChild( urlConfigCheckboxes );
if (numModules > 1) {
moduleFilter = document.createElement( 'span' );
moduleFilter.setAttribute( 'id', 'qunit-modulefilter-container' );
moduleFilter.innerHTML = moduleFilterHtml;
addEvent( moduleFilter, "change", function() {
var selectBox = moduleFilter.getElementsByTagName("select")[0],
selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value);
window.location = QUnit.url( { module: ( selectedModule === "" ) ? undefined : selectedModule } );
});
toolbar.appendChild(moduleFilter);
}
} }
// `main` initialized at top of scope // `main` initialized at top of scope
@@ -1072,9 +1039,9 @@ window.onerror = function ( error, filePath, linerNr ) {
} }
QUnit.pushFailure( error, filePath + ":" + linerNr ); QUnit.pushFailure( error, filePath + ":" + linerNr );
} else { } else {
QUnit.test( "global failure", extend( function() { QUnit.test( "global failure", function() {
QUnit.pushFailure( error, filePath + ":" + linerNr ); QUnit.pushFailure( error, filePath + ":" + linerNr );
}, { validTest: validTest } ) ); });
} }
return false; return false;
} }
@@ -1141,11 +1108,6 @@ function done() {
} }
} }
// scroll back to top to show results
if ( window.scrollTo ) {
window.scrollTo(0, 0);
}
runLoggingCallbacks( "done", QUnit, { runLoggingCallbacks( "done", QUnit, {
failed: config.stats.bad, failed: config.stats.bad,
passed: passed, passed: passed,
@@ -1161,12 +1123,6 @@ function validTest( test ) {
module = config.module && config.module.toLowerCase(), module = config.module && config.module.toLowerCase(),
fullName = (test.module + ": " + test.testName).toLowerCase(); fullName = (test.module + ": " + test.testName).toLowerCase();
// Internally-generated tests are always valid
if ( test.callback && test.callback.validTest === validTest ) {
delete test.callback.validTest;
return true;
}
if ( config.testNumber ) { if ( config.testNumber ) {
return test.testNumber === config.testNumber; return test.testNumber === config.testNumber;
} }
@@ -1448,8 +1404,7 @@ QUnit.equiv = (function() {
a.global === b.global && a.global === b.global &&
// (gmi) ... // (gmi) ...
a.ignoreCase === b.ignoreCase && a.ignoreCase === b.ignoreCase &&
a.multiline === b.multiline && a.multiline === b.multiline;
a.sticky === b.sticky;
}, },
// - skip when the property is a method of an instance (OOP) // - skip when the property is a method of an instance (OOP)

View File

@@ -1,5 +1,5 @@
/** vim: et:ts=4:sw=4:sts=4 /** vim: et:ts=4:sw=4:sts=4
* @license RequireJS 2.0.6 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. * @license RequireJS 2.1.0 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
* Available via the MIT or new BSD license. * Available via the MIT or new BSD license.
* see: http://github.com/jrburke/requirejs for details * see: http://github.com/jrburke/requirejs for details
*/ */
@@ -12,7 +12,7 @@ var requirejs, require, define;
(function (global) { (function (global) {
var req, s, head, baseElement, dataMain, src, var req, s, head, baseElement, dataMain, src,
interactiveScript, currentlyAddingScript, mainScript, subPath, interactiveScript, currentlyAddingScript, mainScript, subPath,
version = '2.0.6', version = '2.1.0',
commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg, commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,
cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g, cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,
jsSuffixRegExp = /\.js$/, jsSuffixRegExp = /\.js$/,
@@ -147,41 +147,6 @@ var requirejs, require, define;
return g; return g;
} }
function makeContextModuleFunc(func, relMap, enableBuildCallback) {
return function () {
//A version of a require function that passes a moduleName
//value for items that may need to
//look up paths relative to the moduleName
var args = aps.call(arguments, 0), lastArg;
if (enableBuildCallback &&
isFunction((lastArg = args[args.length - 1]))) {
lastArg.__requireJsBuild = true;
}
args.push(relMap);
return func.apply(null, args);
};
}
function addRequireMethods(req, context, relMap) {
each([
['toUrl'],
['undef'],
['defined', 'requireDefined'],
['specified', 'requireSpecified']
], function (item) {
var prop = item[1] || item[0];
req[item[0]] = context ? makeContextModuleFunc(context[prop], relMap) :
//If no context, then use default context. Reference from
//contexts instead of early binding to default context, so
//that during builds, the latest instance of the default
//context with its config gets used.
function () {
var ctx = contexts[defContextName];
return ctx[prop].apply(ctx, arguments);
};
});
}
/** /**
* Constructs an error with a pointer to an URL with more information. * Constructs an error with a pointer to an URL with more information.
* @param {String} id the error ID that maps to an ID on a web page. * @param {String} id the error ID that maps to an ID on a web page.
@@ -238,12 +203,7 @@ var requirejs, require, define;
defined = {}, defined = {},
urlFetched = {}, urlFetched = {},
requireCounter = 1, requireCounter = 1,
unnormalizedCounter = 1, unnormalizedCounter = 1;
//Used to track the order in which modules
//should be executed, by the order they
//load. Important for consistent cycle resolution
//behavior.
waitAry = [];
/** /**
* Trims the . and .. from an array of path segments. * Trims the . and .. from an array of path segments.
@@ -405,12 +365,25 @@ var requirejs, require, define;
//Pop off the first array value, since it failed, and //Pop off the first array value, since it failed, and
//retry //retry
pathConfig.shift(); pathConfig.shift();
context.undef(id); context.require.undef(id);
context.require([id]); context.require([id]);
return true; return true;
} }
} }
//Turns a plugin!resource to [plugin, resource]
//with the plugin being undefined if the name
//did not have a plugin prefix.
function splitPrefix(name) {
var prefix,
index = name ? name.indexOf('!') : -1;
if (index > -1) {
prefix = name.substring(0, index);
name = name.substring(index + 1, name.length);
}
return [prefix, name];
}
/** /**
* Creates a module mapping that includes plugin prefix, module * Creates a module mapping that includes plugin prefix, module
* name, and path. If parentModuleMap is provided it will * name, and path. If parentModuleMap is provided it will
@@ -427,8 +400,7 @@ var requirejs, require, define;
* @returns {Object} * @returns {Object}
*/ */
function makeModuleMap(name, parentModuleMap, isNormalized, applyMap) { function makeModuleMap(name, parentModuleMap, isNormalized, applyMap) {
var url, pluginModule, suffix, var url, pluginModule, suffix, nameParts,
index = name ? name.indexOf('!') : -1,
prefix = null, prefix = null,
parentName = parentModuleMap ? parentModuleMap.name : null, parentName = parentModuleMap ? parentModuleMap.name : null,
originalName = name, originalName = name,
@@ -442,10 +414,9 @@ var requirejs, require, define;
name = '_@r' + (requireCounter += 1); name = '_@r' + (requireCounter += 1);
} }
if (index !== -1) { nameParts = splitPrefix(name);
prefix = name.substring(0, index); prefix = nameParts[0];
name = name.substring(index + 1, name.length); name = nameParts[1];
}
if (prefix) { if (prefix) {
prefix = normalize(prefix, parentName, applyMap); prefix = normalize(prefix, parentName, applyMap);
@@ -466,6 +437,15 @@ var requirejs, require, define;
} else { } else {
//A regular module. //A regular module.
normalizedName = normalize(name, parentName, applyMap); normalizedName = normalize(name, parentName, applyMap);
//Normalized name may be a plugin ID due to map config
//application in normalize. The map config values must
//already be normalized, so do not need to redo that part.
nameParts = splitPrefix(normalizedName);
prefix = nameParts[0];
normalizedName = nameParts[1];
isNormalized = true;
url = context.nameToUrl(normalizedName); url = context.nameToUrl(normalizedName);
} }
} }
@@ -557,148 +537,71 @@ var requirejs, require, define;
} }
} }
/**
* Helper function that creates a require function object to give to
* modules that ask for it as a dependency. It needs to be specific
* per module because of the implication of path mappings that may
* need to be relative to the module name.
*/
function makeRequire(mod, enableBuildCallback, altRequire) {
var relMap = mod && mod.map,
modRequire = makeContextModuleFunc(altRequire || context.require,
relMap,
enableBuildCallback);
addRequireMethods(modRequire, context, relMap);
modRequire.isBrowser = isBrowser;
return modRequire;
}
handlers = { handlers = {
'require': function (mod) { 'require': function (mod) {
return makeRequire(mod); if (mod.require) {
return mod.require;
} else {
return (mod.require = context.makeRequire(mod.map));
}
}, },
'exports': function (mod) { 'exports': function (mod) {
mod.usingExports = true; mod.usingExports = true;
if (mod.map.isDefine) { if (mod.map.isDefine) {
return (mod.exports = defined[mod.map.id] = {}); if (mod.exports) {
return mod.exports;
} else {
return (mod.exports = defined[mod.map.id] = {});
}
} }
}, },
'module': function (mod) { 'module': function (mod) {
return (mod.module = { if (mod.module) {
id: mod.map.id, return mod.module;
uri: mod.map.url, } else {
config: function () { return (mod.module = {
return (config.config && config.config[mod.map.id]) || {}; id: mod.map.id,
}, uri: mod.map.url,
exports: defined[mod.map.id] config: function () {
}); return (config.config && config.config[mod.map.id]) || {};
},
exports: defined[mod.map.id]
});
}
} }
}; };
function removeWaiting(id) { function cleanRegistry(id) {
//Clean up machinery used for waiting modules. //Clean up machinery used for waiting modules.
delete registry[id]; delete registry[id];
each(waitAry, function (mod, i) {
if (mod.map.id === id) {
waitAry.splice(i, 1);
if (!mod.defined) {
context.waitCount -= 1;
}
return true;
}
});
} }
function findCycle(mod, traced, processed) { function breakCycle(mod, traced, processed) {
var id = mod.map.id, var id = mod.map.id;
depArray = mod.depMaps,
foundModule;
//Do not bother with unitialized modules or not yet enabled if (mod.error) {
//modules. mod.emit('error', mod.error);
if (!mod.inited) { } else {
return; traced[id] = true;
} each(mod.depMaps, function (depMap, i) {
var depId = depMap.id,
dep = registry[depId];
//Found the cycle. //Only force things that have not completed
if (traced[id]) { //being defined, so still in the registry,
return mod; //and only if it has not been matched up
} //in the module already.
if (dep && !mod.depMatched[i] && !processed[depId]) {
traced[id] = true; if (traced[depId]) {
mod.defineDep(i, defined[depId]);
//Trace through the dependencies. mod.check(); //pass false?
each(depArray, function (depMap) { } else {
var depId = depMap.id, breakCycle(dep, traced, processed);
depMod = registry[depId]; }
if (!depMod || processed[depId] ||
!depMod.inited || !depMod.enabled) {
return;
}
return (foundModule = findCycle(depMod, traced, processed));
});
processed[id] = true;
return foundModule;
}
function forceExec(mod, traced, uninited) {
var id = mod.map.id,
depArray = mod.depMaps;
if (!mod.inited || !mod.map.isDefine) {
return;
}
if (traced[id]) {
return defined[id];
}
traced[id] = mod;
each(depArray, function (depMap) {
var depId = depMap.id,
depMod = registry[depId],
value;
if (handlers[depId]) {
return;
}
if (depMod) {
if (!depMod.inited || !depMod.enabled) {
//Dependency is not inited,
//so this module cannot be
//given a forced value yet.
uninited[id] = true;
return;
} }
});
//Get the value for the current dependency processed[id] = true;
value = forceExec(depMod, traced, uninited); }
//Even with forcing it may not be done,
//in particular if the module is waiting
//on a plugin resource.
if (!uninited[depId]) {
mod.defineDepById(depId, value);
}
}
});
mod.check(true);
return defined[id];
}
function modCheck(mod) {
mod.check();
} }
function checkLoaded() { function checkLoaded() {
@@ -707,6 +610,7 @@ var requirejs, require, define;
//It is possible to disable the wait interval by using waitSeconds of 0. //It is possible to disable the wait interval by using waitSeconds of 0.
expired = waitInterval && (context.startTime + waitInterval) < new Date().getTime(), expired = waitInterval && (context.startTime + waitInterval) < new Date().getTime(),
noLoads = [], noLoads = [],
reqCalls = [],
stillLoading = false, stillLoading = false,
needCycleCheck = true; needCycleCheck = true;
@@ -727,6 +631,10 @@ var requirejs, require, define;
return; return;
} }
if (!map.isDefine) {
reqCalls.push(mod);
}
if (!mod.error) { if (!mod.error) {
//If the module should be executed, and it has not //If the module should be executed, and it has not
//been inited and time is up, remember it. //been inited and time is up, remember it.
@@ -761,31 +669,9 @@ var requirejs, require, define;
//Not expired, check for a cycle. //Not expired, check for a cycle.
if (needCycleCheck) { if (needCycleCheck) {
each(reqCalls, function (mod) {
each(waitAry, function (mod) { breakCycle(mod, {}, {});
if (mod.defined) {
return;
}
var cycleMod = findCycle(mod, {}, {}),
traced = {};
if (cycleMod) {
forceExec(cycleMod, traced, {});
//traced modules may have been
//removed from the registry, but
//their listeners still need to
//be called.
eachProp(traced, modCheck);
}
}); });
//Now that dependencies have
//been satisfied, trigger the
//completion check that then
//notifies listeners.
eachProp(registry, modCheck);
} }
//If still waiting on loads, and the waiting load is something //If still waiting on loads, and the waiting load is something
@@ -851,7 +737,6 @@ var requirejs, require, define;
//doing a direct modification of the depMaps array //doing a direct modification of the depMaps array
//would affect that config. //would affect that config.
this.depMaps = depMaps && depMaps.slice(0); this.depMaps = depMaps && depMaps.slice(0);
this.depMaps.rjsSkipMap = depMaps.rjsSkipMap;
this.errback = errback; this.errback = errback;
@@ -873,20 +758,6 @@ var requirejs, require, define;
} }
}, },
defineDepById: function (id, depExports) {
var i;
//Find the index for this dependency.
each(this.depMaps, function (map, index) {
if (map.id === id) {
i = index;
return true;
}
});
return this.defineDep(i, depExports);
},
defineDep: function (i, depExports) { defineDep: function (i, depExports) {
//Because of cycles, defined callback for a given //Because of cycles, defined callback for a given
//export can be called more than once. //export can be called more than once.
@@ -910,7 +781,9 @@ var requirejs, require, define;
//If the manager is for a plugin managed resource, //If the manager is for a plugin managed resource,
//ask the plugin to load it now. //ask the plugin to load it now.
if (this.shim) { if (this.shim) {
makeRequire(this, true)(this.shim.deps || [], bind(this, function () { context.makeRequire(this.map, {
enableBuildCallback: true
})(this.shim.deps || [], bind(this, function () {
return map.prefix ? this.callPlugin() : this.load(); return map.prefix ? this.callPlugin() : this.load();
})); }));
} else { } else {
@@ -931,11 +804,9 @@ var requirejs, require, define;
/** /**
* Checks is the module is ready to define itself, and if so, * Checks is the module is ready to define itself, and if so,
* define it. If the silent argument is true, then it will just * define it.
* define, but not notify listeners, and not ask for a context-wide
* check of all loaded modules. That is useful for cycle breaking.
*/ */
check: function (silent) { check: function () {
if (!this.enabled || this.enabling) { if (!this.enabled || this.enabling) {
return; return;
} }
@@ -1013,11 +884,6 @@ var requirejs, require, define;
delete registry[id]; delete registry[id];
this.defined = true; this.defined = true;
context.waitCount -= 1;
if (context.waitCount === 0) {
//Clear the wait array used for cycles.
waitAry = [];
}
} }
//Finished the define stage. Allow calling check again //Finished the define stage. Allow calling check again
@@ -1025,25 +891,33 @@ var requirejs, require, define;
//cycle. //cycle.
this.defining = false; this.defining = false;
if (!silent) { if (this.defined && !this.defineEmitted) {
if (this.defined && !this.defineEmitted) { this.defineEmitted = true;
this.defineEmitted = true; this.emit('defined', this.exports);
this.emit('defined', this.exports); this.defineEmitComplete = true;
this.defineEmitComplete = true;
}
} }
} }
}, },
callPlugin: function () { callPlugin: function () {
var map = this.map, var map = this.map,
id = map.id, id = map.id,
pluginMap = makeModuleMap(map.prefix, null, false, true); //Map already normalized the prefix.
pluginMap = makeModuleMap(map.prefix);
//Mark this as a dependency for this plugin, so it
//can be traced for cycles.
this.depMaps.push(pluginMap);
on(pluginMap, 'defined', bind(this, function (plugin) { on(pluginMap, 'defined', bind(this, function (plugin) {
var load, normalizedMap, normalizedMod, var load, normalizedMap, normalizedMod,
name = this.map.name, name = this.map.name,
parentName = this.map.parentMap ? this.map.parentMap.name : null; parentName = this.map.parentMap ? this.map.parentMap.name : null,
localRequire = context.makeRequire(map.parentMap, {
enableBuildCallback: true,
skipMap: true
});
//If current map is not normalized, wait for that //If current map is not normalized, wait for that
//normalized name to load instead of continuing. //normalized name to load instead of continuing.
@@ -1055,10 +929,10 @@ var requirejs, require, define;
}) || ''; }) || '';
} }
//prefix and name should already be normalized, no need
//for applying map config again either.
normalizedMap = makeModuleMap(map.prefix + '!' + name, normalizedMap = makeModuleMap(map.prefix + '!' + name,
this.map.parentMap, this.map.parentMap);
false,
true);
on(normalizedMap, on(normalizedMap,
'defined', bind(this, function (value) { 'defined', bind(this, function (value) {
this.init([], function () { return value; }, null, { this.init([], function () { return value; }, null, {
@@ -1066,8 +940,13 @@ var requirejs, require, define;
ignore: true ignore: true
}); });
})); }));
normalizedMod = registry[normalizedMap.id]; normalizedMod = registry[normalizedMap.id];
if (normalizedMod) { if (normalizedMod) {
//Mark this as a dependency for this plugin, so it
//can be traced for cycles.
this.depMaps.push(normalizedMap);
if (this.events.error) { if (this.events.error) {
normalizedMod.on('error', bind(this, function (err) { normalizedMod.on('error', bind(this, function (err) {
this.emit('error', err); this.emit('error', err);
@@ -1094,7 +973,7 @@ var requirejs, require, define;
//since they will never be resolved otherwise now. //since they will never be resolved otherwise now.
eachProp(registry, function (mod) { eachProp(registry, function (mod) {
if (mod.map.id.indexOf(id + '_unnormalized') === 0) { if (mod.map.id.indexOf(id + '_unnormalized') === 0) {
removeWaiting(mod.map.id); cleanRegistry(mod.map.id);
} }
}); });
@@ -1103,9 +982,19 @@ var requirejs, require, define;
//Allow plugins to load other code without having to know the //Allow plugins to load other code without having to know the
//context or how to 'complete' the load. //context or how to 'complete' the load.
load.fromText = function (moduleName, text) { load.fromText = bind(this, function (text, textAlt) {
/*jslint evil: true */ /*jslint evil: true */
var hasInteractive = useInteractive; var moduleName = map.name,
moduleMap = makeModuleMap(moduleName),
hasInteractive = useInteractive;
//As of 2.1.0, support just passing the text, to reinforce
//fromText only being called once per resource. Still
//support old style of passing moduleName but discard
//that moduleName in favor of the internal ref.
if (textAlt) {
text = textAlt;
}
//Turn off interactive script matching for IE for any define //Turn off interactive script matching for IE for any define
//calls in the text, then turn it back on at the end. //calls in the text, then turn it back on at the end.
@@ -1115,25 +1004,35 @@ var requirejs, require, define;
//Prime the system by creating a module instance for //Prime the system by creating a module instance for
//it. //it.
getModule(makeModuleMap(moduleName)); getModule(moduleMap);
req.exec(text); try {
req.exec(text);
} catch (e) {
throw new Error('fromText eval for ' + moduleName +
' failed: ' + e);
}
if (hasInteractive) { if (hasInteractive) {
useInteractive = true; useInteractive = true;
} }
//Mark this as a dependency for the plugin
//resource
this.depMaps.push(moduleMap);
//Support anonymous modules. //Support anonymous modules.
context.completeLoad(moduleName); context.completeLoad(moduleName);
};
//Bind the value of that module to the value for this
//resource ID.
localRequire([moduleName], load);
});
//Use parentName here since the plugin's name is not reliable, //Use parentName here since the plugin's name is not reliable,
//could be some weird string with no path that actually wants to //could be some weird string with no path that actually wants to
//reference the parentName's path. //reference the parentName's path.
plugin.load(map.name, makeRequire(map.parentMap, true, function (deps, cb, er) { plugin.load(map.name, localRequire, load, config);
deps.rjsSkipMap = true;
return context.require(deps, cb, er);
}), load, config);
})); }));
context.enable(pluginMap, this); context.enable(pluginMap, this);
@@ -1143,12 +1042,6 @@ var requirejs, require, define;
enable: function () { enable: function () {
this.enabled = true; this.enabled = true;
if (!this.waitPushed) {
waitAry.push(this);
context.waitCount += 1;
this.waitPushed = true;
}
//Set flag mentioning that the module is enabling, //Set flag mentioning that the module is enabling,
//so that immediate calls to the defined callbacks //so that immediate calls to the defined callbacks
//for dependencies do not trigger inadvertent load //for dependencies do not trigger inadvertent load
@@ -1165,7 +1058,7 @@ var requirejs, require, define;
depMap = makeModuleMap(depMap, depMap = makeModuleMap(depMap,
(this.map.isDefine ? this.map : this.map.parentMap), (this.map.isDefine ? this.map : this.map.parentMap),
false, false,
!this.depMaps.rjsSkipMap); !this.skipMap);
this.depMaps[i] = depMap; this.depMaps[i] = depMap;
handler = handlers[depMap.id]; handler = handlers[depMap.id];
@@ -1227,7 +1120,7 @@ var requirejs, require, define;
if (name === 'error') { if (name === 'error') {
//Now that the error handler was triggered, remove //Now that the error handler was triggered, remove
//the listeners, since this broken Module instance //the listeners, since this broken Module instance
//can stay around for a while in the registry/waitAry. //can stay around for a while in the registry.
delete this.events[name]; delete this.events[name];
} }
} }
@@ -1274,16 +1167,16 @@ var requirejs, require, define;
}; };
} }
return (context = { context = {
config: config, config: config,
contextName: contextName, contextName: contextName,
registry: registry, registry: registry,
defined: defined, defined: defined,
urlFetched: urlFetched, urlFetched: urlFetched,
waitCount: 0,
defQueue: defQueue, defQueue: defQueue,
Module: Module, Module: Module,
makeModuleMap: makeModuleMap, makeModuleMap: makeModuleMap,
nextTick: req.nextTick,
/** /**
* Set a configuration for the context. * Set a configuration for the context.
@@ -1325,8 +1218,8 @@ var requirejs, require, define;
deps: value deps: value
}; };
} }
if (value.exports && !value.exports.__buildReady) { if (value.exports && !value.exportsFn) {
value.exports = context.makeShimExports(value.exports); value.exportsFn = context.makeShimExports(value);
} }
shim[id] = value; shim[id] = value;
}); });
@@ -1381,125 +1274,152 @@ var requirejs, require, define;
} }
}, },
makeShimExports: function (exports) { makeShimExports: function (value) {
var func; function fn() {
if (typeof exports === 'string') { var ret;
func = function () { if (value.init) {
return getGlobal(exports); ret = value.init.apply(global, arguments);
}; }
//Save the exports for use in nodefine checking. return ret || getGlobal(value.exports);
func.exports = exports;
return func;
} else {
return function () {
return exports.apply(global, arguments);
};
} }
return fn;
}, },
requireDefined: function (id, relMap) { makeRequire: function (relMap, options) {
return hasProp(defined, makeModuleMap(id, relMap, false, true).id); options = options || {};
},
requireSpecified: function (id, relMap) { function require(deps, callback, errback) {
id = makeModuleMap(id, relMap, false, true).id; var id, map, requireMod, args;
return hasProp(defined, id) || hasProp(registry, id);
},
require: function (deps, callback, errback, relMap) { if (options.enableBuildCallback && callback && isFunction(callback)) {
var moduleName, id, map, requireMod, args; callback.__requireJsBuild = true;
if (typeof deps === 'string') {
if (isFunction(callback)) {
//Invalid call
return onError(makeError('requireargs', 'Invalid require call'), errback);
} }
//Synchronous access to one module. If require.get is if (typeof deps === 'string') {
//available (as in the Node adapter), prefer that. if (isFunction(callback)) {
//In this case deps is the moduleName and callback is //Invalid call
//the relMap return onError(makeError('requireargs', 'Invalid require call'), errback);
if (req.get) { }
return req.get(context, deps, callback);
//If require|exports|module are requested, get the
//value for them from the special handlers. Caveat:
//this only works while module is being defined.
if (relMap && handlers[deps]) {
return handlers[deps](registry[relMap.id]);
}
//Synchronous access to one module. If require.get is
//available (as in the Node adapter), prefer that.
if (req.get) {
return req.get(context, deps, relMap);
}
//Normalize module name, if it contains . or ..
map = makeModuleMap(deps, relMap, false, true);
id = map.id;
if (!hasProp(defined, id)) {
return onError(makeError('notloaded', 'Module name "' +
id +
'" has not been loaded yet for context: ' +
contextName +
(relMap ? '' : '. Use require([])')));
}
return defined[id];
} }
//Just return the module wanted. In this scenario, the //Any defined modules in the global queue, intake them now.
//second arg (if passed) is just the relMap. takeGlobalQueue();
moduleName = deps;
relMap = callback;
//Normalize module name, if it contains . or .. //Make sure any remaining defQueue items get properly processed.
map = makeModuleMap(moduleName, relMap, false, true); while (defQueue.length) {
id = map.id; args = defQueue.shift();
if (args[0] === null) {
if (!hasProp(defined, id)) { return onError(makeError('mismatch', 'Mismatched anonymous define() module: ' + args[args.length - 1]));
return onError(makeError('notloaded', 'Module name "' + } else {
id + //args are id, deps, factory. Should be normalized by the
'" has not been loaded yet for context: ' + //define() function.
contextName)); callGetModule(args);
}
} }
return defined[id];
//Mark all the dependencies as needing to be loaded.
context.nextTick(function () {
requireMod = getModule(makeModuleMap(null, relMap));
//Store if map config should be applied to this require
//call for dependencies.
requireMod.skipMap = options.skipMap;
requireMod.init(deps, callback, errback, {
enabled: true
});
checkLoaded();
});
return require;
} }
//Callback require. Normalize args. if callback or errback is mixin(require, {
//not a function, it means it is a relMap. Test errback first. isBrowser: isBrowser,
if (errback && !isFunction(errback)) {
relMap = errback;
errback = undefined;
}
if (callback && !isFunction(callback)) {
relMap = callback;
callback = undefined;
}
//Any defined modules in the global queue, intake them now. /**
takeGlobalQueue(); * Converts a module name + .extension into an URL path.
* *Requires* the use of a module name. It does not support using
* plain URLs like nameToUrl.
*/
toUrl: function (moduleNamePlusExt) {
var index = moduleNamePlusExt.lastIndexOf('.'),
ext = null;
//Make sure any remaining defQueue items get properly processed. if (index !== -1) {
while (defQueue.length) { ext = moduleNamePlusExt.substring(index, moduleNamePlusExt.length);
args = defQueue.shift(); moduleNamePlusExt = moduleNamePlusExt.substring(0, index);
if (args[0] === null) { }
return onError(makeError('mismatch', 'Mismatched anonymous define() module: ' + args[args.length - 1]));
} else { return context.nameToUrl(normalize(moduleNamePlusExt,
//args are id, deps, factory. Should be normalized by the relMap && relMap.id, true), ext);
//define() function. },
callGetModule(args);
defined: function (id) {
return hasProp(defined, makeModuleMap(id, relMap, false, true).id);
},
specified: function (id) {
id = makeModuleMap(id, relMap, false, true).id;
return hasProp(defined, id) || hasProp(registry, id);
} }
}
//Mark all the dependencies as needing to be loaded.
requireMod = getModule(makeModuleMap(null, relMap));
requireMod.init(deps, callback, errback, {
enabled: true
}); });
checkLoaded(); //Only allow undef on top level require calls
if (!relMap) {
require.undef = function (id) {
//Bind any waiting define() calls to this context,
//fix for #408
takeGlobalQueue();
return context.require; var map = makeModuleMap(id, relMap, true),
}, mod = registry[id];
undef: function (id) { delete defined[id];
//Bind any waiting define() calls to this context, delete urlFetched[map.url];
//fix for #408 delete undefEvents[id];
takeGlobalQueue();
var map = makeModuleMap(id, null, true), if (mod) {
mod = registry[id]; //Hold on to listeners in case the
//module will be attempted to be reloaded
//using a different config.
if (mod.events.defined) {
undefEvents[id] = mod.events;
}
delete defined[id]; cleanRegistry(id);
delete urlFetched[map.url]; }
delete undefEvents[id]; };
if (mod) {
//Hold on to listeners in case the
//module will be attempted to be reloaded
//using a different config.
if (mod.events.defined) {
undefEvents[id] = mod.events;
}
removeWaiting(id);
} }
return require;
}, },
/** /**
@@ -1523,7 +1443,7 @@ var requirejs, require, define;
completeLoad: function (moduleName) { completeLoad: function (moduleName) {
var found, args, mod, var found, args, mod,
shim = config.shim[moduleName] || {}, shim = config.shim[moduleName] || {},
shExports = shim.exports && shim.exports.exports; shExports = shim.exports;
takeGlobalQueue(); takeGlobalQueue();
@@ -1563,31 +1483,13 @@ var requirejs, require, define;
} else { } else {
//A script that does not call define(), so just simulate //A script that does not call define(), so just simulate
//the call for it. //the call for it.
callGetModule([moduleName, (shim.deps || []), shim.exports]); callGetModule([moduleName, (shim.deps || []), shim.exportsFn]);
} }
} }
checkLoaded(); checkLoaded();
}, },
/**
* Converts a module name + .extension into an URL path.
* *Requires* the use of a module name. It does not support using
* plain URLs like nameToUrl.
*/
toUrl: function (moduleNamePlusExt, relModuleMap) {
var index = moduleNamePlusExt.lastIndexOf('.'),
ext = null;
if (index !== -1) {
ext = moduleNamePlusExt.substring(index, moduleNamePlusExt.length);
moduleNamePlusExt = moduleNamePlusExt.substring(0, index);
}
return context.nameToUrl(normalize(moduleNamePlusExt, relModuleMap && relModuleMap.id, true),
ext);
},
/** /**
* Converts a module name to a file path. Supports cases where * Converts a module name to a file path. Supports cases where
* moduleName may actually be just an URL. * moduleName may actually be just an URL.
@@ -1701,7 +1603,10 @@ var requirejs, require, define;
return onError(makeError('scripterror', 'Script error', evt, [data.id])); return onError(makeError('scripterror', 'Script error', evt, [data.id]));
} }
} }
}); };
context.require = context.makeRequire();
return context;
} }
/** /**
@@ -1762,6 +1667,16 @@ var requirejs, require, define;
return req(config); return req(config);
}; };
/**
* Execute something after the current tick
* of the event loop. Override for other envs
* that have a better solution than setTimeout.
* @param {Function} fn function to execute later.
*/
req.nextTick = typeof setTimeout !== 'undefined' ? function (fn) {
setTimeout(fn, 4);
} : function (fn) { fn(); };
/** /**
* Export require as a global, but only if it does not already exist. * Export require as a global, but only if it does not already exist.
*/ */
@@ -1782,9 +1697,21 @@ var requirejs, require, define;
//Create default context. //Create default context.
req({}); req({});
//Exports some context-sensitive methods on global require, using //Exports some context-sensitive methods on global require.
//default context if no context specified. each([
addRequireMethods(req); 'toUrl',
'undef',
'defined',
'specified'
], function (prop) {
//Reference from contexts instead of early binding to default context,
//so that during builds, the latest instance of the default context
//with its config gets used.
req[prop] = function () {
var ctx = contexts[defContextName];
return ctx.require[prop].apply(ctx, arguments);
};
});
if (isBrowser) { if (isBrowser) {
head = s.head = document.getElementsByTagName('head')[0]; head = s.head = document.getElementsByTagName('head')[0];
@@ -1962,7 +1889,7 @@ var requirejs, require, define;
define = function (name, deps, callback) { define = function (name, deps, callback) {
var node, context; var node, context;
//Allow for anonymous functions //Allow for anonymous modules
if (typeof name !== 'string') { if (typeof name !== 'string') {
//Adjust args appropriately //Adjust args appropriately
callback = deps; callback = deps;

View File

@@ -14,6 +14,8 @@ $(document).ready(function() {
equal(result.join(','), '1,1', 'works well with _.map'); equal(result.join(','), '1,1', 'works well with _.map');
result = (function() { return _.take([1,2,3], 2); })(); result = (function() { return _.take([1,2,3], 2); })();
equal(result.join(','), '1,2', 'aliased as take'); equal(result.join(','), '1,2', 'aliased as take');
equal(_.first(null), undefined, 'handles nulls');
}); });
test("rest", function() { test("rest", function() {
@@ -47,6 +49,8 @@ $(document).ready(function() {
equal(result, 4, 'works on an arguments object'); equal(result, 4, 'works on an arguments object');
result = _.map([[1,2,3],[1,2,3]], _.last); result = _.map([[1,2,3],[1,2,3]], _.last);
equal(result.join(','), '3,3', 'works well with _.map'); equal(result.join(','), '3,3', 'works well with _.map');
equal(_.last(null), undefined, 'handles nulls');
}); });
test("compact", function() { test("compact", function() {
@@ -136,6 +140,8 @@ $(document).ready(function() {
var stooges = {moe: 30, larry: 40, curly: 50}; var stooges = {moe: 30, larry: 40, curly: 50};
ok(_.isEqual(_.object(_.pairs(stooges)), stooges), 'an object converted to pairs and back to an object'); ok(_.isEqual(_.object(_.pairs(stooges)), stooges), 'an object converted to pairs and back to an object');
ok(_.isEqual(_.object(null), {}), 'handles nulls');
}); });
test("indexOf", function() { test("indexOf", function() {
@@ -157,16 +163,27 @@ $(document).ready(function() {
numbers = [1, 40, 40, 40, 40, 40, 40, 40, 50, 60, 70]; num = 40; numbers = [1, 40, 40, 40, 40, 40, 40, 40, 50, 60, 70]; num = 40;
index = _.indexOf(numbers, num, true); index = _.indexOf(numbers, num, true);
equal(index, 1, '40 is in the list'); equal(index, 1, '40 is in the list');
numbers = [1, 2, 3, 1, 2, 3, 1, 2, 3];
index = _.indexOf(numbers, 2, 5);
equal(index, 7, 'supports the fromIndex argument');
}); });
test("lastIndexOf", function() { test("lastIndexOf", function() {
var numbers = [1, 0, 1, 0, 0, 1, 0, 0, 0]; var numbers = [1, 0, 1];
equal(_.lastIndexOf(numbers, 1), 2);
numbers = [1, 0, 1, 0, 0, 1, 0, 0, 0];
numbers.lastIndexOf = null; numbers.lastIndexOf = null;
equal(_.lastIndexOf(numbers, 1), 5, 'can compute lastIndexOf, even without the native function'); equal(_.lastIndexOf(numbers, 1), 5, 'can compute lastIndexOf, even without the native function');
equal(_.lastIndexOf(numbers, 0), 8, 'lastIndexOf the other element'); equal(_.lastIndexOf(numbers, 0), 8, 'lastIndexOf the other element');
var result = (function(){ return _.lastIndexOf(arguments, 1); })(1, 0, 1, 0, 0, 1, 0, 0, 0); var result = (function(){ return _.lastIndexOf(arguments, 1); })(1, 0, 1, 0, 0, 1, 0, 0, 0);
equal(result, 5, 'works on an arguments object'); equal(result, 5, 'works on an arguments object');
equal(_.indexOf(null, 2), -1, 'handles nulls properly'); equal(_.indexOf(null, 2), -1, 'handles nulls properly');
numbers = [1, 2, 3, 1, 2, 3, 1, 2, 3];
index = _.lastIndexOf(numbers, 2, 2);
equal(index, 1, 'supports the fromIndex argument');
}); });
test("range", function() { test("range", function() {

View File

@@ -44,8 +44,13 @@ $(document).ready(function() {
var doubled = _([1, 2, 3]).map(function(num){ return num * 2; }); var doubled = _([1, 2, 3]).map(function(num){ return num * 2; });
equal(doubled.join(', '), '2, 4, 6', 'OO-style doubled numbers'); equal(doubled.join(', '), '2, 4, 6', 'OO-style doubled numbers');
if (document.querySelectorAll) {
var ids = _.map(document.querySelectorAll('#map-test *'), function(n){ return n.id; });
deepEqual(ids, ['id1', 'id2'], 'Can use collection methods on NodeLists.');
}
var ids = _.map($('#map-test').children(), function(n){ return n.id; }); var ids = _.map($('#map-test').children(), function(n){ return n.id; });
deepEqual(ids, ['id1', 'id2'], 'Can use collection methods on NodeLists.'); deepEqual(ids, ['id1', 'id2'], 'Can use collection methods on jQuery Array-likes.');
var ids = _.map(document.images, function(n){ return n.id; }); var ids = _.map(document.images, function(n){ return n.id; });
ok(ids[0] == 'chart_image', 'can use collection methods on HTMLCollections'); ok(ids[0] == 'chart_image', 'can use collection methods on HTMLCollections');
@@ -102,10 +107,46 @@ $(document).ready(function() {
} }
ok(ifnull instanceof TypeError, 'handles a null (without inital value) properly'); ok(ifnull instanceof TypeError, 'handles a null (without inital value) properly');
var sum = _.reduceRight({a: 1, b: 2, c: 3}, function(sum, num){ return sum + num; });
equal(sum, 6, 'default initial value on object');
ok(_.reduceRight(null, function(){}, 138) === 138, 'handles a null (with initial value) properly'); ok(_.reduceRight(null, function(){}, 138) === 138, 'handles a null (with initial value) properly');
equal(_.reduceRight([], function(){}, undefined), undefined, 'undefined can be passed as a special case'); equal(_.reduceRight([], function(){}, undefined), undefined, 'undefined can be passed as a special case');
raises(function() { _.reduceRight([], function(){}); }, TypeError, 'throws an error for empty arrays with no initial value'); raises(function() { _.reduceRight([], function(){}); }, TypeError, 'throws an error for empty arrays with no initial value');
// Assert that the correct arguments are being passed.
var args,
memo = {},
object = {a: 1, b: 2},
lastKey = _.keys(object).pop();
var expected = lastKey == 'a'
? [memo, 1, 'a', object]
: [memo, 2, 'b', object];
_.reduceRight(object, function() {
args || (args = _.toArray(arguments));
}, memo);
deepEqual(args, expected);
// And again, with numeric keys.
object = {'2': 'a', '1': 'b'};
lastKey = _.keys(object).pop();
args = null;
expected = lastKey == '2'
? [memo, 'a', '2', object]
: [memo, 'b', '1', object];
_.reduceRight(object, function() {
args || (args = _.toArray(arguments));
}, memo);
deepEqual(args, expected);
}); });
test('find', function() { test('find', function() {
@@ -201,6 +242,16 @@ $(document).ready(function() {
equal(_.pluck(people, 'name').join(', '), 'moe, curly', 'pulls names out of objects'); equal(_.pluck(people, 'name').join(', '), 'moe, curly', 'pulls names out of objects');
}); });
test('where', function() {
var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}];
var result = _.where(list, {a: 1});
equal(result.length, 3);
equal(result[result.length - 1].b, 4);
result = _.where(list, {b: 2});
equal(result.length, 2);
equal(result[0].a, 1);
});
test('max', function() { test('max', function() {
equal(3, _.max([1, 2, 3]), 'can perform a regular Math.max'); equal(3, _.max([1, 2, 3]), 'can perform a regular Math.max');
@@ -240,6 +291,29 @@ $(document).ready(function() {
var list = ["one", "two", "three", "four", "five"]; var list = ["one", "two", "three", "four", "five"];
var sorted = _.sortBy(list, 'length'); var sorted = _.sortBy(list, 'length');
equal(sorted.join(' '), 'one two four five three', 'sorted by length'); equal(sorted.join(' '), 'one two four five three', 'sorted by length');
function Pair(x, y) {
this.x = x;
this.y = y;
}
var collection = [
new Pair(1, 1), new Pair(1, 2),
new Pair(1, 3), new Pair(1, 4),
new Pair(1, 5), new Pair(1, 6),
new Pair(2, 1), new Pair(2, 2),
new Pair(2, 3), new Pair(2, 4),
new Pair(2, 5), new Pair(2, 6),
new Pair(undefined, 1), new Pair(undefined, 2),
new Pair(undefined, 3), new Pair(undefined, 4),
new Pair(undefined, 5), new Pair(undefined, 6)
];
var actual = _.sortBy(collection, function(pair) {
return pair.x;
});
deepEqual(actual, collection, 'sortBy should be stable');
}); });
test('groupBy', function() { test('groupBy', function() {
@@ -252,6 +326,18 @@ $(document).ready(function() {
equal(grouped['3'].join(' '), 'one two six ten'); equal(grouped['3'].join(' '), 'one two six ten');
equal(grouped['4'].join(' '), 'four five nine'); equal(grouped['4'].join(' '), 'four five nine');
equal(grouped['5'].join(' '), 'three seven eight'); equal(grouped['5'].join(' '), 'three seven eight');
var context = {};
_.groupBy([{}], function(){ ok(this === context); }, context);
grouped = _.groupBy([4.2, 6.1, 6.4], function(num) {
return Math.floor(num) > 4 ? 'hasOwnProperty' : 'constructor';
});
equal(grouped.constructor.length, 1);
equal(grouped.hasOwnProperty.length, 2);
var array = [{}];
_.groupBy(array, function(value, index, obj){ ok(obj === array); });
}); });
test('countBy', function() { test('countBy', function() {
@@ -264,6 +350,18 @@ $(document).ready(function() {
equal(grouped['3'], 4); equal(grouped['3'], 4);
equal(grouped['4'], 3); equal(grouped['4'], 3);
equal(grouped['5'], 3); equal(grouped['5'], 3);
var context = {};
_.countBy([{}], function(){ ok(this === context); }, context);
grouped = _.countBy([4.2, 6.1, 6.4], function(num) {
return Math.floor(num) > 4 ? 'hasOwnProperty' : 'constructor';
});
equal(grouped.constructor, 1);
equal(grouped.hasOwnProperty, 2);
var array = [{}];
_.countBy(array, function(value, index, obj){ ok(obj === array); });
}); });
test('sortedIndex', function() { test('sortedIndex', function() {
@@ -273,6 +371,15 @@ $(document).ready(function() {
var indexFor30 = _.sortedIndex(numbers, 30); var indexFor30 = _.sortedIndex(numbers, 30);
equal(indexFor30, 2, '30 should be inserted at index 2'); equal(indexFor30, 2, '30 should be inserted at index 2');
var objects = [{x: 10}, {x: 20}, {x: 30}, {x: 40}];
var iterator = function(obj){ return obj.x; };
strictEqual(_.sortedIndex(objects, {x: 25}, iterator), 2);
strictEqual(_.sortedIndex(objects, {x: 35}, 'x'), 3);
var context = {1: 2, 2: 3, 3: 4};
iterator = function(obj){ return this[obj]; };
strictEqual(_.sortedIndex([1, 3], 2, iterator, context), 1);
}); });
test('shuffle', function() { test('shuffle', function() {
@@ -291,14 +398,6 @@ $(document).ready(function() {
var numbers = _.toArray({one : 1, two : 2, three : 3}); var numbers = _.toArray({one : 1, two : 2, three : 3});
equal(numbers.join(', '), '1, 2, 3', 'object flattened into array'); equal(numbers.join(', '), '1, 2, 3', 'object flattened into array');
var objectWithToArrayFunction = {toArray: function() {
return [1, 2, 3];
}};
equal(_.toArray(objectWithToArrayFunction).join(', '), '1, 2, 3', 'toArray method used if present');
var objectWithToArrayValue = {toArray: 1};
equal(_.toArray(objectWithToArrayValue).join(', '), '1', 'toArray property ignored if not a function');
}); });
test('size', function() { test('size', function() {
@@ -312,6 +411,8 @@ $(document).ready(function() {
equal(func(1, 2, 3, 4), 4, 'can test the size of the arguments object'); equal(func(1, 2, 3, 4), 4, 'can test the size of the arguments object');
equal(_.size('hello'), 5, 'can compute the size of a string'); equal(_.size('hello'), 5, 'can compute the size of a string');
equal(_.size(null), 0, 'handles nulls');
}); });
}); });

View File

@@ -141,7 +141,7 @@ $(document).ready(function() {
var incr = function(){ return ++counter; }; var incr = function(){ return ++counter; };
var throttledIncr = _.throttle(incr, 100); var throttledIncr = _.throttle(incr, 100);
var results = []; var results = [];
var saveResult = function() { results.push(throttledIncr()); } var saveResult = function() { results.push(throttledIncr()); };
saveResult(); saveResult(); saveResult(); saveResult(); saveResult(); saveResult();
setTimeout(saveResult, 70); setTimeout(saveResult, 70);
setTimeout(saveResult, 120); setTimeout(saveResult, 120);
@@ -176,11 +176,17 @@ $(document).ready(function() {
_.delay(function(){ equal(counter, 1, "incr was debounced"); start(); }, 220); _.delay(function(){ equal(counter, 1, "incr was debounced"); start(); }, 220);
}); });
asyncTest("debounce asap", 2, function() { asyncTest("debounce asap", 5, function() {
var a, b, c;
var counter = 0; var counter = 0;
var incr = function(){ counter++; }; var incr = function(){ return ++counter; };
var debouncedIncr = _.debounce(incr, 50, true); var debouncedIncr = _.debounce(incr, 50, true);
debouncedIncr(); debouncedIncr(); debouncedIncr(); a = debouncedIncr();
b = debouncedIncr();
c = debouncedIncr();
equal(a, 1);
equal(b, 1);
equal(c, 1);
equal(counter, 1, 'incr was called immediately'); equal(counter, 1, 'incr was called immediately');
setTimeout(debouncedIncr, 30); setTimeout(debouncedIncr, 30);
setTimeout(debouncedIncr, 60); setTimeout(debouncedIncr, 60);

View File

@@ -15,17 +15,22 @@ $(document).ready(function() {
}); });
test("values", function() { test("values", function() {
equal(_.values({one : 1, two : 2}).join(', '), '1, 2', 'can extract the values from an object'); equal(_.values({one: 1, two: 2}).join(', '), '1, 2', 'can extract the values from an object');
equal(_.values({one: 1, two: 2, length: 3}).join(', '), '1, 2, 3', '... even when one of them is "length"');
}); });
test("pairs", function() { test("pairs", function() {
deepEqual(_.pairs({one: 1, two: 2}), [['one', 1], ['two', 2]], 'can convert an object into pairs'); deepEqual(_.pairs({one: 1, two: 2}), [['one', 1], ['two', 2]], 'can convert an object into pairs');
deepEqual(_.pairs({one: 1, two: 2, length: 3}), [['one', 1], ['two', 2], ['length', 3]], '... even when one of them is "length"');
}); });
test("invert", function() { test("invert", function() {
var obj = {first: 'Moe', second: 'Larry', third: 'Curly'}; var obj = {first: 'Moe', second: 'Larry', third: 'Curly'};
equal(_.keys(_.invert(obj)).join(' '), 'Moe Larry Curly', 'can invert an object'); equal(_.keys(_.invert(obj)).join(' '), 'Moe Larry Curly', 'can invert an object');
ok(_.isEqual(_.invert(_.invert(obj)), obj), 'two inverts gets you back where you started'); ok(_.isEqual(_.invert(_.invert(obj)), obj), 'two inverts gets you back where you started');
var obj = {length: 3};
ok(_.invert(obj)['3'] == 'length', 'can invert an object with "length"')
}); });
test("functions", function() { test("functions", function() {
@@ -228,14 +233,6 @@ $(document).ready(function() {
ok(_.isEqual(Array(3), Array(3)), "Sparse arrays of identical lengths are equal"); ok(_.isEqual(Array(3), Array(3)), "Sparse arrays of identical lengths are equal");
ok(!_.isEqual(Array(3), Array(6)), "Sparse arrays of different lengths are not equal when both are empty"); ok(!_.isEqual(Array(3), Array(6)), "Sparse arrays of different lengths are not equal when both are empty");
// According to the Microsoft deviations spec, section 2.1.26, JScript 5.x treats `undefined`
// elements in arrays as elisions. Thus, sparse arrays and dense arrays containing `undefined`
// values are equivalent.
if (0 in [undefined]) {
ok(!_.isEqual(Array(3), [undefined, undefined, undefined]), "Sparse and dense arrays are not equal");
ok(!_.isEqual([undefined, undefined, undefined], Array(3)), "Commutative equality is implemented for sparse and dense arrays");
}
// Simple objects. // Simple objects.
ok(_.isEqual({a: "Curly", b: 1, c: true}, {a: "Curly", b: 1, c: true}), "Objects containing identical primitives are equal"); ok(_.isEqual({a: "Curly", b: 1, c: true}, {a: "Curly", b: 1, c: true}), "Objects containing identical primitives are equal");
ok(_.isEqual({a: /Curly/g, b: new Date(2009, 11, 13)}, {a: /Curly/g, b: new Date(2009, 11, 13)}), "Objects containing equivalent members are equal"); ok(_.isEqual({a: /Curly/g, b: new Date(2009, 11, 13)}, {a: /Curly/g, b: new Date(2009, 11, 13)}), "Objects containing equivalent members are equal");
@@ -351,57 +348,8 @@ $(document).ready(function() {
ok(!_.isEqual(isEqualObj, {}), 'Objects that do not implement equivalent `isEqual` methods are not equal'); ok(!_.isEqual(isEqualObj, {}), 'Objects that do not implement equivalent `isEqual` methods are not equal');
ok(!_.isEqual({}, isEqualObj), 'Commutative equality is implemented for objects with different `isEqual` methods'); ok(!_.isEqual({}, isEqualObj), 'Commutative equality is implemented for objects with different `isEqual` methods');
// Custom `isEqual` methods - comparing different types // Objects from another frame.
LocalizedString = (function() { ok(_.isEqual({}, iObject));
function LocalizedString(id) { this.id = id; this.string = (this.id===10)? 'Bonjour': ''; }
LocalizedString.prototype.isEqual = function(that) {
if (_.isString(that)) return this.string == that;
else if (that instanceof LocalizedString) return this.id == that.id;
return false;
};
return LocalizedString;
})();
var localized_string1 = new LocalizedString(10), localized_string2 = new LocalizedString(10), localized_string3 = new LocalizedString(11);
ok(_.isEqual(localized_string1, localized_string2), 'comparing same typed instances with same ids');
ok(!_.isEqual(localized_string1, localized_string3), 'comparing same typed instances with different ids');
ok(_.isEqual(localized_string1, 'Bonjour'), 'comparing different typed instances with same values');
ok(_.isEqual('Bonjour', localized_string1), 'comparing different typed instances with same values');
ok(!_.isEqual('Bonjour', localized_string3), 'comparing two localized strings with different ids');
ok(!_.isEqual(localized_string1, 'Au revoir'), 'comparing different typed instances with different values');
ok(!_.isEqual('Au revoir', localized_string1), 'comparing different typed instances with different values');
// Custom `isEqual` methods - comparing with serialized data
Date.prototype.toJSON = function() {
return {
_type:'Date',
year:this.getUTCFullYear(),
month:this.getUTCMonth(),
day:this.getUTCDate(),
hours:this.getUTCHours(),
minutes:this.getUTCMinutes(),
seconds:this.getUTCSeconds()
};
};
Date.prototype.isEqual = function(that) {
var this_date_components = this.toJSON();
var that_date_components = (that instanceof Date) ? that.toJSON() : that;
delete this_date_components['_type']; delete that_date_components['_type'];
return _.isEqual(this_date_components, that_date_components);
};
var date = new Date();
var date_json = {
_type:'Date',
year:date.getUTCFullYear(),
month:date.getUTCMonth(),
day:date.getUTCDate(),
hours:date.getUTCHours(),
minutes:date.getUTCMinutes(),
seconds:date.getUTCSeconds()
};
ok(_.isEqual(date_json, date), 'serialized date matches date');
ok(_.isEqual(date, date_json), 'date matches serialized date');
}); });
test("isEmpty", function() { test("isEmpty", function() {
@@ -438,6 +386,7 @@ $(document).ready(function() {
parent.iNull = null;\ parent.iNull = null;\
parent.iBoolean = new Boolean(false);\ parent.iBoolean = new Boolean(false);\
parent.iUndefined = undefined;\ parent.iUndefined = undefined;\
parent.iObject = {};\
</script>" </script>"
); );
iDoc.close(); iDoc.close();
@@ -550,6 +499,7 @@ $(document).ready(function() {
ok(!_.isNaN(0), '0 is not NaN'); ok(!_.isNaN(0), '0 is not NaN');
ok(_.isNaN(NaN), 'but NaN is'); ok(_.isNaN(NaN), 'but NaN is');
ok(_.isNaN(iNaN), 'even from another frame'); ok(_.isNaN(iNaN), 'even from another frame');
ok(_.isNaN(new Number(NaN)), 'wrapped NaN is still NaN');
}); });
test("isNull", function() { test("isNull", function() {

View File

@@ -241,4 +241,9 @@ $(document).ready(function() {
deepEqual(settings, {}); deepEqual(settings, {});
}); });
test('#779 - delimeters are applied to unescaped text.', 1, function() {
var template = _.template('<<\nx\n>>', null, {evaluate: /<<(.*?)>>/g});
strictEqual(template(), '<<\nx\n>>');
});
}); });

File diff suppressed because one or more lines are too long

View File

@@ -1,10 +1,7 @@
// Underscore.js 1.3.3 // Underscore.js 1.4.2
// http://underscorejs.org
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore may be freely distributed under the MIT license. // Underscore may be freely distributed under the MIT license.
// Portions of Underscore are inspired or borrowed from Prototype,
// Oliver Steele's Functional, and John Resig's Micro-Templating.
// For all details and documentation:
// http://documentcloud.github.com/underscore
(function() { (function() {
@@ -26,6 +23,7 @@
// Create quick reference variables for speed access to core prototypes. // Create quick reference variables for speed access to core prototypes.
var push = ArrayProto.push, var push = ArrayProto.push,
slice = ArrayProto.slice, slice = ArrayProto.slice,
concat = ArrayProto.concat,
unshift = ArrayProto.unshift, unshift = ArrayProto.unshift,
toString = ObjProto.toString, toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty; hasOwnProperty = ObjProto.hasOwnProperty;
@@ -67,7 +65,7 @@
} }
// Current version. // Current version.
_.VERSION = '1.3.3'; _.VERSION = '1.4.2';
// Collection Functions // Collection Functions
// -------------------- // --------------------
@@ -132,11 +130,24 @@
if (obj == null) obj = []; if (obj == null) obj = [];
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
if (context) iterator = _.bind(iterator, context); if (context) iterator = _.bind(iterator, context);
return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); return arguments.length > 2 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
} }
var reversed = _.toArray(obj).reverse(); var length = obj.length;
if (context && !initial) iterator = _.bind(iterator, context); if (length !== +length) {
return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator); var keys = _.keys(obj);
length = keys.length;
}
each(obj, function(value, index, list) {
index = keys ? keys[--length] : --length;
if (!initial) {
memo = obj[index];
initial = true;
} else {
memo = iterator.call(context, memo, obj[index], index, list);
}
});
if (!initial) throw new TypeError('Reduce of empty array with no initial value');
return memo;
}; };
// Return the first value which passes a truth test. Aliased as `detect`. // Return the first value which passes a truth test. Aliased as `detect`.
@@ -202,9 +213,9 @@
return !!result; return !!result;
}; };
// Determine if a given value is included in the array or object using `===`. // Determine if the array or object contains a given value (using `===`).
// Aliased as `contains`. // Aliased as `include`.
_.include = _.contains = function(obj, target) { _.contains = _.include = function(obj, target) {
var found = false; var found = false;
if (obj == null) return found; if (obj == null) return found;
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
@@ -227,6 +238,18 @@
return _.map(obj, function(value){ return value[key]; }); return _.map(obj, function(value){ return value[key]; });
}; };
// Convenience version of a common use case of `filter`: selecting only objects
// with specific `key:value` pairs.
_.where = function(obj, attrs) {
if (_.isEmpty(attrs)) return [];
return _.filter(obj, function(value) {
for (var key in attrs) {
if (attrs[key] !== value[key]) return false;
}
return true;
});
};
// Return the maximum element or (element-based computation). // Return the maximum element or (element-based computation).
// Can't optimize arrays of integers longer than 65,535 elements. // Can't optimize arrays of integers longer than 65,535 elements.
// See: https://bugs.webkit.org/show_bug.cgi?id=80797 // See: https://bugs.webkit.org/show_bug.cgi?id=80797
@@ -263,40 +286,44 @@
var index = 0; var index = 0;
var shuffled = []; var shuffled = [];
each(obj, function(value) { each(obj, function(value) {
rand = Math.floor(Math.random() * ++index); rand = _.random(index++);
shuffled[index - 1] = shuffled[rand]; shuffled[index - 1] = shuffled[rand];
shuffled[rand] = value; shuffled[rand] = value;
}); });
return shuffled; return shuffled;
}; };
// An internal function to generate lookup iterators.
var lookupIterator = function(value) {
return _.isFunction(value) ? value : function(obj){ return obj[value]; };
};
// Sort the object's values by a criterion produced by an iterator. // Sort the object's values by a criterion produced by an iterator.
_.sortBy = function(obj, val, context) { _.sortBy = function(obj, value, context) {
var iterator = lookupIterator(obj, val); var iterator = lookupIterator(value);
return _.pluck(_.map(obj, function(value, index, list) { return _.pluck(_.map(obj, function(value, index, list) {
return { return {
value : value, value : value,
index : index,
criteria : iterator.call(context, value, index, list) criteria : iterator.call(context, value, index, list)
}; };
}).sort(function(left, right) { }).sort(function(left, right) {
var a = left.criteria, b = right.criteria; var a = left.criteria;
if (a === void 0) return 1; var b = right.criteria;
if (b === void 0) return -1; if (a !== b) {
return a < b ? -1 : a > b ? 1 : 0; if (a > b || a === void 0) return 1;
if (a < b || b === void 0) return -1;
}
return left.index < right.index ? -1 : 1;
}), 'value'); }), 'value');
}; };
// An internal function to generate lookup iterators.
var lookupIterator = function(obj, val) {
return _.isFunction(val) ? val : function(obj) { return obj[val]; };
};
// An internal function used for aggregate "group by" operations. // An internal function used for aggregate "group by" operations.
var group = function(obj, val, behavior) { var group = function(obj, value, context, behavior) {
var result = {}; var result = {};
var iterator = lookupIterator(obj, val); var iterator = lookupIterator(value);
each(obj, function(value, index) { each(obj, function(value, index) {
var key = iterator(value, index); var key = iterator.call(context, value, index, obj);
behavior(result, key, value); behavior(result, key, value);
}); });
return result; return result;
@@ -304,45 +331,45 @@
// Groups the object's values by a criterion. Pass either a string attribute // Groups the object's values by a criterion. Pass either a string attribute
// to group by, or a function that returns the criterion. // to group by, or a function that returns the criterion.
_.groupBy = function(obj, val) { _.groupBy = function(obj, value, context) {
return group(obj, val, function(result, key, value) { return group(obj, value, context, function(result, key, value) {
(result[key] || (result[key] = [])).push(value); (_.has(result, key) ? result[key] : (result[key] = [])).push(value);
}); });
}; };
// Counts instances of an object that group by a certain criterion. Pass // Counts instances of an object that group by a certain criterion. Pass
// either a string attribute to count by, or a function that returns the // either a string attribute to count by, or a function that returns the
// criterion. // criterion.
_.countBy = function(obj, val) { _.countBy = function(obj, value, context) {
return group(obj, val, function(result, key, value) { return group(obj, value, context, function(result, key, value) {
result[key] || (result[key] = 0); if (!_.has(result, key)) result[key] = 0;
result[key]++; result[key]++;
}); });
}; };
// Use a comparator function to figure out the smallest index at which // Use a comparator function to figure out the smallest index at which
// an object should be inserted so as to maintain order. Uses binary search. // an object should be inserted so as to maintain order. Uses binary search.
_.sortedIndex = function(array, obj, iterator) { _.sortedIndex = function(array, obj, iterator, context) {
iterator || (iterator = _.identity); iterator = iterator == null ? _.identity : lookupIterator(iterator);
var value = iterator(obj); var value = iterator.call(context, obj);
var low = 0, high = array.length; var low = 0, high = array.length;
while (low < high) { while (low < high) {
var mid = (low + high) >> 1; var mid = (low + high) >>> 1;
iterator(array[mid]) < value ? low = mid + 1 : high = mid; iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
} }
return low; return low;
}; };
// Safely convert anything iterable into a real, live array. // Safely convert anything iterable into a real, live array.
_.toArray = function(obj) { _.toArray = function(obj) {
if (!obj) return []; if (!obj) return [];
if (_.isArray(obj) || _.isArguments(obj)) return slice.call(obj); if (obj.length === +obj.length) return slice.call(obj);
if (_.isFunction(obj.toArray)) return obj.toArray();
return _.values(obj); return _.values(obj);
}; };
// Return the number of elements in an object. // Return the number of elements in an object.
_.size = function(obj) { _.size = function(obj) {
if (obj == null) return 0;
return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
}; };
@@ -353,6 +380,7 @@
// values in the array. Aliased as `head` and `take`. The **guard** check // values in the array. Aliased as `head` and `take`. The **guard** check
// allows it to work with `_.map`. // allows it to work with `_.map`.
_.first = _.head = _.take = function(array, n, guard) { _.first = _.head = _.take = function(array, n, guard) {
if (array == null) return void 0;
return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
}; };
@@ -367,6 +395,7 @@
// Get the last element of an array. Passing **n** will return the last N // Get the last element of an array. Passing **n** will return the last N
// values in the array. The **guard** check allows it to work with `_.map`. // values in the array. The **guard** check allows it to work with `_.map`.
_.last = function(array, n, guard) { _.last = function(array, n, guard) {
if (array == null) return void 0;
if ((n != null) && !guard) { if ((n != null) && !guard) {
return slice.call(array, Math.max(array.length - n, 0)); return slice.call(array, Math.max(array.length - n, 0));
} else { } else {
@@ -412,23 +441,23 @@
// Produce a duplicate-free version of the array. If the array has already // Produce a duplicate-free version of the array. If the array has already
// been sorted, you have the option of using a faster algorithm. // been sorted, you have the option of using a faster algorithm.
// Aliased as `unique`. // Aliased as `unique`.
_.uniq = _.unique = function(array, isSorted, iterator) { _.uniq = _.unique = function(array, isSorted, iterator, context) {
var initial = iterator ? _.map(array, iterator) : array; var initial = iterator ? _.map(array, iterator, context) : array;
var results = []; var results = [];
_.reduce(initial, function(memo, value, index) { var seen = [];
if (isSorted ? (_.last(memo) !== value || !memo.length) : !_.include(memo, value)) { each(initial, function(value, index) {
memo.push(value); if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) {
seen.push(value);
results.push(array[index]); results.push(array[index]);
} }
return memo; });
}, []);
return results; return results;
}; };
// Produce an array that contains the union: each distinct element from all of // Produce an array that contains the union: each distinct element from all of
// the passed-in arrays. // the passed-in arrays.
_.union = function() { _.union = function() {
return _.uniq(flatten(arguments, true, [])); return _.uniq(concat.apply(ArrayProto, arguments));
}; };
// Produce an array that contains every item shared between all the // Produce an array that contains every item shared between all the
@@ -445,8 +474,8 @@
// Take the difference between one array and a number of other arrays. // Take the difference between one array and a number of other arrays.
// Only the elements present in just the first array will remain. // Only the elements present in just the first array will remain.
_.difference = function(array) { _.difference = function(array) {
var rest = flatten(slice.call(arguments, 1), true, []); var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
return _.filter(array, function(value){ return !_.include(rest, value); }); return _.filter(array, function(value){ return !_.contains(rest, value); });
}; };
// Zip together multiple lists into a single array -- elements that share // Zip together multiple lists into a single array -- elements that share
@@ -465,6 +494,7 @@
// pairs, or two parallel arrays of the same length -- one of keys, and one of // pairs, or two parallel arrays of the same length -- one of keys, and one of
// the corresponding values. // the corresponding values.
_.object = function(list, values) { _.object = function(list, values) {
if (list == null) return {};
var result = {}; var result = {};
for (var i = 0, l = list.length; i < l; i++) { for (var i = 0, l = list.length; i < l; i++) {
if (values) { if (values) {
@@ -484,21 +514,28 @@
// for **isSorted** to use binary search. // for **isSorted** to use binary search.
_.indexOf = function(array, item, isSorted) { _.indexOf = function(array, item, isSorted) {
if (array == null) return -1; if (array == null) return -1;
var i, l; var i = 0, l = array.length;
if (isSorted) { if (isSorted) {
i = _.sortedIndex(array, item); if (typeof isSorted == 'number') {
return array[i] === item ? i : -1; i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted);
} else {
i = _.sortedIndex(array, item);
return array[i] === item ? i : -1;
}
} }
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item); if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i; for (; i < l; i++) if (array[i] === item) return i;
return -1; return -1;
}; };
// Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
_.lastIndexOf = function(array, item) { _.lastIndexOf = function(array, item, from) {
if (array == null) return -1; if (array == null) return -1;
if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item); var hasIndex = from != null;
var i = array.length; if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) {
return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item);
}
var i = (hasIndex ? from : array.length);
while (i--) if (array[i] === item) return i; while (i--) if (array[i] === item) return i;
return -1; return -1;
}; };
@@ -585,25 +622,25 @@
// Returns a function, that, when invoked, will only be triggered at most once // Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time. // during a given window of time.
_.throttle = function(func, wait) { _.throttle = function(func, wait) {
var context, args, timeout, throttling, more, result; var context, args, timeout, result;
var whenDone = _.debounce(function(){ more = throttling = false; }, wait); var previous = 0;
var later = function() {
previous = new Date;
timeout = null;
result = func.apply(context, args);
};
return function() { return function() {
context = this; args = arguments; var now = new Date;
var later = function() { var remaining = wait - (now - previous);
timeout = null; context = this;
if (more) { args = arguments;
result = func.apply(context, args); if (remaining <= 0) {
} clearTimeout(timeout);
whenDone(); previous = now;
};
if (!timeout) timeout = setTimeout(later, wait);
if (throttling) {
more = true;
} else {
throttling = true;
result = func.apply(context, args); result = func.apply(context, args);
} else if (!timeout) {
timeout = setTimeout(later, remaining);
} }
whenDone();
return result; return result;
}; };
}; };
@@ -613,17 +650,18 @@
// N milliseconds. If `immediate` is passed, trigger the function on the // N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing. // leading edge, instead of the trailing.
_.debounce = function(func, wait, immediate) { _.debounce = function(func, wait, immediate) {
var timeout; var timeout, result;
return function() { return function() {
var context = this, args = arguments; var context = this, args = arguments;
var later = function() { var later = function() {
timeout = null; timeout = null;
if (!immediate) func.apply(context, args); if (!immediate) result = func.apply(context, args);
}; };
var callNow = immediate && !timeout; var callNow = immediate && !timeout;
clearTimeout(timeout); clearTimeout(timeout);
timeout = setTimeout(later, wait); timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args); if (callNow) result = func.apply(context, args);
return result;
}; };
}; };
@@ -645,7 +683,8 @@
// conditionally execute the original function. // conditionally execute the original function.
_.wrap = function(func, wrapper) { _.wrap = function(func, wrapper) {
return function() { return function() {
var args = [func].concat(slice.call(arguments, 0)); var args = [func];
push.apply(args, arguments);
return wrapper.apply(this, args); return wrapper.apply(this, args);
}; };
}; };
@@ -687,22 +726,23 @@
// Retrieve the values of an object's properties. // Retrieve the values of an object's properties.
_.values = function(obj) { _.values = function(obj) {
return _.map(obj, _.identity); var values = [];
for (var key in obj) if (_.has(obj, key)) values.push(obj[key]);
return values;
}; };
// Convert an object into a list of `[key, value]` pairs. // Convert an object into a list of `[key, value]` pairs.
_.pairs = function(obj) { _.pairs = function(obj) {
return _.map(obj, function(value, key) { var pairs = [];
return [key, value]; for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]);
}); return pairs;
}; };
// Invert the keys and values of an object. The values must be serializable. // Invert the keys and values of an object. The values must be serializable.
_.invert = function(obj) { _.invert = function(obj) {
return _.reduce(obj, function(memo, value, key) { var result = {};
memo[value] = key; for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key;
return memo; return result;
}, {});
}; };
// Return a sorted list of the function names available on the object. // Return a sorted list of the function names available on the object.
@@ -728,7 +768,7 @@
// Return a copy of the object only containing the whitelisted properties. // Return a copy of the object only containing the whitelisted properties.
_.pick = function(obj) { _.pick = function(obj) {
var copy = {}; var copy = {};
var keys = _.flatten(slice.call(arguments, 1)); var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
each(keys, function(key) { each(keys, function(key) {
if (key in obj) copy[key] = obj[key]; if (key in obj) copy[key] = obj[key];
}); });
@@ -738,9 +778,9 @@
// Return a copy of the object without the blacklisted properties. // Return a copy of the object without the blacklisted properties.
_.omit = function(obj) { _.omit = function(obj) {
var copy = {}; var copy = {};
var keys = _.flatten(slice.call(arguments, 1)); var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
for (var key in obj) { for (var key in obj) {
if (!_.include(keys, key)) copy[key] = obj[key]; if (!_.contains(keys, key)) copy[key] = obj[key];
} }
return copy; return copy;
}; };
@@ -779,9 +819,6 @@
// Unwrap any wrapped objects. // Unwrap any wrapped objects.
if (a instanceof _) a = a._wrapped; if (a instanceof _) a = a._wrapped;
if (b instanceof _) b = b._wrapped; if (b instanceof _) b = b._wrapped;
// Invoke a custom `isEqual` method if one is provided.
if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);
// Compare `[[Class]]` names. // Compare `[[Class]]` names.
var className = toString.call(a); var className = toString.call(a);
if (className != toString.call(b)) return false; if (className != toString.call(b)) return false;
@@ -829,13 +866,17 @@
if (result) { if (result) {
// Deep compare the contents, ignoring non-numeric properties. // Deep compare the contents, ignoring non-numeric properties.
while (size--) { while (size--) {
// Ensure commutative equality for sparse arrays. if (!(result = eq(a[size], b[size], aStack, bStack))) break;
if (!(result = size in a == size in b && eq(a[size], b[size], aStack, bStack))) break;
} }
} }
} else { } else {
// Objects with different constructors are not equivalent. // Objects with different constructors are not equivalent, but `Object`s
if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false; // from different frames are.
var aCtor = a.constructor, bCtor = b.constructor;
if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
_.isFunction(bCtor) && (bCtor instanceof bCtor))) {
return false;
}
// Deep compare objects. // Deep compare objects.
for (var key in a) { for (var key in a) {
if (_.has(a, key)) { if (_.has(a, key)) {
@@ -875,7 +916,7 @@
// Is a given value a DOM element? // Is a given value a DOM element?
_.isElement = function(obj) { _.isElement = function(obj) {
return !!(obj && obj.nodeType == 1); return !!(obj && obj.nodeType === 1);
}; };
// Is a given value an array? // Is a given value an array?
@@ -904,15 +945,21 @@
}; };
} }
// Optimize `isFunction` if appropriate.
if (typeof (/./) !== 'function') {
_.isFunction = function(obj) {
return typeof obj === 'function';
};
}
// Is a given object a finite number? // Is a given object a finite number?
_.isFinite = function(obj) { _.isFinite = function(obj) {
return _.isNumber(obj) && isFinite(obj); return _.isNumber(obj) && isFinite(obj);
}; };
// Is the given value `NaN`? // Is the given value `NaN`? (NaN is the only number which does not equal itself).
_.isNaN = function(obj) { _.isNaN = function(obj) {
// `NaN` is the only value for which `===` is not reflexive. return _.isNumber(obj) && obj != +obj;
return obj !== obj;
}; };
// Is a given value a boolean? // Is a given value a boolean?
@@ -958,6 +1005,10 @@
// Return a random integer between min and max (inclusive). // Return a random integer between min and max (inclusive).
_.random = function(min, max) { _.random = function(min, max) {
if (max == null) {
max = min;
min = 0;
}
return min + (0 | Math.random() * (max - min + 1)); return min + (0 | Math.random() * (max - min + 1));
}; };
@@ -1003,8 +1054,8 @@
each(_.functions(obj), function(name){ each(_.functions(obj), function(name){
var func = _[name] = obj[name]; var func = _[name] = obj[name];
_.prototype[name] = function() { _.prototype[name] = function() {
var args = slice.call(arguments); var args = [this._wrapped];
args.unshift(this._wrapped); push.apply(args, arguments);
return result.call(this, func.apply(_, args)); return result.call(this, func.apply(_, args));
}; };
}); });
@@ -1029,31 +1080,21 @@
// When customizing `templateSettings`, if you don't want to define an // When customizing `templateSettings`, if you don't want to define an
// interpolation, evaluation or escaping regex, we need one that is // interpolation, evaluation or escaping regex, we need one that is
// guaranteed not to match. // guaranteed not to match.
var noMatch = /.^/; var noMatch = /(.)^/;
// Certain characters need to be escaped so that they can be put into a // Certain characters need to be escaped so that they can be put into a
// string literal. // string literal.
var escapes = { var escapes = {
'\\': '\\', "'": "'",
"'": "'", '\\': '\\',
r: '\r', '\r': 'r',
n: '\n', '\n': 'n',
t: '\t', '\t': 't',
u2028: '\u2028', '\u2028': 'u2028',
u2029: '\u2029' '\u2029': 'u2029'
}; };
for (var key in escapes) escapes[escapes[key]] = key;
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
var unescaper = /\\(\\|'|r|n|t|u2028|u2029)/g;
// Within an interpolation, evaluation, or escaping, remove HTML escaping
// that had been previously added.
var unescape = function(code) {
return code.replace(unescaper, function(match, escape) {
return escapes[escape];
});
};
// JavaScript micro-templating, similar to John Resig's implementation. // JavaScript micro-templating, similar to John Resig's implementation.
// Underscore templating handles arbitrary delimiters, preserves whitespace, // Underscore templating handles arbitrary delimiters, preserves whitespace,
@@ -1061,22 +1102,26 @@
_.template = function(text, data, settings) { _.template = function(text, data, settings) {
settings = _.defaults({}, settings, _.templateSettings); settings = _.defaults({}, settings, _.templateSettings);
// Compile the template source, taking care to escape characters that // Combine delimiters into one regular expression via alternation.
// cannot be included in a string literal and then unescape them in code var matcher = new RegExp([
// blocks. (settings.escape || noMatch).source,
var source = "__p+='" + text (settings.interpolate || noMatch).source,
.replace(escaper, function(match) { (settings.evaluate || noMatch).source
return '\\' + escapes[match]; ].join('|') + '|$', 'g');
})
.replace(settings.escape || noMatch, function(match, code) { // Compile the template source, escaping string literals appropriately.
return "'+\n((__t=(" + unescape(code) + "))==null?'':_.escape(__t))+\n'"; var index = 0;
}) var source = "__p+='";
.replace(settings.interpolate || noMatch, function(match, code) { text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
return "'+\n((__t=(" + unescape(code) + "))==null?'':__t)+\n'"; source += text.slice(index, offset)
}) .replace(escaper, function(match) { return '\\' + escapes[match]; });
.replace(settings.evaluate || noMatch, function(match, code) { source +=
return "';\n" + unescape(code) + "\n__p+='"; escape ? "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'" :
}) + "';\n"; interpolate ? "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'" :
evaluate ? "';\n" + evaluate + "\n__p+='" : '';
index = offset + match.length;
});
source += "';\n";
// If a variable is not specified, place data values in local scope. // If a variable is not specified, place data values in local scope.
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';