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
@@ -0,0 +1,348 @@
1
+
2
+ /***
3
+ * @package Object
4
+ * @dependency core
5
+ * @description Object manipulation, type checking (isNumber, isString, ...), extended objects with hash-like methods available as instance methods.
6
+ *
7
+ * Much thanks to kangax for his informative aricle about how problems with instanceof and constructor
8
+ * http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/
9
+ *
10
+ ***/
11
+
12
+ var ObjectTypeMethods = 'isObject,isNaN'.split(',');
13
+ var ObjectHashMethods = 'keys,values,each,merge,clone,equal,watch,tap,has'.split(',');
14
+
15
+ function setParamsObject(obj, param, value, deep) {
16
+ var reg = /^(.+?)(\[.*\])$/, paramIsArray, match, allKeys, key;
17
+ if(deep !== false && (match = param.match(reg))) {
18
+ key = match[1];
19
+ allKeys = match[2].replace(/^\[|\]$/g, '').split('][');
20
+ allKeys.forEach(function(k) {
21
+ paramIsArray = !k || k.match(/^\d+$/);
22
+ if(!key && isArray(obj)) key = obj.length;
23
+ if(!obj[key]) {
24
+ obj[key] = paramIsArray ? [] : {};
25
+ }
26
+ obj = obj[key];
27
+ key = k;
28
+ });
29
+ if(!key && paramIsArray) key = obj.length.toString();
30
+ setParamsObject(obj, key, value);
31
+ } else if(value.match(/^[\d.]+$/)) {
32
+ obj[param] = parseFloat(value);
33
+ } else if(value === 'true') {
34
+ obj[param] = true;
35
+ } else if(value === 'false') {
36
+ obj[param] = false;
37
+ } else {
38
+ obj[param] = value;
39
+ }
40
+ }
41
+
42
+
43
+ /***
44
+ * @method Object.is[Type](<obj>)
45
+ * @returns Boolean
46
+ * @short Returns true if <obj> is an object of that type.
47
+ * @extra %isObject% will return false on anything that is not an object literal, including instances of inherited classes. Note also that %isNaN% will ONLY return true if the object IS %NaN%. It does not mean the same as browser native %isNaN%, which returns true for anything that is "not a number".
48
+ *
49
+ * @set
50
+ * isArray
51
+ * isObject
52
+ * isBoolean
53
+ * isDate
54
+ * isFunction
55
+ * isNaN
56
+ * isNumber
57
+ * isString
58
+ * isRegExp
59
+ *
60
+ * @example
61
+ *
62
+ * Object.isArray([1,2,3]) -> true
63
+ * Object.isDate(3) -> false
64
+ * Object.isRegExp(/wasabi/) -> true
65
+ * Object.isObject({ broken:'wear' }) -> true
66
+ *
67
+ ***/
68
+ function buildTypeMethods() {
69
+ extendSimilar(object, false, false, ClassNames, function(methods, name) {
70
+ var method = 'is' + name;
71
+ ObjectTypeMethods.push(method);
72
+ methods[method] = function(obj) {
73
+ return isClass(obj, name);
74
+ }
75
+ });
76
+ }
77
+
78
+ function buildObjectExtend() {
79
+ extend(object, false, function(){ return arguments.length === 0; }, {
80
+ 'extend': function() {
81
+ buildObjectInstanceMethods(ObjectTypeMethods.concat(ObjectHashMethods), object);
82
+ }
83
+ });
84
+ }
85
+
86
+ extend(object, false, true, {
87
+ /***
88
+ * @method watch(<obj>, <prop>, <fn>)
89
+ * @returns Nothing
90
+ * @short Watches a property of <obj> and runs <fn> when it changes.
91
+ * @extra <fn> is passed three arguments: the property <prop>, the old value, and the new value. The return value of [fn] will be set as the new value. This method is useful for things such as validating or cleaning the value when it is set. Warning: this method WILL NOT work in browsers that don't support %Object.defineProperty%. This notably includes IE 8 and below, and Opera. This is the only method in Sugar that is not fully compatible with all browsers. %watch% is available as an instance method on extended objects.
92
+ * @example
93
+ *
94
+ * Object.watch({ foo: 'bar' }, 'foo', function(prop, oldVal, newVal) {
95
+ * // Will be run when the property 'foo' is set on the object.
96
+ * });
97
+ * Object.extended().watch({ foo: 'bar' }, 'foo', function(prop, oldVal, newVal) {
98
+ * // Will be run when the property 'foo' is set on the object.
99
+ * });
100
+ *
101
+ ***/
102
+ 'watch': function(obj, prop, fn) {
103
+ if(!definePropertySupport) return;
104
+ var value = obj[prop];
105
+ object.defineProperty(obj, prop, {
106
+ 'enumerable' : true,
107
+ 'configurable': true,
108
+ 'get': function() {
109
+ return value;
110
+ },
111
+ 'set': function(to) {
112
+ value = fn.call(obj, prop, value, to);
113
+ }
114
+ });
115
+ }
116
+ });
117
+
118
+ extend(object, false, function(arg1, arg2) { return isFunction(arg2); }, {
119
+
120
+ /***
121
+ * @method keys(<obj>, [fn])
122
+ * @returns Array
123
+ * @short Returns an array containing the keys in <obj>. Optionally calls [fn] for each key.
124
+ * @extra This method is provided for browsers that don't support it natively, and additionally is enhanced to accept the callback [fn]. Returned keys are in no particular order. %keys% is available as an instance method on extended objects.
125
+ * @example
126
+ *
127
+ * Object.keys({ broken: 'wear' }) -> ['broken']
128
+ * Object.keys({ broken: 'wear' }, function(key, value) {
129
+ * // Called once for each key.
130
+ * });
131
+ * Object.extended({ broken: 'wear' }).keys() -> ['broken']
132
+ *
133
+ ***/
134
+ 'keys': function(obj, fn) {
135
+ var keys = object.keys(obj);
136
+ keys.forEach(function(key) {
137
+ fn.call(obj, key, obj[key]);
138
+ });
139
+ return keys;
140
+ }
141
+
142
+ });
143
+
144
+ extend(object, false, false, {
145
+
146
+ 'isObject': function(obj) {
147
+ return isObject(obj);
148
+ },
149
+
150
+ 'isNaN': function(obj) {
151
+ // This is only true of NaN
152
+ return isNumber(obj) && obj.valueOf() !== obj.valueOf();
153
+ },
154
+
155
+ /***
156
+ * @method equal(<a>, <b>)
157
+ * @returns Boolean
158
+ * @short Returns true if <a> and <b> are equal.
159
+ * @extra %equal% in Sugar is "egal", meaning the values are equal if they are "not observably distinguishable". Note that on extended objects the name is %equals% for readability.
160
+ * @example
161
+ *
162
+ * Object.equal({a:2}, {a:2}) -> true
163
+ * Object.equal({a:2}, {a:3}) -> false
164
+ * Object.extended({a:2}).equals({a:3}) -> false
165
+ *
166
+ ***/
167
+ 'equal': function(a, b) {
168
+ return stringify(a) === stringify(b);
169
+ },
170
+
171
+ /***
172
+ * @method Object.extended(<obj> = {})
173
+ * @returns Extended object
174
+ * @short Creates a new object, equivalent to %new Object()% or %{}%, but with extended methods.
175
+ * @extra See extended objects for more.
176
+ * @example
177
+ *
178
+ * Object.extended()
179
+ * Object.extended({ happy:true, pappy:false }).keys() -> ['happy','pappy']
180
+ * Object.extended({ happy:true, pappy:false }).values() -> [true, false]
181
+ *
182
+ ***/
183
+ 'extended': function(obj) {
184
+ return new Hash(obj);
185
+ },
186
+
187
+ /***
188
+ * @method merge(<target>, <source>, [deep] = false, [resolve] = true)
189
+ * @returns Merged object
190
+ * @short Merges all the properties of <source> into <target>.
191
+ * @extra Merges are shallow unless [deep] is %true%. Properties of <source> will win in the case of conflicts, unless [resolve] is %false%. [resolve] can also be a function that resolves the conflict. In this case it will be passed 3 arguments, %key%, %targetVal%, and %sourceVal%, with the context set to <source>. This will allow you to solve conflict any way you want, ie. adding two numbers together, etc. %merge% is available as an instance method on extended objects.
192
+ * @example
193
+ *
194
+ * Object.merge({a:1},{b:2}) -> { a:1, b:2 }
195
+ * Object.merge({a:1},{a:2}, false, false) -> { a:1 }
196
+ + Object.merge({a:1},{a:2}, false, function(key, a, b) {
197
+ * return a + b;
198
+ * }); -> { a:3 }
199
+ * Object.extended({a:1}).merge({b:2}) -> { a:1, b:2 }
200
+ *
201
+ ***/
202
+ 'merge': function(target, source, deep, resolve) {
203
+ var key, val;
204
+ // Strings cannot be reliably merged thanks to
205
+ // their properties not being enumerable in < IE8.
206
+ if(target && typeof source != 'string') {
207
+ for(key in source) {
208
+ if(!hasOwnProperty(source, key) || !target) continue;
209
+ val = source[key];
210
+ // Conflict!
211
+ if(isDefined(target[key])) {
212
+ // Do not merge.
213
+ if(resolve === false) {
214
+ continue;
215
+ }
216
+ // Use the result of the callback as the result.
217
+ if(isFunction(resolve)) {
218
+ val = resolve.call(source, key, target[key], source[key])
219
+ }
220
+ }
221
+ // Deep merging.
222
+ if(deep === true && val && isObjectPrimitive(val)) {
223
+ if(isDate(val)) {
224
+ val = new date(val.getTime());
225
+ } else if(isRegExp(val)) {
226
+ val = new regexp(val.source, getRegExpFlags(val));
227
+ } else {
228
+ if(!target[key]) target[key] = array.isArray(val) ? [] : {};
229
+ object.merge(target[key], source[key], deep, resolve);
230
+ continue;
231
+ }
232
+ }
233
+ target[key] = val;
234
+ }
235
+ }
236
+ return target;
237
+ },
238
+
239
+ /***
240
+ * @method values(<obj>, [fn])
241
+ * @returns Array
242
+ * @short Returns an array containing the values in <obj>. Optionally calls [fn] for each value.
243
+ * @extra Returned values are in no particular order. %values% is available as an instance method on extended objects.
244
+ * @example
245
+ *
246
+ * Object.values({ broken: 'wear' }) -> ['wear']
247
+ * Object.values({ broken: 'wear' }, function(value) {
248
+ * // Called once for each value.
249
+ * });
250
+ * Object.extended({ broken: 'wear' }).values() -> ['wear']
251
+ *
252
+ ***/
253
+ 'values': function(obj, fn) {
254
+ var values = [];
255
+ iterateOverObject(obj, function(k,v) {
256
+ values.push(v);
257
+ if(fn) fn.call(obj,v);
258
+ });
259
+ return values;
260
+ },
261
+
262
+ /***
263
+ * @method clone(<obj> = {}, [deep] = false)
264
+ * @returns Cloned object
265
+ * @short Creates a clone (copy) of <obj>.
266
+ * @extra Default is a shallow clone, unless [deep] is true. %clone% is available as an instance method on extended objects.
267
+ * @example
268
+ *
269
+ * Object.clone({foo:'bar'}) -> { foo: 'bar' }
270
+ * Object.clone() -> {}
271
+ * Object.extended({foo:'bar'}).clone() -> { foo: 'bar' }
272
+ *
273
+ ***/
274
+ 'clone': function(obj, deep) {
275
+ if(!isObjectPrimitive(obj)) return obj;
276
+ if(array.isArray(obj)) return obj.concat();
277
+ var target = obj instanceof Hash ? new Hash() : {};
278
+ return object.merge(target, obj, deep);
279
+ },
280
+
281
+ /***
282
+ * @method Object.fromQueryString(<str>, [deep] = true)
283
+ * @returns Object
284
+ * @short Converts the query string of a URL into an object.
285
+ * @extra If [deep] is %false%, conversion will only accept shallow params (ie. no object or arrays with %[]% syntax) as these are not universally supported.
286
+ * @example
287
+ *
288
+ * Object.fromQueryString('foo=bar&broken=wear') -> { foo: 'bar', broken: 'wear' }
289
+ * Object.fromQueryString('foo[]=1&foo[]=2') -> { foo: [1,2] }
290
+ *
291
+ ***/
292
+ 'fromQueryString': function(str, deep) {
293
+ var result = object.extended(), split;
294
+ str = str && str.toString ? str.toString() : '';
295
+ decodeURIComponent(str.replace(/^.*?\?/, '')).split('&').forEach(function(p) {
296
+ var split = p.split('=');
297
+ if(split.length !== 2) return;
298
+ setParamsObject(result, split[0], split[1], deep);
299
+ });
300
+ return result;
301
+ },
302
+
303
+ /***
304
+ * @method tap(<obj>, <fn>)
305
+ * @returns Object
306
+ * @short Runs <fn> and returns <obj>.
307
+ * @extra A string can also be used as a shortcut to a method. This method is used to run an intermediary function in the middle of method chaining. As a standalone method on the Object class it doesn't have too much use. The power of %tap% comes when using extended objects or modifying the Object prototype with Object.extend().
308
+ * @example
309
+ *
310
+ * Object.extend();
311
+ * [2,4,6].map(Math.exp).tap(function(){ arr.pop(); }).map(Math.round); -> [7,55]
312
+ * [2,4,6].map(Math.exp).tap('pop').map(Math.round); -> [7,55]
313
+ *
314
+ ***/
315
+ 'tap': function(obj, arg) {
316
+ var fn = arg;
317
+ if(!isFunction(arg)) {
318
+ fn = function() {
319
+ if(arg) obj[arg]();
320
+ }
321
+ }
322
+ fn.call(obj, obj);
323
+ return obj;
324
+ },
325
+
326
+ /***
327
+ * @method has(<obj>, <key>)
328
+ * @returns Boolean
329
+ * @short Checks if <obj> has <key> using hasOwnProperty from Object.prototype.
330
+ * @extra This method is considered safer than %Object#hasOwnProperty% when using objects as hashes. See http://www.devthought.com/2012/01/18/an-object-is-not-a-hash/ for more.
331
+ * @example
332
+ *
333
+ * Object.has({ foo: 'bar' }, 'foo') -> true
334
+ * Object.has({ foo: 'bar' }, 'baz') -> false
335
+ * Object.has({ hasOwnProperty: true }, 'foo') -> false
336
+ *
337
+ ***/
338
+ 'has': function (obj, key) {
339
+ return hasOwnProperty(obj, key);
340
+ }
341
+
342
+ });
343
+
344
+
345
+ buildTypeMethods();
346
+ buildObjectExtend();
347
+ buildObjectInstanceMethods(ObjectHashMethods, Hash);
348
+
@@ -0,0 +1,92 @@
1
+
2
+ /***
3
+ * @package RegExp
4
+ * @dependency core
5
+ * @description Escaping regexes and manipulating their flags.
6
+ *
7
+ * Note here that methods on the RegExp class like .exec and .test will fail in the current version of SpiderMonkey being
8
+ * used by CouchDB when using shorthand regex notation like /foo/. This is the reason for the intermixed use of shorthand
9
+ * and compiled regexes here. If you're using JS in CouchDB, it is safer to ALWAYS compile your regexes from a string.
10
+ *
11
+ ***/
12
+
13
+ function uniqueRegExpFlags(flags) {
14
+ return flags.split('').sort().join('').replace(/([gimy])\1+/g, '$1');
15
+ }
16
+
17
+ extend(regexp, false, false, {
18
+
19
+ /***
20
+ * @method RegExp.escape(<str> = '')
21
+ * @returns String
22
+ * @short Escapes all RegExp tokens in a string.
23
+ * @example
24
+ *
25
+ * RegExp.escape('really?') -> 'really\?'
26
+ * RegExp.escape('yes.') -> 'yes\.'
27
+ * RegExp.escape('(not really)') -> '\(not really\)'
28
+ *
29
+ ***/
30
+ 'escape': function(str) {
31
+ return escapeRegExp(str);
32
+ }
33
+
34
+ });
35
+
36
+ extend(regexp, true, false, {
37
+
38
+ /***
39
+ * @method getFlags()
40
+ * @returns String
41
+ * @short Returns the flags of the regex as a string.
42
+ * @example
43
+ *
44
+ * /texty/gim.getFlags('testy') -> 'gim'
45
+ *
46
+ ***/
47
+ 'getFlags': function() {
48
+ return getRegExpFlags(this);
49
+ },
50
+
51
+ /***
52
+ * @method setFlags(<flags>)
53
+ * @returns RegExp
54
+ * @short Sets the flags on a regex and retuns a copy.
55
+ * @example
56
+ *
57
+ * /texty/.setFlags('gim') -> now has global, ignoreCase, and multiline set
58
+ *
59
+ ***/
60
+ 'setFlags': function(flags) {
61
+ return regexp(this.source, flags);
62
+ },
63
+
64
+ /***
65
+ * @method addFlag(<flag>)
66
+ * @returns RegExp
67
+ * @short Adds <flag> to the regex.
68
+ * @example
69
+ *
70
+ * /texty/.addFlag('g') -> now has global flag set
71
+ *
72
+ ***/
73
+ 'addFlag': function(flag) {
74
+ return this.setFlags(getRegExpFlags(this, flag));
75
+ },
76
+
77
+ /***
78
+ * @method removeFlag(<flag>)
79
+ * @returns RegExp
80
+ * @short Removes <flag> from the regex.
81
+ * @example
82
+ *
83
+ * /texty/g.removeFlag('g') -> now has global flag removed
84
+ *
85
+ ***/
86
+ 'removeFlag': function(flag) {
87
+ return this.setFlags(getRegExpFlags(this).replace(flag, ''));
88
+ }
89
+
90
+ });
91
+
92
+
@@ -0,0 +1,871 @@
1
+
2
+ /***
3
+ * @package String
4
+ * @dependency core
5
+ * @description String manupulation, escaping, encoding, truncation, and:conversion.
6
+ *
7
+ ***/
8
+
9
+ function getAcronym(word) {
10
+ var inflector = string.Inflector;
11
+ var word = inflector && inflector.acronyms[word];
12
+ if(isString(word)) {
13
+ return word;
14
+ }
15
+ }
16
+
17
+ function padString(str, p, left, right) {
18
+ var padding = string(p);
19
+ if(padding != p) {
20
+ padding = '';
21
+ }
22
+ if(!isNumber(left)) left = 1;
23
+ if(!isNumber(right)) right = 1;
24
+ return padding.repeat(left) + str + padding.repeat(right);
25
+ }
26
+
27
+ function chr(num) {
28
+ return string.fromCharCode(num);
29
+ }
30
+
31
+ var btoa, atob;
32
+
33
+ function buildBase64(key) {
34
+ if(this.btoa) {
35
+ btoa = this.btoa;
36
+ atob = this.atob;
37
+ return;
38
+ }
39
+ var base64reg = /[^A-Za-z0-9\+\/\=]/g;
40
+ btoa = function(str) {
41
+ var output = '';
42
+ var chr1, chr2, chr3;
43
+ var enc1, enc2, enc3, enc4;
44
+ var i = 0;
45
+ do {
46
+ chr1 = str.charCodeAt(i++);
47
+ chr2 = str.charCodeAt(i++);
48
+ chr3 = str.charCodeAt(i++);
49
+ enc1 = chr1 >> 2;
50
+ enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
51
+ enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
52
+ enc4 = chr3 & 63;
53
+ if (isNaN(chr2)) {
54
+ enc3 = enc4 = 64;
55
+ } else if (isNaN(chr3)) {
56
+ enc4 = 64;
57
+ }
58
+ output = output + key.charAt(enc1) + key.charAt(enc2) + key.charAt(enc3) + key.charAt(enc4);
59
+ chr1 = chr2 = chr3 = '';
60
+ enc1 = enc2 = enc3 = enc4 = '';
61
+ } while (i < str.length);
62
+ return output;
63
+ }
64
+ atob = function(input) {
65
+ var output = '';
66
+ var chr1, chr2, chr3;
67
+ var enc1, enc2, enc3, enc4;
68
+ var i = 0;
69
+ if(input.match(base64reg)) {
70
+ throw new Error('String contains invalid base64 characters');
71
+ }
72
+ input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
73
+ do {
74
+ enc1 = key.indexOf(input.charAt(i++));
75
+ enc2 = key.indexOf(input.charAt(i++));
76
+ enc3 = key.indexOf(input.charAt(i++));
77
+ enc4 = key.indexOf(input.charAt(i++));
78
+ chr1 = (enc1 << 2) | (enc2 >> 4);
79
+ chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
80
+ chr3 = ((enc3 & 3) << 6) | enc4;
81
+ output = output + chr(chr1);
82
+ if (enc3 != 64) {
83
+ output = output + chr(chr2);
84
+ }
85
+ if (enc4 != 64) {
86
+ output = output + chr(chr3);
87
+ }
88
+ chr1 = chr2 = chr3 = '';
89
+ enc1 = enc2 = enc3 = enc4 = '';
90
+ } while (i < input.length);
91
+ return output;
92
+ }
93
+ }
94
+
95
+
96
+
97
+ extend(string, true, false, {
98
+
99
+ /***
100
+ * @method escapeRegExp()
101
+ * @returns String
102
+ * @short Escapes all RegExp tokens in the string.
103
+ * @example
104
+ *
105
+ * 'really?'.escapeRegExp() -> 'really\?'
106
+ * 'yes.'.escapeRegExp() -> 'yes\.'
107
+ * '(not really)'.escapeRegExp() -> '\(not really\)'
108
+ *
109
+ ***/
110
+ 'escapeRegExp': function() {
111
+ return escapeRegExp(this);
112
+ },
113
+
114
+ /***
115
+ * @method escapeURL([param] = false)
116
+ * @returns String
117
+ * @short Escapes characters in a string to make a valid URL.
118
+ * @extra If [param] is true, it will also escape valid URL characters for use as a URL parameter.
119
+ * @example
120
+ *
121
+ * 'http://foo.com/"bar"'.escapeURL() -> 'http://foo.com/%22bar%22'
122
+ * 'http://foo.com/"bar"'.escapeURL(true) -> 'http%3A%2F%2Ffoo.com%2F%22bar%22'
123
+ *
124
+ ***/
125
+ 'escapeURL': function(param) {
126
+ return param ? encodeURIComponent(this) : encodeURI(this);
127
+ },
128
+
129
+ /***
130
+ * @method unescapeURL([partial] = false)
131
+ * @returns String
132
+ * @short Restores escaped characters in a URL escaped string.
133
+ * @extra If [partial] is true, it will only unescape non-valid URL characters. [partial] is included here for completeness, but should very rarely be needed.
134
+ * @example
135
+ *
136
+ * 'http%3A%2F%2Ffoo.com%2Fthe%20bar'.unescapeURL() -> 'http://foo.com/the bar'
137
+ * 'http%3A%2F%2Ffoo.com%2Fthe%20bar'.unescapeURL(true) -> 'http%3A%2F%2Ffoo.com%2Fthe bar'
138
+ *
139
+ ***/
140
+ 'unescapeURL': function(param) {
141
+ return param ? decodeURI(this) : decodeURIComponent(this);
142
+ },
143
+
144
+ /***
145
+ * @method escapeHTML()
146
+ * @returns String
147
+ * @short Converts HTML characters to their entity equivalents.
148
+ * @example
149
+ *
150
+ * '<p>some text</p>'.escapeHTML() -> '&lt;p&gt;some text&lt;/p&gt;'
151
+ * 'one & two'.escapeHTML() -> 'one &amp; two'
152
+ *
153
+ ***/
154
+ 'escapeHTML': function() {
155
+ return this.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
156
+ },
157
+
158
+ /***
159
+ * @method unescapeHTML([partial] = false)
160
+ * @returns String
161
+ * @short Restores escaped HTML characters.
162
+ * @example
163
+ *
164
+ * '&lt;p&gt;some text&lt;/p&gt;'.unescapeHTML() -> '<p>some text</p>'
165
+ * 'one &amp; two'.unescapeHTML() -> 'one & two'
166
+ *
167
+ ***/
168
+ 'unescapeHTML': function() {
169
+ return this.replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&');
170
+ },
171
+
172
+ /***
173
+ * @method encodeBase64()
174
+ * @returns String
175
+ * @short Encodes the string into base64 encoding.
176
+ * @extra This method wraps the browser native %btoa% when available, and uses a custom implementation when not available.
177
+ * @example
178
+ *
179
+ * 'gonna get encoded!'.encodeBase64() -> 'Z29ubmEgZ2V0IGVuY29kZWQh'
180
+ * 'http://twitter.com/'.encodeBase64() -> 'aHR0cDovL3R3aXR0ZXIuY29tLw=='
181
+ *
182
+ ***/
183
+ 'encodeBase64': function() {
184
+ return btoa(this);
185
+ },
186
+
187
+ /***
188
+ * @method decodeBase64()
189
+ * @returns String
190
+ * @short Decodes the string from base64 encoding.
191
+ * @extra This method wraps the browser native %atob% when available, and uses a custom implementation when not available.
192
+ * @example
193
+ *
194
+ * 'aHR0cDovL3R3aXR0ZXIuY29tLw=='.decodeBase64() -> 'http://twitter.com/'
195
+ * 'anVzdCBnb3QgZGVjb2RlZA=='.decodeBase64() -> 'just got decoded!'
196
+ *
197
+ ***/
198
+ 'decodeBase64': function() {
199
+ return atob(this);
200
+ },
201
+
202
+ /***
203
+ * @method each([search] = single character, [fn])
204
+ * @returns Array
205
+ * @short Runs callback [fn] against each occurence of [search].
206
+ * @extra Returns an array of matches. [search] may be either a string or regex, and defaults to every character in the string.
207
+ * @example
208
+ *
209
+ * 'jumpy'.each() -> ['j','u','m','p','y']
210
+ * 'jumpy'.each(/[r-z]/) -> ['u','y']
211
+ * 'jumpy'.each(/[r-z]/, function(m) {
212
+ * // Called twice: "u", "y"
213
+ * });
214
+ *
215
+ ***/
216
+ 'each': function(search, fn) {
217
+ var match, i;
218
+ if(isFunction(search)) {
219
+ fn = search;
220
+ search = /[\s\S]/g;
221
+ } else if(!search) {
222
+ search = /[\s\S]/g
223
+ } else if(isString(search)) {
224
+ search = regexp(escapeRegExp(search), 'gi');
225
+ } else if(isRegExp(search)) {
226
+ search = regexp(search.source, getRegExpFlags(search, 'g'));
227
+ }
228
+ match = this.match(search) || [];
229
+ if(fn) {
230
+ for(i = 0; i < match.length; i++) {
231
+ match[i] = fn.call(this, match[i], i, match) || match[i];
232
+ }
233
+ }
234
+ return match;
235
+ },
236
+
237
+ /***
238
+ * @method shift(<n>)
239
+ * @returns Array
240
+ * @short Shifts each character in the string <n> places in the character map.
241
+ * @example
242
+ *
243
+ * 'a'.shift(1) -> 'b'
244
+ * 'ク'.shift(1) -> 'グ'
245
+ *
246
+ ***/
247
+ 'shift': function(n) {
248
+ var result = '';
249
+ n = n || 0;
250
+ this.codes(function(c) {
251
+ result += chr(c + n);
252
+ });
253
+ return result;
254
+ },
255
+
256
+ /***
257
+ * @method codes([fn])
258
+ * @returns Array
259
+ * @short Runs callback [fn] against each character code in the string. Returns an array of character codes.
260
+ * @example
261
+ *
262
+ * 'jumpy'.codes() -> [106,117,109,112,121]
263
+ * 'jumpy'.codes(function(c) {
264
+ * // Called 5 times: 106, 117, 109, 112, 121
265
+ * });
266
+ *
267
+ ***/
268
+ 'codes': function(fn) {
269
+ var codes = [];
270
+ for(var i=0; i<this.length; i++) {
271
+ var code = this.charCodeAt(i);
272
+ codes.push(code);
273
+ if(fn) fn.call(this, code, i);
274
+ }
275
+ return codes;
276
+ },
277
+
278
+ /***
279
+ * @method chars([fn])
280
+ * @returns Array
281
+ * @short Runs callback [fn] against each character in the string. Returns an array of characters.
282
+ * @example
283
+ *
284
+ * 'jumpy'.chars() -> ['j','u','m','p','y']
285
+ * 'jumpy'.chars(function(c) {
286
+ * // Called 5 times: "j","u","m","p","y"
287
+ * });
288
+ *
289
+ ***/
290
+ 'chars': function(fn) {
291
+ return this.each(fn);
292
+ },
293
+
294
+ /***
295
+ * @method words([fn])
296
+ * @returns Array
297
+ * @short Runs callback [fn] against each word in the string. Returns an array of words.
298
+ * @extra A "word" here is defined as any sequence of non-whitespace characters.
299
+ * @example
300
+ *
301
+ * 'broken wear'.words() -> ['broken','wear']
302
+ * 'broken wear'.words(function(w) {
303
+ * // Called twice: "broken", "wear"
304
+ * });
305
+ *
306
+ ***/
307
+ 'words': function(fn) {
308
+ return this.trim().each(/\S+/g, fn);
309
+ },
310
+
311
+ /***
312
+ * @method lines([fn])
313
+ * @returns Array
314
+ * @short Runs callback [fn] against each line in the string. Returns an array of lines.
315
+ * @example
316
+ *
317
+ * 'broken wear\nand\njumpy jump'.lines() -> ['broken wear','and','jumpy jump']
318
+ * 'broken wear\nand\njumpy jump'.lines(function(l) {
319
+ * // Called three times: "broken wear", "and", "jumpy jump"
320
+ * });
321
+ *
322
+ ***/
323
+ 'lines': function(fn) {
324
+ return this.trim().each(/^.*$/gm, fn);
325
+ },
326
+
327
+ /***
328
+ * @method paragraphs([fn])
329
+ * @returns Array
330
+ * @short Runs callback [fn] against each paragraph in the string. Returns an array of paragraphs.
331
+ * @extra A paragraph here is defined as a block of text bounded by two or more line breaks.
332
+ * @example
333
+ *
334
+ * 'Once upon a time.\n\nIn the land of oz...'.paragraphs() -> ['Once upon a time.','In the land of oz...']
335
+ * 'Once upon a time.\n\nIn the land of oz...'.paragraphs(function(p) {
336
+ * // Called twice: "Once upon a time.", "In teh land of oz..."
337
+ * });
338
+ *
339
+ ***/
340
+ 'paragraphs': function(fn) {
341
+ var paragraphs = this.trim().split(/[\r\n]{2,}/);
342
+ paragraphs = paragraphs.map(function(p) {
343
+ if(fn) var s = fn.call(p);
344
+ return s ? s : p;
345
+ });
346
+ return paragraphs;
347
+ },
348
+
349
+ /***
350
+ * @method startsWith(<find>, [case] = true)
351
+ * @returns Boolean
352
+ * @short Returns true if the string starts with <find>.
353
+ * @extra <find> may be either a string or regex. Case sensitive if [case] is true.
354
+ * @example
355
+ *
356
+ * 'hello'.startsWith('hell') -> true
357
+ * 'hello'.startsWith(/[a-h]/) -> true
358
+ * 'hello'.startsWith('HELL') -> false
359
+ * 'hello'.startsWith('HELL', false) -> true
360
+ *
361
+ ***/
362
+ 'startsWith': function(reg, c) {
363
+ if(isUndefined(c)) c = true;
364
+ var source = isRegExp(reg) ? reg.source.replace('^', '') : escapeRegExp(reg);
365
+ return regexp('^' + source, c ? '' : 'i').test(this);
366
+ },
367
+
368
+ /***
369
+ * @method endsWith(<find>, [case] = true)
370
+ * @returns Boolean
371
+ * @short Returns true if the string ends with <find>.
372
+ * @extra <find> may be either a string or regex. Case sensitive if [case] is true.
373
+ * @example
374
+ *
375
+ * 'jumpy'.endsWith('py') -> true
376
+ * 'jumpy'.endsWith(/[q-z]/) -> true
377
+ * 'jumpy'.endsWith('MPY') -> false
378
+ * 'jumpy'.endsWith('MPY', false) -> true
379
+ *
380
+ ***/
381
+ 'endsWith': function(reg, c) {
382
+ if(isUndefined(c)) c = true;
383
+ var source = isRegExp(reg) ? reg.source.replace('$', '') : escapeRegExp(reg);
384
+ return regexp(source + '$', c ? '' : 'i').test(this);
385
+ },
386
+
387
+ /***
388
+ * @method isBlank()
389
+ * @returns Boolean
390
+ * @short Returns true if the string has a length of 0 or contains only whitespace.
391
+ * @example
392
+ *
393
+ * ''.isBlank() -> true
394
+ * ' '.isBlank() -> true
395
+ * 'noway'.isBlank() -> false
396
+ *
397
+ ***/
398
+ 'isBlank': function() {
399
+ return this.trim().length === 0;
400
+ },
401
+
402
+ /***
403
+ * @method has(<find>)
404
+ * @returns Boolean
405
+ * @short Returns true if the string matches <find>.
406
+ * @extra <find> may be a string or regex.
407
+ * @example
408
+ *
409
+ * 'jumpy'.has('py') -> true
410
+ * 'broken'.has(/[a-n]/) -> true
411
+ * 'broken'.has(/[s-z]/) -> false
412
+ *
413
+ ***/
414
+ 'has': function(find) {
415
+ return this.search(isRegExp(find) ? find : escapeRegExp(find)) !== -1;
416
+ },
417
+
418
+
419
+ /***
420
+ * @method add(<str>, [index] = length)
421
+ * @returns String
422
+ * @short Adds <str> at [index]. Negative values are also allowed.
423
+ * @extra %insert% is provided as an alias, and is generally more readable when using an index.
424
+ * @example
425
+ *
426
+ * 'schfifty'.add(' five') -> schfifty five
427
+ * 'dopamine'.insert('e', 3) -> dopeamine
428
+ * 'spelling eror'.insert('r', -3) -> spelling error
429
+ *
430
+ ***/
431
+ 'add': function(str, index) {
432
+ index = isUndefined(index) ? this.length : index;
433
+ return this.slice(0, index) + str + this.slice(index);
434
+ },
435
+
436
+ /***
437
+ * @method remove(<f>)
438
+ * @returns String
439
+ * @short Removes any part of the string that matches <f>.
440
+ * @extra <f> can be a string or a regex.
441
+ * @example
442
+ *
443
+ * 'schfifty five'.remove('f') -> 'schity ive'
444
+ * 'schfifty five'.remove(/[a-f]/g) -> 'shity iv'
445
+ *
446
+ ***/
447
+ 'remove': function(f) {
448
+ return this.replace(f, '');
449
+ },
450
+
451
+ /***
452
+ * @method reverse()
453
+ * @returns String
454
+ * @short Reverses the string.
455
+ * @example
456
+ *
457
+ * 'jumpy'.reverse() -> 'ypmuj'
458
+ * 'lucky charms'.reverse() -> 'smrahc ykcul'
459
+ *
460
+ ***/
461
+ 'reverse': function() {
462
+ return this.split('').reverse().join('');
463
+ },
464
+
465
+ /***
466
+ * @method compact()
467
+ * @returns String
468
+ * @short Compacts all white space in the string to a single space and trims the ends.
469
+ * @example
470
+ *
471
+ * 'too \n much \n space'.compact() -> 'too much space'
472
+ * 'enough \n '.compact() -> 'enought'
473
+ *
474
+ ***/
475
+ 'compact': function() {
476
+ return this.trim().replace(/([\r\n\s ])+/g, function(match, whitespace){
477
+ return whitespace === ' ' ? whitespace : ' ';
478
+ });
479
+ },
480
+
481
+ /***
482
+ * @method at(<index>, [loop] = true)
483
+ * @returns String or Array
484
+ * @short Gets the character(s) at a given index.
485
+ * @extra When [loop] is true, overshooting the end of the string (or the beginning) will begin counting from the other end. As an alternate syntax, passing multiple indexes will get the characters at those indexes.
486
+ * @example
487
+ *
488
+ * 'jumpy'.at(0) -> 'j'
489
+ * 'jumpy'.at(2) -> 'm'
490
+ * 'jumpy'.at(5) -> 'j'
491
+ * 'jumpy'.at(5, false) -> ''
492
+ * 'jumpy'.at(-1) -> 'y'
493
+ * 'lucky charms'.at(2,4,6,8) -> ['u','k','y',c']
494
+ *
495
+ ***/
496
+ 'at': function() {
497
+ return entryAtIndex(this, arguments, true);
498
+ },
499
+
500
+ /***
501
+ * @method from([index] = 0)
502
+ * @returns String
503
+ * @short Returns a section of the string starting from [index].
504
+ * @example
505
+ *
506
+ * 'lucky charms'.from() -> 'lucky charms'
507
+ * 'lucky charms'.from(7) -> 'harms'
508
+ *
509
+ ***/
510
+ 'from': function(num) {
511
+ return this.slice(num);
512
+ },
513
+
514
+ /***
515
+ * @method to([index] = end)
516
+ * @returns String
517
+ * @short Returns a section of the string ending at [index].
518
+ * @example
519
+ *
520
+ * 'lucky charms'.to() -> 'lucky charms'
521
+ * 'lucky charms'.to(7) -> 'lucky ch'
522
+ *
523
+ ***/
524
+ 'to': function(num) {
525
+ if(isUndefined(num)) num = this.length;
526
+ return this.slice(0, num);
527
+ },
528
+
529
+ /***
530
+ * @method dasherize()
531
+ * @returns String
532
+ * @short Converts underscores and camel casing to hypens.
533
+ * @example
534
+ *
535
+ * 'a_farewell_to_arms'.dasherize() -> 'a-farewell-to-arms'
536
+ * 'capsLock'.dasherize() -> 'caps-lock'
537
+ *
538
+ ***/
539
+ 'dasherize': function() {
540
+ return this.underscore().replace(/_/g, '-');
541
+ },
542
+
543
+ /***
544
+ * @method underscore()
545
+ * @returns String
546
+ * @short Converts hyphens and camel casing to underscores.
547
+ * @example
548
+ *
549
+ * 'a-farewell-to-arms'.underscore() -> 'a_farewell_to_arms'
550
+ * 'capsLock'.underscore() -> 'caps_lock'
551
+ *
552
+ ***/
553
+ 'underscore': function() {
554
+ return this
555
+ .replace(/[-\s]+/g, '_')
556
+ .replace(string.Inflector && string.Inflector.acronymRegExp, function(acronym, index) {
557
+ return (index > 0 ? '_' : '') + acronym.toLowerCase();
558
+ })
559
+ .replace(/([A-Z\d]+)([A-Z][a-z])/g,'$1_$2')
560
+ .replace(/([a-z\d])([A-Z])/g,'$1_$2')
561
+ .toLowerCase();
562
+ },
563
+
564
+ /***
565
+ * @method camelize([first] = true)
566
+ * @returns String
567
+ * @short Converts underscores and hyphens to camel case. If [first] is true the first letter will also be capitalized.
568
+ * @extra If the Inflections package is included acryonyms can also be defined that will be used when camelizing.
569
+ * @example
570
+ *
571
+ * 'caps_lock'.camelize() -> 'CapsLock'
572
+ * 'moz-border-radius'.camelize() -> 'MozBorderRadius'
573
+ * 'moz-border-radius'.camelize(false) -> 'mozBorderRadius'
574
+ *
575
+ ***/
576
+ 'camelize': function(first) {
577
+ return this.underscore().replace(/(^|_)([^_]+)/g, function(match, pre, word, index) {
578
+ var acronym = getAcronym(word), capitalize = first !== false || index > 0;
579
+ if(acronym) return capitalize ? acronym : acronym.toLowerCase();
580
+ return capitalize ? word.capitalize() : word;
581
+ });
582
+ },
583
+
584
+ /***
585
+ * @method spacify()
586
+ * @returns String
587
+ * @short Converts camel case, underscores, and hyphens to a properly spaced string.
588
+ * @example
589
+ *
590
+ * 'camelCase'.spacify() -> 'camel case'
591
+ * 'an-ugly-string'.spacify() -> 'an ugly string'
592
+ * 'oh-no_youDid-not'.spacify().capitalize(true) -> 'something else'
593
+ *
594
+ ***/
595
+ 'spacify': function() {
596
+ return this.underscore().replace(/_/g, ' ');
597
+ },
598
+
599
+ /***
600
+ * @method stripTags([tag1], [tag2], ...)
601
+ * @returns String
602
+ * @short Strips all HTML tags from the string.
603
+ * @extra Tags to strip may be enumerated in the parameters, otherwise will strip all.
604
+ * @example
605
+ *
606
+ * '<p>just <b>some</b> text</p>'.stripTags() -> 'just some text'
607
+ * '<p>just <b>some</b> text</p>'.stripTags('p') -> 'just <b>some</b> text'
608
+ *
609
+ ***/
610
+ 'stripTags': function() {
611
+ var str = this, args = arguments.length > 0 ? arguments : [''];
612
+ multiArgs(args, function(tag) {
613
+ str = str.replace(regexp('<\/?' + escapeRegExp(tag) + '[^<>]*>', 'gi'), '');
614
+ });
615
+ return str;
616
+ },
617
+
618
+ /***
619
+ * @method removeTags([tag1], [tag2], ...)
620
+ * @returns String
621
+ * @short Removes all HTML tags and their contents from the string.
622
+ * @extra Tags to remove may be enumerated in the parameters, otherwise will remove all.
623
+ * @example
624
+ *
625
+ * '<p>just <b>some</b> text</p>'.removeTags() -> ''
626
+ * '<p>just <b>some</b> text</p>'.removeTags('b') -> '<p>just text</p>'
627
+ *
628
+ ***/
629
+ 'removeTags': function() {
630
+ var str = this, args = arguments.length > 0 ? arguments : ['\\S+'];
631
+ multiArgs(args, function(t) {
632
+ var reg = regexp('<(' + t + ')[^<>]*(?:\\/>|>.*?<\\/\\1>)', 'gi');
633
+ str = str.replace(reg, '');
634
+ });
635
+ return str;
636
+ },
637
+
638
+ /***
639
+ * @method truncate(<length>, [split] = true, [from] = 'right', [ellipsis] = '...')
640
+ * @returns Object
641
+ * @short Truncates a string.
642
+ * @extra If [split] is %false%, will not split words up, and instead discard the word where the truncation occurred. [from] can also be %"middle"% or %"left"%.
643
+ * @example
644
+ *
645
+ * 'just sittin on the dock of the bay'.truncate(20) -> 'just sittin on the do...'
646
+ * 'just sittin on the dock of the bay'.truncate(20, false) -> 'just sittin on the...'
647
+ * 'just sittin on the dock of the bay'.truncate(20, true, 'middle') -> 'just sitt...of the bay'
648
+ * 'just sittin on the dock of the bay'.truncate(20, true, 'left') -> '...the dock of the bay'
649
+ *
650
+ ***/
651
+ 'truncate': function(length, split, from, ellipsis) {
652
+ var pos,
653
+ prepend = '',
654
+ append = '',
655
+ str = this.toString(),
656
+ chars = '[' + getTrimmableCharacters() + ']+',
657
+ space = '[^' + getTrimmableCharacters() + ']*',
658
+ reg = regexp(chars + space + '$');
659
+ ellipsis = isUndefined(ellipsis) ? '...' : string(ellipsis);
660
+ if(str.length <= length) {
661
+ return str;
662
+ }
663
+ switch(from) {
664
+ case 'left':
665
+ pos = str.length - length;
666
+ prepend = ellipsis;
667
+ str = str.slice(pos);
668
+ reg = regexp('^' + space + chars);
669
+ break;
670
+ case 'middle':
671
+ pos = floor(length / 2);
672
+ append = ellipsis + str.slice(str.length - pos).trimLeft();
673
+ str = str.slice(0, pos);
674
+ break;
675
+ default:
676
+ pos = length;
677
+ append = ellipsis;
678
+ str = str.slice(0, pos);
679
+ }
680
+ if(split === false && this.slice(pos, pos + 1).match(/\S/)) {
681
+ str = str.remove(reg);
682
+ }
683
+ return prepend + str + append;
684
+ },
685
+
686
+ /***
687
+ * @method pad[Side](<padding> = '', [num] = 1)
688
+ * @returns String
689
+ * @short Pads either/both sides of the string.
690
+ * @extra [num] is the number of characters on each side, and [padding] is the character to pad with.
691
+ *
692
+ * @set
693
+ * pad
694
+ * padLeft
695
+ * padRight
696
+ *
697
+ * @example
698
+ *
699
+ * 'wasabi'.pad('-') -> '-wasabi-'
700
+ * 'wasabi'.pad('-', 2) -> '--wasabi--'
701
+ * 'wasabi'.padLeft('-', 2) -> '--wasabi'
702
+ * 'wasabi'.padRight('-', 2) -> 'wasabi--'
703
+ *
704
+ ***/
705
+ 'pad': function(padding, num) {
706
+ return repeatString(num, padding) + this + repeatString(num, padding);
707
+ },
708
+
709
+ 'padLeft': function(padding, num) {
710
+ return repeatString(num, padding) + this;
711
+ },
712
+
713
+ 'padRight': function(padding, num) {
714
+ return this + repeatString(num, padding);
715
+ },
716
+
717
+ /***
718
+ * @method first([n] = 1)
719
+ * @returns String
720
+ * @short Returns the first [n] characters of the string.
721
+ * @example
722
+ *
723
+ * 'lucky charms'.first() -> 'l'
724
+ * 'lucky charms'.first(3) -> 'luc'
725
+ *
726
+ ***/
727
+ 'first': function(num) {
728
+ if(isUndefined(num)) num = 1;
729
+ return this.substr(0, num);
730
+ },
731
+
732
+ /***
733
+ * @method last([n] = 1)
734
+ * @returns String
735
+ * @short Returns the last [n] characters of the string.
736
+ * @example
737
+ *
738
+ * 'lucky charms'.last() -> 's'
739
+ * 'lucky charms'.last(3) -> 'rms'
740
+ *
741
+ ***/
742
+ 'last': function(num) {
743
+ if(isUndefined(num)) num = 1;
744
+ var start = this.length - num < 0 ? 0 : this.length - num;
745
+ return this.substr(start);
746
+ },
747
+
748
+ /***
749
+ * @method repeat([num] = 0)
750
+ * @returns String
751
+ * @short Returns the string repeated [num] times.
752
+ * @example
753
+ *
754
+ * 'jumpy'.repeat(2) -> 'jumpyjumpy'
755
+ * 'a'.repeat(5) -> 'aaaaa'
756
+ *
757
+ ***/
758
+ 'repeat': function(num) {
759
+ var str = '', i = 0;
760
+ if(isNumber(num) && num > 0) {
761
+ while(i < num) {
762
+ str += this;
763
+ i++;
764
+ }
765
+ }
766
+ return str;
767
+ },
768
+
769
+ /***
770
+ * @method toNumber([base] = 10)
771
+ * @returns Number
772
+ * @short Converts the string into a number.
773
+ * @extra Any value with a "." fill be converted to a floating point value, otherwise an integer.
774
+ * @example
775
+ *
776
+ * '153'.toNumber() -> 153
777
+ * '12,000'.toNumber() -> 12000
778
+ * '10px'.toNumber() -> 10
779
+ * 'ff'.toNumber(16) -> 255
780
+ *
781
+ ***/
782
+ 'toNumber': function(base) {
783
+ var str = this.replace(/,/g, '');
784
+ return str.match(/\./) ? parseFloat(str) : parseInt(str, base || 10);
785
+ },
786
+
787
+ /***
788
+ * @method capitalize([all] = false)
789
+ * @returns String
790
+ * @short Capitalizes the first character in the string.
791
+ * @extra If [all] is true, all words in the string will be capitalized.
792
+ * @example
793
+ *
794
+ * 'hello'.capitalize() -> 'Hello'
795
+ * 'hello kitty'.capitalize() -> 'Hello kitty'
796
+ * 'hello kitty'.capitalize(true) -> 'Hello Kitty'
797
+ *
798
+ *
799
+ ***/
800
+ 'capitalize': function(all) {
801
+ var lastResponded;
802
+ return this.toLowerCase().replace(all ? /[\s\S]/g : /^\S/, function(lower) {
803
+ var upper = lower.toUpperCase(), result;
804
+ result = lastResponded ? lower : upper;
805
+ lastResponded = upper !== lower;
806
+ return result;
807
+ });
808
+ },
809
+
810
+ /***
811
+ * @method assign(<obj1>, <obj2>, ...)
812
+ * @returns String
813
+ * @short Assigns variables to tokens in a string.
814
+ * @extra If an object is passed, it's properties can be assigned using the object's keys. If a non-object (string, number, etc.) is passed it can be accessed by the argument number beginning with 1 (as with regex tokens). Multiple objects can be passed and will be merged together (original objects are unaffected).
815
+ * @example
816
+ *
817
+ * 'Welcome, Mr. {name}.'.assign({ name: 'Franklin' }) -> 'Welcome, Mr. Franklin.'
818
+ * 'You are {1} years old today.'.assign(14) -> 'You are 14 years old today.'
819
+ * '{n} and {r}'.assign({ n: 'Cheech' }, { r: 'Chong' }) -> 'Cheech and Chong'
820
+ *
821
+ ***/
822
+ 'assign': function() {
823
+ var assign = {};
824
+ multiArgs(arguments, function(a, i) {
825
+ if(isObject(a)) {
826
+ simpleMerge(assign, a);
827
+ } else {
828
+ assign[i + 1] = a;
829
+ }
830
+ });
831
+ return this.replace(/\{([^{]+?)\}/g, function(m, key) {
832
+ return hasOwnProperty(assign, key) ? assign[key] : m;
833
+ });
834
+ },
835
+
836
+ /***
837
+ * @method namespace([init] = global)
838
+ * @returns Mixed
839
+ * @short Finds the namespace or property indicated by the string.
840
+ * @extra [init] can be passed to provide a starting context, otherwise the global context will be used. If any level returns a falsy value, that will be the final result.
841
+ * @example
842
+ *
843
+ * 'Path.To.Namespace'.namespace() -> Path.To.Namespace
844
+ * '$.fn'.namespace() -> $.fn
845
+ *
846
+ ***/
847
+ 'namespace': function(context) {
848
+ context = context || globalContext;
849
+ iterateOverObject(this.split('.'), function(i,s) {
850
+ return !!(context = context[s]);
851
+ });
852
+ return context;
853
+ }
854
+
855
+ });
856
+
857
+
858
+ // Aliases
859
+
860
+ extend(string, true, false, {
861
+
862
+ /***
863
+ * @method insert()
864
+ * @alias add
865
+ *
866
+ ***/
867
+ 'insert': string.prototype.add
868
+ });
869
+
870
+ buildBase64('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=');
871
+