1 /**
  2  * @fileOverview This file contains new methods for the String class
  3  */
  4 /**
  5  * @name String
  6  * @class A built-in class for sequences of UTF-16 characters forming a "string" of textual data.
  7  */
  8 
  9 /**
 10  * Repeat a string a number of times.
 11  * 
 12  * @param {Number} num The number of times to repeat
 13  * @param {String} separator An optional separator to insert in between the strings
 14  * @return {String} the repeated string
 15  */
 16 String.prototype.repeat = function repeat(num, separator) {
 17 	return Array.fill(num||1, this).join(separator||'');
 18 };
 19 
 20 /**
 21  * Expand a string repeating it up to a certain length.
 22  * 
 23  * @param {Number} len The length of the string to expand to
 24  * @return {String} the expanded string
 25  */
 26 String.prototype.expand = function expand(len) {
 27 	return this.repeat(Math.ceil(len / this.length)).substr(0, len);
 28 };
 29 
 30 /**
 31  * Return a version of this string with the first character in upper case
 32  * 
 33  * @return {String} The string with the modified case
 34  */
 35 String.prototype.toFirstUpperCase = function toFirstUpperCase() {
 36 	return this.charAt(0).toUpperCase() + this.substr(1);
 37 };
 38 
 39 /**
 40  * Return a version of this string with the first character in lower case
 41  * 
 42  * @return {String} The string with the modified case
 43  */
 44 String.prototype.toFirstLowerCase = function toFirstLowerCase() {
 45 	return this.charAt(0).toLowerCase() + this.substr(1);
 46 };
 47 
 48 /**
 49  * Return a version fo this string with all words matched by \w given an upper
 50  * case first character.
 51  * 
 52  * @return {String} The string with the modified case
 53  */
 54 String.prototype.toTitleCase = function toTitleCase() {
 55 	return this.replace(/\w+/g, function(m) { return m.toFirstUpperCase(); });
 56 };
 57 
 58 /**
 59  * Check and see if this string starts with another
 60  * 
 61  * @return {Boolean} A boolean indicating if this string starts with another
 62  */
 63 String.prototype.startsWith = function startsWith(other) {
 64 	return this.substr(0, other.length) === other;
 65 };
 66 
 67 /**
 68  * Check and see if this string ends with another
 69  * 
 70  * @return {Boolean} A boolean indicating if this string ends with another
 71  */
 72 String.prototype.endsWith = function endsWith(other) {
 73 	return this.substr(-other.length) === other;
 74 };
 75 
 76 /**
 77  * Check and see if this string contains another
 78  * 
 79  * @return {Boolean} A boolean indicating if this string contains another
 80  */
 81 String.prototype.contains = function contains(other) {
 82 	return this.indexOf(other) > -1;
 83 };
 84 
 85 /**
 86  * Count the number of times a substring is found within this string
 87  * 
 88  * @param {String} other The substring to search for
 89  * @param {Number} [offset=0] The offset from the start of the string for the search
 90  * @return {Number} An integer indicating how many times the substring is found
 91  */
 92 String.prototype.numberOf = function numberOf(other, offset) {
 93 	offset = offset || 0;
 94 	var i, c = 0;
 95 	while( (i = this.indexOf(other, offset)) && i >= 0 ) {
 96 		c++;
 97 		offset = i + other.length;
 98 	}
 99 	return c;
100 };
101 
102 /**
103  * Reverse the order of characters in this string
104  * 
105  * @return {String} A new string with characters in the reverse order
106  */
107 String.prototype.reverse = function reverse() {
108 	return this.split('').reverse().join('');
109 };
110 /**
111  * Trim all whitespace from the left side of the string
112  * 
113  * @return {String} The new trimmed string
114  */
115 
116 /**
117  * Trim all whitespace from the left side of the string
118  * 
119  * @return {String} The new trimmed string
120  */
121 if ( !String.prototype.trimLeft )
122 	String.prototype.trimLeft = function trimLeft() {
123 		return this.replace(/^\s\s*/, '');
124 	};
125 
126 /**
127  * Trim all whitespace from the right side of the string
128  * 
129  * @return {String} The new trimmed string
130  */
131 if ( !String.prototype.trimRight )
132 	String.prototype.trimRight = function trimRight() {
133 		return this.replace(/\s*\s*$/, '');
134 	};
135 
136 /**
137  * Trim all whitespace from both sides of the string
138  * 
139  * @return {String} The new trimmed string
140  */
141 if ( !String.prototype.trim )
142 	String.prototype.trim = function trim() {
143 		return this.trimLeft().trimRight();
144 	};
145 
146 
147 /**
148  * Strip characters from both sides of the string
149  * 
150  * @param {String} chars The characters to remove
151  * @return {String} The new stripped string
152  */
153 String.prototype.strip = function strip(chars, internal) {
154 	if(!chars) throw new TypeError("Stripping requires a list of characters to strip");
155 	internal = internal || 3;
156 	// This creates a table where chars[char] will be truthy/falsey for inclusion
157 	var chars = Object.invert(typeof chars === 'string' ? chars.split('') : chars);
158 	
159 	var start = 0, end = this.length;
160 	
161 	if ( internal & 1 ) { // Left
162 		while( this.charAt(start) in chars )
163 			start++;
164 	}
165 	if ( internal & 2 ) { // Right
166 		while( this.charAt(end-1) in chars && end > start )
167 			end--;
168 	}
169 	
170 	return this.substring(start, end);
171 };
172 
173 /**
174  * Strip characters from the left side of the string
175  * 
176  * @param {String} chars The characters to remove
177  * @return {String} The new stripped string
178  */
179 String.prototype.stripLeft = function stripLeft(chars) {
180 	return this.strip(chars, 1);
181 };
182 
183 /**
184  * Strip characters from the right side of the string
185  * 
186  * @param {String} chars The characters to remove
187  * @return {String} The new stripped string
188  */
189 String.prototype.stripRight = function stripRight(chars) {
190 	return this.strip(chars, 2);
191 };
192 
193 /**
194  * Pad a string on both sides to a certain length
195  * If the string is equal to or larger than that length then it's size will be left alone
196  * 
197  * @param {Number} len The length to pad the string to
198  * @param {String} [chars=" "] The characters to pad the string with
199  */
200 String.prototype.pad = function pad(len, chars) {
201 	return this.padLeft(Math.floor(len/2), chars).padRight(Math.ceil(len/2), chars);
202 };
203 
204 /**
205  * Pad a string on the left side to a certain length
206  * If the string is equal to or larger than that length then it's size will be left alone
207  * 
208  * @param {Number} len The length to pad the string to
209  * @param {String} [chars=" "] The characters to pad the string with
210  */
211 String.prototype.padLeft = function padLeft(len, chars) {
212 	chars = chars || ' ';
213 	return chars.expand(Math.max(0, len - this.length)) + this;
214 };
215 
216 /**
217  * Pad a string on the right side to a certain length
218  * If the string is equal to or larger than that length then it's size will be left alone
219  * 
220  * @param {Number} len The length to pad the string to
221  * @param {String} [chars=" "] The characters to pad the string with
222  */
223 String.prototype.padRight = function padRight() {
224 	chars = chars || ' ';
225 	return this + chars.expand(Math.max(0, len - this.length));
226 };
227 
228 /**
229  * Partition a string. This breaks up a string by the first occurence of a separator
230  * and returns a 3 item array containing the part before the separator,
231  * the separator itself, and the part after the separator.
232  * If the separator is not found then the returned array will contain the string
233  * followed by two empty strings.
234  * @see http://docs.python.org/library/stdtypes.html#str.partition
235  * 
236  * @param {String|RegExp} sep The separator to split by
237  * @return {Array} The three item partitioned array
238  */
239 String.prototype.partition = function partition(sep) {
240 	if ( sep instanceof RegExp ) {
241 		var m = this.match(m);
242 		var i = m ? m.index : -1;
243 		sep = m[0];
244 	} else {
245 		var i = this.indexOf(sep);
246 	}
247 	var l = sep.length;
248 	
249 	return i > -1 ?
250 		[ this.substr(0, i), sep, this.substr(i+l) ] :
251 		[ this, '', '' ];
252 };
253 
254 /**
255  * Same as string.partition but from the rightmost occurence of the separator
256  * @see String#partition
257  * 
258  * @param {String} sep The separator to split by
259  * @return {Array} The three item partitioned array
260  */
261 String.prototype.partitionRight = function partitionRight(sep) {
262 	var i = this.lastIndexOf(sep);
263 	return i > -1 ?
264 		[ this.substr(0, i), sep, this.substr(i+sep.length) ] :
265 		[ '', '', this ];
266 };
267 
268 String.prototype.explode = function explode(sep, limit) {
269 	
270 };
271 
272 String.prototype.scan = function scan(regex, offset) {
273 	var m, list = [];
274 	offset = offset || 0;
275 	if ( regex.global ) {
276 		var str = this.substr(offset);
277 		while( m = regex.exec(str) )
278 			list.push( m.length > 1 ? m.slice(1) : m[0] );
279 	} else {
280 		while( m = this.substr(offset).match(regex) ) {
281 			offset = m.index + m[0].length;
282 			list.push( m.length > 1 ? m.slice(1) : m[0] );
283 		}
284 	}
285 	
286 	return list;
287 };
288 
289 /**
290  * Converts a dash (foo-bar) or underscore (foo_bar) style name into a
291  * cammel case (fooBar) name.
292  * 
293  * @return {String} The new cammel case style string
294  */
295 String.prototype.toCamelCase = function toCamelCase() {
296 	return this.replace(/[-_][a-z]/g, function(i) { return i[1].toUpperCase(); });
297 };
298 
299 /**
300  * Converts a cammel case (fooBar) name into an underscore (foo_bar) style name.
301  * 
302  * @return {String} The new underscore style string
303  */
304 String.prototype.toUnderscore = function toUnderscore() {
305 	return this.replace(/[A-Z]/, function(i) { return '_' + i.toLowerCase(); });
306 };
307 
308 /**
309  * Converts a cammel case (fooBar) name into a dash (foo-bar) style name.
310  * 
311  * @return {String} The new dash style string
312  */
313 String.prototype.toDash = function toDash() {
314 	return this.replace(/[A-Z]/, function(i) { return '-' + i.toLowerCase(); });
315 };
316 
317