judge 1.5.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/README.md +273 -14
  2. data/app/assets/javascripts/judge.js +391 -0
  3. data/app/controllers/judge/validations_controller.rb +9 -0
  4. data/app/models/judge/validation.rb +53 -0
  5. data/config/routes.rb +3 -0
  6. data/lib/generators/judge/install/install_generator.rb +42 -0
  7. data/lib/judge.rb +13 -8
  8. data/lib/judge/config.rb +39 -0
  9. data/lib/judge/controller.rb +35 -0
  10. data/lib/judge/each_validator.rb +5 -4
  11. data/lib/judge/engine.rb +5 -0
  12. data/lib/judge/form_builder.rb +19 -24
  13. data/lib/judge/html.rb +5 -7
  14. data/lib/judge/message_collection.rb +2 -1
  15. data/lib/judge/message_config.rb +2 -1
  16. data/lib/judge/validator.rb +3 -1
  17. data/lib/judge/validator_collection.rb +1 -1
  18. data/lib/judge/version.rb +2 -2
  19. data/lib/tasks/judge_tasks.rake +4 -0
  20. data/{lib/generators/judge/templates → vendor/assets/javascripts}/json2.js +4 -5
  21. data/{lib/generators/judge/templates → vendor/assets/javascripts}/underscore.js +451 -285
  22. metadata +94 -87
  23. data/.gitignore +0 -9
  24. data/.travis.yml +0 -21
  25. data/Gemfile +0 -3
  26. data/Rakefile +0 -11
  27. data/judge.gemspec +0 -23
  28. data/lib/generators/judge/judge_generator.rb +0 -21
  29. data/lib/generators/judge/templates/judge.js +0 -330
  30. data/spec/each_validator_spec.rb +0 -17
  31. data/spec/form_builder_spec.rb +0 -68
  32. data/spec/html_spec.rb +0 -14
  33. data/spec/javascripts/JudgeSpec.js +0 -509
  34. data/spec/javascripts/fixtures/form.html +0 -538
  35. data/spec/javascripts/helpers/customMatchers.js +0 -20
  36. data/spec/javascripts/helpers/jasmine-jquery.js +0 -204
  37. data/spec/javascripts/helpers/jquery-1.5.1.min.js +0 -18
  38. data/spec/javascripts/helpers/json2.js +0 -487
  39. data/spec/javascripts/helpers/underscore.js +0 -1060
  40. data/spec/javascripts/support/jasmine.yml +0 -79
  41. data/spec/javascripts/support/jasmine_config.rb +0 -6
  42. data/spec/javascripts/support/jasmine_runner.rb +0 -21
  43. data/spec/javascripts/support/runner.js +0 -51
  44. data/spec/message_collection_spec.rb +0 -73
  45. data/spec/setup.rb +0 -75
  46. data/spec/support/factories.rb +0 -23
  47. data/spec/support/locale/en.yml +0 -18
  48. data/spec/support/setup.rb +0 -72
  49. data/spec/support/spec_helper.rb +0 -13
  50. data/spec/support/validators/city_validator.rb +0 -9
  51. data/spec/validator_collection_spec.rb +0 -21
  52. data/spec/validator_spec.rb +0 -33
@@ -0,0 +1,9 @@
1
+ module Judge
2
+ class ValidationsController < ::ApplicationController
3
+ include Judge::Controller
4
+
5
+ def build
6
+ respond_with(validation(params))
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,53 @@
1
+ module Judge
2
+
3
+ class Validation
4
+ def initialize(params)
5
+ @klass = params[:klass]
6
+ @attribute = params[:attribute]
7
+ @value = params[:value]
8
+ @kind = params[:kind]
9
+ validate!
10
+ end
11
+
12
+ def amv
13
+ @amv ||= begin
14
+ validators = @klass.validators_on(@attribute)
15
+ validators.keep_if { |amv| amv.kind == @kind }
16
+ validators.first
17
+ end
18
+ end
19
+
20
+ def record
21
+ @record ||= begin
22
+ rec = @klass.new
23
+ rec[@attribute] = @value
24
+ rec
25
+ end
26
+ end
27
+
28
+ def validate!
29
+ record.errors.delete(@attribute)
30
+ amv.validate_each(record, @attribute, @value)
31
+ self
32
+ end
33
+
34
+ def as_json(options = {})
35
+ record.errors.get(@attribute) || []
36
+ end
37
+ end
38
+
39
+ class NullValidation
40
+ def initialize(params)
41
+ @params = params
42
+ end
43
+
44
+ def as_json(options = {})
45
+ ["Judge validation for #{@params[:klass]}##{@params[:attribute]} not allowed"]
46
+ end
47
+
48
+ def method_missing(*args)
49
+ self
50
+ end
51
+ end
52
+
53
+ end
@@ -0,0 +1,3 @@
1
+ Judge::Engine.routes.draw do
2
+ root :to => "validations#build", :as => :build_validation, :via => :get
3
+ end
@@ -0,0 +1,42 @@
1
+ module Judge
2
+ module Generators
3
+ class InstallGenerator < ::Rails::Generators::Base
4
+
5
+ desc %Q{For use where asset pipeline is disabled.
6
+ Installs judge.js and optionally installs underscore.js and json2.js.
7
+
8
+ Copy judge.js to public/javascripts:
9
+ $ rails generate judge:install
10
+
11
+ Copy judge.js to different path:
12
+ $ rails generate judge:install path
13
+
14
+ Copy judge.js and dependencies:
15
+ $ rails generate judge:install path --underscore --json2
16
+ }
17
+
18
+ argument :path, :type => :string, :default => "public/javascripts"
19
+ class_option :underscore, :type => :boolean, :default => false, :desc => "Install underscore.js"
20
+ class_option :json2, :type => :boolean, :default => false, :desc => "Install json2.js"
21
+ source_root File.expand_path("../../../../..", __FILE__)
22
+
23
+ def exec
24
+ unless !::Rails.application.config.assets.enabled
25
+ say_status("deprecated", "You don't need to use this generator as your app is running on Rails >= 3.1 with the asset pipeline enabled")
26
+ return
27
+ end
28
+ say_status("copying", "judge.js", :green)
29
+ copy_file("app/assets/javascripts/judge.js", "#{path}/judge.js")
30
+ if options.underscore?
31
+ say_status("copying", "underscore.js", :green)
32
+ copy_file("vendor/assets/javascripts/underscore.js", "#{path}/underscore.js")
33
+ end
34
+ if options.json2?
35
+ say_status("copying", "json2.js", :green)
36
+ copy_file("vendor/assets/javascripts/json2.js", "#{path}/json2.js")
37
+ end
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -1,8 +1,13 @@
1
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "judge"))
2
- require "version"
3
- require "validator"
4
- require "validator_collection"
5
- require "message_collection"
6
- require "form_builder"
7
- require "html"
8
- require "each_validator"
1
+ files = [
2
+ 'version',
3
+ 'config',
4
+ 'engine',
5
+ 'validator',
6
+ 'validator_collection',
7
+ 'message_collection',
8
+ 'html',
9
+ 'form_builder',
10
+ 'each_validator',
11
+ 'controller'
12
+ ]
13
+ files.each { |filename| require "judge/#{filename}" }
@@ -0,0 +1,39 @@
1
+ require 'singleton'
2
+
3
+ module Judge
4
+ class Config
5
+ include Singleton
6
+
7
+ @@exposed = {}
8
+
9
+ def expose(klass, *attributes)
10
+ attrs = (@@exposed[klass] ||= [])
11
+ attrs.concat(attributes).uniq!
12
+ end
13
+
14
+ def exposed
15
+ @@exposed
16
+ end
17
+
18
+ def exposed?(klass, attribute)
19
+ @@exposed.has_key?(klass) && @@exposed[klass].include?(attribute)
20
+ end
21
+
22
+ def unexpose(klass, *attributes)
23
+ attributes.each do |a|
24
+ @@exposed[klass].delete(a)
25
+ end
26
+ if attributes.empty? || @@exposed[klass].empty?
27
+ @@exposed.delete(klass)
28
+ end
29
+ end
30
+ end
31
+
32
+ def self.config
33
+ Config.instance
34
+ end
35
+
36
+ def self.configure(&block)
37
+ Config.instance.instance_eval(&block)
38
+ end
39
+ end
@@ -0,0 +1,35 @@
1
+ module Judge
2
+ module Controller
3
+
4
+ def self.included(base)
5
+ base.clear_respond_to
6
+ base.respond_to(:json)
7
+ end
8
+
9
+ def validation(params)
10
+ params = normalize_validation_params(params)
11
+ if params[:klass] && Judge.config.exposed?(params[:klass], params[:attribute])
12
+ Validation.new(params)
13
+ else
14
+ NullValidation.new(params)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def normalize_validation_params(params)
21
+ params.keep_if { |key| %w{klass attribute value kind}.include?(key) }
22
+ params[:klass] = find_klass(params[:klass])
23
+ params[:attribute] = params[:attribute].to_sym
24
+ params[:kind] = params[:kind].to_sym
25
+ params
26
+ end
27
+
28
+ def find_klass(name)
29
+ Module.const_get(name.classify)
30
+ rescue NameError
31
+ nil
32
+ end
33
+
34
+ end
35
+ end
@@ -1,16 +1,17 @@
1
1
  module Judge
2
2
  module EachValidator
3
3
 
4
+ require 'set'
5
+
4
6
  def self.included(base)
7
+ base.send(:cattr_accessor, :messages_to_lookup) { Set.new }
5
8
  base.send(:extend, ClassMethods)
6
9
  end
7
10
 
8
11
  module ClassMethods
9
12
 
10
- def declare_messages(*keys)
11
- send :define_method, :messages_to_lookup do
12
- keys
13
- end
13
+ def uses_messages(*keys)
14
+ self.messages_to_lookup.merge(keys)
14
15
  end
15
16
 
16
17
  end
@@ -0,0 +1,5 @@
1
+ module Judge
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Judge
4
+ end
5
+ end
@@ -1,13 +1,13 @@
1
1
  module Judge
2
2
 
3
3
  class FormBuilder < ActionView::Helpers::FormBuilder
4
+
5
+ include Judge::Html
4
6
 
5
7
  %w{text_field text_area password_field}.each do |type|
6
8
  helper = <<-END
7
9
  def #{type}(method, options = {})
8
- if options.delete(:validate).present?
9
- options = Judge::HTML.attrs_for(self.object, method).merge(options)
10
- end
10
+ add_validate_attr!(self.object, method, options)
11
11
  super
12
12
  end
13
13
  END
@@ -15,46 +15,34 @@ module Judge
15
15
  end
16
16
 
17
17
  def radio_button(method, tag_value, options = {})
18
- if options.delete(:validate).present?
19
- options = Judge::HTML.attrs_for(self.object, method).merge(options)
20
- end
18
+ add_validate_attr!(self.object, method, options)
21
19
  super
22
20
  end
23
21
 
24
22
  def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
25
- if options.delete(:validate).present?
26
- options = Judge::HTML.attrs_for(self.object, method).merge(options)
27
- end
23
+ add_validate_attr!(self.object, method, options)
28
24
  super
29
25
  end
30
26
 
31
27
  def select(method, choices, options = {}, html_options = {})
32
- if options.delete(:validate).present?
33
- html_options = Judge::HTML.attrs_for(self.object, method).merge(html_options)
34
- end
28
+ add_validate_attr!(self.object, method, options, html_options)
35
29
  super
36
30
  end
37
31
 
38
32
  def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
39
- if options.delete(:validate).present?
40
- html_options = Judge::HTML.attrs_for(self.object, method).merge(html_options)
41
- end
33
+ add_validate_attr!(self.object, method, options, html_options)
42
34
  super
43
35
  end
44
36
 
45
37
  def grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
46
- if options.delete(:validate).present?
47
- html_options = Judge::HTML.attrs_for(self.object, method).merge(html_options)
48
- end
38
+ add_validate_attr!(self.object, method, options, html_options)
49
39
  super
50
40
  end
51
41
 
52
42
  %w{date_select datetime_select time_select}.each do |type|
53
43
  helper = <<-END
54
44
  def #{type}(method, options = {}, html_options = {})
55
- if options.delete(:validate).present?
56
- html_options = Judge::HTML.attrs_for(self.object, method).merge(html_options)
57
- end
45
+ add_validate_attr!(self.object, method, options, html_options)
58
46
  super
59
47
  end
60
48
  END
@@ -62,11 +50,18 @@ module Judge
62
50
  end
63
51
 
64
52
  def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
65
- if options.delete(:validate).present?
66
- html_options = Judge::HTML.attrs_for(self.object, method).merge(html_options)
67
- end
53
+ add_validate_attr!(self.object, method, options, html_options)
68
54
  super
69
55
  end
56
+
57
+ private
58
+
59
+ def add_validate_attr!(object, method, options, html_options = nil)
60
+ options_to_merge = html_options || options
61
+ if options.delete(:validate)
62
+ options_to_merge.merge! attrs_for(object, method)
63
+ end
64
+ end
70
65
 
71
66
  end
72
67
 
@@ -1,13 +1,11 @@
1
1
  module Judge
2
-
3
- module HTML
4
-
2
+ module Html
5
3
  extend self
6
-
4
+
7
5
  def attrs_for(object, method)
8
- { "data-validate" => Judge::ValidatorCollection.new(object, method).to_json }
6
+ {
7
+ "data-validate" => ValidatorCollection.new(object, method).to_json
8
+ }
9
9
  end
10
-
11
10
  end
12
-
13
11
  end
@@ -1,4 +1,4 @@
1
- require 'message_config'
1
+ require "judge/message_config"
2
2
 
3
3
  module Judge
4
4
 
@@ -19,6 +19,7 @@ module Judge
19
19
  end
20
20
 
21
21
  def generate_messages!
22
+ return if @kind == :uniqueness
22
23
  %w{base options integer custom blank}.each do |type|
23
24
  @messages = @messages.merge(self.send(:"#{type}_messages"))
24
25
  end
@@ -33,7 +33,8 @@ module Judge
33
33
  :odd => :odd,
34
34
  :even => :even
35
35
  }
36
- }
36
+ },
37
+ :uniqueness => { :base => :taken }
37
38
  }
38
39
 
39
40
  end
@@ -4,9 +4,11 @@ module Judge
4
4
 
5
5
  attr_reader :active_model_validator, :kind, :options, :method, :messages
6
6
 
7
+ REJECTED_OPTIONS = [:if, :on, :unless, :tokenizer, :scope, :case_sensitive]
8
+
7
9
  def initialize(object, method, amv)
8
10
  @kind = amv.kind
9
- @options = amv.options.reject { |key| [:if, :on, :unless, :tokenizer].include?(key) }
11
+ @options = amv.options.reject { |key| REJECTED_OPTIONS.include?(key) }
10
12
  @method = method
11
13
  @messages = Judge::MessageCollection.new(object, method, amv)
12
14
  end
@@ -7,7 +7,7 @@ module Judge
7
7
  attr_reader :validators
8
8
 
9
9
  def initialize(object, method)
10
- amvs = object.class.validators_on(method).reject { |amv| amv.kind == :uniqueness }
10
+ amvs = object.class.validators_on(method)
11
11
  @validators = amvs.map { |amv| Judge::Validator.new(object, method, amv) }
12
12
  end
13
13
 
@@ -1,3 +1,3 @@
1
1
  module Judge
2
- VERSION = "1.5.0"
3
- end
2
+ VERSION = "2.0.0"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :judge do
3
+ # # Task goes here
4
+ # end
@@ -1,6 +1,6 @@
1
1
  /*
2
- http://www.JSON.org/json2.js
3
- 2011-10-19
2
+ json2.js
3
+ 2012-10-08
4
4
 
5
5
  Public Domain.
6
6
 
@@ -159,8 +159,7 @@
159
159
  // Create a JSON object only if one does not already exist. We create the
160
160
  // methods in a closure to avoid creating global variables.
161
161
 
162
- var JSON;
163
- if (!JSON) {
162
+ if (typeof JSON !== 'object') {
164
163
  JSON = {};
165
164
  }
166
165
 
@@ -484,4 +483,4 @@ if (!JSON) {
484
483
  throw new SyntaxError('JSON.parse');
485
484
  };
486
485
  }
487
- }());
486
+ }());
@@ -1,11 +1,7 @@
1
-
2
- // Underscore.js 1.3.2
3
- // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
4
- // Underscore is freely distributable under the MIT license.
5
- // Portions of Underscore are inspired or borrowed from Prototype,
6
- // Oliver Steele's Functional, and John Resig's Micro-Templating.
7
- // For all details and documentation:
8
- // http://documentcloud.github.com/underscore
1
+ // Underscore.js 1.4.4
2
+ // http://underscorejs.org
3
+ // (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc.
4
+ // Underscore may be freely distributed under the MIT license.
9
5
 
10
6
  (function() {
11
7
 
@@ -25,8 +21,9 @@
25
21
  var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
26
22
 
27
23
  // Create quick reference variables for speed access to core prototypes.
28
- var slice = ArrayProto.slice,
29
- unshift = ArrayProto.unshift,
24
+ var push = ArrayProto.push,
25
+ slice = ArrayProto.slice,
26
+ concat = ArrayProto.concat,
30
27
  toString = ObjProto.toString,
31
28
  hasOwnProperty = ObjProto.hasOwnProperty;
32
29
 
@@ -47,7 +44,11 @@
47
44
  nativeBind = FuncProto.bind;
48
45
 
49
46
  // Create a safe reference to the Underscore object for use below.
50
- var _ = function(obj) { return new wrapper(obj); };
47
+ var _ = function(obj) {
48
+ if (obj instanceof _) return obj;
49
+ if (!(this instanceof _)) return new _(obj);
50
+ this._wrapped = obj;
51
+ };
51
52
 
52
53
  // Export the Underscore object for **Node.js**, with
53
54
  // backwards-compatibility for the old `require()` API. If we're in
@@ -59,11 +60,11 @@
59
60
  }
60
61
  exports._ = _;
61
62
  } else {
62
- root['_'] = _;
63
+ root._ = _;
63
64
  }
64
65
 
65
66
  // Current version.
66
- _.VERSION = '1.3.2';
67
+ _.VERSION = '1.4.4';
67
68
 
68
69
  // Collection Functions
69
70
  // --------------------
@@ -77,7 +78,7 @@
77
78
  obj.forEach(iterator, context);
78
79
  } else if (obj.length === +obj.length) {
79
80
  for (var i = 0, l = obj.length; i < l; i++) {
80
- if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
81
+ if (iterator.call(context, obj[i], i, obj) === breaker) return;
81
82
  }
82
83
  } else {
83
84
  for (var key in obj) {
@@ -97,10 +98,11 @@
97
98
  each(obj, function(value, index, list) {
98
99
  results[results.length] = iterator.call(context, value, index, list);
99
100
  });
100
- if (obj.length === +obj.length) results.length = obj.length;
101
101
  return results;
102
102
  };
103
103
 
104
+ var reduceError = 'Reduce of empty array with no initial value';
105
+
104
106
  // **Reduce** builds up a single result from a list of values, aka `inject`,
105
107
  // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
106
108
  _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
@@ -118,7 +120,7 @@
118
120
  memo = iterator.call(context, memo, value, index, list);
119
121
  }
120
122
  });
121
- if (!initial) throw new TypeError('Reduce of empty array with no initial value');
123
+ if (!initial) throw new TypeError(reduceError);
122
124
  return memo;
123
125
  };
124
126
 
@@ -131,9 +133,22 @@
131
133
  if (context) iterator = _.bind(iterator, context);
132
134
  return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
133
135
  }
134
- var reversed = _.toArray(obj).reverse();
135
- if (context && !initial) iterator = _.bind(iterator, context);
136
- return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
136
+ var length = obj.length;
137
+ if (length !== +length) {
138
+ var keys = _.keys(obj);
139
+ length = keys.length;
140
+ }
141
+ each(obj, function(value, index, list) {
142
+ index = keys ? keys[--length] : --length;
143
+ if (!initial) {
144
+ memo = obj[index];
145
+ initial = true;
146
+ } else {
147
+ memo = iterator.call(context, memo, obj[index], index, list);
148
+ }
149
+ });
150
+ if (!initial) throw new TypeError(reduceError);
151
+ return memo;
137
152
  };
138
153
 
139
154
  // Return the first value which passes a truth test. Aliased as `detect`.
@@ -163,18 +178,16 @@
163
178
 
164
179
  // Return all the elements for which a truth test fails.
165
180
  _.reject = function(obj, iterator, context) {
166
- var results = [];
167
- if (obj == null) return results;
168
- each(obj, function(value, index, list) {
169
- if (!iterator.call(context, value, index, list)) results[results.length] = value;
170
- });
171
- return results;
181
+ return _.filter(obj, function(value, index, list) {
182
+ return !iterator.call(context, value, index, list);
183
+ }, context);
172
184
  };
173
185
 
174
186
  // Determine whether all of the elements match a truth test.
175
187
  // Delegates to **ECMAScript 5**'s native `every` if available.
176
188
  // Aliased as `all`.
177
189
  _.every = _.all = function(obj, iterator, context) {
190
+ iterator || (iterator = _.identity);
178
191
  var result = true;
179
192
  if (obj == null) return result;
180
193
  if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
@@ -198,23 +211,22 @@
198
211
  return !!result;
199
212
  };
200
213
 
201
- // Determine if a given value is included in the array or object using `===`.
202
- // Aliased as `contains`.
203
- _.include = _.contains = function(obj, target) {
204
- var found = false;
205
- if (obj == null) return found;
214
+ // Determine if the array or object contains a given value (using `===`).
215
+ // Aliased as `include`.
216
+ _.contains = _.include = function(obj, target) {
217
+ if (obj == null) return false;
206
218
  if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
207
- found = any(obj, function(value) {
219
+ return any(obj, function(value) {
208
220
  return value === target;
209
221
  });
210
- return found;
211
222
  };
212
223
 
213
224
  // Invoke a method (with arguments) on every item in a collection.
214
225
  _.invoke = function(obj, method) {
215
226
  var args = slice.call(arguments, 2);
227
+ var isFunc = _.isFunction(method);
216
228
  return _.map(obj, function(value) {
217
- return (_.isFunction(method) ? method || value : value[method]).apply(value, args);
229
+ return (isFunc ? method : value[method]).apply(value, args);
218
230
  });
219
231
  };
220
232
 
@@ -223,11 +235,33 @@
223
235
  return _.map(obj, function(value){ return value[key]; });
224
236
  };
225
237
 
238
+ // Convenience version of a common use case of `filter`: selecting only objects
239
+ // containing specific `key:value` pairs.
240
+ _.where = function(obj, attrs, first) {
241
+ if (_.isEmpty(attrs)) return first ? null : [];
242
+ return _[first ? 'find' : 'filter'](obj, function(value) {
243
+ for (var key in attrs) {
244
+ if (attrs[key] !== value[key]) return false;
245
+ }
246
+ return true;
247
+ });
248
+ };
249
+
250
+ // Convenience version of a common use case of `find`: getting the first object
251
+ // containing specific `key:value` pairs.
252
+ _.findWhere = function(obj, attrs) {
253
+ return _.where(obj, attrs, true);
254
+ };
255
+
226
256
  // Return the maximum element or (element-based computation).
257
+ // Can't optimize arrays of integers longer than 65,535 elements.
258
+ // See: https://bugs.webkit.org/show_bug.cgi?id=80797
227
259
  _.max = function(obj, iterator, context) {
228
- if (!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.max.apply(Math, obj);
260
+ if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
261
+ return Math.max.apply(Math, obj);
262
+ }
229
263
  if (!iterator && _.isEmpty(obj)) return -Infinity;
230
- var result = {computed : -Infinity};
264
+ var result = {computed : -Infinity, value: -Infinity};
231
265
  each(obj, function(value, index, list) {
232
266
  var computed = iterator ? iterator.call(context, value, index, list) : value;
233
267
  computed >= result.computed && (result = {value : value, computed : computed});
@@ -237,9 +271,11 @@
237
271
 
238
272
  // Return the minimum element (or element-based computation).
239
273
  _.min = function(obj, iterator, context) {
240
- if (!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.min.apply(Math, obj);
274
+ if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
275
+ return Math.min.apply(Math, obj);
276
+ }
241
277
  if (!iterator && _.isEmpty(obj)) return Infinity;
242
- var result = {computed : Infinity};
278
+ var result = {computed : Infinity, value: Infinity};
243
279
  each(obj, function(value, index, list) {
244
280
  var computed = iterator ? iterator.call(context, value, index, list) : value;
245
281
  computed < result.computed && (result = {value : value, computed : computed});
@@ -249,67 +285,96 @@
249
285
 
250
286
  // Shuffle an array.
251
287
  _.shuffle = function(obj) {
252
- var shuffled = [], rand;
253
- each(obj, function(value, index, list) {
254
- rand = Math.floor(Math.random() * (index + 1));
255
- shuffled[index] = shuffled[rand];
288
+ var rand;
289
+ var index = 0;
290
+ var shuffled = [];
291
+ each(obj, function(value) {
292
+ rand = _.random(index++);
293
+ shuffled[index - 1] = shuffled[rand];
256
294
  shuffled[rand] = value;
257
295
  });
258
296
  return shuffled;
259
297
  };
260
298
 
299
+ // An internal function to generate lookup iterators.
300
+ var lookupIterator = function(value) {
301
+ return _.isFunction(value) ? value : function(obj){ return obj[value]; };
302
+ };
303
+
261
304
  // Sort the object's values by a criterion produced by an iterator.
262
- _.sortBy = function(obj, val, context) {
263
- var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
305
+ _.sortBy = function(obj, value, context) {
306
+ var iterator = lookupIterator(value);
264
307
  return _.pluck(_.map(obj, function(value, index, list) {
265
308
  return {
266
309
  value : value,
310
+ index : index,
267
311
  criteria : iterator.call(context, value, index, list)
268
312
  };
269
313
  }).sort(function(left, right) {
270
- var a = left.criteria, b = right.criteria;
271
- if (a === void 0) return 1;
272
- if (b === void 0) return -1;
273
- return a < b ? -1 : a > b ? 1 : 0;
314
+ var a = left.criteria;
315
+ var b = right.criteria;
316
+ if (a !== b) {
317
+ if (a > b || a === void 0) return 1;
318
+ if (a < b || b === void 0) return -1;
319
+ }
320
+ return left.index < right.index ? -1 : 1;
274
321
  }), 'value');
275
322
  };
276
323
 
277
- // Groups the object's values by a criterion. Pass either a string attribute
278
- // to group by, or a function that returns the criterion.
279
- _.groupBy = function(obj, val) {
324
+ // An internal function used for aggregate "group by" operations.
325
+ var group = function(obj, value, context, behavior) {
280
326
  var result = {};
281
- var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
327
+ var iterator = lookupIterator(value || _.identity);
282
328
  each(obj, function(value, index) {
283
- var key = iterator(value, index);
284
- (result[key] || (result[key] = [])).push(value);
329
+ var key = iterator.call(context, value, index, obj);
330
+ behavior(result, key, value);
285
331
  });
286
332
  return result;
287
333
  };
288
334
 
289
- // Use a comparator function to figure out at what index an object should
290
- // be inserted so as to maintain order. Uses binary search.
291
- _.sortedIndex = function(array, obj, iterator) {
292
- iterator || (iterator = _.identity);
335
+ // Groups the object's values by a criterion. Pass either a string attribute
336
+ // to group by, or a function that returns the criterion.
337
+ _.groupBy = function(obj, value, context) {
338
+ return group(obj, value, context, function(result, key, value) {
339
+ (_.has(result, key) ? result[key] : (result[key] = [])).push(value);
340
+ });
341
+ };
342
+
343
+ // Counts instances of an object that group by a certain criterion. Pass
344
+ // either a string attribute to count by, or a function that returns the
345
+ // criterion.
346
+ _.countBy = function(obj, value, context) {
347
+ return group(obj, value, context, function(result, key) {
348
+ if (!_.has(result, key)) result[key] = 0;
349
+ result[key]++;
350
+ });
351
+ };
352
+
353
+ // Use a comparator function to figure out the smallest index at which
354
+ // an object should be inserted so as to maintain order. Uses binary search.
355
+ _.sortedIndex = function(array, obj, iterator, context) {
356
+ iterator = iterator == null ? _.identity : lookupIterator(iterator);
357
+ var value = iterator.call(context, obj);
293
358
  var low = 0, high = array.length;
294
359
  while (low < high) {
295
- var mid = (low + high) >> 1;
296
- iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
360
+ var mid = (low + high) >>> 1;
361
+ iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
297
362
  }
298
363
  return low;
299
364
  };
300
365
 
301
366
  // Safely convert anything iterable into a real, live array.
302
367
  _.toArray = function(obj) {
303
- if (!obj) return [];
304
- if (_.isArray(obj)) return slice.call(obj);
305
- if (_.isArguments(obj)) return slice.call(obj);
306
- if (obj.toArray && _.isFunction(obj.toArray)) return obj.toArray();
368
+ if (!obj) return [];
369
+ if (_.isArray(obj)) return slice.call(obj);
370
+ if (obj.length === +obj.length) return _.map(obj, _.identity);
307
371
  return _.values(obj);
308
372
  };
309
373
 
310
374
  // Return the number of elements in an object.
311
375
  _.size = function(obj) {
312
- return _.isArray(obj) ? obj.length : _.keys(obj).length;
376
+ if (obj == null) return 0;
377
+ return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
313
378
  };
314
379
 
315
380
  // Array Functions
@@ -319,10 +384,11 @@
319
384
  // values in the array. Aliased as `head` and `take`. The **guard** check
320
385
  // allows it to work with `_.map`.
321
386
  _.first = _.head = _.take = function(array, n, guard) {
387
+ if (array == null) return void 0;
322
388
  return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
323
389
  };
324
390
 
325
- // Returns everything but the last entry of the array. Especcialy useful on
391
+ // Returns everything but the last entry of the array. Especially useful on
326
392
  // the arguments object. Passing **n** will return all the values in
327
393
  // the array, excluding the last N. The **guard** check allows it to work with
328
394
  // `_.map`.
@@ -333,6 +399,7 @@
333
399
  // Get the last element of an array. Passing **n** will return the last N
334
400
  // values in the array. The **guard** check allows it to work with `_.map`.
335
401
  _.last = function(array, n, guard) {
402
+ if (array == null) return void 0;
336
403
  if ((n != null) && !guard) {
337
404
  return slice.call(array, Math.max(array.length - n, 0));
338
405
  } else {
@@ -340,26 +407,34 @@
340
407
  }
341
408
  };
342
409
 
343
- // Returns everything but the first entry of the array. Aliased as `tail`.
344
- // Especially useful on the arguments object. Passing an **index** will return
345
- // the rest of the values in the array from that index onward. The **guard**
410
+ // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
411
+ // Especially useful on the arguments object. Passing an **n** will return
412
+ // the rest N values in the array. The **guard**
346
413
  // check allows it to work with `_.map`.
347
- _.rest = _.tail = function(array, index, guard) {
348
- return slice.call(array, (index == null) || guard ? 1 : index);
414
+ _.rest = _.tail = _.drop = function(array, n, guard) {
415
+ return slice.call(array, (n == null) || guard ? 1 : n);
349
416
  };
350
417
 
351
418
  // Trim out all falsy values from an array.
352
419
  _.compact = function(array) {
353
- return _.filter(array, function(value){ return !!value; });
420
+ return _.filter(array, _.identity);
421
+ };
422
+
423
+ // Internal implementation of a recursive `flatten` function.
424
+ var flatten = function(input, shallow, output) {
425
+ each(input, function(value) {
426
+ if (_.isArray(value)) {
427
+ shallow ? push.apply(output, value) : flatten(value, shallow, output);
428
+ } else {
429
+ output.push(value);
430
+ }
431
+ });
432
+ return output;
354
433
  };
355
434
 
356
435
  // Return a completely flattened version of an array.
357
436
  _.flatten = function(array, shallow) {
358
- return _.reduce(array, function(memo, value) {
359
- if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value));
360
- memo[memo.length] = value;
361
- return memo;
362
- }, []);
437
+ return flatten(array, shallow, []);
363
438
  };
364
439
 
365
440
  // Return a version of the array that does not contain the specified value(s).
@@ -370,30 +445,33 @@
370
445
  // Produce a duplicate-free version of the array. If the array has already
371
446
  // been sorted, you have the option of using a faster algorithm.
372
447
  // Aliased as `unique`.
373
- _.uniq = _.unique = function(array, isSorted, iterator) {
374
- var initial = iterator ? _.map(array, iterator) : array;
448
+ _.uniq = _.unique = function(array, isSorted, iterator, context) {
449
+ if (_.isFunction(isSorted)) {
450
+ context = iterator;
451
+ iterator = isSorted;
452
+ isSorted = false;
453
+ }
454
+ var initial = iterator ? _.map(array, iterator, context) : array;
375
455
  var results = [];
376
- // The `isSorted` flag is irrelevant if the array only contains two elements.
377
- if (array.length < 3) isSorted = true;
378
- _.reduce(initial, function (memo, value, index) {
379
- if (isSorted ? _.last(memo) !== value || !memo.length : !_.include(memo, value)) {
380
- memo.push(value);
456
+ var seen = [];
457
+ each(initial, function(value, index) {
458
+ if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) {
459
+ seen.push(value);
381
460
  results.push(array[index]);
382
461
  }
383
- return memo;
384
- }, []);
462
+ });
385
463
  return results;
386
464
  };
387
465
 
388
466
  // Produce an array that contains the union: each distinct element from all of
389
467
  // the passed-in arrays.
390
468
  _.union = function() {
391
- return _.uniq(_.flatten(arguments, true));
469
+ return _.uniq(concat.apply(ArrayProto, arguments));
392
470
  };
393
471
 
394
472
  // Produce an array that contains every item shared between all the
395
- // passed-in arrays. (Aliased as "intersect" for back-compat.)
396
- _.intersection = _.intersect = function(array) {
473
+ // passed-in arrays.
474
+ _.intersection = function(array) {
397
475
  var rest = slice.call(arguments, 1);
398
476
  return _.filter(_.uniq(array), function(item) {
399
477
  return _.every(rest, function(other) {
@@ -405,8 +483,8 @@
405
483
  // Take the difference between one array and a number of other arrays.
406
484
  // Only the elements present in just the first array will remain.
407
485
  _.difference = function(array) {
408
- var rest = _.flatten(slice.call(arguments, 1), true);
409
- return _.filter(array, function(value){ return !_.include(rest, value); });
486
+ var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
487
+ return _.filter(array, function(value){ return !_.contains(rest, value); });
410
488
  };
411
489
 
412
490
  // Zip together multiple lists into a single array -- elements that share
@@ -415,10 +493,28 @@
415
493
  var args = slice.call(arguments);
416
494
  var length = _.max(_.pluck(args, 'length'));
417
495
  var results = new Array(length);
418
- for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i);
496
+ for (var i = 0; i < length; i++) {
497
+ results[i] = _.pluck(args, "" + i);
498
+ }
419
499
  return results;
420
500
  };
421
501
 
502
+ // Converts lists into objects. Pass either a single array of `[key, value]`
503
+ // pairs, or two parallel arrays of the same length -- one of keys, and one of
504
+ // the corresponding values.
505
+ _.object = function(list, values) {
506
+ if (list == null) return {};
507
+ var result = {};
508
+ for (var i = 0, l = list.length; i < l; i++) {
509
+ if (values) {
510
+ result[list[i]] = values[i];
511
+ } else {
512
+ result[list[i][0]] = list[i][1];
513
+ }
514
+ }
515
+ return result;
516
+ };
517
+
422
518
  // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
423
519
  // we need this function. Return the position of the first occurrence of an
424
520
  // item in an array, or -1 if the item is not included in the array.
@@ -427,22 +523,29 @@
427
523
  // for **isSorted** to use binary search.
428
524
  _.indexOf = function(array, item, isSorted) {
429
525
  if (array == null) return -1;
430
- var i, l;
526
+ var i = 0, l = array.length;
431
527
  if (isSorted) {
432
- i = _.sortedIndex(array, item);
433
- return array[i] === item ? i : -1;
528
+ if (typeof isSorted == 'number') {
529
+ i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted);
530
+ } else {
531
+ i = _.sortedIndex(array, item);
532
+ return array[i] === item ? i : -1;
533
+ }
434
534
  }
435
- if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
436
- for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i;
535
+ if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
536
+ for (; i < l; i++) if (array[i] === item) return i;
437
537
  return -1;
438
538
  };
439
539
 
440
540
  // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
441
- _.lastIndexOf = function(array, item) {
541
+ _.lastIndexOf = function(array, item, from) {
442
542
  if (array == null) return -1;
443
- if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
444
- var i = array.length;
445
- while (i--) if (i in array && array[i] === item) return i;
543
+ var hasIndex = from != null;
544
+ if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) {
545
+ return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item);
546
+ }
547
+ var i = (hasIndex ? from : array.length);
548
+ while (i--) if (array[i] === item) return i;
446
549
  return -1;
447
550
  };
448
551
 
@@ -471,25 +574,23 @@
471
574
  // Function (ahem) Functions
472
575
  // ------------------
473
576
 
474
- // Reusable constructor function for prototype setting.
475
- var ctor = function(){};
476
-
477
577
  // Create a function bound to a given object (assigning `this`, and arguments,
478
- // optionally). Binding with arguments is also known as `curry`.
479
- // Delegates to **ECMAScript 5**'s native `Function.bind` if available.
480
- // We check for `func.bind` first, to fail fast when `func` is undefined.
481
- _.bind = function bind(func, context) {
482
- var bound, args;
578
+ // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
579
+ // available.
580
+ _.bind = function(func, context) {
483
581
  if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
484
- if (!_.isFunction(func)) throw new TypeError;
485
- args = slice.call(arguments, 2);
486
- return bound = function() {
487
- if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
488
- ctor.prototype = func.prototype;
489
- var self = new ctor;
490
- var result = func.apply(self, args.concat(slice.call(arguments)));
491
- if (Object(result) === result) return result;
492
- return self;
582
+ var args = slice.call(arguments, 2);
583
+ return function() {
584
+ return func.apply(context, args.concat(slice.call(arguments)));
585
+ };
586
+ };
587
+
588
+ // Partially apply a function by creating a version that has had some of its
589
+ // arguments pre-filled, without changing its dynamic `this` context.
590
+ _.partial = function(func) {
591
+ var args = slice.call(arguments, 1);
592
+ return function() {
593
+ return func.apply(this, args.concat(slice.call(arguments)));
493
594
  };
494
595
  };
495
596
 
@@ -497,7 +598,7 @@
497
598
  // all callbacks defined on an object belong to it.
498
599
  _.bindAll = function(obj) {
499
600
  var funcs = slice.call(arguments, 1);
500
- if (funcs.length == 0) funcs = _.functions(obj);
601
+ if (funcs.length === 0) funcs = _.functions(obj);
501
602
  each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
502
603
  return obj;
503
604
  };
@@ -528,23 +629,26 @@
528
629
  // Returns a function, that, when invoked, will only be triggered at most once
529
630
  // during a given window of time.
530
631
  _.throttle = function(func, wait) {
531
- var context, args, timeout, throttling, more, result;
532
- var whenDone = _.debounce(function(){ more = throttling = false; }, wait);
632
+ var context, args, timeout, result;
633
+ var previous = 0;
634
+ var later = function() {
635
+ previous = new Date;
636
+ timeout = null;
637
+ result = func.apply(context, args);
638
+ };
533
639
  return function() {
534
- context = this; args = arguments;
535
- var later = function() {
640
+ var now = new Date;
641
+ var remaining = wait - (now - previous);
642
+ context = this;
643
+ args = arguments;
644
+ if (remaining <= 0) {
645
+ clearTimeout(timeout);
536
646
  timeout = null;
537
- if (more) func.apply(context, args);
538
- whenDone();
539
- };
540
- if (!timeout) timeout = setTimeout(later, wait);
541
- if (throttling) {
542
- more = true;
543
- } else {
647
+ previous = now;
544
648
  result = func.apply(context, args);
649
+ } else if (!timeout) {
650
+ timeout = setTimeout(later, remaining);
545
651
  }
546
- whenDone();
547
- throttling = true;
548
652
  return result;
549
653
  };
550
654
  };
@@ -554,16 +658,18 @@
554
658
  // N milliseconds. If `immediate` is passed, trigger the function on the
555
659
  // leading edge, instead of the trailing.
556
660
  _.debounce = function(func, wait, immediate) {
557
- var timeout;
661
+ var timeout, result;
558
662
  return function() {
559
663
  var context = this, args = arguments;
560
664
  var later = function() {
561
665
  timeout = null;
562
- if (!immediate) func.apply(context, args);
666
+ if (!immediate) result = func.apply(context, args);
563
667
  };
564
- if (immediate && !timeout) func.apply(context, args);
668
+ var callNow = immediate && !timeout;
565
669
  clearTimeout(timeout);
566
670
  timeout = setTimeout(later, wait);
671
+ if (callNow) result = func.apply(context, args);
672
+ return result;
567
673
  };
568
674
  };
569
675
 
@@ -574,7 +680,9 @@
574
680
  return function() {
575
681
  if (ran) return memo;
576
682
  ran = true;
577
- return memo = func.apply(this, arguments);
683
+ memo = func.apply(this, arguments);
684
+ func = null;
685
+ return memo;
578
686
  };
579
687
  };
580
688
 
@@ -583,7 +691,8 @@
583
691
  // conditionally execute the original function.
584
692
  _.wrap = function(func, wrapper) {
585
693
  return function() {
586
- var args = [func].concat(slice.call(arguments, 0));
694
+ var args = [func];
695
+ push.apply(args, arguments);
587
696
  return wrapper.apply(this, args);
588
697
  };
589
698
  };
@@ -605,7 +714,9 @@
605
714
  _.after = function(times, func) {
606
715
  if (times <= 0) return func();
607
716
  return function() {
608
- if (--times < 1) { return func.apply(this, arguments); }
717
+ if (--times < 1) {
718
+ return func.apply(this, arguments);
719
+ }
609
720
  };
610
721
  };
611
722
 
@@ -623,7 +734,23 @@
623
734
 
624
735
  // Retrieve the values of an object's properties.
625
736
  _.values = function(obj) {
626
- return _.map(obj, _.identity);
737
+ var values = [];
738
+ for (var key in obj) if (_.has(obj, key)) values.push(obj[key]);
739
+ return values;
740
+ };
741
+
742
+ // Convert an object into a list of `[key, value]` pairs.
743
+ _.pairs = function(obj) {
744
+ var pairs = [];
745
+ for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]);
746
+ return pairs;
747
+ };
748
+
749
+ // Invert the keys and values of an object. The values must be serializable.
750
+ _.invert = function(obj) {
751
+ var result = {};
752
+ for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key;
753
+ return result;
627
754
  };
628
755
 
629
756
  // Return a sorted list of the function names available on the object.
@@ -639,8 +766,10 @@
639
766
  // Extend a given object with all the properties in passed-in object(s).
640
767
  _.extend = function(obj) {
641
768
  each(slice.call(arguments, 1), function(source) {
642
- for (var prop in source) {
643
- obj[prop] = source[prop];
769
+ if (source) {
770
+ for (var prop in source) {
771
+ obj[prop] = source[prop];
772
+ }
644
773
  }
645
774
  });
646
775
  return obj;
@@ -648,18 +777,31 @@
648
777
 
649
778
  // Return a copy of the object only containing the whitelisted properties.
650
779
  _.pick = function(obj) {
651
- var result = {};
652
- each(_.flatten(slice.call(arguments, 1)), function(key) {
653
- if (key in obj) result[key] = obj[key];
780
+ var copy = {};
781
+ var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
782
+ each(keys, function(key) {
783
+ if (key in obj) copy[key] = obj[key];
654
784
  });
655
- return result;
785
+ return copy;
786
+ };
787
+
788
+ // Return a copy of the object without the blacklisted properties.
789
+ _.omit = function(obj) {
790
+ var copy = {};
791
+ var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
792
+ for (var key in obj) {
793
+ if (!_.contains(keys, key)) copy[key] = obj[key];
794
+ }
795
+ return copy;
656
796
  };
657
797
 
658
798
  // Fill in a given object with default properties.
659
799
  _.defaults = function(obj) {
660
800
  each(slice.call(arguments, 1), function(source) {
661
- for (var prop in source) {
662
- if (obj[prop] == null) obj[prop] = source[prop];
801
+ if (source) {
802
+ for (var prop in source) {
803
+ if (obj[prop] == null) obj[prop] = source[prop];
804
+ }
663
805
  }
664
806
  });
665
807
  return obj;
@@ -679,19 +821,16 @@
679
821
  return obj;
680
822
  };
681
823
 
682
- // Internal recursive comparison function.
683
- function eq(a, b, stack) {
824
+ // Internal recursive comparison function for `isEqual`.
825
+ var eq = function(a, b, aStack, bStack) {
684
826
  // Identical objects are equal. `0 === -0`, but they aren't identical.
685
827
  // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
686
828
  if (a === b) return a !== 0 || 1 / a == 1 / b;
687
829
  // A strict comparison is necessary because `null == undefined`.
688
830
  if (a == null || b == null) return a === b;
689
831
  // Unwrap any wrapped objects.
690
- if (a._chain) a = a._wrapped;
691
- if (b._chain) b = b._wrapped;
692
- // Invoke a custom `isEqual` method if one is provided.
693
- if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
694
- if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);
832
+ if (a instanceof _) a = a._wrapped;
833
+ if (b instanceof _) b = b._wrapped;
695
834
  // Compare `[[Class]]` names.
696
835
  var className = toString.call(a);
697
836
  if (className != toString.call(b)) return false;
@@ -721,14 +860,15 @@
721
860
  if (typeof a != 'object' || typeof b != 'object') return false;
722
861
  // Assume equality for cyclic structures. The algorithm for detecting cyclic
723
862
  // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
724
- var length = stack.length;
863
+ var length = aStack.length;
725
864
  while (length--) {
726
865
  // Linear search. Performance is inversely proportional to the number of
727
866
  // unique nested structures.
728
- if (stack[length] == a) return true;
867
+ if (aStack[length] == a) return bStack[length] == b;
729
868
  }
730
869
  // Add the first object to the stack of traversed objects.
731
- stack.push(a);
870
+ aStack.push(a);
871
+ bStack.push(b);
732
872
  var size = 0, result = true;
733
873
  // Recursively compare objects and arrays.
734
874
  if (className == '[object Array]') {
@@ -738,20 +878,24 @@
738
878
  if (result) {
739
879
  // Deep compare the contents, ignoring non-numeric properties.
740
880
  while (size--) {
741
- // Ensure commutative equality for sparse arrays.
742
- if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break;
881
+ if (!(result = eq(a[size], b[size], aStack, bStack))) break;
743
882
  }
744
883
  }
745
884
  } else {
746
- // Objects with different constructors are not equivalent.
747
- if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false;
885
+ // Objects with different constructors are not equivalent, but `Object`s
886
+ // from different frames are.
887
+ var aCtor = a.constructor, bCtor = b.constructor;
888
+ if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
889
+ _.isFunction(bCtor) && (bCtor instanceof bCtor))) {
890
+ return false;
891
+ }
748
892
  // Deep compare objects.
749
893
  for (var key in a) {
750
894
  if (_.has(a, key)) {
751
895
  // Count the expected number of properties.
752
896
  size++;
753
897
  // Deep compare each member.
754
- if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break;
898
+ if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
755
899
  }
756
900
  }
757
901
  // Ensure that both objects contain the same number of properties.
@@ -763,13 +907,14 @@
763
907
  }
764
908
  }
765
909
  // Remove the first object from the stack of traversed objects.
766
- stack.pop();
910
+ aStack.pop();
911
+ bStack.pop();
767
912
  return result;
768
- }
913
+ };
769
914
 
770
915
  // Perform a deep comparison to check if two objects are equal.
771
916
  _.isEqual = function(a, b) {
772
- return eq(a, b, []);
917
+ return eq(a, b, [], []);
773
918
  };
774
919
 
775
920
  // Is a given array, string, or object empty?
@@ -783,7 +928,7 @@
783
928
 
784
929
  // Is a given value a DOM element?
785
930
  _.isElement = function(obj) {
786
- return !!(obj && obj.nodeType == 1);
931
+ return !!(obj && obj.nodeType === 1);
787
932
  };
788
933
 
789
934
  // Is a given value an array?
@@ -797,40 +942,36 @@
797
942
  return obj === Object(obj);
798
943
  };
799
944
 
800
- // Is a given variable an arguments object?
801
- _.isArguments = function(obj) {
802
- return toString.call(obj) == '[object Arguments]';
803
- };
945
+ // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
946
+ each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
947
+ _['is' + name] = function(obj) {
948
+ return toString.call(obj) == '[object ' + name + ']';
949
+ };
950
+ });
951
+
952
+ // Define a fallback version of the method in browsers (ahem, IE), where
953
+ // there isn't any inspectable "Arguments" type.
804
954
  if (!_.isArguments(arguments)) {
805
955
  _.isArguments = function(obj) {
806
956
  return !!(obj && _.has(obj, 'callee'));
807
957
  };
808
958
  }
809
959
 
810
- // Is a given value a function?
811
- _.isFunction = function(obj) {
812
- return toString.call(obj) == '[object Function]';
813
- };
814
-
815
- // Is a given value a string?
816
- _.isString = function(obj) {
817
- return toString.call(obj) == '[object String]';
818
- };
819
-
820
- // Is a given value a number?
821
- _.isNumber = function(obj) {
822
- return toString.call(obj) == '[object Number]';
823
- };
960
+ // Optimize `isFunction` if appropriate.
961
+ if (typeof (/./) !== 'function') {
962
+ _.isFunction = function(obj) {
963
+ return typeof obj === 'function';
964
+ };
965
+ }
824
966
 
825
967
  // Is a given object a finite number?
826
968
  _.isFinite = function(obj) {
827
- return _.isNumber(obj) && isFinite(obj);
969
+ return isFinite(obj) && !isNaN(parseFloat(obj));
828
970
  };
829
971
 
830
- // Is the given value `NaN`?
972
+ // Is the given value `NaN`? (NaN is the only number which does not equal itself).
831
973
  _.isNaN = function(obj) {
832
- // `NaN` is the only value for which `===` is not reflexive.
833
- return obj !== obj;
974
+ return _.isNumber(obj) && obj != +obj;
834
975
  };
835
976
 
836
977
  // Is a given value a boolean?
@@ -838,16 +979,6 @@
838
979
  return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
839
980
  };
840
981
 
841
- // Is a given value a date?
842
- _.isDate = function(obj) {
843
- return toString.call(obj) == '[object Date]';
844
- };
845
-
846
- // Is the given value a regular expression?
847
- _.isRegExp = function(obj) {
848
- return toString.call(obj) == '[object RegExp]';
849
- };
850
-
851
982
  // Is a given value equal to null?
852
983
  _.isNull = function(obj) {
853
984
  return obj === null;
@@ -858,7 +989,8 @@
858
989
  return obj === void 0;
859
990
  };
860
991
 
861
- // Has own property?
992
+ // Shortcut function for checking if an object has a given property directly
993
+ // on itself (in other words, not on a prototype).
862
994
  _.has = function(obj, key) {
863
995
  return hasOwnProperty.call(obj, key);
864
996
  };
@@ -879,15 +1011,50 @@
879
1011
  };
880
1012
 
881
1013
  // Run a function **n** times.
882
- _.times = function (n, iterator, context) {
883
- for (var i = 0; i < n; i++) iterator.call(context, i);
1014
+ _.times = function(n, iterator, context) {
1015
+ var accum = Array(n);
1016
+ for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i);
1017
+ return accum;
884
1018
  };
885
1019
 
886
- // Escape a string for HTML interpolation.
887
- _.escape = function(string) {
888
- return (''+string).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/\//g,'&#x2F;');
1020
+ // Return a random integer between min and max (inclusive).
1021
+ _.random = function(min, max) {
1022
+ if (max == null) {
1023
+ max = min;
1024
+ min = 0;
1025
+ }
1026
+ return min + Math.floor(Math.random() * (max - min + 1));
1027
+ };
1028
+
1029
+ // List of HTML entities for escaping.
1030
+ var entityMap = {
1031
+ escape: {
1032
+ '&': '&amp;',
1033
+ '<': '&lt;',
1034
+ '>': '&gt;',
1035
+ '"': '&quot;',
1036
+ "'": '&#x27;',
1037
+ '/': '&#x2F;'
1038
+ }
1039
+ };
1040
+ entityMap.unescape = _.invert(entityMap.escape);
1041
+
1042
+ // Regexes containing the keys and values listed immediately above.
1043
+ var entityRegexes = {
1044
+ escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),
1045
+ unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')
889
1046
  };
890
1047
 
1048
+ // Functions for escaping and unescaping strings to/from HTML interpolation.
1049
+ _.each(['escape', 'unescape'], function(method) {
1050
+ _[method] = function(string) {
1051
+ if (string == null) return '';
1052
+ return ('' + string).replace(entityRegexes[method], function(match) {
1053
+ return entityMap[method][match];
1054
+ });
1055
+ };
1056
+ });
1057
+
891
1058
  // If the value of the named property is a function then invoke it;
892
1059
  // otherwise, return it.
893
1060
  _.result = function(object, property) {
@@ -896,11 +1063,15 @@
896
1063
  return _.isFunction(value) ? value.call(object) : value;
897
1064
  };
898
1065
 
899
- // Add your own custom functions to the Underscore object, ensuring that
900
- // they're correctly added to the OOP wrapper as well.
1066
+ // Add your own custom functions to the Underscore object.
901
1067
  _.mixin = function(obj) {
902
1068
  each(_.functions(obj), function(name){
903
- addToWrapper(name, _[name] = obj[name]);
1069
+ var func = _[name] = obj[name];
1070
+ _.prototype[name] = function() {
1071
+ var args = [this._wrapped];
1072
+ push.apply(args, arguments);
1073
+ return result.call(this, func.apply(_, args));
1074
+ };
904
1075
  });
905
1076
  };
906
1077
 
@@ -908,7 +1079,7 @@
908
1079
  // Useful for temporary DOM ids.
909
1080
  var idCounter = 0;
910
1081
  _.uniqueId = function(prefix) {
911
- var id = idCounter++;
1082
+ var id = ++idCounter + '';
912
1083
  return prefix ? prefix + id : id;
913
1084
  };
914
1085
 
@@ -923,72 +1094,78 @@
923
1094
  // When customizing `templateSettings`, if you don't want to define an
924
1095
  // interpolation, evaluation or escaping regex, we need one that is
925
1096
  // guaranteed not to match.
926
- var noMatch = /.^/;
1097
+ var noMatch = /(.)^/;
927
1098
 
928
1099
  // Certain characters need to be escaped so that they can be put into a
929
1100
  // string literal.
930
1101
  var escapes = {
931
- '\\': '\\',
932
- "'": "'",
933
- 'r': '\r',
934
- 'n': '\n',
935
- 't': '\t',
936
- 'u2028': '\u2028',
937
- 'u2029': '\u2029'
1102
+ "'": "'",
1103
+ '\\': '\\',
1104
+ '\r': 'r',
1105
+ '\n': 'n',
1106
+ '\t': 't',
1107
+ '\u2028': 'u2028',
1108
+ '\u2029': 'u2029'
938
1109
  };
939
1110
 
940
- for (var p in escapes) escapes[escapes[p]] = p;
941
1111
  var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
942
- var unescaper = /\\(\\|'|r|n|t|u2028|u2029)/g;
943
-
944
- // Within an interpolation, evaluation, or escaping, remove HTML escaping
945
- // that had been previously added.
946
- var unescape = function(code) {
947
- return code.replace(unescaper, function(match, escape) {
948
- return escapes[escape];
949
- });
950
- };
951
1112
 
952
1113
  // JavaScript micro-templating, similar to John Resig's implementation.
953
1114
  // Underscore templating handles arbitrary delimiters, preserves whitespace,
954
1115
  // and correctly escapes quotes within interpolated code.
955
1116
  _.template = function(text, data, settings) {
956
- settings = _.extend(_.templateSettings, settings);
957
-
958
- // Compile the template source, taking care to escape characters that
959
- // cannot be included in a string literal and then unescape them in code
960
- // blocks.
961
- var source = "__p+='" + text
962
- .replace(escaper, function(match) {
963
- return '\\' + escapes[match];
964
- })
965
- .replace(settings.escape || noMatch, function(match, code) {
966
- return "'+\n_.escape(" + unescape(code) + ")+\n'";
967
- })
968
- .replace(settings.interpolate || noMatch, function(match, code) {
969
- return "'+\n(" + unescape(code) + ")+\n'";
970
- })
971
- .replace(settings.evaluate || noMatch, function(match, code) {
972
- return "';\n" + unescape(code) + "\n;__p+='";
973
- }) + "';\n";
1117
+ var render;
1118
+ settings = _.defaults({}, settings, _.templateSettings);
1119
+
1120
+ // Combine delimiters into one regular expression via alternation.
1121
+ var matcher = new RegExp([
1122
+ (settings.escape || noMatch).source,
1123
+ (settings.interpolate || noMatch).source,
1124
+ (settings.evaluate || noMatch).source
1125
+ ].join('|') + '|$', 'g');
1126
+
1127
+ // Compile the template source, escaping string literals appropriately.
1128
+ var index = 0;
1129
+ var source = "__p+='";
1130
+ text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
1131
+ source += text.slice(index, offset)
1132
+ .replace(escaper, function(match) { return '\\' + escapes[match]; });
1133
+
1134
+ if (escape) {
1135
+ source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
1136
+ }
1137
+ if (interpolate) {
1138
+ source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
1139
+ }
1140
+ if (evaluate) {
1141
+ source += "';\n" + evaluate + "\n__p+='";
1142
+ }
1143
+ index = offset + match.length;
1144
+ return match;
1145
+ });
1146
+ source += "';\n";
974
1147
 
975
1148
  // If a variable is not specified, place data values in local scope.
976
1149
  if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
977
1150
 
978
- source = "var __p='';" +
979
- "var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n" +
1151
+ source = "var __t,__p='',__j=Array.prototype.join," +
1152
+ "print=function(){__p+=__j.call(arguments,'');};\n" +
980
1153
  source + "return __p;\n";
981
1154
 
982
- var render = new Function(settings.variable || 'obj', '_', source);
1155
+ try {
1156
+ render = new Function(settings.variable || 'obj', '_', source);
1157
+ } catch (e) {
1158
+ e.source = source;
1159
+ throw e;
1160
+ }
1161
+
983
1162
  if (data) return render(data, _);
984
1163
  var template = function(data) {
985
1164
  return render.call(this, data, _);
986
1165
  };
987
1166
 
988
- // Provide the compiled function source as a convenience for build time
989
- // precompilation.
990
- template.source = 'function(' + (settings.variable || 'obj') + '){\n' +
991
- source + '}';
1167
+ // Provide the compiled function source as a convenience for precompilation.
1168
+ template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
992
1169
 
993
1170
  return template;
994
1171
  };
@@ -998,29 +1175,15 @@
998
1175
  return _(obj).chain();
999
1176
  };
1000
1177
 
1001
- // The OOP Wrapper
1178
+ // OOP
1002
1179
  // ---------------
1003
-
1004
1180
  // If Underscore is called as a function, it returns a wrapped object that
1005
1181
  // can be used OO-style. This wrapper holds altered versions of all the
1006
1182
  // underscore functions. Wrapped objects may be chained.
1007
- var wrapper = function(obj) { this._wrapped = obj; };
1008
-
1009
- // Expose `wrapper.prototype` as `_.prototype`
1010
- _.prototype = wrapper.prototype;
1011
1183
 
1012
1184
  // Helper function to continue chaining intermediate results.
1013
- var result = function(obj, chain) {
1014
- return chain ? _(obj).chain() : obj;
1015
- };
1016
-
1017
- // A method to easily add functions to the OOP wrapper.
1018
- var addToWrapper = function(name, func) {
1019
- wrapper.prototype[name] = function() {
1020
- var args = slice.call(arguments);
1021
- unshift.call(args, this._wrapped);
1022
- return result(func.apply(_, args), this._chain);
1023
- };
1185
+ var result = function(obj) {
1186
+ return this._chain ? _(obj).chain() : obj;
1024
1187
  };
1025
1188
 
1026
1189
  // Add all of the Underscore functions to the wrapper object.
@@ -1029,32 +1192,35 @@
1029
1192
  // Add all mutator Array functions to the wrapper.
1030
1193
  each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
1031
1194
  var method = ArrayProto[name];
1032
- wrapper.prototype[name] = function() {
1033
- var wrapped = this._wrapped;
1034
- method.apply(wrapped, arguments);
1035
- var length = wrapped.length;
1036
- if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0];
1037
- return result(wrapped, this._chain);
1195
+ _.prototype[name] = function() {
1196
+ var obj = this._wrapped;
1197
+ method.apply(obj, arguments);
1198
+ if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];
1199
+ return result.call(this, obj);
1038
1200
  };
1039
1201
  });
1040
1202
 
1041
1203
  // Add all accessor Array functions to the wrapper.
1042
1204
  each(['concat', 'join', 'slice'], function(name) {
1043
1205
  var method = ArrayProto[name];
1044
- wrapper.prototype[name] = function() {
1045
- return result(method.apply(this._wrapped, arguments), this._chain);
1206
+ _.prototype[name] = function() {
1207
+ return result.call(this, method.apply(this._wrapped, arguments));
1046
1208
  };
1047
1209
  });
1048
1210
 
1049
- // Start chaining a wrapped Underscore object.
1050
- wrapper.prototype.chain = function() {
1051
- this._chain = true;
1052
- return this;
1053
- };
1211
+ _.extend(_.prototype, {
1054
1212
 
1055
- // Extracts the result from a wrapped and chained object.
1056
- wrapper.prototype.value = function() {
1057
- return this._wrapped;
1058
- };
1213
+ // Start chaining a wrapped Underscore object.
1214
+ chain: function() {
1215
+ this._chain = true;
1216
+ return this;
1217
+ },
1218
+
1219
+ // Extracts the result from a wrapped and chained object.
1220
+ value: function() {
1221
+ return this._wrapped;
1222
+ }
1223
+
1224
+ });
1059
1225
 
1060
- }).call(this);
1226
+ }).call(this);