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.
- data/README.md +110 -28
- data/lib/generators/sugar/build/build_generator.rb +107 -0
- data/lib/generators/sugar/install/install_generator.rb +2 -2
- data/lib/sugar/rails/version.rb +2 -2
- data/sugar-rails.gemspec +1 -1
- data/vendor/assets/javascripts/precompiled/development/array.js +1212 -0
- data/vendor/assets/javascripts/precompiled/development/core.js +329 -0
- data/vendor/assets/javascripts/precompiled/development/date.js +2179 -0
- data/vendor/assets/javascripts/precompiled/development/date_locales.js +952 -0
- data/vendor/assets/javascripts/precompiled/development/date_ranges.js +183 -0
- data/vendor/assets/javascripts/precompiled/development/es5.js +443 -0
- data/vendor/assets/javascripts/precompiled/development/function.js +222 -0
- data/vendor/assets/javascripts/{sugar-inflections.js → precompiled/development/inflections.js} +51 -162
- data/vendor/assets/javascripts/precompiled/development/language.js +383 -0
- data/vendor/assets/javascripts/precompiled/development/number.js +422 -0
- data/vendor/assets/javascripts/precompiled/development/object.js +348 -0
- data/vendor/assets/javascripts/precompiled/development/regexp.js +92 -0
- data/vendor/assets/javascripts/precompiled/development/string.js +871 -0
- data/vendor/assets/javascripts/precompiled/minified/array.js +16 -0
- data/vendor/assets/javascripts/precompiled/minified/core.js +8 -0
- data/vendor/assets/javascripts/precompiled/minified/date.js +40 -0
- data/vendor/assets/javascripts/precompiled/minified/date_locales.js +38 -0
- data/vendor/assets/javascripts/precompiled/minified/date_ranges.js +3 -0
- data/vendor/assets/javascripts/precompiled/minified/es5.js +7 -0
- data/vendor/assets/javascripts/precompiled/minified/function.js +4 -0
- data/vendor/assets/javascripts/precompiled/minified/inflections.js +11 -0
- data/vendor/assets/javascripts/precompiled/minified/language.js +19 -0
- data/vendor/assets/javascripts/precompiled/minified/number.js +5 -0
- data/vendor/assets/javascripts/precompiled/minified/object.js +6 -0
- data/vendor/assets/javascripts/precompiled/minified/regexp.js +2 -0
- data/vendor/assets/javascripts/precompiled/minified/string.js +12 -0
- data/vendor/assets/javascripts/precompiled/readme.txt +3 -0
- data/vendor/assets/javascripts/sugar-development.js +8054 -0
- data/vendor/assets/javascripts/sugar-full.js +179 -0
- data/vendor/assets/javascripts/sugar.js +111 -6211
- metadata +35 -9
- data/vendor/assets/javascripts/sugar-core.js +0 -4001
- data/vendor/assets/javascripts/sugar-dates-only.js +0 -3121
- 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() -> '<p>some text</p>'
|
151
|
+
* 'one & two'.escapeHTML() -> 'one & two'
|
152
|
+
*
|
153
|
+
***/
|
154
|
+
'escapeHTML': function() {
|
155
|
+
return this.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
156
|
+
},
|
157
|
+
|
158
|
+
/***
|
159
|
+
* @method unescapeHTML([partial] = false)
|
160
|
+
* @returns String
|
161
|
+
* @short Restores escaped HTML characters.
|
162
|
+
* @example
|
163
|
+
*
|
164
|
+
* '<p>some text</p>'.unescapeHTML() -> '<p>some text</p>'
|
165
|
+
* 'one & two'.unescapeHTML() -> 'one & two'
|
166
|
+
*
|
167
|
+
***/
|
168
|
+
'unescapeHTML': function() {
|
169
|
+
return this.replace(/</g, '<').replace(/>/g, '>').replace(/&/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
|
+
|