sugar-rails 1.2.5.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/README.md +110 -28
  2. data/lib/generators/sugar/build/build_generator.rb +107 -0
  3. data/lib/generators/sugar/install/install_generator.rb +2 -2
  4. data/lib/sugar/rails/version.rb +2 -2
  5. data/sugar-rails.gemspec +1 -1
  6. data/vendor/assets/javascripts/precompiled/development/array.js +1212 -0
  7. data/vendor/assets/javascripts/precompiled/development/core.js +329 -0
  8. data/vendor/assets/javascripts/precompiled/development/date.js +2179 -0
  9. data/vendor/assets/javascripts/precompiled/development/date_locales.js +952 -0
  10. data/vendor/assets/javascripts/precompiled/development/date_ranges.js +183 -0
  11. data/vendor/assets/javascripts/precompiled/development/es5.js +443 -0
  12. data/vendor/assets/javascripts/precompiled/development/function.js +222 -0
  13. data/vendor/assets/javascripts/{sugar-inflections.js → precompiled/development/inflections.js} +51 -162
  14. data/vendor/assets/javascripts/precompiled/development/language.js +383 -0
  15. data/vendor/assets/javascripts/precompiled/development/number.js +422 -0
  16. data/vendor/assets/javascripts/precompiled/development/object.js +348 -0
  17. data/vendor/assets/javascripts/precompiled/development/regexp.js +92 -0
  18. data/vendor/assets/javascripts/precompiled/development/string.js +871 -0
  19. data/vendor/assets/javascripts/precompiled/minified/array.js +16 -0
  20. data/vendor/assets/javascripts/precompiled/minified/core.js +8 -0
  21. data/vendor/assets/javascripts/precompiled/minified/date.js +40 -0
  22. data/vendor/assets/javascripts/precompiled/minified/date_locales.js +38 -0
  23. data/vendor/assets/javascripts/precompiled/minified/date_ranges.js +3 -0
  24. data/vendor/assets/javascripts/precompiled/minified/es5.js +7 -0
  25. data/vendor/assets/javascripts/precompiled/minified/function.js +4 -0
  26. data/vendor/assets/javascripts/precompiled/minified/inflections.js +11 -0
  27. data/vendor/assets/javascripts/precompiled/minified/language.js +19 -0
  28. data/vendor/assets/javascripts/precompiled/minified/number.js +5 -0
  29. data/vendor/assets/javascripts/precompiled/minified/object.js +6 -0
  30. data/vendor/assets/javascripts/precompiled/minified/regexp.js +2 -0
  31. data/vendor/assets/javascripts/precompiled/minified/string.js +12 -0
  32. data/vendor/assets/javascripts/precompiled/readme.txt +3 -0
  33. data/vendor/assets/javascripts/sugar-development.js +8054 -0
  34. data/vendor/assets/javascripts/sugar-full.js +179 -0
  35. data/vendor/assets/javascripts/sugar.js +111 -6211
  36. metadata +35 -9
  37. data/vendor/assets/javascripts/sugar-core.js +0 -4001
  38. data/vendor/assets/javascripts/sugar-dates-only.js +0 -3121
  39. data/vendor/assets/javascripts/sugar-dates.js +0 -2210
data/README.md CHANGED
@@ -1,9 +1,55 @@
1
1
  # sugar-rails
2
2
 
3
+ ## Description
4
+
3
5
  [Sugar](http://sugarjs.com/), tastefully bundled for the Rails 3.0 and up. Sweet!
4
6
 
5
7
 
6
- ## Rails 3.1+
8
+ ## Requirements
9
+
10
+ This gem requires Rails 3.0+ and has been tested on the following versions:
11
+
12
+ * 3.0
13
+ * 3.1
14
+ * 3.2
15
+
16
+ This gem has been tested against the following Ruby versions:
17
+
18
+ * MRI 1.8.7
19
+ * REE 1.8.7
20
+ * 1.9.2
21
+ * 1.9.3
22
+ * JRuby
23
+ * Rubinius
24
+
25
+
26
+ ## Installation
27
+
28
+ Add this line to your application's Gemfile:
29
+
30
+ ```ruby
31
+ gem "sugar-rails"
32
+ ```
33
+
34
+ And then execute:
35
+
36
+ ```
37
+ $ bundle
38
+ ```
39
+
40
+ Or install it yourself as:
41
+
42
+ ```
43
+ $ gem install sugar-rails
44
+ ```
45
+
46
+
47
+ ## Usage
48
+
49
+ `sugar-rails` supports both default and customized builds of Sugar.
50
+
51
+
52
+ ### Rails 3.1+
7
53
 
8
54
  For Rails 3.1 and greater, the files will be added to the asset pipeline and available for you to use.
9
55
 
@@ -15,54 +61,87 @@ To enable:
15
61
  //= require sugar
16
62
  ```
17
63
 
18
- * You can also customize the components required with the following:
64
+ The following default builds are available:
19
65
 
20
- ``` javascript
21
- //= require sugar-core
22
- //= require sugar-dates
23
- //= require sugar-inflections
66
+ * `sugar` (default minified package)
67
+ * `sugar-full` (full minified package)
68
+ * `sugar-development` (full development package)
69
+
70
+
71
+ ### Rails 3.0
24
72
 
25
- // ... or ...
73
+ This gem adds an installation generator to Rails 3, `sugar:install`. Running the generator will install the JavaScript file (default minified package) necessary to use Sugar.
26
74
 
27
- //= require sugar-dates-only
75
+ * Invoke the generator:
76
+
77
+ ```
78
+ $ rails generate sugar:install
28
79
  ```
29
80
 
30
- More details about customized builds can be found at http://sugarjs.com/customize.
81
+ * Add the following to your layout or view files:
31
82
 
83
+ ```erb
84
+ <%= javascript_include_tag "sugar" %>
85
+ ```
32
86
 
33
- ### Installation
34
87
 
35
- * Add `sugar-rails` to your Gemfile
36
- * Run `bundle`
88
+ ## Customized Builds
37
89
 
38
- Enjoy!
90
+ This gem adds a generator to provide customized builds of Sugar, `sugar:build`.
39
91
 
92
+ The `sugar:build` generator requires Rails 3.1+ and `config.assets.enabled = true` since custom builds are made available through the Rails Asset Pipeline.
40
93
 
41
- ## Rails 3.0
94
+ Running the generator will create a customized JavaScript file with the pacakges of your choosing:
42
95
 
43
- This gem adds a single generator to Rails 3, `sugar:install`. Running the generator will install the JavaScript file necessary to use Sugar.
96
+ ```
97
+ $ rails generate sugar:build package1 package2 ...
98
+ ```
44
99
 
45
- ### Installation
100
+ Once the generator has been run, you will be provided with the file `vendor/assets/javascripts/sugar-custom.js`. Add the following line to your sprockets manifest to enable your custom build:
46
101
 
47
- * Add `sugar-rails` to your Gemfile
48
- * Run `bundle`
49
- * Invoke the generator: `rails generate sugar:install`
50
- * Add the following to your layout or view files:
102
+ ``` javascript
103
+ //= require sugar-custom
104
+ ```
51
105
 
52
- ```erb
53
- <%= javascript_include_tag "sugar" %>
106
+ By default, the `sugar:build` generator will provide the development (un-minified) sources. If you would like to use the pre-minified sources, add the `--minify` option to the generator:
107
+
108
+ ```
109
+ $ rails generate sugar:build package1 package2 --minify
54
110
  ```
55
111
 
56
- You're done!
112
+ The following packages are available for custom builds:
113
+
114
+ * `es5` - Shim methods that provide ES5 compatible functionality. This package can be excluded if you do not require legacy browser support (IE8 and below).
115
+ * `object` - Object manipulation, type checking (isNumber, isString, ...), extended objects with hash-like methods available as instance methods.
116
+ * `array` - Array manipulation and traversal, "fuzzy matching" against elements, alphanumeric sorting and collation, enumerable methods on Object.
117
+ * `number` - Number formatting, rounding (with precision), and ranges. Aliases to Math methods.
118
+ * `regexp` - Escaping regexes and manipulating their flags.
119
+ * `function` - Lazy, throttled, and memoized functions, delayed functions and handling of timers, argument currying.
120
+ * `date` - Date parsing and formatting, relative formats like "1 minute ago", Number methods like "daysAgo", localization support with default English locale definition.
121
+ * `date_ranges` - Date Ranges define a range of time. They can enumerate over specific points within that range, and be manipulated and compared.
122
+ * `date_locales` - Locale definitions: fr, it, es, pt, de, ru, pl, sv, ja, ko, zh-CN, zh-TW
123
+ * `string` - String manupulation, escaping, encoding, truncation, and:conversion.
124
+ * `inflections` - Pluralization similar to ActiveSupport including uncountable words and acronyms. Humanized and URL-friendly strings.
125
+ * `language` - Normalizing accented characters, character width conversion, Hiragana and Katakana conversions.
126
+
127
+ Some dependencies must be met for your custom build to work properly:
128
+
129
+ * `date` < `date_locales`
130
+ * `date` < `date_ranges`
131
+ * `string` < `inflections`
132
+ * `string` < `language`
133
+
134
+ Run `rails generate sugar:build --help` for full usage instructions. Please visit [http://sugarjs.com/customize](http://sugarjs.com/customize) to learn more about customized builds.
135
+
57
136
 
58
137
 
59
138
  ## Contributing
60
139
 
61
- 1. Fork it
140
+ 1. [Fork it](https://github.com/phlipper/sugar-rails/fork_select)
62
141
  2. Create your feature branch (`git checkout -b my-new-feature`)
63
142
  3. Commit your changes (`git commit -am 'Added some feature'`)
64
143
  4. Push to the branch (`git push origin my-new-feature`)
65
- 5. Create new Pull Request
144
+ 5. [Create a Pull Request](hhttps://github.com/phlipper/sugar-rails/pull/new)
66
145
 
67
146
 
68
147
  ## Contributors
@@ -70,7 +149,10 @@ You're done!
70
149
  Many thanks go to the following who have contributed to making this gem even better:
71
150
 
72
151
  * **[@sandrew](https://github.com/sandrew)**
73
- * support for customized builds
152
+ * add support for customized builds for 1.2.x
153
+ * **[@andrewplummer](https://github.com/andrewplummer)**
154
+ * add build generator to support customized builds in 1.3+
155
+ * created [Sugar](https://github.com/andrewplummer/Sugar) :)
74
156
 
75
157
 
76
158
  ## License
@@ -81,7 +163,7 @@ Many thanks go to the following who have contributed to making this gem even bet
81
163
  * Copyright (c) 2011-2012 Phil Cohen (github@phlippers.net) [![endorse](http://api.coderwall.com/phlipper/endorsecount.png)](http://coderwall.com/phlipper)
82
164
  * http://phlippers.net/
83
165
 
84
- **Sugar JS**
166
+ **Sugar**
85
167
 
86
168
  * Freely distributable and licensed under the MIT-style license.
87
169
  * Copyright (c) 2012 Andrew Plummer
@@ -89,4 +171,4 @@ Many thanks go to the following who have contributed to making this gem even bet
89
171
 
90
172
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sub-license, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
91
173
  The above copyright notice, and every other copyright notice found in this software, and all the attributions in every file, and this permission notice shall be included in all copies or substantial portions of the Software.
92
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
174
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,107 @@
1
+ require "rails"
2
+
3
+ module Sugar
4
+ module Generators
5
+ class BuildGenerator < ::Rails::Generators::Base
6
+ source_root File.expand_path("../../../../../vendor/assets", __FILE__)
7
+ argument :packages, :type => :array, :default => []
8
+ class_option :minify, :type => :boolean, :default => false
9
+
10
+ desc "Creates a custom Sugar build."
11
+ def build
12
+ return unless valid_rails_version?
13
+
14
+ if packages.present?
15
+ build_packages
16
+ else
17
+ display_help
18
+ end
19
+ end
20
+
21
+
22
+ private
23
+
24
+ def get_package_content(packages)
25
+ type = options.minify? ? "minified" : "development"
26
+ base_path = "javascripts/precompiled/#{type}"
27
+
28
+ packages.each do |name|
29
+ filename = find_in_source_paths "#{base_path}/#{name}.js"
30
+ yield name, File.read(filename)
31
+ end
32
+ end
33
+
34
+ def build_packages
35
+ output = ""
36
+ destination = "vendor/assets/javascripts/sugar-custom.js"
37
+
38
+ say_status "", ""
39
+ say_status "build", "Custom Sugar packages: #{packages.join(", ")}", :green
40
+
41
+ remove_file destination
42
+ packages.unshift("core") # add 'core' package to the top of the bulid
43
+ get_package_content(packages) { |name, content| output << content }
44
+ create_file destination, "(function() { #{output} })();"
45
+
46
+ say_status "", ""
47
+ say_status "success", "Add `//= require sugar-custom` to your app/assets/javascripts/application.js", :green
48
+ say_status "", ""
49
+ end
50
+
51
+ def valid_rails_version?
52
+ valid = ::Rails.version >= "3.1" && ::Rails.application.config.assets.enabled
53
+
54
+ unless valid
55
+ say_status "", ""
56
+ say_status "error", "You are using Rails below version 3.1 or the Asset Pipeline is not enabled.", :red
57
+ say_status "", "This generator requires Rails 3.1+ and the Asset Pipeline."
58
+ say_status "", ""
59
+ say_status "", "Please ensure that you are running a supported version of Rails."
60
+ say_status "", "Yoy may need to enable the Asset Pipeline by setting `config.assets.enabled = true` in your `config/application.rb` file."
61
+ end
62
+
63
+ valid
64
+ end
65
+
66
+ def display_help
67
+ default_packages = [
68
+ :es5, :object, :array, :number, :regexp, :function, :date,
69
+ :date_ranges, :date_locales, :string, :inflections, :language
70
+ ]
71
+
72
+ say_status "", ""
73
+ say_status "[USAGE]", "rails g sugar:build package1 package2 ... [options]", :yellow
74
+ say_status "", ""
75
+ say_status "", "This script creates a custom Sugar build."
76
+ say_status "", "You can include the following packages:"
77
+ say_status "", ""
78
+
79
+
80
+ get_package_content(default_packages) do |name, content|
81
+ description = if name == :date_locales
82
+ "Locale definitions: fr, it, es, pt, de, ru, pl, sv, ja, ko, zh-CN, zh-TW"
83
+ else
84
+ content[/@description (.+)$/, 1]
85
+ end
86
+
87
+ say_status "", "#{name.to_s.ljust(15)} #{description}"
88
+ end
89
+
90
+ say_status "", ""
91
+ say_status "", "Dependencies must be met for your build to work properly:"
92
+ say_status "", ""
93
+ say_status "", "date < date_locales"
94
+ say_status "", "date < date_ranges"
95
+ say_status "", "string < inflections"
96
+ say_status "", "string < language"
97
+ say_status "", ""
98
+ say_status "", "Note that all packages require proper ES5 support, however this package"
99
+ say_status "", "can be omitted if you are only targeting modern browsers (ie. not < IE9)."
100
+ say_status "", ""
101
+ say_status "", "Also note that the core package is required and included by default."
102
+ say_status "", "Custom builds can also be created at http://sugarjs.com/customize"
103
+ say_status "", ""
104
+ end
105
+ end
106
+ end
107
+ end
@@ -5,11 +5,11 @@ if ::Rails.version < "3.1" || !::Rails.application.config.assets.enabled
5
5
  module Sugar
6
6
  module Generators
7
7
  class InstallGenerator < ::Rails::Generators::Base
8
- desc "This generator installs SugarJS #{Sugar::Rails::SUGARJS_VERSION}"
8
+ desc "This generator installs Sugar #{Sugar::Rails::SUGARJS_VERSION}"
9
9
  source_root File.expand_path("../../../../../vendor/assets", __FILE__)
10
10
 
11
11
  def copy_javascript
12
- say_status("copying", "Sugar JS", :green)
12
+ say_status("copying", "Sugar", :green)
13
13
  copy_file "javascripts/sugar.js", "public/javascripts/sugar.js"
14
14
  end
15
15
  end
@@ -1,6 +1,6 @@
1
1
  module Sugar
2
2
  module Rails
3
- VERSION = "1.2.5.1"
4
- SUGARJS_VERSION = "1.2.5"
3
+ VERSION = "1.3.0"
4
+ SUGARJS_VERSION = "1.3"
5
5
  end
6
6
  end
data/sugar-rails.gemspec CHANGED
@@ -6,7 +6,7 @@ Gem::Specification.new do |gem|
6
6
  gem.email = ["github@phlippers.net"]
7
7
  gem.description = %q{Sugar, tastefully bundled for the Rails 3.0 and up. Sweet!}
8
8
  gem.summary = %q{Sugar, tastefully bundled for the Rails 3.0 and up. Sweet!}
9
- gem.homepage = "https://github.com/phlipper/sugar-rails"
9
+ gem.homepage = "http://phlippers.net/sugar-rails"
10
10
 
11
11
  gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
12
  gem.files = `git ls-files`.split("\n")
@@ -0,0 +1,1212 @@
1
+
2
+
3
+
4
+ /***
5
+ * @package Array
6
+ * @dependency core
7
+ * @description Array manipulation and traversal, "fuzzy matching" against elements, alphanumeric sorting and collation, enumerable methods on Object.
8
+ *
9
+ ***/
10
+
11
+
12
+ function multiMatch(el, match, scope, params) {
13
+ var result = true;
14
+ if(el === match) {
15
+ // Match strictly equal values up front.
16
+ return true;
17
+ } else if(isRegExp(match) && isString(el)) {
18
+ // Match against a regexp
19
+ return regexp(match).test(el);
20
+ } else if(isFunction(match)) {
21
+ // Match against a filtering function
22
+ return match.apply(scope, params);
23
+ } else if(isObject(match) && isObjectPrimitive(el)) {
24
+ // Match against a hash or array.
25
+ iterateOverObject(match, function(key, value) {
26
+ if(!multiMatch(el[key], match[key], scope, [el[key], el])) {
27
+ result = false;
28
+ }
29
+ });
30
+ return object.keys(match).length > 0 && result;
31
+ } else {
32
+ return stringify(el) === stringify(match);
33
+ }
34
+ }
35
+
36
+ function transformArgument(el, map, context, mapArgs) {
37
+ if(isUndefined(map)) {
38
+ return el;
39
+ } else if(isFunction(map)) {
40
+ return map.apply(context, mapArgs || []);
41
+ } else if(isFunction(el[map])) {
42
+ return el[map].call(el);
43
+ } else {
44
+ return el[map];
45
+ }
46
+ }
47
+
48
+ // Basic array internal methods
49
+
50
+ function arrayEach(arr, fn, startIndex, loop) {
51
+ var length, index, i;
52
+ if(startIndex < 0) startIndex = arr.length + startIndex;
53
+ i = isNaN(startIndex) ? 0 : startIndex;
54
+ length = loop === true ? arr.length + i : arr.length;
55
+ while(i < length) {
56
+ index = i % arr.length;
57
+ if(!(index in arr)) {
58
+ return iterateOverSparseArray(arr, fn, i, loop);
59
+ } else if(fn.call(arr, arr[index], index, arr) === false) {
60
+ break;
61
+ }
62
+ i++;
63
+ }
64
+ }
65
+
66
+ function iterateOverSparseArray(arr, fn, fromIndex, loop) {
67
+ var indexes = [], i;
68
+ for(i in arr) {
69
+ if(isArrayIndex(arr, i) && i >= fromIndex) {
70
+ indexes.push(parseInt(i));
71
+ }
72
+ }
73
+ indexes.sort().each(function(index) {
74
+ return fn.call(arr, arr[index], index, arr);
75
+ });
76
+ return arr;
77
+ }
78
+
79
+ function isArrayIndex(arr, i) {
80
+ return i in arr && toUInt32(i) == i && i != 0xffffffff;
81
+ }
82
+
83
+ function toUInt32(i) {
84
+ return i >>> 0;
85
+ }
86
+
87
+ function arrayFind(arr, f, startIndex, loop, returnIndex) {
88
+ var result, index;
89
+ arrayEach(arr, function(el, i, arr) {
90
+ if(multiMatch(el, f, arr, [el, i, arr])) {
91
+ result = el;
92
+ index = i;
93
+ return false;
94
+ }
95
+ }, startIndex, loop);
96
+ return returnIndex ? index : result;
97
+ }
98
+
99
+ function arrayUnique(arr, map) {
100
+ var result = [], o = {}, stringified, transformed;
101
+ arrayEach(arr, function(el, i) {
102
+ transformed = map ? transformArgument(el, map, arr, [el, i, arr]) : el;
103
+ stringified = stringify(transformed);
104
+ if(!arrayObjectExists(o, stringified, el)) {
105
+ o[stringified] = transformed;
106
+ result.push(el);
107
+ }
108
+ })
109
+ return result;
110
+ }
111
+
112
+ function arrayIntersect(arr1, arr2, subtract) {
113
+ var result = [], o = {};
114
+ arr2.each(function(el) {
115
+ o[stringify(el)] = el;
116
+ });
117
+ arr1.each(function(el) {
118
+ var stringified = stringify(el), exists = arrayObjectExists(o, stringified, el);
119
+ // Add the result to the array if:
120
+ // 1. We're subtracting intersections or it doesn't already exist in the result and
121
+ // 2. It exists in the compared array and we're adding, or it doesn't exist and we're removing.
122
+ if(exists != subtract) {
123
+ delete o[stringified];
124
+ result.push(el);
125
+ }
126
+ });
127
+ return result;
128
+ }
129
+
130
+ function arrayFlatten(arr, level, current) {
131
+ level = level || Infinity;
132
+ current = current || 0;
133
+ var result = [];
134
+ arrayEach(arr, function(el) {
135
+ if(isArray(el) && current < level) {
136
+ result = result.concat(arrayFlatten(el, level, current + 1));
137
+ } else {
138
+ result.push(el);
139
+ }
140
+ });
141
+ return result;
142
+ }
143
+
144
+ function flatArguments(args) {
145
+ var result = [];
146
+ multiArgs(args, function(arg) {
147
+ result = result.concat(arg);
148
+ });
149
+ return result;
150
+ }
151
+
152
+ function arrayObjectExists(hash, stringified, obj) {
153
+ return stringified in hash && (typeof obj !== 'function' || obj === hash[stringified]);
154
+ }
155
+
156
+
157
+ // Support methods
158
+
159
+ function getMinOrMax(obj, map, which, all) {
160
+ var edge,
161
+ result = [],
162
+ max = which === 'max',
163
+ min = which === 'min',
164
+ isArray = Array.isArray(obj);
165
+ iterateOverObject(obj, function(key) {
166
+ var el = obj[key];
167
+ var test = transformArgument(el, map, obj, isArray ? [el, parseInt(key), obj] : []);
168
+ if(test === edge) {
169
+ result.push(el);
170
+ } else if(isUndefined(edge) || (max && test > edge) || (min && test < edge)) {
171
+ result = [el];
172
+ edge = test;
173
+ }
174
+ });
175
+ if(!isArray) result = arrayFlatten(result, 1);
176
+ return all ? result : result[0];
177
+ }
178
+
179
+
180
+ // Alphanumeric collation helpers
181
+
182
+ function collateStrings(a, b) {
183
+ var aValue, bValue, aChar, bChar, aEquiv, bEquiv, index = 0, tiebreaker = 0;
184
+ a = getCollationReadyString(a);
185
+ b = getCollationReadyString(b);
186
+ do {
187
+ aChar = getCollationCharacter(a, index);
188
+ bChar = getCollationCharacter(b, index);
189
+ aValue = getCollationValue(aChar);
190
+ bValue = getCollationValue(bChar);
191
+ if(aValue === -1 || bValue === -1) {
192
+ aValue = a.charCodeAt(index) || null;
193
+ bValue = b.charCodeAt(index) || null;
194
+ }
195
+ aEquiv = aChar !== a.charAt(index);
196
+ bEquiv = bChar !== b.charAt(index);
197
+ if(aEquiv !== bEquiv && tiebreaker === 0) {
198
+ tiebreaker = aEquiv - bEquiv;
199
+ }
200
+ index += 1;
201
+ } while(aValue != null && bValue != null && aValue === bValue);
202
+ if(aValue === bValue) return tiebreaker;
203
+ return aValue < bValue ? -1 : 1;
204
+ }
205
+
206
+ function getCollationReadyString(str) {
207
+ if(array[AlphanumericSortIgnoreCase]) {
208
+ str = str.toLowerCase();
209
+ }
210
+ return str.replace(array[AlphanumericSortIgnore], '');
211
+ }
212
+
213
+ function getCollationCharacter(str, index) {
214
+ var chr = str.charAt(index), eq = array[AlphanumericSortEquivalents] || {};
215
+ return eq[chr] || chr;
216
+ }
217
+
218
+ function getCollationValue(chr) {
219
+ var order = array[AlphanumericSortOrder];
220
+ if(!chr) {
221
+ return null;
222
+ } else {
223
+ return order.indexOf(chr);
224
+ }
225
+ }
226
+
227
+ var AlphanumericSortOrder = 'AlphanumericSortOrder';
228
+ var AlphanumericSortIgnore = 'AlphanumericSortIgnore';
229
+ var AlphanumericSortIgnoreCase = 'AlphanumericSortIgnoreCase';
230
+ var AlphanumericSortEquivalents = 'AlphanumericSortEquivalents';
231
+
232
+
233
+
234
+ /***
235
+ * @method every(<f>, [scope])
236
+ * @returns Boolean
237
+ * @short Returns true if all elements in the array match <f>.
238
+ * @extra [scope] is the %this% object. In addition to providing this method for browsers that don't support it natively, this enhanced method also directly accepts strings, numbers, deep objects, and arrays for <f>. %all% is provided an alias.
239
+ * @example
240
+ *
241
+ + ['a','a','a'].every(function(n) {
242
+ * return n == 'a';
243
+ * });
244
+ * ['a','a','a'].every('a') -> true
245
+ * [{a:2},{a:2}].every({a:2}) -> true
246
+ *
247
+ ***
248
+ * @method some(<f>, [scope])
249
+ * @returns Boolean
250
+ * @short Returns true if any element in the array matches <f>.
251
+ * @extra [scope] is the %this% object. In addition to providing this method for browsers that don't support it natively, this enhanced method also directly accepts strings, numbers, deep objects, and arrays for <f>. %any% is provided as aliases.
252
+ * @example
253
+ *
254
+ + ['a','b','c'].some(function(n) {
255
+ * return n == 'a';
256
+ * });
257
+ + ['a','b','c'].some(function(n) {
258
+ * return n == 'd';
259
+ * });
260
+ * ['a','b','c'].some('a') -> true
261
+ * [{a:2},{b:5}].some({a:2}) -> true
262
+ *
263
+ ***
264
+ * @method map(<map>, [scope])
265
+ * @returns Array
266
+ * @short Maps the array to another array containing the values that are the result of calling <map> on each element.
267
+ * @extra [scope] is the %this% object. In addition to providing this method for browsers that don't support it natively, this enhanced method also directly accepts a string, which is a shortcut for a function that gets that property (or invokes a function) on each element.
268
+ * @example
269
+ *
270
+ + [1,2,3].map(function(n) {
271
+ * return n * 3;
272
+ * }); -> [3,6,9]
273
+ * ['one','two','three'].map(function(n) {
274
+ * return n.length;
275
+ * }); -> [3,3,5]
276
+ * ['one','two','three'].map('length') -> [3,3,5]
277
+ *
278
+ ***
279
+ * @method filter(<f>, [scope])
280
+ * @returns Array
281
+ * @short Returns any elements in the array that match <f>.
282
+ * @extra [scope] is the %this% object. In addition to providing this method for browsers that don't support it natively, this enhanced method also directly accepts strings, numbers, deep objects, and arrays for <f>.
283
+ * @example
284
+ *
285
+ + [1,2,3].filter(function(n) {
286
+ * return n > 1;
287
+ * });
288
+ * [1,2,2,4].filter(2) -> 2
289
+ *
290
+ ***/
291
+ function buildEnhancements() {
292
+ var callbackCheck = function() { var a = arguments; return a.length > 0 && !isFunction(a[0]); };
293
+ extendSimilar(array, true, callbackCheck, 'map,every,all,some,any,none,filter', function(methods, name) {
294
+ methods[name] = function(f) {
295
+ return this[name](function(el, index) {
296
+ if(name === 'map') {
297
+ return transformArgument(el, f, this, [el, index, this]);
298
+ } else {
299
+ return multiMatch(el, f, this, [el, index, this]);
300
+ }
301
+ });
302
+ }
303
+ });
304
+ }
305
+
306
+ function buildAlphanumericSort() {
307
+ var order = 'AÁÀÂÃĄBCĆČÇDĎÐEÉÈĚÊËĘFGĞHıIÍÌİÎÏJKLŁMNŃŇÑOÓÒÔPQRŘSŚŠŞTŤUÚÙŮÛÜVWXYÝZŹŻŽÞÆŒØÕÅÄÖ';
308
+ var equiv = 'AÁÀÂÃÄ,CÇ,EÉÈÊË,IÍÌİÎÏ,OÓÒÔÕÖ,Sß,UÚÙÛÜ';
309
+ array[AlphanumericSortOrder] = order.split('').map(function(str) {
310
+ return str + str.toLowerCase();
311
+ }).join('');
312
+ var equivalents = {};
313
+ arrayEach(equiv.split(','), function(set) {
314
+ var equivalent = set.charAt(0);
315
+ arrayEach(set.slice(1).split(''), function(chr) {
316
+ equivalents[chr] = equivalent;
317
+ equivalents[chr.toLowerCase()] = equivalent.toLowerCase();
318
+ });
319
+ });
320
+ array[AlphanumericSortIgnoreCase] = true;
321
+ array[AlphanumericSortEquivalents] = equivalents;
322
+ }
323
+
324
+ extend(array, false, false, {
325
+
326
+ /***
327
+ *
328
+ * @method Array.create(<obj1>, <obj2>, ...)
329
+ * @returns Array
330
+ * @short Alternate array constructor.
331
+ * @extra This method will create a single array by calling %concat% on all arguments passed. In addition to ensuring that an unknown variable is in a single, flat array (the standard constructor will create nested arrays, this one will not), it is also a useful shorthand to convert a function's arguments object into a standard array.
332
+ * @example
333
+ *
334
+ * Array.create('one', true, 3) -> ['one', true, 3]
335
+ * Array.create(['one', true, 3]) -> ['one', true, 3]
336
+ + Array.create(function(n) {
337
+ * return arguments;
338
+ * }('howdy', 'doody'));
339
+ *
340
+ ***/
341
+ 'create': function() {
342
+ var result = []
343
+ multiArgs(arguments, function(a) {
344
+ if(a && a.callee) a = multiArgs(a);
345
+ result = result.concat(a);
346
+ });
347
+ return result;
348
+ }
349
+
350
+ });
351
+
352
+ extend(array, true, false, {
353
+
354
+ /***
355
+ * @method find(<f>, [index] = 0, [loop] = false)
356
+ * @returns Mixed
357
+ * @short Returns the first element that matches <f>.
358
+ * @extra <f> will match a string, number, array, object, or alternately test against a function or regex. Starts at [index], and will continue once from index = 0 if [loop] is true.
359
+ * @example
360
+ *
361
+ + [{a:1,b:2},{a:1,b:3},{a:1,b:4}].find(function(n) {
362
+ * return n['a'] == 1;
363
+ * }); -> {a:1,b:3}
364
+ * ['cuba','japan','canada'].find(/^c/, 2) -> 'canada'
365
+ *
366
+ ***/
367
+ 'find': function(f, index, loop) {
368
+ return arrayFind(this, f, index, loop);
369
+ },
370
+
371
+ /***
372
+ * @method findAll(<f>, [index] = 0, [loop] = false)
373
+ * @returns Array
374
+ * @short Returns all elements that match <f>.
375
+ * @extra <f> will match a string, number, array, object, or alternately test against a function or regex. Starts at [index], and will continue once from index = 0 if [loop] is true.
376
+ * @example
377
+ *
378
+ + [{a:1,b:2},{a:1,b:3},{a:2,b:4}].findAll(function(n) {
379
+ * return n['a'] == 1;
380
+ * }); -> [{a:1,b:3},{a:1,b:4}]
381
+ * ['cuba','japan','canada'].findAll(/^c/) -> 'cuba','canada'
382
+ * ['cuba','japan','canada'].findAll(/^c/, 2) -> 'canada'
383
+ *
384
+ ***/
385
+ 'findAll': function(f, index, loop) {
386
+ var result = [];
387
+ arrayEach(this, function(el, i, arr) {
388
+ if(multiMatch(el, f, arr, [el, i, arr])) {
389
+ result.push(el);
390
+ }
391
+ }, index, loop);
392
+ return result;
393
+ },
394
+
395
+ /***
396
+ * @method findIndex(<f>, [startIndex] = 0, [loop] = false)
397
+ * @returns Number
398
+ * @short Returns the index of the first element that matches <f> or -1 if not found.
399
+ * @extra This method has a few notable differences to native %indexOf%. Although <f> will similarly match a primitive such as a string or number, it will also match deep objects and arrays that are not equal by reference (%===%). Additionally, if a function is passed it will be run as a matching function (similar to the behavior of %Array#filter%) rather than attempting to find that function itself by reference in the array. Finally, a regexp will be matched against elements in the array, presumed to be strings. Starts at [index], and will continue once from index = 0 if [loop] is true.
400
+ * @example
401
+ *
402
+ + [1,2,3,4].findIndex(3); -> 2
403
+ + [1,2,3,4].findIndex(function(n) {
404
+ * return n % 2 == 0;
405
+ * }); -> 1
406
+ + ['one','two','three'].findIndex(/th/); -> 2
407
+ *
408
+ ***/
409
+ 'findIndex': function(f, startIndex, loop) {
410
+ var index = arrayFind(this, f, startIndex, loop, true);
411
+ return isUndefined(index) ? -1 : index;
412
+ },
413
+
414
+ /***
415
+ * @method count(<f>)
416
+ * @returns Number
417
+ * @short Counts all elements in the array that match <f>.
418
+ * @extra <f> will match a string, number, array, object, or alternately test against a function or regex.
419
+ * @example
420
+ *
421
+ * [1,2,3,1].count(1) -> 2
422
+ * ['a','b','c'].count(/b/) -> 1
423
+ + [{a:1},{b:2}].count(function(n) {
424
+ * return n['a'] > 1;
425
+ * }); -> 0
426
+ *
427
+ ***/
428
+ 'count': function(f) {
429
+ if(isUndefined(f)) return this.length;
430
+ return this.findAll(f).length;
431
+ },
432
+
433
+ /***
434
+ * @method removeAt(<start>, [end])
435
+ * @returns Array
436
+ * @short Removes element at <start>. If [end] is specified, removes the range between <start> and [end]. This method will change the array! If you don't intend the array to be changed use %clone% first.
437
+ * @example
438
+ *
439
+ * ['a','b','c'].removeAt(0) -> ['b','c']
440
+ * [1,2,3,4].removeAt(1, 3) -> [1]
441
+ *
442
+ ***/
443
+ 'removeAt': function(start, end) {
444
+ if(isUndefined(start)) return this;
445
+ if(isUndefined(end)) end = start;
446
+ for(var i = 0; i <= (end - start); i++) {
447
+ this.splice(start, 1);
448
+ }
449
+ return this;
450
+ },
451
+
452
+ /***
453
+ * @method include(<el>, [index])
454
+ * @returns Array
455
+ * @short Adds <el> to the array.
456
+ * @extra This is a non-destructive alias for %add%. It will not change the original array.
457
+ * @example
458
+ *
459
+ * [1,2,3,4].include(5) -> [1,2,3,4,5]
460
+ * [1,2,3,4].include(8, 1) -> [1,8,2,3,4]
461
+ * [1,2,3,4].include([5,6,7]) -> [1,2,3,4,5,6,7]
462
+ *
463
+ ***/
464
+ 'include': function(el, index) {
465
+ return this.clone().add(el, index);
466
+ },
467
+
468
+ /***
469
+ * @method exclude([f1], [f2], ...)
470
+ * @returns Array
471
+ * @short Removes any element in the array that matches [f1], [f2], etc.
472
+ * @extra This is a non-destructive alias for %remove%. It will not change the original array.
473
+ * @example
474
+ *
475
+ * [1,2,3].exclude(3) -> [1,2]
476
+ * ['a','b','c'].exclude(/b/) -> ['a','c']
477
+ + [{a:1},{b:2}].exclude(function(n) {
478
+ * return n['a'] == 1;
479
+ * }); -> [{b:2}]
480
+ *
481
+ ***/
482
+ 'exclude': function() {
483
+ return array.prototype.remove.apply(this.clone(), arguments);
484
+ },
485
+
486
+ /***
487
+ * @method clone()
488
+ * @returns Array
489
+ * @short Clones the array.
490
+ * @example
491
+ *
492
+ * [1,2,3].clone() -> [1,2,3]
493
+ *
494
+ ***/
495
+ 'clone': function() {
496
+ return simpleMerge([], this);
497
+ },
498
+
499
+ /***
500
+ * @method unique([map] = null)
501
+ * @returns Array
502
+ * @short Removes all duplicate elements in the array.
503
+ * @extra [map] may be a function mapping the value to be uniqued on or a string acting as a shortcut. This is most commonly used when you have a key that ensures the object's uniqueness, and don't need to check all fields. This method will also correctly operate on arrays of objects.
504
+ * @example
505
+ *
506
+ * [1,2,2,3].unique() -> [1,2,3]
507
+ * [{foo:'bar'},{foo:'bar'}].unique() -> [{foo:'bar'}]
508
+ + [{foo:'bar'},{foo:'bar'}].unique(function(obj){
509
+ * return obj.foo;
510
+ * }); -> [{foo:'bar'}]
511
+ * [{foo:'bar'},{foo:'bar'}].unique('foo') -> [{foo:'bar'}]
512
+ *
513
+ ***/
514
+ 'unique': function(map) {
515
+ return arrayUnique(this, map);
516
+ },
517
+
518
+ /***
519
+ * @method flatten([limit] = Infinity)
520
+ * @returns Array
521
+ * @short Returns a flattened, one-dimensional copy of the array.
522
+ * @extra You can optionally specify a [limit], which will only flatten that depth.
523
+ * @example
524
+ *
525
+ * [[1], 2, [3]].flatten() -> [1,2,3]
526
+ * [['a'],[],'b','c'].flatten() -> ['a','b','c']
527
+ *
528
+ ***/
529
+ 'flatten': function(limit) {
530
+ return arrayFlatten(this, limit);
531
+ },
532
+
533
+ /***
534
+ * @method union([a1], [a2], ...)
535
+ * @returns Array
536
+ * @short Returns an array containing all elements in all arrays with duplicates removed.
537
+ * @extra This method will also correctly operate on arrays of objects.
538
+ * @example
539
+ *
540
+ * [1,3,5].union([5,7,9]) -> [1,3,5,7,9]
541
+ * ['a','b'].union(['b','c']) -> ['a','b','c']
542
+ *
543
+ ***/
544
+ 'union': function() {
545
+ return arrayUnique(this.concat(flatArguments(arguments)));
546
+ },
547
+
548
+ /***
549
+ * @method intersect([a1], [a2], ...)
550
+ * @returns Array
551
+ * @short Returns an array containing the elements all arrays have in common.
552
+ * @extra This method will also correctly operate on arrays of objects.
553
+ * @example
554
+ *
555
+ * [1,3,5].intersect([5,7,9]) -> [5]
556
+ * ['a','b'].intersect('b','c') -> ['b']
557
+ *
558
+ ***/
559
+ 'intersect': function() {
560
+ return arrayIntersect(this, flatArguments(arguments), false);
561
+ },
562
+
563
+ /***
564
+ * @method subtract([a1], [a2], ...)
565
+ * @returns Array
566
+ * @short Subtracts from the array all elements in [a1], [a2], etc.
567
+ * @extra This method will also correctly operate on arrays of objects.
568
+ * @example
569
+ *
570
+ * [1,3,5].subtract([5,7,9]) -> [1,3]
571
+ * [1,3,5].subtract([3],[5]) -> [1]
572
+ * ['a','b'].subtract('b','c') -> ['a']
573
+ *
574
+ ***/
575
+ 'subtract': function(a) {
576
+ return arrayIntersect(this, flatArguments(arguments), true);
577
+ },
578
+
579
+ /***
580
+ * @method at(<index>, [loop] = true)
581
+ * @returns Mixed
582
+ * @short Gets the element(s) at a given index.
583
+ * @extra When [loop] is true, overshooting the end of the array (or the beginning) will begin counting from the other end. As an alternate syntax, passing multiple indexes will get the elements at those indexes.
584
+ * @example
585
+ *
586
+ * [1,2,3].at(0) -> 1
587
+ * [1,2,3].at(2) -> 3
588
+ * [1,2,3].at(4) -> 2
589
+ * [1,2,3].at(4, false) -> null
590
+ * [1,2,3].at(-1) -> 3
591
+ * [1,2,3].at(0,1) -> [1,2]
592
+ *
593
+ ***/
594
+ 'at': function() {
595
+ return entryAtIndex(this, arguments);
596
+ },
597
+
598
+ /***
599
+ * @method first([num] = 1)
600
+ * @returns Mixed
601
+ * @short Returns the first element(s) in the array.
602
+ * @extra When <num> is passed, returns the first <num> elements in the array.
603
+ * @example
604
+ *
605
+ * [1,2,3].first() -> 1
606
+ * [1,2,3].first(2) -> [1,2]
607
+ *
608
+ ***/
609
+ 'first': function(num) {
610
+ if(isUndefined(num)) return this[0];
611
+ if(num < 0) num = 0;
612
+ return this.slice(0, num);
613
+ },
614
+
615
+ /***
616
+ * @method last([num] = 1)
617
+ * @returns Mixed
618
+ * @short Returns the last element(s) in the array.
619
+ * @extra When <num> is passed, returns the last <num> elements in the array.
620
+ * @example
621
+ *
622
+ * [1,2,3].last() -> 3
623
+ * [1,2,3].last(2) -> [2,3]
624
+ *
625
+ ***/
626
+ 'last': function(num) {
627
+ if(isUndefined(num)) return this[this.length - 1];
628
+ var start = this.length - num < 0 ? 0 : this.length - num;
629
+ return this.slice(start);
630
+ },
631
+
632
+ /***
633
+ * @method from(<index>)
634
+ * @returns Array
635
+ * @short Returns a slice of the array from <index>.
636
+ * @example
637
+ *
638
+ * [1,2,3].from(1) -> [2,3]
639
+ * [1,2,3].from(2) -> [3]
640
+ *
641
+ ***/
642
+ 'from': function(num) {
643
+ return this.slice(num);
644
+ },
645
+
646
+ /***
647
+ * @method to(<index>)
648
+ * @returns Array
649
+ * @short Returns a slice of the array up to <index>.
650
+ * @example
651
+ *
652
+ * [1,2,3].to(1) -> [1]
653
+ * [1,2,3].to(2) -> [1,2]
654
+ *
655
+ ***/
656
+ 'to': function(num) {
657
+ if(isUndefined(num)) num = this.length;
658
+ return this.slice(0, num);
659
+ },
660
+
661
+ /***
662
+ * @method min([map], [all] = false)
663
+ * @returns Mixed
664
+ * @short Returns the element in the array with the lowest value.
665
+ * @extra [map] may be a function mapping the value to be checked or a string acting as a shortcut. If [all] is true, will return all min values in an array.
666
+ * @example
667
+ *
668
+ * [1,2,3].min() -> 1
669
+ * ['fee','fo','fum'].min('length') -> 'fo'
670
+ * ['fee','fo','fum'].min('length', true) -> ['fo']
671
+ + ['fee','fo','fum'].min(function(n) {
672
+ * return n.length;
673
+ * }); -> ['fo']
674
+ + [{a:3,a:2}].min(function(n) {
675
+ * return n['a'];
676
+ * }); -> [{a:2}]
677
+ *
678
+ ***/
679
+ 'min': function(map, all) {
680
+ return getMinOrMax(this, map, 'min', all);
681
+ },
682
+
683
+ /***
684
+ * @method max([map], [all] = false)
685
+ * @returns Mixed
686
+ * @short Returns the element in the array with the greatest value.
687
+ * @extra [map] may be a function mapping the value to be checked or a string acting as a shortcut. If [all] is true, will return all max values in an array.
688
+ * @example
689
+ *
690
+ * [1,2,3].max() -> 3
691
+ * ['fee','fo','fum'].max('length') -> 'fee'
692
+ * ['fee','fo','fum'].max('length', true) -> ['fee']
693
+ + [{a:3,a:2}].max(function(n) {
694
+ * return n['a'];
695
+ * }); -> {a:3}
696
+ *
697
+ ***/
698
+ 'max': function(map, all) {
699
+ return getMinOrMax(this, map, 'max', all);
700
+ },
701
+
702
+ /***
703
+ * @method least([map])
704
+ * @returns Array
705
+ * @short Returns the elements in the array with the least commonly occuring value.
706
+ * @extra [map] may be a function mapping the value to be checked or a string acting as a shortcut.
707
+ * @example
708
+ *
709
+ * [3,2,2].least() -> [3]
710
+ * ['fe','fo','fum'].least('length') -> ['fum']
711
+ + [{age:35,name:'ken'},{age:12,name:'bob'},{age:12,name:'ted'}].least(function(n) {
712
+ * return n.age;
713
+ * }); -> [{age:35,name:'ken'}]
714
+ *
715
+ ***/
716
+ 'least': function(map, all) {
717
+ return getMinOrMax(this.groupBy.apply(this, [map]), 'length', 'min', all);
718
+ },
719
+
720
+ /***
721
+ * @method most([map])
722
+ * @returns Array
723
+ * @short Returns the elements in the array with the most commonly occuring value.
724
+ * @extra [map] may be a function mapping the value to be checked or a string acting as a shortcut.
725
+ * @example
726
+ *
727
+ * [3,2,2].most() -> [2]
728
+ * ['fe','fo','fum'].most('length') -> ['fe','fo']
729
+ + [{age:35,name:'ken'},{age:12,name:'bob'},{age:12,name:'ted'}].most(function(n) {
730
+ * return n.age;
731
+ * }); -> [{age:12,name:'bob'},{age:12,name:'ted'}]
732
+ *
733
+ ***/
734
+ 'most': function(map, all) {
735
+ return getMinOrMax(this.groupBy.apply(this, [map]), 'length', 'max', all);
736
+ },
737
+
738
+ /***
739
+ * @method sum([map])
740
+ * @returns Number
741
+ * @short Sums all values in the array.
742
+ * @extra [map] may be a function mapping the value to be summed or a string acting as a shortcut.
743
+ * @example
744
+ *
745
+ * [1,2,2].sum() -> 5
746
+ + [{age:35},{age:12},{age:12}].sum(function(n) {
747
+ * return n.age;
748
+ * }); -> 59
749
+ * [{age:35},{age:12},{age:12}].sum('age') -> 59
750
+ *
751
+ ***/
752
+ 'sum': function(map) {
753
+ var arr = map ? this.map(map) : this;
754
+ return arr.length > 0 ? arr.reduce(function(a,b) { return a + b; }) : 0;
755
+ },
756
+
757
+ /***
758
+ * @method average([map])
759
+ * @returns Number
760
+ * @short Averages all values in the array.
761
+ * @extra [map] may be a function mapping the value to be averaged or a string acting as a shortcut.
762
+ * @example
763
+ *
764
+ * [1,2,3].average() -> 2
765
+ + [{age:35},{age:11},{age:11}].average(function(n) {
766
+ * return n.age;
767
+ * }); -> 19
768
+ * [{age:35},{age:11},{age:11}].average('age') -> 19
769
+ *
770
+ ***/
771
+ 'average': function(map) {
772
+ var arr = map ? this.map(map) : this;
773
+ return arr.length > 0 ? arr.sum() / arr.length : 0;
774
+ },
775
+
776
+ /***
777
+ * @method inGroups(<num>, [padding])
778
+ * @returns Array
779
+ * @short Groups the array into <num> arrays.
780
+ * @extra [padding] specifies a value with which to pad the last array so that they are all equal length.
781
+ * @example
782
+ *
783
+ * [1,2,3,4,5,6,7].inGroups(3) -> [ [1,2,3], [4,5,6], [7] ]
784
+ * [1,2,3,4,5,6,7].inGroups(3, 'none') -> [ [1,2,3], [4,5,6], [7,'none','none'] ]
785
+ *
786
+ ***/
787
+ 'inGroups': function(num, padding) {
788
+ var pad = arguments.length > 1;
789
+ var arr = this;
790
+ var result = [];
791
+ var divisor = ceil(this.length / num);
792
+ getRange(0, num - 1, function(i) {
793
+ var index = i * divisor;
794
+ var group = arr.slice(index, index + divisor);
795
+ if(pad && group.length < divisor) {
796
+ getRange(1, divisor - group.length, function() {
797
+ group = group.add(padding);
798
+ });
799
+ }
800
+ result.push(group);
801
+ });
802
+ return result;
803
+ },
804
+
805
+ /***
806
+ * @method inGroupsOf(<num>, [padding] = null)
807
+ * @returns Array
808
+ * @short Groups the array into arrays of <num> elements each.
809
+ * @extra [padding] specifies a value with which to pad the last array so that they are all equal length.
810
+ * @example
811
+ *
812
+ * [1,2,3,4,5,6,7].inGroupsOf(4) -> [ [1,2,3,4], [5,6,7] ]
813
+ * [1,2,3,4,5,6,7].inGroupsOf(4, 'none') -> [ [1,2,3,4], [5,6,7,'none'] ]
814
+ *
815
+ ***/
816
+ 'inGroupsOf': function(num, padding) {
817
+ var result = [], len = this.length, arr = this, group;
818
+ if(len === 0 || num === 0) return arr;
819
+ if(isUndefined(num)) num = 1;
820
+ if(isUndefined(padding)) padding = null;
821
+ getRange(0, ceil(len / num) - 1, function(i) {
822
+ group = arr.slice(num * i, num * i + num);
823
+ while(group.length < num) {
824
+ group.push(padding);
825
+ }
826
+ result.push(group);
827
+ });
828
+ return result;
829
+ },
830
+
831
+ /***
832
+ * @method isEmpty()
833
+ * @returns Boolean
834
+ * @short Returns true if the array is empty.
835
+ * @extra This is true if the array has a length of zero, or contains only %undefined%, %null%, or %NaN%.
836
+ * @example
837
+ *
838
+ * [].isEmpty() -> true
839
+ * [null,undefined].isEmpty() -> true
840
+ *
841
+ ***/
842
+ 'isEmpty': function() {
843
+ return this.compact().length == 0;
844
+ },
845
+
846
+ /***
847
+ * @method sortBy(<map>, [desc] = false)
848
+ * @returns Array
849
+ * @short Sorts the array by <map>.
850
+ * @extra <map> may be a function, a string acting as a shortcut, or blank (direct comparison of array values). [desc] will sort the array in descending order. When the field being sorted on is a string, the resulting order will be determined by an internal collation algorithm that is optimized for major Western languages, but can be customized. For more information see @array_sorting.
851
+ * @example
852
+ *
853
+ * ['world','a','new'].sortBy('length') -> ['a','new','world']
854
+ * ['world','a','new'].sortBy('length', true) -> ['world','new','a']
855
+ + [{age:72},{age:13},{age:18}].sortBy(function(n) {
856
+ * return n.age;
857
+ * }); -> [{age:13},{age:18},{age:72}]
858
+ *
859
+ ***/
860
+ 'sortBy': function(map, desc) {
861
+ var arr = this.clone();
862
+ arr.sort(function(a, b) {
863
+ var aProperty, bProperty, comp;
864
+ aProperty = transformArgument(a, map, arr, [a]);
865
+ bProperty = transformArgument(b, map, arr, [b]);
866
+ if(isString(aProperty) && isString(bProperty)) {
867
+ comp = collateStrings(aProperty, bProperty);
868
+ } else if(aProperty < bProperty) {
869
+ comp = -1;
870
+ } else if(aProperty > bProperty) {
871
+ comp = 1;
872
+ } else {
873
+ comp = 0;
874
+ }
875
+ return comp * (desc ? -1 : 1);
876
+ });
877
+ return arr;
878
+ },
879
+
880
+ /***
881
+ * @method randomize()
882
+ * @returns Array
883
+ * @short Returns a copy of the array with the elements randomized.
884
+ * @extra Uses Fisher-Yates algorithm.
885
+ * @example
886
+ *
887
+ * [1,2,3,4].randomize() -> [?,?,?,?]
888
+ *
889
+ ***/
890
+ 'randomize': function() {
891
+ var a = this.concat();
892
+ for(var j, x, i = a.length; i; j = parseInt(math.random() * i), x = a[--i], a[i] = a[j], a[j] = x) {};
893
+ return a;
894
+ },
895
+
896
+ /***
897
+ * @method zip([arr1], [arr2], ...)
898
+ * @returns Array
899
+ * @short Merges multiple arrays together.
900
+ * @extra This method "zips up" smaller arrays into one large whose elements are "all elements at index 0", "all elements at index 1", etc. Useful when you have associated data that is split over separated arrays. If the arrays passed have more elements than the original array, they will be discarded. If they have fewer elements, the missing elements will filled with %null%.
901
+ * @example
902
+ *
903
+ * [1,2,3].zip([4,5,6]) -> [[1,2], [3,4], [5,6]]
904
+ * ['Martin','John'].zip(['Luther','F.'], ['King','Kennedy']) -> [['Martin','Luther','King'], ['John','F.','Kennedy']]
905
+ *
906
+ ***/
907
+ 'zip': function() {
908
+ var args = multiArgs(arguments);
909
+ return this.map(function(el, i) {
910
+ return [el].concat(args.map(function(k) {
911
+ return (i in k) ? k[i] : null;
912
+ }));
913
+ });
914
+ },
915
+
916
+ /***
917
+ * @method sample([num])
918
+ * @returns Mixed
919
+ * @short Returns a random element from the array.
920
+ * @extra If [num] is passed, will return [num] samples from the array.
921
+ * @example
922
+ *
923
+ * [1,2,3,4,5].sample() -> // Random element
924
+ * [1,2,3,4,5].sample(3) -> // Array of 3 random elements
925
+ *
926
+ ***/
927
+ 'sample': function(num) {
928
+ var result = [], arr = this.clone(), index;
929
+ if(isUndefined(num)) num = 1;
930
+ while(result.length < num) {
931
+ index = floor(math.random() * (arr.length - 1));
932
+ result.push(arr[index]);
933
+ arr.removeAt(index);
934
+ if(arr.length == 0) break;
935
+ }
936
+ return arguments.length > 0 ? result : result[0];
937
+ },
938
+
939
+ /***
940
+ * @method each(<fn>, [index] = 0, [loop] = false)
941
+ * @returns Array
942
+ * @short Runs <fn> against each element in the array. Enhanced version of %Array#forEach%.
943
+ * @extra Parameters passed to <fn> are identical to %forEach%, ie. the first parameter is the current element, second parameter is the current index, and third parameter is the array itself. If <fn> returns %false% at any time it will break out of the loop. Once %each% finishes, it will return the array. If [index] is passed, <fn> will begin at that index and work its way to the end. If [loop] is true, it will then start over from the beginning of the array and continue until it reaches [index] - 1.
944
+ * @example
945
+ *
946
+ * [1,2,3,4].each(function(n) {
947
+ * // Called 4 times: 1, 2, 3, 4
948
+ * });
949
+ * [1,2,3,4].each(function(n) {
950
+ * // Called 4 times: 3, 4, 1, 2
951
+ * }, 2, true);
952
+ *
953
+ ***/
954
+ 'each': function(fn, index, loop) {
955
+ arrayEach(this, fn, index, loop);
956
+ return this;
957
+ },
958
+
959
+ /***
960
+ * @method add(<el>, [index])
961
+ * @returns Array
962
+ * @short Adds <el> to the array.
963
+ * @extra If [index] is specified, it will add at [index], otherwise adds to the end of the array. %add% behaves like %concat% in that if <el> is an array it will be joined, not inserted. This method will change the array! Use %include% for a non-destructive alias. Also, %insert% is provided as an alias that reads better when using an index.
964
+ * @example
965
+ *
966
+ * [1,2,3,4].add(5) -> [1,2,3,4,5]
967
+ * [1,2,3,4].add([5,6,7]) -> [1,2,3,4,5,6,7]
968
+ * [1,2,3,4].insert(8, 1) -> [1,8,2,3,4]
969
+ *
970
+ ***/
971
+ 'add': function(el, index) {
972
+ if(!isNumber(number(index)) || isNaN(index)) index = this.length;
973
+ array.prototype.splice.apply(this, [index, 0].concat(el));
974
+ return this;
975
+ },
976
+
977
+ /***
978
+ * @method remove([f1], [f2], ...)
979
+ * @returns Array
980
+ * @short Removes any element in the array that matches [f1], [f2], etc.
981
+ * @extra Will match a string, number, array, object, or alternately test against a function or regex. This method will change the array! Use %exclude% for a non-destructive alias.
982
+ * @example
983
+ *
984
+ * [1,2,3].remove(3) -> [1,2]
985
+ * ['a','b','c'].remove(/b/) -> ['a','c']
986
+ + [{a:1},{b:2}].remove(function(n) {
987
+ * return n['a'] == 1;
988
+ * }); -> [{b:2}]
989
+ *
990
+ ***/
991
+ 'remove': function() {
992
+ var i, arr = this;
993
+ multiArgs(arguments, function(f) {
994
+ i = 0;
995
+ while(i < arr.length) {
996
+ if(multiMatch(arr[i], f, arr, [arr[i], i, arr])) {
997
+ arr.splice(i, 1);
998
+ } else {
999
+ i++;
1000
+ }
1001
+ }
1002
+ });
1003
+ return arr;
1004
+ },
1005
+
1006
+ /***
1007
+ * @method compact([all] = false)
1008
+ * @returns Array
1009
+ * @short Removes all instances of %undefined%, %null%, and %NaN% from the array.
1010
+ * @extra If [all] is %true%, all "falsy" elements will be removed. This includes empty strings, 0, and false.
1011
+ * @example
1012
+ *
1013
+ * [1,null,2,undefined,3].compact() -> [1,2,3]
1014
+ * [1,'',2,false,3].compact() -> [1,'',2,false,3]
1015
+ * [1,'',2,false,3].compact(true) -> [1,2,3]
1016
+ *
1017
+ ***/
1018
+ 'compact': function(all) {
1019
+ var result = [];
1020
+ arrayEach(this, function(el, i) {
1021
+ if(isArray(el)) {
1022
+ result.push(el.compact());
1023
+ } else if(all && el) {
1024
+ result.push(el);
1025
+ } else if(!all && el != null && el.valueOf() === el.valueOf()) {
1026
+ result.push(el);
1027
+ }
1028
+ });
1029
+ return result;
1030
+ },
1031
+
1032
+ /***
1033
+ * @method groupBy(<map>, [fn])
1034
+ * @returns Object
1035
+ * @short Groups the array by <map>.
1036
+ * @extra Will return an object with keys equal to the grouped values. <map> may be a mapping function, or a string acting as a shortcut. Optionally calls [fn] for each group.
1037
+ * @example
1038
+ *
1039
+ * ['fee','fi','fum'].groupBy('length') -> { 2: ['fi'], 3: ['fee','fum'] }
1040
+ + [{age:35,name:'ken'},{age:15,name:'bob'}].groupBy(function(n) {
1041
+ * return n.age;
1042
+ * }); -> { 35: [{age:35,name:'ken'}], 15: [{age:15,name:'bob'}] }
1043
+ *
1044
+ ***/
1045
+ 'groupBy': function(map, fn) {
1046
+ var arr = this, result = {}, key;
1047
+ arrayEach(arr, function(el, index) {
1048
+ key = transformArgument(el, map, arr, [el, index, arr]);
1049
+ if(!result[key]) result[key] = [];
1050
+ result[key].push(el);
1051
+ });
1052
+ if(fn) {
1053
+ iterateOverObject(result, fn);
1054
+ }
1055
+ return result;
1056
+ },
1057
+
1058
+ /***
1059
+ * @method none(<f>)
1060
+ * @returns Boolean
1061
+ * @short Returns true if none of the elements in the array match <f>.
1062
+ * @extra <f> will match a string, number, array, object, or alternately test against a function or regex.
1063
+ * @example
1064
+ *
1065
+ * [1,2,3].none(5) -> true
1066
+ * ['a','b','c'].none(/b/) -> false
1067
+ + [{a:1},{b:2}].none(function(n) {
1068
+ * return n['a'] > 1;
1069
+ * }); -> true
1070
+ *
1071
+ ***/
1072
+ 'none': function() {
1073
+ return !this.any.apply(this, arguments);
1074
+ }
1075
+
1076
+
1077
+ });
1078
+
1079
+ // Aliases
1080
+ extend(array, true, false, {
1081
+
1082
+ /***
1083
+ * @method all()
1084
+ * @alias every
1085
+ *
1086
+ ***/
1087
+ 'all': array.prototype.every,
1088
+
1089
+ /*** @method any()
1090
+ * @alias some
1091
+ *
1092
+ ***/
1093
+ 'any': array.prototype.some,
1094
+
1095
+ /***
1096
+ * @method insert()
1097
+ * @alias add
1098
+ *
1099
+ ***/
1100
+ 'insert': array.prototype.add
1101
+
1102
+ });
1103
+
1104
+
1105
+ /***
1106
+ * Object module
1107
+ * Enumerable methods on objects
1108
+ *
1109
+ ***/
1110
+
1111
+ function keysWithCoercion(obj) {
1112
+ if(obj && obj.valueOf) {
1113
+ obj = obj.valueOf();
1114
+ }
1115
+ return object.keys(obj);
1116
+ }
1117
+
1118
+ /***
1119
+ * @method [enumerable](<obj>)
1120
+ * @returns Boolean
1121
+ * @short Enumerable methods in the Array package are also available to the Object class. They will perform their normal operations for every property in <obj>.
1122
+ * @extra In cases where a callback is used, instead of %element, index%, the callback will instead be passed %key, value%. Enumerable methods are also available to extended objects as instance methods.
1123
+ *
1124
+ * @set
1125
+ * map
1126
+ * any
1127
+ * all
1128
+ * none
1129
+ * count
1130
+ * find
1131
+ * findAll
1132
+ * reduce
1133
+ * isEmpty
1134
+ * sum
1135
+ * average
1136
+ * min
1137
+ * max
1138
+ * least
1139
+ * most
1140
+ *
1141
+ * @example
1142
+ *
1143
+ * Object.any({foo:'bar'}, 'bar') -> true
1144
+ * Object.extended({foo:'bar'}).any('bar') -> true
1145
+ * Object.isEmpty({}) -> true
1146
+ + Object.map({ fred: { age: 52 } }, 'age'); -> { fred: 52 }
1147
+ *
1148
+ ***/
1149
+
1150
+ function buildEnumerableMethods(names, mapping) {
1151
+ extendSimilar(object, false, false, names, function(methods, name) {
1152
+ methods[name] = function(obj, arg1, arg2) {
1153
+ var result;
1154
+ result = array.prototype[name].call(keysWithCoercion(obj), function(key) {
1155
+ if(mapping) {
1156
+ return transformArgument(obj[key], arg1, obj, [key, obj[key], obj]);
1157
+ } else {
1158
+ return multiMatch(obj[key], arg1, obj, [key, obj[key], obj]);
1159
+ }
1160
+ }, arg2);
1161
+ if(isArray(result)) {
1162
+ // The method has returned an array of keys so use this array
1163
+ // to build up the resulting object in the form we want it in.
1164
+ result = result.reduce(function(o, key, i) {
1165
+ o[key] = obj[key];
1166
+ return o;
1167
+ }, {});
1168
+ }
1169
+ return result;
1170
+ };
1171
+ });
1172
+ buildObjectInstanceMethods(names, Hash);
1173
+ }
1174
+
1175
+ extend(object, false, false, {
1176
+
1177
+ 'map': function(obj, map) {
1178
+ return keysWithCoercion(obj).reduce(function(result, key) {
1179
+ result[key] = transformArgument(obj[key], map, obj, [key, obj[key], obj]);
1180
+ return result;
1181
+ }, {});
1182
+ },
1183
+
1184
+ 'reduce': function(obj) {
1185
+ var values = keysWithCoercion(obj).map(function(key) {
1186
+ return obj[key];
1187
+ });
1188
+ return values.reduce.apply(values, multiArgs(arguments).slice(1));
1189
+ },
1190
+
1191
+ /***
1192
+ * @method size(<obj>)
1193
+ * @returns Number
1194
+ * @short Returns the number of properties in <obj>.
1195
+ * @extra %size% is available as an instance method on extended objects.
1196
+ * @example
1197
+ *
1198
+ * Object.size({ foo: 'bar' }) -> 1
1199
+ *
1200
+ ***/
1201
+ 'size': function (obj) {
1202
+ return keysWithCoercion(obj).length;
1203
+ }
1204
+
1205
+ });
1206
+
1207
+ buildEnhancements();
1208
+ buildAlphanumericSort();
1209
+ buildEnumerableMethods('each,any,all,none,count,find,findAll,isEmpty');
1210
+ buildEnumerableMethods('sum,average,min,max,least,most', true);
1211
+ buildObjectInstanceMethods('map,reduce,size', Hash);
1212
+