chiropractor 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,176 @@
1
+ // Underscore.inflection.js
2
+ // (c) 2011 Jeremy Ruppel
3
+ // Underscore.inflection is freely distributable under the MIT license.
4
+ // Portions of Underscore.inflection are inspired or borrowed from ActiveSupport
5
+ // Version 1.0.0
6
+
7
+ ( function( _, undefined )
8
+ {
9
+ var
10
+ plurals = [ ],
11
+
12
+ singulars = [ ],
13
+
14
+ uncountables = [ ];
15
+
16
+ /**
17
+ * Inflector
18
+ */
19
+ var inflector = {
20
+
21
+ gsub : function( word, rule, replacement )
22
+ {
23
+ var pattern = new RegExp( rule.source || rule, 'gi' );
24
+
25
+ return pattern.test( word ) ? word.replace( pattern, replacement ) : null;
26
+ },
27
+
28
+ plural : function( rule, replacement )
29
+ {
30
+ plurals.unshift( [ rule, replacement ] );
31
+ },
32
+
33
+ pluralize : function( word, count, includeNumber )
34
+ {
35
+ var result;
36
+
37
+ if( count !== undefined )
38
+ {
39
+ result = ( count === 1 ) ? this.singularize( word ) : this.pluralize( word );
40
+ result = ( includeNumber ) ? [ count, result ].join( ' ' ) : result;
41
+ }
42
+ else
43
+ {
44
+ if( _( uncountables ).include( word ) )
45
+ {
46
+ return word;
47
+ }
48
+
49
+ result = word;
50
+
51
+ _( plurals ).detect( function( rule )
52
+ {
53
+ var gsub = this.gsub( word, rule[ 0 ], rule[ 1 ] );
54
+
55
+ return gsub ? ( result = gsub ) : false;
56
+ },
57
+ this );
58
+ }
59
+
60
+ return result;
61
+ },
62
+
63
+ singular : function( rule, replacement )
64
+ {
65
+ singulars.unshift( [ rule, replacement ] );
66
+ },
67
+
68
+ singularize : function( word )
69
+ {
70
+ if( _( uncountables ).include( word ) )
71
+ {
72
+ return word;
73
+ }
74
+
75
+ var result = word;
76
+
77
+ _( singulars ).detect( function( rule )
78
+ {
79
+ var gsub = this.gsub( word, rule[ 0 ], rule[ 1 ] );
80
+
81
+ return gsub ? ( result = gsub ) : false;
82
+ },
83
+ this );
84
+
85
+ return result;
86
+ },
87
+
88
+ irregular : function( singular, plural )
89
+ {
90
+ this.plural( '\\b' + singular + '\\b', plural );
91
+ this.singular( '\\b' + plural + '\\b', singular );
92
+ },
93
+
94
+ uncountable : function( word )
95
+ {
96
+ uncountables.unshift( word );
97
+ },
98
+
99
+ resetInflections : function( )
100
+ {
101
+ plurals = [ ];
102
+ singulars = [ ];
103
+ uncountables = [ ];
104
+
105
+ this.plural( /$/, 's' );
106
+ this.plural( /s$/, 's' );
107
+ this.plural( /(ax|test)is$/, '$1es' );
108
+ this.plural( /(octop|vir)us$/, '$1i' );
109
+ this.plural( /(octop|vir)i$/, '$1i' );
110
+ this.plural( /(alias|status)$/, '$1es' );
111
+ this.plural( /(bu)s$/, '$1ses' );
112
+ this.plural( /(buffal|tomat)o$/, '$1oes' );
113
+ this.plural( /([ti])um$/, '$1a' );
114
+ this.plural( /([ti])a$/, '$1a' );
115
+ this.plural( /sis$/, 'ses' );
116
+ this.plural( /(?:([^f])fe|([lr])f)$/, '$1$2ves' );
117
+ this.plural( /(hive)$/, '$1s' );
118
+ this.plural( /([^aeiouy]|qu)y$/, '$1ies' );
119
+ this.plural( /(x|ch|ss|sh)$/, '$1es' );
120
+ this.plural( /(matr|vert|ind)(?:ix|ex)$/, '$1ices' );
121
+ this.plural( /([m|l])ouse$/, '$1ice' );
122
+ this.plural( /([m|l])ice$/, '$1ice' );
123
+ this.plural( /^(ox)$/, '$1en' );
124
+ this.plural( /^(oxen)$/, '$1' );
125
+ this.plural( /(quiz)$/, '$1zes' );
126
+
127
+ this.singular( /s$/, '' );
128
+ this.singular( /(n)ews$/, '$1ews' );
129
+ this.singular( /([ti])a$/, '$1um' );
130
+ this.singular( /((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/, '$1$2sis' );
131
+ this.singular( /(^analy)ses$/, '$1sis' );
132
+ this.singular( /([^f])ves$/, '$1fe' );
133
+ this.singular( /(hive)s$/, '$1' );
134
+ this.singular( /(tive)s$/, '$1' );
135
+ this.singular( /([lr])ves$/, '$1f' );
136
+ this.singular( /([^aeiouy]|qu)ies$/, '$1y' );
137
+ this.singular( /(s)eries$/, '$1eries' );
138
+ this.singular( /(m)ovies$/, '$1ovie' );
139
+ this.singular( /(x|ch|ss|sh)es$/, '$1' );
140
+ this.singular( /([m|l])ice$/, '$1ouse' );
141
+ this.singular( /(bus)es$/, '$1' );
142
+ this.singular( /(o)es$/, '$1' );
143
+ this.singular( /(shoe)s$/, '$1' );
144
+ this.singular( /(cris|ax|test)es$/, '$1is' );
145
+ this.singular( /(octop|vir)i$/, '$1us' );
146
+ this.singular( /(alias|status)es$/, '$1' );
147
+ this.singular( /^(ox)en/, '$1' );
148
+ this.singular( /(vert|ind)ices$/, '$1ex' );
149
+ this.singular( /(matr)ices$/, '$1ix' );
150
+ this.singular( /(quiz)zes$/, '$1' );
151
+ this.singular( /(database)s$/, '$1' );
152
+
153
+ this.irregular( 'person', 'people' );
154
+ this.irregular( 'man', 'men' );
155
+ this.irregular( 'child', 'children' );
156
+ this.irregular( 'sex', 'sexes' );
157
+ this.irregular( 'move', 'moves' );
158
+ this.irregular( 'cow', 'kine' );
159
+
160
+ _( 'equipment information rice money species series fish sheep jeans'.split( /\s+/ ) ).each( function( word )
161
+ {
162
+ this.uncountable( word );
163
+ },
164
+ this );
165
+
166
+ return this;
167
+ }
168
+
169
+ };
170
+
171
+ /**
172
+ * Underscore integration
173
+ */
174
+ _.mixin( inflector.resetInflections( ) );
175
+
176
+ } )( _ );
@@ -0,0 +1,613 @@
1
+ // Underscore.string
2
+ // (c) 2010 Esa-Matti Suuronen <esa-matti aet suuronen dot org>
3
+ // Underscore.string is freely distributable under the terms of the MIT license.
4
+ // Documentation: https://github.com/epeli/underscore.string
5
+ // Some code is borrowed from MooTools and Alexandru Marasteanu.
6
+ // Version '2.3.0'
7
+
8
+ !function(root, String){
9
+ 'use strict';
10
+
11
+ // Defining helper functions.
12
+
13
+ var nativeTrim = String.prototype.trim;
14
+ var nativeTrimRight = String.prototype.trimRight;
15
+ var nativeTrimLeft = String.prototype.trimLeft;
16
+
17
+ var parseNumber = function(source) { return source * 1 || 0; };
18
+
19
+ var strRepeat = function(str, qty){
20
+ if (qty < 1) return '';
21
+ var result = '';
22
+ while (qty > 0) {
23
+ if (qty & 1) result += str;
24
+ qty >>= 1, str += str;
25
+ }
26
+ return result;
27
+ };
28
+
29
+ var slice = [].slice;
30
+
31
+ var defaultToWhiteSpace = function(characters) {
32
+ if (characters == null)
33
+ return '\\s';
34
+ else if (characters.source)
35
+ return characters.source;
36
+ else
37
+ return '[' + _s.escapeRegExp(characters) + ']';
38
+ };
39
+
40
+ var escapeChars = {
41
+ lt: '<',
42
+ gt: '>',
43
+ quot: '"',
44
+ apos: "'",
45
+ amp: '&'
46
+ };
47
+
48
+ var reversedEscapeChars = {};
49
+ for(var key in escapeChars){ reversedEscapeChars[escapeChars[key]] = key; }
50
+
51
+ // sprintf() for JavaScript 0.7-beta1
52
+ // http://www.diveintojavascript.com/projects/javascript-sprintf
53
+ //
54
+ // Copyright (c) Alexandru Marasteanu <alexaholic [at) gmail (dot] com>
55
+ // All rights reserved.
56
+
57
+ var sprintf = (function() {
58
+ function get_type(variable) {
59
+ return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
60
+ }
61
+
62
+ var str_repeat = strRepeat;
63
+
64
+ var str_format = function() {
65
+ if (!str_format.cache.hasOwnProperty(arguments[0])) {
66
+ str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
67
+ }
68
+ return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
69
+ };
70
+
71
+ str_format.format = function(parse_tree, argv) {
72
+ var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
73
+ for (i = 0; i < tree_length; i++) {
74
+ node_type = get_type(parse_tree[i]);
75
+ if (node_type === 'string') {
76
+ output.push(parse_tree[i]);
77
+ }
78
+ else if (node_type === 'array') {
79
+ match = parse_tree[i]; // convenience purposes only
80
+ if (match[2]) { // keyword argument
81
+ arg = argv[cursor];
82
+ for (k = 0; k < match[2].length; k++) {
83
+ if (!arg.hasOwnProperty(match[2][k])) {
84
+ throw new Error(sprintf('[_.sprintf] property "%s" does not exist', match[2][k]));
85
+ }
86
+ arg = arg[match[2][k]];
87
+ }
88
+ } else if (match[1]) { // positional argument (explicit)
89
+ arg = argv[match[1]];
90
+ }
91
+ else { // positional argument (implicit)
92
+ arg = argv[cursor++];
93
+ }
94
+
95
+ if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
96
+ throw new Error(sprintf('[_.sprintf] expecting number but found %s', get_type(arg)));
97
+ }
98
+ switch (match[8]) {
99
+ case 'b': arg = arg.toString(2); break;
100
+ case 'c': arg = String.fromCharCode(arg); break;
101
+ case 'd': arg = parseInt(arg, 10); break;
102
+ case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
103
+ case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
104
+ case 'o': arg = arg.toString(8); break;
105
+ case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
106
+ case 'u': arg = Math.abs(arg); break;
107
+ case 'x': arg = arg.toString(16); break;
108
+ case 'X': arg = arg.toString(16).toUpperCase(); break;
109
+ }
110
+ arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
111
+ pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
112
+ pad_length = match[6] - String(arg).length;
113
+ pad = match[6] ? str_repeat(pad_character, pad_length) : '';
114
+ output.push(match[5] ? arg + pad : pad + arg);
115
+ }
116
+ }
117
+ return output.join('');
118
+ };
119
+
120
+ str_format.cache = {};
121
+
122
+ str_format.parse = function(fmt) {
123
+ var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
124
+ while (_fmt) {
125
+ if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
126
+ parse_tree.push(match[0]);
127
+ }
128
+ else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
129
+ parse_tree.push('%');
130
+ }
131
+ else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
132
+ if (match[2]) {
133
+ arg_names |= 1;
134
+ var field_list = [], replacement_field = match[2], field_match = [];
135
+ if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
136
+ field_list.push(field_match[1]);
137
+ while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
138
+ if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
139
+ field_list.push(field_match[1]);
140
+ }
141
+ else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
142
+ field_list.push(field_match[1]);
143
+ }
144
+ else {
145
+ throw new Error('[_.sprintf] huh?');
146
+ }
147
+ }
148
+ }
149
+ else {
150
+ throw new Error('[_.sprintf] huh?');
151
+ }
152
+ match[2] = field_list;
153
+ }
154
+ else {
155
+ arg_names |= 2;
156
+ }
157
+ if (arg_names === 3) {
158
+ throw new Error('[_.sprintf] mixing positional and named placeholders is not (yet) supported');
159
+ }
160
+ parse_tree.push(match);
161
+ }
162
+ else {
163
+ throw new Error('[_.sprintf] huh?');
164
+ }
165
+ _fmt = _fmt.substring(match[0].length);
166
+ }
167
+ return parse_tree;
168
+ };
169
+
170
+ return str_format;
171
+ })();
172
+
173
+
174
+
175
+ // Defining underscore.string
176
+
177
+ var _s = {
178
+
179
+ VERSION: '2.3.0',
180
+
181
+ isBlank: function(str){
182
+ if (str == null) str = '';
183
+ return (/^\s*$/).test(str);
184
+ },
185
+
186
+ stripTags: function(str){
187
+ if (str == null) return '';
188
+ return String(str).replace(/<\/?[^>]+>/g, '');
189
+ },
190
+
191
+ capitalize : function(str){
192
+ str = str == null ? '' : String(str);
193
+ return str.charAt(0).toUpperCase() + str.slice(1);
194
+ },
195
+
196
+ chop: function(str, step){
197
+ if (str == null) return [];
198
+ str = String(str);
199
+ step = ~~step;
200
+ return step > 0 ? str.match(new RegExp('.{1,' + step + '}', 'g')) : [str];
201
+ },
202
+
203
+ clean: function(str){
204
+ return _s.strip(str).replace(/\s+/g, ' ');
205
+ },
206
+
207
+ count: function(str, substr){
208
+ if (str == null || substr == null) return 0;
209
+
210
+ str = String(str);
211
+ substr = String(substr);
212
+
213
+ var count = 0,
214
+ pos = 0,
215
+ length = substr.length;
216
+
217
+ while (true) {
218
+ pos = str.indexOf(substr, pos);
219
+ if (pos === -1) break;
220
+ count++;
221
+ pos += length;
222
+ }
223
+
224
+ return count;
225
+ },
226
+
227
+ chars: function(str) {
228
+ if (str == null) return [];
229
+ return String(str).split('');
230
+ },
231
+
232
+ swapCase: function(str) {
233
+ if (str == null) return '';
234
+ return String(str).replace(/\S/g, function(c){
235
+ return c === c.toUpperCase() ? c.toLowerCase() : c.toUpperCase();
236
+ });
237
+ },
238
+
239
+ escapeHTML: function(str) {
240
+ if (str == null) return '';
241
+ return String(str).replace(/[&<>"']/g, function(m){ return '&' + reversedEscapeChars[m] + ';'; });
242
+ },
243
+
244
+ unescapeHTML: function(str) {
245
+ if (str == null) return '';
246
+ return String(str).replace(/\&([^;]+);/g, function(entity, entityCode){
247
+ var match;
248
+
249
+ if (entityCode in escapeChars) {
250
+ return escapeChars[entityCode];
251
+ } else if (match = entityCode.match(/^#x([\da-fA-F]+)$/)) {
252
+ return String.fromCharCode(parseInt(match[1], 16));
253
+ } else if (match = entityCode.match(/^#(\d+)$/)) {
254
+ return String.fromCharCode(~~match[1]);
255
+ } else {
256
+ return entity;
257
+ }
258
+ });
259
+ },
260
+
261
+ escapeRegExp: function(str){
262
+ if (str == null) return '';
263
+ return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
264
+ },
265
+
266
+ splice: function(str, i, howmany, substr){
267
+ var arr = _s.chars(str);
268
+ arr.splice(~~i, ~~howmany, substr);
269
+ return arr.join('');
270
+ },
271
+
272
+ insert: function(str, i, substr){
273
+ return _s.splice(str, i, 0, substr);
274
+ },
275
+
276
+ include: function(str, needle){
277
+ if (needle === '') return true;
278
+ if (str == null) return false;
279
+ return String(str).indexOf(needle) !== -1;
280
+ },
281
+
282
+ join: function() {
283
+ var args = slice.call(arguments),
284
+ separator = args.shift();
285
+
286
+ if (separator == null) separator = '';
287
+
288
+ return args.join(separator);
289
+ },
290
+
291
+ lines: function(str) {
292
+ if (str == null) return [];
293
+ return String(str).split("\n");
294
+ },
295
+
296
+ reverse: function(str){
297
+ return _s.chars(str).reverse().join('');
298
+ },
299
+
300
+ startsWith: function(str, starts){
301
+ if (starts === '') return true;
302
+ if (str == null || starts == null) return false;
303
+ str = String(str); starts = String(starts);
304
+ return str.length >= starts.length && str.slice(0, starts.length) === starts;
305
+ },
306
+
307
+ endsWith: function(str, ends){
308
+ if (ends === '') return true;
309
+ if (str == null || ends == null) return false;
310
+ str = String(str); ends = String(ends);
311
+ return str.length >= ends.length && str.slice(str.length - ends.length) === ends;
312
+ },
313
+
314
+ succ: function(str){
315
+ if (str == null) return '';
316
+ str = String(str);
317
+ return str.slice(0, -1) + String.fromCharCode(str.charCodeAt(str.length-1) + 1);
318
+ },
319
+
320
+ titleize: function(str){
321
+ if (str == null) return '';
322
+ return String(str).replace(/(?:^|\s)\S/g, function(c){ return c.toUpperCase(); });
323
+ },
324
+
325
+ camelize: function(str){
326
+ return _s.trim(str).replace(/[-_\s]+(.)?/g, function(match, c){ return c.toUpperCase(); });
327
+ },
328
+
329
+ underscored: function(str){
330
+ return _s.trim(str).replace(/([a-z\d])([A-Z]+)/g, '$1_$2').replace(/[-\s]+/g, '_').toLowerCase();
331
+ },
332
+
333
+ dasherize: function(str){
334
+ return _s.trim(str).replace(/([A-Z])/g, '-$1').replace(/[-_\s]+/g, '-').toLowerCase();
335
+ },
336
+
337
+ classify: function(str){
338
+ return _s.titleize(String(str).replace(/_/g, ' ')).replace(/\s/g, '');
339
+ },
340
+
341
+ humanize: function(str){
342
+ return _s.capitalize(_s.underscored(str).replace(/_id$/,'').replace(/_/g, ' '));
343
+ },
344
+
345
+ trim: function(str, characters){
346
+ if (str == null) return '';
347
+ if (!characters && nativeTrim) return nativeTrim.call(str);
348
+ characters = defaultToWhiteSpace(characters);
349
+ return String(str).replace(new RegExp('\^' + characters + '+|' + characters + '+$', 'g'), '');
350
+ },
351
+
352
+ ltrim: function(str, characters){
353
+ if (str == null) return '';
354
+ if (!characters && nativeTrimLeft) return nativeTrimLeft.call(str);
355
+ characters = defaultToWhiteSpace(characters);
356
+ return String(str).replace(new RegExp('^' + characters + '+'), '');
357
+ },
358
+
359
+ rtrim: function(str, characters){
360
+ if (str == null) return '';
361
+ if (!characters && nativeTrimRight) return nativeTrimRight.call(str);
362
+ characters = defaultToWhiteSpace(characters);
363
+ return String(str).replace(new RegExp(characters + '+$'), '');
364
+ },
365
+
366
+ truncate: function(str, length, truncateStr){
367
+ if (str == null) return '';
368
+ str = String(str); truncateStr = truncateStr || '...';
369
+ length = ~~length;
370
+ return str.length > length ? str.slice(0, length) + truncateStr : str;
371
+ },
372
+
373
+ /**
374
+ * _s.prune: a more elegant version of truncate
375
+ * prune extra chars, never leaving a half-chopped word.
376
+ * @author github.com/rwz
377
+ */
378
+ prune: function(str, length, pruneStr){
379
+ if (str == null) return '';
380
+
381
+ str = String(str); length = ~~length;
382
+ pruneStr = pruneStr != null ? String(pruneStr) : '...';
383
+
384
+ if (str.length <= length) return str;
385
+
386
+ var tmpl = function(c){ return c.toUpperCase() !== c.toLowerCase() ? 'A' : ' '; },
387
+ template = str.slice(0, length+1).replace(/.(?=\W*\w*$)/g, tmpl); // 'Hello, world' -> 'HellAA AAAAA'
388
+
389
+ if (template.slice(template.length-2).match(/\w\w/))
390
+ template = template.replace(/\s*\S+$/, '');
391
+ else
392
+ template = _s.rtrim(template.slice(0, template.length-1));
393
+
394
+ return (template+pruneStr).length > str.length ? str : str.slice(0, template.length)+pruneStr;
395
+ },
396
+
397
+ words: function(str, delimiter) {
398
+ if (_s.isBlank(str)) return [];
399
+ return _s.trim(str, delimiter).split(delimiter || /\s+/);
400
+ },
401
+
402
+ pad: function(str, length, padStr, type) {
403
+ str = str == null ? '' : String(str);
404
+ length = ~~length;
405
+
406
+ var padlen = 0;
407
+
408
+ if (!padStr)
409
+ padStr = ' ';
410
+ else if (padStr.length > 1)
411
+ padStr = padStr.charAt(0);
412
+
413
+ switch(type) {
414
+ case 'right':
415
+ padlen = length - str.length;
416
+ return str + strRepeat(padStr, padlen);
417
+ case 'both':
418
+ padlen = length - str.length;
419
+ return strRepeat(padStr, Math.ceil(padlen/2)) + str
420
+ + strRepeat(padStr, Math.floor(padlen/2));
421
+ default: // 'left'
422
+ padlen = length - str.length;
423
+ return strRepeat(padStr, padlen) + str;
424
+ }
425
+ },
426
+
427
+ lpad: function(str, length, padStr) {
428
+ return _s.pad(str, length, padStr);
429
+ },
430
+
431
+ rpad: function(str, length, padStr) {
432
+ return _s.pad(str, length, padStr, 'right');
433
+ },
434
+
435
+ lrpad: function(str, length, padStr) {
436
+ return _s.pad(str, length, padStr, 'both');
437
+ },
438
+
439
+ sprintf: sprintf,
440
+
441
+ vsprintf: function(fmt, argv){
442
+ argv.unshift(fmt);
443
+ return sprintf.apply(null, argv);
444
+ },
445
+
446
+ toNumber: function(str, decimals) {
447
+ if (str == null || str == '') return 0;
448
+ str = String(str);
449
+ var num = parseNumber(parseNumber(str).toFixed(~~decimals));
450
+ return num === 0 && !str.match(/^0+$/) ? Number.NaN : num;
451
+ },
452
+
453
+ numberFormat : function(number, dec, dsep, tsep) {
454
+ if (isNaN(number) || number == null) return '';
455
+
456
+ number = number.toFixed(~~dec);
457
+ tsep = tsep || ',';
458
+
459
+ var parts = number.split('.'), fnums = parts[0],
460
+ decimals = parts[1] ? (dsep || '.') + parts[1] : '';
461
+
462
+ return fnums.replace(/(\d)(?=(?:\d{3})+$)/g, '$1' + tsep) + decimals;
463
+ },
464
+
465
+ strRight: function(str, sep){
466
+ if (str == null) return '';
467
+ str = String(str); sep = sep != null ? String(sep) : sep;
468
+ var pos = !sep ? -1 : str.indexOf(sep);
469
+ return ~pos ? str.slice(pos+sep.length, str.length) : str;
470
+ },
471
+
472
+ strRightBack: function(str, sep){
473
+ if (str == null) return '';
474
+ str = String(str); sep = sep != null ? String(sep) : sep;
475
+ var pos = !sep ? -1 : str.lastIndexOf(sep);
476
+ return ~pos ? str.slice(pos+sep.length, str.length) : str;
477
+ },
478
+
479
+ strLeft: function(str, sep){
480
+ if (str == null) return '';
481
+ str = String(str); sep = sep != null ? String(sep) : sep;
482
+ var pos = !sep ? -1 : str.indexOf(sep);
483
+ return ~pos ? str.slice(0, pos) : str;
484
+ },
485
+
486
+ strLeftBack: function(str, sep){
487
+ if (str == null) return '';
488
+ str += ''; sep = sep != null ? ''+sep : sep;
489
+ var pos = str.lastIndexOf(sep);
490
+ return ~pos ? str.slice(0, pos) : str;
491
+ },
492
+
493
+ toSentence: function(array, separator, lastSeparator, serial) {
494
+ separator = separator || ', '
495
+ lastSeparator = lastSeparator || ' and '
496
+ var a = array.slice(), lastMember = a.pop();
497
+
498
+ if (array.length > 2 && serial) lastSeparator = _s.rtrim(separator) + lastSeparator;
499
+
500
+ return a.length ? a.join(separator) + lastSeparator + lastMember : lastMember;
501
+ },
502
+
503
+ toSentenceSerial: function() {
504
+ var args = slice.call(arguments);
505
+ args[3] = true;
506
+ return _s.toSentence.apply(_s, args);
507
+ },
508
+
509
+ slugify: function(str) {
510
+ if (str == null) return '';
511
+
512
+ var from = "ąàáäâãåæćęèéëêìíïîłńòóöôõøùúüûñçżź",
513
+ to = "aaaaaaaaceeeeeiiiilnoooooouuuunczz",
514
+ regex = new RegExp(defaultToWhiteSpace(from), 'g');
515
+
516
+ str = String(str).toLowerCase().replace(regex, function(c){
517
+ var index = from.indexOf(c);
518
+ return to.charAt(index) || '-';
519
+ });
520
+
521
+ return _s.dasherize(str.replace(/[^\w\s-]/g, ''));
522
+ },
523
+
524
+ surround: function(str, wrapper) {
525
+ return [wrapper, str, wrapper].join('');
526
+ },
527
+
528
+ quote: function(str) {
529
+ return _s.surround(str, '"');
530
+ },
531
+
532
+ exports: function() {
533
+ var result = {};
534
+
535
+ for (var prop in this) {
536
+ if (!this.hasOwnProperty(prop) || prop.match(/^(?:include|contains|reverse)$/)) continue;
537
+ result[prop] = this[prop];
538
+ }
539
+
540
+ return result;
541
+ },
542
+
543
+ repeat: function(str, qty, separator){
544
+ if (str == null) return '';
545
+
546
+ qty = ~~qty;
547
+
548
+ // using faster implementation if separator is not needed;
549
+ if (separator == null) return strRepeat(String(str), qty);
550
+
551
+ // this one is about 300x slower in Google Chrome
552
+ for (var repeat = []; qty > 0; repeat[--qty] = str) {}
553
+ return repeat.join(separator);
554
+ },
555
+
556
+ levenshtein: function(str1, str2) {
557
+ if (str1 == null && str2 == null) return 0;
558
+ if (str1 == null) return String(str2).length;
559
+ if (str2 == null) return String(str1).length;
560
+
561
+ str1 = String(str1); str2 = String(str2);
562
+
563
+ var current = [], prev, value;
564
+
565
+ for (var i = 0; i <= str2.length; i++)
566
+ for (var j = 0; j <= str1.length; j++) {
567
+ if (i && j)
568
+ if (str1.charAt(j - 1) === str2.charAt(i - 1))
569
+ value = prev;
570
+ else
571
+ value = Math.min(current[j], current[j - 1], prev) + 1;
572
+ else
573
+ value = i + j;
574
+
575
+ prev = current[j];
576
+ current[j] = value;
577
+ }
578
+
579
+ return current.pop();
580
+ }
581
+ };
582
+
583
+ // Aliases
584
+
585
+ _s.strip = _s.trim;
586
+ _s.lstrip = _s.ltrim;
587
+ _s.rstrip = _s.rtrim;
588
+ _s.center = _s.lrpad;
589
+ _s.rjust = _s.lpad;
590
+ _s.ljust = _s.rpad;
591
+ _s.contains = _s.include;
592
+ _s.q = _s.quote;
593
+
594
+ // Exporting
595
+
596
+ // CommonJS module is defined
597
+ if (typeof exports !== 'undefined') {
598
+ if (typeof module !== 'undefined' && module.exports)
599
+ module.exports = _s;
600
+
601
+ exports._s = _s;
602
+ }
603
+
604
+ // Register as a named module with AMD.
605
+ if (typeof define === 'function' && define.amd)
606
+ define('underscore.string', [], function(){ return _s; });
607
+
608
+
609
+ // Integrate with Underscore.js if defined
610
+ // or create our own underscore object.
611
+ root._ = root._ || {};
612
+ root._.string = root._.str = _s;
613
+ }(this, String);