"
+ }
+ ],
+ "dependencies": {},
+ "description": "String manipulation extensions for Underscore.js javascript library.",
+ "devDependencies": {},
+ "directories": {
+ "lib": "./lib"
+ },
+ "dist": {
+ "shasum": "71c08bf6b428b1133f37e78fa3a21c82f7329b0d",
+ "tarball": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "homepage": "http://epeli.github.com/underscore.string/",
+ "keywords": [
+ "underscore",
+ "string"
+ ],
+ "licenses": [
+ {
+ "type": "MIT"
+ }
+ ],
+ "main": "./lib/underscore.string",
+ "maintainers": [
+ {
+ "name": "edtsech",
+ "email": "edtsech@gmail.com"
+ },
+ {
+ "name": "rwz",
+ "email": "rwz@duckroll.ru"
+ },
+ {
+ "name": "epeli",
+ "email": "esa-matti@suuronen.org"
+ }
+ ],
+ "name": "underscore.string",
+ "optionalDependencies": {},
+ "readme": "ERROR: No README data found!",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/epeli/underscore.string.git"
+ },
+ "version": "2.3.3"
+}
diff --git a/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/run-qunit.js b/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/run-qunit.js
new file mode 100644
index 0000000..44a2167
--- /dev/null
+++ b/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/run-qunit.js
@@ -0,0 +1,45 @@
+function waitFor(test, complete, timeout) {
+ var result, start = new Date().getTime()
+ setInterval(function interval() {
+ if ((new Date().getTime() - start < timeout) && !result) {
+ result = test()
+ } else {
+ if (!result) {
+ phantom.exit(1)
+ } else {
+ complete()
+ clearInterval(interval)
+ }
+ }
+ }, 100)
+}
+
+
+var fs = require('fs'), page = require('webpage').create();
+var url = 'file://localhost' + fs.workingDirectory + '/' + phantom.args[0];
+
+page.onConsoleMessage = function(msg) {
+ console.log(msg)
+}
+
+page.open(url, function(status) {
+ waitFor(function() {
+ return page.evaluate(function(){
+ var el = document.getElementById('qunit-testresult')
+ return el && el.innerText.match('completed')
+ })
+ }, function() {
+ var failures = page.evaluate(function() {
+ var el = document.getElementById('qunit-testresult'),
+ fails = document.getElementsByClassName('fail')
+
+ for (var i = 0; i < fails.length; i++)
+ console.log(fails[i].innerText)
+
+ console.log(el.innerText)
+
+ return parseInt(el.getElementsByClassName('failed')[0].innerHTML)
+ })
+ phantom.exit(failures > 0 ? 1 : 0)
+ }, 10000)
+})
\ No newline at end of file
diff --git a/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/speed.js b/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/speed.js
new file mode 100644
index 0000000..9ceeea7
--- /dev/null
+++ b/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/speed.js
@@ -0,0 +1,148 @@
+(function() {
+
+ JSLitmus.test('levenshtein', function() {
+ return [
+ _.levenshtein('pineapple', 'potato'),
+ _.levenshtein('seven', 'eight'),
+ _.levenshtein('the very same string', 'the very same string'),
+ _.levenshtein('very very very long string', 'something completely different')
+ ];
+ });
+
+
+ JSLitmus.test('trimNoNative', function() {
+ return _.trim(" foobar ", " ");
+ });
+
+ JSLitmus.test('trim', function() {
+ return _.trim(" foobar ");
+ });
+
+ JSLitmus.test('trim object-oriented', function() {
+ return _(" foobar ").trim();
+ });
+
+ JSLitmus.test('trim jQuery', function() {
+ return jQuery.trim(" foobar ");
+ });
+
+ JSLitmus.test('ltrimp', function() {
+ return _.ltrim(" foobar ", " ");
+ });
+
+ JSLitmus.test('rtrimp', function() {
+ return _.rtrim(" foobar ", " ");
+ });
+
+ JSLitmus.test('startsWith', function() {
+ return _.startsWith("foobar", "foo");
+ });
+
+ JSLitmus.test('endsWith', function() {
+ return _.endsWith("foobar", "xx");
+ });
+
+ JSLitmus.test('chop', function(){
+ return _('whitespace').chop(2);
+ });
+
+ JSLitmus.test('count', function(){
+ return _('Hello worls').count('l');
+ });
+
+ JSLitmus.test('insert', function() {
+ return _('Hello ').insert(6, 'world');
+ });
+
+ JSLitmus.test('splice', function() {
+ return _('https://edtsech@bitbucket.org/edtsech/underscore.strings').splice(30, 7, 'epeli');
+ });
+
+ JSLitmus.test('succ', function(){
+ var let = 'a', alphabet = [];
+
+ for (var i=0; i < 26; i++) {
+ alphabet.push(let);
+ let = _(let).succ();
+ }
+
+ return alphabet;
+ });
+
+ JSLitmus.test('titleize', function(){
+ return _('the titleize string method').titleize();
+ });
+
+ JSLitmus.test('truncate', function(){
+ return _('Hello world').truncate(5);
+ });
+
+ JSLitmus.test('prune', function(){
+ return _('Hello world').prune(5);
+ });
+
+ JSLitmus.test('isBlank', function(){
+ return _('').isBlank();
+ });
+
+ JSLitmus.test('escapeHTML', function(){
+ _('Blah blah blah
').escapeHTML();
+ });
+
+ JSLitmus.test('unescapeHTML', function(){
+ _('<div>Blah blah blah</div>').unescapeHTML();
+ });
+
+ JSLitmus.test('reverse', function(){
+ _('Hello World').reverse();
+ });
+
+ JSLitmus.test('pad default', function(){
+ _('foo').pad(12);
+ });
+
+ JSLitmus.test('pad hash left', function(){
+ _('foo').pad(12, '#');
+ });
+
+ JSLitmus.test('pad hash right', function(){
+ _('foo').pad(12, '#', 'right');
+ });
+
+ JSLitmus.test('pad hash both', function(){
+ _('foo').pad(12, '#', 'both');
+ });
+
+ JSLitmus.test('pad hash both longPad', function(){
+ _('foo').pad(12, 'f00f00f00', 'both');
+ });
+
+ JSLitmus.test('toNumber', function(){
+ _('10.232323').toNumber(2);
+ });
+
+ JSLitmus.test('strRight', function(){
+ _('aaa_bbb_ccc').strRight('_');
+ });
+
+ JSLitmus.test('strRightBack', function(){
+ _('aaa_bbb_ccc').strRightBack('_');
+ });
+
+ JSLitmus.test('strLeft', function(){
+ _('aaa_bbb_ccc').strLeft('_');
+ });
+
+ JSLitmus.test('strLeftBack', function(){
+ _('aaa_bbb_ccc').strLeftBack('_');
+ });
+
+ JSLitmus.test('join', function(){
+ _('separator').join(1, 2, 3, 4, 5, 6, 7, 8, 'foo', 'bar', 'lol', 'wut');
+ });
+
+ JSLitmus.test('slugify', function(){
+ _("Un éléphant à l'orée du bois").slugify();
+ });
+
+})();
diff --git a/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/strings.js b/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/strings.js
new file mode 100644
index 0000000..77364f2
--- /dev/null
+++ b/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/strings.js
@@ -0,0 +1,685 @@
+$(document).ready(function() {
+
+ // Include Underscore.string methods to Underscore namespace
+ _.mixin(_.str.exports());
+
+ module('String extensions');
+
+ test('Strings: naturalSort', function() {
+ var arr = ['foo2', 'foo1', 'foo10', 'foo30', 'foo100', 'foo10bar'],
+ sorted = ['foo1', 'foo2', 'foo10', 'foo10bar', 'foo30', 'foo100'];
+ deepEqual(arr.sort(_.naturalCmp), sorted);
+ });
+
+ test('Strings: trim', function() {
+ equal(_.trim(123), '123', 'Non string');
+ equal(_(' foo').trim(), 'foo');
+ equal(_('foo ').trim(), 'foo');
+ equal(_(' foo ').trim(), 'foo');
+ equal(_(' foo ').trim(), 'foo');
+ equal(_(' foo ').trim(' '), 'foo', 'Manually set whitespace');
+ equal(_('\t foo \t ').trim(/\s/), 'foo', 'Manually set RegExp /\\s+/');
+
+ equal(_('ffoo').trim('f'), 'oo');
+ equal(_('ooff').trim('f'), 'oo');
+ equal(_('ffooff').trim('f'), 'oo');
+
+
+ equal(_('_-foobar-_').trim('_-'), 'foobar');
+
+ equal(_('http://foo/').trim('/'), 'http://foo');
+ equal(_('c:\\').trim('\\'), 'c:');
+
+ equal(_(123).trim(), '123');
+ equal(_(123).trim(3), '12');
+ equal(_('').trim(), '', 'Trim empty string should return empty string');
+ equal(_(null).trim(), '', 'Trim null should return empty string');
+ equal(_(undefined).trim(), '', 'Trim undefined should return empty string');
+ });
+
+ test('String: levenshtein', function() {
+ equal(_.levenshtein('Godfather', 'Godfather'), 0);
+ equal(_.levenshtein('Godfather', 'Godfathe'), 1);
+ equal(_.levenshtein('Godfather', 'odfather'), 1);
+ equal(_.levenshtein('Godfather', 'Gdfthr'), 3);
+ equal(_.levenshtein('seven', 'eight'), 5);
+ equal(_.levenshtein('123', 123), 0);
+ equal(_.levenshtein(321, '321'), 0);
+ equal(_.levenshtein('lol', null), 3);
+ equal(_.levenshtein('lol'), 3);
+ equal(_.levenshtein(null, 'lol'), 3);
+ equal(_.levenshtein(undefined, 'lol'), 3);
+ equal(_.levenshtein(), 0);
+ });
+
+ test('Strings: ltrim', function() {
+ equal(_(' foo').ltrim(), 'foo');
+ equal(_(' foo').ltrim(), 'foo');
+ equal(_('foo ').ltrim(), 'foo ');
+ equal(_(' foo ').ltrim(), 'foo ');
+ equal(_('').ltrim(), '', 'ltrim empty string should return empty string');
+ equal(_(null).ltrim(), '', 'ltrim null should return empty string');
+ equal(_(undefined).ltrim(), '', 'ltrim undefined should return empty string');
+
+ equal(_('ffoo').ltrim('f'), 'oo');
+ equal(_('ooff').ltrim('f'), 'ooff');
+ equal(_('ffooff').ltrim('f'), 'ooff');
+
+ equal(_('_-foobar-_').ltrim('_-'), 'foobar-_');
+
+ equal(_(123).ltrim(1), '23');
+ });
+
+ test('Strings: rtrim', function() {
+ equal(_('http://foo/').rtrim('/'), 'http://foo', 'clean trailing slash');
+ equal(_(' foo').rtrim(), ' foo');
+ equal(_('foo ').rtrim(), 'foo');
+ equal(_('foo ').rtrim(), 'foo');
+ equal(_('foo bar ').rtrim(), 'foo bar');
+ equal(_(' foo ').rtrim(), ' foo');
+
+ equal(_('ffoo').rtrim('f'), 'ffoo');
+ equal(_('ooff').rtrim('f'), 'oo');
+ equal(_('ffooff').rtrim('f'), 'ffoo');
+
+ equal(_('_-foobar-_').rtrim('_-'), '_-foobar');
+
+ equal(_(123).rtrim(3), '12');
+ equal(_('').rtrim(), '', 'rtrim empty string should return empty string');
+ equal(_(null).rtrim(), '', 'rtrim null should return empty string');
+ });
+
+ test('Strings: capitalize', function() {
+ equal(_('fabio').capitalize(), 'Fabio', 'First letter is upper case');
+ equal(_.capitalize('fabio'), 'Fabio', 'First letter is upper case');
+ equal(_.capitalize('FOO'), 'FOO', 'Other letters unchanged');
+ equal(_(123).capitalize(), '123', 'Non string');
+ equal(_.capitalize(''), '', 'Capitalizing empty string returns empty string');
+ equal(_.capitalize(null), '', 'Capitalizing null returns empty string');
+ equal(_.capitalize(undefined), '', 'Capitalizing undefined returns empty string');
+ });
+
+ test('Strings: join', function() {
+ equal(_.join('', 'foo', 'bar'), 'foobar', 'basic join');
+ equal(_.join('', 1, 'foo', 2), '1foo2', 'join numbers and strings');
+ equal(_.join(' ','foo', 'bar'), 'foo bar', 'join with spaces');
+ equal(_.join('1', '2', '2'), '212', 'join number strings');
+ equal(_.join(1, 2, 2), '212', 'join numbers');
+ equal(_.join('','foo', null), 'foo', 'join null with string returns string');
+ equal(_.join(null,'foo', 'bar'), 'foobar', 'join strings with null returns string');
+ equal(_(' ').join('foo', 'bar'), 'foo bar', 'join object oriented');
+ });
+
+ test('Strings: reverse', function() {
+ equal(_.str.reverse('foo'), 'oof' );
+ equal(_.str.reverse('foobar'), 'raboof' );
+ equal(_.str.reverse('foo bar'), 'rab oof' );
+ equal(_.str.reverse('saippuakauppias'), 'saippuakauppias' );
+ equal(_.str.reverse(123), '321', 'Non string');
+ equal(_.str.reverse(123.45), '54.321', 'Non string');
+ equal(_.str.reverse(''), '', 'reversing empty string returns empty string' );
+ equal(_.str.reverse(null), '', 'reversing null returns empty string' );
+ equal(_.str.reverse(undefined), '', 'reversing undefined returns empty string' );
+ });
+
+ test('Strings: clean', function() {
+ equal(_(' foo bar ').clean(), 'foo bar');
+ equal(_(123).clean(), '123');
+ equal(_('').clean(), '', 'claning empty string returns empty string');
+ equal(_(null).clean(), '', 'claning null returns empty string');
+ equal(_(undefined).clean(), '', 'claning undefined returns empty string');
+ });
+
+ test('Strings: sprintf', function() {
+ // Should be very tested function already. Thanks to
+ // http://www.diveintojavascript.com/projects/sprintf-for-javascript
+ equal(_.sprintf('Hello %s', 'me'), 'Hello me', 'basic');
+ equal(_('Hello %s').sprintf('me'), 'Hello me', 'object');
+ equal(_('hello %s').chain().sprintf('me').capitalize().value(), 'Hello me', 'Chaining works');
+ equal(_.sprintf('%.1f', 1.22222), '1.2', 'round');
+ equal(_.sprintf('%.1f', 1.17), '1.2', 'round 2');
+ equal(_.sprintf('%(id)d - %(name)s', {id: 824, name: 'Hello World'}), '824 - Hello World', 'Named replacements work');
+ equal(_.sprintf('%(args[0].id)d - %(args[1].name)s', {args: [{id: 824}, {name: 'Hello World'}]}), '824 - Hello World', 'Named replacements with arrays work');
+ });
+
+
+ test('Strings: vsprintf', function() {
+ equal(_.vsprintf('Hello %s', ['me']), 'Hello me', 'basic');
+ equal(_('Hello %s').vsprintf(['me']), 'Hello me', 'object');
+ equal(_('hello %s').chain().vsprintf(['me']).capitalize().value(), 'Hello me', 'Chaining works');
+ equal(_.vsprintf('%.1f', [1.22222]), '1.2', 'round');
+ equal(_.vsprintf('%.1f', [1.17]), '1.2', 'round 2');
+ equal(_.vsprintf('%(id)d - %(name)s', [{id: 824, name: 'Hello World'}]), '824 - Hello World', 'Named replacement works');
+ equal(_.vsprintf('%(args[0].id)d - %(args[1].name)s', [{args: [{id: 824}, {name: 'Hello World'}]}]), '824 - Hello World', 'Named replacement with arrays works');
+ });
+
+ test('Strings: startsWith', function() {
+ ok(_('foobar').startsWith('foo'), 'foobar starts with foo');
+ ok(!_('oobar').startsWith('foo'), 'oobar does not start with foo');
+ ok(_(12345).startsWith(123), '12345 starts with 123');
+ ok(!_(2345).startsWith(123), '2345 does not start with 123');
+ ok(_('').startsWith(''), 'empty string starts with empty string');
+ ok(_(null).startsWith(''), 'null starts with empty string');
+ ok(!_(null).startsWith('foo'), 'null starts with foo');
+ });
+
+ test('Strings: endsWith', function() {
+ ok(_('foobar').endsWith('bar'), 'foobar ends with bar');
+ ok(_.endsWith('foobar', 'bar'), 'foobar ends with bar');
+ ok(_.endsWith('00018-0000062.Plone.sdh264.1a7264e6912a91aa4a81b64dc5517df7b8875994.mp4', 'mp4'), 'endsWith .mp4');
+ ok(!_('fooba').endsWith('bar'), 'fooba does not end with bar');
+ ok(_.endsWith(12345, 45), '12345 ends with 45');
+ ok(!_.endsWith(12345, 6), '12345 does not end with 6');
+ ok(_('').endsWith(''), 'empty string ends with empty string');
+ ok(_(null).endsWith(''), 'null ends with empty string');
+ ok(!_(null).endsWith('foo'), 'null ends with foo');
+ });
+
+ test('Strings: include', function() {
+ ok(_.str.include('foobar', 'bar'), 'foobar includes bar');
+ ok(!_.str.include('foobar', 'buzz'), 'foobar does not includes buzz');
+ ok(_.str.include(12345, 34), '12345 includes 34');
+ ok(!_.str.contains(12345, 6), '12345 does not includes 6');
+ ok(!_.str.include('', 34), 'empty string includes 34');
+ ok(!_.str.include(null, 34), 'null includes 34');
+ ok(_.str.include(null, ''), 'null includes empty string');
+ });
+
+ test('String: chop', function(){
+ ok(_('whitespace').chop(2).length === 5, 'output [wh, it, es, pa, ce]');
+ ok(_('whitespace').chop(3).length === 4, 'output [whi, tes, pac, e]');
+ ok(_('whitespace').chop()[0].length === 10, 'output [whitespace]');
+ ok(_(12345).chop(1).length === 5, 'output [1, 2, 3, 4, 5]');
+ });
+
+ test('String: clean', function(){
+ equal(_.clean(' foo bar '), 'foo bar');
+ equal(_.clean(''), '');
+ equal(_.clean(null), '');
+ equal(_.clean(1), '1');
+ });
+
+ test('String: count', function(){
+ equal(_('Hello world').count('l'), 3);
+ equal(_('Hello world').count('Hello'), 1);
+ equal(_('Hello world').count('foo'), 0);
+ equal(_('x.xx....x.x').count('x'), 5);
+ equal(_('').count('x'), 0);
+ equal(_(null).count('x'), 0);
+ equal(_(undefined).count('x'), 0);
+ equal(_(12345).count(1), 1);
+ equal(_(11345).count(1), 2);
+ });
+
+ test('String: insert', function(){
+ equal(_('Hello ').insert(6, 'Jessy'), 'Hello Jessy');
+ equal(_('Hello ').insert(100, 'Jessy'), 'Hello Jessy');
+ equal(_('').insert(100, 'Jessy'), 'Jessy');
+ equal(_(null).insert(100, 'Jessy'), 'Jessy');
+ equal(_(undefined).insert(100, 'Jessy'), 'Jessy');
+ equal(_(12345).insert(6, 'Jessy'), '12345Jessy');
+ });
+
+ test('String: splice', function(){
+ equal(_('https://edtsech@bitbucket.org/edtsech/underscore.strings').splice(30, 7, 'epeli'),
+ 'https://edtsech@bitbucket.org/epeli/underscore.strings');
+ equal(_.splice(12345, 1, 2, 321), '132145', 'Non strings');
+ });
+
+ test('String: succ', function(){
+ equal(_('a').succ(), 'b');
+ equal(_('A').succ(), 'B');
+ equal(_('+').succ(), ',');
+ equal(_(1).succ(), '2');
+ });
+
+ test('String: titleize', function(){
+ equal(_('the titleize string method').titleize(), 'The Titleize String Method');
+ equal(_('the titleize string method').titleize(), 'The Titleize String Method');
+ equal(_('').titleize(), '', 'Titleize empty string returns empty string');
+ equal(_(null).titleize(), '', 'Titleize null returns empty string');
+ equal(_(undefined).titleize(), '', 'Titleize undefined returns empty string');
+ equal(_('let\'s have some fun').titleize(), 'Let\'s Have Some Fun');
+ equal(_('a-dash-separated-string').titleize(), 'A-Dash-Separated-String');
+ equal(_('A-DASH-SEPARATED-STRING').titleize(), 'A-Dash-Separated-String');
+ equal(_(123).titleize(), '123');
+ });
+
+ test('String: camelize', function(){
+ equal(_('the_camelize_string_method').camelize(), 'theCamelizeStringMethod');
+ equal(_('-the-camelize-string-method').camelize(), 'TheCamelizeStringMethod');
+ equal(_('the camelize string method').camelize(), 'theCamelizeStringMethod');
+ equal(_(' the camelize string method').camelize(), 'theCamelizeStringMethod');
+ equal(_('the camelize string method').camelize(), 'theCamelizeStringMethod');
+ equal(_('').camelize(), '', 'Camelize empty string returns empty string');
+ equal(_(null).camelize(), '', 'Camelize null returns empty string');
+ equal(_(undefined).camelize(), '', 'Camelize undefined returns empty string');
+ equal(_(123).camelize(), '123');
+ });
+
+ test('String: underscored', function(){
+ equal(_('the-underscored-string-method').underscored(), 'the_underscored_string_method');
+ equal(_('theUnderscoredStringMethod').underscored(), 'the_underscored_string_method');
+ equal(_('TheUnderscoredStringMethod').underscored(), 'the_underscored_string_method');
+ equal(_(' the underscored string method').underscored(), 'the_underscored_string_method');
+ equal(_('').underscored(), '');
+ equal(_(null).underscored(), '');
+ equal(_(undefined).underscored(), '');
+ equal(_(123).underscored(), '123');
+ });
+
+ test('String: dasherize', function(){
+ equal(_('the_dasherize_string_method').dasherize(), 'the-dasherize-string-method');
+ equal(_('TheDasherizeStringMethod').dasherize(), '-the-dasherize-string-method');
+ equal(_('thisIsATest').dasherize(), 'this-is-a-test');
+ equal(_('this Is A Test').dasherize(), 'this-is-a-test');
+ equal(_('thisIsATest123').dasherize(), 'this-is-a-test123');
+ equal(_('123thisIsATest').dasherize(), '123this-is-a-test');
+ equal(_('the dasherize string method').dasherize(), 'the-dasherize-string-method');
+ equal(_('the dasherize string method ').dasherize(), 'the-dasherize-string-method');
+ equal(_('téléphone').dasherize(), 'téléphone');
+ equal(_('foo$bar').dasherize(), 'foo$bar');
+ equal(_('').dasherize(), '');
+ equal(_(null).dasherize(), '');
+ equal(_(undefined).dasherize(), '');
+ equal(_(123).dasherize(), '123');
+ });
+
+ test('String: camelize', function(){
+ equal(_.camelize('-moz-transform'), 'MozTransform');
+ equal(_.camelize('webkit-transform'), 'webkitTransform');
+ equal(_.camelize('under_scored'), 'underScored');
+ equal(_.camelize(' with spaces'), 'withSpaces');
+ equal(_('').camelize(), '');
+ equal(_(null).camelize(), '');
+ equal(_(undefined).camelize(), '');
+ equal(_("_som eWeird---name-").camelize(), 'SomEWeirdName');
+ });
+
+ test('String: join', function(){
+ equal(_.join(1, 2, 3, 4), '21314');
+ equal(_.join('|', 'foo', 'bar', 'baz'), 'foo|bar|baz');
+ equal(_.join('',2,3,null), '23');
+ equal(_.join(null,2,3), '23');
+ });
+
+ test('String: classify', function(){
+ equal(_.classify(1), '1');
+ equal(_('some_class_name').classify(), 'SomeClassName');
+ equal(_('my wonderfull class_name').classify(), 'MyWonderfullClassName');
+ equal(_('my wonderfull.class.name').classify(), 'MyWonderfullClassName');
+ });
+
+ test('String: humanize', function(){
+ equal(_('the_humanize_string_method').humanize(), 'The humanize string method');
+ equal(_('ThehumanizeStringMethod').humanize(), 'Thehumanize string method');
+ equal(_('the humanize string method').humanize(), 'The humanize string method');
+ equal(_('the humanize_id string method_id').humanize(), 'The humanize id string method');
+ equal(_('the humanize string method ').humanize(), 'The humanize string method');
+ equal(_(' capitalize dash-CamelCase_underscore trim ').humanize(), 'Capitalize dash camel case underscore trim');
+ equal(_(123).humanize(), '123');
+ equal(_('').humanize(), '');
+ equal(_(null).humanize(), '');
+ equal(_(undefined).humanize(), '');
+ });
+
+ test('String: truncate', function(){
+ equal(_('Hello world').truncate(6, 'read more'), 'Hello read more');
+ equal(_('Hello world').truncate(5), 'Hello...');
+ equal(_('Hello').truncate(10), 'Hello');
+ equal(_('').truncate(10), '');
+ equal(_(null).truncate(10), '');
+ equal(_(undefined).truncate(10), '');
+ equal(_(1234567890).truncate(5), '12345...');
+ });
+
+ test('String: prune', function(){
+ equal(_('Hello, cruel world').prune(6, ' read more'), 'Hello read more');
+ equal(_('Hello, world').prune(5, 'read a lot more'), 'Hello, world');
+ equal(_('Hello, world').prune(5), 'Hello...');
+ equal(_('Hello, world').prune(8), 'Hello...');
+ equal(_('Hello, cruel world').prune(15), 'Hello, cruel...');
+ equal(_('Hello world').prune(22), 'Hello world');
+ equal(_('Привет, жестокий мир').prune(6, ' read more'), 'Привет read more');
+ equal(_('Привет, мир').prune(6, 'read a lot more'), 'Привет, мир');
+ equal(_('Привет, мир').prune(6), 'Привет...');
+ equal(_('Привет, мир').prune(8), 'Привет...');
+ equal(_('Привет, жестокий мир').prune(16), 'Привет, жестокий...');
+ equal(_('Привет, мир').prune(22), 'Привет, мир');
+ equal(_('alksjd!!!!!!....').prune(100, ''), 'alksjd!!!!!!....');
+ equal(_(123).prune(10), '123');
+ equal(_(123).prune(1, 321), '321');
+ equal(_('').prune(5), '');
+ equal(_(null).prune(5), '');
+ equal(_(undefined).prune(5), '');
+ });
+
+ test('String: isBlank', function(){
+ ok(_('').isBlank());
+ ok(_(' ').isBlank());
+ ok(_('\n').isBlank());
+ ok(!_('a').isBlank());
+ ok(!_('0').isBlank());
+ ok(!_(0).isBlank());
+ ok(_('').isBlank());
+ ok(_(null).isBlank());
+ ok(_(undefined).isBlank());
+ });
+
+ test('String: escapeRegExp', function(){
+ equal(_.escapeRegExp(/hello(?=\sworld)/.source), 'hello\\(\\?\\=\\\\sworld\\)', 'with lookahead');
+ equal(_.escapeRegExp(/hello(?!\shell)/.source), 'hello\\(\\?\\!\\\\shell\\)', 'with negative lookahead');
+ });
+
+ test('String: escapeHTML', function(){
+ equal(_('Blah & "blah" & \'blah\'
').escapeHTML(),
+ '<div>Blah & "blah" & 'blah'</div>');
+ equal(_('<').escapeHTML(), '<');
+ equal(_(5).escapeHTML(), '5');
+ equal(_('').escapeHTML(), '');
+ equal(_(null).escapeHTML(), '');
+ equal(_(undefined).escapeHTML(), '');
+ });
+
+ test('String: unescapeHTML', function(){
+ equal(_('<div>Blah & "blah" & 'blah'</div>').unescapeHTML(),
+ 'Blah & "blah" & \'blah\'
');
+ equal(_('<').unescapeHTML(), '<');
+ equal(_(''').unescapeHTML(), '\'');
+ equal(_(''').unescapeHTML(), '\'');
+ equal(_(''').unescapeHTML(), '\'');
+ equal(_('J').unescapeHTML(), 'J');
+ equal(_('J').unescapeHTML(), 'J');
+ equal(_('J').unescapeHTML(), 'J');
+ equal(_('&_#39;').unescapeHTML(), '&_#39;');
+ equal(_(''_;').unescapeHTML(), ''_;');
+ equal(_('&').unescapeHTML(), '&');
+ equal(_('&').unescapeHTML(), '&');
+ equal(_('').unescapeHTML(), '');
+ equal(_(null).unescapeHTML(), '');
+ equal(_(undefined).unescapeHTML(), '');
+ equal(_(5).unescapeHTML(), '5');
+ // equal(_(undefined).unescapeHTML(), '');
+ });
+
+ test('String: words', function() {
+ deepEqual(_('I love you!').words(), ['I', 'love', 'you!']);
+ deepEqual(_(' I love you! ').words(), ['I', 'love', 'you!']);
+ deepEqual(_('I_love_you!').words('_'), ['I', 'love', 'you!']);
+ deepEqual(_('I-love-you!').words(/-/), ['I', 'love', 'you!']);
+ deepEqual(_(123).words(), ['123'], '123 number has one word "123".');
+ deepEqual(_(0).words(), ['0'], 'Zero number has one word "0".');
+ deepEqual(_('').words(), [], 'Empty strings has no words.');
+ deepEqual(_(' ').words(), [], 'Blank strings has no words.');
+ deepEqual(_(null).words(), [], 'null has no words.');
+ deepEqual(_(undefined).words(), [], 'undefined has no words.');
+ });
+
+ test('String: chars', function() {
+ equal(_('Hello').chars().length, 5);
+ equal(_(123).chars().length, 3);
+ equal(_('').chars().length, 0);
+ equal(_(null).chars().length, 0);
+ equal(_(undefined).chars().length, 0);
+ });
+
+ test('String: swapCase', function(){
+ equal(_('AaBbCcDdEe').swapCase(), 'aAbBcCdDeE');
+ equal(_('Hello World').swapCase(), 'hELLO wORLD');
+ equal(_('').swapCase(), '');
+ equal(_(null).swapCase(), '');
+ equal(_(undefined).swapCase(), '');
+ });
+
+ test('String: lines', function() {
+ equal(_('Hello\nWorld').lines().length, 2);
+ equal(_('Hello World').lines().length, 1);
+ equal(_(123).lines().length, 1);
+ equal(_('').lines().length, 1);
+ equal(_(null).lines().length, 0);
+ equal(_(undefined).lines().length, 0);
+ });
+
+ test('String: pad', function() {
+ equal(_('1').pad(8), ' 1');
+ equal(_(1).pad(8), ' 1');
+ equal(_('1').pad(8, '0'), '00000001');
+ equal(_('1').pad(8, '0', 'left'), '00000001');
+ equal(_('1').pad(8, '0', 'right'), '10000000');
+ equal(_('1').pad(8, '0', 'both'), '00001000');
+ equal(_('foo').pad(8, '0', 'both'), '000foo00');
+ equal(_('foo').pad(7, '0', 'both'), '00foo00');
+ equal(_('foo').pad(7, '!@$%dofjrofj', 'both'), '!!foo!!');
+ equal(_('').pad(2), ' ');
+ equal(_(null).pad(2), ' ');
+ equal(_(undefined).pad(2), ' ');
+ });
+
+ test('String: lpad', function() {
+ equal(_('1').lpad(8), ' 1');
+ equal(_(1).lpad(8), ' 1');
+ equal(_('1').lpad(8, '0'), '00000001');
+ equal(_('1').lpad(8, '0', 'left'), '00000001');
+ equal(_('').lpad(2), ' ');
+ equal(_(null).lpad(2), ' ');
+ equal(_(undefined).lpad(2), ' ');
+ });
+
+ test('String: rpad', function() {
+ equal(_('1').rpad(8), '1 ');
+ equal(_(1).lpad(8), ' 1');
+ equal(_('1').rpad(8, '0'), '10000000');
+ equal(_('foo').rpad(8, '0'), 'foo00000');
+ equal(_('foo').rpad(7, '0'), 'foo0000');
+ equal(_('').rpad(2), ' ');
+ equal(_(null).rpad(2), ' ');
+ equal(_(undefined).rpad(2), ' ');
+ });
+
+ test('String: lrpad', function() {
+ equal(_('1').lrpad(8), ' 1 ');
+ equal(_(1).lrpad(8), ' 1 ');
+ equal(_('1').lrpad(8, '0'), '00001000');
+ equal(_('foo').lrpad(8, '0'), '000foo00');
+ equal(_('foo').lrpad(7, '0'), '00foo00');
+ equal(_('foo').lrpad(7, '!@$%dofjrofj'), '!!foo!!');
+ equal(_('').lrpad(2), ' ');
+ equal(_(null).lrpad(2), ' ');
+ equal(_(undefined).lrpad(2), ' ');
+ });
+
+ test('String: toNumber', function() {
+ deepEqual(_('not a number').toNumber(), NaN);
+ equal(_(0).toNumber(), 0);
+ equal(_('0').toNumber(), 0);
+ equal(_('0.0').toNumber(), 0);
+ equal(_('0.1').toNumber(), 0);
+ equal(_('0.1').toNumber(1), 0.1);
+ equal(_(' 0.1 ').toNumber(1), 0.1);
+ equal(_('0000').toNumber(), 0);
+ equal(_('2.345').toNumber(), 2);
+ equal(_('2.345').toNumber(NaN), 2);
+ equal(_('2.345').toNumber(2), 2.35);
+ equal(_('2.344').toNumber(2), 2.34);
+ equal(_('2').toNumber(2), 2.00);
+ equal(_(2).toNumber(2), 2.00);
+ equal(_(-2).toNumber(), -2);
+ equal(_('-2').toNumber(), -2);
+ equal(_('').toNumber(), 0);
+ equal(_(null).toNumber(), 0);
+ equal(_(undefined).toNumber(), 0);
+ });
+
+ test('String: numberFormat', function() {
+ equal(_.numberFormat(9000), '9,000');
+ equal(_.numberFormat(9000, 0), '9,000');
+ equal(_.numberFormat(9000, 0, '', ''), '9000');
+ equal(_.numberFormat(90000, 2), '90,000.00');
+ equal(_.numberFormat(1000.754), '1,001');
+ equal(_.numberFormat(1000.754, 2), '1,000.75');
+ equal(_.numberFormat(1000.754, 0, ',', '.'), '1.001');
+ equal(_.numberFormat(1000.754, 2, ',', '.'), '1.000,75');
+ equal(_.numberFormat(1000000.754, 2, ',', '.'), '1.000.000,75');
+ equal(_.numberFormat(1000000000), '1,000,000,000');
+ equal(_.numberFormat(100000000), '100,000,000');
+ equal(_.numberFormat('not number'), '');
+ equal(_.numberFormat(), '');
+ equal(_.numberFormat(null, '.', ','), '');
+ equal(_.numberFormat(undefined, '.', ','), '');
+ equal(_.numberFormat(new Number(5000)), '5,000');
+ });
+
+ test('String: strRight', function() {
+ equal(_('This_is_a_test_string').strRight('_'), 'is_a_test_string');
+ equal(_('This_is_a_test_string').strRight('string'), '');
+ equal(_('This_is_a_test_string').strRight(), 'This_is_a_test_string');
+ equal(_('This_is_a_test_string').strRight(''), 'This_is_a_test_string');
+ equal(_('This_is_a_test_string').strRight('-'), 'This_is_a_test_string');
+ equal(_('This_is_a_test_string').strRight(''), 'This_is_a_test_string');
+ equal(_('').strRight('foo'), '');
+ equal(_(null).strRight('foo'), '');
+ equal(_(undefined).strRight('foo'), '');
+ equal(_(12345).strRight(2), '345');
+ });
+
+ test('String: strRightBack', function() {
+ equal(_('This_is_a_test_string').strRightBack('_'), 'string');
+ equal(_('This_is_a_test_string').strRightBack('string'), '');
+ equal(_('This_is_a_test_string').strRightBack(), 'This_is_a_test_string');
+ equal(_('This_is_a_test_string').strRightBack(''), 'This_is_a_test_string');
+ equal(_('This_is_a_test_string').strRightBack('-'), 'This_is_a_test_string');
+ equal(_('').strRightBack('foo'), '');
+ equal(_(null).strRightBack('foo'), '');
+ equal(_(undefined).strRightBack('foo'), '');
+ equal(_(12345).strRightBack(2), '345');
+ });
+
+ test('String: strLeft', function() {
+ equal(_('This_is_a_test_string').strLeft('_'), 'This');
+ equal(_('This_is_a_test_string').strLeft('This'), '');
+ equal(_('This_is_a_test_string').strLeft(), 'This_is_a_test_string');
+ equal(_('This_is_a_test_string').strLeft(''), 'This_is_a_test_string');
+ equal(_('This_is_a_test_string').strLeft('-'), 'This_is_a_test_string');
+ equal(_('').strLeft('foo'), '');
+ equal(_(null).strLeft('foo'), '');
+ equal(_(undefined).strLeft('foo'), '');
+ equal(_(123454321).strLeft(3), '12');
+ });
+
+ test('String: strLeftBack', function() {
+ equal(_('This_is_a_test_string').strLeftBack('_'), 'This_is_a_test');
+ equal(_('This_is_a_test_string').strLeftBack('This'), '');
+ equal(_('This_is_a_test_string').strLeftBack(), 'This_is_a_test_string');
+ equal(_('This_is_a_test_string').strLeftBack(''), 'This_is_a_test_string');
+ equal(_('This_is_a_test_string').strLeftBack('-'), 'This_is_a_test_string');
+ equal(_('').strLeftBack('foo'), '');
+ equal(_(null).strLeftBack('foo'), '');
+ equal(_(undefined).strLeftBack('foo'), '');
+ equal(_(123454321).strLeftBack(3), '123454');
+ });
+
+ test('Strings: stripTags', function() {
+ equal(_('a link').stripTags(), 'a link');
+ equal(_('a link
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/test_standalone.html b/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/test_standalone.html
new file mode 100644
index 0000000..9854c17
--- /dev/null
+++ b/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/test_standalone.html
@@ -0,0 +1,18 @@
+
+
+
+ Underscore.strings Test Suite
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/test_underscore/arrays.js b/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/test_underscore/arrays.js
new file mode 100644
index 0000000..32252a3
--- /dev/null
+++ b/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/test_underscore/arrays.js
@@ -0,0 +1,200 @@
+$(document).ready(function() {
+
+ module("Arrays");
+
+ test("first", function() {
+ equal(_.first([1,2,3]), 1, 'can pull out the first element of an array');
+ equal(_([1, 2, 3]).first(), 1, 'can perform OO-style "first()"');
+ equal(_.first([1,2,3], 0).join(', '), "", 'can pass an index to first');
+ equal(_.first([1,2,3], 2).join(', '), '1, 2', 'can pass an index to first');
+ equal(_.first([1,2,3], 5).join(', '), '1, 2, 3', 'can pass an index to first');
+ var result = (function(){ return _.first(arguments); })(4, 3, 2, 1);
+ equal(result, 4, 'works on an arguments object.');
+ result = _.map([[1,2,3],[1,2,3]], _.first);
+ equal(result.join(','), '1,1', 'works well with _.map');
+ result = (function() { return _.take([1,2,3], 2); })();
+ equal(result.join(','), '1,2', 'aliased as take');
+
+ equal(_.first(null), undefined, 'handles nulls');
+ });
+
+ test("rest", function() {
+ var numbers = [1, 2, 3, 4];
+ equal(_.rest(numbers).join(", "), "2, 3, 4", 'working rest()');
+ equal(_.rest(numbers, 0).join(", "), "1, 2, 3, 4", 'working rest(0)');
+ equal(_.rest(numbers, 2).join(', '), '3, 4', 'rest can take an index');
+ var result = (function(){ return _(arguments).tail(); })(1, 2, 3, 4);
+ equal(result.join(', '), '2, 3, 4', 'aliased as tail and works on arguments object');
+ result = _.map([[1,2,3],[1,2,3]], _.rest);
+ equal(_.flatten(result).join(','), '2,3,2,3', 'works well with _.map');
+ result = (function(){ return _(arguments).drop(); })(1, 2, 3, 4);
+ equal(result.join(', '), '2, 3, 4', 'aliased as drop and works on arguments object');
+ });
+
+ test("initial", function() {
+ equal(_.initial([1,2,3,4,5]).join(", "), "1, 2, 3, 4", 'working initial()');
+ equal(_.initial([1,2,3,4],2).join(", "), "1, 2", 'initial can take an index');
+ var result = (function(){ return _(arguments).initial(); })(1, 2, 3, 4);
+ equal(result.join(", "), "1, 2, 3", 'initial works on arguments object');
+ result = _.map([[1,2,3],[1,2,3]], _.initial);
+ equal(_.flatten(result).join(','), '1,2,1,2', 'initial works with _.map');
+ });
+
+ test("last", function() {
+ equal(_.last([1,2,3]), 3, 'can pull out the last element of an array');
+ equal(_.last([1,2,3], 0).join(', '), "", 'can pass an index to last');
+ equal(_.last([1,2,3], 2).join(', '), '2, 3', 'can pass an index to last');
+ equal(_.last([1,2,3], 5).join(', '), '1, 2, 3', 'can pass an index to last');
+ var result = (function(){ return _(arguments).last(); })(1, 2, 3, 4);
+ equal(result, 4, 'works on an arguments object');
+ result = _.map([[1,2,3],[1,2,3]], _.last);
+ equal(result.join(','), '3,3', 'works well with _.map');
+
+ equal(_.last(null), undefined, 'handles nulls');
+ });
+
+ test("compact", function() {
+ equal(_.compact([0, 1, false, 2, false, 3]).length, 3, 'can trim out all falsy values');
+ var result = (function(){ return _(arguments).compact().length; })(0, 1, false, 2, false, 3);
+ equal(result, 3, 'works on an arguments object');
+ });
+
+ test("flatten", function() {
+ if (window.JSON) {
+ var list = [1, [2], [3, [[[4]]]]];
+ equal(JSON.stringify(_.flatten(list)), '[1,2,3,4]', 'can flatten nested arrays');
+ equal(JSON.stringify(_.flatten(list, true)), '[1,2,3,[[[4]]]]', 'can shallowly flatten nested arrays');
+ var result = (function(){ return _.flatten(arguments); })(1, [2], [3, [[[4]]]]);
+ equal(JSON.stringify(result), '[1,2,3,4]', 'works on an arguments object');
+ }
+ });
+
+ test("without", function() {
+ var list = [1, 2, 1, 0, 3, 1, 4];
+ equal(_.without(list, 0, 1).join(', '), '2, 3, 4', 'can remove all instances of an object');
+ var result = (function(){ return _.without(arguments, 0, 1); })(1, 2, 1, 0, 3, 1, 4);
+ equal(result.join(', '), '2, 3, 4', 'works on an arguments object');
+
+ var list = [{one : 1}, {two : 2}];
+ ok(_.without(list, {one : 1}).length == 2, 'uses real object identity for comparisons.');
+ ok(_.without(list, list[0]).length == 1, 'ditto.');
+ });
+
+ test("uniq", function() {
+ var list = [1, 2, 1, 3, 1, 4];
+ equal(_.uniq(list).join(', '), '1, 2, 3, 4', 'can find the unique values of an unsorted array');
+
+ var list = [1, 1, 1, 2, 2, 3];
+ equal(_.uniq(list, true).join(', '), '1, 2, 3', 'can find the unique values of a sorted array faster');
+
+ var list = [{name:'moe'}, {name:'curly'}, {name:'larry'}, {name:'curly'}];
+ var iterator = function(value) { return value.name; };
+ equal(_.map(_.uniq(list, false, iterator), iterator).join(', '), 'moe, curly, larry', 'can find the unique values of an array using a custom iterator');
+
+ var iterator = function(value) { return value +1; };
+ var list = [1, 2, 2, 3, 4, 4];
+ equal(_.uniq(list, true, iterator).join(', '), '1, 2, 3, 4', 'iterator works with sorted array');
+
+ var result = (function(){ return _.uniq(arguments); })(1, 2, 1, 3, 1, 4);
+ equal(result.join(', '), '1, 2, 3, 4', 'works on an arguments object');
+ });
+
+ test("intersection", function() {
+ var stooges = ['moe', 'curly', 'larry'], leaders = ['moe', 'groucho'];
+ equal(_.intersection(stooges, leaders).join(''), 'moe', 'can take the set intersection of two arrays');
+ equal(_(stooges).intersection(leaders).join(''), 'moe', 'can perform an OO-style intersection');
+ var result = (function(){ return _.intersection(arguments, leaders); })('moe', 'curly', 'larry');
+ equal(result.join(''), 'moe', 'works on an arguments object');
+ });
+
+ test("union", function() {
+ var result = _.union([1, 2, 3], [2, 30, 1], [1, 40]);
+ equal(result.join(' '), '1 2 3 30 40', 'takes the union of a list of arrays');
+
+ var result = _.union([1, 2, 3], [2, 30, 1], [1, 40, [1]]);
+ equal(result.join(' '), '1 2 3 30 40 1', 'takes the union of a list of nested arrays');
+ });
+
+ test("difference", function() {
+ var result = _.difference([1, 2, 3], [2, 30, 40]);
+ equal(result.join(' '), '1 3', 'takes the difference of two arrays');
+
+ var result = _.difference([1, 2, 3, 4], [2, 30, 40], [1, 11, 111]);
+ equal(result.join(' '), '3 4', 'takes the difference of three arrays');
+ });
+
+ test('zip', function() {
+ var names = ['moe', 'larry', 'curly'], ages = [30, 40, 50], leaders = [true];
+ var stooges = _.zip(names, ages, leaders);
+ equal(String(stooges), 'moe,30,true,larry,40,,curly,50,', 'zipped together arrays of different lengths');
+ });
+
+ test('object', function() {
+ var result = _.object(['moe', 'larry', 'curly'], [30, 40, 50]);
+ var shouldBe = {moe: 30, larry: 40, curly: 50};
+ ok(_.isEqual(result, shouldBe), 'two arrays zipped together into an object');
+
+ result = _.object([['one', 1], ['two', 2], ['three', 3]]);
+ shouldBe = {one: 1, two: 2, three: 3};
+ ok(_.isEqual(result, shouldBe), 'an array of pairs zipped together into an object');
+
+ 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(null), {}), 'handles nulls');
+ });
+
+ test("indexOf", function() {
+ var numbers = [1, 2, 3];
+ numbers.indexOf = null;
+ equal(_.indexOf(numbers, 2), 1, 'can compute indexOf, even without the native function');
+ var result = (function(){ return _.indexOf(arguments, 2); })(1, 2, 3);
+ equal(result, 1, 'works on an arguments object');
+ equal(_.indexOf(null, 2), -1, 'handles nulls properly');
+
+ var numbers = [10, 20, 30, 40, 50], num = 35;
+ var index = _.indexOf(numbers, num, true);
+ equal(index, -1, '35 is not in the list');
+
+ numbers = [10, 20, 30, 40, 50]; num = 40;
+ index = _.indexOf(numbers, num, true);
+ equal(index, 3, '40 is in the list');
+
+ numbers = [1, 40, 40, 40, 40, 40, 40, 40, 50, 60, 70]; num = 40;
+ index = _.indexOf(numbers, num, true);
+ 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() {
+ var numbers = [1, 0, 1];
+ equal(_.lastIndexOf(numbers, 1), 2);
+
+ numbers = [1, 0, 1, 0, 0, 1, 0, 0, 0];
+ numbers.lastIndexOf = null;
+ equal(_.lastIndexOf(numbers, 1), 5, 'can compute lastIndexOf, even without the native function');
+ 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);
+ equal(result, 5, 'works on an arguments object');
+ 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() {
+ equal(_.range(0).join(''), '', 'range with 0 as a first argument generates an empty array');
+ equal(_.range(4).join(' '), '0 1 2 3', 'range with a single positive argument generates an array of elements 0,1,2,...,n-1');
+ equal(_.range(5, 8).join(' '), '5 6 7', 'range with two arguments a & b, a<b generates an array of elements a,a+1,a+2,...,b-2,b-1');
+ equal(_.range(8, 5).join(''), '', 'range with two arguments a & b, b<a generates an empty array');
+ equal(_.range(3, 10, 3).join(' '), '3 6 9', 'range with three arguments a & b & c, c < b-a, a < b generates an array of elements a,a+c,a+2c,...,b - (multiplier of a) < c');
+ equal(_.range(3, 10, 15).join(''), '3', 'range with three arguments a & b & c, c > b-a, a < b generates an array with a single element, equal to a');
+ equal(_.range(12, 7, -2).join(' '), '12 10 8', 'range with three arguments a & b & c, a > b, c < 0 generates an array of elements a,a-c,a-2c and ends with the number not less than b');
+ equal(_.range(0, -10, -1).join(' '), '0 -1 -2 -3 -4 -5 -6 -7 -8 -9', 'final example in the Python docs');
+ });
+
+});
diff --git a/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/test_underscore/chaining.js b/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/test_underscore/chaining.js
new file mode 100644
index 0000000..16cf7bf
--- /dev/null
+++ b/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/test_underscore/chaining.js
@@ -0,0 +1,59 @@
+$(document).ready(function() {
+
+ module("Chaining");
+
+ test("map/flatten/reduce", function() {
+ var lyrics = [
+ "I'm a lumberjack and I'm okay",
+ "I sleep all night and I work all day",
+ "He's a lumberjack and he's okay",
+ "He sleeps all night and he works all day"
+ ];
+ var counts = _(lyrics).chain()
+ .map(function(line) { return line.split(''); })
+ .flatten()
+ .reduce(function(hash, l) {
+ hash[l] = hash[l] || 0;
+ hash[l]++;
+ return hash;
+ }, {}).value();
+ ok(counts['a'] == 16 && counts['e'] == 10, 'counted all the letters in the song');
+ });
+
+ test("select/reject/sortBy", function() {
+ var numbers = [1,2,3,4,5,6,7,8,9,10];
+ numbers = _(numbers).chain().select(function(n) {
+ return n % 2 == 0;
+ }).reject(function(n) {
+ return n % 4 == 0;
+ }).sortBy(function(n) {
+ return -n;
+ }).value();
+ equal(numbers.join(', '), "10, 6, 2", "filtered and reversed the numbers");
+ });
+
+ test("select/reject/sortBy in functional style", function() {
+ var numbers = [1,2,3,4,5,6,7,8,9,10];
+ numbers = _.chain(numbers).select(function(n) {
+ return n % 2 == 0;
+ }).reject(function(n) {
+ return n % 4 == 0;
+ }).sortBy(function(n) {
+ return -n;
+ }).value();
+ equal(numbers.join(', '), "10, 6, 2", "filtered and reversed the numbers");
+ });
+
+ test("reverse/concat/unshift/pop/map", function() {
+ var numbers = [1,2,3,4,5];
+ numbers = _(numbers).chain()
+ .reverse()
+ .concat([5, 5, 5])
+ .unshift(17)
+ .pop()
+ .map(function(n){ return n * 2; })
+ .value();
+ equal(numbers.join(', '), "34, 10, 8, 6, 4, 2, 10, 10", 'can chain together array functions.');
+ });
+
+});
diff --git a/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/test_underscore/collections.js b/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/test_underscore/collections.js
new file mode 100644
index 0000000..e089626
--- /dev/null
+++ b/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/test_underscore/collections.js
@@ -0,0 +1,426 @@
+$(document).ready(function() {
+
+ module("Collections");
+
+ test("each", function() {
+ _.each([1, 2, 3], function(num, i) {
+ equal(num, i + 1, 'each iterators provide value and iteration count');
+ });
+
+ var answers = [];
+ _.each([1, 2, 3], function(num){ answers.push(num * this.multiplier);}, {multiplier : 5});
+ equal(answers.join(', '), '5, 10, 15', 'context object property accessed');
+
+ answers = [];
+ _.forEach([1, 2, 3], function(num){ answers.push(num); });
+ equal(answers.join(', '), '1, 2, 3', 'aliased as "forEach"');
+
+ answers = [];
+ var obj = {one : 1, two : 2, three : 3};
+ obj.constructor.prototype.four = 4;
+ _.each(obj, function(value, key){ answers.push(key); });
+ equal(answers.join(", "), 'one, two, three', 'iterating over objects works, and ignores the object prototype.');
+ delete obj.constructor.prototype.four;
+
+ answer = null;
+ _.each([1, 2, 3], function(num, index, arr){ if (_.include(arr, num)) answer = true; });
+ ok(answer, 'can reference the original collection from inside the iterator');
+
+ answers = 0;
+ _.each(null, function(){ ++answers; });
+ equal(answers, 0, 'handles a null properly');
+ });
+
+ test('map', function() {
+ var doubled = _.map([1, 2, 3], function(num){ return num * 2; });
+ equal(doubled.join(', '), '2, 4, 6', 'doubled numbers');
+
+ doubled = _.collect([1, 2, 3], function(num){ return num * 2; });
+ equal(doubled.join(', '), '2, 4, 6', 'aliased as "collect"');
+
+ var tripled = _.map([1, 2, 3], function(num){ return num * this.multiplier; }, {multiplier : 3});
+ equal(tripled.join(', '), '3, 6, 9', 'tripled numbers with context');
+
+ var doubled = _([1, 2, 3]).map(function(num){ return num * 2; });
+ 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; });
+ deepEqual(ids, ['id1', 'id2'], 'Can use collection methods on jQuery Array-likes.');
+
+ var ids = _.map(document.images, function(n){ return n.id; });
+ ok(ids[0] == 'chart_image', 'can use collection methods on HTMLCollections');
+
+ var ifnull = _.map(null, function(){});
+ ok(_.isArray(ifnull) && ifnull.length === 0, 'handles a null properly');
+ });
+
+ test('reduce', function() {
+ var sum = _.reduce([1, 2, 3], function(sum, num){ return sum + num; }, 0);
+ equal(sum, 6, 'can sum up an array');
+
+ var context = {multiplier : 3};
+ sum = _.reduce([1, 2, 3], function(sum, num){ return sum + num * this.multiplier; }, 0, context);
+ equal(sum, 18, 'can reduce with a context object');
+
+ sum = _.inject([1, 2, 3], function(sum, num){ return sum + num; }, 0);
+ equal(sum, 6, 'aliased as "inject"');
+
+ sum = _([1, 2, 3]).reduce(function(sum, num){ return sum + num; }, 0);
+ equal(sum, 6, 'OO-style reduce');
+
+ var sum = _.reduce([1, 2, 3], function(sum, num){ return sum + num; });
+ equal(sum, 6, 'default initial value');
+
+ var ifnull;
+ try {
+ _.reduce(null, function(){});
+ } catch (ex) {
+ ifnull = ex;
+ }
+ ok(ifnull instanceof TypeError, 'handles a null (without inital value) properly');
+
+ ok(_.reduce(null, function(){}, 138) === 138, 'handles a null (with initial value) properly');
+ equal(_.reduce([], function(){}, undefined), undefined, 'undefined can be passed as a special case');
+ raises(function() { _.reduce([], function(){}); }, TypeError, 'throws an error for empty arrays with no initial value');
+ });
+
+ test('reduceRight', function() {
+ var list = _.reduceRight(["foo", "bar", "baz"], function(memo, str){ return memo + str; }, '');
+ equal(list, 'bazbarfoo', 'can perform right folds');
+
+ var list = _.foldr(["foo", "bar", "baz"], function(memo, str){ return memo + str; }, '');
+ equal(list, 'bazbarfoo', 'aliased as "foldr"');
+
+ var list = _.foldr(["foo", "bar", "baz"], function(memo, str){ return memo + str; });
+ equal(list, 'bazbarfoo', 'default initial value');
+
+ var ifnull;
+ try {
+ _.reduceRight(null, function(){});
+ } catch (ex) {
+ ifnull = ex;
+ }
+ 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');
+
+ 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');
+
+ // 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() {
+ var array = [1, 2, 3, 4];
+ strictEqual(_.find(array, function(n) { return n > 2; }), 3, 'should return first found `value`');
+ strictEqual(_.find(array, function() { return false; }), void 0, 'should return `undefined` if `value` is not found');
+ });
+
+ test('detect', function() {
+ var result = _.detect([1, 2, 3], function(num){ return num * 2 == 4; });
+ equal(result, 2, 'found the first "2" and broke the loop');
+ });
+
+ test('select', function() {
+ var evens = _.select([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
+ equal(evens.join(', '), '2, 4, 6', 'selected each even number');
+
+ evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
+ equal(evens.join(', '), '2, 4, 6', 'aliased as "filter"');
+ });
+
+ test('reject', function() {
+ var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
+ equal(odds.join(', '), '1, 3, 5', 'rejected each even number');
+
+ var context = "obj";
+
+ var evens = _.reject([1, 2, 3, 4, 5, 6], function(num){
+ equal(context, "obj");
+ return num % 2 != 0;
+ }, context);
+ equal(evens.join(', '), '2, 4, 6', 'rejected each odd number');
+ });
+
+ test('all', function() {
+ ok(_.all([], _.identity), 'the empty set');
+ ok(_.all([true, true, true], _.identity), 'all true values');
+ ok(!_.all([true, false, true], _.identity), 'one false value');
+ ok(_.all([0, 10, 28], function(num){ return num % 2 == 0; }), 'even numbers');
+ ok(!_.all([0, 11, 28], function(num){ return num % 2 == 0; }), 'an odd number');
+ ok(_.all([1], _.identity) === true, 'cast to boolean - true');
+ ok(_.all([0], _.identity) === false, 'cast to boolean - false');
+ ok(_.every([true, true, true], _.identity), 'aliased as "every"');
+ ok(!_.all([undefined, undefined, undefined], _.identity), 'works with arrays of undefined');
+ });
+
+ test('any', function() {
+ var nativeSome = Array.prototype.some;
+ Array.prototype.some = null;
+ ok(!_.any([]), 'the empty set');
+ ok(!_.any([false, false, false]), 'all false values');
+ ok(_.any([false, false, true]), 'one true value');
+ ok(_.any([null, 0, 'yes', false]), 'a string');
+ ok(!_.any([null, 0, '', false]), 'falsy values');
+ ok(!_.any([1, 11, 29], function(num){ return num % 2 == 0; }), 'all odd numbers');
+ ok(_.any([1, 10, 29], function(num){ return num % 2 == 0; }), 'an even number');
+ ok(_.any([1], _.identity) === true, 'cast to boolean - true');
+ ok(_.any([0], _.identity) === false, 'cast to boolean - false');
+ ok(_.some([false, false, true]), 'aliased as "some"');
+ Array.prototype.some = nativeSome;
+ });
+
+ test('include', function() {
+ ok(_.include([1,2,3], 2), 'two is in the array');
+ ok(!_.include([1,3,9], 2), 'two is not in the array');
+ ok(_.contains({moe:1, larry:3, curly:9}, 3) === true, '_.include on objects checks their values');
+ ok(_([1,2,3]).include(2), 'OO-style include');
+ });
+
+ test('invoke', function() {
+ var list = [[5, 1, 7], [3, 2, 1]];
+ var result = _.invoke(list, 'sort');
+ equal(result[0].join(', '), '1, 5, 7', 'first array sorted');
+ equal(result[1].join(', '), '1, 2, 3', 'second array sorted');
+ });
+
+ test('invoke w/ function reference', function() {
+ var list = [[5, 1, 7], [3, 2, 1]];
+ var result = _.invoke(list, Array.prototype.sort);
+ equal(result[0].join(', '), '1, 5, 7', 'first array sorted');
+ equal(result[1].join(', '), '1, 2, 3', 'second array sorted');
+ });
+
+ // Relevant when using ClojureScript
+ test('invoke when strings have a call method', function() {
+ String.prototype.call = function() {
+ return 42;
+ };
+ var list = [[5, 1, 7], [3, 2, 1]];
+ var s = "foo";
+ equal(s.call(), 42, "call function exists");
+ var result = _.invoke(list, 'sort');
+ equal(result[0].join(', '), '1, 5, 7', 'first array sorted');
+ equal(result[1].join(', '), '1, 2, 3', 'second array sorted');
+ delete String.prototype.call;
+ equal(s.call, undefined, "call function removed");
+ });
+
+ test('pluck', function() {
+ var people = [{name : 'moe', age : 30}, {name : 'curly', age : 50}];
+ 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() {
+ equal(3, _.max([1, 2, 3]), 'can perform a regular Math.max');
+
+ var neg = _.max([1, 2, 3], function(num){ return -num; });
+ equal(neg, 1, 'can perform a computation-based max');
+
+ equal(-Infinity, _.max({}), 'Maximum value of an empty object');
+ equal(-Infinity, _.max([]), 'Maximum value of an empty array');
+
+ equal(299999, _.max(_.range(1,300000)), "Maximum value of a too-big array");
+ });
+
+ test('min', function() {
+ equal(1, _.min([1, 2, 3]), 'can perform a regular Math.min');
+
+ var neg = _.min([1, 2, 3], function(num){ return -num; });
+ equal(neg, 3, 'can perform a computation-based min');
+
+ equal(Infinity, _.min({}), 'Minimum value of an empty object');
+ equal(Infinity, _.min([]), 'Minimum value of an empty array');
+
+ var now = new Date(9999999999);
+ var then = new Date(0);
+ equal(_.min([now, then]), then);
+
+ equal(1, _.min(_.range(1,300000)), "Minimum value of a too-big array");
+ });
+
+ test('sortBy', function() {
+ var people = [{name : 'curly', age : 50}, {name : 'moe', age : 30}];
+ people = _.sortBy(people, function(person){ return person.age; });
+ equal(_.pluck(people, 'name').join(', '), 'moe, curly', 'stooges sorted by age');
+
+ var list = [undefined, 4, 1, undefined, 3, 2];
+ equal(_.sortBy(list, _.identity).join(','), '1,2,3,4,,', 'sortBy with undefined values');
+
+ var list = ["one", "two", "three", "four", "five"];
+ var sorted = _.sortBy(list, '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() {
+ var parity = _.groupBy([1, 2, 3, 4, 5, 6], function(num){ return num % 2; });
+ ok('0' in parity && '1' in parity, 'created a group for each value');
+ equal(parity[0].join(', '), '2, 4, 6', 'put each even number in the right group');
+
+ var list = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"];
+ var grouped = _.groupBy(list, 'length');
+ equal(grouped['3'].join(' '), 'one two six ten');
+ equal(grouped['4'].join(' '), 'four five nine');
+ 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() {
+ var parity = _.countBy([1, 2, 3, 4, 5], function(num){ return num % 2 == 0; });
+ equal(parity['true'], 2);
+ equal(parity['false'], 3);
+
+ var list = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"];
+ var grouped = _.countBy(list, 'length');
+ equal(grouped['3'], 4);
+ equal(grouped['4'], 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() {
+ var numbers = [10, 20, 30, 40, 50], num = 35;
+ var indexForNum = _.sortedIndex(numbers, num);
+ equal(indexForNum, 3, '35 should be inserted at index 3');
+
+ var indexFor30 = _.sortedIndex(numbers, 30);
+ 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() {
+ var numbers = _.range(10);
+ var shuffled = _.shuffle(numbers).sort();
+ notStrictEqual(numbers, shuffled, 'original object is unmodified');
+ equal(shuffled.join(','), numbers.join(','), 'contains the same members before and after shuffle');
+ });
+
+ test('toArray', function() {
+ ok(!_.isArray(arguments), 'arguments object is not an array');
+ ok(_.isArray(_.toArray(arguments)), 'arguments object converted into array');
+ var a = [1,2,3];
+ ok(_.toArray(a) !== a, 'array is cloned');
+ equal(_.toArray(a).join(', '), '1, 2, 3', 'cloned array contains same elements');
+
+ var numbers = _.toArray({one : 1, two : 2, three : 3});
+ equal(numbers.join(', '), '1, 2, 3', 'object flattened into array');
+ });
+
+ test('size', function() {
+ equal(_.size({one : 1, two : 2, three : 3}), 3, 'can compute the size of an object');
+ equal(_.size([1, 2, 3]), 3, 'can compute the size of an array');
+
+ var func = function() {
+ return _.size(arguments);
+ };
+
+ 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(null), 0, 'handles nulls');
+ });
+
+});
diff --git a/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/test_underscore/functions.js b/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/test_underscore/functions.js
new file mode 100644
index 0000000..a529658
--- /dev/null
+++ b/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/test_underscore/functions.js
@@ -0,0 +1,259 @@
+$(document).ready(function() {
+
+ module("Functions");
+
+ test("bind", function() {
+ var context = {name : 'moe'};
+ var func = function(arg) { return "name: " + (this.name || arg); };
+ var bound = _.bind(func, context);
+ equal(bound(), 'name: moe', 'can bind a function to a context');
+
+ bound = _(func).bind(context);
+ equal(bound(), 'name: moe', 'can do OO-style binding');
+
+ bound = _.bind(func, null, 'curly');
+ equal(bound(), 'name: curly', 'can bind without specifying a context');
+
+ func = function(salutation, name) { return salutation + ': ' + name; };
+ func = _.bind(func, this, 'hello');
+ equal(func('moe'), 'hello: moe', 'the function was partially applied in advance');
+
+ var func = _.bind(func, this, 'curly');
+ equal(func(), 'hello: curly', 'the function was completely applied in advance');
+
+ var func = function(salutation, firstname, lastname) { return salutation + ': ' + firstname + ' ' + lastname; };
+ func = _.bind(func, this, 'hello', 'moe', 'curly');
+ equal(func(), 'hello: moe curly', 'the function was partially applied in advance and can accept multiple arguments');
+
+ func = function(context, message) { equal(this, context, message); };
+ _.bind(func, 0, 0, 'can bind a function to `0`')();
+ _.bind(func, '', '', 'can bind a function to an empty string')();
+ _.bind(func, false, false, 'can bind a function to `false`')();
+
+ // These tests are only meaningful when using a browser without a native bind function
+ // To test this with a modern browser, set underscore's nativeBind to undefined
+ var F = function () { return this; };
+ var Boundf = _.bind(F, {hello: "moe curly"});
+ equal(new Boundf().hello, undefined, "function should not be bound to the context, to comply with ECMAScript 5");
+ equal(Boundf().hello, "moe curly", "When called without the new operator, it's OK to be bound to the context");
+ });
+
+ test("bindAll", function() {
+ var curly = {name : 'curly'}, moe = {
+ name : 'moe',
+ getName : function() { return 'name: ' + this.name; },
+ sayHi : function() { return 'hi: ' + this.name; }
+ };
+ curly.getName = moe.getName;
+ _.bindAll(moe, 'getName', 'sayHi');
+ curly.sayHi = moe.sayHi;
+ equal(curly.getName(), 'name: curly', 'unbound function is bound to current object');
+ equal(curly.sayHi(), 'hi: moe', 'bound function is still bound to original object');
+
+ curly = {name : 'curly'};
+ moe = {
+ name : 'moe',
+ getName : function() { return 'name: ' + this.name; },
+ sayHi : function() { return 'hi: ' + this.name; }
+ };
+ _.bindAll(moe);
+ curly.sayHi = moe.sayHi;
+ equal(curly.sayHi(), 'hi: moe', 'calling bindAll with no arguments binds all functions to the object');
+ });
+
+ test("memoize", function() {
+ var fib = function(n) {
+ return n < 2 ? n : fib(n - 1) + fib(n - 2);
+ };
+ var fastFib = _.memoize(fib);
+ equal(fib(10), 55, 'a memoized version of fibonacci produces identical results');
+ equal(fastFib(10), 55, 'a memoized version of fibonacci produces identical results');
+
+ var o = function(str) {
+ return str;
+ };
+ var fastO = _.memoize(o);
+ equal(o('toString'), 'toString', 'checks hasOwnProperty');
+ equal(fastO('toString'), 'toString', 'checks hasOwnProperty');
+ });
+
+ asyncTest("delay", 2, function() {
+ var delayed = false;
+ _.delay(function(){ delayed = true; }, 100);
+ setTimeout(function(){ ok(!delayed, "didn't delay the function quite yet"); }, 50);
+ setTimeout(function(){ ok(delayed, 'delayed the function'); start(); }, 150);
+ });
+
+ asyncTest("defer", 1, function() {
+ var deferred = false;
+ _.defer(function(bool){ deferred = bool; }, true);
+ _.delay(function(){ ok(deferred, "deferred the function"); start(); }, 50);
+ });
+
+ asyncTest("throttle", 2, function() {
+ var counter = 0;
+ var incr = function(){ counter++; };
+ var throttledIncr = _.throttle(incr, 100);
+ throttledIncr(); throttledIncr(); throttledIncr();
+ setTimeout(throttledIncr, 70);
+ setTimeout(throttledIncr, 120);
+ setTimeout(throttledIncr, 140);
+ setTimeout(throttledIncr, 190);
+ setTimeout(throttledIncr, 220);
+ setTimeout(throttledIncr, 240);
+ _.delay(function(){ equal(counter, 1, "incr was called immediately"); }, 30);
+ _.delay(function(){ equal(counter, 4, "incr was throttled"); start(); }, 400);
+ });
+
+ asyncTest("throttle arguments", 2, function() {
+ var value = 0;
+ var update = function(val){ value = val; };
+ var throttledUpdate = _.throttle(update, 100);
+ throttledUpdate(1); throttledUpdate(2); throttledUpdate(3);
+ setTimeout(function(){ throttledUpdate(4); }, 120);
+ setTimeout(function(){ throttledUpdate(5); }, 140);
+ setTimeout(function(){ throttledUpdate(6); }, 250);
+ _.delay(function(){ equal(value, 1, "updated to latest value"); }, 40);
+ _.delay(function(){ equal(value, 6, "updated to latest value"); start(); }, 400);
+ });
+
+ asyncTest("throttle once", 2, function() {
+ var counter = 0;
+ var incr = function(){ return ++counter; };
+ var throttledIncr = _.throttle(incr, 100);
+ var result = throttledIncr();
+ _.delay(function(){
+ equal(result, 1, "throttled functions return their value");
+ equal(counter, 1, "incr was called once"); start();
+ }, 220);
+ });
+
+ asyncTest("throttle twice", 1, function() {
+ var counter = 0;
+ var incr = function(){ counter++; };
+ var throttledIncr = _.throttle(incr, 100);
+ throttledIncr(); throttledIncr();
+ _.delay(function(){ equal(counter, 2, "incr was called twice"); start(); }, 220);
+ });
+
+ asyncTest("throttle repeatedly with results", 9, function() {
+ var counter = 0;
+ var incr = function(){ return ++counter; };
+ var throttledIncr = _.throttle(incr, 100);
+ var results = [];
+ var saveResult = function() { results.push(throttledIncr()); };
+ saveResult(); saveResult(); saveResult();
+ setTimeout(saveResult, 70);
+ setTimeout(saveResult, 120);
+ setTimeout(saveResult, 140);
+ setTimeout(saveResult, 190);
+ setTimeout(saveResult, 240);
+ setTimeout(saveResult, 260);
+ _.delay(function() {
+ equal(results[0], 1, "incr was called once");
+ equal(results[1], 1, "incr was throttled");
+ equal(results[2], 1, "incr was throttled");
+ equal(results[3], 1, "incr was throttled");
+ equal(results[4], 2, "incr was called twice");
+ equal(results[5], 2, "incr was throttled");
+ equal(results[6], 2, "incr was throttled");
+ equal(results[7], 3, "incr was called thrice");
+ equal(results[8], 3, "incr was throttled");
+ start();
+ }, 400);
+ });
+
+ asyncTest("debounce", 1, function() {
+ var counter = 0;
+ var incr = function(){ counter++; };
+ var debouncedIncr = _.debounce(incr, 50);
+ debouncedIncr(); debouncedIncr(); debouncedIncr();
+ setTimeout(debouncedIncr, 30);
+ setTimeout(debouncedIncr, 60);
+ setTimeout(debouncedIncr, 90);
+ setTimeout(debouncedIncr, 120);
+ setTimeout(debouncedIncr, 150);
+ _.delay(function(){ equal(counter, 1, "incr was debounced"); start(); }, 220);
+ });
+
+ asyncTest("debounce asap", 5, function() {
+ var a, b, c;
+ var counter = 0;
+ var incr = function(){ return ++counter; };
+ var debouncedIncr = _.debounce(incr, 50, true);
+ a = debouncedIncr();
+ b = debouncedIncr();
+ c = debouncedIncr();
+ equal(a, 1);
+ equal(b, 1);
+ equal(c, 1);
+ equal(counter, 1, 'incr was called immediately');
+ setTimeout(debouncedIncr, 30);
+ setTimeout(debouncedIncr, 60);
+ setTimeout(debouncedIncr, 90);
+ setTimeout(debouncedIncr, 120);
+ setTimeout(debouncedIncr, 150);
+ _.delay(function(){ equal(counter, 1, "incr was debounced"); start(); }, 220);
+ });
+
+ asyncTest("debounce asap recursively", 2, function() {
+ var counter = 0;
+ var debouncedIncr = _.debounce(function(){
+ counter++;
+ if (counter < 5) debouncedIncr();
+ }, 50, true);
+ debouncedIncr();
+ equal(counter, 1, 'incr was called immediately');
+ _.delay(function(){ equal(counter, 1, "incr was debounced"); start(); }, 70);
+ });
+
+ test("once", function() {
+ var num = 0;
+ var increment = _.once(function(){ num++; });
+ increment();
+ increment();
+ equal(num, 1);
+ });
+
+ test("wrap", function() {
+ var greet = function(name){ return "hi: " + name; };
+ var backwards = _.wrap(greet, function(func, name){ return func(name) + ' ' + name.split('').reverse().join(''); });
+ equal(backwards('moe'), 'hi: moe eom', 'wrapped the saluation function');
+
+ var inner = function(){ return "Hello "; };
+ var obj = {name : "Moe"};
+ obj.hi = _.wrap(inner, function(fn){ return fn() + this.name; });
+ equal(obj.hi(), "Hello Moe");
+
+ var noop = function(){};
+ var wrapped = _.wrap(noop, function(fn){ return Array.prototype.slice.call(arguments, 0); });
+ var ret = wrapped(['whats', 'your'], 'vector', 'victor');
+ deepEqual(ret, [noop, ['whats', 'your'], 'vector', 'victor']);
+ });
+
+ test("compose", function() {
+ var greet = function(name){ return "hi: " + name; };
+ var exclaim = function(sentence){ return sentence + '!'; };
+ var composed = _.compose(exclaim, greet);
+ equal(composed('moe'), 'hi: moe!', 'can compose a function that takes another');
+
+ composed = _.compose(greet, exclaim);
+ equal(composed('moe'), 'hi: moe!', 'in this case, the functions are also commutative');
+ });
+
+ test("after", function() {
+ var testAfter = function(afterAmount, timesCalled) {
+ var afterCalled = 0;
+ var after = _.after(afterAmount, function() {
+ afterCalled++;
+ });
+ while (timesCalled--) after();
+ return afterCalled;
+ };
+
+ equal(testAfter(5, 5), 1, "after(N) should fire after being called N times");
+ equal(testAfter(5, 4), 0, "after(N) should not fire unless called N times");
+ equal(testAfter(0, 0), 1, "after(0) should fire immediately");
+ });
+
+});
diff --git a/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/test_underscore/index.html b/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/test_underscore/index.html
new file mode 100644
index 0000000..064fa98
--- /dev/null
+++ b/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/test_underscore/index.html
@@ -0,0 +1,45 @@
+
+
+
+ Underscore Test Suite
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A representative sample of the functions are benchmarked here, to provide
+ a sense of how fast they might run in different browsers.
+ Each iteration runs on an array of 1000 elements.
+ For example, the 'intersection' test measures the number of times you can
+ find the intersection of two thousand-element arrays in one second.
+
+
+
+
+
diff --git a/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/test_underscore/objects.js b/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/test_underscore/objects.js
new file mode 100644
index 0000000..22949c3
--- /dev/null
+++ b/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/test_underscore/objects.js
@@ -0,0 +1,548 @@
+$(document).ready(function() {
+
+ module("Objects");
+
+ test("keys", function() {
+ equal(_.keys({one : 1, two : 2}).join(', '), 'one, two', 'can extract the keys from an object');
+ // the test above is not safe because it relies on for-in enumeration order
+ var a = []; a[1] = 0;
+ equal(_.keys(a).join(', '), '1', 'is not fooled by sparse arrays; see issue #95');
+ raises(function() { _.keys(null); }, TypeError, 'throws an error for `null` values');
+ raises(function() { _.keys(void 0); }, TypeError, 'throws an error for `undefined` values');
+ raises(function() { _.keys(1); }, TypeError, 'throws an error for number primitives');
+ raises(function() { _.keys('a'); }, TypeError, 'throws an error for string primitives');
+ raises(function() { _.keys(true); }, TypeError, 'throws an error for boolean primitives');
+ });
+
+ 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, length: 3}).join(', '), '1, 2, 3', '... even when one of them is "length"');
+ });
+
+ 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, length: 3}), [['one', 1], ['two', 2], ['length', 3]], '... even when one of them is "length"');
+ });
+
+ test("invert", function() {
+ var obj = {first: 'Moe', second: 'Larry', third: 'Curly'};
+ 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');
+
+ var obj = {length: 3};
+ ok(_.invert(obj)['3'] == 'length', 'can invert an object with "length"')
+ });
+
+ test("functions", function() {
+ var obj = {a : 'dash', b : _.map, c : (/yo/), d : _.reduce};
+ ok(_.isEqual(['b', 'd'], _.functions(obj)), 'can grab the function names of any passed-in object');
+
+ var Animal = function(){};
+ Animal.prototype.run = function(){};
+ equal(_.functions(new Animal).join(''), 'run', 'also looks up functions on the prototype');
+ });
+
+ test("extend", function() {
+ var result;
+ equal(_.extend({}, {a:'b'}).a, 'b', 'can extend an object with the attributes of another');
+ equal(_.extend({a:'x'}, {a:'b'}).a, 'b', 'properties in source override destination');
+ equal(_.extend({x:'x'}, {a:'b'}).x, 'x', 'properties not in source dont get overriden');
+ result = _.extend({x:'x'}, {a:'a'}, {b:'b'});
+ ok(_.isEqual(result, {x:'x', a:'a', b:'b'}), 'can extend from multiple source objects');
+ result = _.extend({x:'x'}, {a:'a', x:2}, {a:'b'});
+ ok(_.isEqual(result, {x:2, a:'b'}), 'extending from multiple source objects last property trumps');
+ result = _.extend({}, {a: void 0, b: null});
+ equal(_.keys(result).join(''), 'ab', 'extend does not copy undefined values');
+ });
+
+ test("pick", function() {
+ var result;
+ result = _.pick({a:1, b:2, c:3}, 'a', 'c');
+ ok(_.isEqual(result, {a:1, c:3}), 'can restrict properties to those named');
+ result = _.pick({a:1, b:2, c:3}, ['b', 'c']);
+ ok(_.isEqual(result, {b:2, c:3}), 'can restrict properties to those named in an array');
+ result = _.pick({a:1, b:2, c:3}, ['a'], 'b');
+ ok(_.isEqual(result, {a:1, b:2}), 'can restrict properties to those named in mixed args');
+
+ var Obj = function(){};
+ Obj.prototype = {a: 1, b: 2, c: 3};
+ ok(_.isEqual(_.pick(new Obj, 'a', 'c'), {a:1, c: 3}), 'include prototype props');
+ });
+
+ test("omit", function() {
+ var result;
+ result = _.omit({a:1, b:2, c:3}, 'b');
+ ok(_.isEqual(result, {a:1, c:3}), 'can omit a single named property');
+ result = _.omit({a:1, b:2, c:3}, 'a', 'c');
+ ok(_.isEqual(result, {b:2}), 'can omit several named properties');
+ result = _.omit({a:1, b:2, c:3}, ['b', 'c']);
+ ok(_.isEqual(result, {a:1}), 'can omit properties named in an array');
+
+ var Obj = function(){};
+ Obj.prototype = {a: 1, b: 2, c: 3};
+ ok(_.isEqual(_.omit(new Obj, 'b'), {a:1, c: 3}), 'include prototype props');
+ });
+
+ test("defaults", function() {
+ var result;
+ var options = {zero: 0, one: 1, empty: "", nan: NaN, string: "string"};
+
+ _.defaults(options, {zero: 1, one: 10, twenty: 20});
+ equal(options.zero, 0, 'value exists');
+ equal(options.one, 1, 'value exists');
+ equal(options.twenty, 20, 'default applied');
+
+ _.defaults(options, {empty: "full"}, {nan: "nan"}, {word: "word"}, {word: "dog"});
+ equal(options.empty, "", 'value exists');
+ ok(_.isNaN(options.nan), "NaN isn't overridden");
+ equal(options.word, "word", 'new value is added, first one wins');
+ });
+
+ test("clone", function() {
+ var moe = {name : 'moe', lucky : [13, 27, 34]};
+ var clone = _.clone(moe);
+ equal(clone.name, 'moe', 'the clone as the attributes of the original');
+
+ clone.name = 'curly';
+ ok(clone.name == 'curly' && moe.name == 'moe', 'clones can change shallow attributes without affecting the original');
+
+ clone.lucky.push(101);
+ equal(_.last(moe.lucky), 101, 'changes to deep attributes are shared with the original');
+
+ equal(_.clone(undefined), void 0, 'non objects should not be changed by clone');
+ equal(_.clone(1), 1, 'non objects should not be changed by clone');
+ equal(_.clone(null), null, 'non objects should not be changed by clone');
+ });
+
+ test("isEqual", function() {
+ function First() {
+ this.value = 1;
+ }
+ First.prototype.value = 1;
+ function Second() {
+ this.value = 1;
+ }
+ Second.prototype.value = 2;
+
+ // Basic equality and identity comparisons.
+ ok(_.isEqual(null, null), "`null` is equal to `null`");
+ ok(_.isEqual(), "`undefined` is equal to `undefined`");
+
+ ok(!_.isEqual(0, -0), "`0` is not equal to `-0`");
+ ok(!_.isEqual(-0, 0), "Commutative equality is implemented for `0` and `-0`");
+ ok(!_.isEqual(null, undefined), "`null` is not equal to `undefined`");
+ ok(!_.isEqual(undefined, null), "Commutative equality is implemented for `null` and `undefined`");
+
+ // String object and primitive comparisons.
+ ok(_.isEqual("Curly", "Curly"), "Identical string primitives are equal");
+ ok(_.isEqual(new String("Curly"), new String("Curly")), "String objects with identical primitive values are equal");
+ ok(_.isEqual(new String("Curly"), "Curly"), "String primitives and their corresponding object wrappers are equal");
+ ok(_.isEqual("Curly", new String("Curly")), "Commutative equality is implemented for string objects and primitives");
+
+ ok(!_.isEqual("Curly", "Larry"), "String primitives with different values are not equal");
+ ok(!_.isEqual(new String("Curly"), new String("Larry")), "String objects with different primitive values are not equal");
+ ok(!_.isEqual(new String("Curly"), {toString: function(){ return "Curly"; }}), "String objects and objects with a custom `toString` method are not equal");
+
+ // Number object and primitive comparisons.
+ ok(_.isEqual(75, 75), "Identical number primitives are equal");
+ ok(_.isEqual(new Number(75), new Number(75)), "Number objects with identical primitive values are equal");
+ ok(_.isEqual(75, new Number(75)), "Number primitives and their corresponding object wrappers are equal");
+ ok(_.isEqual(new Number(75), 75), "Commutative equality is implemented for number objects and primitives");
+ ok(!_.isEqual(new Number(0), -0), "`new Number(0)` and `-0` are not equal");
+ ok(!_.isEqual(0, new Number(-0)), "Commutative equality is implemented for `new Number(0)` and `-0`");
+
+ ok(!_.isEqual(new Number(75), new Number(63)), "Number objects with different primitive values are not equal");
+ ok(!_.isEqual(new Number(63), {valueOf: function(){ return 63; }}), "Number objects and objects with a `valueOf` method are not equal");
+
+ // Comparisons involving `NaN`.
+ ok(_.isEqual(NaN, NaN), "`NaN` is equal to `NaN`");
+ ok(!_.isEqual(61, NaN), "A number primitive is not equal to `NaN`");
+ ok(!_.isEqual(new Number(79), NaN), "A number object is not equal to `NaN`");
+ ok(!_.isEqual(Infinity, NaN), "`Infinity` is not equal to `NaN`");
+
+ // Boolean object and primitive comparisons.
+ ok(_.isEqual(true, true), "Identical boolean primitives are equal");
+ ok(_.isEqual(new Boolean, new Boolean), "Boolean objects with identical primitive values are equal");
+ ok(_.isEqual(true, new Boolean(true)), "Boolean primitives and their corresponding object wrappers are equal");
+ ok(_.isEqual(new Boolean(true), true), "Commutative equality is implemented for booleans");
+ ok(!_.isEqual(new Boolean(true), new Boolean), "Boolean objects with different primitive values are not equal");
+
+ // Common type coercions.
+ ok(!_.isEqual(true, new Boolean(false)), "Boolean objects are not equal to the boolean primitive `true`");
+ ok(!_.isEqual("75", 75), "String and number primitives with like values are not equal");
+ ok(!_.isEqual(new Number(63), new String(63)), "String and number objects with like values are not equal");
+ ok(!_.isEqual(75, "75"), "Commutative equality is implemented for like string and number values");
+ ok(!_.isEqual(0, ""), "Number and string primitives with like values are not equal");
+ ok(!_.isEqual(1, true), "Number and boolean primitives with like values are not equal");
+ ok(!_.isEqual(new Boolean(false), new Number(0)), "Boolean and number objects with like values are not equal");
+ ok(!_.isEqual(false, new String("")), "Boolean primitives and string objects with like values are not equal");
+ ok(!_.isEqual(12564504e5, new Date(2009, 9, 25)), "Dates and their corresponding numeric primitive values are not equal");
+
+ // Dates.
+ ok(_.isEqual(new Date(2009, 9, 25), new Date(2009, 9, 25)), "Date objects referencing identical times are equal");
+ ok(!_.isEqual(new Date(2009, 9, 25), new Date(2009, 11, 13)), "Date objects referencing different times are not equal");
+ ok(!_.isEqual(new Date(2009, 11, 13), {
+ getTime: function(){
+ return 12606876e5;
+ }
+ }), "Date objects and objects with a `getTime` method are not equal");
+ ok(!_.isEqual(new Date("Curly"), new Date("Curly")), "Invalid dates are not equal");
+
+ // Functions.
+ ok(!_.isEqual(First, Second), "Different functions with identical bodies and source code representations are not equal");
+
+ // RegExps.
+ ok(_.isEqual(/(?:)/gim, /(?:)/gim), "RegExps with equivalent patterns and flags are equal");
+ ok(!_.isEqual(/(?:)/g, /(?:)/gi), "RegExps with equivalent patterns and different flags are not equal");
+ ok(!_.isEqual(/Moe/gim, /Curly/gim), "RegExps with different patterns and equivalent flags are not equal");
+ ok(!_.isEqual(/(?:)/gi, /(?:)/g), "Commutative equality is implemented for RegExps");
+ ok(!_.isEqual(/Curly/g, {source: "Larry", global: true, ignoreCase: false, multiline: false}), "RegExps and RegExp-like objects are not equal");
+
+ // Empty arrays, array-like objects, and object literals.
+ ok(_.isEqual({}, {}), "Empty object literals are equal");
+ ok(_.isEqual([], []), "Empty array literals are equal");
+ ok(_.isEqual([{}], [{}]), "Empty nested arrays and objects are equal");
+ ok(!_.isEqual({length: 0}, []), "Array-like objects and arrays are not equal.");
+ ok(!_.isEqual([], {length: 0}), "Commutative equality is implemented for array-like objects");
+
+ ok(!_.isEqual({}, []), "Object literals and array literals are not equal");
+ ok(!_.isEqual([], {}), "Commutative equality is implemented for objects and arrays");
+
+ // Arrays with primitive and object values.
+ ok(_.isEqual([1, "Larry", true], [1, "Larry", true]), "Arrays containing identical primitives are equal");
+ ok(_.isEqual([(/Moe/g), new Date(2009, 9, 25)], [(/Moe/g), new Date(2009, 9, 25)]), "Arrays containing equivalent elements are equal");
+
+ // Multi-dimensional arrays.
+ var a = [new Number(47), false, "Larry", /Moe/, new Date(2009, 11, 13), ['running', 'biking', new String('programming')], {a: 47}];
+ var b = [new Number(47), false, "Larry", /Moe/, new Date(2009, 11, 13), ['running', 'biking', new String('programming')], {a: 47}];
+ ok(_.isEqual(a, b), "Arrays containing nested arrays and objects are recursively compared");
+
+ // Overwrite the methods defined in ES 5.1 section 15.4.4.
+ a.forEach = a.map = a.filter = a.every = a.indexOf = a.lastIndexOf = a.some = a.reduce = a.reduceRight = null;
+ b.join = b.pop = b.reverse = b.shift = b.slice = b.splice = b.concat = b.sort = b.unshift = null;
+
+ // Array elements and properties.
+ ok(_.isEqual(a, b), "Arrays containing equivalent elements and different non-numeric properties are equal");
+ a.push("White Rocks");
+ ok(!_.isEqual(a, b), "Arrays of different lengths are not equal");
+ a.push("East Boulder");
+ b.push("Gunbarrel Ranch", "Teller Farm");
+ ok(!_.isEqual(a, b), "Arrays of identical lengths containing different elements are not equal");
+
+ // Sparse arrays.
+ 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");
+
+ // 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/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: 63, b: 75}, {a: 61, b: 55}), "Objects of identical sizes with different values are not equal");
+ ok(!_.isEqual({a: 63, b: 75}, {a: 61, c: 55}), "Objects of identical sizes with different property names are not equal");
+ ok(!_.isEqual({a: 1, b: 2}, {a: 1}), "Objects of different sizes are not equal");
+ ok(!_.isEqual({a: 1}, {a: 1, b: 2}), "Commutative equality is implemented for objects");
+ ok(!_.isEqual({x: 1, y: undefined}, {x: 1, z: 2}), "Objects with identical keys and different values are not equivalent");
+
+ // `A` contains nested objects and arrays.
+ a = {
+ name: new String("Moe Howard"),
+ age: new Number(77),
+ stooge: true,
+ hobbies: ["acting"],
+ film: {
+ name: "Sing a Song of Six Pants",
+ release: new Date(1947, 9, 30),
+ stars: [new String("Larry Fine"), "Shemp Howard"],
+ minutes: new Number(16),
+ seconds: 54
+ }
+ };
+
+ // `B` contains equivalent nested objects and arrays.
+ b = {
+ name: new String("Moe Howard"),
+ age: new Number(77),
+ stooge: true,
+ hobbies: ["acting"],
+ film: {
+ name: "Sing a Song of Six Pants",
+ release: new Date(1947, 9, 30),
+ stars: [new String("Larry Fine"), "Shemp Howard"],
+ minutes: new Number(16),
+ seconds: 54
+ }
+ };
+ ok(_.isEqual(a, b), "Objects with nested equivalent members are recursively compared");
+
+ // Instances.
+ ok(_.isEqual(new First, new First), "Object instances are equal");
+ ok(!_.isEqual(new First, new Second), "Objects with different constructors and identical own properties are not equal");
+ ok(!_.isEqual({value: 1}, new First), "Object instances and objects sharing equivalent properties are not equal");
+ ok(!_.isEqual({value: 2}, new Second), "The prototype chain of objects should not be examined");
+
+ // Circular Arrays.
+ (a = []).push(a);
+ (b = []).push(b);
+ ok(_.isEqual(a, b), "Arrays containing circular references are equal");
+ a.push(new String("Larry"));
+ b.push(new String("Larry"));
+ ok(_.isEqual(a, b), "Arrays containing circular references and equivalent properties are equal");
+ a.push("Shemp");
+ b.push("Curly");
+ ok(!_.isEqual(a, b), "Arrays containing circular references and different properties are not equal");
+
+ // More circular arrays #767.
+ a = ["everything is checked but", "this", "is not"];
+ a[1] = a;
+ b = ["everything is checked but", ["this", "array"], "is not"];
+ ok(!_.isEqual(a, b), "Comparison of circular references with non-circular references are not equal");
+
+ // Circular Objects.
+ a = {abc: null};
+ b = {abc: null};
+ a.abc = a;
+ b.abc = b;
+ ok(_.isEqual(a, b), "Objects containing circular references are equal");
+ a.def = 75;
+ b.def = 75;
+ ok(_.isEqual(a, b), "Objects containing circular references and equivalent properties are equal");
+ a.def = new Number(75);
+ b.def = new Number(63);
+ ok(!_.isEqual(a, b), "Objects containing circular references and different properties are not equal");
+
+ // More circular objects #767.
+ a = {everything: "is checked", but: "this", is: "not"};
+ a.but = a;
+ b = {everything: "is checked", but: {that:"object"}, is: "not"};
+ ok(!_.isEqual(a, b), "Comparison of circular references with non-circular object references are not equal");
+
+ // Cyclic Structures.
+ a = [{abc: null}];
+ b = [{abc: null}];
+ (a[0].abc = a).push(a);
+ (b[0].abc = b).push(b);
+ ok(_.isEqual(a, b), "Cyclic structures are equal");
+ a[0].def = "Larry";
+ b[0].def = "Larry";
+ ok(_.isEqual(a, b), "Cyclic structures containing equivalent properties are equal");
+ a[0].def = new String("Larry");
+ b[0].def = new String("Curly");
+ ok(!_.isEqual(a, b), "Cyclic structures containing different properties are not equal");
+
+ // Complex Circular References.
+ a = {foo: {b: {foo: {c: {foo: null}}}}};
+ b = {foo: {b: {foo: {c: {foo: null}}}}};
+ a.foo.b.foo.c.foo = a;
+ b.foo.b.foo.c.foo = b;
+ ok(_.isEqual(a, b), "Cyclic structures with nested and identically-named properties are equal");
+
+ // Chaining.
+ ok(!_.isEqual(_({x: 1, y: undefined}).chain(), _({x: 1, z: 2}).chain()), 'Chained objects containing different values are not equal');
+ equal(_({x: 1, y: 2}).chain().isEqual(_({x: 1, y: 2}).chain()).value(), true, '`isEqual` can be chained');
+
+ // Custom `isEqual` methods.
+ var isEqualObj = {isEqual: function (o) { return o.isEqual == this.isEqual; }, unique: {}};
+ var isEqualObjClone = {isEqual: isEqualObj.isEqual, unique: {}};
+
+ ok(_.isEqual(isEqualObj, isEqualObjClone), 'Both objects implement identical `isEqual` methods');
+ ok(_.isEqual(isEqualObjClone, isEqualObj), 'Commutative equality is implemented for objects with custom `isEqual` methods');
+ 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');
+
+ // Objects from another frame.
+ ok(_.isEqual({}, iObject));
+ });
+
+ test("isEmpty", function() {
+ ok(!_([1]).isEmpty(), '[1] is not empty');
+ ok(_.isEmpty([]), '[] is empty');
+ ok(!_.isEmpty({one : 1}), '{one : 1} is not empty');
+ ok(_.isEmpty({}), '{} is empty');
+ ok(_.isEmpty(new RegExp('')), 'objects with prototype properties are empty');
+ ok(_.isEmpty(null), 'null is empty');
+ ok(_.isEmpty(), 'undefined is empty');
+ ok(_.isEmpty(''), 'the empty string is empty');
+ ok(!_.isEmpty('moe'), 'but other strings are not');
+
+ var obj = {one : 1};
+ delete obj.one;
+ ok(_.isEmpty(obj), 'deleting all the keys from an object empties it');
+ });
+
+ // Setup remote variables for iFrame tests.
+ var iframe = document.createElement('iframe');
+ jQuery(iframe).appendTo(document.body);
+ var iDoc = iframe.contentDocument || iframe.contentWindow.document;
+ iDoc.write(
+ ""
+ );
+ iDoc.close();
+
+ test("isElement", function() {
+ ok(!_.isElement('div'), 'strings are not dom elements');
+ ok(_.isElement($('html')[0]), 'the html tag is a DOM element');
+ ok(_.isElement(iElement), 'even from another frame');
+ });
+
+ test("isArguments", function() {
+ var args = (function(){ return arguments; })(1, 2, 3);
+ ok(!_.isArguments('string'), 'a string is not an arguments object');
+ ok(!_.isArguments(_.isArguments), 'a function is not an arguments object');
+ ok(_.isArguments(args), 'but the arguments object is an arguments object');
+ ok(!_.isArguments(_.toArray(args)), 'but not when it\'s converted into an array');
+ ok(!_.isArguments([1,2,3]), 'and not vanilla arrays.');
+ ok(_.isArguments(iArguments), 'even from another frame');
+ });
+
+ test("isObject", function() {
+ ok(_.isObject(arguments), 'the arguments object is object');
+ ok(_.isObject([1, 2, 3]), 'and arrays');
+ ok(_.isObject($('html')[0]), 'and DOM element');
+ ok(_.isObject(iElement), 'even from another frame');
+ ok(_.isObject(function () {}), 'and functions');
+ ok(_.isObject(iFunction), 'even from another frame');
+ ok(!_.isObject(null), 'but not null');
+ ok(!_.isObject(undefined), 'and not undefined');
+ ok(!_.isObject('string'), 'and not string');
+ ok(!_.isObject(12), 'and not number');
+ ok(!_.isObject(true), 'and not boolean');
+ ok(_.isObject(new String('string')), 'but new String()');
+ });
+
+ test("isArray", function() {
+ ok(!_.isArray(arguments), 'the arguments object is not an array');
+ ok(_.isArray([1, 2, 3]), 'but arrays are');
+ ok(_.isArray(iArray), 'even from another frame');
+ });
+
+ test("isString", function() {
+ ok(!_.isString(document.body), 'the document body is not a string');
+ ok(_.isString([1, 2, 3].join(', ')), 'but strings are');
+ ok(_.isString(iString), 'even from another frame');
+ });
+
+ test("isNumber", function() {
+ ok(!_.isNumber('string'), 'a string is not a number');
+ ok(!_.isNumber(arguments), 'the arguments object is not a number');
+ ok(!_.isNumber(undefined), 'undefined is not a number');
+ ok(_.isNumber(3 * 4 - 7 / 10), 'but numbers are');
+ ok(_.isNumber(NaN), 'NaN *is* a number');
+ ok(_.isNumber(Infinity), 'Infinity is a number');
+ ok(_.isNumber(iNumber), 'even from another frame');
+ ok(!_.isNumber('1'), 'numeric strings are not numbers');
+ });
+
+ test("isBoolean", function() {
+ ok(!_.isBoolean(2), 'a number is not a boolean');
+ ok(!_.isBoolean("string"), 'a string is not a boolean');
+ ok(!_.isBoolean("false"), 'the string "false" is not a boolean');
+ ok(!_.isBoolean("true"), 'the string "true" is not a boolean');
+ ok(!_.isBoolean(arguments), 'the arguments object is not a boolean');
+ ok(!_.isBoolean(undefined), 'undefined is not a boolean');
+ ok(!_.isBoolean(NaN), 'NaN is not a boolean');
+ ok(!_.isBoolean(null), 'null is not a boolean');
+ ok(_.isBoolean(true), 'but true is');
+ ok(_.isBoolean(false), 'and so is false');
+ ok(_.isBoolean(iBoolean), 'even from another frame');
+ });
+
+ test("isFunction", function() {
+ ok(!_.isFunction([1, 2, 3]), 'arrays are not functions');
+ ok(!_.isFunction('moe'), 'strings are not functions');
+ ok(_.isFunction(_.isFunction), 'but functions are');
+ ok(_.isFunction(iFunction), 'even from another frame');
+ });
+
+ test("isDate", function() {
+ ok(!_.isDate(100), 'numbers are not dates');
+ ok(!_.isDate({}), 'objects are not dates');
+ ok(_.isDate(new Date()), 'but dates are');
+ ok(_.isDate(iDate), 'even from another frame');
+ });
+
+ test("isRegExp", function() {
+ ok(!_.isRegExp(_.identity), 'functions are not RegExps');
+ ok(_.isRegExp(/identity/), 'but RegExps are');
+ ok(_.isRegExp(iRegExp), 'even from another frame');
+ });
+
+ test("isFinite", function() {
+ ok(!_.isFinite(undefined), 'undefined is not Finite');
+ ok(!_.isFinite(null), 'null is not Finite');
+ ok(!_.isFinite(NaN), 'NaN is not Finite');
+ ok(!_.isFinite(Infinity), 'Infinity is not Finite');
+ ok(!_.isFinite(-Infinity), '-Infinity is not Finite');
+ ok(!_.isFinite('12'), 'Strings are not numbers');
+ var obj = new Number(5);
+ ok(_.isFinite(obj), 'Number instances can be finite');
+ ok(_.isFinite(0), '0 is Finite');
+ ok(_.isFinite(123), 'Ints are Finite');
+ ok(_.isFinite(-12.44), 'Floats are Finite');
+ });
+
+ test("isNaN", function() {
+ ok(!_.isNaN(undefined), 'undefined is not NaN');
+ ok(!_.isNaN(null), 'null is not NaN');
+ ok(!_.isNaN(0), '0 is not NaN');
+ ok(_.isNaN(NaN), 'but NaN is');
+ ok(_.isNaN(iNaN), 'even from another frame');
+ ok(_.isNaN(new Number(NaN)), 'wrapped NaN is still NaN');
+ });
+
+ test("isNull", function() {
+ ok(!_.isNull(undefined), 'undefined is not null');
+ ok(!_.isNull(NaN), 'NaN is not null');
+ ok(_.isNull(null), 'but null is');
+ ok(_.isNull(iNull), 'even from another frame');
+ });
+
+ test("isUndefined", function() {
+ ok(!_.isUndefined(1), 'numbers are defined');
+ ok(!_.isUndefined(null), 'null is defined');
+ ok(!_.isUndefined(false), 'false is defined');
+ ok(!_.isUndefined(NaN), 'NaN is defined');
+ ok(_.isUndefined(), 'nothing is undefined');
+ ok(_.isUndefined(undefined), 'undefined is undefined');
+ ok(_.isUndefined(iUndefined), 'even from another frame');
+ });
+
+ if (window.ActiveXObject) {
+ test("IE host objects", function() {
+ var xml = new ActiveXObject("Msxml2.DOMDocument.3.0");
+ ok(!_.isNumber(xml));
+ ok(!_.isBoolean(xml));
+ ok(!_.isNaN(xml));
+ ok(!_.isFunction(xml));
+ ok(!_.isNull(xml));
+ ok(!_.isUndefined(xml));
+ });
+ }
+
+ test("tap", function() {
+ var intercepted = null;
+ var interceptor = function(obj) { intercepted = obj; };
+ var returned = _.tap(1, interceptor);
+ equal(intercepted, 1, "passes tapped object to interceptor");
+ equal(returned, 1, "returns tapped object");
+
+ returned = _([1,2,3]).chain().
+ map(function(n){ return n * 2; }).
+ max().
+ tap(interceptor).
+ value();
+ ok(returned == 6 && intercepted == 6, 'can use tapped objects in a chain');
+ });
+});
diff --git a/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/test_underscore/speed.js b/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/test_underscore/speed.js
new file mode 100644
index 0000000..05e3f2a
--- /dev/null
+++ b/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/test_underscore/speed.js
@@ -0,0 +1,75 @@
+(function() {
+
+ var numbers = [];
+ for (var i=0; i<1000; i++) numbers.push(i);
+ var objects = _.map(numbers, function(n){ return {num : n}; });
+ var randomized = _.sortBy(numbers, function(){ return Math.random(); });
+ var deep = _.map(_.range(100), function() { return _.range(1000); });
+
+ JSLitmus.test('_.each()', function() {
+ var timesTwo = [];
+ _.each(numbers, function(num){ timesTwo.push(num * 2); });
+ return timesTwo;
+ });
+
+ JSLitmus.test('_(list).each()', function() {
+ var timesTwo = [];
+ _(numbers).each(function(num){ timesTwo.push(num * 2); });
+ return timesTwo;
+ });
+
+ JSLitmus.test('jQuery.each()', function() {
+ var timesTwo = [];
+ jQuery.each(numbers, function(){ timesTwo.push(this * 2); });
+ return timesTwo;
+ });
+
+ JSLitmus.test('_.map()', function() {
+ return _.map(objects, function(obj){ return obj.num; });
+ });
+
+ JSLitmus.test('jQuery.map()', function() {
+ return jQuery.map(objects, function(obj){ return obj.num; });
+ });
+
+ JSLitmus.test('_.pluck()', function() {
+ return _.pluck(objects, 'num');
+ });
+
+ JSLitmus.test('_.uniq()', function() {
+ return _.uniq(randomized);
+ });
+
+ JSLitmus.test('_.uniq() (sorted)', function() {
+ return _.uniq(numbers, true);
+ });
+
+ JSLitmus.test('_.sortBy()', function() {
+ return _.sortBy(numbers, function(num){ return -num; });
+ });
+
+ JSLitmus.test('_.isEqual()', function() {
+ return _.isEqual(numbers, randomized);
+ });
+
+ JSLitmus.test('_.keys()', function() {
+ return _.keys(objects);
+ });
+
+ JSLitmus.test('_.values()', function() {
+ return _.values(objects);
+ });
+
+ JSLitmus.test('_.intersection()', function() {
+ return _.intersection(numbers, randomized);
+ });
+
+ JSLitmus.test('_.range()', function() {
+ return _.range(1000);
+ });
+
+ JSLitmus.test('_.flatten()', function() {
+ return _.flatten(deep);
+ });
+
+})();
diff --git a/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/test_underscore/utility.js b/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/test_underscore/utility.js
new file mode 100644
index 0000000..c9be20a
--- /dev/null
+++ b/node_modules/grunt-legacy-log-utils/node_modules/underscore.string/test/test_underscore/utility.js
@@ -0,0 +1,249 @@
+$(document).ready(function() {
+
+ var templateSettings;
+
+ module("Utility", {
+
+ setup: function() {
+ templateSettings = _.clone(_.templateSettings);
+ },
+
+ teardown: function() {
+ _.templateSettings = templateSettings;
+ }
+
+ });
+
+ test("#750 - Return _ instance.", 2, function() {
+ var instance = _([]);
+ ok(_(instance) === instance);
+ ok(new _(instance) === instance);
+ });
+
+ test("identity", function() {
+ var moe = {name : 'moe'};
+ equal(_.identity(moe), moe, 'moe is the same as his identity');
+ });
+
+ test("uniqueId", function() {
+ var ids = [], i = 0;
+ while(i++ < 100) ids.push(_.uniqueId());
+ equal(_.uniq(ids).length, ids.length, 'can generate a globally-unique stream of ids');
+ });
+
+ test("times", function() {
+ var vals = [];
+ _.times(3, function (i) { vals.push(i); });
+ ok(_.isEqual(vals, [0,1,2]), "is 0 indexed");
+ //
+ vals = [];
+ _(3).times(function (i) { vals.push(i); });
+ ok(_.isEqual(vals, [0,1,2]), "works as a wrapper");
+ });
+
+ test("mixin", function() {
+ _.mixin({
+ myReverse: function(string) {
+ return string.split('').reverse().join('');
+ }
+ });
+ equal(_.myReverse('panacea'), 'aecanap', 'mixed in a function to _');
+ equal(_('champ').myReverse(), 'pmahc', 'mixed in a function to the OOP wrapper');
+ });
+
+ test("_.escape", function() {
+ equal(_.escape("Curly & Moe"), "Curly & Moe");
+ equal(_.escape("Curly & Moe"), "Curly & Moe");
+ equal(_.escape(null), '');
+ });
+
+ test("_.unescape", function() {
+ var string = "Curly & Moe";
+ equal(_.unescape("Curly & Moe"), string);
+ equal(_.unescape("Curly & Moe"), "Curly & Moe");
+ equal(_.unescape(null), '');
+ equal(_.unescape(_.escape(string)), string);
+ });
+
+ test("template", function() {
+ var basicTemplate = _.template("<%= thing %> is gettin' on my noives!");
+ var result = basicTemplate({thing : 'This'});
+ equal(result, "This is gettin' on my noives!", 'can do basic attribute interpolation');
+
+ var sansSemicolonTemplate = _.template("A <% this %> B");
+ equal(sansSemicolonTemplate(), "A B");
+
+ var backslashTemplate = _.template("<%= thing %> is \\ridanculous");
+ equal(backslashTemplate({thing: 'This'}), "This is \\ridanculous");
+
+ var escapeTemplate = _.template('<%= a ? "checked=\\"checked\\"" : "" %>');
+ equal(escapeTemplate({a: true}), 'checked="checked"', 'can handle slash escapes in interpolations.');
+
+ var fancyTemplate = _.template("<% \
+ for (key in people) { \
+ %>- <%= people[key] %>
<% } %>
");
+ result = fancyTemplate({people : {moe : "Moe", larry : "Larry", curly : "Curly"}});
+ equal(result, "", 'can run arbitrary javascript in templates');
+
+ var escapedCharsInJavascriptTemplate = _.template("<% _.each(numbers.split('\\n'), function(item) { %>- <%= item %>
<% }) %>
");
+ result = escapedCharsInJavascriptTemplate({numbers: "one\ntwo\nthree\nfour"});
+ equal(result, "", 'Can use escaped characters (e.g. \\n) in Javascript');
+
+ var namespaceCollisionTemplate = _.template("<%= pageCount %> <%= thumbnails[pageCount] %> <% _.each(thumbnails, function(p) { %>\">
<% }); %>");
+ result = namespaceCollisionTemplate({
+ pageCount: 3,
+ thumbnails: {
+ 1: "p1-thumbnail.gif",
+ 2: "p2-thumbnail.gif",
+ 3: "p3-thumbnail.gif"
+ }
+ });
+ equal(result, "3 p3-thumbnail.gif ");
+
+ var noInterpolateTemplate = _.template("Just some text. Hey, I know this is silly but it aids consistency.
");
+ result = noInterpolateTemplate();
+ equal(result, "Just some text. Hey, I know this is silly but it aids consistency.
");
+
+ var quoteTemplate = _.template("It's its, not it's");
+ equal(quoteTemplate({}), "It's its, not it's");
+
+ var quoteInStatementAndBody = _.template("<%\
+ if(foo == 'bar'){ \
+ %>Statement quotes and 'quotes'.<% } %>");
+ equal(quoteInStatementAndBody({foo: "bar"}), "Statement quotes and 'quotes'.");
+
+ var withNewlinesAndTabs = _.template('This\n\t\tis: <%= x %>.\n\tok.\nend.');
+ equal(withNewlinesAndTabs({x: 'that'}), 'This\n\t\tis: that.\n\tok.\nend.');
+
+ var template = _.template("<%- value %>");
+ var result = template({value: "
+```
+
+Using [`npm`](http://npmjs.org/):
+
+```bash
+npm i --save lodash
+
+{sudo} npm i -g lodash
+npm ln lodash
+```
+
+In [Node.js](http://nodejs.org/) & [Ringo](http://ringojs.org/):
+
+```js
+var _ = require('lodash');
+// or as Underscore
+var _ = require('lodash/dist/lodash.underscore');
+```
+
+**Notes:**
+ * Don’t assign values to [special variable](http://nodejs.org/api/repl.html#repl_repl_features) `_` when in the REPL
+ * If Lo-Dash is installed globally, run [`npm ln lodash`](http://blog.nodejs.org/2011/03/23/npm-1-0-global-vs-local-installation/) in your project’s root directory *before* requiring it
+
+In [Rhino](http://www.mozilla.org/rhino/):
+
+```js
+load('lodash.js');
+```
+
+In an AMD loader:
+
+```js
+require({
+ 'packages': [
+ { 'name': 'lodash', 'location': 'path/to/lodash', 'main': 'lodash' }
+ ]
+},
+['lodash'], function(_) {
+ console.log(_.VERSION);
+});
+```
+
+## Resources
+
+ * Podcasts
+ - [JavaScript Jabber](http://javascriptjabber.com/079-jsj-lo-dash-with-john-david-dalton/)
+
+ * Posts
+ - [Say “Hello” to Lo-Dash](http://kitcambridge.be/blog/say-hello-to-lo-dash/)
+ - [Custom builds in Lo-Dash 2.0](http://kitcambridge.be/blog/custom-builds-in-lo-dash-2-dot-0/)
+
+ * Videos
+ - [Introduction](https://vimeo.com/44154599)
+ - [Origins](https://vimeo.com/44154600)
+ - [Optimizations & builds](https://vimeo.com/44154601)
+ - [Native method use](https://vimeo.com/48576012)
+ - [Testing](https://vimeo.com/45865290)
+ - [CascadiaJS ’12](http://www.youtube.com/watch?v=dpPy4f_SeEk)
+
+ A list of other community created podcasts, posts, & videos is available on our [wiki](https://github.com/lodash/lodash/wiki/Resources).
+
+## Features
+
+ * AMD loader support ([curl](https://github.com/cujojs/curl), [dojo](http://dojotoolkit.org/), [requirejs](http://requirejs.org/), etc.)
+ * [_(…)](https://lodash.com/docs#_) supports intuitive chaining
+ * [_.at](https://lodash.com/docs#at) for cherry-picking collection values
+ * [_.bindKey](https://lodash.com/docs#bindKey) for binding [*“lazy”*](http://michaux.ca/articles/lazy-function-definition-pattern) defined methods
+ * [_.clone](https://lodash.com/docs#clone) supports shallow cloning of `Date` & `RegExp` objects
+ * [_.cloneDeep](https://lodash.com/docs#cloneDeep) for deep cloning arrays & objects
+ * [_.constant](https://lodash.com/docs#constant) & [_.property](https://lodash.com/docs#property) function generators for composing functions
+ * [_.contains](https://lodash.com/docs#contains) accepts a `fromIndex`
+ * [_.create](https://lodash.com/docs#create) for easier object inheritance
+ * [_.createCallback](https://lodash.com/docs#createCallback) for extending callbacks in methods & mixins
+ * [_.curry](https://lodash.com/docs#curry) for creating [curried](http://hughfdjackson.com/javascript/2013/07/06/why-curry-helps/) functions
+ * [_.debounce](https://lodash.com/docs#debounce) & [_.throttle](https://lodash.com/docs#throttle) accept additional `options` for more control
+ * [_.findIndex](https://lodash.com/docs#findIndex) & [_.findKey](https://lodash.com/docs#findKey) for finding indexes & keys
+ * [_.forEach](https://lodash.com/docs#forEach) is chainable & supports exiting early
+ * [_.forIn](https://lodash.com/docs#forIn) for iterating own & inherited properties
+ * [_.forOwn](https://lodash.com/docs#forOwn) for iterating own properties
+ * [_.isPlainObject](https://lodash.com/docs#isPlainObject) for checking if values are created by `Object`
+ * [_.mapValues](https://lodash.com/docs#mapValues) for [mapping](https://lodash.com/docs#map) values to an object
+ * [_.memoize](https://lodash.com/docs#memoize) exposes the `cache` of memoized functions
+ * [_.merge](https://lodash.com/docs#merge) for a deep [_.extend](https://lodash.com/docs#extend)
+ * [_.noop](https://lodash.com/docs#noop) for function placeholders
+ * [_.now](https://lodash.com/docs#now) as a cross-browser `Date.now` alternative
+ * [_.parseInt](https://lodash.com/docs#parseInt) for consistent behavior
+ * [_.pull](https://lodash.com/docs#pull) & [_.remove](https://lodash.com/docs#remove) for mutating arrays
+ * [_.random](https://lodash.com/docs#random) supports returning floating-point numbers
+ * [_.runInContext](https://lodash.com/docs#runInContext) for easier mocking
+ * [_.sortBy](https://lodash.com/docs#sortBy) supports sorting by multiple properties
+ * [_.support](https://lodash.com/docs#support) for flagging environment features
+ * [_.template](https://lodash.com/docs#template) supports [*“imports”*](https://lodash.com/docs#templateSettings_imports) options & [ES6 template delimiters](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-literals-string-literals)
+ * [_.transform](https://lodash.com/docs#transform) as a powerful alternative to [_.reduce](https://lodash.com/docs#reduce) for transforming objects
+ * [_.where](https://lodash.com/docs#where) supports deep object comparisons
+ * [_.xor](https://lodash.com/docs#xor) as a companion to [_.difference](https://lodash.com/docs#difference), [_.intersection](https://lodash.com/docs#intersection), & [_.union](https://lodash.com/docs#union)
+ * [_.zip](https://lodash.com/docs#zip) is capable of unzipping values
+ * [_.omit](https://lodash.com/docs#omit), [_.pick](https://lodash.com/docs#pick), &
+ [more](https://lodash.com/docs "_.assign, _.clone, _.cloneDeep, _.first, _.initial, _.isEqual, _.last, _.merge, _.rest") accept callbacks
+ * [_.contains](https://lodash.com/docs#contains), [_.toArray](https://lodash.com/docs#toArray), &
+ [more](https://lodash.com/docs "_.at, _.countBy, _.every, _.filter, _.find, _.forEach, _.forEachRight, _.groupBy, _.invoke, _.map, _.max, _.min, _.pluck, _.reduce, _.reduceRight, _.reject, _.shuffle, _.size, _.some, _.sortBy, _.where") accept strings
+ * [_.filter](https://lodash.com/docs#filter), [_.map](https://lodash.com/docs#map), &
+ [more](https://lodash.com/docs "_.countBy, _.every, _.find, _.findKey, _.findLast, _.findLastIndex, _.findLastKey, _.first, _.groupBy, _.initial, _.last, _.max, _.min, _.reject, _.rest, _.some, _.sortBy, _.sortedIndex, _.uniq") support *“_.pluck”* & *“_.where”* shorthands
+ * [_.findLast](https://lodash.com/docs#findLast), [_.findLastIndex](https://lodash.com/docs#findLastIndex), &
+ [more](https://lodash.com/docs "_.findLastKey, _.forEachRight, _.forInRight, _.forOwnRight, _.partialRight") right-associative methods
+
+## Support
+
+Tested in Chrome 5~31, Firefox 2~25, IE 6-11, Opera 9.25-17, Safari 3-7, Node.js 0.6.21-0.10.22, Narwhal 0.3.2, PhantomJS 1.9.2, RingoJS 0.9, & Rhino 1.7RC5.
diff --git a/node_modules/grunt-legacy-log/node_modules/lodash/dist/lodash.compat.js b/node_modules/grunt-legacy-log/node_modules/lodash/dist/lodash.compat.js
new file mode 100644
index 0000000..4d35185
--- /dev/null
+++ b/node_modules/grunt-legacy-log/node_modules/lodash/dist/lodash.compat.js
@@ -0,0 +1,7158 @@
+/**
+ * @license
+ * Lo-Dash 2.4.2 (Custom Build)
+ * Build: `lodash -o ./dist/lodash.compat.js`
+ * Copyright 2012-2013 The Dojo Foundation
+ * Based on Underscore.js 1.5.2
+ * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license
+ */
+;(function() {
+
+ /** Used as a safe reference for `undefined` in pre ES5 environments */
+ var undefined;
+
+ /** Used to pool arrays and objects used internally */
+ var arrayPool = [],
+ objectPool = [];
+
+ /** Used to generate unique IDs */
+ var idCounter = 0;
+
+ /** Used internally to indicate various things */
+ var indicatorObject = {};
+
+ /** Used to prefix keys to avoid issues with `__proto__` and properties on `Object.prototype` */
+ var keyPrefix = +new Date + '';
+
+ /** Used as the size when optimizations are enabled for large arrays */
+ var largeArraySize = 75;
+
+ /** Used as the max size of the `arrayPool` and `objectPool` */
+ var maxPoolSize = 40;
+
+ /** Used to detect and test whitespace */
+ var whitespace = (
+ // whitespace
+ ' \t\x0B\f\xA0\ufeff' +
+
+ // line terminators
+ '\n\r\u2028\u2029' +
+
+ // unicode category "Zs" space separators
+ '\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000'
+ );
+
+ /** Used to match empty string literals in compiled template source */
+ var reEmptyStringLeading = /\b__p \+= '';/g,
+ reEmptyStringMiddle = /\b(__p \+=) '' \+/g,
+ reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g;
+
+ /**
+ * Used to match ES6 template delimiters
+ * http://people.mozilla.org/~jorendorff/es6-draft.html#sec-literals-string-literals
+ */
+ var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;
+
+ /** Used to match regexp flags from their coerced string values */
+ var reFlags = /\w*$/;
+
+ /** Used to detected named functions */
+ var reFuncName = /^\s*function[ \n\r\t]+\w/;
+
+ /** Used to match "interpolate" template delimiters */
+ var reInterpolate = /<%=([\s\S]+?)%>/g;
+
+ /** Used to match leading whitespace and zeros to be removed */
+ var reLeadingSpacesAndZeros = RegExp('^[' + whitespace + ']*0+(?=.$)');
+
+ /** Used to ensure capturing order of template delimiters */
+ var reNoMatch = /($^)/;
+
+ /** Used to detect functions containing a `this` reference */
+ var reThis = /\bthis\b/;
+
+ /** Used to match unescaped characters in compiled string literals */
+ var reUnescapedString = /['\n\r\t\u2028\u2029\\]/g;
+
+ /** Used to assign default `context` object properties */
+ var contextProps = [
+ 'Array', 'Boolean', 'Date', 'Error', 'Function', 'Math', 'Number', 'Object',
+ 'RegExp', 'String', '_', 'attachEvent', 'clearTimeout', 'isFinite', 'isNaN',
+ 'parseInt', 'setTimeout'
+ ];
+
+ /** Used to fix the JScript [[DontEnum]] bug */
+ var shadowedProps = [
+ 'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable',
+ 'toLocaleString', 'toString', 'valueOf'
+ ];
+
+ /** Used to make template sourceURLs easier to identify */
+ var templateCounter = 0;
+
+ /** `Object#toString` result shortcuts */
+ var argsClass = '[object Arguments]',
+ arrayClass = '[object Array]',
+ boolClass = '[object Boolean]',
+ dateClass = '[object Date]',
+ errorClass = '[object Error]',
+ funcClass = '[object Function]',
+ numberClass = '[object Number]',
+ objectClass = '[object Object]',
+ regexpClass = '[object RegExp]',
+ stringClass = '[object String]';
+
+ /** Used to identify object classifications that `_.clone` supports */
+ var cloneableClasses = {};
+ cloneableClasses[funcClass] = false;
+ cloneableClasses[argsClass] = cloneableClasses[arrayClass] =
+ cloneableClasses[boolClass] = cloneableClasses[dateClass] =
+ cloneableClasses[numberClass] = cloneableClasses[objectClass] =
+ cloneableClasses[regexpClass] = cloneableClasses[stringClass] = true;
+
+ /** Used as an internal `_.debounce` options object */
+ var debounceOptions = {
+ 'leading': false,
+ 'maxWait': 0,
+ 'trailing': false
+ };
+
+ /** Used as the property descriptor for `__bindData__` */
+ var descriptor = {
+ 'configurable': false,
+ 'enumerable': false,
+ 'value': null,
+ 'writable': false
+ };
+
+ /** Used as the data object for `iteratorTemplate` */
+ var iteratorData = {
+ 'args': '',
+ 'array': null,
+ 'bottom': '',
+ 'firstArg': '',
+ 'init': '',
+ 'keys': null,
+ 'loop': '',
+ 'shadowedProps': null,
+ 'support': null,
+ 'top': '',
+ 'useHas': false
+ };
+
+ /** Used to determine if values are of the language type Object */
+ var objectTypes = {
+ 'boolean': false,
+ 'function': true,
+ 'object': true,
+ 'number': false,
+ 'string': false,
+ 'undefined': false
+ };
+
+ /** Used to escape characters for inclusion in compiled string literals */
+ var stringEscapes = {
+ '\\': '\\',
+ "'": "'",
+ '\n': 'n',
+ '\r': 'r',
+ '\t': 't',
+ '\u2028': 'u2028',
+ '\u2029': 'u2029'
+ };
+
+ /** Used as a reference to the global object */
+ var root = (objectTypes[typeof window] && window) || this;
+
+ /** Detect free variable `exports` */
+ var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports;
+
+ /** Detect free variable `module` */
+ var freeModule = objectTypes[typeof module] && module && !module.nodeType && module;
+
+ /** Detect the popular CommonJS extension `module.exports` */
+ var moduleExports = freeModule && freeModule.exports === freeExports && freeExports;
+
+ /** Detect free variable `global` from Node.js or Browserified code and use it as `root` */
+ var freeGlobal = objectTypes[typeof global] && global;
+ if (freeGlobal && (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal)) {
+ root = freeGlobal;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * The base implementation of `_.indexOf` without support for binary searches
+ * or `fromIndex` constraints.
+ *
+ * @private
+ * @param {Array} array The array to search.
+ * @param {*} value The value to search for.
+ * @param {number} [fromIndex=0] The index to search from.
+ * @returns {number} Returns the index of the matched value or `-1`.
+ */
+ function baseIndexOf(array, value, fromIndex) {
+ var index = (fromIndex || 0) - 1,
+ length = array ? array.length : 0;
+
+ while (++index < length) {
+ if (array[index] === value) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * An implementation of `_.contains` for cache objects that mimics the return
+ * signature of `_.indexOf` by returning `0` if the value is found, else `-1`.
+ *
+ * @private
+ * @param {Object} cache The cache object to inspect.
+ * @param {*} value The value to search for.
+ * @returns {number} Returns `0` if `value` is found, else `-1`.
+ */
+ function cacheIndexOf(cache, value) {
+ var type = typeof value;
+ cache = cache.cache;
+
+ if (type == 'boolean' || value == null) {
+ return cache[value] ? 0 : -1;
+ }
+ if (type != 'number' && type != 'string') {
+ type = 'object';
+ }
+ var key = type == 'number' ? value : keyPrefix + value;
+ cache = (cache = cache[type]) && cache[key];
+
+ return type == 'object'
+ ? (cache && baseIndexOf(cache, value) > -1 ? 0 : -1)
+ : (cache ? 0 : -1);
+ }
+
+ /**
+ * Adds a given value to the corresponding cache object.
+ *
+ * @private
+ * @param {*} value The value to add to the cache.
+ */
+ function cachePush(value) {
+ var cache = this.cache,
+ type = typeof value;
+
+ if (type == 'boolean' || value == null) {
+ cache[value] = true;
+ } else {
+ if (type != 'number' && type != 'string') {
+ type = 'object';
+ }
+ var key = type == 'number' ? value : keyPrefix + value,
+ typeCache = cache[type] || (cache[type] = {});
+
+ if (type == 'object') {
+ (typeCache[key] || (typeCache[key] = [])).push(value);
+ } else {
+ typeCache[key] = true;
+ }
+ }
+ }
+
+ /**
+ * Used by `_.max` and `_.min` as the default callback when a given
+ * collection is a string value.
+ *
+ * @private
+ * @param {string} value The character to inspect.
+ * @returns {number} Returns the code unit of given character.
+ */
+ function charAtCallback(value) {
+ return value.charCodeAt(0);
+ }
+
+ /**
+ * Used by `sortBy` to compare transformed `collection` elements, stable sorting
+ * them in ascending order.
+ *
+ * @private
+ * @param {Object} a The object to compare to `b`.
+ * @param {Object} b The object to compare to `a`.
+ * @returns {number} Returns the sort order indicator of `1` or `-1`.
+ */
+ function compareAscending(a, b) {
+ var ac = a.criteria,
+ bc = b.criteria,
+ index = -1,
+ length = ac.length;
+
+ while (++index < length) {
+ var value = ac[index],
+ other = bc[index];
+
+ if (value !== other) {
+ if (value > other || typeof value == 'undefined') {
+ return 1;
+ }
+ if (value < other || typeof other == 'undefined') {
+ return -1;
+ }
+ }
+ }
+ // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications
+ // that causes it, under certain circumstances, to return the same value for
+ // `a` and `b`. See https://github.com/jashkenas/underscore/pull/1247
+ //
+ // This also ensures a stable sort in V8 and other engines.
+ // See http://code.google.com/p/v8/issues/detail?id=90
+ return a.index - b.index;
+ }
+
+ /**
+ * Creates a cache object to optimize linear searches of large arrays.
+ *
+ * @private
+ * @param {Array} [array=[]] The array to search.
+ * @returns {null|Object} Returns the cache object or `null` if caching should not be used.
+ */
+ function createCache(array) {
+ var index = -1,
+ length = array.length,
+ first = array[0],
+ mid = array[(length / 2) | 0],
+ last = array[length - 1];
+
+ if (first && typeof first == 'object' &&
+ mid && typeof mid == 'object' && last && typeof last == 'object') {
+ return false;
+ }
+ var cache = getObject();
+ cache['false'] = cache['null'] = cache['true'] = cache['undefined'] = false;
+
+ var result = getObject();
+ result.array = array;
+ result.cache = cache;
+ result.push = cachePush;
+
+ while (++index < length) {
+ result.push(array[index]);
+ }
+ return result;
+ }
+
+ /**
+ * Used by `template` to escape characters for inclusion in compiled
+ * string literals.
+ *
+ * @private
+ * @param {string} match The matched character to escape.
+ * @returns {string} Returns the escaped character.
+ */
+ function escapeStringChar(match) {
+ return '\\' + stringEscapes[match];
+ }
+
+ /**
+ * Gets an array from the array pool or creates a new one if the pool is empty.
+ *
+ * @private
+ * @returns {Array} The array from the pool.
+ */
+ function getArray() {
+ return arrayPool.pop() || [];
+ }
+
+ /**
+ * Gets an object from the object pool or creates a new one if the pool is empty.
+ *
+ * @private
+ * @returns {Object} The object from the pool.
+ */
+ function getObject() {
+ return objectPool.pop() || {
+ 'array': null,
+ 'cache': null,
+ 'criteria': null,
+ 'false': false,
+ 'index': 0,
+ 'null': false,
+ 'number': null,
+ 'object': null,
+ 'push': null,
+ 'string': null,
+ 'true': false,
+ 'undefined': false,
+ 'value': null
+ };
+ }
+
+ /**
+ * Checks if `value` is a DOM node in IE < 9.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is a DOM node, else `false`.
+ */
+ function isNode(value) {
+ // IE < 9 presents DOM nodes as `Object` objects except they have `toString`
+ // methods that are `typeof` "string" and still can coerce nodes to strings
+ return typeof value.toString != 'function' && typeof (value + '') == 'string';
+ }
+
+ /**
+ * Releases the given array back to the array pool.
+ *
+ * @private
+ * @param {Array} [array] The array to release.
+ */
+ function releaseArray(array) {
+ array.length = 0;
+ if (arrayPool.length < maxPoolSize) {
+ arrayPool.push(array);
+ }
+ }
+
+ /**
+ * Releases the given object back to the object pool.
+ *
+ * @private
+ * @param {Object} [object] The object to release.
+ */
+ function releaseObject(object) {
+ var cache = object.cache;
+ if (cache) {
+ releaseObject(cache);
+ }
+ object.array = object.cache = object.criteria = object.object = object.number = object.string = object.value = null;
+ if (objectPool.length < maxPoolSize) {
+ objectPool.push(object);
+ }
+ }
+
+ /**
+ * Slices the `collection` from the `start` index up to, but not including,
+ * the `end` index.
+ *
+ * Note: This function is used instead of `Array#slice` to support node lists
+ * in IE < 9 and to ensure dense arrays are returned.
+ *
+ * @private
+ * @param {Array|Object|string} collection The collection to slice.
+ * @param {number} start The start index.
+ * @param {number} end The end index.
+ * @returns {Array} Returns the new array.
+ */
+ function slice(array, start, end) {
+ start || (start = 0);
+ if (typeof end == 'undefined') {
+ end = array ? array.length : 0;
+ }
+ var index = -1,
+ length = end - start || 0,
+ result = Array(length < 0 ? 0 : length);
+
+ while (++index < length) {
+ result[index] = array[start + index];
+ }
+ return result;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Create a new `lodash` function using the given context object.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {Object} [context=root] The context object.
+ * @returns {Function} Returns the `lodash` function.
+ */
+ function runInContext(context) {
+ // Avoid issues with some ES3 environments that attempt to use values, named
+ // after built-in constructors like `Object`, for the creation of literals.
+ // ES5 clears this up by stating that literals must use built-in constructors.
+ // See http://es5.github.io/#x11.1.5.
+ context = context ? _.defaults(root.Object(), context, _.pick(root, contextProps)) : root;
+
+ /** Native constructor references */
+ var Array = context.Array,
+ Boolean = context.Boolean,
+ Date = context.Date,
+ Error = context.Error,
+ Function = context.Function,
+ Math = context.Math,
+ Number = context.Number,
+ Object = context.Object,
+ RegExp = context.RegExp,
+ String = context.String,
+ TypeError = context.TypeError;
+
+ /**
+ * Used for `Array` method references.
+ *
+ * Normally `Array.prototype` would suffice, however, using an array literal
+ * avoids issues in Narwhal.
+ */
+ var arrayRef = [];
+
+ /** Used for native method references */
+ var errorProto = Error.prototype,
+ objectProto = Object.prototype,
+ stringProto = String.prototype;
+
+ /** Used to restore the original `_` reference in `noConflict` */
+ var oldDash = context._;
+
+ /** Used to resolve the internal [[Class]] of values */
+ var toString = objectProto.toString;
+
+ /** Used to detect if a method is native */
+ var reNative = RegExp('^' +
+ String(toString)
+ .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+ .replace(/toString| for [^\]]+/g, '.*?') + '$'
+ );
+
+ /** Native method shortcuts */
+ var ceil = Math.ceil,
+ clearTimeout = context.clearTimeout,
+ floor = Math.floor,
+ fnToString = Function.prototype.toString,
+ getPrototypeOf = isNative(getPrototypeOf = Object.getPrototypeOf) && getPrototypeOf,
+ hasOwnProperty = objectProto.hasOwnProperty,
+ push = arrayRef.push,
+ propertyIsEnumerable = objectProto.propertyIsEnumerable,
+ setTimeout = context.setTimeout,
+ splice = arrayRef.splice,
+ unshift = arrayRef.unshift;
+
+ /** Used to set meta data on functions */
+ var defineProperty = (function() {
+ // IE 8 only accepts DOM elements
+ try {
+ var o = {},
+ func = isNative(func = Object.defineProperty) && func,
+ result = func(o, o, o) && func;
+ } catch(e) { }
+ return result;
+ }());
+
+ /* Native method shortcuts for methods with the same name as other `lodash` methods */
+ var nativeCreate = isNative(nativeCreate = Object.create) && nativeCreate,
+ nativeIsArray = isNative(nativeIsArray = Array.isArray) && nativeIsArray,
+ nativeIsFinite = context.isFinite,
+ nativeIsNaN = context.isNaN,
+ nativeKeys = isNative(nativeKeys = Object.keys) && nativeKeys,
+ nativeMax = Math.max,
+ nativeMin = Math.min,
+ nativeParseInt = context.parseInt,
+ nativeRandom = Math.random;
+
+ /** Used to lookup a built-in constructor by [[Class]] */
+ var ctorByClass = {};
+ ctorByClass[arrayClass] = Array;
+ ctorByClass[boolClass] = Boolean;
+ ctorByClass[dateClass] = Date;
+ ctorByClass[funcClass] = Function;
+ ctorByClass[objectClass] = Object;
+ ctorByClass[numberClass] = Number;
+ ctorByClass[regexpClass] = RegExp;
+ ctorByClass[stringClass] = String;
+
+ /** Used to avoid iterating non-enumerable properties in IE < 9 */
+ var nonEnumProps = {};
+ nonEnumProps[arrayClass] = nonEnumProps[dateClass] = nonEnumProps[numberClass] = { 'constructor': true, 'toLocaleString': true, 'toString': true, 'valueOf': true };
+ nonEnumProps[boolClass] = nonEnumProps[stringClass] = { 'constructor': true, 'toString': true, 'valueOf': true };
+ nonEnumProps[errorClass] = nonEnumProps[funcClass] = nonEnumProps[regexpClass] = { 'constructor': true, 'toString': true };
+ nonEnumProps[objectClass] = { 'constructor': true };
+
+ (function() {
+ var length = shadowedProps.length;
+ while (length--) {
+ var key = shadowedProps[length];
+ for (var className in nonEnumProps) {
+ if (hasOwnProperty.call(nonEnumProps, className) && !hasOwnProperty.call(nonEnumProps[className], key)) {
+ nonEnumProps[className][key] = false;
+ }
+ }
+ }
+ }());
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates a `lodash` object which wraps the given value to enable intuitive
+ * method chaining.
+ *
+ * In addition to Lo-Dash methods, wrappers also have the following `Array` methods:
+ * `concat`, `join`, `pop`, `push`, `reverse`, `shift`, `slice`, `sort`, `splice`,
+ * and `unshift`
+ *
+ * Chaining is supported in custom builds as long as the `value` method is
+ * implicitly or explicitly included in the build.
+ *
+ * The chainable wrapper functions are:
+ * `after`, `assign`, `bind`, `bindAll`, `bindKey`, `chain`, `compact`,
+ * `compose`, `concat`, `countBy`, `create`, `createCallback`, `curry`,
+ * `debounce`, `defaults`, `defer`, `delay`, `difference`, `filter`, `flatten`,
+ * `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`, `forOwnRight`,
+ * `functions`, `groupBy`, `indexBy`, `initial`, `intersection`, `invert`,
+ * `invoke`, `keys`, `map`, `max`, `memoize`, `merge`, `min`, `object`, `omit`,
+ * `once`, `pairs`, `partial`, `partialRight`, `pick`, `pluck`, `pull`, `push`,
+ * `range`, `reject`, `remove`, `rest`, `reverse`, `shuffle`, `slice`, `sort`,
+ * `sortBy`, `splice`, `tap`, `throttle`, `times`, `toArray`, `transform`,
+ * `union`, `uniq`, `unshift`, `unzip`, `values`, `where`, `without`, `wrap`,
+ * and `zip`
+ *
+ * The non-chainable wrapper functions are:
+ * `clone`, `cloneDeep`, `contains`, `escape`, `every`, `find`, `findIndex`,
+ * `findKey`, `findLast`, `findLastIndex`, `findLastKey`, `has`, `identity`,
+ * `indexOf`, `isArguments`, `isArray`, `isBoolean`, `isDate`, `isElement`,
+ * `isEmpty`, `isEqual`, `isFinite`, `isFunction`, `isNaN`, `isNull`, `isNumber`,
+ * `isObject`, `isPlainObject`, `isRegExp`, `isString`, `isUndefined`, `join`,
+ * `lastIndexOf`, `mixin`, `noConflict`, `parseInt`, `pop`, `random`, `reduce`,
+ * `reduceRight`, `result`, `shift`, `size`, `some`, `sortedIndex`, `runInContext`,
+ * `template`, `unescape`, `uniqueId`, and `value`
+ *
+ * The wrapper functions `first` and `last` return wrapped values when `n` is
+ * provided, otherwise they return unwrapped values.
+ *
+ * Explicit chaining can be enabled by using the `_.chain` method.
+ *
+ * @name _
+ * @constructor
+ * @category Chaining
+ * @param {*} value The value to wrap in a `lodash` instance.
+ * @returns {Object} Returns a `lodash` instance.
+ * @example
+ *
+ * var wrapped = _([1, 2, 3]);
+ *
+ * // returns an unwrapped value
+ * wrapped.reduce(function(sum, num) {
+ * return sum + num;
+ * });
+ * // => 6
+ *
+ * // returns a wrapped value
+ * var squares = wrapped.map(function(num) {
+ * return num * num;
+ * });
+ *
+ * _.isArray(squares);
+ * // => false
+ *
+ * _.isArray(squares.value());
+ * // => true
+ */
+ function lodash(value) {
+ // don't wrap if already wrapped, even if wrapped by a different `lodash` constructor
+ return (value && typeof value == 'object' && !isArray(value) && hasOwnProperty.call(value, '__wrapped__'))
+ ? value
+ : new lodashWrapper(value);
+ }
+
+ /**
+ * A fast path for creating `lodash` wrapper objects.
+ *
+ * @private
+ * @param {*} value The value to wrap in a `lodash` instance.
+ * @param {boolean} chainAll A flag to enable chaining for all methods
+ * @returns {Object} Returns a `lodash` instance.
+ */
+ function lodashWrapper(value, chainAll) {
+ this.__chain__ = !!chainAll;
+ this.__wrapped__ = value;
+ }
+ // ensure `new lodashWrapper` is an instance of `lodash`
+ lodashWrapper.prototype = lodash.prototype;
+
+ /**
+ * An object used to flag environments features.
+ *
+ * @static
+ * @memberOf _
+ * @type Object
+ */
+ var support = lodash.support = {};
+
+ (function() {
+ var ctor = function() { this.x = 1; },
+ object = { '0': 1, 'length': 1 },
+ props = [];
+
+ ctor.prototype = { 'valueOf': 1, 'y': 1 };
+ for (var key in new ctor) { props.push(key); }
+ for (key in arguments) { }
+
+ /**
+ * Detect if an `arguments` object's [[Class]] is resolvable (all but Firefox < 4, IE < 9).
+ *
+ * @memberOf _.support
+ * @type boolean
+ */
+ support.argsClass = toString.call(arguments) == argsClass;
+
+ /**
+ * Detect if `arguments` objects are `Object` objects (all but Narwhal and Opera < 10.5).
+ *
+ * @memberOf _.support
+ * @type boolean
+ */
+ support.argsObject = arguments.constructor == Object && !(arguments instanceof Array);
+
+ /**
+ * Detect if `name` or `message` properties of `Error.prototype` are
+ * enumerable by default. (IE < 9, Safari < 5.1)
+ *
+ * @memberOf _.support
+ * @type boolean
+ */
+ support.enumErrorProps = propertyIsEnumerable.call(errorProto, 'message') || propertyIsEnumerable.call(errorProto, 'name');
+
+ /**
+ * Detect if `prototype` properties are enumerable by default.
+ *
+ * Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1
+ * (if the prototype or a property on the prototype has been set)
+ * incorrectly sets a function's `prototype` property [[Enumerable]]
+ * value to `true`.
+ *
+ * @memberOf _.support
+ * @type boolean
+ */
+ support.enumPrototypes = propertyIsEnumerable.call(ctor, 'prototype');
+
+ /**
+ * Detect if functions can be decompiled by `Function#toString`
+ * (all but PS3 and older Opera mobile browsers & avoided in Windows 8 apps).
+ *
+ * @memberOf _.support
+ * @type boolean
+ */
+ support.funcDecomp = !isNative(context.WinRTError) && reThis.test(runInContext);
+
+ /**
+ * Detect if `Function#name` is supported (all but IE).
+ *
+ * @memberOf _.support
+ * @type boolean
+ */
+ support.funcNames = typeof Function.name == 'string';
+
+ /**
+ * Detect if `arguments` object indexes are non-enumerable
+ * (Firefox < 4, IE < 9, PhantomJS, Safari < 5.1).
+ *
+ * @memberOf _.support
+ * @type boolean
+ */
+ support.nonEnumArgs = key != 0;
+
+ /**
+ * Detect if properties shadowing those on `Object.prototype` are non-enumerable.
+ *
+ * In IE < 9 an objects own properties, shadowing non-enumerable ones, are
+ * made non-enumerable as well (a.k.a the JScript [[DontEnum]] bug).
+ *
+ * @memberOf _.support
+ * @type boolean
+ */
+ support.nonEnumShadows = !/valueOf/.test(props);
+
+ /**
+ * Detect if own properties are iterated after inherited properties (all but IE < 9).
+ *
+ * @memberOf _.support
+ * @type boolean
+ */
+ support.ownLast = props[0] != 'x';
+
+ /**
+ * Detect if `Array#shift` and `Array#splice` augment array-like objects correctly.
+ *
+ * Firefox < 10, IE compatibility mode, and IE < 9 have buggy Array `shift()`
+ * and `splice()` functions that fail to remove the last element, `value[0]`,
+ * of array-like objects even though the `length` property is set to `0`.
+ * The `shift()` method is buggy in IE 8 compatibility mode, while `splice()`
+ * is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9.
+ *
+ * @memberOf _.support
+ * @type boolean
+ */
+ support.spliceObjects = (arrayRef.splice.call(object, 0, 1), !object[0]);
+
+ /**
+ * Detect lack of support for accessing string characters by index.
+ *
+ * IE < 8 can't access characters by index and IE 8 can only access
+ * characters by index on string literals.
+ *
+ * @memberOf _.support
+ * @type boolean
+ */
+ support.unindexedChars = ('x'[0] + Object('x')[0]) != 'xx';
+
+ /**
+ * Detect if a DOM node's [[Class]] is resolvable (all but IE < 9)
+ * and that the JS engine errors when attempting to coerce an object to
+ * a string without a `toString` function.
+ *
+ * @memberOf _.support
+ * @type boolean
+ */
+ try {
+ support.nodeClass = !(toString.call(document) == objectClass && !({ 'toString': 0 } + ''));
+ } catch(e) {
+ support.nodeClass = true;
+ }
+ }(1));
+
+ /**
+ * By default, the template delimiters used by Lo-Dash are similar to those in
+ * embedded Ruby (ERB). Change the following template settings to use alternative
+ * delimiters.
+ *
+ * @static
+ * @memberOf _
+ * @type Object
+ */
+ lodash.templateSettings = {
+
+ /**
+ * Used to detect `data` property values to be HTML-escaped.
+ *
+ * @memberOf _.templateSettings
+ * @type RegExp
+ */
+ 'escape': /<%-([\s\S]+?)%>/g,
+
+ /**
+ * Used to detect code to be evaluated.
+ *
+ * @memberOf _.templateSettings
+ * @type RegExp
+ */
+ 'evaluate': /<%([\s\S]+?)%>/g,
+
+ /**
+ * Used to detect `data` property values to inject.
+ *
+ * @memberOf _.templateSettings
+ * @type RegExp
+ */
+ 'interpolate': reInterpolate,
+
+ /**
+ * Used to reference the data object in the template text.
+ *
+ * @memberOf _.templateSettings
+ * @type string
+ */
+ 'variable': '',
+
+ /**
+ * Used to import variables into the compiled template.
+ *
+ * @memberOf _.templateSettings
+ * @type Object
+ */
+ 'imports': {
+
+ /**
+ * A reference to the `lodash` function.
+ *
+ * @memberOf _.templateSettings.imports
+ * @type Function
+ */
+ '_': lodash
+ }
+ };
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * The template used to create iterator functions.
+ *
+ * @private
+ * @param {Object} data The data object used to populate the text.
+ * @returns {string} Returns the interpolated text.
+ */
+ var iteratorTemplate = function(obj) {
+
+ var __p = 'var index, iterable = ' +
+ (obj.firstArg) +
+ ', result = ' +
+ (obj.init) +
+ ';\nif (!iterable) return result;\n' +
+ (obj.top) +
+ ';';
+ if (obj.array) {
+ __p += '\nvar length = iterable.length; index = -1;\nif (' +
+ (obj.array) +
+ ') { ';
+ if (support.unindexedChars) {
+ __p += '\n if (isString(iterable)) {\n iterable = iterable.split(\'\')\n } ';
+ }
+ __p += '\n while (++index < length) {\n ' +
+ (obj.loop) +
+ ';\n }\n}\nelse { ';
+ } else if (support.nonEnumArgs) {
+ __p += '\n var length = iterable.length; index = -1;\n if (length && isArguments(iterable)) {\n while (++index < length) {\n index += \'\';\n ' +
+ (obj.loop) +
+ ';\n }\n } else { ';
+ }
+
+ if (support.enumPrototypes) {
+ __p += '\n var skipProto = typeof iterable == \'function\';\n ';
+ }
+
+ if (support.enumErrorProps) {
+ __p += '\n var skipErrorProps = iterable === errorProto || iterable instanceof Error;\n ';
+ }
+
+ var conditions = []; if (support.enumPrototypes) { conditions.push('!(skipProto && index == "prototype")'); } if (support.enumErrorProps) { conditions.push('!(skipErrorProps && (index == "message" || index == "name"))'); }
+
+ if (obj.useHas && obj.keys) {
+ __p += '\n var ownIndex = -1,\n ownProps = objectTypes[typeof iterable] && keys(iterable),\n length = ownProps ? ownProps.length : 0;\n\n while (++ownIndex < length) {\n index = ownProps[ownIndex];\n';
+ if (conditions.length) {
+ __p += ' if (' +
+ (conditions.join(' && ')) +
+ ') {\n ';
+ }
+ __p +=
+ (obj.loop) +
+ '; ';
+ if (conditions.length) {
+ __p += '\n }';
+ }
+ __p += '\n } ';
+ } else {
+ __p += '\n for (index in iterable) {\n';
+ if (obj.useHas) { conditions.push("hasOwnProperty.call(iterable, index)"); } if (conditions.length) {
+ __p += ' if (' +
+ (conditions.join(' && ')) +
+ ') {\n ';
+ }
+ __p +=
+ (obj.loop) +
+ '; ';
+ if (conditions.length) {
+ __p += '\n }';
+ }
+ __p += '\n } ';
+ if (support.nonEnumShadows) {
+ __p += '\n\n if (iterable !== objectProto) {\n var ctor = iterable.constructor,\n isProto = iterable === (ctor && ctor.prototype),\n className = iterable === stringProto ? stringClass : iterable === errorProto ? errorClass : toString.call(iterable),\n nonEnum = nonEnumProps[className];\n ';
+ for (k = 0; k < 7; k++) {
+ __p += '\n index = \'' +
+ (obj.shadowedProps[k]) +
+ '\';\n if ((!(isProto && nonEnum[index]) && hasOwnProperty.call(iterable, index))';
+ if (!obj.useHas) {
+ __p += ' || (!nonEnum[index] && iterable[index] !== objectProto[index])';
+ }
+ __p += ') {\n ' +
+ (obj.loop) +
+ ';\n } ';
+ }
+ __p += '\n } ';
+ }
+
+ }
+
+ if (obj.array || support.nonEnumArgs) {
+ __p += '\n}';
+ }
+ __p +=
+ (obj.bottom) +
+ ';\nreturn result';
+
+ return __p
+ };
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * The base implementation of `_.bind` that creates the bound function and
+ * sets its meta data.
+ *
+ * @private
+ * @param {Array} bindData The bind data array.
+ * @returns {Function} Returns the new bound function.
+ */
+ function baseBind(bindData) {
+ var func = bindData[0],
+ partialArgs = bindData[2],
+ thisArg = bindData[4];
+
+ function bound() {
+ // `Function#bind` spec
+ // http://es5.github.io/#x15.3.4.5
+ if (partialArgs) {
+ // avoid `arguments` object deoptimizations by using `slice` instead
+ // of `Array.prototype.slice.call` and not assigning `arguments` to a
+ // variable as a ternary expression
+ var args = slice(partialArgs);
+ push.apply(args, arguments);
+ }
+ // mimic the constructor's `return` behavior
+ // http://es5.github.io/#x13.2.2
+ if (this instanceof bound) {
+ // ensure `new bound` is an instance of `func`
+ var thisBinding = baseCreate(func.prototype),
+ result = func.apply(thisBinding, args || arguments);
+ return isObject(result) ? result : thisBinding;
+ }
+ return func.apply(thisArg, args || arguments);
+ }
+ setBindData(bound, bindData);
+ return bound;
+ }
+
+ /**
+ * The base implementation of `_.clone` without argument juggling or support
+ * for `thisArg` binding.
+ *
+ * @private
+ * @param {*} value The value to clone.
+ * @param {boolean} [isDeep=false] Specify a deep clone.
+ * @param {Function} [callback] The function to customize cloning values.
+ * @param {Array} [stackA=[]] Tracks traversed source objects.
+ * @param {Array} [stackB=[]] Associates clones with source counterparts.
+ * @returns {*} Returns the cloned value.
+ */
+ function baseClone(value, isDeep, callback, stackA, stackB) {
+ if (callback) {
+ var result = callback(value);
+ if (typeof result != 'undefined') {
+ return result;
+ }
+ }
+ // inspect [[Class]]
+ var isObj = isObject(value);
+ if (isObj) {
+ var className = toString.call(value);
+ if (!cloneableClasses[className] || (!support.nodeClass && isNode(value))) {
+ return value;
+ }
+ var ctor = ctorByClass[className];
+ switch (className) {
+ case boolClass:
+ case dateClass:
+ return new ctor(+value);
+
+ case numberClass:
+ case stringClass:
+ return new ctor(value);
+
+ case regexpClass:
+ result = ctor(value.source, reFlags.exec(value));
+ result.lastIndex = value.lastIndex;
+ return result;
+ }
+ } else {
+ return value;
+ }
+ var isArr = isArray(value);
+ if (isDeep) {
+ // check for circular references and return corresponding clone
+ var initedStack = !stackA;
+ stackA || (stackA = getArray());
+ stackB || (stackB = getArray());
+
+ var length = stackA.length;
+ while (length--) {
+ if (stackA[length] == value) {
+ return stackB[length];
+ }
+ }
+ result = isArr ? ctor(value.length) : {};
+ }
+ else {
+ result = isArr ? slice(value) : assign({}, value);
+ }
+ // add array properties assigned by `RegExp#exec`
+ if (isArr) {
+ if (hasOwnProperty.call(value, 'index')) {
+ result.index = value.index;
+ }
+ if (hasOwnProperty.call(value, 'input')) {
+ result.input = value.input;
+ }
+ }
+ // exit for shallow clone
+ if (!isDeep) {
+ return result;
+ }
+ // add the source value to the stack of traversed objects
+ // and associate it with its clone
+ stackA.push(value);
+ stackB.push(result);
+
+ // recursively populate clone (susceptible to call stack limits)
+ (isArr ? baseEach : forOwn)(value, function(objValue, key) {
+ result[key] = baseClone(objValue, isDeep, callback, stackA, stackB);
+ });
+
+ if (initedStack) {
+ releaseArray(stackA);
+ releaseArray(stackB);
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.create` without support for assigning
+ * properties to the created object.
+ *
+ * @private
+ * @param {Object} prototype The object to inherit from.
+ * @returns {Object} Returns the new object.
+ */
+ function baseCreate(prototype, properties) {
+ return isObject(prototype) ? nativeCreate(prototype) : {};
+ }
+ // fallback for browsers without `Object.create`
+ if (!nativeCreate) {
+ baseCreate = (function() {
+ function Object() {}
+ return function(prototype) {
+ if (isObject(prototype)) {
+ Object.prototype = prototype;
+ var result = new Object;
+ Object.prototype = null;
+ }
+ return result || context.Object();
+ };
+ }());
+ }
+
+ /**
+ * The base implementation of `_.createCallback` without support for creating
+ * "_.pluck" or "_.where" style callbacks.
+ *
+ * @private
+ * @param {*} [func=identity] The value to convert to a callback.
+ * @param {*} [thisArg] The `this` binding of the created callback.
+ * @param {number} [argCount] The number of arguments the callback accepts.
+ * @returns {Function} Returns a callback function.
+ */
+ function baseCreateCallback(func, thisArg, argCount) {
+ if (typeof func != 'function') {
+ return identity;
+ }
+ // exit early for no `thisArg` or already bound by `Function#bind`
+ if (typeof thisArg == 'undefined' || !('prototype' in func)) {
+ return func;
+ }
+ var bindData = func.__bindData__;
+ if (typeof bindData == 'undefined') {
+ if (support.funcNames) {
+ bindData = !func.name;
+ }
+ bindData = bindData || !support.funcDecomp;
+ if (!bindData) {
+ var source = fnToString.call(func);
+ if (!support.funcNames) {
+ bindData = !reFuncName.test(source);
+ }
+ if (!bindData) {
+ // checks if `func` references the `this` keyword and stores the result
+ bindData = reThis.test(source);
+ setBindData(func, bindData);
+ }
+ }
+ }
+ // exit early if there are no `this` references or `func` is bound
+ if (bindData === false || (bindData !== true && bindData[1] & 1)) {
+ return func;
+ }
+ switch (argCount) {
+ case 1: return function(value) {
+ return func.call(thisArg, value);
+ };
+ case 2: return function(a, b) {
+ return func.call(thisArg, a, b);
+ };
+ case 3: return function(value, index, collection) {
+ return func.call(thisArg, value, index, collection);
+ };
+ case 4: return function(accumulator, value, index, collection) {
+ return func.call(thisArg, accumulator, value, index, collection);
+ };
+ }
+ return bind(func, thisArg);
+ }
+
+ /**
+ * The base implementation of `createWrapper` that creates the wrapper and
+ * sets its meta data.
+ *
+ * @private
+ * @param {Array} bindData The bind data array.
+ * @returns {Function} Returns the new function.
+ */
+ function baseCreateWrapper(bindData) {
+ var func = bindData[0],
+ bitmask = bindData[1],
+ partialArgs = bindData[2],
+ partialRightArgs = bindData[3],
+ thisArg = bindData[4],
+ arity = bindData[5];
+
+ var isBind = bitmask & 1,
+ isBindKey = bitmask & 2,
+ isCurry = bitmask & 4,
+ isCurryBound = bitmask & 8,
+ key = func;
+
+ function bound() {
+ var thisBinding = isBind ? thisArg : this;
+ if (partialArgs) {
+ var args = slice(partialArgs);
+ push.apply(args, arguments);
+ }
+ if (partialRightArgs || isCurry) {
+ args || (args = slice(arguments));
+ if (partialRightArgs) {
+ push.apply(args, partialRightArgs);
+ }
+ if (isCurry && args.length < arity) {
+ bitmask |= 16 & ~32;
+ return baseCreateWrapper([func, (isCurryBound ? bitmask : bitmask & ~3), args, null, thisArg, arity]);
+ }
+ }
+ args || (args = arguments);
+ if (isBindKey) {
+ func = thisBinding[key];
+ }
+ if (this instanceof bound) {
+ thisBinding = baseCreate(func.prototype);
+ var result = func.apply(thisBinding, args);
+ return isObject(result) ? result : thisBinding;
+ }
+ return func.apply(thisBinding, args);
+ }
+ setBindData(bound, bindData);
+ return bound;
+ }
+
+ /**
+ * The base implementation of `_.difference` that accepts a single array
+ * of values to exclude.
+ *
+ * @private
+ * @param {Array} array The array to process.
+ * @param {Array} [values] The array of values to exclude.
+ * @returns {Array} Returns a new array of filtered values.
+ */
+ function baseDifference(array, values) {
+ var index = -1,
+ indexOf = getIndexOf(),
+ length = array ? array.length : 0,
+ isLarge = length >= largeArraySize && indexOf === baseIndexOf,
+ result = [];
+
+ if (isLarge) {
+ var cache = createCache(values);
+ if (cache) {
+ indexOf = cacheIndexOf;
+ values = cache;
+ } else {
+ isLarge = false;
+ }
+ }
+ while (++index < length) {
+ var value = array[index];
+ if (indexOf(values, value) < 0) {
+ result.push(value);
+ }
+ }
+ if (isLarge) {
+ releaseObject(values);
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.flatten` without support for callback
+ * shorthands or `thisArg` binding.
+ *
+ * @private
+ * @param {Array} array The array to flatten.
+ * @param {boolean} [isShallow=false] A flag to restrict flattening to a single level.
+ * @param {boolean} [isStrict=false] A flag to restrict flattening to arrays and `arguments` objects.
+ * @param {number} [fromIndex=0] The index to start from.
+ * @returns {Array} Returns a new flattened array.
+ */
+ function baseFlatten(array, isShallow, isStrict, fromIndex) {
+ var index = (fromIndex || 0) - 1,
+ length = array ? array.length : 0,
+ result = [];
+
+ while (++index < length) {
+ var value = array[index];
+
+ if (value && typeof value == 'object' && typeof value.length == 'number'
+ && (isArray(value) || isArguments(value))) {
+ // recursively flatten arrays (susceptible to call stack limits)
+ if (!isShallow) {
+ value = baseFlatten(value, isShallow, isStrict);
+ }
+ var valIndex = -1,
+ valLength = value.length,
+ resIndex = result.length;
+
+ result.length += valLength;
+ while (++valIndex < valLength) {
+ result[resIndex++] = value[valIndex];
+ }
+ } else if (!isStrict) {
+ result.push(value);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.isEqual`, without support for `thisArg` binding,
+ * that allows partial "_.where" style comparisons.
+ *
+ * @private
+ * @param {*} a The value to compare.
+ * @param {*} b The other value to compare.
+ * @param {Function} [callback] The function to customize comparing values.
+ * @param {Function} [isWhere=false] A flag to indicate performing partial comparisons.
+ * @param {Array} [stackA=[]] Tracks traversed `a` objects.
+ * @param {Array} [stackB=[]] Tracks traversed `b` objects.
+ * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+ */
+ function baseIsEqual(a, b, callback, isWhere, stackA, stackB) {
+ // used to indicate that when comparing objects, `a` has at least the properties of `b`
+ if (callback) {
+ var result = callback(a, b);
+ if (typeof result != 'undefined') {
+ return !!result;
+ }
+ }
+ // exit early for identical values
+ if (a === b) {
+ // treat `+0` vs. `-0` as not equal
+ return a !== 0 || (1 / a == 1 / b);
+ }
+ var type = typeof a,
+ otherType = typeof b;
+
+ // exit early for unlike primitive values
+ if (a === a &&
+ !(a && objectTypes[type]) &&
+ !(b && objectTypes[otherType])) {
+ return false;
+ }
+ // exit early for `null` and `undefined` avoiding ES3's Function#call behavior
+ // http://es5.github.io/#x15.3.4.4
+ if (a == null || b == null) {
+ return a === b;
+ }
+ // compare [[Class]] names
+ var className = toString.call(a),
+ otherClass = toString.call(b);
+
+ if (className == argsClass) {
+ className = objectClass;
+ }
+ if (otherClass == argsClass) {
+ otherClass = objectClass;
+ }
+ if (className != otherClass) {
+ return false;
+ }
+ switch (className) {
+ case boolClass:
+ case dateClass:
+ // coerce dates and booleans to numbers, dates to milliseconds and booleans
+ // to `1` or `0` treating invalid dates coerced to `NaN` as not equal
+ return +a == +b;
+
+ case numberClass:
+ // treat `NaN` vs. `NaN` as equal
+ return (a != +a)
+ ? b != +b
+ // but treat `+0` vs. `-0` as not equal
+ : (a == 0 ? (1 / a == 1 / b) : a == +b);
+
+ case regexpClass:
+ case stringClass:
+ // coerce regexes to strings (http://es5.github.io/#x15.10.6.4)
+ // treat string primitives and their corresponding object instances as equal
+ return a == String(b);
+ }
+ var isArr = className == arrayClass;
+ if (!isArr) {
+ // unwrap any `lodash` wrapped values
+ var aWrapped = hasOwnProperty.call(a, '__wrapped__'),
+ bWrapped = hasOwnProperty.call(b, '__wrapped__');
+
+ if (aWrapped || bWrapped) {
+ return baseIsEqual(aWrapped ? a.__wrapped__ : a, bWrapped ? b.__wrapped__ : b, callback, isWhere, stackA, stackB);
+ }
+ // exit for functions and DOM nodes
+ if (className != objectClass || (!support.nodeClass && (isNode(a) || isNode(b)))) {
+ return false;
+ }
+ // in older versions of Opera, `arguments` objects have `Array` constructors
+ var ctorA = !support.argsObject && isArguments(a) ? Object : a.constructor,
+ ctorB = !support.argsObject && isArguments(b) ? Object : b.constructor;
+
+ // non `Object` object instances with different constructors are not equal
+ if (ctorA != ctorB &&
+ !(isFunction(ctorA) && ctorA instanceof ctorA && isFunction(ctorB) && ctorB instanceof ctorB) &&
+ ('constructor' in a && 'constructor' in b)
+ ) {
+ return false;
+ }
+ }
+ // assume cyclic structures are equal
+ // the algorithm for detecting cyclic structures is adapted from ES 5.1
+ // section 15.12.3, abstract operation `JO` (http://es5.github.io/#x15.12.3)
+ var initedStack = !stackA;
+ stackA || (stackA = getArray());
+ stackB || (stackB = getArray());
+
+ var length = stackA.length;
+ while (length--) {
+ if (stackA[length] == a) {
+ return stackB[length] == b;
+ }
+ }
+ var size = 0;
+ result = true;
+
+ // add `a` and `b` to the stack of traversed objects
+ stackA.push(a);
+ stackB.push(b);
+
+ // recursively compare objects and arrays (susceptible to call stack limits)
+ if (isArr) {
+ // compare lengths to determine if a deep comparison is necessary
+ length = a.length;
+ size = b.length;
+ result = size == length;
+
+ if (result || isWhere) {
+ // deep compare the contents, ignoring non-numeric properties
+ while (size--) {
+ var index = length,
+ value = b[size];
+
+ if (isWhere) {
+ while (index--) {
+ if ((result = baseIsEqual(a[index], value, callback, isWhere, stackA, stackB))) {
+ break;
+ }
+ }
+ } else if (!(result = baseIsEqual(a[size], value, callback, isWhere, stackA, stackB))) {
+ break;
+ }
+ }
+ }
+ }
+ else {
+ // deep compare objects using `forIn`, instead of `forOwn`, to avoid `Object.keys`
+ // which, in this case, is more costly
+ forIn(b, function(value, key, b) {
+ if (hasOwnProperty.call(b, key)) {
+ // count the number of properties.
+ size++;
+ // deep compare each property value.
+ return (result = hasOwnProperty.call(a, key) && baseIsEqual(a[key], value, callback, isWhere, stackA, stackB));
+ }
+ });
+
+ if (result && !isWhere) {
+ // ensure both objects have the same number of properties
+ forIn(a, function(value, key, a) {
+ if (hasOwnProperty.call(a, key)) {
+ // `size` will be `-1` if `a` has more properties than `b`
+ return (result = --size > -1);
+ }
+ });
+ }
+ }
+ stackA.pop();
+ stackB.pop();
+
+ if (initedStack) {
+ releaseArray(stackA);
+ releaseArray(stackB);
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.merge` without argument juggling or support
+ * for `thisArg` binding.
+ *
+ * @private
+ * @param {Object} object The destination object.
+ * @param {Object} source The source object.
+ * @param {Function} [callback] The function to customize merging properties.
+ * @param {Array} [stackA=[]] Tracks traversed source objects.
+ * @param {Array} [stackB=[]] Associates values with source counterparts.
+ */
+ function baseMerge(object, source, callback, stackA, stackB) {
+ (isArray(source) ? forEach : forOwn)(source, function(source, key) {
+ var found,
+ isArr,
+ result = source,
+ value = object[key];
+
+ if (source && ((isArr = isArray(source)) || isPlainObject(source))) {
+ // avoid merging previously merged cyclic sources
+ var stackLength = stackA.length;
+ while (stackLength--) {
+ if ((found = stackA[stackLength] == source)) {
+ value = stackB[stackLength];
+ break;
+ }
+ }
+ if (!found) {
+ var isShallow;
+ if (callback) {
+ result = callback(value, source);
+ if ((isShallow = typeof result != 'undefined')) {
+ value = result;
+ }
+ }
+ if (!isShallow) {
+ value = isArr
+ ? (isArray(value) ? value : [])
+ : (isPlainObject(value) ? value : {});
+ }
+ // add `source` and associated `value` to the stack of traversed objects
+ stackA.push(source);
+ stackB.push(value);
+
+ // recursively merge objects and arrays (susceptible to call stack limits)
+ if (!isShallow) {
+ baseMerge(value, source, callback, stackA, stackB);
+ }
+ }
+ }
+ else {
+ if (callback) {
+ result = callback(value, source);
+ if (typeof result == 'undefined') {
+ result = source;
+ }
+ }
+ if (typeof result != 'undefined') {
+ value = result;
+ }
+ }
+ object[key] = value;
+ });
+ }
+
+ /**
+ * The base implementation of `_.random` without argument juggling or support
+ * for returning floating-point numbers.
+ *
+ * @private
+ * @param {number} min The minimum possible value.
+ * @param {number} max The maximum possible value.
+ * @returns {number} Returns a random number.
+ */
+ function baseRandom(min, max) {
+ return min + floor(nativeRandom() * (max - min + 1));
+ }
+
+ /**
+ * The base implementation of `_.uniq` without support for callback shorthands
+ * or `thisArg` binding.
+ *
+ * @private
+ * @param {Array} array The array to process.
+ * @param {boolean} [isSorted=false] A flag to indicate that `array` is sorted.
+ * @param {Function} [callback] The function called per iteration.
+ * @returns {Array} Returns a duplicate-value-free array.
+ */
+ function baseUniq(array, isSorted, callback) {
+ var index = -1,
+ indexOf = getIndexOf(),
+ length = array ? array.length : 0,
+ result = [];
+
+ var isLarge = !isSorted && length >= largeArraySize && indexOf === baseIndexOf,
+ seen = (callback || isLarge) ? getArray() : result;
+
+ if (isLarge) {
+ var cache = createCache(seen);
+ indexOf = cacheIndexOf;
+ seen = cache;
+ }
+ while (++index < length) {
+ var value = array[index],
+ computed = callback ? callback(value, index, array) : value;
+
+ if (isSorted
+ ? !index || seen[seen.length - 1] !== computed
+ : indexOf(seen, computed) < 0
+ ) {
+ if (callback || isLarge) {
+ seen.push(computed);
+ }
+ result.push(value);
+ }
+ }
+ if (isLarge) {
+ releaseArray(seen.array);
+ releaseObject(seen);
+ } else if (callback) {
+ releaseArray(seen);
+ }
+ return result;
+ }
+
+ /**
+ * Creates a function that aggregates a collection, creating an object composed
+ * of keys generated from the results of running each element of the collection
+ * through a callback. The given `setter` function sets the keys and values
+ * of the composed object.
+ *
+ * @private
+ * @param {Function} setter The setter function.
+ * @returns {Function} Returns the new aggregator function.
+ */
+ function createAggregator(setter) {
+ return function(collection, callback, thisArg) {
+ var result = {};
+ callback = lodash.createCallback(callback, thisArg, 3);
+
+ if (isArray(collection)) {
+ var index = -1,
+ length = collection.length;
+
+ while (++index < length) {
+ var value = collection[index];
+ setter(result, value, callback(value, index, collection), collection);
+ }
+ } else {
+ baseEach(collection, function(value, key, collection) {
+ setter(result, value, callback(value, key, collection), collection);
+ });
+ }
+ return result;
+ };
+ }
+
+ /**
+ * Creates a function that, when called, either curries or invokes `func`
+ * with an optional `this` binding and partially applied arguments.
+ *
+ * @private
+ * @param {Function|string} func The function or method name to reference.
+ * @param {number} bitmask The bitmask of method flags to compose.
+ * The bitmask may be composed of the following flags:
+ * 1 - `_.bind`
+ * 2 - `_.bindKey`
+ * 4 - `_.curry`
+ * 8 - `_.curry` (bound)
+ * 16 - `_.partial`
+ * 32 - `_.partialRight`
+ * @param {Array} [partialArgs] An array of arguments to prepend to those
+ * provided to the new function.
+ * @param {Array} [partialRightArgs] An array of arguments to append to those
+ * provided to the new function.
+ * @param {*} [thisArg] The `this` binding of `func`.
+ * @param {number} [arity] The arity of `func`.
+ * @returns {Function} Returns the new function.
+ */
+ function createWrapper(func, bitmask, partialArgs, partialRightArgs, thisArg, arity) {
+ var isBind = bitmask & 1,
+ isBindKey = bitmask & 2,
+ isCurry = bitmask & 4,
+ isCurryBound = bitmask & 8,
+ isPartial = bitmask & 16,
+ isPartialRight = bitmask & 32;
+
+ if (!isBindKey && !isFunction(func)) {
+ throw new TypeError;
+ }
+ if (isPartial && !partialArgs.length) {
+ bitmask &= ~16;
+ isPartial = partialArgs = false;
+ }
+ if (isPartialRight && !partialRightArgs.length) {
+ bitmask &= ~32;
+ isPartialRight = partialRightArgs = false;
+ }
+ var bindData = func && func.__bindData__;
+ if (bindData && bindData !== true) {
+ // clone `bindData`
+ bindData = slice(bindData);
+ if (bindData[2]) {
+ bindData[2] = slice(bindData[2]);
+ }
+ if (bindData[3]) {
+ bindData[3] = slice(bindData[3]);
+ }
+ // set `thisBinding` is not previously bound
+ if (isBind && !(bindData[1] & 1)) {
+ bindData[4] = thisArg;
+ }
+ // set if previously bound but not currently (subsequent curried functions)
+ if (!isBind && bindData[1] & 1) {
+ bitmask |= 8;
+ }
+ // set curried arity if not yet set
+ if (isCurry && !(bindData[1] & 4)) {
+ bindData[5] = arity;
+ }
+ // append partial left arguments
+ if (isPartial) {
+ push.apply(bindData[2] || (bindData[2] = []), partialArgs);
+ }
+ // append partial right arguments
+ if (isPartialRight) {
+ unshift.apply(bindData[3] || (bindData[3] = []), partialRightArgs);
+ }
+ // merge flags
+ bindData[1] |= bitmask;
+ return createWrapper.apply(null, bindData);
+ }
+ // fast path for `_.bind`
+ var creater = (bitmask == 1 || bitmask === 17) ? baseBind : baseCreateWrapper;
+ return creater([func, bitmask, partialArgs, partialRightArgs, thisArg, arity]);
+ }
+
+ /**
+ * Creates compiled iteration functions.
+ *
+ * @private
+ * @param {...Object} [options] The compile options object(s).
+ * @param {string} [options.array] Code to determine if the iterable is an array or array-like.
+ * @param {boolean} [options.useHas] Specify using `hasOwnProperty` checks in the object loop.
+ * @param {Function} [options.keys] A reference to `_.keys` for use in own property iteration.
+ * @param {string} [options.args] A comma separated string of iteration function arguments.
+ * @param {string} [options.top] Code to execute before the iteration branches.
+ * @param {string} [options.loop] Code to execute in the object loop.
+ * @param {string} [options.bottom] Code to execute after the iteration branches.
+ * @returns {Function} Returns the compiled function.
+ */
+ function createIterator() {
+ // data properties
+ iteratorData.shadowedProps = shadowedProps;
+
+ // iterator options
+ iteratorData.array = iteratorData.bottom = iteratorData.loop = iteratorData.top = '';
+ iteratorData.init = 'iterable';
+ iteratorData.useHas = true;
+
+ // merge options into a template data object
+ for (var object, index = 0; object = arguments[index]; index++) {
+ for (var key in object) {
+ iteratorData[key] = object[key];
+ }
+ }
+ var args = iteratorData.args;
+ iteratorData.firstArg = /^[^,]+/.exec(args)[0];
+
+ // create the function factory
+ var factory = Function(
+ 'baseCreateCallback, errorClass, errorProto, hasOwnProperty, ' +
+ 'indicatorObject, isArguments, isArray, isString, keys, objectProto, ' +
+ 'objectTypes, nonEnumProps, stringClass, stringProto, toString',
+ 'return function(' + args + ') {\n' + iteratorTemplate(iteratorData) + '\n}'
+ );
+
+ // return the compiled function
+ return factory(
+ baseCreateCallback, errorClass, errorProto, hasOwnProperty,
+ indicatorObject, isArguments, isArray, isString, iteratorData.keys, objectProto,
+ objectTypes, nonEnumProps, stringClass, stringProto, toString
+ );
+ }
+
+ /**
+ * Used by `escape` to convert characters to HTML entities.
+ *
+ * @private
+ * @param {string} match The matched character to escape.
+ * @returns {string} Returns the escaped character.
+ */
+ function escapeHtmlChar(match) {
+ return htmlEscapes[match];
+ }
+
+ /**
+ * Gets the appropriate "indexOf" function. If the `_.indexOf` method is
+ * customized, this method returns the custom method, otherwise it returns
+ * the `baseIndexOf` function.
+ *
+ * @private
+ * @returns {Function} Returns the "indexOf" function.
+ */
+ function getIndexOf() {
+ var result = (result = lodash.indexOf) === indexOf ? baseIndexOf : result;
+ return result;
+ }
+
+ /**
+ * Checks if `value` is a native function.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is a native function, else `false`.
+ */
+ function isNative(value) {
+ return typeof value == 'function' && reNative.test(value);
+ }
+
+ /**
+ * Sets `this` binding data on a given function.
+ *
+ * @private
+ * @param {Function} func The function to set data on.
+ * @param {Array} value The data array to set.
+ */
+ var setBindData = !defineProperty ? noop : function(func, value) {
+ descriptor.value = value;
+ defineProperty(func, '__bindData__', descriptor);
+ descriptor.value = null;
+ };
+
+ /**
+ * A fallback implementation of `isPlainObject` which checks if a given value
+ * is an object created by the `Object` constructor, assuming objects created
+ * by the `Object` constructor have no inherited enumerable properties and that
+ * there are no `Object.prototype` extensions.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
+ */
+ function shimIsPlainObject(value) {
+ var ctor,
+ result;
+
+ // avoid non Object objects, `arguments` objects, and DOM elements
+ if (!(value && toString.call(value) == objectClass) ||
+ (ctor = value.constructor, isFunction(ctor) && !(ctor instanceof ctor)) ||
+ (!support.argsClass && isArguments(value)) ||
+ (!support.nodeClass && isNode(value))) {
+ return false;
+ }
+ // IE < 9 iterates inherited properties before own properties. If the first
+ // iterated property is an object's own property then there are no inherited
+ // enumerable properties.
+ if (support.ownLast) {
+ forIn(value, function(value, key, object) {
+ result = hasOwnProperty.call(object, key);
+ return false;
+ });
+ return result !== false;
+ }
+ // In most environments an object's own properties are iterated before
+ // its inherited properties. If the last iterated property is an object's
+ // own property then there are no inherited enumerable properties.
+ forIn(value, function(value, key) {
+ result = key;
+ });
+ return typeof result == 'undefined' || hasOwnProperty.call(value, result);
+ }
+
+ /**
+ * Used by `unescape` to convert HTML entities to characters.
+ *
+ * @private
+ * @param {string} match The matched character to unescape.
+ * @returns {string} Returns the unescaped character.
+ */
+ function unescapeHtmlChar(match) {
+ return htmlUnescapes[match];
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Checks if `value` is an `arguments` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is an `arguments` object, else `false`.
+ * @example
+ *
+ * (function() { return _.isArguments(arguments); })(1, 2, 3);
+ * // => true
+ *
+ * _.isArguments([1, 2, 3]);
+ * // => false
+ */
+ function isArguments(value) {
+ return value && typeof value == 'object' && typeof value.length == 'number' &&
+ toString.call(value) == argsClass || false;
+ }
+ // fallback for browsers that can't detect `arguments` objects by [[Class]]
+ if (!support.argsClass) {
+ isArguments = function(value) {
+ return value && typeof value == 'object' && typeof value.length == 'number' &&
+ hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee') || false;
+ };
+ }
+
+ /**
+ * Checks if `value` is an array.
+ *
+ * @static
+ * @memberOf _
+ * @type Function
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is an array, else `false`.
+ * @example
+ *
+ * (function() { return _.isArray(arguments); })();
+ * // => false
+ *
+ * _.isArray([1, 2, 3]);
+ * // => true
+ */
+ var isArray = nativeIsArray || function(value) {
+ return value && typeof value == 'object' && typeof value.length == 'number' &&
+ toString.call(value) == arrayClass || false;
+ };
+
+ /**
+ * A fallback implementation of `Object.keys` which produces an array of the
+ * given object's own enumerable property names.
+ *
+ * @private
+ * @type Function
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns an array of property names.
+ */
+ var shimKeys = createIterator({
+ 'args': 'object',
+ 'init': '[]',
+ 'top': 'if (!(objectTypes[typeof object])) return result',
+ 'loop': 'result.push(index)'
+ });
+
+ /**
+ * Creates an array composed of the own enumerable property names of an object.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns an array of property names.
+ * @example
+ *
+ * _.keys({ 'one': 1, 'two': 2, 'three': 3 });
+ * // => ['one', 'two', 'three'] (property order is not guaranteed across environments)
+ */
+ var keys = !nativeKeys ? shimKeys : function(object) {
+ if (!isObject(object)) {
+ return [];
+ }
+ if ((support.enumPrototypes && typeof object == 'function') ||
+ (support.nonEnumArgs && object.length && isArguments(object))) {
+ return shimKeys(object);
+ }
+ return nativeKeys(object);
+ };
+
+ /** Reusable iterator options shared by `each`, `forIn`, and `forOwn` */
+ var eachIteratorOptions = {
+ 'args': 'collection, callback, thisArg',
+ 'top': "callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3)",
+ 'array': "typeof length == 'number'",
+ 'keys': keys,
+ 'loop': 'if (callback(iterable[index], index, collection) === false) return result'
+ };
+
+ /** Reusable iterator options for `assign` and `defaults` */
+ var defaultsIteratorOptions = {
+ 'args': 'object, source, guard',
+ 'top':
+ 'var args = arguments,\n' +
+ ' argsIndex = 0,\n' +
+ " argsLength = typeof guard == 'number' ? 2 : args.length;\n" +
+ 'while (++argsIndex < argsLength) {\n' +
+ ' iterable = args[argsIndex];\n' +
+ ' if (iterable && objectTypes[typeof iterable]) {',
+ 'keys': keys,
+ 'loop': "if (typeof result[index] == 'undefined') result[index] = iterable[index]",
+ 'bottom': ' }\n}'
+ };
+
+ /** Reusable iterator options for `forIn` and `forOwn` */
+ var forOwnIteratorOptions = {
+ 'top': 'if (!objectTypes[typeof iterable]) return result;\n' + eachIteratorOptions.top,
+ 'array': false
+ };
+
+ /**
+ * Used to convert characters to HTML entities:
+ *
+ * Though the `>` character is escaped for symmetry, characters like `>` and `/`
+ * don't require escaping in HTML and have no special meaning unless they're part
+ * of a tag or an unquoted attribute value.
+ * http://mathiasbynens.be/notes/ambiguous-ampersands (under "semi-related fun fact")
+ */
+ var htmlEscapes = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": '''
+ };
+
+ /** Used to convert HTML entities to characters */
+ var htmlUnescapes = invert(htmlEscapes);
+
+ /** Used to match HTML entities and HTML characters */
+ var reEscapedHtml = RegExp('(' + keys(htmlUnescapes).join('|') + ')', 'g'),
+ reUnescapedHtml = RegExp('[' + keys(htmlEscapes).join('') + ']', 'g');
+
+ /**
+ * A function compiled to iterate `arguments` objects, arrays, objects, and
+ * strings consistenly across environments, executing the callback for each
+ * element in the collection. The callback is bound to `thisArg` and invoked
+ * with three arguments; (value, index|key, collection). Callbacks may exit
+ * iteration early by explicitly returning `false`.
+ *
+ * @private
+ * @type Function
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array|Object|string} Returns `collection`.
+ */
+ var baseEach = createIterator(eachIteratorOptions);
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Assigns own enumerable properties of source object(s) to the destination
+ * object. Subsequent sources will overwrite property assignments of previous
+ * sources. If a callback is provided it will be executed to produce the
+ * assigned values. The callback is bound to `thisArg` and invoked with two
+ * arguments; (objectValue, sourceValue).
+ *
+ * @static
+ * @memberOf _
+ * @type Function
+ * @alias extend
+ * @category Objects
+ * @param {Object} object The destination object.
+ * @param {...Object} [source] The source objects.
+ * @param {Function} [callback] The function to customize assigning values.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns the destination object.
+ * @example
+ *
+ * _.assign({ 'name': 'fred' }, { 'employer': 'slate' });
+ * // => { 'name': 'fred', 'employer': 'slate' }
+ *
+ * var defaults = _.partialRight(_.assign, function(a, b) {
+ * return typeof a == 'undefined' ? b : a;
+ * });
+ *
+ * var object = { 'name': 'barney' };
+ * defaults(object, { 'name': 'fred', 'employer': 'slate' });
+ * // => { 'name': 'barney', 'employer': 'slate' }
+ */
+ var assign = createIterator(defaultsIteratorOptions, {
+ 'top':
+ defaultsIteratorOptions.top.replace(';',
+ ';\n' +
+ "if (argsLength > 3 && typeof args[argsLength - 2] == 'function') {\n" +
+ ' var callback = baseCreateCallback(args[--argsLength - 1], args[argsLength--], 2);\n' +
+ "} else if (argsLength > 2 && typeof args[argsLength - 1] == 'function') {\n" +
+ ' callback = args[--argsLength];\n' +
+ '}'
+ ),
+ 'loop': 'result[index] = callback ? callback(result[index], iterable[index]) : iterable[index]'
+ });
+
+ /**
+ * Creates a clone of `value`. If `isDeep` is `true` nested objects will also
+ * be cloned, otherwise they will be assigned by reference. If a callback
+ * is provided it will be executed to produce the cloned values. If the
+ * callback returns `undefined` cloning will be handled by the method instead.
+ * The callback is bound to `thisArg` and invoked with one argument; (value).
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to clone.
+ * @param {boolean} [isDeep=false] Specify a deep clone.
+ * @param {Function} [callback] The function to customize cloning values.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the cloned value.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 }
+ * ];
+ *
+ * var shallow = _.clone(characters);
+ * shallow[0] === characters[0];
+ * // => true
+ *
+ * var deep = _.clone(characters, true);
+ * deep[0] === characters[0];
+ * // => false
+ *
+ * _.mixin({
+ * 'clone': _.partialRight(_.clone, function(value) {
+ * return _.isElement(value) ? value.cloneNode(false) : undefined;
+ * })
+ * });
+ *
+ * var clone = _.clone(document.body);
+ * clone.childNodes.length;
+ * // => 0
+ */
+ function clone(value, isDeep, callback, thisArg) {
+ // allows working with "Collections" methods without using their `index`
+ // and `collection` arguments for `isDeep` and `callback`
+ if (typeof isDeep != 'boolean' && isDeep != null) {
+ thisArg = callback;
+ callback = isDeep;
+ isDeep = false;
+ }
+ return baseClone(value, isDeep, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 1));
+ }
+
+ /**
+ * Creates a deep clone of `value`. If a callback is provided it will be
+ * executed to produce the cloned values. If the callback returns `undefined`
+ * cloning will be handled by the method instead. The callback is bound to
+ * `thisArg` and invoked with one argument; (value).
+ *
+ * Note: This method is loosely based on the structured clone algorithm. Functions
+ * and DOM nodes are **not** cloned. The enumerable properties of `arguments` objects and
+ * objects created by constructors other than `Object` are cloned to plain `Object` objects.
+ * See http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to deep clone.
+ * @param {Function} [callback] The function to customize cloning values.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the deep cloned value.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 }
+ * ];
+ *
+ * var deep = _.cloneDeep(characters);
+ * deep[0] === characters[0];
+ * // => false
+ *
+ * var view = {
+ * 'label': 'docs',
+ * 'node': element
+ * };
+ *
+ * var clone = _.cloneDeep(view, function(value) {
+ * return _.isElement(value) ? value.cloneNode(true) : undefined;
+ * });
+ *
+ * clone.node == view.node;
+ * // => false
+ */
+ function cloneDeep(value, callback, thisArg) {
+ return baseClone(value, true, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 1));
+ }
+
+ /**
+ * Creates an object that inherits from the given `prototype` object. If a
+ * `properties` object is provided its own enumerable properties are assigned
+ * to the created object.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} prototype The object to inherit from.
+ * @param {Object} [properties] The properties to assign to the object.
+ * @returns {Object} Returns the new object.
+ * @example
+ *
+ * function Shape() {
+ * this.x = 0;
+ * this.y = 0;
+ * }
+ *
+ * function Circle() {
+ * Shape.call(this);
+ * }
+ *
+ * Circle.prototype = _.create(Shape.prototype, { 'constructor': Circle });
+ *
+ * var circle = new Circle;
+ * circle instanceof Circle;
+ * // => true
+ *
+ * circle instanceof Shape;
+ * // => true
+ */
+ function create(prototype, properties) {
+ var result = baseCreate(prototype);
+ return properties ? assign(result, properties) : result;
+ }
+
+ /**
+ * Assigns own enumerable properties of source object(s) to the destination
+ * object for all destination properties that resolve to `undefined`. Once a
+ * property is set, additional defaults of the same property will be ignored.
+ *
+ * @static
+ * @memberOf _
+ * @type Function
+ * @category Objects
+ * @param {Object} object The destination object.
+ * @param {...Object} [source] The source objects.
+ * @param- {Object} [guard] Allows working with `_.reduce` without using its
+ * `key` and `object` arguments as sources.
+ * @returns {Object} Returns the destination object.
+ * @example
+ *
+ * var object = { 'name': 'barney' };
+ * _.defaults(object, { 'name': 'fred', 'employer': 'slate' });
+ * // => { 'name': 'barney', 'employer': 'slate' }
+ */
+ var defaults = createIterator(defaultsIteratorOptions);
+
+ /**
+ * This method is like `_.findIndex` except that it returns the key of the
+ * first element that passes the callback check, instead of the element itself.
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to search.
+ * @param {Function|Object|string} [callback=identity] The function called per
+ * iteration. If a property name or object is provided it will be used to
+ * create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {string|undefined} Returns the key of the found element, else `undefined`.
+ * @example
+ *
+ * var characters = {
+ * 'barney': { 'age': 36, 'blocked': false },
+ * 'fred': { 'age': 40, 'blocked': true },
+ * 'pebbles': { 'age': 1, 'blocked': false }
+ * };
+ *
+ * _.findKey(characters, function(chr) {
+ * return chr.age < 40;
+ * });
+ * // => 'barney' (property order is not guaranteed across environments)
+ *
+ * // using "_.where" callback shorthand
+ * _.findKey(characters, { 'age': 1 });
+ * // => 'pebbles'
+ *
+ * // using "_.pluck" callback shorthand
+ * _.findKey(characters, 'blocked');
+ * // => 'fred'
+ */
+ function findKey(object, callback, thisArg) {
+ var result;
+ callback = lodash.createCallback(callback, thisArg, 3);
+ forOwn(object, function(value, key, object) {
+ if (callback(value, key, object)) {
+ result = key;
+ return false;
+ }
+ });
+ return result;
+ }
+
+ /**
+ * This method is like `_.findKey` except that it iterates over elements
+ * of a `collection` in the opposite order.
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to search.
+ * @param {Function|Object|string} [callback=identity] The function called per
+ * iteration. If a property name or object is provided it will be used to
+ * create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {string|undefined} Returns the key of the found element, else `undefined`.
+ * @example
+ *
+ * var characters = {
+ * 'barney': { 'age': 36, 'blocked': true },
+ * 'fred': { 'age': 40, 'blocked': false },
+ * 'pebbles': { 'age': 1, 'blocked': true }
+ * };
+ *
+ * _.findLastKey(characters, function(chr) {
+ * return chr.age < 40;
+ * });
+ * // => returns `pebbles`, assuming `_.findKey` returns `barney`
+ *
+ * // using "_.where" callback shorthand
+ * _.findLastKey(characters, { 'age': 40 });
+ * // => 'fred'
+ *
+ * // using "_.pluck" callback shorthand
+ * _.findLastKey(characters, 'blocked');
+ * // => 'pebbles'
+ */
+ function findLastKey(object, callback, thisArg) {
+ var result;
+ callback = lodash.createCallback(callback, thisArg, 3);
+ forOwnRight(object, function(value, key, object) {
+ if (callback(value, key, object)) {
+ result = key;
+ return false;
+ }
+ });
+ return result;
+ }
+
+ /**
+ * Iterates over own and inherited enumerable properties of an object,
+ * executing the callback for each property. The callback is bound to `thisArg`
+ * and invoked with three arguments; (value, key, object). Callbacks may exit
+ * iteration early by explicitly returning `false`.
+ *
+ * @static
+ * @memberOf _
+ * @type Function
+ * @category Objects
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * function Shape() {
+ * this.x = 0;
+ * this.y = 0;
+ * }
+ *
+ * Shape.prototype.move = function(x, y) {
+ * this.x += x;
+ * this.y += y;
+ * };
+ *
+ * _.forIn(new Shape, function(value, key) {
+ * console.log(key);
+ * });
+ * // => logs 'x', 'y', and 'move' (property order is not guaranteed across environments)
+ */
+ var forIn = createIterator(eachIteratorOptions, forOwnIteratorOptions, {
+ 'useHas': false
+ });
+
+ /**
+ * This method is like `_.forIn` except that it iterates over elements
+ * of a `collection` in the opposite order.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * function Shape() {
+ * this.x = 0;
+ * this.y = 0;
+ * }
+ *
+ * Shape.prototype.move = function(x, y) {
+ * this.x += x;
+ * this.y += y;
+ * };
+ *
+ * _.forInRight(new Shape, function(value, key) {
+ * console.log(key);
+ * });
+ * // => logs 'move', 'y', and 'x' assuming `_.forIn ` logs 'x', 'y', and 'move'
+ */
+ function forInRight(object, callback, thisArg) {
+ var pairs = [];
+
+ forIn(object, function(value, key) {
+ pairs.push(key, value);
+ });
+
+ var length = pairs.length;
+ callback = baseCreateCallback(callback, thisArg, 3);
+ while (length--) {
+ if (callback(pairs[length--], pairs[length], object) === false) {
+ break;
+ }
+ }
+ return object;
+ }
+
+ /**
+ * Iterates over own enumerable properties of an object, executing the callback
+ * for each property. The callback is bound to `thisArg` and invoked with three
+ * arguments; (value, key, object). Callbacks may exit iteration early by
+ * explicitly returning `false`.
+ *
+ * @static
+ * @memberOf _
+ * @type Function
+ * @category Objects
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) {
+ * console.log(key);
+ * });
+ * // => logs '0', '1', and 'length' (property order is not guaranteed across environments)
+ */
+ var forOwn = createIterator(eachIteratorOptions, forOwnIteratorOptions);
+
+ /**
+ * This method is like `_.forOwn` except that it iterates over elements
+ * of a `collection` in the opposite order.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * _.forOwnRight({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) {
+ * console.log(key);
+ * });
+ * // => logs 'length', '1', and '0' assuming `_.forOwn` logs '0', '1', and 'length'
+ */
+ function forOwnRight(object, callback, thisArg) {
+ var props = keys(object),
+ length = props.length;
+
+ callback = baseCreateCallback(callback, thisArg, 3);
+ while (length--) {
+ var key = props[length];
+ if (callback(object[key], key, object) === false) {
+ break;
+ }
+ }
+ return object;
+ }
+
+ /**
+ * Creates a sorted array of property names of all enumerable properties,
+ * own and inherited, of `object` that have function values.
+ *
+ * @static
+ * @memberOf _
+ * @alias methods
+ * @category Objects
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns an array of property names that have function values.
+ * @example
+ *
+ * _.functions(_);
+ * // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...]
+ */
+ function functions(object) {
+ var result = [];
+ forIn(object, function(value, key) {
+ if (isFunction(value)) {
+ result.push(key);
+ }
+ });
+ return result.sort();
+ }
+
+ /**
+ * Checks if the specified property name exists as a direct property of `object`,
+ * instead of an inherited property.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to inspect.
+ * @param {string} key The name of the property to check.
+ * @returns {boolean} Returns `true` if key is a direct property, else `false`.
+ * @example
+ *
+ * _.has({ 'a': 1, 'b': 2, 'c': 3 }, 'b');
+ * // => true
+ */
+ function has(object, key) {
+ return object ? hasOwnProperty.call(object, key) : false;
+ }
+
+ /**
+ * Creates an object composed of the inverted keys and values of the given object.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to invert.
+ * @returns {Object} Returns the created inverted object.
+ * @example
+ *
+ * _.invert({ 'first': 'fred', 'second': 'barney' });
+ * // => { 'fred': 'first', 'barney': 'second' }
+ */
+ function invert(object) {
+ var index = -1,
+ props = keys(object),
+ length = props.length,
+ result = {};
+
+ while (++index < length) {
+ var key = props[index];
+ result[object[key]] = key;
+ }
+ return result;
+ }
+
+ /**
+ * Checks if `value` is a boolean value.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is a boolean value, else `false`.
+ * @example
+ *
+ * _.isBoolean(null);
+ * // => false
+ */
+ function isBoolean(value) {
+ return value === true || value === false ||
+ value && typeof value == 'object' && toString.call(value) == boolClass || false;
+ }
+
+ /**
+ * Checks if `value` is a date.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is a date, else `false`.
+ * @example
+ *
+ * _.isDate(new Date);
+ * // => true
+ */
+ function isDate(value) {
+ return value && typeof value == 'object' && toString.call(value) == dateClass || false;
+ }
+
+ /**
+ * Checks if `value` is a DOM element.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is a DOM element, else `false`.
+ * @example
+ *
+ * _.isElement(document.body);
+ * // => true
+ */
+ function isElement(value) {
+ return value && value.nodeType === 1 || false;
+ }
+
+ /**
+ * Checks if `value` is empty. Arrays, strings, or `arguments` objects with a
+ * length of `0` and objects with no own enumerable properties are considered
+ * "empty".
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Array|Object|string} value The value to inspect.
+ * @returns {boolean} Returns `true` if the `value` is empty, else `false`.
+ * @example
+ *
+ * _.isEmpty([1, 2, 3]);
+ * // => false
+ *
+ * _.isEmpty({});
+ * // => true
+ *
+ * _.isEmpty('');
+ * // => true
+ */
+ function isEmpty(value) {
+ var result = true;
+ if (!value) {
+ return result;
+ }
+ var className = toString.call(value),
+ length = value.length;
+
+ if ((className == arrayClass || className == stringClass ||
+ (support.argsClass ? className == argsClass : isArguments(value))) ||
+ (className == objectClass && typeof length == 'number' && isFunction(value.splice))) {
+ return !length;
+ }
+ forOwn(value, function() {
+ return (result = false);
+ });
+ return result;
+ }
+
+ /**
+ * Performs a deep comparison between two values to determine if they are
+ * equivalent to each other. If a callback is provided it will be executed
+ * to compare values. If the callback returns `undefined` comparisons will
+ * be handled by the method instead. The callback is bound to `thisArg` and
+ * invoked with two arguments; (a, b).
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} a The value to compare.
+ * @param {*} b The other value to compare.
+ * @param {Function} [callback] The function to customize comparing values.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+ * @example
+ *
+ * var object = { 'name': 'fred' };
+ * var copy = { 'name': 'fred' };
+ *
+ * object == copy;
+ * // => false
+ *
+ * _.isEqual(object, copy);
+ * // => true
+ *
+ * var words = ['hello', 'goodbye'];
+ * var otherWords = ['hi', 'goodbye'];
+ *
+ * _.isEqual(words, otherWords, function(a, b) {
+ * var reGreet = /^(?:hello|hi)$/i,
+ * aGreet = _.isString(a) && reGreet.test(a),
+ * bGreet = _.isString(b) && reGreet.test(b);
+ *
+ * return (aGreet || bGreet) ? (aGreet == bGreet) : undefined;
+ * });
+ * // => true
+ */
+ function isEqual(a, b, callback, thisArg) {
+ return baseIsEqual(a, b, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 2));
+ }
+
+ /**
+ * Checks if `value` is, or can be coerced to, a finite number.
+ *
+ * Note: This is not the same as native `isFinite` which will return true for
+ * booleans and empty strings. See http://es5.github.io/#x15.1.2.5.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is finite, else `false`.
+ * @example
+ *
+ * _.isFinite(-101);
+ * // => true
+ *
+ * _.isFinite('10');
+ * // => true
+ *
+ * _.isFinite(true);
+ * // => false
+ *
+ * _.isFinite('');
+ * // => false
+ *
+ * _.isFinite(Infinity);
+ * // => false
+ */
+ function isFinite(value) {
+ return nativeIsFinite(value) && !nativeIsNaN(parseFloat(value));
+ }
+
+ /**
+ * Checks if `value` is a function.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is a function, else `false`.
+ * @example
+ *
+ * _.isFunction(_);
+ * // => true
+ */
+ function isFunction(value) {
+ return typeof value == 'function';
+ }
+ // fallback for older versions of Chrome and Safari
+ if (isFunction(/x/)) {
+ isFunction = function(value) {
+ return typeof value == 'function' && toString.call(value) == funcClass;
+ };
+ }
+
+ /**
+ * Checks if `value` is the language type of Object.
+ * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is an object, else `false`.
+ * @example
+ *
+ * _.isObject({});
+ * // => true
+ *
+ * _.isObject([1, 2, 3]);
+ * // => true
+ *
+ * _.isObject(1);
+ * // => false
+ */
+ function isObject(value) {
+ // check if the value is the ECMAScript language type of Object
+ // http://es5.github.io/#x8
+ // and avoid a V8 bug
+ // http://code.google.com/p/v8/issues/detail?id=2291
+ return !!(value && objectTypes[typeof value]);
+ }
+
+ /**
+ * Checks if `value` is `NaN`.
+ *
+ * Note: This is not the same as native `isNaN` which will return `true` for
+ * `undefined` and other non-numeric values. See http://es5.github.io/#x15.1.2.4.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is `NaN`, else `false`.
+ * @example
+ *
+ * _.isNaN(NaN);
+ * // => true
+ *
+ * _.isNaN(new Number(NaN));
+ * // => true
+ *
+ * isNaN(undefined);
+ * // => true
+ *
+ * _.isNaN(undefined);
+ * // => false
+ */
+ function isNaN(value) {
+ // `NaN` as a primitive is the only value that is not equal to itself
+ // (perform the [[Class]] check first to avoid errors with some host objects in IE)
+ return isNumber(value) && value != +value;
+ }
+
+ /**
+ * Checks if `value` is `null`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is `null`, else `false`.
+ * @example
+ *
+ * _.isNull(null);
+ * // => true
+ *
+ * _.isNull(undefined);
+ * // => false
+ */
+ function isNull(value) {
+ return value === null;
+ }
+
+ /**
+ * Checks if `value` is a number.
+ *
+ * Note: `NaN` is considered a number. See http://es5.github.io/#x8.5.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is a number, else `false`.
+ * @example
+ *
+ * _.isNumber(8.4 * 5);
+ * // => true
+ */
+ function isNumber(value) {
+ return typeof value == 'number' ||
+ value && typeof value == 'object' && toString.call(value) == numberClass || false;
+ }
+
+ /**
+ * Checks if `value` is an object created by the `Object` constructor.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
+ * @example
+ *
+ * function Shape() {
+ * this.x = 0;
+ * this.y = 0;
+ * }
+ *
+ * _.isPlainObject(new Shape);
+ * // => false
+ *
+ * _.isPlainObject([1, 2, 3]);
+ * // => false
+ *
+ * _.isPlainObject({ 'x': 0, 'y': 0 });
+ * // => true
+ */
+ var isPlainObject = !getPrototypeOf ? shimIsPlainObject : function(value) {
+ if (!(value && toString.call(value) == objectClass) || (!support.argsClass && isArguments(value))) {
+ return false;
+ }
+ var valueOf = value.valueOf,
+ objProto = isNative(valueOf) && (objProto = getPrototypeOf(valueOf)) && getPrototypeOf(objProto);
+
+ return objProto
+ ? (value == objProto || getPrototypeOf(value) == objProto)
+ : shimIsPlainObject(value);
+ };
+
+ /**
+ * Checks if `value` is a regular expression.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is a regular expression, else `false`.
+ * @example
+ *
+ * _.isRegExp(/fred/);
+ * // => true
+ */
+ function isRegExp(value) {
+ return value && objectTypes[typeof value] && toString.call(value) == regexpClass || false;
+ }
+
+ /**
+ * Checks if `value` is a string.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is a string, else `false`.
+ * @example
+ *
+ * _.isString('fred');
+ * // => true
+ */
+ function isString(value) {
+ return typeof value == 'string' ||
+ value && typeof value == 'object' && toString.call(value) == stringClass || false;
+ }
+
+ /**
+ * Checks if `value` is `undefined`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is `undefined`, else `false`.
+ * @example
+ *
+ * _.isUndefined(void 0);
+ * // => true
+ */
+ function isUndefined(value) {
+ return typeof value == 'undefined';
+ }
+
+ /**
+ * Creates an object with the same keys as `object` and values generated by
+ * running each own enumerable property of `object` through the callback.
+ * The callback is bound to `thisArg` and invoked with three arguments;
+ * (value, key, object).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a new object with values of the results of each `callback` execution.
+ * @example
+ *
+ * _.mapValues({ 'a': 1, 'b': 2, 'c': 3} , function(num) { return num * 3; });
+ * // => { 'a': 3, 'b': 6, 'c': 9 }
+ *
+ * var characters = {
+ * 'fred': { 'name': 'fred', 'age': 40 },
+ * 'pebbles': { 'name': 'pebbles', 'age': 1 }
+ * };
+ *
+ * // using "_.pluck" callback shorthand
+ * _.mapValues(characters, 'age');
+ * // => { 'fred': 40, 'pebbles': 1 }
+ */
+ function mapValues(object, callback, thisArg) {
+ var result = {};
+ callback = lodash.createCallback(callback, thisArg, 3);
+
+ forOwn(object, function(value, key, object) {
+ result[key] = callback(value, key, object);
+ });
+ return result;
+ }
+
+ /**
+ * Recursively merges own enumerable properties of the source object(s), that
+ * don't resolve to `undefined` into the destination object. Subsequent sources
+ * will overwrite property assignments of previous sources. If a callback is
+ * provided it will be executed to produce the merged values of the destination
+ * and source properties. If the callback returns `undefined` merging will
+ * be handled by the method instead. The callback is bound to `thisArg` and
+ * invoked with two arguments; (objectValue, sourceValue).
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The destination object.
+ * @param {...Object} [source] The source objects.
+ * @param {Function} [callback] The function to customize merging properties.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns the destination object.
+ * @example
+ *
+ * var names = {
+ * 'characters': [
+ * { 'name': 'barney' },
+ * { 'name': 'fred' }
+ * ]
+ * };
+ *
+ * var ages = {
+ * 'characters': [
+ * { 'age': 36 },
+ * { 'age': 40 }
+ * ]
+ * };
+ *
+ * _.merge(names, ages);
+ * // => { 'characters': [{ 'name': 'barney', 'age': 36 }, { 'name': 'fred', 'age': 40 }] }
+ *
+ * var food = {
+ * 'fruits': ['apple'],
+ * 'vegetables': ['beet']
+ * };
+ *
+ * var otherFood = {
+ * 'fruits': ['banana'],
+ * 'vegetables': ['carrot']
+ * };
+ *
+ * _.merge(food, otherFood, function(a, b) {
+ * return _.isArray(a) ? a.concat(b) : undefined;
+ * });
+ * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot] }
+ */
+ function merge(object) {
+ var args = arguments,
+ length = 2;
+
+ if (!isObject(object)) {
+ return object;
+ }
+ // allows working with `_.reduce` and `_.reduceRight` without using
+ // their `index` and `collection` arguments
+ if (typeof args[2] != 'number') {
+ length = args.length;
+ }
+ if (length > 3 && typeof args[length - 2] == 'function') {
+ var callback = baseCreateCallback(args[--length - 1], args[length--], 2);
+ } else if (length > 2 && typeof args[length - 1] == 'function') {
+ callback = args[--length];
+ }
+ var sources = slice(arguments, 1, length),
+ index = -1,
+ stackA = getArray(),
+ stackB = getArray();
+
+ while (++index < length) {
+ baseMerge(object, sources[index], callback, stackA, stackB);
+ }
+ releaseArray(stackA);
+ releaseArray(stackB);
+ return object;
+ }
+
+ /**
+ * Creates a shallow clone of `object` excluding the specified properties.
+ * Property names may be specified as individual arguments or as arrays of
+ * property names. If a callback is provided it will be executed for each
+ * property of `object` omitting the properties the callback returns truey
+ * for. The callback is bound to `thisArg` and invoked with three arguments;
+ * (value, key, object).
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The source object.
+ * @param {Function|...string|string[]} [callback] The properties to omit or the
+ * function called per iteration.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns an object without the omitted properties.
+ * @example
+ *
+ * _.omit({ 'name': 'fred', 'age': 40 }, 'age');
+ * // => { 'name': 'fred' }
+ *
+ * _.omit({ 'name': 'fred', 'age': 40 }, function(value) {
+ * return typeof value == 'number';
+ * });
+ * // => { 'name': 'fred' }
+ */
+ function omit(object, callback, thisArg) {
+ var result = {};
+ if (typeof callback != 'function') {
+ var props = [];
+ forIn(object, function(value, key) {
+ props.push(key);
+ });
+ props = baseDifference(props, baseFlatten(arguments, true, false, 1));
+
+ var index = -1,
+ length = props.length;
+
+ while (++index < length) {
+ var key = props[index];
+ result[key] = object[key];
+ }
+ } else {
+ callback = lodash.createCallback(callback, thisArg, 3);
+ forIn(object, function(value, key, object) {
+ if (!callback(value, key, object)) {
+ result[key] = value;
+ }
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Creates a two dimensional array of an object's key-value pairs,
+ * i.e. `[[key1, value1], [key2, value2]]`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns new array of key-value pairs.
+ * @example
+ *
+ * _.pairs({ 'barney': 36, 'fred': 40 });
+ * // => [['barney', 36], ['fred', 40]] (property order is not guaranteed across environments)
+ */
+ function pairs(object) {
+ var index = -1,
+ props = keys(object),
+ length = props.length,
+ result = Array(length);
+
+ while (++index < length) {
+ var key = props[index];
+ result[index] = [key, object[key]];
+ }
+ return result;
+ }
+
+ /**
+ * Creates a shallow clone of `object` composed of the specified properties.
+ * Property names may be specified as individual arguments or as arrays of
+ * property names. If a callback is provided it will be executed for each
+ * property of `object` picking the properties the callback returns truey
+ * for. The callback is bound to `thisArg` and invoked with three arguments;
+ * (value, key, object).
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The source object.
+ * @param {Function|...string|string[]} [callback] The function called per
+ * iteration or property names to pick, specified as individual property
+ * names or arrays of property names.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns an object composed of the picked properties.
+ * @example
+ *
+ * _.pick({ 'name': 'fred', '_userid': 'fred1' }, 'name');
+ * // => { 'name': 'fred' }
+ *
+ * _.pick({ 'name': 'fred', '_userid': 'fred1' }, function(value, key) {
+ * return key.charAt(0) != '_';
+ * });
+ * // => { 'name': 'fred' }
+ */
+ function pick(object, callback, thisArg) {
+ var result = {};
+ if (typeof callback != 'function') {
+ var index = -1,
+ props = baseFlatten(arguments, true, false, 1),
+ length = isObject(object) ? props.length : 0;
+
+ while (++index < length) {
+ var key = props[index];
+ if (key in object) {
+ result[key] = object[key];
+ }
+ }
+ } else {
+ callback = lodash.createCallback(callback, thisArg, 3);
+ forIn(object, function(value, key, object) {
+ if (callback(value, key, object)) {
+ result[key] = value;
+ }
+ });
+ }
+ return result;
+ }
+
+ /**
+ * An alternative to `_.reduce` this method transforms `object` to a new
+ * `accumulator` object which is the result of running each of its own
+ * enumerable properties through a callback, with each callback execution
+ * potentially mutating the `accumulator` object. The callback is bound to
+ * `thisArg` and invoked with four arguments; (accumulator, value, key, object).
+ * Callbacks may exit iteration early by explicitly returning `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Array|Object} object The object to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [accumulator] The custom accumulator value.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the accumulated value.
+ * @example
+ *
+ * var squares = _.transform([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], function(result, num) {
+ * num *= num;
+ * if (num % 2) {
+ * return result.push(num) < 3;
+ * }
+ * });
+ * // => [1, 9, 25]
+ *
+ * var mapped = _.transform({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) {
+ * result[key] = num * 3;
+ * });
+ * // => { 'a': 3, 'b': 6, 'c': 9 }
+ */
+ function transform(object, callback, accumulator, thisArg) {
+ var isArr = isArray(object);
+ if (accumulator == null) {
+ if (isArr) {
+ accumulator = [];
+ } else {
+ var ctor = object && object.constructor,
+ proto = ctor && ctor.prototype;
+
+ accumulator = baseCreate(proto);
+ }
+ }
+ if (callback) {
+ callback = lodash.createCallback(callback, thisArg, 4);
+ (isArr ? baseEach : forOwn)(object, function(value, index, object) {
+ return callback(accumulator, value, index, object);
+ });
+ }
+ return accumulator;
+ }
+
+ /**
+ * Creates an array composed of the own enumerable property values of `object`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns an array of property values.
+ * @example
+ *
+ * _.values({ 'one': 1, 'two': 2, 'three': 3 });
+ * // => [1, 2, 3] (property order is not guaranteed across environments)
+ */
+ function values(object) {
+ var index = -1,
+ props = keys(object),
+ length = props.length,
+ result = Array(length);
+
+ while (++index < length) {
+ result[index] = object[props[index]];
+ }
+ return result;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates an array of elements from the specified indexes, or keys, of the
+ * `collection`. Indexes may be specified as individual arguments or as arrays
+ * of indexes.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {...(number|number[]|string|string[])} [index] The indexes of `collection`
+ * to retrieve, specified as individual indexes or arrays of indexes.
+ * @returns {Array} Returns a new array of elements corresponding to the
+ * provided indexes.
+ * @example
+ *
+ * _.at(['a', 'b', 'c', 'd', 'e'], [0, 2, 4]);
+ * // => ['a', 'c', 'e']
+ *
+ * _.at(['fred', 'barney', 'pebbles'], 0, 2);
+ * // => ['fred', 'pebbles']
+ */
+ function at(collection) {
+ var args = arguments,
+ index = -1,
+ props = baseFlatten(args, true, false, 1),
+ length = (args[2] && args[2][args[1]] === collection) ? 1 : props.length,
+ result = Array(length);
+
+ if (support.unindexedChars && isString(collection)) {
+ collection = collection.split('');
+ }
+ while(++index < length) {
+ result[index] = collection[props[index]];
+ }
+ return result;
+ }
+
+ /**
+ * Checks if a given value is present in a collection using strict equality
+ * for comparisons, i.e. `===`. If `fromIndex` is negative, it is used as the
+ * offset from the end of the collection.
+ *
+ * @static
+ * @memberOf _
+ * @alias include
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {*} target The value to check for.
+ * @param {number} [fromIndex=0] The index to search from.
+ * @returns {boolean} Returns `true` if the `target` element is found, else `false`.
+ * @example
+ *
+ * _.contains([1, 2, 3], 1);
+ * // => true
+ *
+ * _.contains([1, 2, 3], 1, 2);
+ * // => false
+ *
+ * _.contains({ 'name': 'fred', 'age': 40 }, 'fred');
+ * // => true
+ *
+ * _.contains('pebbles', 'eb');
+ * // => true
+ */
+ function contains(collection, target, fromIndex) {
+ var index = -1,
+ indexOf = getIndexOf(),
+ length = collection ? collection.length : 0,
+ result = false;
+
+ fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex) || 0;
+ if (isArray(collection)) {
+ result = indexOf(collection, target, fromIndex) > -1;
+ } else if (typeof length == 'number') {
+ result = (isString(collection) ? collection.indexOf(target, fromIndex) : indexOf(collection, target, fromIndex)) > -1;
+ } else {
+ baseEach(collection, function(value) {
+ if (++index >= fromIndex) {
+ return !(result = value === target);
+ }
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Creates an object composed of keys generated from the results of running
+ * each element of `collection` through the callback. The corresponding value
+ * of each key is the number of times the key was returned by the callback.
+ * The callback is bound to `thisArg` and invoked with three arguments;
+ * (value, index|key, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns the composed aggregate object.
+ * @example
+ *
+ * _.countBy([4.3, 6.1, 6.4], function(num) { return Math.floor(num); });
+ * // => { '4': 1, '6': 2 }
+ *
+ * _.countBy([4.3, 6.1, 6.4], function(num) { return this.floor(num); }, Math);
+ * // => { '4': 1, '6': 2 }
+ *
+ * _.countBy(['one', 'two', 'three'], 'length');
+ * // => { '3': 2, '5': 1 }
+ */
+ var countBy = createAggregator(function(result, value, key) {
+ (hasOwnProperty.call(result, key) ? result[key]++ : result[key] = 1);
+ });
+
+ /**
+ * Checks if the given callback returns truey value for **all** elements of
+ * a collection. The callback is bound to `thisArg` and invoked with three
+ * arguments; (value, index|key, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @alias all
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {boolean} Returns `true` if all elements passed the callback check,
+ * else `false`.
+ * @example
+ *
+ * _.every([true, 1, null, 'yes']);
+ * // => false
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.every(characters, 'age');
+ * // => true
+ *
+ * // using "_.where" callback shorthand
+ * _.every(characters, { 'age': 36 });
+ * // => false
+ */
+ function every(collection, callback, thisArg) {
+ var result = true;
+ callback = lodash.createCallback(callback, thisArg, 3);
+
+ if (isArray(collection)) {
+ var index = -1,
+ length = collection.length;
+
+ while (++index < length) {
+ if (!(result = !!callback(collection[index], index, collection))) {
+ break;
+ }
+ }
+ } else {
+ baseEach(collection, function(value, index, collection) {
+ return (result = !!callback(value, index, collection));
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Iterates over elements of a collection, returning an array of all elements
+ * the callback returns truey for. The callback is bound to `thisArg` and
+ * invoked with three arguments; (value, index|key, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @alias select
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a new array of elements that passed the callback check.
+ * @example
+ *
+ * var evens = _.filter([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });
+ * // => [2, 4, 6]
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36, 'blocked': false },
+ * { 'name': 'fred', 'age': 40, 'blocked': true }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.filter(characters, 'blocked');
+ * // => [{ 'name': 'fred', 'age': 40, 'blocked': true }]
+ *
+ * // using "_.where" callback shorthand
+ * _.filter(characters, { 'age': 36 });
+ * // => [{ 'name': 'barney', 'age': 36, 'blocked': false }]
+ */
+ function filter(collection, callback, thisArg) {
+ var result = [];
+ callback = lodash.createCallback(callback, thisArg, 3);
+
+ if (isArray(collection)) {
+ var index = -1,
+ length = collection.length;
+
+ while (++index < length) {
+ var value = collection[index];
+ if (callback(value, index, collection)) {
+ result.push(value);
+ }
+ }
+ } else {
+ baseEach(collection, function(value, index, collection) {
+ if (callback(value, index, collection)) {
+ result.push(value);
+ }
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Iterates over elements of a collection, returning the first element that
+ * the callback returns truey for. The callback is bound to `thisArg` and
+ * invoked with three arguments; (value, index|key, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @alias detect, findWhere
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the found element, else `undefined`.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36, 'blocked': false },
+ * { 'name': 'fred', 'age': 40, 'blocked': true },
+ * { 'name': 'pebbles', 'age': 1, 'blocked': false }
+ * ];
+ *
+ * _.find(characters, function(chr) {
+ * return chr.age < 40;
+ * });
+ * // => { 'name': 'barney', 'age': 36, 'blocked': false }
+ *
+ * // using "_.where" callback shorthand
+ * _.find(characters, { 'age': 1 });
+ * // => { 'name': 'pebbles', 'age': 1, 'blocked': false }
+ *
+ * // using "_.pluck" callback shorthand
+ * _.find(characters, 'blocked');
+ * // => { 'name': 'fred', 'age': 40, 'blocked': true }
+ */
+ function find(collection, callback, thisArg) {
+ callback = lodash.createCallback(callback, thisArg, 3);
+
+ if (isArray(collection)) {
+ var index = -1,
+ length = collection.length;
+
+ while (++index < length) {
+ var value = collection[index];
+ if (callback(value, index, collection)) {
+ return value;
+ }
+ }
+ } else {
+ var result;
+ baseEach(collection, function(value, index, collection) {
+ if (callback(value, index, collection)) {
+ result = value;
+ return false;
+ }
+ });
+ return result;
+ }
+ }
+
+ /**
+ * This method is like `_.find` except that it iterates over elements
+ * of a `collection` from right to left.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the found element, else `undefined`.
+ * @example
+ *
+ * _.findLast([1, 2, 3, 4], function(num) {
+ * return num % 2 == 1;
+ * });
+ * // => 3
+ */
+ function findLast(collection, callback, thisArg) {
+ var result;
+ callback = lodash.createCallback(callback, thisArg, 3);
+ forEachRight(collection, function(value, index, collection) {
+ if (callback(value, index, collection)) {
+ result = value;
+ return false;
+ }
+ });
+ return result;
+ }
+
+ /**
+ * Iterates over elements of a collection, executing the callback for each
+ * element. The callback is bound to `thisArg` and invoked with three arguments;
+ * (value, index|key, collection). Callbacks may exit iteration early by
+ * explicitly returning `false`.
+ *
+ * Note: As with other "Collections" methods, objects with a `length` property
+ * are iterated like arrays. To avoid this behavior `_.forIn` or `_.forOwn`
+ * may be used for object iteration.
+ *
+ * @static
+ * @memberOf _
+ * @alias each
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array|Object|string} Returns `collection`.
+ * @example
+ *
+ * _([1, 2, 3]).forEach(function(num) { console.log(num); }).join(',');
+ * // => logs each number and returns '1,2,3'
+ *
+ * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { console.log(num); });
+ * // => logs each number and returns the object (property order is not guaranteed across environments)
+ */
+ function forEach(collection, callback, thisArg) {
+ if (callback && typeof thisArg == 'undefined' && isArray(collection)) {
+ var index = -1,
+ length = collection.length;
+
+ while (++index < length) {
+ if (callback(collection[index], index, collection) === false) {
+ break;
+ }
+ }
+ } else {
+ baseEach(collection, callback, thisArg);
+ }
+ return collection;
+ }
+
+ /**
+ * This method is like `_.forEach` except that it iterates over elements
+ * of a `collection` from right to left.
+ *
+ * @static
+ * @memberOf _
+ * @alias eachRight
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array|Object|string} Returns `collection`.
+ * @example
+ *
+ * _([1, 2, 3]).forEachRight(function(num) { console.log(num); }).join(',');
+ * // => logs each number from right to left and returns '3,2,1'
+ */
+ function forEachRight(collection, callback, thisArg) {
+ var iterable = collection,
+ length = collection ? collection.length : 0;
+
+ callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
+ if (isArray(collection)) {
+ while (length--) {
+ if (callback(collection[length], length, collection) === false) {
+ break;
+ }
+ }
+ } else {
+ if (typeof length != 'number') {
+ var props = keys(collection);
+ length = props.length;
+ } else if (support.unindexedChars && isString(collection)) {
+ iterable = collection.split('');
+ }
+ baseEach(collection, function(value, key, collection) {
+ key = props ? props[--length] : --length;
+ return callback(iterable[key], key, collection);
+ });
+ }
+ return collection;
+ }
+
+ /**
+ * Creates an object composed of keys generated from the results of running
+ * each element of a collection through the callback. The corresponding value
+ * of each key is an array of the elements responsible for generating the key.
+ * The callback is bound to `thisArg` and invoked with three arguments;
+ * (value, index|key, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns the composed aggregate object.
+ * @example
+ *
+ * _.groupBy([4.2, 6.1, 6.4], function(num) { return Math.floor(num); });
+ * // => { '4': [4.2], '6': [6.1, 6.4] }
+ *
+ * _.groupBy([4.2, 6.1, 6.4], function(num) { return this.floor(num); }, Math);
+ * // => { '4': [4.2], '6': [6.1, 6.4] }
+ *
+ * // using "_.pluck" callback shorthand
+ * _.groupBy(['one', 'two', 'three'], 'length');
+ * // => { '3': ['one', 'two'], '5': ['three'] }
+ */
+ var groupBy = createAggregator(function(result, value, key) {
+ (hasOwnProperty.call(result, key) ? result[key] : result[key] = []).push(value);
+ });
+
+ /**
+ * Creates an object composed of keys generated from the results of running
+ * each element of the collection through the given callback. The corresponding
+ * value of each key is the last element responsible for generating the key.
+ * The callback is bound to `thisArg` and invoked with three arguments;
+ * (value, index|key, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns the composed aggregate object.
+ * @example
+ *
+ * var keys = [
+ * { 'dir': 'left', 'code': 97 },
+ * { 'dir': 'right', 'code': 100 }
+ * ];
+ *
+ * _.indexBy(keys, 'dir');
+ * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } }
+ *
+ * _.indexBy(keys, function(key) { return String.fromCharCode(key.code); });
+ * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
+ *
+ * _.indexBy(characters, function(key) { this.fromCharCode(key.code); }, String);
+ * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
+ */
+ var indexBy = createAggregator(function(result, value, key) {
+ result[key] = value;
+ });
+
+ /**
+ * Invokes the method named by `methodName` on each element in the `collection`
+ * returning an array of the results of each invoked method. Additional arguments
+ * will be provided to each invoked method. If `methodName` is a function it
+ * will be invoked for, and `this` bound to, each element in the `collection`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|string} methodName The name of the method to invoke or
+ * the function invoked per iteration.
+ * @param {...*} [arg] Arguments to invoke the method with.
+ * @returns {Array} Returns a new array of the results of each invoked method.
+ * @example
+ *
+ * _.invoke([[5, 1, 7], [3, 2, 1]], 'sort');
+ * // => [[1, 5, 7], [1, 2, 3]]
+ *
+ * _.invoke([123, 456], String.prototype.split, '');
+ * // => [['1', '2', '3'], ['4', '5', '6']]
+ */
+ function invoke(collection, methodName) {
+ var args = slice(arguments, 2),
+ index = -1,
+ isFunc = typeof methodName == 'function',
+ length = collection ? collection.length : 0,
+ result = Array(typeof length == 'number' ? length : 0);
+
+ forEach(collection, function(value) {
+ result[++index] = (isFunc ? methodName : value[methodName]).apply(value, args);
+ });
+ return result;
+ }
+
+ /**
+ * Creates an array of values by running each element in the collection
+ * through the callback. The callback is bound to `thisArg` and invoked with
+ * three arguments; (value, index|key, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @alias collect
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a new array of the results of each `callback` execution.
+ * @example
+ *
+ * _.map([1, 2, 3], function(num) { return num * 3; });
+ * // => [3, 6, 9]
+ *
+ * _.map({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { return num * 3; });
+ * // => [3, 6, 9] (property order is not guaranteed across environments)
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.map(characters, 'name');
+ * // => ['barney', 'fred']
+ */
+ function map(collection, callback, thisArg) {
+ var index = -1,
+ length = collection ? collection.length : 0,
+ result = Array(typeof length == 'number' ? length : 0);
+
+ callback = lodash.createCallback(callback, thisArg, 3);
+ if (isArray(collection)) {
+ while (++index < length) {
+ result[index] = callback(collection[index], index, collection);
+ }
+ } else {
+ baseEach(collection, function(value, key, collection) {
+ result[++index] = callback(value, key, collection);
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Retrieves the maximum value of a collection. If the collection is empty or
+ * falsey `-Infinity` is returned. If a callback is provided it will be executed
+ * for each value in the collection to generate the criterion by which the value
+ * is ranked. The callback is bound to `thisArg` and invoked with three
+ * arguments; (value, index, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the maximum value.
+ * @example
+ *
+ * _.max([4, 2, 8, 6]);
+ * // => 8
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 }
+ * ];
+ *
+ * _.max(characters, function(chr) { return chr.age; });
+ * // => { 'name': 'fred', 'age': 40 };
+ *
+ * // using "_.pluck" callback shorthand
+ * _.max(characters, 'age');
+ * // => { 'name': 'fred', 'age': 40 };
+ */
+ function max(collection, callback, thisArg) {
+ var computed = -Infinity,
+ result = computed;
+
+ // allows working with functions like `_.map` without using
+ // their `index` argument as a callback
+ if (typeof callback != 'function' && thisArg && thisArg[callback] === collection) {
+ callback = null;
+ }
+ if (callback == null && isArray(collection)) {
+ var index = -1,
+ length = collection.length;
+
+ while (++index < length) {
+ var value = collection[index];
+ if (value > result) {
+ result = value;
+ }
+ }
+ } else {
+ callback = (callback == null && isString(collection))
+ ? charAtCallback
+ : lodash.createCallback(callback, thisArg, 3);
+
+ baseEach(collection, function(value, index, collection) {
+ var current = callback(value, index, collection);
+ if (current > computed) {
+ computed = current;
+ result = value;
+ }
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Retrieves the minimum value of a collection. If the collection is empty or
+ * falsey `Infinity` is returned. If a callback is provided it will be executed
+ * for each value in the collection to generate the criterion by which the value
+ * is ranked. The callback is bound to `thisArg` and invoked with three
+ * arguments; (value, index, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the minimum value.
+ * @example
+ *
+ * _.min([4, 2, 8, 6]);
+ * // => 2
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 }
+ * ];
+ *
+ * _.min(characters, function(chr) { return chr.age; });
+ * // => { 'name': 'barney', 'age': 36 };
+ *
+ * // using "_.pluck" callback shorthand
+ * _.min(characters, 'age');
+ * // => { 'name': 'barney', 'age': 36 };
+ */
+ function min(collection, callback, thisArg) {
+ var computed = Infinity,
+ result = computed;
+
+ // allows working with functions like `_.map` without using
+ // their `index` argument as a callback
+ if (typeof callback != 'function' && thisArg && thisArg[callback] === collection) {
+ callback = null;
+ }
+ if (callback == null && isArray(collection)) {
+ var index = -1,
+ length = collection.length;
+
+ while (++index < length) {
+ var value = collection[index];
+ if (value < result) {
+ result = value;
+ }
+ }
+ } else {
+ callback = (callback == null && isString(collection))
+ ? charAtCallback
+ : lodash.createCallback(callback, thisArg, 3);
+
+ baseEach(collection, function(value, index, collection) {
+ var current = callback(value, index, collection);
+ if (current < computed) {
+ computed = current;
+ result = value;
+ }
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Retrieves the value of a specified property from all elements in the collection.
+ *
+ * @static
+ * @memberOf _
+ * @type Function
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {string} property The name of the property to pluck.
+ * @returns {Array} Returns a new array of property values.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 }
+ * ];
+ *
+ * _.pluck(characters, 'name');
+ * // => ['barney', 'fred']
+ */
+ var pluck = map;
+
+ /**
+ * Reduces a collection to a value which is the accumulated result of running
+ * each element in the collection through the callback, where each successive
+ * callback execution consumes the return value of the previous execution. If
+ * `accumulator` is not provided the first element of the collection will be
+ * used as the initial `accumulator` value. The callback is bound to `thisArg`
+ * and invoked with four arguments; (accumulator, value, index|key, collection).
+ *
+ * @static
+ * @memberOf _
+ * @alias foldl, inject
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [accumulator] Initial value of the accumulator.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the accumulated value.
+ * @example
+ *
+ * var sum = _.reduce([1, 2, 3], function(sum, num) {
+ * return sum + num;
+ * });
+ * // => 6
+ *
+ * var mapped = _.reduce({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) {
+ * result[key] = num * 3;
+ * return result;
+ * }, {});
+ * // => { 'a': 3, 'b': 6, 'c': 9 }
+ */
+ function reduce(collection, callback, accumulator, thisArg) {
+ var noaccum = arguments.length < 3;
+ callback = lodash.createCallback(callback, thisArg, 4);
+
+ if (isArray(collection)) {
+ var index = -1,
+ length = collection.length;
+
+ if (noaccum) {
+ accumulator = collection[++index];
+ }
+ while (++index < length) {
+ accumulator = callback(accumulator, collection[index], index, collection);
+ }
+ } else {
+ baseEach(collection, function(value, index, collection) {
+ accumulator = noaccum
+ ? (noaccum = false, value)
+ : callback(accumulator, value, index, collection)
+ });
+ }
+ return accumulator;
+ }
+
+ /**
+ * This method is like `_.reduce` except that it iterates over elements
+ * of a `collection` from right to left.
+ *
+ * @static
+ * @memberOf _
+ * @alias foldr
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [accumulator] Initial value of the accumulator.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the accumulated value.
+ * @example
+ *
+ * var list = [[0, 1], [2, 3], [4, 5]];
+ * var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []);
+ * // => [4, 5, 2, 3, 0, 1]
+ */
+ function reduceRight(collection, callback, accumulator, thisArg) {
+ var noaccum = arguments.length < 3;
+ callback = lodash.createCallback(callback, thisArg, 4);
+ forEachRight(collection, function(value, index, collection) {
+ accumulator = noaccum
+ ? (noaccum = false, value)
+ : callback(accumulator, value, index, collection);
+ });
+ return accumulator;
+ }
+
+ /**
+ * The opposite of `_.filter` this method returns the elements of a
+ * collection that the callback does **not** return truey for.
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a new array of elements that failed the callback check.
+ * @example
+ *
+ * var odds = _.reject([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });
+ * // => [1, 3, 5]
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36, 'blocked': false },
+ * { 'name': 'fred', 'age': 40, 'blocked': true }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.reject(characters, 'blocked');
+ * // => [{ 'name': 'barney', 'age': 36, 'blocked': false }]
+ *
+ * // using "_.where" callback shorthand
+ * _.reject(characters, { 'age': 36 });
+ * // => [{ 'name': 'fred', 'age': 40, 'blocked': true }]
+ */
+ function reject(collection, callback, thisArg) {
+ callback = lodash.createCallback(callback, thisArg, 3);
+ return filter(collection, function(value, index, collection) {
+ return !callback(value, index, collection);
+ });
+ }
+
+ /**
+ * Retrieves a random element or `n` random elements from a collection.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to sample.
+ * @param {number} [n] The number of elements to sample.
+ * @param- {Object} [guard] Allows working with functions like `_.map`
+ * without using their `index` arguments as `n`.
+ * @returns {Array} Returns the random sample(s) of `collection`.
+ * @example
+ *
+ * _.sample([1, 2, 3, 4]);
+ * // => 2
+ *
+ * _.sample([1, 2, 3, 4], 2);
+ * // => [3, 1]
+ */
+ function sample(collection, n, guard) {
+ if (collection && typeof collection.length != 'number') {
+ collection = values(collection);
+ } else if (support.unindexedChars && isString(collection)) {
+ collection = collection.split('');
+ }
+ if (n == null || guard) {
+ return collection ? collection[baseRandom(0, collection.length - 1)] : undefined;
+ }
+ var result = shuffle(collection);
+ result.length = nativeMin(nativeMax(0, n), result.length);
+ return result;
+ }
+
+ /**
+ * Creates an array of shuffled values, using a version of the Fisher-Yates
+ * shuffle. See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to shuffle.
+ * @returns {Array} Returns a new shuffled collection.
+ * @example
+ *
+ * _.shuffle([1, 2, 3, 4, 5, 6]);
+ * // => [4, 1, 6, 3, 5, 2]
+ */
+ function shuffle(collection) {
+ var index = -1,
+ length = collection ? collection.length : 0,
+ result = Array(typeof length == 'number' ? length : 0);
+
+ forEach(collection, function(value) {
+ var rand = baseRandom(0, ++index);
+ result[index] = result[rand];
+ result[rand] = value;
+ });
+ return result;
+ }
+
+ /**
+ * Gets the size of the `collection` by returning `collection.length` for arrays
+ * and array-like objects or the number of own enumerable properties for objects.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to inspect.
+ * @returns {number} Returns `collection.length` or number of own enumerable properties.
+ * @example
+ *
+ * _.size([1, 2]);
+ * // => 2
+ *
+ * _.size({ 'one': 1, 'two': 2, 'three': 3 });
+ * // => 3
+ *
+ * _.size('pebbles');
+ * // => 7
+ */
+ function size(collection) {
+ var length = collection ? collection.length : 0;
+ return typeof length == 'number' ? length : keys(collection).length;
+ }
+
+ /**
+ * Checks if the callback returns a truey value for **any** element of a
+ * collection. The function returns as soon as it finds a passing value and
+ * does not iterate over the entire collection. The callback is bound to
+ * `thisArg` and invoked with three arguments; (value, index|key, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @alias any
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {boolean} Returns `true` if any element passed the callback check,
+ * else `false`.
+ * @example
+ *
+ * _.some([null, 0, 'yes', false], Boolean);
+ * // => true
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36, 'blocked': false },
+ * { 'name': 'fred', 'age': 40, 'blocked': true }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.some(characters, 'blocked');
+ * // => true
+ *
+ * // using "_.where" callback shorthand
+ * _.some(characters, { 'age': 1 });
+ * // => false
+ */
+ function some(collection, callback, thisArg) {
+ var result;
+ callback = lodash.createCallback(callback, thisArg, 3);
+
+ if (isArray(collection)) {
+ var index = -1,
+ length = collection.length;
+
+ while (++index < length) {
+ if ((result = callback(collection[index], index, collection))) {
+ break;
+ }
+ }
+ } else {
+ baseEach(collection, function(value, index, collection) {
+ return !(result = callback(value, index, collection));
+ });
+ }
+ return !!result;
+ }
+
+ /**
+ * Creates an array of elements, sorted in ascending order by the results of
+ * running each element in a collection through the callback. This method
+ * performs a stable sort, that is, it will preserve the original sort order
+ * of equal elements. The callback is bound to `thisArg` and invoked with
+ * three arguments; (value, index|key, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an array of property names is provided for `callback` the collection
+ * will be sorted by each property value.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Array|Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a new array of sorted elements.
+ * @example
+ *
+ * _.sortBy([1, 2, 3], function(num) { return Math.sin(num); });
+ * // => [3, 1, 2]
+ *
+ * _.sortBy([1, 2, 3], function(num) { return this.sin(num); }, Math);
+ * // => [3, 1, 2]
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 },
+ * { 'name': 'barney', 'age': 26 },
+ * { 'name': 'fred', 'age': 30 }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.map(_.sortBy(characters, 'age'), _.values);
+ * // => [['barney', 26], ['fred', 30], ['barney', 36], ['fred', 40]]
+ *
+ * // sorting by multiple properties
+ * _.map(_.sortBy(characters, ['name', 'age']), _.values);
+ * // = > [['barney', 26], ['barney', 36], ['fred', 30], ['fred', 40]]
+ */
+ function sortBy(collection, callback, thisArg) {
+ var index = -1,
+ isArr = isArray(callback),
+ length = collection ? collection.length : 0,
+ result = Array(typeof length == 'number' ? length : 0);
+
+ if (!isArr) {
+ callback = lodash.createCallback(callback, thisArg, 3);
+ }
+ forEach(collection, function(value, key, collection) {
+ var object = result[++index] = getObject();
+ if (isArr) {
+ object.criteria = map(callback, function(key) { return value[key]; });
+ } else {
+ (object.criteria = getArray())[0] = callback(value, key, collection);
+ }
+ object.index = index;
+ object.value = value;
+ });
+
+ length = result.length;
+ result.sort(compareAscending);
+ while (length--) {
+ var object = result[length];
+ result[length] = object.value;
+ if (!isArr) {
+ releaseArray(object.criteria);
+ }
+ releaseObject(object);
+ }
+ return result;
+ }
+
+ /**
+ * Converts the `collection` to an array.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to convert.
+ * @returns {Array} Returns the new converted array.
+ * @example
+ *
+ * (function() { return _.toArray(arguments).slice(1); })(1, 2, 3, 4);
+ * // => [2, 3, 4]
+ */
+ function toArray(collection) {
+ if (collection && typeof collection.length == 'number') {
+ return (support.unindexedChars && isString(collection))
+ ? collection.split('')
+ : slice(collection);
+ }
+ return values(collection);
+ }
+
+ /**
+ * Performs a deep comparison of each element in a `collection` to the given
+ * `properties` object, returning an array of all elements that have equivalent
+ * property values.
+ *
+ * @static
+ * @memberOf _
+ * @type Function
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Object} props The object of property values to filter by.
+ * @returns {Array} Returns a new array of elements that have the given properties.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36, 'pets': ['hoppy'] },
+ * { 'name': 'fred', 'age': 40, 'pets': ['baby puss', 'dino'] }
+ * ];
+ *
+ * _.where(characters, { 'age': 36 });
+ * // => [{ 'name': 'barney', 'age': 36, 'pets': ['hoppy'] }]
+ *
+ * _.where(characters, { 'pets': ['dino'] });
+ * // => [{ 'name': 'fred', 'age': 40, 'pets': ['baby puss', 'dino'] }]
+ */
+ var where = filter;
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates an array with all falsey values removed. The values `false`, `null`,
+ * `0`, `""`, `undefined`, and `NaN` are all falsey.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to compact.
+ * @returns {Array} Returns a new array of filtered values.
+ * @example
+ *
+ * _.compact([0, 1, false, 2, '', 3]);
+ * // => [1, 2, 3]
+ */
+ function compact(array) {
+ var index = -1,
+ length = array ? array.length : 0,
+ result = [];
+
+ while (++index < length) {
+ var value = array[index];
+ if (value) {
+ result.push(value);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Creates an array excluding all values of the provided arrays using strict
+ * equality for comparisons, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to process.
+ * @param {...Array} [values] The arrays of values to exclude.
+ * @returns {Array} Returns a new array of filtered values.
+ * @example
+ *
+ * _.difference([1, 2, 3, 4, 5], [5, 2, 10]);
+ * // => [1, 3, 4]
+ */
+ function difference(array) {
+ return baseDifference(array, baseFlatten(arguments, true, true, 1));
+ }
+
+ /**
+ * This method is like `_.find` except that it returns the index of the first
+ * element that passes the callback check, instead of the element itself.
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to search.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {number} Returns the index of the found element, else `-1`.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36, 'blocked': false },
+ * { 'name': 'fred', 'age': 40, 'blocked': true },
+ * { 'name': 'pebbles', 'age': 1, 'blocked': false }
+ * ];
+ *
+ * _.findIndex(characters, function(chr) {
+ * return chr.age < 20;
+ * });
+ * // => 2
+ *
+ * // using "_.where" callback shorthand
+ * _.findIndex(characters, { 'age': 36 });
+ * // => 0
+ *
+ * // using "_.pluck" callback shorthand
+ * _.findIndex(characters, 'blocked');
+ * // => 1
+ */
+ function findIndex(array, callback, thisArg) {
+ var index = -1,
+ length = array ? array.length : 0;
+
+ callback = lodash.createCallback(callback, thisArg, 3);
+ while (++index < length) {
+ if (callback(array[index], index, array)) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * This method is like `_.findIndex` except that it iterates over elements
+ * of a `collection` from right to left.
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to search.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {number} Returns the index of the found element, else `-1`.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36, 'blocked': true },
+ * { 'name': 'fred', 'age': 40, 'blocked': false },
+ * { 'name': 'pebbles', 'age': 1, 'blocked': true }
+ * ];
+ *
+ * _.findLastIndex(characters, function(chr) {
+ * return chr.age > 30;
+ * });
+ * // => 1
+ *
+ * // using "_.where" callback shorthand
+ * _.findLastIndex(characters, { 'age': 36 });
+ * // => 0
+ *
+ * // using "_.pluck" callback shorthand
+ * _.findLastIndex(characters, 'blocked');
+ * // => 2
+ */
+ function findLastIndex(array, callback, thisArg) {
+ var length = array ? array.length : 0;
+ callback = lodash.createCallback(callback, thisArg, 3);
+ while (length--) {
+ if (callback(array[length], length, array)) {
+ return length;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Gets the first element or first `n` elements of an array. If a callback
+ * is provided elements at the beginning of the array are returned as long
+ * as the callback returns truey. The callback is bound to `thisArg` and
+ * invoked with three arguments; (value, index, array).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @alias head, take
+ * @category Arrays
+ * @param {Array} array The array to query.
+ * @param {Function|Object|number|string} [callback] The function called
+ * per element or the number of elements to return. If a property name or
+ * object is provided it will be used to create a "_.pluck" or "_.where"
+ * style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the first element(s) of `array`.
+ * @example
+ *
+ * _.first([1, 2, 3]);
+ * // => 1
+ *
+ * _.first([1, 2, 3], 2);
+ * // => [1, 2]
+ *
+ * _.first([1, 2, 3], function(num) {
+ * return num < 3;
+ * });
+ * // => [1, 2]
+ *
+ * var characters = [
+ * { 'name': 'barney', 'blocked': true, 'employer': 'slate' },
+ * { 'name': 'fred', 'blocked': false, 'employer': 'slate' },
+ * { 'name': 'pebbles', 'blocked': true, 'employer': 'na' }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.first(characters, 'blocked');
+ * // => [{ 'name': 'barney', 'blocked': true, 'employer': 'slate' }]
+ *
+ * // using "_.where" callback shorthand
+ * _.pluck(_.first(characters, { 'employer': 'slate' }), 'name');
+ * // => ['barney', 'fred']
+ */
+ function first(array, callback, thisArg) {
+ var n = 0,
+ length = array ? array.length : 0;
+
+ if (typeof callback != 'number' && callback != null) {
+ var index = -1;
+ callback = lodash.createCallback(callback, thisArg, 3);
+ while (++index < length && callback(array[index], index, array)) {
+ n++;
+ }
+ } else {
+ n = callback;
+ if (n == null || thisArg) {
+ return array ? array[0] : undefined;
+ }
+ }
+ return slice(array, 0, nativeMin(nativeMax(0, n), length));
+ }
+
+ /**
+ * Flattens a nested array (the nesting can be to any depth). If `isShallow`
+ * is truey, the array will only be flattened a single level. If a callback
+ * is provided each element of the array is passed through the callback before
+ * flattening. The callback is bound to `thisArg` and invoked with three
+ * arguments; (value, index, array).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to flatten.
+ * @param {boolean} [isShallow=false] A flag to restrict flattening to a single level.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a new flattened array.
+ * @example
+ *
+ * _.flatten([1, [2], [3, [[4]]]]);
+ * // => [1, 2, 3, 4];
+ *
+ * _.flatten([1, [2], [3, [[4]]]], true);
+ * // => [1, 2, 3, [[4]]];
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 30, 'pets': ['hoppy'] },
+ * { 'name': 'fred', 'age': 40, 'pets': ['baby puss', 'dino'] }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.flatten(characters, 'pets');
+ * // => ['hoppy', 'baby puss', 'dino']
+ */
+ function flatten(array, isShallow, callback, thisArg) {
+ // juggle arguments
+ if (typeof isShallow != 'boolean' && isShallow != null) {
+ thisArg = callback;
+ callback = (typeof isShallow != 'function' && thisArg && thisArg[isShallow] === array) ? null : isShallow;
+ isShallow = false;
+ }
+ if (callback != null) {
+ array = map(array, callback, thisArg);
+ }
+ return baseFlatten(array, isShallow);
+ }
+
+ /**
+ * Gets the index at which the first occurrence of `value` is found using
+ * strict equality for comparisons, i.e. `===`. If the array is already sorted
+ * providing `true` for `fromIndex` will run a faster binary search.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to search.
+ * @param {*} value The value to search for.
+ * @param {boolean|number} [fromIndex=0] The index to search from or `true`
+ * to perform a binary search on a sorted array.
+ * @returns {number} Returns the index of the matched value or `-1`.
+ * @example
+ *
+ * _.indexOf([1, 2, 3, 1, 2, 3], 2);
+ * // => 1
+ *
+ * _.indexOf([1, 2, 3, 1, 2, 3], 2, 3);
+ * // => 4
+ *
+ * _.indexOf([1, 1, 2, 2, 3, 3], 2, true);
+ * // => 2
+ */
+ function indexOf(array, value, fromIndex) {
+ if (typeof fromIndex == 'number') {
+ var length = array ? array.length : 0;
+ fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex || 0);
+ } else if (fromIndex) {
+ var index = sortedIndex(array, value);
+ return array[index] === value ? index : -1;
+ }
+ return baseIndexOf(array, value, fromIndex);
+ }
+
+ /**
+ * Gets all but the last element or last `n` elements of an array. If a
+ * callback is provided elements at the end of the array are excluded from
+ * the result as long as the callback returns truey. The callback is bound
+ * to `thisArg` and invoked with three arguments; (value, index, array).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to query.
+ * @param {Function|Object|number|string} [callback=1] The function called
+ * per element or the number of elements to exclude. If a property name or
+ * object is provided it will be used to create a "_.pluck" or "_.where"
+ * style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a slice of `array`.
+ * @example
+ *
+ * _.initial([1, 2, 3]);
+ * // => [1, 2]
+ *
+ * _.initial([1, 2, 3], 2);
+ * // => [1]
+ *
+ * _.initial([1, 2, 3], function(num) {
+ * return num > 1;
+ * });
+ * // => [1]
+ *
+ * var characters = [
+ * { 'name': 'barney', 'blocked': false, 'employer': 'slate' },
+ * { 'name': 'fred', 'blocked': true, 'employer': 'slate' },
+ * { 'name': 'pebbles', 'blocked': true, 'employer': 'na' }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.initial(characters, 'blocked');
+ * // => [{ 'name': 'barney', 'blocked': false, 'employer': 'slate' }]
+ *
+ * // using "_.where" callback shorthand
+ * _.pluck(_.initial(characters, { 'employer': 'na' }), 'name');
+ * // => ['barney', 'fred']
+ */
+ function initial(array, callback, thisArg) {
+ var n = 0,
+ length = array ? array.length : 0;
+
+ if (typeof callback != 'number' && callback != null) {
+ var index = length;
+ callback = lodash.createCallback(callback, thisArg, 3);
+ while (index-- && callback(array[index], index, array)) {
+ n++;
+ }
+ } else {
+ n = (callback == null || thisArg) ? 1 : callback || n;
+ }
+ return slice(array, 0, nativeMin(nativeMax(0, length - n), length));
+ }
+
+ /**
+ * Creates an array of unique values present in all provided arrays using
+ * strict equality for comparisons, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {...Array} [array] The arrays to inspect.
+ * @returns {Array} Returns an array of shared values.
+ * @example
+ *
+ * _.intersection([1, 2, 3], [5, 2, 1, 4], [2, 1]);
+ * // => [1, 2]
+ */
+ function intersection() {
+ var args = [],
+ argsIndex = -1,
+ argsLength = arguments.length,
+ caches = getArray(),
+ indexOf = getIndexOf(),
+ trustIndexOf = indexOf === baseIndexOf,
+ seen = getArray();
+
+ while (++argsIndex < argsLength) {
+ var value = arguments[argsIndex];
+ if (isArray(value) || isArguments(value)) {
+ args.push(value);
+ caches.push(trustIndexOf && value.length >= largeArraySize &&
+ createCache(argsIndex ? args[argsIndex] : seen));
+ }
+ }
+ var array = args[0],
+ index = -1,
+ length = array ? array.length : 0,
+ result = [];
+
+ outer:
+ while (++index < length) {
+ var cache = caches[0];
+ value = array[index];
+
+ if ((cache ? cacheIndexOf(cache, value) : indexOf(seen, value)) < 0) {
+ argsIndex = argsLength;
+ (cache || seen).push(value);
+ while (--argsIndex) {
+ cache = caches[argsIndex];
+ if ((cache ? cacheIndexOf(cache, value) : indexOf(args[argsIndex], value)) < 0) {
+ continue outer;
+ }
+ }
+ result.push(value);
+ }
+ }
+ while (argsLength--) {
+ cache = caches[argsLength];
+ if (cache) {
+ releaseObject(cache);
+ }
+ }
+ releaseArray(caches);
+ releaseArray(seen);
+ return result;
+ }
+
+ /**
+ * Gets the last element or last `n` elements of an array. If a callback is
+ * provided elements at the end of the array are returned as long as the
+ * callback returns truey. The callback is bound to `thisArg` and invoked
+ * with three arguments; (value, index, array).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to query.
+ * @param {Function|Object|number|string} [callback] The function called
+ * per element or the number of elements to return. If a property name or
+ * object is provided it will be used to create a "_.pluck" or "_.where"
+ * style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the last element(s) of `array`.
+ * @example
+ *
+ * _.last([1, 2, 3]);
+ * // => 3
+ *
+ * _.last([1, 2, 3], 2);
+ * // => [2, 3]
+ *
+ * _.last([1, 2, 3], function(num) {
+ * return num > 1;
+ * });
+ * // => [2, 3]
+ *
+ * var characters = [
+ * { 'name': 'barney', 'blocked': false, 'employer': 'slate' },
+ * { 'name': 'fred', 'blocked': true, 'employer': 'slate' },
+ * { 'name': 'pebbles', 'blocked': true, 'employer': 'na' }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.pluck(_.last(characters, 'blocked'), 'name');
+ * // => ['fred', 'pebbles']
+ *
+ * // using "_.where" callback shorthand
+ * _.last(characters, { 'employer': 'na' });
+ * // => [{ 'name': 'pebbles', 'blocked': true, 'employer': 'na' }]
+ */
+ function last(array, callback, thisArg) {
+ var n = 0,
+ length = array ? array.length : 0;
+
+ if (typeof callback != 'number' && callback != null) {
+ var index = length;
+ callback = lodash.createCallback(callback, thisArg, 3);
+ while (index-- && callback(array[index], index, array)) {
+ n++;
+ }
+ } else {
+ n = callback;
+ if (n == null || thisArg) {
+ return array ? array[length - 1] : undefined;
+ }
+ }
+ return slice(array, nativeMax(0, length - n));
+ }
+
+ /**
+ * Gets the index at which the last occurrence of `value` is found using strict
+ * equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used
+ * as the offset from the end of the collection.
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to search.
+ * @param {*} value The value to search for.
+ * @param {number} [fromIndex=array.length-1] The index to search from.
+ * @returns {number} Returns the index of the matched value or `-1`.
+ * @example
+ *
+ * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2);
+ * // => 4
+ *
+ * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2, 3);
+ * // => 1
+ */
+ function lastIndexOf(array, value, fromIndex) {
+ var index = array ? array.length : 0;
+ if (typeof fromIndex == 'number') {
+ index = (fromIndex < 0 ? nativeMax(0, index + fromIndex) : nativeMin(fromIndex, index - 1)) + 1;
+ }
+ while (index--) {
+ if (array[index] === value) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Removes all provided values from the given array using strict equality for
+ * comparisons, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to modify.
+ * @param {...*} [value] The values to remove.
+ * @returns {Array} Returns `array`.
+ * @example
+ *
+ * var array = [1, 2, 3, 1, 2, 3];
+ * _.pull(array, 2, 3);
+ * console.log(array);
+ * // => [1, 1]
+ */
+ function pull(array) {
+ var args = arguments,
+ argsIndex = 0,
+ argsLength = args.length,
+ length = array ? array.length : 0;
+
+ while (++argsIndex < argsLength) {
+ var index = -1,
+ value = args[argsIndex];
+ while (++index < length) {
+ if (array[index] === value) {
+ splice.call(array, index--, 1);
+ length--;
+ }
+ }
+ }
+ return array;
+ }
+
+ /**
+ * Creates an array of numbers (positive and/or negative) progressing from
+ * `start` up to but not including `end`. If `start` is less than `stop` a
+ * zero-length range is created unless a negative `step` is specified.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {number} [start=0] The start of the range.
+ * @param {number} end The end of the range.
+ * @param {number} [step=1] The value to increment or decrement by.
+ * @returns {Array} Returns a new range array.
+ * @example
+ *
+ * _.range(4);
+ * // => [0, 1, 2, 3]
+ *
+ * _.range(1, 5);
+ * // => [1, 2, 3, 4]
+ *
+ * _.range(0, 20, 5);
+ * // => [0, 5, 10, 15]
+ *
+ * _.range(0, -4, -1);
+ * // => [0, -1, -2, -3]
+ *
+ * _.range(1, 4, 0);
+ * // => [1, 1, 1]
+ *
+ * _.range(0);
+ * // => []
+ */
+ function range(start, end, step) {
+ start = +start || 0;
+ step = typeof step == 'number' ? step : (+step || 1);
+
+ if (end == null) {
+ end = start;
+ start = 0;
+ }
+ // use `Array(length)` so engines like Chakra and V8 avoid slower modes
+ // http://youtu.be/XAqIpGU8ZZk#t=17m25s
+ var index = -1,
+ length = nativeMax(0, ceil((end - start) / (step || 1))),
+ result = Array(length);
+
+ while (++index < length) {
+ result[index] = start;
+ start += step;
+ }
+ return result;
+ }
+
+ /**
+ * Removes all elements from an array that the callback returns truey for
+ * and returns an array of removed elements. The callback is bound to `thisArg`
+ * and invoked with three arguments; (value, index, array).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to modify.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a new array of removed elements.
+ * @example
+ *
+ * var array = [1, 2, 3, 4, 5, 6];
+ * var evens = _.remove(array, function(num) { return num % 2 == 0; });
+ *
+ * console.log(array);
+ * // => [1, 3, 5]
+ *
+ * console.log(evens);
+ * // => [2, 4, 6]
+ */
+ function remove(array, callback, thisArg) {
+ var index = -1,
+ length = array ? array.length : 0,
+ result = [];
+
+ callback = lodash.createCallback(callback, thisArg, 3);
+ while (++index < length) {
+ var value = array[index];
+ if (callback(value, index, array)) {
+ result.push(value);
+ splice.call(array, index--, 1);
+ length--;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * The opposite of `_.initial` this method gets all but the first element or
+ * first `n` elements of an array. If a callback function is provided elements
+ * at the beginning of the array are excluded from the result as long as the
+ * callback returns truey. The callback is bound to `thisArg` and invoked
+ * with three arguments; (value, index, array).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @alias drop, tail
+ * @category Arrays
+ * @param {Array} array The array to query.
+ * @param {Function|Object|number|string} [callback=1] The function called
+ * per element or the number of elements to exclude. If a property name or
+ * object is provided it will be used to create a "_.pluck" or "_.where"
+ * style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a slice of `array`.
+ * @example
+ *
+ * _.rest([1, 2, 3]);
+ * // => [2, 3]
+ *
+ * _.rest([1, 2, 3], 2);
+ * // => [3]
+ *
+ * _.rest([1, 2, 3], function(num) {
+ * return num < 3;
+ * });
+ * // => [3]
+ *
+ * var characters = [
+ * { 'name': 'barney', 'blocked': true, 'employer': 'slate' },
+ * { 'name': 'fred', 'blocked': false, 'employer': 'slate' },
+ * { 'name': 'pebbles', 'blocked': true, 'employer': 'na' }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.pluck(_.rest(characters, 'blocked'), 'name');
+ * // => ['fred', 'pebbles']
+ *
+ * // using "_.where" callback shorthand
+ * _.rest(characters, { 'employer': 'slate' });
+ * // => [{ 'name': 'pebbles', 'blocked': true, 'employer': 'na' }]
+ */
+ function rest(array, callback, thisArg) {
+ if (typeof callback != 'number' && callback != null) {
+ var n = 0,
+ index = -1,
+ length = array ? array.length : 0;
+
+ callback = lodash.createCallback(callback, thisArg, 3);
+ while (++index < length && callback(array[index], index, array)) {
+ n++;
+ }
+ } else {
+ n = (callback == null || thisArg) ? 1 : nativeMax(0, callback);
+ }
+ return slice(array, n);
+ }
+
+ /**
+ * Uses a binary search to determine the smallest index at which a value
+ * should be inserted into a given sorted array in order to maintain the sort
+ * order of the array. If a callback is provided it will be executed for
+ * `value` and each element of `array` to compute their sort ranking. The
+ * callback is bound to `thisArg` and invoked with one argument; (value).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to inspect.
+ * @param {*} value The value to evaluate.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {number} Returns the index at which `value` should be inserted
+ * into `array`.
+ * @example
+ *
+ * _.sortedIndex([20, 30, 50], 40);
+ * // => 2
+ *
+ * // using "_.pluck" callback shorthand
+ * _.sortedIndex([{ 'x': 20 }, { 'x': 30 }, { 'x': 50 }], { 'x': 40 }, 'x');
+ * // => 2
+ *
+ * var dict = {
+ * 'wordToNumber': { 'twenty': 20, 'thirty': 30, 'fourty': 40, 'fifty': 50 }
+ * };
+ *
+ * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) {
+ * return dict.wordToNumber[word];
+ * });
+ * // => 2
+ *
+ * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) {
+ * return this.wordToNumber[word];
+ * }, dict);
+ * // => 2
+ */
+ function sortedIndex(array, value, callback, thisArg) {
+ var low = 0,
+ high = array ? array.length : low;
+
+ // explicitly reference `identity` for better inlining in Firefox
+ callback = callback ? lodash.createCallback(callback, thisArg, 1) : identity;
+ value = callback(value);
+
+ while (low < high) {
+ var mid = (low + high) >>> 1;
+ (callback(array[mid]) < value)
+ ? low = mid + 1
+ : high = mid;
+ }
+ return low;
+ }
+
+ /**
+ * Creates an array of unique values, in order, of the provided arrays using
+ * strict equality for comparisons, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {...Array} [array] The arrays to inspect.
+ * @returns {Array} Returns an array of combined values.
+ * @example
+ *
+ * _.union([1, 2, 3], [5, 2, 1, 4], [2, 1]);
+ * // => [1, 2, 3, 5, 4]
+ */
+ function union() {
+ return baseUniq(baseFlatten(arguments, true, true));
+ }
+
+ /**
+ * Creates a duplicate-value-free version of an array using strict equality
+ * for comparisons, i.e. `===`. If the array is sorted, providing
+ * `true` for `isSorted` will use a faster algorithm. If a callback is provided
+ * each element of `array` is passed through the callback before uniqueness
+ * is computed. The callback is bound to `thisArg` and invoked with three
+ * arguments; (value, index, array).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @alias unique
+ * @category Arrays
+ * @param {Array} array The array to process.
+ * @param {boolean} [isSorted=false] A flag to indicate that `array` is sorted.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a duplicate-value-free array.
+ * @example
+ *
+ * _.uniq([1, 2, 1, 3, 1]);
+ * // => [1, 2, 3]
+ *
+ * _.uniq([1, 1, 2, 2, 3], true);
+ * // => [1, 2, 3]
+ *
+ * _.uniq(['A', 'b', 'C', 'a', 'B', 'c'], function(letter) { return letter.toLowerCase(); });
+ * // => ['A', 'b', 'C']
+ *
+ * _.uniq([1, 2.5, 3, 1.5, 2, 3.5], function(num) { return this.floor(num); }, Math);
+ * // => [1, 2.5, 3]
+ *
+ * // using "_.pluck" callback shorthand
+ * _.uniq([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
+ * // => [{ 'x': 1 }, { 'x': 2 }]
+ */
+ function uniq(array, isSorted, callback, thisArg) {
+ // juggle arguments
+ if (typeof isSorted != 'boolean' && isSorted != null) {
+ thisArg = callback;
+ callback = (typeof isSorted != 'function' && thisArg && thisArg[isSorted] === array) ? null : isSorted;
+ isSorted = false;
+ }
+ if (callback != null) {
+ callback = lodash.createCallback(callback, thisArg, 3);
+ }
+ return baseUniq(array, isSorted, callback);
+ }
+
+ /**
+ * Creates an array excluding all provided values using strict equality for
+ * comparisons, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to filter.
+ * @param {...*} [value] The values to exclude.
+ * @returns {Array} Returns a new array of filtered values.
+ * @example
+ *
+ * _.without([1, 2, 1, 0, 3, 1, 4], 0, 1);
+ * // => [2, 3, 4]
+ */
+ function without(array) {
+ return baseDifference(array, slice(arguments, 1));
+ }
+
+ /**
+ * Creates an array that is the symmetric difference of the provided arrays.
+ * See http://en.wikipedia.org/wiki/Symmetric_difference.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {...Array} [array] The arrays to inspect.
+ * @returns {Array} Returns an array of values.
+ * @example
+ *
+ * _.xor([1, 2, 3], [5, 2, 1, 4]);
+ * // => [3, 5, 4]
+ *
+ * _.xor([1, 2, 5], [2, 3, 5], [3, 4, 5]);
+ * // => [1, 4, 5]
+ */
+ function xor() {
+ var index = -1,
+ length = arguments.length;
+
+ while (++index < length) {
+ var array = arguments[index];
+ if (isArray(array) || isArguments(array)) {
+ var result = result
+ ? baseUniq(baseDifference(result, array).concat(baseDifference(array, result)))
+ : array;
+ }
+ }
+ return result || [];
+ }
+
+ /**
+ * Creates an array of grouped elements, the first of which contains the first
+ * elements of the given arrays, the second of which contains the second
+ * elements of the given arrays, and so on.
+ *
+ * @static
+ * @memberOf _
+ * @alias unzip
+ * @category Arrays
+ * @param {...Array} [array] Arrays to process.
+ * @returns {Array} Returns a new array of grouped elements.
+ * @example
+ *
+ * _.zip(['fred', 'barney'], [30, 40], [true, false]);
+ * // => [['fred', 30, true], ['barney', 40, false]]
+ */
+ function zip() {
+ var array = arguments.length > 1 ? arguments : arguments[0],
+ index = -1,
+ length = array ? max(pluck(array, 'length')) : 0,
+ result = Array(length < 0 ? 0 : length);
+
+ while (++index < length) {
+ result[index] = pluck(array, index);
+ }
+ return result;
+ }
+
+ /**
+ * Creates an object composed from arrays of `keys` and `values`. Provide
+ * either a single two dimensional array, i.e. `[[key1, value1], [key2, value2]]`
+ * or two arrays, one of `keys` and one of corresponding `values`.
+ *
+ * @static
+ * @memberOf _
+ * @alias object
+ * @category Arrays
+ * @param {Array} keys The array of keys.
+ * @param {Array} [values=[]] The array of values.
+ * @returns {Object} Returns an object composed of the given keys and
+ * corresponding values.
+ * @example
+ *
+ * _.zipObject(['fred', 'barney'], [30, 40]);
+ * // => { 'fred': 30, 'barney': 40 }
+ */
+ function zipObject(keys, values) {
+ var index = -1,
+ length = keys ? keys.length : 0,
+ result = {};
+
+ if (!values && length && !isArray(keys[0])) {
+ values = [];
+ }
+ while (++index < length) {
+ var key = keys[index];
+ if (values) {
+ result[key] = values[index];
+ } else if (key) {
+ result[key[0]] = key[1];
+ }
+ }
+ return result;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates a function that executes `func`, with the `this` binding and
+ * arguments of the created function, only after being called `n` times.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {number} n The number of times the function must be called before
+ * `func` is executed.
+ * @param {Function} func The function to restrict.
+ * @returns {Function} Returns the new restricted function.
+ * @example
+ *
+ * var saves = ['profile', 'settings'];
+ *
+ * var done = _.after(saves.length, function() {
+ * console.log('Done saving!');
+ * });
+ *
+ * _.forEach(saves, function(type) {
+ * asyncSave({ 'type': type, 'complete': done });
+ * });
+ * // => logs 'Done saving!', after all saves have completed
+ */
+ function after(n, func) {
+ if (!isFunction(func)) {
+ throw new TypeError;
+ }
+ return function() {
+ if (--n < 1) {
+ return func.apply(this, arguments);
+ }
+ };
+ }
+
+ /**
+ * Creates a function that, when called, invokes `func` with the `this`
+ * binding of `thisArg` and prepends any additional `bind` arguments to those
+ * provided to the bound function.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to bind.
+ * @param {*} [thisArg] The `this` binding of `func`.
+ * @param {...*} [arg] Arguments to be partially applied.
+ * @returns {Function} Returns the new bound function.
+ * @example
+ *
+ * var func = function(greeting) {
+ * return greeting + ' ' + this.name;
+ * };
+ *
+ * func = _.bind(func, { 'name': 'fred' }, 'hi');
+ * func();
+ * // => 'hi fred'
+ */
+ function bind(func, thisArg) {
+ return arguments.length > 2
+ ? createWrapper(func, 17, slice(arguments, 2), null, thisArg)
+ : createWrapper(func, 1, null, null, thisArg);
+ }
+
+ /**
+ * Binds methods of an object to the object itself, overwriting the existing
+ * method. Method names may be specified as individual arguments or as arrays
+ * of method names. If no method names are provided all the function properties
+ * of `object` will be bound.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Object} object The object to bind and assign the bound methods to.
+ * @param {...string} [methodName] The object method names to
+ * bind, specified as individual method names or arrays of method names.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * var view = {
+ * 'label': 'docs',
+ * 'onClick': function() { console.log('clicked ' + this.label); }
+ * };
+ *
+ * _.bindAll(view);
+ * jQuery('#docs').on('click', view.onClick);
+ * // => logs 'clicked docs', when the button is clicked
+ */
+ function bindAll(object) {
+ var funcs = arguments.length > 1 ? baseFlatten(arguments, true, false, 1) : functions(object),
+ index = -1,
+ length = funcs.length;
+
+ while (++index < length) {
+ var key = funcs[index];
+ object[key] = createWrapper(object[key], 1, null, null, object);
+ }
+ return object;
+ }
+
+ /**
+ * Creates a function that, when called, invokes the method at `object[key]`
+ * and prepends any additional `bindKey` arguments to those provided to the bound
+ * function. This method differs from `_.bind` by allowing bound functions to
+ * reference methods that will be redefined or don't yet exist.
+ * See http://michaux.ca/articles/lazy-function-definition-pattern.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Object} object The object the method belongs to.
+ * @param {string} key The key of the method.
+ * @param {...*} [arg] Arguments to be partially applied.
+ * @returns {Function} Returns the new bound function.
+ * @example
+ *
+ * var object = {
+ * 'name': 'fred',
+ * 'greet': function(greeting) {
+ * return greeting + ' ' + this.name;
+ * }
+ * };
+ *
+ * var func = _.bindKey(object, 'greet', 'hi');
+ * func();
+ * // => 'hi fred'
+ *
+ * object.greet = function(greeting) {
+ * return greeting + 'ya ' + this.name + '!';
+ * };
+ *
+ * func();
+ * // => 'hiya fred!'
+ */
+ function bindKey(object, key) {
+ return arguments.length > 2
+ ? createWrapper(key, 19, slice(arguments, 2), null, object)
+ : createWrapper(key, 3, null, null, object);
+ }
+
+ /**
+ * Creates a function that is the composition of the provided functions,
+ * where each function consumes the return value of the function that follows.
+ * For example, composing the functions `f()`, `g()`, and `h()` produces `f(g(h()))`.
+ * Each function is executed with the `this` binding of the composed function.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {...Function} [func] Functions to compose.
+ * @returns {Function} Returns the new composed function.
+ * @example
+ *
+ * var realNameMap = {
+ * 'pebbles': 'penelope'
+ * };
+ *
+ * var format = function(name) {
+ * name = realNameMap[name.toLowerCase()] || name;
+ * return name.charAt(0).toUpperCase() + name.slice(1).toLowerCase();
+ * };
+ *
+ * var greet = function(formatted) {
+ * return 'Hiya ' + formatted + '!';
+ * };
+ *
+ * var welcome = _.compose(greet, format);
+ * welcome('pebbles');
+ * // => 'Hiya Penelope!'
+ */
+ function compose() {
+ var funcs = arguments,
+ length = funcs.length;
+
+ while (length--) {
+ if (!isFunction(funcs[length])) {
+ throw new TypeError;
+ }
+ }
+ return function() {
+ var args = arguments,
+ length = funcs.length;
+
+ while (length--) {
+ args = [funcs[length].apply(this, args)];
+ }
+ return args[0];
+ };
+ }
+
+ /**
+ * Creates a function which accepts one or more arguments of `func` that when
+ * invoked either executes `func` returning its result, if all `func` arguments
+ * have been provided, or returns a function that accepts one or more of the
+ * remaining `func` arguments, and so on. The arity of `func` can be specified
+ * if `func.length` is not sufficient.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to curry.
+ * @param {number} [arity=func.length] The arity of `func`.
+ * @returns {Function} Returns the new curried function.
+ * @example
+ *
+ * var curried = _.curry(function(a, b, c) {
+ * console.log(a + b + c);
+ * });
+ *
+ * curried(1)(2)(3);
+ * // => 6
+ *
+ * curried(1, 2)(3);
+ * // => 6
+ *
+ * curried(1, 2, 3);
+ * // => 6
+ */
+ function curry(func, arity) {
+ arity = typeof arity == 'number' ? arity : (+arity || func.length);
+ return createWrapper(func, 4, null, null, null, arity);
+ }
+
+ /**
+ * Creates a function that will delay the execution of `func` until after
+ * `wait` milliseconds have elapsed since the last time it was invoked.
+ * Provide an options object to indicate that `func` should be invoked on
+ * the leading and/or trailing edge of the `wait` timeout. Subsequent calls
+ * to the debounced function will return the result of the last `func` call.
+ *
+ * Note: If `leading` and `trailing` options are `true` `func` will be called
+ * on the trailing edge of the timeout only if the the debounced function is
+ * invoked more than once during the `wait` timeout.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to debounce.
+ * @param {number} wait The number of milliseconds to delay.
+ * @param {Object} [options] The options object.
+ * @param {boolean} [options.leading=false] Specify execution on the leading edge of the timeout.
+ * @param {number} [options.maxWait] The maximum time `func` is allowed to be delayed before it's called.
+ * @param {boolean} [options.trailing=true] Specify execution on the trailing edge of the timeout.
+ * @returns {Function} Returns the new debounced function.
+ * @example
+ *
+ * // avoid costly calculations while the window size is in flux
+ * var lazyLayout = _.debounce(calculateLayout, 150);
+ * jQuery(window).on('resize', lazyLayout);
+ *
+ * // execute `sendMail` when the click event is fired, debouncing subsequent calls
+ * jQuery('#postbox').on('click', _.debounce(sendMail, 300, {
+ * 'leading': true,
+ * 'trailing': false
+ * });
+ *
+ * // ensure `batchLog` is executed once after 1 second of debounced calls
+ * var source = new EventSource('/stream');
+ * source.addEventListener('message', _.debounce(batchLog, 250, {
+ * 'maxWait': 1000
+ * }, false);
+ */
+ function debounce(func, wait, options) {
+ var args,
+ maxTimeoutId,
+ result,
+ stamp,
+ thisArg,
+ timeoutId,
+ trailingCall,
+ lastCalled = 0,
+ maxWait = false,
+ trailing = true;
+
+ if (!isFunction(func)) {
+ throw new TypeError;
+ }
+ wait = nativeMax(0, wait) || 0;
+ if (options === true) {
+ var leading = true;
+ trailing = false;
+ } else if (isObject(options)) {
+ leading = options.leading;
+ maxWait = 'maxWait' in options && (nativeMax(wait, options.maxWait) || 0);
+ trailing = 'trailing' in options ? options.trailing : trailing;
+ }
+ var delayed = function() {
+ var remaining = wait - (now() - stamp);
+ if (remaining <= 0) {
+ if (maxTimeoutId) {
+ clearTimeout(maxTimeoutId);
+ }
+ var isCalled = trailingCall;
+ maxTimeoutId = timeoutId = trailingCall = undefined;
+ if (isCalled) {
+ lastCalled = now();
+ result = func.apply(thisArg, args);
+ if (!timeoutId && !maxTimeoutId) {
+ args = thisArg = null;
+ }
+ }
+ } else {
+ timeoutId = setTimeout(delayed, remaining);
+ }
+ };
+
+ var maxDelayed = function() {
+ if (timeoutId) {
+ clearTimeout(timeoutId);
+ }
+ maxTimeoutId = timeoutId = trailingCall = undefined;
+ if (trailing || (maxWait !== wait)) {
+ lastCalled = now();
+ result = func.apply(thisArg, args);
+ if (!timeoutId && !maxTimeoutId) {
+ args = thisArg = null;
+ }
+ }
+ };
+
+ return function() {
+ args = arguments;
+ stamp = now();
+ thisArg = this;
+ trailingCall = trailing && (timeoutId || !leading);
+
+ if (maxWait === false) {
+ var leadingCall = leading && !timeoutId;
+ } else {
+ if (!maxTimeoutId && !leading) {
+ lastCalled = stamp;
+ }
+ var remaining = maxWait - (stamp - lastCalled),
+ isCalled = remaining <= 0;
+
+ if (isCalled) {
+ if (maxTimeoutId) {
+ maxTimeoutId = clearTimeout(maxTimeoutId);
+ }
+ lastCalled = stamp;
+ result = func.apply(thisArg, args);
+ }
+ else if (!maxTimeoutId) {
+ maxTimeoutId = setTimeout(maxDelayed, remaining);
+ }
+ }
+ if (isCalled && timeoutId) {
+ timeoutId = clearTimeout(timeoutId);
+ }
+ else if (!timeoutId && wait !== maxWait) {
+ timeoutId = setTimeout(delayed, wait);
+ }
+ if (leadingCall) {
+ isCalled = true;
+ result = func.apply(thisArg, args);
+ }
+ if (isCalled && !timeoutId && !maxTimeoutId) {
+ args = thisArg = null;
+ }
+ return result;
+ };
+ }
+
+ /**
+ * Defers executing the `func` function until the current call stack has cleared.
+ * Additional arguments will be provided to `func` when it is invoked.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to defer.
+ * @param {...*} [arg] Arguments to invoke the function with.
+ * @returns {number} Returns the timer id.
+ * @example
+ *
+ * _.defer(function(text) { console.log(text); }, 'deferred');
+ * // logs 'deferred' after one or more milliseconds
+ */
+ function defer(func) {
+ if (!isFunction(func)) {
+ throw new TypeError;
+ }
+ var args = slice(arguments, 1);
+ return setTimeout(function() { func.apply(undefined, args); }, 1);
+ }
+
+ /**
+ * Executes the `func` function after `wait` milliseconds. Additional arguments
+ * will be provided to `func` when it is invoked.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to delay.
+ * @param {number} wait The number of milliseconds to delay execution.
+ * @param {...*} [arg] Arguments to invoke the function with.
+ * @returns {number} Returns the timer id.
+ * @example
+ *
+ * _.delay(function(text) { console.log(text); }, 1000, 'later');
+ * // => logs 'later' after one second
+ */
+ function delay(func, wait) {
+ if (!isFunction(func)) {
+ throw new TypeError;
+ }
+ var args = slice(arguments, 2);
+ return setTimeout(function() { func.apply(undefined, args); }, wait);
+ }
+
+ /**
+ * Creates a function that memoizes the result of `func`. If `resolver` is
+ * provided it will be used to determine the cache key for storing the result
+ * based on the arguments provided to the memoized function. By default, the
+ * first argument provided to the memoized function is used as the cache key.
+ * The `func` is executed with the `this` binding of the memoized function.
+ * The result cache is exposed as the `cache` property on the memoized function.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to have its output memoized.
+ * @param {Function} [resolver] A function used to resolve the cache key.
+ * @returns {Function} Returns the new memoizing function.
+ * @example
+ *
+ * var fibonacci = _.memoize(function(n) {
+ * return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
+ * });
+ *
+ * fibonacci(9)
+ * // => 34
+ *
+ * var data = {
+ * 'fred': { 'name': 'fred', 'age': 40 },
+ * 'pebbles': { 'name': 'pebbles', 'age': 1 }
+ * };
+ *
+ * // modifying the result cache
+ * var get = _.memoize(function(name) { return data[name]; }, _.identity);
+ * get('pebbles');
+ * // => { 'name': 'pebbles', 'age': 1 }
+ *
+ * get.cache.pebbles.name = 'penelope';
+ * get('pebbles');
+ * // => { 'name': 'penelope', 'age': 1 }
+ */
+ function memoize(func, resolver) {
+ if (!isFunction(func)) {
+ throw new TypeError;
+ }
+ var memoized = function() {
+ var cache = memoized.cache,
+ key = resolver ? resolver.apply(this, arguments) : keyPrefix + arguments[0];
+
+ return hasOwnProperty.call(cache, key)
+ ? cache[key]
+ : (cache[key] = func.apply(this, arguments));
+ }
+ memoized.cache = {};
+ return memoized;
+ }
+
+ /**
+ * Creates a function that is restricted to execute `func` once. Repeat calls to
+ * the function will return the value of the first call. The `func` is executed
+ * with the `this` binding of the created function.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to restrict.
+ * @returns {Function} Returns the new restricted function.
+ * @example
+ *
+ * var initialize = _.once(createApplication);
+ * initialize();
+ * initialize();
+ * // `initialize` executes `createApplication` once
+ */
+ function once(func) {
+ var ran,
+ result;
+
+ if (!isFunction(func)) {
+ throw new TypeError;
+ }
+ return function() {
+ if (ran) {
+ return result;
+ }
+ ran = true;
+ result = func.apply(this, arguments);
+
+ // clear the `func` variable so the function may be garbage collected
+ func = null;
+ return result;
+ };
+ }
+
+ /**
+ * Creates a function that, when called, invokes `func` with any additional
+ * `partial` arguments prepended to those provided to the new function. This
+ * method is similar to `_.bind` except it does **not** alter the `this` binding.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to partially apply arguments to.
+ * @param {...*} [arg] Arguments to be partially applied.
+ * @returns {Function} Returns the new partially applied function.
+ * @example
+ *
+ * var greet = function(greeting, name) { return greeting + ' ' + name; };
+ * var hi = _.partial(greet, 'hi');
+ * hi('fred');
+ * // => 'hi fred'
+ */
+ function partial(func) {
+ return createWrapper(func, 16, slice(arguments, 1));
+ }
+
+ /**
+ * This method is like `_.partial` except that `partial` arguments are
+ * appended to those provided to the new function.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to partially apply arguments to.
+ * @param {...*} [arg] Arguments to be partially applied.
+ * @returns {Function} Returns the new partially applied function.
+ * @example
+ *
+ * var defaultsDeep = _.partialRight(_.merge, _.defaults);
+ *
+ * var options = {
+ * 'variable': 'data',
+ * 'imports': { 'jq': $ }
+ * };
+ *
+ * defaultsDeep(options, _.templateSettings);
+ *
+ * options.variable
+ * // => 'data'
+ *
+ * options.imports
+ * // => { '_': _, 'jq': $ }
+ */
+ function partialRight(func) {
+ return createWrapper(func, 32, null, slice(arguments, 1));
+ }
+
+ /**
+ * Creates a function that, when executed, will only call the `func` function
+ * at most once per every `wait` milliseconds. Provide an options object to
+ * indicate that `func` should be invoked on the leading and/or trailing edge
+ * of the `wait` timeout. Subsequent calls to the throttled function will
+ * return the result of the last `func` call.
+ *
+ * Note: If `leading` and `trailing` options are `true` `func` will be called
+ * on the trailing edge of the timeout only if the the throttled function is
+ * invoked more than once during the `wait` timeout.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to throttle.
+ * @param {number} wait The number of milliseconds to throttle executions to.
+ * @param {Object} [options] The options object.
+ * @param {boolean} [options.leading=true] Specify execution on the leading edge of the timeout.
+ * @param {boolean} [options.trailing=true] Specify execution on the trailing edge of the timeout.
+ * @returns {Function} Returns the new throttled function.
+ * @example
+ *
+ * // avoid excessively updating the position while scrolling
+ * var throttled = _.throttle(updatePosition, 100);
+ * jQuery(window).on('scroll', throttled);
+ *
+ * // execute `renewToken` when the click event is fired, but not more than once every 5 minutes
+ * jQuery('.interactive').on('click', _.throttle(renewToken, 300000, {
+ * 'trailing': false
+ * }));
+ */
+ function throttle(func, wait, options) {
+ var leading = true,
+ trailing = true;
+
+ if (!isFunction(func)) {
+ throw new TypeError;
+ }
+ if (options === false) {
+ leading = false;
+ } else if (isObject(options)) {
+ leading = 'leading' in options ? options.leading : leading;
+ trailing = 'trailing' in options ? options.trailing : trailing;
+ }
+ debounceOptions.leading = leading;
+ debounceOptions.maxWait = wait;
+ debounceOptions.trailing = trailing;
+
+ return debounce(func, wait, debounceOptions);
+ }
+
+ /**
+ * Creates a function that provides `value` to the wrapper function as its
+ * first argument. Additional arguments provided to the function are appended
+ * to those provided to the wrapper function. The wrapper is executed with
+ * the `this` binding of the created function.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {*} value The value to wrap.
+ * @param {Function} wrapper The wrapper function.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var p = _.wrap(_.escape, function(func, text) {
+ * return '' + func(text) + '
';
+ * });
+ *
+ * p('Fred, Wilma, & Pebbles');
+ * // => 'Fred, Wilma, & Pebbles
'
+ */
+ function wrap(value, wrapper) {
+ return createWrapper(wrapper, 16, [value]);
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates a function that returns `value`.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {*} value The value to return from the new function.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var object = { 'name': 'fred' };
+ * var getter = _.constant(object);
+ * getter() === object;
+ * // => true
+ */
+ function constant(value) {
+ return function() {
+ return value;
+ };
+ }
+
+ /**
+ * Produces a callback bound to an optional `thisArg`. If `func` is a property
+ * name the created callback will return the property value for a given element.
+ * If `func` is an object the created callback will return `true` for elements
+ * that contain the equivalent object properties, otherwise it will return `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {*} [func=identity] The value to convert to a callback.
+ * @param {*} [thisArg] The `this` binding of the created callback.
+ * @param {number} [argCount] The number of arguments the callback accepts.
+ * @returns {Function} Returns a callback function.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 }
+ * ];
+ *
+ * // wrap to create custom callback shorthands
+ * _.createCallback = _.wrap(_.createCallback, function(func, callback, thisArg) {
+ * var match = /^(.+?)__([gl]t)(.+)$/.exec(callback);
+ * return !match ? func(callback, thisArg) : function(object) {
+ * return match[2] == 'gt' ? object[match[1]] > match[3] : object[match[1]] < match[3];
+ * };
+ * });
+ *
+ * _.filter(characters, 'age__gt38');
+ * // => [{ 'name': 'fred', 'age': 40 }]
+ */
+ function createCallback(func, thisArg, argCount) {
+ var type = typeof func;
+ if (func == null || type == 'function') {
+ return baseCreateCallback(func, thisArg, argCount);
+ }
+ // handle "_.pluck" style callback shorthands
+ if (type != 'object') {
+ return property(func);
+ }
+ var props = keys(func),
+ key = props[0],
+ a = func[key];
+
+ // handle "_.where" style callback shorthands
+ if (props.length == 1 && a === a && !isObject(a)) {
+ // fast path the common case of providing an object with a single
+ // property containing a primitive value
+ return function(object) {
+ var b = object[key];
+ return a === b && (a !== 0 || (1 / a == 1 / b));
+ };
+ }
+ return function(object) {
+ var length = props.length,
+ result = false;
+
+ while (length--) {
+ if (!(result = baseIsEqual(object[props[length]], func[props[length]], null, true))) {
+ break;
+ }
+ }
+ return result;
+ };
+ }
+
+ /**
+ * Converts the characters `&`, `<`, `>`, `"`, and `'` in `string` to their
+ * corresponding HTML entities.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {string} string The string to escape.
+ * @returns {string} Returns the escaped string.
+ * @example
+ *
+ * _.escape('Fred, Wilma, & Pebbles');
+ * // => 'Fred, Wilma, & Pebbles'
+ */
+ function escape(string) {
+ return string == null ? '' : String(string).replace(reUnescapedHtml, escapeHtmlChar);
+ }
+
+ /**
+ * This method returns the first argument provided to it.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {*} value Any value.
+ * @returns {*} Returns `value`.
+ * @example
+ *
+ * var object = { 'name': 'fred' };
+ * _.identity(object) === object;
+ * // => true
+ */
+ function identity(value) {
+ return value;
+ }
+
+ /**
+ * Adds function properties of a source object to the destination object.
+ * If `object` is a function methods will be added to its prototype as well.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {Function|Object} [object=lodash] object The destination object.
+ * @param {Object} source The object of functions to add.
+ * @param {Object} [options] The options object.
+ * @param {boolean} [options.chain=true] Specify whether the functions added are chainable.
+ * @example
+ *
+ * function capitalize(string) {
+ * return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
+ * }
+ *
+ * _.mixin({ 'capitalize': capitalize });
+ * _.capitalize('fred');
+ * // => 'Fred'
+ *
+ * _('fred').capitalize().value();
+ * // => 'Fred'
+ *
+ * _.mixin({ 'capitalize': capitalize }, { 'chain': false });
+ * _('fred').capitalize();
+ * // => 'Fred'
+ */
+ function mixin(object, source, options) {
+ var chain = true,
+ methodNames = source && functions(source);
+
+ if (!source || (!options && !methodNames.length)) {
+ if (options == null) {
+ options = source;
+ }
+ ctor = lodashWrapper;
+ source = object;
+ object = lodash;
+ methodNames = functions(source);
+ }
+ if (options === false) {
+ chain = false;
+ } else if (isObject(options) && 'chain' in options) {
+ chain = options.chain;
+ }
+ var ctor = object,
+ isFunc = isFunction(ctor);
+
+ forEach(methodNames, function(methodName) {
+ var func = object[methodName] = source[methodName];
+ if (isFunc) {
+ ctor.prototype[methodName] = function() {
+ var chainAll = this.__chain__,
+ value = this.__wrapped__,
+ args = [value];
+
+ push.apply(args, arguments);
+ var result = func.apply(object, args);
+ if (chain || chainAll) {
+ if (value === result && isObject(result)) {
+ return this;
+ }
+ result = new ctor(result);
+ result.__chain__ = chainAll;
+ }
+ return result;
+ };
+ }
+ });
+ }
+
+ /**
+ * Reverts the '_' variable to its previous value and returns a reference to
+ * the `lodash` function.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @returns {Function} Returns the `lodash` function.
+ * @example
+ *
+ * var lodash = _.noConflict();
+ */
+ function noConflict() {
+ context._ = oldDash;
+ return this;
+ }
+
+ /**
+ * A no-operation function.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @example
+ *
+ * var object = { 'name': 'fred' };
+ * _.noop(object) === undefined;
+ * // => true
+ */
+ function noop() {
+ // no operation performed
+ }
+
+ /**
+ * Gets the number of milliseconds that have elapsed since the Unix epoch
+ * (1 January 1970 00:00:00 UTC).
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @example
+ *
+ * var stamp = _.now();
+ * _.defer(function() { console.log(_.now() - stamp); });
+ * // => logs the number of milliseconds it took for the deferred function to be called
+ */
+ var now = isNative(now = Date.now) && now || function() {
+ return new Date().getTime();
+ };
+
+ /**
+ * Converts the given value into an integer of the specified radix.
+ * If `radix` is `undefined` or `0` a `radix` of `10` is used unless the
+ * `value` is a hexadecimal, in which case a `radix` of `16` is used.
+ *
+ * Note: This method avoids differences in native ES3 and ES5 `parseInt`
+ * implementations. See http://es5.github.io/#E.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {string} value The value to parse.
+ * @param {number} [radix] The radix used to interpret the value to parse.
+ * @returns {number} Returns the new integer value.
+ * @example
+ *
+ * _.parseInt('08');
+ * // => 8
+ */
+ var parseInt = nativeParseInt(whitespace + '08') == 8 ? nativeParseInt : function(value, radix) {
+ // Firefox < 21 and Opera < 15 follow the ES3 specified implementation of `parseInt`
+ return nativeParseInt(isString(value) ? value.replace(reLeadingSpacesAndZeros, '') : value, radix || 0);
+ };
+
+ /**
+ * Creates a "_.pluck" style function, which returns the `key` value of a
+ * given object.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {string} key The name of the property to retrieve.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'fred', 'age': 40 },
+ * { 'name': 'barney', 'age': 36 }
+ * ];
+ *
+ * var getName = _.property('name');
+ *
+ * _.map(characters, getName);
+ * // => ['barney', 'fred']
+ *
+ * _.sortBy(characters, getName);
+ * // => [{ 'name': 'barney', 'age': 36 }, { 'name': 'fred', 'age': 40 }]
+ */
+ function property(key) {
+ return function(object) {
+ return object[key];
+ };
+ }
+
+ /**
+ * Produces a random number between `min` and `max` (inclusive). If only one
+ * argument is provided a number between `0` and the given number will be
+ * returned. If `floating` is truey or either `min` or `max` are floats a
+ * floating-point number will be returned instead of an integer.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {number} [min=0] The minimum possible value.
+ * @param {number} [max=1] The maximum possible value.
+ * @param {boolean} [floating=false] Specify returning a floating-point number.
+ * @returns {number} Returns a random number.
+ * @example
+ *
+ * _.random(0, 5);
+ * // => an integer between 0 and 5
+ *
+ * _.random(5);
+ * // => also an integer between 0 and 5
+ *
+ * _.random(5, true);
+ * // => a floating-point number between 0 and 5
+ *
+ * _.random(1.2, 5.2);
+ * // => a floating-point number between 1.2 and 5.2
+ */
+ function random(min, max, floating) {
+ var noMin = min == null,
+ noMax = max == null;
+
+ if (floating == null) {
+ if (typeof min == 'boolean' && noMax) {
+ floating = min;
+ min = 1;
+ }
+ else if (!noMax && typeof max == 'boolean') {
+ floating = max;
+ noMax = true;
+ }
+ }
+ if (noMin && noMax) {
+ max = 1;
+ }
+ min = +min || 0;
+ if (noMax) {
+ max = min;
+ min = 0;
+ } else {
+ max = +max || 0;
+ }
+ if (floating || min % 1 || max % 1) {
+ var rand = nativeRandom();
+ return nativeMin(min + (rand * (max - min + parseFloat('1e-' + ((rand +'').length - 1)))), max);
+ }
+ return baseRandom(min, max);
+ }
+
+ /**
+ * Resolves the value of property `key` on `object`. If `key` is a function
+ * it will be invoked with the `this` binding of `object` and its result returned,
+ * else the property value is returned. If `object` is falsey then `undefined`
+ * is returned.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {Object} object The object to inspect.
+ * @param {string} key The name of the property to resolve.
+ * @returns {*} Returns the resolved value.
+ * @example
+ *
+ * var object = {
+ * 'cheese': 'crumpets',
+ * 'stuff': function() {
+ * return 'nonsense';
+ * }
+ * };
+ *
+ * _.result(object, 'cheese');
+ * // => 'crumpets'
+ *
+ * _.result(object, 'stuff');
+ * // => 'nonsense'
+ */
+ function result(object, key) {
+ if (object) {
+ var value = object[key];
+ return isFunction(value) ? object[key]() : value;
+ }
+ }
+
+ /**
+ * A micro-templating method that handles arbitrary delimiters, preserves
+ * whitespace, and correctly escapes quotes within interpolated code.
+ *
+ * Note: In the development build, `_.template` utilizes sourceURLs for easier
+ * debugging. See http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl
+ *
+ * For more information on precompiling templates see:
+ * https://lodash.com/custom-builds
+ *
+ * For more information on Chrome extension sandboxes see:
+ * http://developer.chrome.com/stable/extensions/sandboxingEval.html
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {string} text The template text.
+ * @param {Object} data The data object used to populate the text.
+ * @param {Object} [options] The options object.
+ * @param {RegExp} [options.escape] The "escape" delimiter.
+ * @param {RegExp} [options.evaluate] The "evaluate" delimiter.
+ * @param {Object} [options.imports] An object to import into the template as local variables.
+ * @param {RegExp} [options.interpolate] The "interpolate" delimiter.
+ * @param {string} [sourceURL] The sourceURL of the template's compiled source.
+ * @param {string} [variable] The data object variable name.
+ * @returns {Function|string} Returns a compiled function when no `data` object
+ * is given, else it returns the interpolated text.
+ * @example
+ *
+ * // using the "interpolate" delimiter to create a compiled template
+ * var compiled = _.template('hello <%= name %>');
+ * compiled({ 'name': 'fred' });
+ * // => 'hello fred'
+ *
+ * // using the "escape" delimiter to escape HTML in data property values
+ * _.template('<%- value %>', { 'value': '').stripTags()
+=> 'a linkalert("hello world!")'
+```
+
+**toSentence** _.toSentence(array, [delimiter, lastDelimiter])
+
+Join an array into a human readable sentence.
+
+```javascript
+_.toSentence(['jQuery', 'Mootools', 'Prototype'])
+=> 'jQuery, Mootools and Prototype';
+
+_.toSentence(['jQuery', 'Mootools', 'Prototype'], ', ', ' unt ')
+=> 'jQuery, Mootools unt Prototype';
+```
+
+**toSentenceSerial** _.toSentenceSerial(array, [delimiter, lastDelimiter])
+
+The same as `toSentence`, but adjusts delimeters to use [Serial comma](http://en.wikipedia.org/wiki/Serial_comma).
+
+```javascript
+_.toSentenceSerial(['jQuery', 'Mootools'])
+=> 'jQuery and Mootools';
+
+_.toSentenceSerial(['jQuery', 'Mootools', 'Prototype'])
+=> 'jQuery, Mootools, and Prototype'
+
+_.toSentenceSerial(['jQuery', 'Mootools', 'Prototype'], ', ', ' unt ');
+=> 'jQuery, Mootools, unt Prototype';
+```
+
+**repeat** _.repeat(string, count, [separator])
+
+Repeats a string count times.
+
+```javascript
+_.repeat("foo", 3)
+=> 'foofoofoo';
+
+_.repeat("foo", 3, "bar")
+=> 'foobarfoobarfoo'
+```
+
+**surround** _.surround(string, wrap)
+
+Surround a string with another string.
+
+```javascript
+_.surround("foo", "ab")
+=> 'abfooab';
+```
+
+**quote** _.quote(string, quoteChar) or _.q(string, quoteChar)
+
+Quotes a string. `quoteChar` defaults to `"`.
+
+```javascript
+_.quote('foo', quoteChar)
+=> '"foo"';
+```
+**unquote** _.unquote(string, quoteChar)
+
+Unquotes a string. `quoteChar` defaults to `"`.
+
+```javascript
+_.unquote('"foo"')
+=> 'foo';
+_.unquote("'foo'", "'")
+=> 'foo';
+```
+
+
+**slugify** _.slugify(string)
+
+Transform text into a URL slug. Replaces whitespaces, accentuated, and special characters with a dash.
+
+```javascript
+_.slugify("Un éléphant à l'orée du bois")
+=> 'un-elephant-a-loree-du-bois';
+```
+
+***Caution: this function is charset dependent***
+
+**naturalCmp** array.sort(_.naturalCmp)
+
+Naturally sort strings like humans would do.
+
+```javascript
+['foo20', 'foo5'].sort(_.naturalCmp)
+=> [ 'foo5', 'foo20' ]
+```
+
+**toBoolean** _.toBoolean(string) or _.toBool(string)
+
+Turn strings that can be commonly considered as booleas to real booleans. Such as "true", "false", "1" and "0". This function is case insensitive.
+
+```javascript
+_.toBoolean("true")
+=> true
+_.toBoolean("FALSE")
+=> false
+_.toBoolean("random")
+=> undefined
+```
+
+It can be customized by giving arrays of truth and falsy value matcher as parameters. Matchers can be also RegExp objects.
+
+```javascript
+_.toBoolean("truthy", ["truthy"], ["falsy"])
+=> true
+_.toBoolean("true only at start", [/^true/])
+=> true
+```
+
+## Roadmap ##
+
+Any suggestions or bug reports are welcome. Just email me or more preferably open an issue.
+
+#### Problems
+
+We lose two things for `include` and `reverse` methods from `_.string`:
+
+* Calls like `_('foobar').include('bar')` aren't available;
+* Chaining isn't available too.
+
+But if you need this functionality you can create aliases for conflict functions which will be convenient for you:
+
+```javascript
+_.mixin({
+ includeString: _.str.include,
+ reverseString: _.str.reverse
+})
+
+// Now wrapper calls and chaining are available.
+_('foobar').chain().reverseString().includeString('rab').value()
+```
+
+#### Standalone Usage
+
+If you are using Underscore.string without Underscore. You also have `_.string` namespace for it and `_.str` alias
+But of course you can just reassign `_` variable with `_.string`
+
+```javascript
+_ = _.string
+```
+
+## Changelog ##
+
+### 2.3.3 ###
+
+* Add `toBoolean`
+* Add `unquote`
+* Add quote char option to `quote`
+* Support dash-separated words in `titleize`
+
+### 2.3.2 ###
+
+* Add `naturalCmp`
+* Bug fix to `camelize`
+* Add ă, ș, ț and ś to `slugify`
+* Doc updates
+* Add support for [component](http://component.io/)
+* [Full changelog](https://github.com/epeli/underscore.string/compare/v2.3.1...v2.3.2)
+
+### 2.3.1 ###
+
+* Bug fixes to `escapeHTML`, `classify`, `substr`
+* Faster `count`
+* Documentation fixes
+* [Full changelog](https://github.com/epeli/underscore.string/compare/v2.3.0...v2.3.1)
+
+### 2.3.0 ###
+
+* Added `numberformat` method
+* Added `levenshtein` method (Levenshtein distance calculation)
+* Added `swapCase` method
+* Changed default behavior of `words` method
+* Added `toSentenceSerial` method
+* Added `surround` and `quote` methods
+
+### 2.2.1 ###
+
+* Same as 2.2.0 (2.2.0rc on npm) to fix some npm drama
+
+### 2.2.0 ###
+
+* Capitalize method behavior changed
+* Various perfomance tweaks
+
+### 2.1.1###
+
+* Fixed words method bug
+* Added classify method
+
+### 2.1.0 ###
+
+* AMD support
+* Added toSentence method
+* Added slugify method
+* Lots of speed optimizations
+
+### 2.0.0 ###
+
+* Added prune, humanize functions
+* Added _.string (_.str) namespace for Underscore.string library
+* Removed includes function
+
+For upgrading to this version you need to mix in Underscore.string library to Underscore object:
+
+```javascript
+_.mixin(_.string.exports());
+```
+
+and all non-conflict Underscore.string functions will be available through Underscore object.
+Also function `includes` has been removed, you should replace this function by `_.str.include`
+or create alias `_.includes = _.str.include` and all your code will work fine.
+
+### 1.1.6 ###
+
+* Fixed reverse and truncate
+* Added isBlank, stripTags, inlude(alias for includes)
+* Added uglifier compression
+
+### 1.1.5 ###
+
+* Added strRight, strRightBack, strLeft, strLeftBack
+
+### 1.1.4 ###
+
+* Added pad, lpad, rpad, lrpad methods and aliases center, ljust, rjust
+* Integration with Underscore 1.1.6
+
+### 1.1.3 ###
+
+* Added methods: underscored, camelize, dasherize
+* Support newer version of npm
+
+### 1.1.2 ###
+
+* Created functions: lines, chars, words functions
+
+### 1.0.2 ###
+
+* Created integration test suite with underscore.js 1.1.4 (now it's absolutely compatible)
+* Removed 'reverse' function, because this function override underscore.js 'reverse'
+
+## Contribute ##
+
+* Fork & pull request. Don't forget about tests.
+* If you planning add some feature please create issue before.
+
+Otherwise changes will be rejected.
+
+## Contributors list ##
+[Can be found here](https://github.com/epeli/underscore.string/graphs/contributors).
+
+
+## Licence ##
+
+The MIT License
+
+Copyright (c) 2011 Esa-Matti Suuronen esa-matti@suuronen.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/node_modules/grunt-legacy-log/node_modules/underscore.string/Rakefile b/node_modules/grunt-legacy-log/node_modules/underscore.string/Rakefile
new file mode 100644
index 0000000..2cd9eed
--- /dev/null
+++ b/node_modules/grunt-legacy-log/node_modules/underscore.string/Rakefile
@@ -0,0 +1,23 @@
+# encoding: utf-8
+task default: :test
+
+desc 'Use UglifyJS to compress Underscore.string'
+task :build do
+ require 'uglifier'
+ source = File.read('lib/underscore.string.js', :encoding => 'utf-8')
+ compressed = Uglifier.compile(source, copyright: false)
+ File.open('dist/underscore.string.min.js', 'w'){ |f| f.write compressed }
+ compression_rate = compressed.length.to_f/source.length
+ puts "compressed dist/underscore.string.min.js: #{compressed.length}/#{source.length} #{(compression_rate * 100).round}%"
+end
+
+desc 'Run tests'
+task :test do
+ puts "Running underscore.string test suite."
+ result1 = system %{phantomjs ./test/run-qunit.js "test/test.html"}
+
+ puts "Running Underscore test suite."
+ result2 = system %{phantomjs ./test/run-qunit.js "test/test_underscore/index.html"}
+
+ exit(result1 && result2 ? 0 : 1)
+end
diff --git a/node_modules/grunt-legacy-log/node_modules/underscore.string/component.json b/node_modules/grunt-legacy-log/node_modules/underscore.string/component.json
new file mode 100644
index 0000000..ae91b65
--- /dev/null
+++ b/node_modules/grunt-legacy-log/node_modules/underscore.string/component.json
@@ -0,0 +1,11 @@
+{
+ "name": "underscore.string",
+ "repo": "epeli/underscore.string",
+ "description": "String manipulation extensions for Underscore.js javascript library",
+ "version": "2.3.3",
+ "keywords": ["underscore", "string"],
+ "dependencies": {},
+ "development": {},
+ "main": "lib/underscore.string.js",
+ "scripts": ["lib/underscore.string.js"]
+}
diff --git a/node_modules/grunt-legacy-log/node_modules/underscore.string/dist/underscore.string.min.js b/node_modules/grunt-legacy-log/node_modules/underscore.string/dist/underscore.string.min.js
new file mode 100644
index 0000000..4f6b2b9
--- /dev/null
+++ b/node_modules/grunt-legacy-log/node_modules/underscore.string/dist/underscore.string.min.js
@@ -0,0 +1 @@
+!function(e,n){"use strict";function r(e,n){var r,t,u=e.toLowerCase();for(n=[].concat(n),r=0;n.length>r;r+=1)if(t=n[r]){if(t.test&&t.test(e))return!0;if(t.toLowerCase()===u)return!0}}var t=n.prototype.trim,u=n.prototype.trimRight,i=n.prototype.trimLeft,l=function(e){return 1*e||0},o=function(e,n){if(1>n)return"";for(var r="";n>0;)1&n&&(r+=e),n>>=1,e+=e;return r},a=[].slice,c=function(e){return null==e?"\\s":e.source?e.source:"["+g.escapeRegExp(e)+"]"},s={lt:"<",gt:">",quot:'"',amp:"&",apos:"'"},f={};for(var p in s)f[s[p]]=p;f["'"]="#39";var h=function(){function e(e){return Object.prototype.toString.call(e).slice(8,-1).toLowerCase()}var r=o,t=function(){return t.cache.hasOwnProperty(arguments[0])||(t.cache[arguments[0]]=t.parse(arguments[0])),t.format.call(null,t.cache[arguments[0]],arguments)};return t.format=function(t,u){var i,l,o,a,c,s,f,p=1,g=t.length,d="",m=[];for(l=0;g>l;l++)if(d=e(t[l]),"string"===d)m.push(t[l]);else if("array"===d){if(a=t[l],a[2])for(i=u[p],o=0;a[2].length>o;o++){if(!i.hasOwnProperty(a[2][o]))throw new Error(h('[_.sprintf] property "%s" does not exist',a[2][o]));i=i[a[2][o]]}else i=a[1]?u[a[1]]:u[p++];if(/[^s]/.test(a[8])&&"number"!=e(i))throw new Error(h("[_.sprintf] expecting number but found %s",e(i)));switch(a[8]){case"b":i=i.toString(2);break;case"c":i=n.fromCharCode(i);break;case"d":i=parseInt(i,10);break;case"e":i=a[7]?i.toExponential(a[7]):i.toExponential();break;case"f":i=a[7]?parseFloat(i).toFixed(a[7]):parseFloat(i);break;case"o":i=i.toString(8);break;case"s":i=(i=n(i))&&a[7]?i.substring(0,a[7]):i;break;case"u":i=Math.abs(i);break;case"x":i=i.toString(16);break;case"X":i=i.toString(16).toUpperCase()}i=/[def]/.test(a[8])&&a[3]&&i>=0?"+"+i:i,s=a[4]?"0"==a[4]?"0":a[4].charAt(1):" ",f=a[6]-n(i).length,c=a[6]?r(s,f):"",m.push(a[5]?i+c:c+i)}return m.join("")},t.cache={},t.parse=function(e){for(var n=e,r=[],t=[],u=0;n;){if(null!==(r=/^[^\x25]+/.exec(n)))t.push(r[0]);else if(null!==(r=/^\x25{2}/.exec(n)))t.push("%");else{if(null===(r=/^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(n)))throw new Error("[_.sprintf] huh?");if(r[2]){u|=1;var i=[],l=r[2],o=[];if(null===(o=/^([a-z_][a-z_\d]*)/i.exec(l)))throw new Error("[_.sprintf] huh?");for(i.push(o[1]);""!==(l=l.substring(o[0].length));)if(null!==(o=/^\.([a-z_][a-z_\d]*)/i.exec(l)))i.push(o[1]);else{if(null===(o=/^\[(\d+)\]/.exec(l)))throw new Error("[_.sprintf] huh?");i.push(o[1])}r[2]=i}else u|=2;if(3===u)throw new Error("[_.sprintf] mixing positional and named placeholders is not (yet) supported");t.push(r)}n=n.substring(r[0].length)}return t},t}(),g={VERSION:"2.3.0",isBlank:function(e){return null==e&&(e=""),/^\s*$/.test(e)},stripTags:function(e){return null==e?"":n(e).replace(/<\/?[^>]+>/g,"")},capitalize:function(e){return e=null==e?"":n(e),e.charAt(0).toUpperCase()+e.slice(1)},chop:function(e,r){return null==e?[]:(e=n(e),r=~~r,r>0?e.match(new RegExp(".{1,"+r+"}","g")):[e])},clean:function(e){return g.strip(e).replace(/\s+/g," ")},count:function(e,r){if(null==e||null==r)return 0;e=n(e),r=n(r);for(var t=0,u=0,i=r.length;;){if(u=e.indexOf(r,u),-1===u)break;t++,u+=i}return t},chars:function(e){return null==e?[]:n(e).split("")},swapCase:function(e){return null==e?"":n(e).replace(/\S/g,function(e){return e===e.toUpperCase()?e.toLowerCase():e.toUpperCase()})},escapeHTML:function(e){return null==e?"":n(e).replace(/[&<>"']/g,function(e){return"&"+f[e]+";"})},unescapeHTML:function(e){return null==e?"":n(e).replace(/\&([^;]+);/g,function(e,r){var t;return r in s?s[r]:(t=r.match(/^#x([\da-fA-F]+)$/))?n.fromCharCode(parseInt(t[1],16)):(t=r.match(/^#(\d+)$/))?n.fromCharCode(~~t[1]):e})},escapeRegExp:function(e){return null==e?"":n(e).replace(/([.*+?^=!:${}()|[\]\/\\])/g,"\\$1")},splice:function(e,n,r,t){var u=g.chars(e);return u.splice(~~n,~~r,t),u.join("")},insert:function(e,n,r){return g.splice(e,n,0,r)},include:function(e,r){return""===r?!0:null==e?!1:-1!==n(e).indexOf(r)},join:function(){var e=a.call(arguments),n=e.shift();return null==n&&(n=""),e.join(n)},lines:function(e){return null==e?[]:n(e).split("\n")},reverse:function(e){return g.chars(e).reverse().join("")},startsWith:function(e,r){return""===r?!0:null==e||null==r?!1:(e=n(e),r=n(r),e.length>=r.length&&e.slice(0,r.length)===r)},endsWith:function(e,r){return""===r?!0:null==e||null==r?!1:(e=n(e),r=n(r),e.length>=r.length&&e.slice(e.length-r.length)===r)},succ:function(e){return null==e?"":(e=n(e),e.slice(0,-1)+n.fromCharCode(e.charCodeAt(e.length-1)+1))},titleize:function(e){return null==e?"":(e=n(e).toLowerCase(),e.replace(/(?:^|\s|-)\S/g,function(e){return e.toUpperCase()}))},camelize:function(e){return g.trim(e).replace(/[-_\s]+(.)?/g,function(e,n){return n?n.toUpperCase():""})},underscored:function(e){return g.trim(e).replace(/([a-z\d])([A-Z]+)/g,"$1_$2").replace(/[-\s]+/g,"_").toLowerCase()},dasherize:function(e){return g.trim(e).replace(/([A-Z])/g,"-$1").replace(/[-_\s]+/g,"-").toLowerCase()},classify:function(e){return g.titleize(n(e).replace(/[\W_]/g," ")).replace(/\s/g,"")},humanize:function(e){return g.capitalize(g.underscored(e).replace(/_id$/,"").replace(/_/g," "))},trim:function(e,r){return null==e?"":!r&&t?t.call(e):(r=c(r),n(e).replace(new RegExp("^"+r+"+|"+r+"+$","g"),""))},ltrim:function(e,r){return null==e?"":!r&&i?i.call(e):(r=c(r),n(e).replace(new RegExp("^"+r+"+"),""))},rtrim:function(e,r){return null==e?"":!r&&u?u.call(e):(r=c(r),n(e).replace(new RegExp(r+"+$"),""))},truncate:function(e,r,t){return null==e?"":(e=n(e),t=t||"...",r=~~r,e.length>r?e.slice(0,r)+t:e)},prune:function(e,r,t){if(null==e)return"";if(e=n(e),r=~~r,t=null!=t?n(t):"...",r>=e.length)return e;var u=function(e){return e.toUpperCase()!==e.toLowerCase()?"A":" "},i=e.slice(0,r+1).replace(/.(?=\W*\w*$)/g,u);return i=i.slice(i.length-2).match(/\w\w/)?i.replace(/\s*\S+$/,""):g.rtrim(i.slice(0,i.length-1)),(i+t).length>e.length?e:e.slice(0,i.length)+t},words:function(e,n){return g.isBlank(e)?[]:g.trim(e,n).split(n||/\s+/)},pad:function(e,r,t,u){e=null==e?"":n(e),r=~~r;var i=0;switch(t?t.length>1&&(t=t.charAt(0)):t=" ",u){case"right":return i=r-e.length,e+o(t,i);case"both":return i=r-e.length,o(t,Math.ceil(i/2))+e+o(t,Math.floor(i/2));default:return i=r-e.length,o(t,i)+e}},lpad:function(e,n,r){return g.pad(e,n,r)},rpad:function(e,n,r){return g.pad(e,n,r,"right")},lrpad:function(e,n,r){return g.pad(e,n,r,"both")},sprintf:h,vsprintf:function(e,n){return n.unshift(e),h.apply(null,n)},toNumber:function(e,n){return e?(e=g.trim(e),e.match(/^-?\d+(?:\.\d+)?$/)?l(l(e).toFixed(~~n)):0/0):0},numberFormat:function(e,n,r,t){if(isNaN(e)||null==e)return"";e=e.toFixed(~~n),t="string"==typeof t?t:",";var u=e.split("."),i=u[0],l=u[1]?(r||".")+u[1]:"";return i.replace(/(\d)(?=(?:\d{3})+$)/g,"$1"+t)+l},strRight:function(e,r){if(null==e)return"";e=n(e),r=null!=r?n(r):r;var t=r?e.indexOf(r):-1;return~t?e.slice(t+r.length,e.length):e},strRightBack:function(e,r){if(null==e)return"";e=n(e),r=null!=r?n(r):r;var t=r?e.lastIndexOf(r):-1;return~t?e.slice(t+r.length,e.length):e},strLeft:function(e,r){if(null==e)return"";e=n(e),r=null!=r?n(r):r;var t=r?e.indexOf(r):-1;return~t?e.slice(0,t):e},strLeftBack:function(e,n){if(null==e)return"";e+="",n=null!=n?""+n:n;var r=e.lastIndexOf(n);return~r?e.slice(0,r):e},toSentence:function(e,n,r,t){n=n||", ",r=r||" and ";var u=e.slice(),i=u.pop();return e.length>2&&t&&(r=g.rtrim(n)+r),u.length?u.join(n)+r+i:i},toSentenceSerial:function(){var e=a.call(arguments);return e[3]=!0,g.toSentence.apply(g,e)},slugify:function(e){if(null==e)return"";var r="ąàáäâãåæăćęèéëêìíïîłńòóöôõøśșțùúüûñçżź",t="aaaaaaaaaceeeeeiiiilnoooooosstuuuunczz",u=new RegExp(c(r),"g");return e=n(e).toLowerCase().replace(u,function(e){var n=r.indexOf(e);return t.charAt(n)||"-"}),g.dasherize(e.replace(/[^\w\s-]/g,""))},surround:function(e,n){return[n,e,n].join("")},quote:function(e,n){return g.surround(e,n||'"')},unquote:function(e,n){return n=n||'"',e[0]===n&&e[e.length-1]===n?e.slice(1,e.length-1):e},exports:function(){var e={};for(var n in this)this.hasOwnProperty(n)&&!n.match(/^(?:include|contains|reverse)$/)&&(e[n]=this[n]);return e},repeat:function(e,r,t){if(null==e)return"";if(r=~~r,null==t)return o(n(e),r);for(var u=[];r>0;u[--r]=e);return u.join(t)},naturalCmp:function(e,r){if(e==r)return 0;if(!e)return-1;if(!r)return 1;for(var t=/(\.\d+)|(\d+)|(\D+)/g,u=n(e).toLowerCase().match(t),i=n(r).toLowerCase().match(t),l=Math.min(u.length,i.length),o=0;l>o;o++){var a=u[o],c=i[o];if(a!==c){var s=parseInt(a,10);if(!isNaN(s)){var f=parseInt(c,10);if(!isNaN(f)&&s-f)return s-f}return c>a?-1:1}}return u.length===i.length?u.length-i.length:r>e?-1:1},levenshtein:function(e,r){if(null==e&&null==r)return 0;if(null==e)return n(r).length;if(null==r)return n(e).length;e=n(e),r=n(r);for(var t,u,i=[],l=0;r.length>=l;l++)for(var o=0;e.length>=o;o++)u=l&&o?e.charAt(o-1)===r.charAt(l-1)?t:Math.min(i[o],i[o-1],t)+1:l+o,t=i[o],i[o]=u;return i.pop()},toBoolean:function(e,n,t){return"number"==typeof e&&(e=""+e),"string"!=typeof e?!!e:(e=g.trim(e),r(e,n||["true","1"])?!0:r(e,t||["false","0"])?!1:void 0)}};g.strip=g.trim,g.lstrip=g.ltrim,g.rstrip=g.rtrim,g.center=g.lrpad,g.rjust=g.lpad,g.ljust=g.rpad,g.contains=g.include,g.q=g.quote,g.toBool=g.toBoolean,"undefined"!=typeof exports&&("undefined"!=typeof module&&module.exports&&(module.exports=g),exports._s=g),"function"==typeof define&&define.amd&&define("underscore.string",[],function(){return g}),e._=e._||{},e._.string=e._.str=g}(this,String);
\ No newline at end of file
diff --git a/node_modules/grunt-legacy-log/node_modules/underscore.string/lib/underscore.string.js b/node_modules/grunt-legacy-log/node_modules/underscore.string/lib/underscore.string.js
new file mode 100644
index 0000000..8761117
--- /dev/null
+++ b/node_modules/grunt-legacy-log/node_modules/underscore.string/lib/underscore.string.js
@@ -0,0 +1,673 @@
+// Underscore.string
+// (c) 2010 Esa-Matti Suuronen
+// Underscore.string is freely distributable under the terms of the MIT license.
+// Documentation: https://github.com/epeli/underscore.string
+// Some code is borrowed from MooTools and Alexandru Marasteanu.
+// Version '2.3.2'
+
+!function(root, String){
+ 'use strict';
+
+ // Defining helper functions.
+
+ var nativeTrim = String.prototype.trim;
+ var nativeTrimRight = String.prototype.trimRight;
+ var nativeTrimLeft = String.prototype.trimLeft;
+
+ var parseNumber = function(source) { return source * 1 || 0; };
+
+ var strRepeat = function(str, qty){
+ if (qty < 1) return '';
+ var result = '';
+ while (qty > 0) {
+ if (qty & 1) result += str;
+ qty >>= 1, str += str;
+ }
+ return result;
+ };
+
+ var slice = [].slice;
+
+ var defaultToWhiteSpace = function(characters) {
+ if (characters == null)
+ return '\\s';
+ else if (characters.source)
+ return characters.source;
+ else
+ return '[' + _s.escapeRegExp(characters) + ']';
+ };
+
+ // Helper for toBoolean
+ function boolMatch(s, matchers) {
+ var i, matcher, down = s.toLowerCase();
+ matchers = [].concat(matchers);
+ for (i = 0; i < matchers.length; i += 1) {
+ matcher = matchers[i];
+ if (!matcher) continue;
+ if (matcher.test && matcher.test(s)) return true;
+ if (matcher.toLowerCase() === down) return true;
+ }
+ }
+
+ var escapeChars = {
+ lt: '<',
+ gt: '>',
+ quot: '"',
+ amp: '&',
+ apos: "'"
+ };
+
+ var reversedEscapeChars = {};
+ for(var key in escapeChars) reversedEscapeChars[escapeChars[key]] = key;
+ reversedEscapeChars["'"] = '#39';
+
+ // sprintf() for JavaScript 0.7-beta1
+ // http://www.diveintojavascript.com/projects/javascript-sprintf
+ //
+ // Copyright (c) Alexandru Marasteanu
+ // All rights reserved.
+
+ var sprintf = (function() {
+ function get_type(variable) {
+ return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
+ }
+
+ var str_repeat = strRepeat;
+
+ var str_format = function() {
+ if (!str_format.cache.hasOwnProperty(arguments[0])) {
+ str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
+ }
+ return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
+ };
+
+ str_format.format = function(parse_tree, argv) {
+ var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
+ for (i = 0; i < tree_length; i++) {
+ node_type = get_type(parse_tree[i]);
+ if (node_type === 'string') {
+ output.push(parse_tree[i]);
+ }
+ else if (node_type === 'array') {
+ match = parse_tree[i]; // convenience purposes only
+ if (match[2]) { // keyword argument
+ arg = argv[cursor];
+ for (k = 0; k < match[2].length; k++) {
+ if (!arg.hasOwnProperty(match[2][k])) {
+ throw new Error(sprintf('[_.sprintf] property "%s" does not exist', match[2][k]));
+ }
+ arg = arg[match[2][k]];
+ }
+ } else if (match[1]) { // positional argument (explicit)
+ arg = argv[match[1]];
+ }
+ else { // positional argument (implicit)
+ arg = argv[cursor++];
+ }
+
+ if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
+ throw new Error(sprintf('[_.sprintf] expecting number but found %s', get_type(arg)));
+ }
+ switch (match[8]) {
+ case 'b': arg = arg.toString(2); break;
+ case 'c': arg = String.fromCharCode(arg); break;
+ case 'd': arg = parseInt(arg, 10); break;
+ case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
+ case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
+ case 'o': arg = arg.toString(8); break;
+ case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
+ case 'u': arg = Math.abs(arg); break;
+ case 'x': arg = arg.toString(16); break;
+ case 'X': arg = arg.toString(16).toUpperCase(); break;
+ }
+ arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
+ pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
+ pad_length = match[6] - String(arg).length;
+ pad = match[6] ? str_repeat(pad_character, pad_length) : '';
+ output.push(match[5] ? arg + pad : pad + arg);
+ }
+ }
+ return output.join('');
+ };
+
+ str_format.cache = {};
+
+ str_format.parse = function(fmt) {
+ var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
+ while (_fmt) {
+ if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
+ parse_tree.push(match[0]);
+ }
+ else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
+ parse_tree.push('%');
+ }
+ else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
+ if (match[2]) {
+ arg_names |= 1;
+ var field_list = [], replacement_field = match[2], field_match = [];
+ if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
+ field_list.push(field_match[1]);
+ while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
+ if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
+ field_list.push(field_match[1]);
+ }
+ else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
+ field_list.push(field_match[1]);
+ }
+ else {
+ throw new Error('[_.sprintf] huh?');
+ }
+ }
+ }
+ else {
+ throw new Error('[_.sprintf] huh?');
+ }
+ match[2] = field_list;
+ }
+ else {
+ arg_names |= 2;
+ }
+ if (arg_names === 3) {
+ throw new Error('[_.sprintf] mixing positional and named placeholders is not (yet) supported');
+ }
+ parse_tree.push(match);
+ }
+ else {
+ throw new Error('[_.sprintf] huh?');
+ }
+ _fmt = _fmt.substring(match[0].length);
+ }
+ return parse_tree;
+ };
+
+ return str_format;
+ })();
+
+
+
+ // Defining underscore.string
+
+ var _s = {
+
+ VERSION: '2.3.0',
+
+ isBlank: function(str){
+ if (str == null) str = '';
+ return (/^\s*$/).test(str);
+ },
+
+ stripTags: function(str){
+ if (str == null) return '';
+ return String(str).replace(/<\/?[^>]+>/g, '');
+ },
+
+ capitalize : function(str){
+ str = str == null ? '' : String(str);
+ return str.charAt(0).toUpperCase() + str.slice(1);
+ },
+
+ chop: function(str, step){
+ if (str == null) return [];
+ str = String(str);
+ step = ~~step;
+ return step > 0 ? str.match(new RegExp('.{1,' + step + '}', 'g')) : [str];
+ },
+
+ clean: function(str){
+ return _s.strip(str).replace(/\s+/g, ' ');
+ },
+
+ count: function(str, substr){
+ if (str == null || substr == null) return 0;
+
+ str = String(str);
+ substr = String(substr);
+
+ var count = 0,
+ pos = 0,
+ length = substr.length;
+
+ while (true) {
+ pos = str.indexOf(substr, pos);
+ if (pos === -1) break;
+ count++;
+ pos += length;
+ }
+
+ return count;
+ },
+
+ chars: function(str) {
+ if (str == null) return [];
+ return String(str).split('');
+ },
+
+ swapCase: function(str) {
+ if (str == null) return '';
+ return String(str).replace(/\S/g, function(c){
+ return c === c.toUpperCase() ? c.toLowerCase() : c.toUpperCase();
+ });
+ },
+
+ escapeHTML: function(str) {
+ if (str == null) return '';
+ return String(str).replace(/[&<>"']/g, function(m){ return '&' + reversedEscapeChars[m] + ';'; });
+ },
+
+ unescapeHTML: function(str) {
+ if (str == null) return '';
+ return String(str).replace(/\&([^;]+);/g, function(entity, entityCode){
+ var match;
+
+ if (entityCode in escapeChars) {
+ return escapeChars[entityCode];
+ } else if (match = entityCode.match(/^#x([\da-fA-F]+)$/)) {
+ return String.fromCharCode(parseInt(match[1], 16));
+ } else if (match = entityCode.match(/^#(\d+)$/)) {
+ return String.fromCharCode(~~match[1]);
+ } else {
+ return entity;
+ }
+ });
+ },
+
+ escapeRegExp: function(str){
+ if (str == null) return '';
+ return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
+ },
+
+ splice: function(str, i, howmany, substr){
+ var arr = _s.chars(str);
+ arr.splice(~~i, ~~howmany, substr);
+ return arr.join('');
+ },
+
+ insert: function(str, i, substr){
+ return _s.splice(str, i, 0, substr);
+ },
+
+ include: function(str, needle){
+ if (needle === '') return true;
+ if (str == null) return false;
+ return String(str).indexOf(needle) !== -1;
+ },
+
+ join: function() {
+ var args = slice.call(arguments),
+ separator = args.shift();
+
+ if (separator == null) separator = '';
+
+ return args.join(separator);
+ },
+
+ lines: function(str) {
+ if (str == null) return [];
+ return String(str).split("\n");
+ },
+
+ reverse: function(str){
+ return _s.chars(str).reverse().join('');
+ },
+
+ startsWith: function(str, starts){
+ if (starts === '') return true;
+ if (str == null || starts == null) return false;
+ str = String(str); starts = String(starts);
+ return str.length >= starts.length && str.slice(0, starts.length) === starts;
+ },
+
+ endsWith: function(str, ends){
+ if (ends === '') return true;
+ if (str == null || ends == null) return false;
+ str = String(str); ends = String(ends);
+ return str.length >= ends.length && str.slice(str.length - ends.length) === ends;
+ },
+
+ succ: function(str){
+ if (str == null) return '';
+ str = String(str);
+ return str.slice(0, -1) + String.fromCharCode(str.charCodeAt(str.length-1) + 1);
+ },
+
+ titleize: function(str){
+ if (str == null) return '';
+ str = String(str).toLowerCase();
+ return str.replace(/(?:^|\s|-)\S/g, function(c){ return c.toUpperCase(); });
+ },
+
+ camelize: function(str){
+ return _s.trim(str).replace(/[-_\s]+(.)?/g, function(match, c){ return c ? c.toUpperCase() : ""; });
+ },
+
+ underscored: function(str){
+ return _s.trim(str).replace(/([a-z\d])([A-Z]+)/g, '$1_$2').replace(/[-\s]+/g, '_').toLowerCase();
+ },
+
+ dasherize: function(str){
+ return _s.trim(str).replace(/([A-Z])/g, '-$1').replace(/[-_\s]+/g, '-').toLowerCase();
+ },
+
+ classify: function(str){
+ return _s.titleize(String(str).replace(/[\W_]/g, ' ')).replace(/\s/g, '');
+ },
+
+ humanize: function(str){
+ return _s.capitalize(_s.underscored(str).replace(/_id$/,'').replace(/_/g, ' '));
+ },
+
+ trim: function(str, characters){
+ if (str == null) return '';
+ if (!characters && nativeTrim) return nativeTrim.call(str);
+ characters = defaultToWhiteSpace(characters);
+ return String(str).replace(new RegExp('\^' + characters + '+|' + characters + '+$', 'g'), '');
+ },
+
+ ltrim: function(str, characters){
+ if (str == null) return '';
+ if (!characters && nativeTrimLeft) return nativeTrimLeft.call(str);
+ characters = defaultToWhiteSpace(characters);
+ return String(str).replace(new RegExp('^' + characters + '+'), '');
+ },
+
+ rtrim: function(str, characters){
+ if (str == null) return '';
+ if (!characters && nativeTrimRight) return nativeTrimRight.call(str);
+ characters = defaultToWhiteSpace(characters);
+ return String(str).replace(new RegExp(characters + '+$'), '');
+ },
+
+ truncate: function(str, length, truncateStr){
+ if (str == null) return '';
+ str = String(str); truncateStr = truncateStr || '...';
+ length = ~~length;
+ return str.length > length ? str.slice(0, length) + truncateStr : str;
+ },
+
+ /**
+ * _s.prune: a more elegant version of truncate
+ * prune extra chars, never leaving a half-chopped word.
+ * @author github.com/rwz
+ */
+ prune: function(str, length, pruneStr){
+ if (str == null) return '';
+
+ str = String(str); length = ~~length;
+ pruneStr = pruneStr != null ? String(pruneStr) : '...';
+
+ if (str.length <= length) return str;
+
+ var tmpl = function(c){ return c.toUpperCase() !== c.toLowerCase() ? 'A' : ' '; },
+ template = str.slice(0, length+1).replace(/.(?=\W*\w*$)/g, tmpl); // 'Hello, world' -> 'HellAA AAAAA'
+
+ if (template.slice(template.length-2).match(/\w\w/))
+ template = template.replace(/\s*\S+$/, '');
+ else
+ template = _s.rtrim(template.slice(0, template.length-1));
+
+ return (template+pruneStr).length > str.length ? str : str.slice(0, template.length)+pruneStr;
+ },
+
+ words: function(str, delimiter) {
+ if (_s.isBlank(str)) return [];
+ return _s.trim(str, delimiter).split(delimiter || /\s+/);
+ },
+
+ pad: function(str, length, padStr, type) {
+ str = str == null ? '' : String(str);
+ length = ~~length;
+
+ var padlen = 0;
+
+ if (!padStr)
+ padStr = ' ';
+ else if (padStr.length > 1)
+ padStr = padStr.charAt(0);
+
+ switch(type) {
+ case 'right':
+ padlen = length - str.length;
+ return str + strRepeat(padStr, padlen);
+ case 'both':
+ padlen = length - str.length;
+ return strRepeat(padStr, Math.ceil(padlen/2)) + str
+ + strRepeat(padStr, Math.floor(padlen/2));
+ default: // 'left'
+ padlen = length - str.length;
+ return strRepeat(padStr, padlen) + str;
+ }
+ },
+
+ lpad: function(str, length, padStr) {
+ return _s.pad(str, length, padStr);
+ },
+
+ rpad: function(str, length, padStr) {
+ return _s.pad(str, length, padStr, 'right');
+ },
+
+ lrpad: function(str, length, padStr) {
+ return _s.pad(str, length, padStr, 'both');
+ },
+
+ sprintf: sprintf,
+
+ vsprintf: function(fmt, argv){
+ argv.unshift(fmt);
+ return sprintf.apply(null, argv);
+ },
+
+ toNumber: function(str, decimals) {
+ if (!str) return 0;
+ str = _s.trim(str);
+ if (!str.match(/^-?\d+(?:\.\d+)?$/)) return NaN;
+ return parseNumber(parseNumber(str).toFixed(~~decimals));
+ },
+
+ numberFormat : function(number, dec, dsep, tsep) {
+ if (isNaN(number) || number == null) return '';
+
+ number = number.toFixed(~~dec);
+ tsep = typeof tsep == 'string' ? tsep : ',';
+
+ var parts = number.split('.'), fnums = parts[0],
+ decimals = parts[1] ? (dsep || '.') + parts[1] : '';
+
+ return fnums.replace(/(\d)(?=(?:\d{3})+$)/g, '$1' + tsep) + decimals;
+ },
+
+ strRight: function(str, sep){
+ if (str == null) return '';
+ str = String(str); sep = sep != null ? String(sep) : sep;
+ var pos = !sep ? -1 : str.indexOf(sep);
+ return ~pos ? str.slice(pos+sep.length, str.length) : str;
+ },
+
+ strRightBack: function(str, sep){
+ if (str == null) return '';
+ str = String(str); sep = sep != null ? String(sep) : sep;
+ var pos = !sep ? -1 : str.lastIndexOf(sep);
+ return ~pos ? str.slice(pos+sep.length, str.length) : str;
+ },
+
+ strLeft: function(str, sep){
+ if (str == null) return '';
+ str = String(str); sep = sep != null ? String(sep) : sep;
+ var pos = !sep ? -1 : str.indexOf(sep);
+ return ~pos ? str.slice(0, pos) : str;
+ },
+
+ strLeftBack: function(str, sep){
+ if (str == null) return '';
+ str += ''; sep = sep != null ? ''+sep : sep;
+ var pos = str.lastIndexOf(sep);
+ return ~pos ? str.slice(0, pos) : str;
+ },
+
+ toSentence: function(array, separator, lastSeparator, serial) {
+ separator = separator || ', ';
+ lastSeparator = lastSeparator || ' and ';
+ var a = array.slice(), lastMember = a.pop();
+
+ if (array.length > 2 && serial) lastSeparator = _s.rtrim(separator) + lastSeparator;
+
+ return a.length ? a.join(separator) + lastSeparator + lastMember : lastMember;
+ },
+
+ toSentenceSerial: function() {
+ var args = slice.call(arguments);
+ args[3] = true;
+ return _s.toSentence.apply(_s, args);
+ },
+
+ slugify: function(str) {
+ if (str == null) return '';
+
+ var from = "ąàáäâãåæăćęèéëêìíïîłńòóöôõøśșțùúüûñçżź",
+ to = "aaaaaaaaaceeeeeiiiilnoooooosstuuuunczz",
+ regex = new RegExp(defaultToWhiteSpace(from), 'g');
+
+ str = String(str).toLowerCase().replace(regex, function(c){
+ var index = from.indexOf(c);
+ return to.charAt(index) || '-';
+ });
+
+ return _s.dasherize(str.replace(/[^\w\s-]/g, ''));
+ },
+
+ surround: function(str, wrapper) {
+ return [wrapper, str, wrapper].join('');
+ },
+
+ quote: function(str, quoteChar) {
+ return _s.surround(str, quoteChar || '"');
+ },
+
+ unquote: function(str, quoteChar) {
+ quoteChar = quoteChar || '"';
+ if (str[0] === quoteChar && str[str.length-1] === quoteChar)
+ return str.slice(1,str.length-1);
+ else return str;
+ },
+
+ exports: function() {
+ var result = {};
+
+ for (var prop in this) {
+ if (!this.hasOwnProperty(prop) || prop.match(/^(?:include|contains|reverse)$/)) continue;
+ result[prop] = this[prop];
+ }
+
+ return result;
+ },
+
+ repeat: function(str, qty, separator){
+ if (str == null) return '';
+
+ qty = ~~qty;
+
+ // using faster implementation if separator is not needed;
+ if (separator == null) return strRepeat(String(str), qty);
+
+ // this one is about 300x slower in Google Chrome
+ for (var repeat = []; qty > 0; repeat[--qty] = str) {}
+ return repeat.join(separator);
+ },
+
+ naturalCmp: function(str1, str2){
+ if (str1 == str2) return 0;
+ if (!str1) return -1;
+ if (!str2) return 1;
+
+ var cmpRegex = /(\.\d+)|(\d+)|(\D+)/g,
+ tokens1 = String(str1).toLowerCase().match(cmpRegex),
+ tokens2 = String(str2).toLowerCase().match(cmpRegex),
+ count = Math.min(tokens1.length, tokens2.length);
+
+ for(var i = 0; i < count; i++) {
+ var a = tokens1[i], b = tokens2[i];
+
+ if (a !== b){
+ var num1 = parseInt(a, 10);
+ if (!isNaN(num1)){
+ var num2 = parseInt(b, 10);
+ if (!isNaN(num2) && num1 - num2)
+ return num1 - num2;
+ }
+ return a < b ? -1 : 1;
+ }
+ }
+
+ if (tokens1.length === tokens2.length)
+ return tokens1.length - tokens2.length;
+
+ return str1 < str2 ? -1 : 1;
+ },
+
+ levenshtein: function(str1, str2) {
+ if (str1 == null && str2 == null) return 0;
+ if (str1 == null) return String(str2).length;
+ if (str2 == null) return String(str1).length;
+
+ str1 = String(str1); str2 = String(str2);
+
+ var current = [], prev, value;
+
+ for (var i = 0; i <= str2.length; i++)
+ for (var j = 0; j <= str1.length; j++) {
+ if (i && j)
+ if (str1.charAt(j - 1) === str2.charAt(i - 1))
+ value = prev;
+ else
+ value = Math.min(current[j], current[j - 1], prev) + 1;
+ else
+ value = i + j;
+
+ prev = current[j];
+ current[j] = value;
+ }
+
+ return current.pop();
+ },
+
+ toBoolean: function(str, trueValues, falseValues) {
+ if (typeof str === "number") str = "" + str;
+ if (typeof str !== "string") return !!str;
+ str = _s.trim(str);
+ if (boolMatch(str, trueValues || ["true", "1"])) return true;
+ if (boolMatch(str, falseValues || ["false", "0"])) return false;
+ }
+ };
+
+ // Aliases
+
+ _s.strip = _s.trim;
+ _s.lstrip = _s.ltrim;
+ _s.rstrip = _s.rtrim;
+ _s.center = _s.lrpad;
+ _s.rjust = _s.lpad;
+ _s.ljust = _s.rpad;
+ _s.contains = _s.include;
+ _s.q = _s.quote;
+ _s.toBool = _s.toBoolean;
+
+ // Exporting
+
+ // CommonJS module is defined
+ if (typeof exports !== 'undefined') {
+ if (typeof module !== 'undefined' && module.exports)
+ module.exports = _s;
+
+ exports._s = _s;
+ }
+
+ // Register as a named module with AMD.
+ if (typeof define === 'function' && define.amd)
+ define('underscore.string', [], function(){ return _s; });
+
+
+ // Integrate with Underscore.js if defined
+ // or create our own underscore object.
+ root._ = root._ || {};
+ root._.string = root._.str = _s;
+}(this, String);
diff --git a/node_modules/grunt-legacy-log/node_modules/underscore.string/libpeerconnection.log b/node_modules/grunt-legacy-log/node_modules/underscore.string/libpeerconnection.log
new file mode 100644
index 0000000..e69de29
diff --git a/node_modules/grunt-legacy-log/node_modules/underscore.string/package.json b/node_modules/grunt-legacy-log/node_modules/underscore.string/package.json
new file mode 100644
index 0000000..c2e03c9
--- /dev/null
+++ b/node_modules/grunt-legacy-log/node_modules/underscore.string/package.json
@@ -0,0 +1,132 @@
+{
+ "_args": [
+ [
+ {
+ "raw": "underscore.string@~2.3.3",
+ "scope": null,
+ "escapedName": "underscore.string",
+ "name": "underscore.string",
+ "rawSpec": "~2.3.3",
+ "spec": ">=2.3.3 <2.4.0",
+ "type": "range"
+ },
+ "/home/vagrant/projects/legislature-tracker/node_modules/grunt-legacy-log"
+ ]
+ ],
+ "_from": "underscore.string@>=2.3.3 <2.4.0",
+ "_id": "underscore.string@2.3.3",
+ "_inCache": true,
+ "_location": "/grunt-legacy-log/underscore.string",
+ "_npmUser": {
+ "name": "epeli",
+ "email": "esa-matti@suuronen.org"
+ },
+ "_npmVersion": "1.2.32",
+ "_phantomChildren": {},
+ "_requested": {
+ "raw": "underscore.string@~2.3.3",
+ "scope": null,
+ "escapedName": "underscore.string",
+ "name": "underscore.string",
+ "rawSpec": "~2.3.3",
+ "spec": ">=2.3.3 <2.4.0",
+ "type": "range"
+ },
+ "_requiredBy": [
+ "/grunt-legacy-log"
+ ],
+ "_resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz",
+ "_shasum": "71c08bf6b428b1133f37e78fa3a21c82f7329b0d",
+ "_shrinkwrap": null,
+ "_spec": "underscore.string@~2.3.3",
+ "_where": "/home/vagrant/projects/legislature-tracker/node_modules/grunt-legacy-log",
+ "bugs": {
+ "url": "https://github.com/epeli/underscore.string/issues"
+ },
+ "contributors": [
+ {
+ "name": "Esa-Matti Suuronen",
+ "email": "esa-matti@suuronen.org",
+ "url": "http://esa-matti.suuronen.org/"
+ },
+ {
+ "name": "Edward Tsech",
+ "email": "edtsech@gmail.com"
+ },
+ {
+ "name": "Pavel Pravosud",
+ "email": "pavel@pravosud.com",
+ "url": ""
+ },
+ {
+ "name": "Sasha Koss",
+ "email": "kossnocorp@gmail.com",
+ "url": "http://koss.nocorp.me/"
+ },
+ {
+ "name": "Vladimir Dronnikov",
+ "email": "dronnikov@gmail.com"
+ },
+ {
+ "name": "Pete Kruckenberg",
+ "email": "https://github.com/kruckenb",
+ "url": ""
+ },
+ {
+ "name": "Paul Chavard",
+ "email": "paul@chavard.net",
+ "url": ""
+ },
+ {
+ "name": "Ed Finkler",
+ "email": "coj@funkatron.com",
+ "url": ""
+ }
+ ],
+ "dependencies": {},
+ "description": "String manipulation extensions for Underscore.js javascript library.",
+ "devDependencies": {},
+ "directories": {
+ "lib": "./lib"
+ },
+ "dist": {
+ "shasum": "71c08bf6b428b1133f37e78fa3a21c82f7329b0d",
+ "tarball": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "homepage": "http://epeli.github.com/underscore.string/",
+ "keywords": [
+ "underscore",
+ "string"
+ ],
+ "licenses": [
+ {
+ "type": "MIT"
+ }
+ ],
+ "main": "./lib/underscore.string",
+ "maintainers": [
+ {
+ "name": "edtsech",
+ "email": "edtsech@gmail.com"
+ },
+ {
+ "name": "rwz",
+ "email": "rwz@duckroll.ru"
+ },
+ {
+ "name": "epeli",
+ "email": "esa-matti@suuronen.org"
+ }
+ ],
+ "name": "underscore.string",
+ "optionalDependencies": {},
+ "readme": "ERROR: No README data found!",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/epeli/underscore.string.git"
+ },
+ "version": "2.3.3"
+}
diff --git a/node_modules/grunt-legacy-log/node_modules/underscore.string/test/run-qunit.js b/node_modules/grunt-legacy-log/node_modules/underscore.string/test/run-qunit.js
new file mode 100644
index 0000000..44a2167
--- /dev/null
+++ b/node_modules/grunt-legacy-log/node_modules/underscore.string/test/run-qunit.js
@@ -0,0 +1,45 @@
+function waitFor(test, complete, timeout) {
+ var result, start = new Date().getTime()
+ setInterval(function interval() {
+ if ((new Date().getTime() - start < timeout) && !result) {
+ result = test()
+ } else {
+ if (!result) {
+ phantom.exit(1)
+ } else {
+ complete()
+ clearInterval(interval)
+ }
+ }
+ }, 100)
+}
+
+
+var fs = require('fs'), page = require('webpage').create();
+var url = 'file://localhost' + fs.workingDirectory + '/' + phantom.args[0];
+
+page.onConsoleMessage = function(msg) {
+ console.log(msg)
+}
+
+page.open(url, function(status) {
+ waitFor(function() {
+ return page.evaluate(function(){
+ var el = document.getElementById('qunit-testresult')
+ return el && el.innerText.match('completed')
+ })
+ }, function() {
+ var failures = page.evaluate(function() {
+ var el = document.getElementById('qunit-testresult'),
+ fails = document.getElementsByClassName('fail')
+
+ for (var i = 0; i < fails.length; i++)
+ console.log(fails[i].innerText)
+
+ console.log(el.innerText)
+
+ return parseInt(el.getElementsByClassName('failed')[0].innerHTML)
+ })
+ phantom.exit(failures > 0 ? 1 : 0)
+ }, 10000)
+})
\ No newline at end of file
diff --git a/node_modules/grunt-legacy-log/node_modules/underscore.string/test/speed.js b/node_modules/grunt-legacy-log/node_modules/underscore.string/test/speed.js
new file mode 100644
index 0000000..9ceeea7
--- /dev/null
+++ b/node_modules/grunt-legacy-log/node_modules/underscore.string/test/speed.js
@@ -0,0 +1,148 @@
+(function() {
+
+ JSLitmus.test('levenshtein', function() {
+ return [
+ _.levenshtein('pineapple', 'potato'),
+ _.levenshtein('seven', 'eight'),
+ _.levenshtein('the very same string', 'the very same string'),
+ _.levenshtein('very very very long string', 'something completely different')
+ ];
+ });
+
+
+ JSLitmus.test('trimNoNative', function() {
+ return _.trim(" foobar ", " ");
+ });
+
+ JSLitmus.test('trim', function() {
+ return _.trim(" foobar ");
+ });
+
+ JSLitmus.test('trim object-oriented', function() {
+ return _(" foobar ").trim();
+ });
+
+ JSLitmus.test('trim jQuery', function() {
+ return jQuery.trim(" foobar ");
+ });
+
+ JSLitmus.test('ltrimp', function() {
+ return _.ltrim(" foobar ", " ");
+ });
+
+ JSLitmus.test('rtrimp', function() {
+ return _.rtrim(" foobar ", " ");
+ });
+
+ JSLitmus.test('startsWith', function() {
+ return _.startsWith("foobar", "foo");
+ });
+
+ JSLitmus.test('endsWith', function() {
+ return _.endsWith("foobar", "xx");
+ });
+
+ JSLitmus.test('chop', function(){
+ return _('whitespace').chop(2);
+ });
+
+ JSLitmus.test('count', function(){
+ return _('Hello worls').count('l');
+ });
+
+ JSLitmus.test('insert', function() {
+ return _('Hello ').insert(6, 'world');
+ });
+
+ JSLitmus.test('splice', function() {
+ return _('https://edtsech@bitbucket.org/edtsech/underscore.strings').splice(30, 7, 'epeli');
+ });
+
+ JSLitmus.test('succ', function(){
+ var let = 'a', alphabet = [];
+
+ for (var i=0; i < 26; i++) {
+ alphabet.push(let);
+ let = _(let).succ();
+ }
+
+ return alphabet;
+ });
+
+ JSLitmus.test('titleize', function(){
+ return _('the titleize string method').titleize();
+ });
+
+ JSLitmus.test('truncate', function(){
+ return _('Hello world').truncate(5);
+ });
+
+ JSLitmus.test('prune', function(){
+ return _('Hello world').prune(5);
+ });
+
+ JSLitmus.test('isBlank', function(){
+ return _('').isBlank();
+ });
+
+ JSLitmus.test('escapeHTML', function(){
+ _('Blah blah blah
').escapeHTML();
+ });
+
+ JSLitmus.test('unescapeHTML', function(){
+ _('<div>Blah blah blah</div>').unescapeHTML();
+ });
+
+ JSLitmus.test('reverse', function(){
+ _('Hello World').reverse();
+ });
+
+ JSLitmus.test('pad default', function(){
+ _('foo').pad(12);
+ });
+
+ JSLitmus.test('pad hash left', function(){
+ _('foo').pad(12, '#');
+ });
+
+ JSLitmus.test('pad hash right', function(){
+ _('foo').pad(12, '#', 'right');
+ });
+
+ JSLitmus.test('pad hash both', function(){
+ _('foo').pad(12, '#', 'both');
+ });
+
+ JSLitmus.test('pad hash both longPad', function(){
+ _('foo').pad(12, 'f00f00f00', 'both');
+ });
+
+ JSLitmus.test('toNumber', function(){
+ _('10.232323').toNumber(2);
+ });
+
+ JSLitmus.test('strRight', function(){
+ _('aaa_bbb_ccc').strRight('_');
+ });
+
+ JSLitmus.test('strRightBack', function(){
+ _('aaa_bbb_ccc').strRightBack('_');
+ });
+
+ JSLitmus.test('strLeft', function(){
+ _('aaa_bbb_ccc').strLeft('_');
+ });
+
+ JSLitmus.test('strLeftBack', function(){
+ _('aaa_bbb_ccc').strLeftBack('_');
+ });
+
+ JSLitmus.test('join', function(){
+ _('separator').join(1, 2, 3, 4, 5, 6, 7, 8, 'foo', 'bar', 'lol', 'wut');
+ });
+
+ JSLitmus.test('slugify', function(){
+ _("Un éléphant à l'orée du bois").slugify();
+ });
+
+})();
diff --git a/node_modules/grunt-legacy-log/node_modules/underscore.string/test/strings.js b/node_modules/grunt-legacy-log/node_modules/underscore.string/test/strings.js
new file mode 100644
index 0000000..77364f2
--- /dev/null
+++ b/node_modules/grunt-legacy-log/node_modules/underscore.string/test/strings.js
@@ -0,0 +1,685 @@
+$(document).ready(function() {
+
+ // Include Underscore.string methods to Underscore namespace
+ _.mixin(_.str.exports());
+
+ module('String extensions');
+
+ test('Strings: naturalSort', function() {
+ var arr = ['foo2', 'foo1', 'foo10', 'foo30', 'foo100', 'foo10bar'],
+ sorted = ['foo1', 'foo2', 'foo10', 'foo10bar', 'foo30', 'foo100'];
+ deepEqual(arr.sort(_.naturalCmp), sorted);
+ });
+
+ test('Strings: trim', function() {
+ equal(_.trim(123), '123', 'Non string');
+ equal(_(' foo').trim(), 'foo');
+ equal(_('foo ').trim(), 'foo');
+ equal(_(' foo ').trim(), 'foo');
+ equal(_(' foo ').trim(), 'foo');
+ equal(_(' foo ').trim(' '), 'foo', 'Manually set whitespace');
+ equal(_('\t foo \t ').trim(/\s/), 'foo', 'Manually set RegExp /\\s+/');
+
+ equal(_('ffoo').trim('f'), 'oo');
+ equal(_('ooff').trim('f'), 'oo');
+ equal(_('ffooff').trim('f'), 'oo');
+
+
+ equal(_('_-foobar-_').trim('_-'), 'foobar');
+
+ equal(_('http://foo/').trim('/'), 'http://foo');
+ equal(_('c:\\').trim('\\'), 'c:');
+
+ equal(_(123).trim(), '123');
+ equal(_(123).trim(3), '12');
+ equal(_('').trim(), '', 'Trim empty string should return empty string');
+ equal(_(null).trim(), '', 'Trim null should return empty string');
+ equal(_(undefined).trim(), '', 'Trim undefined should return empty string');
+ });
+
+ test('String: levenshtein', function() {
+ equal(_.levenshtein('Godfather', 'Godfather'), 0);
+ equal(_.levenshtein('Godfather', 'Godfathe'), 1);
+ equal(_.levenshtein('Godfather', 'odfather'), 1);
+ equal(_.levenshtein('Godfather', 'Gdfthr'), 3);
+ equal(_.levenshtein('seven', 'eight'), 5);
+ equal(_.levenshtein('123', 123), 0);
+ equal(_.levenshtein(321, '321'), 0);
+ equal(_.levenshtein('lol', null), 3);
+ equal(_.levenshtein('lol'), 3);
+ equal(_.levenshtein(null, 'lol'), 3);
+ equal(_.levenshtein(undefined, 'lol'), 3);
+ equal(_.levenshtein(), 0);
+ });
+
+ test('Strings: ltrim', function() {
+ equal(_(' foo').ltrim(), 'foo');
+ equal(_(' foo').ltrim(), 'foo');
+ equal(_('foo ').ltrim(), 'foo ');
+ equal(_(' foo ').ltrim(), 'foo ');
+ equal(_('').ltrim(), '', 'ltrim empty string should return empty string');
+ equal(_(null).ltrim(), '', 'ltrim null should return empty string');
+ equal(_(undefined).ltrim(), '', 'ltrim undefined should return empty string');
+
+ equal(_('ffoo').ltrim('f'), 'oo');
+ equal(_('ooff').ltrim('f'), 'ooff');
+ equal(_('ffooff').ltrim('f'), 'ooff');
+
+ equal(_('_-foobar-_').ltrim('_-'), 'foobar-_');
+
+ equal(_(123).ltrim(1), '23');
+ });
+
+ test('Strings: rtrim', function() {
+ equal(_('http://foo/').rtrim('/'), 'http://foo', 'clean trailing slash');
+ equal(_(' foo').rtrim(), ' foo');
+ equal(_('foo ').rtrim(), 'foo');
+ equal(_('foo ').rtrim(), 'foo');
+ equal(_('foo bar ').rtrim(), 'foo bar');
+ equal(_(' foo ').rtrim(), ' foo');
+
+ equal(_('ffoo').rtrim('f'), 'ffoo');
+ equal(_('ooff').rtrim('f'), 'oo');
+ equal(_('ffooff').rtrim('f'), 'ffoo');
+
+ equal(_('_-foobar-_').rtrim('_-'), '_-foobar');
+
+ equal(_(123).rtrim(3), '12');
+ equal(_('').rtrim(), '', 'rtrim empty string should return empty string');
+ equal(_(null).rtrim(), '', 'rtrim null should return empty string');
+ });
+
+ test('Strings: capitalize', function() {
+ equal(_('fabio').capitalize(), 'Fabio', 'First letter is upper case');
+ equal(_.capitalize('fabio'), 'Fabio', 'First letter is upper case');
+ equal(_.capitalize('FOO'), 'FOO', 'Other letters unchanged');
+ equal(_(123).capitalize(), '123', 'Non string');
+ equal(_.capitalize(''), '', 'Capitalizing empty string returns empty string');
+ equal(_.capitalize(null), '', 'Capitalizing null returns empty string');
+ equal(_.capitalize(undefined), '', 'Capitalizing undefined returns empty string');
+ });
+
+ test('Strings: join', function() {
+ equal(_.join('', 'foo', 'bar'), 'foobar', 'basic join');
+ equal(_.join('', 1, 'foo', 2), '1foo2', 'join numbers and strings');
+ equal(_.join(' ','foo', 'bar'), 'foo bar', 'join with spaces');
+ equal(_.join('1', '2', '2'), '212', 'join number strings');
+ equal(_.join(1, 2, 2), '212', 'join numbers');
+ equal(_.join('','foo', null), 'foo', 'join null with string returns string');
+ equal(_.join(null,'foo', 'bar'), 'foobar', 'join strings with null returns string');
+ equal(_(' ').join('foo', 'bar'), 'foo bar', 'join object oriented');
+ });
+
+ test('Strings: reverse', function() {
+ equal(_.str.reverse('foo'), 'oof' );
+ equal(_.str.reverse('foobar'), 'raboof' );
+ equal(_.str.reverse('foo bar'), 'rab oof' );
+ equal(_.str.reverse('saippuakauppias'), 'saippuakauppias' );
+ equal(_.str.reverse(123), '321', 'Non string');
+ equal(_.str.reverse(123.45), '54.321', 'Non string');
+ equal(_.str.reverse(''), '', 'reversing empty string returns empty string' );
+ equal(_.str.reverse(null), '', 'reversing null returns empty string' );
+ equal(_.str.reverse(undefined), '', 'reversing undefined returns empty string' );
+ });
+
+ test('Strings: clean', function() {
+ equal(_(' foo bar ').clean(), 'foo bar');
+ equal(_(123).clean(), '123');
+ equal(_('').clean(), '', 'claning empty string returns empty string');
+ equal(_(null).clean(), '', 'claning null returns empty string');
+ equal(_(undefined).clean(), '', 'claning undefined returns empty string');
+ });
+
+ test('Strings: sprintf', function() {
+ // Should be very tested function already. Thanks to
+ // http://www.diveintojavascript.com/projects/sprintf-for-javascript
+ equal(_.sprintf('Hello %s', 'me'), 'Hello me', 'basic');
+ equal(_('Hello %s').sprintf('me'), 'Hello me', 'object');
+ equal(_('hello %s').chain().sprintf('me').capitalize().value(), 'Hello me', 'Chaining works');
+ equal(_.sprintf('%.1f', 1.22222), '1.2', 'round');
+ equal(_.sprintf('%.1f', 1.17), '1.2', 'round 2');
+ equal(_.sprintf('%(id)d - %(name)s', {id: 824, name: 'Hello World'}), '824 - Hello World', 'Named replacements work');
+ equal(_.sprintf('%(args[0].id)d - %(args[1].name)s', {args: [{id: 824}, {name: 'Hello World'}]}), '824 - Hello World', 'Named replacements with arrays work');
+ });
+
+
+ test('Strings: vsprintf', function() {
+ equal(_.vsprintf('Hello %s', ['me']), 'Hello me', 'basic');
+ equal(_('Hello %s').vsprintf(['me']), 'Hello me', 'object');
+ equal(_('hello %s').chain().vsprintf(['me']).capitalize().value(), 'Hello me', 'Chaining works');
+ equal(_.vsprintf('%.1f', [1.22222]), '1.2', 'round');
+ equal(_.vsprintf('%.1f', [1.17]), '1.2', 'round 2');
+ equal(_.vsprintf('%(id)d - %(name)s', [{id: 824, name: 'Hello World'}]), '824 - Hello World', 'Named replacement works');
+ equal(_.vsprintf('%(args[0].id)d - %(args[1].name)s', [{args: [{id: 824}, {name: 'Hello World'}]}]), '824 - Hello World', 'Named replacement with arrays works');
+ });
+
+ test('Strings: startsWith', function() {
+ ok(_('foobar').startsWith('foo'), 'foobar starts with foo');
+ ok(!_('oobar').startsWith('foo'), 'oobar does not start with foo');
+ ok(_(12345).startsWith(123), '12345 starts with 123');
+ ok(!_(2345).startsWith(123), '2345 does not start with 123');
+ ok(_('').startsWith(''), 'empty string starts with empty string');
+ ok(_(null).startsWith(''), 'null starts with empty string');
+ ok(!_(null).startsWith('foo'), 'null starts with foo');
+ });
+
+ test('Strings: endsWith', function() {
+ ok(_('foobar').endsWith('bar'), 'foobar ends with bar');
+ ok(_.endsWith('foobar', 'bar'), 'foobar ends with bar');
+ ok(_.endsWith('00018-0000062.Plone.sdh264.1a7264e6912a91aa4a81b64dc5517df7b8875994.mp4', 'mp4'), 'endsWith .mp4');
+ ok(!_('fooba').endsWith('bar'), 'fooba does not end with bar');
+ ok(_.endsWith(12345, 45), '12345 ends with 45');
+ ok(!_.endsWith(12345, 6), '12345 does not end with 6');
+ ok(_('').endsWith(''), 'empty string ends with empty string');
+ ok(_(null).endsWith(''), 'null ends with empty string');
+ ok(!_(null).endsWith('foo'), 'null ends with foo');
+ });
+
+ test('Strings: include', function() {
+ ok(_.str.include('foobar', 'bar'), 'foobar includes bar');
+ ok(!_.str.include('foobar', 'buzz'), 'foobar does not includes buzz');
+ ok(_.str.include(12345, 34), '12345 includes 34');
+ ok(!_.str.contains(12345, 6), '12345 does not includes 6');
+ ok(!_.str.include('', 34), 'empty string includes 34');
+ ok(!_.str.include(null, 34), 'null includes 34');
+ ok(_.str.include(null, ''), 'null includes empty string');
+ });
+
+ test('String: chop', function(){
+ ok(_('whitespace').chop(2).length === 5, 'output [wh, it, es, pa, ce]');
+ ok(_('whitespace').chop(3).length === 4, 'output [whi, tes, pac, e]');
+ ok(_('whitespace').chop()[0].length === 10, 'output [whitespace]');
+ ok(_(12345).chop(1).length === 5, 'output [1, 2, 3, 4, 5]');
+ });
+
+ test('String: clean', function(){
+ equal(_.clean(' foo bar '), 'foo bar');
+ equal(_.clean(''), '');
+ equal(_.clean(null), '');
+ equal(_.clean(1), '1');
+ });
+
+ test('String: count', function(){
+ equal(_('Hello world').count('l'), 3);
+ equal(_('Hello world').count('Hello'), 1);
+ equal(_('Hello world').count('foo'), 0);
+ equal(_('x.xx....x.x').count('x'), 5);
+ equal(_('').count('x'), 0);
+ equal(_(null).count('x'), 0);
+ equal(_(undefined).count('x'), 0);
+ equal(_(12345).count(1), 1);
+ equal(_(11345).count(1), 2);
+ });
+
+ test('String: insert', function(){
+ equal(_('Hello ').insert(6, 'Jessy'), 'Hello Jessy');
+ equal(_('Hello ').insert(100, 'Jessy'), 'Hello Jessy');
+ equal(_('').insert(100, 'Jessy'), 'Jessy');
+ equal(_(null).insert(100, 'Jessy'), 'Jessy');
+ equal(_(undefined).insert(100, 'Jessy'), 'Jessy');
+ equal(_(12345).insert(6, 'Jessy'), '12345Jessy');
+ });
+
+ test('String: splice', function(){
+ equal(_('https://edtsech@bitbucket.org/edtsech/underscore.strings').splice(30, 7, 'epeli'),
+ 'https://edtsech@bitbucket.org/epeli/underscore.strings');
+ equal(_.splice(12345, 1, 2, 321), '132145', 'Non strings');
+ });
+
+ test('String: succ', function(){
+ equal(_('a').succ(), 'b');
+ equal(_('A').succ(), 'B');
+ equal(_('+').succ(), ',');
+ equal(_(1).succ(), '2');
+ });
+
+ test('String: titleize', function(){
+ equal(_('the titleize string method').titleize(), 'The Titleize String Method');
+ equal(_('the titleize string method').titleize(), 'The Titleize String Method');
+ equal(_('').titleize(), '', 'Titleize empty string returns empty string');
+ equal(_(null).titleize(), '', 'Titleize null returns empty string');
+ equal(_(undefined).titleize(), '', 'Titleize undefined returns empty string');
+ equal(_('let\'s have some fun').titleize(), 'Let\'s Have Some Fun');
+ equal(_('a-dash-separated-string').titleize(), 'A-Dash-Separated-String');
+ equal(_('A-DASH-SEPARATED-STRING').titleize(), 'A-Dash-Separated-String');
+ equal(_(123).titleize(), '123');
+ });
+
+ test('String: camelize', function(){
+ equal(_('the_camelize_string_method').camelize(), 'theCamelizeStringMethod');
+ equal(_('-the-camelize-string-method').camelize(), 'TheCamelizeStringMethod');
+ equal(_('the camelize string method').camelize(), 'theCamelizeStringMethod');
+ equal(_(' the camelize string method').camelize(), 'theCamelizeStringMethod');
+ equal(_('the camelize string method').camelize(), 'theCamelizeStringMethod');
+ equal(_('').camelize(), '', 'Camelize empty string returns empty string');
+ equal(_(null).camelize(), '', 'Camelize null returns empty string');
+ equal(_(undefined).camelize(), '', 'Camelize undefined returns empty string');
+ equal(_(123).camelize(), '123');
+ });
+
+ test('String: underscored', function(){
+ equal(_('the-underscored-string-method').underscored(), 'the_underscored_string_method');
+ equal(_('theUnderscoredStringMethod').underscored(), 'the_underscored_string_method');
+ equal(_('TheUnderscoredStringMethod').underscored(), 'the_underscored_string_method');
+ equal(_(' the underscored string method').underscored(), 'the_underscored_string_method');
+ equal(_('').underscored(), '');
+ equal(_(null).underscored(), '');
+ equal(_(undefined).underscored(), '');
+ equal(_(123).underscored(), '123');
+ });
+
+ test('String: dasherize', function(){
+ equal(_('the_dasherize_string_method').dasherize(), 'the-dasherize-string-method');
+ equal(_('TheDasherizeStringMethod').dasherize(), '-the-dasherize-string-method');
+ equal(_('thisIsATest').dasherize(), 'this-is-a-test');
+ equal(_('this Is A Test').dasherize(), 'this-is-a-test');
+ equal(_('thisIsATest123').dasherize(), 'this-is-a-test123');
+ equal(_('123thisIsATest').dasherize(), '123this-is-a-test');
+ equal(_('the dasherize string method').dasherize(), 'the-dasherize-string-method');
+ equal(_('the dasherize string method ').dasherize(), 'the-dasherize-string-method');
+ equal(_('téléphone').dasherize(), 'téléphone');
+ equal(_('foo$bar').dasherize(), 'foo$bar');
+ equal(_('').dasherize(), '');
+ equal(_(null).dasherize(), '');
+ equal(_(undefined).dasherize(), '');
+ equal(_(123).dasherize(), '123');
+ });
+
+ test('String: camelize', function(){
+ equal(_.camelize('-moz-transform'), 'MozTransform');
+ equal(_.camelize('webkit-transform'), 'webkitTransform');
+ equal(_.camelize('under_scored'), 'underScored');
+ equal(_.camelize(' with spaces'), 'withSpaces');
+ equal(_('').camelize(), '');
+ equal(_(null).camelize(), '');
+ equal(_(undefined).camelize(), '');
+ equal(_("_som eWeird---name-").camelize(), 'SomEWeirdName');
+ });
+
+ test('String: join', function(){
+ equal(_.join(1, 2, 3, 4), '21314');
+ equal(_.join('|', 'foo', 'bar', 'baz'), 'foo|bar|baz');
+ equal(_.join('',2,3,null), '23');
+ equal(_.join(null,2,3), '23');
+ });
+
+ test('String: classify', function(){
+ equal(_.classify(1), '1');
+ equal(_('some_class_name').classify(), 'SomeClassName');
+ equal(_('my wonderfull class_name').classify(), 'MyWonderfullClassName');
+ equal(_('my wonderfull.class.name').classify(), 'MyWonderfullClassName');
+ });
+
+ test('String: humanize', function(){
+ equal(_('the_humanize_string_method').humanize(), 'The humanize string method');
+ equal(_('ThehumanizeStringMethod').humanize(), 'Thehumanize string method');
+ equal(_('the humanize string method').humanize(), 'The humanize string method');
+ equal(_('the humanize_id string method_id').humanize(), 'The humanize id string method');
+ equal(_('the humanize string method ').humanize(), 'The humanize string method');
+ equal(_(' capitalize dash-CamelCase_underscore trim ').humanize(), 'Capitalize dash camel case underscore trim');
+ equal(_(123).humanize(), '123');
+ equal(_('').humanize(), '');
+ equal(_(null).humanize(), '');
+ equal(_(undefined).humanize(), '');
+ });
+
+ test('String: truncate', function(){
+ equal(_('Hello world').truncate(6, 'read more'), 'Hello read more');
+ equal(_('Hello world').truncate(5), 'Hello...');
+ equal(_('Hello').truncate(10), 'Hello');
+ equal(_('').truncate(10), '');
+ equal(_(null).truncate(10), '');
+ equal(_(undefined).truncate(10), '');
+ equal(_(1234567890).truncate(5), '12345...');
+ });
+
+ test('String: prune', function(){
+ equal(_('Hello, cruel world').prune(6, ' read more'), 'Hello read more');
+ equal(_('Hello, world').prune(5, 'read a lot more'), 'Hello, world');
+ equal(_('Hello, world').prune(5), 'Hello...');
+ equal(_('Hello, world').prune(8), 'Hello...');
+ equal(_('Hello, cruel world').prune(15), 'Hello, cruel...');
+ equal(_('Hello world').prune(22), 'Hello world');
+ equal(_('Привет, жестокий мир').prune(6, ' read more'), 'Привет read more');
+ equal(_('Привет, мир').prune(6, 'read a lot more'), 'Привет, мир');
+ equal(_('Привет, мир').prune(6), 'Привет...');
+ equal(_('Привет, мир').prune(8), 'Привет...');
+ equal(_('Привет, жестокий мир').prune(16), 'Привет, жестокий...');
+ equal(_('Привет, мир').prune(22), 'Привет, мир');
+ equal(_('alksjd!!!!!!....').prune(100, ''), 'alksjd!!!!!!....');
+ equal(_(123).prune(10), '123');
+ equal(_(123).prune(1, 321), '321');
+ equal(_('').prune(5), '');
+ equal(_(null).prune(5), '');
+ equal(_(undefined).prune(5), '');
+ });
+
+ test('String: isBlank', function(){
+ ok(_('').isBlank());
+ ok(_(' ').isBlank());
+ ok(_('\n').isBlank());
+ ok(!_('a').isBlank());
+ ok(!_('0').isBlank());
+ ok(!_(0).isBlank());
+ ok(_('').isBlank());
+ ok(_(null).isBlank());
+ ok(_(undefined).isBlank());
+ });
+
+ test('String: escapeRegExp', function(){
+ equal(_.escapeRegExp(/hello(?=\sworld)/.source), 'hello\\(\\?\\=\\\\sworld\\)', 'with lookahead');
+ equal(_.escapeRegExp(/hello(?!\shell)/.source), 'hello\\(\\?\\!\\\\shell\\)', 'with negative lookahead');
+ });
+
+ test('String: escapeHTML', function(){
+ equal(_('Blah & "blah" & \'blah\'
').escapeHTML(),
+ '<div>Blah & "blah" & 'blah'</div>');
+ equal(_('<').escapeHTML(), '<');
+ equal(_(5).escapeHTML(), '5');
+ equal(_('').escapeHTML(), '');
+ equal(_(null).escapeHTML(), '');
+ equal(_(undefined).escapeHTML(), '');
+ });
+
+ test('String: unescapeHTML', function(){
+ equal(_('<div>Blah & "blah" & 'blah'</div>').unescapeHTML(),
+ 'Blah & "blah" & \'blah\'
');
+ equal(_('<').unescapeHTML(), '<');
+ equal(_(''').unescapeHTML(), '\'');
+ equal(_(''').unescapeHTML(), '\'');
+ equal(_(''').unescapeHTML(), '\'');
+ equal(_('J').unescapeHTML(), 'J');
+ equal(_('J').unescapeHTML(), 'J');
+ equal(_('J').unescapeHTML(), 'J');
+ equal(_('&_#39;').unescapeHTML(), '&_#39;');
+ equal(_(''_;').unescapeHTML(), ''_;');
+ equal(_('&').unescapeHTML(), '&');
+ equal(_('&').unescapeHTML(), '&');
+ equal(_('').unescapeHTML(), '');
+ equal(_(null).unescapeHTML(), '');
+ equal(_(undefined).unescapeHTML(), '');
+ equal(_(5).unescapeHTML(), '5');
+ // equal(_(undefined).unescapeHTML(), '');
+ });
+
+ test('String: words', function() {
+ deepEqual(_('I love you!').words(), ['I', 'love', 'you!']);
+ deepEqual(_(' I love you! ').words(), ['I', 'love', 'you!']);
+ deepEqual(_('I_love_you!').words('_'), ['I', 'love', 'you!']);
+ deepEqual(_('I-love-you!').words(/-/), ['I', 'love', 'you!']);
+ deepEqual(_(123).words(), ['123'], '123 number has one word "123".');
+ deepEqual(_(0).words(), ['0'], 'Zero number has one word "0".');
+ deepEqual(_('').words(), [], 'Empty strings has no words.');
+ deepEqual(_(' ').words(), [], 'Blank strings has no words.');
+ deepEqual(_(null).words(), [], 'null has no words.');
+ deepEqual(_(undefined).words(), [], 'undefined has no words.');
+ });
+
+ test('String: chars', function() {
+ equal(_('Hello').chars().length, 5);
+ equal(_(123).chars().length, 3);
+ equal(_('').chars().length, 0);
+ equal(_(null).chars().length, 0);
+ equal(_(undefined).chars().length, 0);
+ });
+
+ test('String: swapCase', function(){
+ equal(_('AaBbCcDdEe').swapCase(), 'aAbBcCdDeE');
+ equal(_('Hello World').swapCase(), 'hELLO wORLD');
+ equal(_('').swapCase(), '');
+ equal(_(null).swapCase(), '');
+ equal(_(undefined).swapCase(), '');
+ });
+
+ test('String: lines', function() {
+ equal(_('Hello\nWorld').lines().length, 2);
+ equal(_('Hello World').lines().length, 1);
+ equal(_(123).lines().length, 1);
+ equal(_('').lines().length, 1);
+ equal(_(null).lines().length, 0);
+ equal(_(undefined).lines().length, 0);
+ });
+
+ test('String: pad', function() {
+ equal(_('1').pad(8), ' 1');
+ equal(_(1).pad(8), ' 1');
+ equal(_('1').pad(8, '0'), '00000001');
+ equal(_('1').pad(8, '0', 'left'), '00000001');
+ equal(_('1').pad(8, '0', 'right'), '10000000');
+ equal(_('1').pad(8, '0', 'both'), '00001000');
+ equal(_('foo').pad(8, '0', 'both'), '000foo00');
+ equal(_('foo').pad(7, '0', 'both'), '00foo00');
+ equal(_('foo').pad(7, '!@$%dofjrofj', 'both'), '!!foo!!');
+ equal(_('').pad(2), ' ');
+ equal(_(null).pad(2), ' ');
+ equal(_(undefined).pad(2), ' ');
+ });
+
+ test('String: lpad', function() {
+ equal(_('1').lpad(8), ' 1');
+ equal(_(1).lpad(8), ' 1');
+ equal(_('1').lpad(8, '0'), '00000001');
+ equal(_('1').lpad(8, '0', 'left'), '00000001');
+ equal(_('').lpad(2), ' ');
+ equal(_(null).lpad(2), ' ');
+ equal(_(undefined).lpad(2), ' ');
+ });
+
+ test('String: rpad', function() {
+ equal(_('1').rpad(8), '1 ');
+ equal(_(1).lpad(8), ' 1');
+ equal(_('1').rpad(8, '0'), '10000000');
+ equal(_('foo').rpad(8, '0'), 'foo00000');
+ equal(_('foo').rpad(7, '0'), 'foo0000');
+ equal(_('').rpad(2), ' ');
+ equal(_(null).rpad(2), ' ');
+ equal(_(undefined).rpad(2), ' ');
+ });
+
+ test('String: lrpad', function() {
+ equal(_('1').lrpad(8), ' 1 ');
+ equal(_(1).lrpad(8), ' 1 ');
+ equal(_('1').lrpad(8, '0'), '00001000');
+ equal(_('foo').lrpad(8, '0'), '000foo00');
+ equal(_('foo').lrpad(7, '0'), '00foo00');
+ equal(_('foo').lrpad(7, '!@$%dofjrofj'), '!!foo!!');
+ equal(_('').lrpad(2), ' ');
+ equal(_(null).lrpad(2), ' ');
+ equal(_(undefined).lrpad(2), ' ');
+ });
+
+ test('String: toNumber', function() {
+ deepEqual(_('not a number').toNumber(), NaN);
+ equal(_(0).toNumber(), 0);
+ equal(_('0').toNumber(), 0);
+ equal(_('0.0').toNumber(), 0);
+ equal(_('0.1').toNumber(), 0);
+ equal(_('0.1').toNumber(1), 0.1);
+ equal(_(' 0.1 ').toNumber(1), 0.1);
+ equal(_('0000').toNumber(), 0);
+ equal(_('2.345').toNumber(), 2);
+ equal(_('2.345').toNumber(NaN), 2);
+ equal(_('2.345').toNumber(2), 2.35);
+ equal(_('2.344').toNumber(2), 2.34);
+ equal(_('2').toNumber(2), 2.00);
+ equal(_(2).toNumber(2), 2.00);
+ equal(_(-2).toNumber(), -2);
+ equal(_('-2').toNumber(), -2);
+ equal(_('').toNumber(), 0);
+ equal(_(null).toNumber(), 0);
+ equal(_(undefined).toNumber(), 0);
+ });
+
+ test('String: numberFormat', function() {
+ equal(_.numberFormat(9000), '9,000');
+ equal(_.numberFormat(9000, 0), '9,000');
+ equal(_.numberFormat(9000, 0, '', ''), '9000');
+ equal(_.numberFormat(90000, 2), '90,000.00');
+ equal(_.numberFormat(1000.754), '1,001');
+ equal(_.numberFormat(1000.754, 2), '1,000.75');
+ equal(_.numberFormat(1000.754, 0, ',', '.'), '1.001');
+ equal(_.numberFormat(1000.754, 2, ',', '.'), '1.000,75');
+ equal(_.numberFormat(1000000.754, 2, ',', '.'), '1.000.000,75');
+ equal(_.numberFormat(1000000000), '1,000,000,000');
+ equal(_.numberFormat(100000000), '100,000,000');
+ equal(_.numberFormat('not number'), '');
+ equal(_.numberFormat(), '');
+ equal(_.numberFormat(null, '.', ','), '');
+ equal(_.numberFormat(undefined, '.', ','), '');
+ equal(_.numberFormat(new Number(5000)), '5,000');
+ });
+
+ test('String: strRight', function() {
+ equal(_('This_is_a_test_string').strRight('_'), 'is_a_test_string');
+ equal(_('This_is_a_test_string').strRight('string'), '');
+ equal(_('This_is_a_test_string').strRight(), 'This_is_a_test_string');
+ equal(_('This_is_a_test_string').strRight(''), 'This_is_a_test_string');
+ equal(_('This_is_a_test_string').strRight('-'), 'This_is_a_test_string');
+ equal(_('This_is_a_test_string').strRight(''), 'This_is_a_test_string');
+ equal(_('').strRight('foo'), '');
+ equal(_(null).strRight('foo'), '');
+ equal(_(undefined).strRight('foo'), '');
+ equal(_(12345).strRight(2), '345');
+ });
+
+ test('String: strRightBack', function() {
+ equal(_('This_is_a_test_string').strRightBack('_'), 'string');
+ equal(_('This_is_a_test_string').strRightBack('string'), '');
+ equal(_('This_is_a_test_string').strRightBack(), 'This_is_a_test_string');
+ equal(_('This_is_a_test_string').strRightBack(''), 'This_is_a_test_string');
+ equal(_('This_is_a_test_string').strRightBack('-'), 'This_is_a_test_string');
+ equal(_('').strRightBack('foo'), '');
+ equal(_(null).strRightBack('foo'), '');
+ equal(_(undefined).strRightBack('foo'), '');
+ equal(_(12345).strRightBack(2), '345');
+ });
+
+ test('String: strLeft', function() {
+ equal(_('This_is_a_test_string').strLeft('_'), 'This');
+ equal(_('This_is_a_test_string').strLeft('This'), '');
+ equal(_('This_is_a_test_string').strLeft(), 'This_is_a_test_string');
+ equal(_('This_is_a_test_string').strLeft(''), 'This_is_a_test_string');
+ equal(_('This_is_a_test_string').strLeft('-'), 'This_is_a_test_string');
+ equal(_('').strLeft('foo'), '');
+ equal(_(null).strLeft('foo'), '');
+ equal(_(undefined).strLeft('foo'), '');
+ equal(_(123454321).strLeft(3), '12');
+ });
+
+ test('String: strLeftBack', function() {
+ equal(_('This_is_a_test_string').strLeftBack('_'), 'This_is_a_test');
+ equal(_('This_is_a_test_string').strLeftBack('This'), '');
+ equal(_('This_is_a_test_string').strLeftBack(), 'This_is_a_test_string');
+ equal(_('This_is_a_test_string').strLeftBack(''), 'This_is_a_test_string');
+ equal(_('This_is_a_test_string').strLeftBack('-'), 'This_is_a_test_string');
+ equal(_('').strLeftBack('foo'), '');
+ equal(_(null).strLeftBack('foo'), '');
+ equal(_(undefined).strLeftBack('foo'), '');
+ equal(_(123454321).strLeftBack(3), '123454');
+ });
+
+ test('Strings: stripTags', function() {
+ equal(_('a link').stripTags(), 'a link');
+ equal(_('a link
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+