judge 1.5.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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);