"use strict";
/**
* @fileOverview These methods shape a string into the form you
* want it
* @module ink/strings/shape
* @author Terry Weiss
* @author zumbrunn
* @author Esa-Matti Suurone
* @author Pavel Pravosud
* @requires ink/strings/patterns
* @requires lodash
*/
var patterns = require( "./patterns" );
var tests = require("./tests") ;
var sys = require( "lodash" );
var nativeTrim = String.prototype.trim;
var nativeTrimRight = String.prototype.trimRight;
var nativeTrimLeft = String.prototype.trimLeft;
var defaultToWhiteSpace = function ( characters ) {
if ( sys.isNull( characters ) ) {
return '\\s';
} else if ( characters.source ) {
return characters.source;
} else {
return '[' + exports.escapeRegExp( characters ) + ']';
}
};
/**
* converts a string into a hexadecimal color representation (e.g. "ffcc33") from a color string
* like "rgb (255, 204, 51)".
*
* @param {String}
* string the string to convert
* @returns {String} the resulting hex color (w/o "#")
* @example
* var strings = require("ink-strings");
* strings.toHexColor("rgb(255, 0, 0)");
* -> "ff0000"
*/
exports.toHexColor = function ( string ) {
// noinspection MagicNumberJS
var HEXVALUE = 16;
if ( tests.startsWith( string, "rgb" ) ) {
var buffer = [];
var col = string.replace( /[^0-9,]/g, '' );
sys.each( col.split( "," ), function ( part ) {
var num = parseInt( part, 10 );
var hex = num.toString( HEXVALUE );
buffer.push( exports.pad( hex, 2, "0", -1 ) );
} );
return buffer.join( "" );
}
var color = string.replace( new RegExp( patterns.HEXPATTERN.source ), '' );
return exports.pad( color.toLowerCase(), 6, "0", -1 );
};
/**
* function cleans a string by throwing away all non-alphanumeric characters
*
* @returns cleaned string
*/
exports.toAlphanumeric = function ( string ) {
return string.replace( new RegExp( patterns.ANUMPATTERN.source, "g" ), '' );
};
/**
* Transforms string from space, dash, or underscore notation to camel-case.
*
* @param {String}
* string a string
* @returns {String} the resulting string
* @example
* strings.camelize( "the_doctor" );
* -> "theDoctor"
*/
exports.camelize = function ( string ) {
return string.replace( patterns.CAPITALSPATTERN,function ( m, l ) {
// "ABC" -> "Abc"
return l[ 0 ].toUpperCase() + l.substring( 1 ).toLowerCase();
} ).replace( patterns.SEPARATORS, function ( m, l ) {
// foo-bar -> fooBar
return l.toUpperCase();
} );
};
/**
* function inserts a string every number of characters
*
* @param {String}
* string
* @param {Number}
* interval number of characters after which insertion should take place
* @param {String}
* string to be inserted
* @returns String resulting string
* @example
* strings.group( "Madame Vastra wears a veil", 3, ":)" )
* -> "Mad:)ame:) Va:)str:)a w:)ear:)s a:) ve:)il:)"
*/
exports.group = function ( string, interval, str ) {
if ( !interval || interval < 1 ) {
// noinspection MagicNumberJS
interval = 20;
}
if ( !str || string.length < interval ) { return string; }
var buffer = [];
for ( var i = 0; i < string.length; i += interval ) {
var strPart = string.substring( i, i + interval );
buffer.push( strPart );
// if ( ignoreWhiteSpace === true || ( strPart.length === interval && !/\s/g.test( strPart ) ) ) {
buffer.push( str );
// }
}
return buffer.join( "" );
};
/**
* replace all linebreaks and optionally all br tags
*
* @param {String}
* flag indicating if html tags should be replaced
* @param {Boolean=}
* removeTags When true, <br/> and \n \r are all removed
* @param {string=} replacement for the linebreaks / html tags
* @returns {string} the unwrapped string
* @example
* strings.unwrap( "The Doctor <br/>has a cool\n bowtie" );
* -> "The Doctor <br/>has a cool bowtie"
*
* strings.unwrap( "The Doctor <br/>has a cool\n bowtie", true );
* ->"The Doctor has a cool bowtie"
*
*/
exports.unwrap = function ( string, removeTags, replacement ) {
replacement = replacement || "";
string = string.replace( patterns.LINEBREAKS, replacement );
return removeTags === true ? string.replace( patterns.BRTAGS, replacement ) : string;
};
/**
* Chops a string into chunks that are <code>steps</code> wide.
*
* @param {string}
* str The string to check
* @param {integer}
* step The chunk to use when chopping the string up
* @returns {array.<string>}
*/
exports.chop = function ( str, step ) {
if ( sys.isNull( str ) ) {
return [];
}
str = String( str );
//noinspection JSHint
step = ~~step;
return step > 0 ? str.match( new RegExp( '.{1,' + step + '}', 'g' ) ) : [ str ];
};
/**
* Compress consecutive whitespaces to one.
*
* @param {string}
* str The string to check
* @returns {string}
*/
exports.clean = function ( str ) {
return exports.trim( str ).replace( /\s+/g, ' ' );
};
/**
* Returns an array of the characters in the string
*
* @param {string}
* str The string to split up
* @returns {array.<string>}
*/
exports.chars = function ( str ) {
if ( sys.isNull( str ) ) {
return [];
}
return String( str ).split( '' );
};
/**
* Returns a copy of the string in which all the case-based characters have had their case swapped.
*
* @param {string}
* str The string to work with
* @return {string}
*/
exports.swapCase = function ( str ) {
if ( sys.isNull( str ) ) {
return '';
}
return String( str ).replace( /\S/g, function ( c ) {
return c === c.toUpperCase() ? c.toLowerCase() : c.toUpperCase();
} );
};
/**
* Splices one string into another.
*
* @param {string}
* str The string to work with
* @param {integer}
* i The index to start splicing the new string into
* @param {integer}
* howmany How many characters to replace
* @param {string}
* substr The string to splice in
* @returns {string}
*/
exports.splice = function ( str, i, howmany, substr ) {
var arr = exports.chars( str );
//noinspection JSHint
arr.splice( ~~i, ~~howmany, substr );
return arr.join( '' );
};
/**
* Splits a string with <code>\n</code> new line markers into one string per line as an array.
*
* @param {string}
* str The string to split
* @returns {array.<string>}
*/
exports.lines = function ( str ) {
if ( sys.isNull( str ) ) {
return [];
}
return String( str ).split( "\n" );
};
/**
* Return reversed strings
*
* @param {string}
* str The string to reverse
* @returns {string}
*/
exports.reverse = function ( str ) {
return exports.chars( str ).reverse().join( '' );
};
/**
* Returns the next alphabetic letter. When you get to Z, you get the next char in the table (set by the OS or the
* browser), so be careful to limit yourself to alphas only.
*
* @param {char}
* str The character to get the next letter for
* @returns {char}
*/
exports.succ = function ( str ) {
if ( sys.isNull( str ) ) {
return '';
}
str = String( str );
return str.slice( 0, -1 ) + String.fromCharCode( str.charCodeAt( str.length - 1 ) + 1 );
};
/**
* Converts a camelized or dasherized string into an underscored one
*
* @param {string}
* str The string to convert
* @returns {string}
*/
exports.underscored = function ( str ) {
return exports.trim( str ).replace( /([a-z\d])([A-Z]+)/g, '$1_$2' ).replace( /[-\s]+/g, '_' ).toLowerCase();
};
/**
* Converts a underscored or camelized string into an dasherized one
*
* @param {string}
* str The string to convert
* @returns {string}
*/
exports.dasherize = function ( str ) {
return exports.trim( str ).replace( /([A-Z])/g, '-$1' ).replace( /[-_\s]+/g, '-' ).toLowerCase();
};
/**
* Converts string to camelized class name
*
* @param {string}
* str The string to convert
* @returns {string}
*/
exports.classify = function ( str ) {
return exports.titleize( String( str ).replace( /_/g, ' ' ) ).replace( /\s/g, '' );
};
/**
* Converts an underscored, camelized, or dasherized string into a humanized one. Also removes beginning and ending
* whitespace, and removes the postfix '_id'.
*
* @param {string}
* str The string to convert
* @returns {string}
*/
exports.humanize = function ( str ) {
return exports.capitalize( exports.underscored( str ).replace( /_id$/, '' ).replace( /_/g, ' ' ) );
};
/**
* Insert one string into another
*
* @param {string}
* str The string that receive the insert
* @param {integer}
* i Where to insert the string
* @param {string}
* substr The string to insert
* @returns {string}
*/
exports.insert = function ( str, i, substr ) {
return exports.splice( str, i, 0, substr );
};
/**
* Trims defined characters from begining and ending of the string. Defaults to whitespace characters.
*
* @param {string}
* str The string to trim
* @returns {string}
*/
exports.trim = function ( str ) {
if ( sys.isNull( str ) ) {
return '';
}
if ( nativeTrim ) {
return nativeTrim.call( str );
}
return String( str ).replace( new RegExp( /^\s+|\s+$/, 'g' ), '' );
};
/**
* Left trim. Similar to trim, but only for left side.
*
* @param {string}
* str The string to trim
* @param {string=}
* characters The characters to replace
* @returns {string}
*/
exports.ltrim = function ( str, characters ) {
if ( sys.isNull( str ) ) {
return '';
}
if ( !characters && nativeTrimLeft ) {
return nativeTrimLeft.call( str );
}
characters = defaultToWhiteSpace( characters );
return String( str ).replace( new RegExp( '^' + characters + '+' ), '' );
};
/**
* Right trim. Similar to trim, but only for right side.
*
* @param {string}
* str The string to trim
* @param {string=}
* characters The characters to replace
* @returns {string}
*/
exports.rtrim = function ( str, characters ) {
if ( sys.isNull( str ) ) {
return '';
}
if ( !characters && nativeTrimRight ) {
return nativeTrimRight.call( str );
}
characters = defaultToWhiteSpace( characters );
return String( str ).replace( new RegExp( characters + '+$' ), '' );
};
/**
* Truncates a string to a fixed length and you can optionally specify what you want the trailer to be, defaults to
* "..."
*
* @param {string}
* str The string to truncate
* @param {integer}
* length The length of the truncated string
* @param {string=}
* truncateStr The trailer
* @returns {string}
*/
exports.truncate = function ( str, length, truncateStr ) {
if ( sys.isNull( str ) ) {
return '';
}
str = String( str );
truncateStr = truncateStr || '...';
//noinspection JSHint
length = ~~length;
return str.length > length ? str.slice( 0, length ) + truncateStr : str;
};
/**
* Prefixes RegEx special characters with a backslash (\).
*
* @param {string}
* str The string to work with
* @return {string}
*/
exports.escapeRegExp = function ( str ) {
if ( str === null ) {
return '';
}
return String( str ).replace( patterns.REGEXCHARS, '\\$1' );
};
/**
* A more elegant version of truncate prune extra chars, never leaving a half-chopped word.
*
* @param {string}
* str The string to truncate
* @param {integer}
* length The length of the truncated string
* @param {string=}
* pruneStr The trailer
* @returns {string}
* @author Pavel Pravosud
* @see https://github.com/rwz
* @see https://github.com/epeli/underscore.string
*/
exports.prune = function ( str, length, pruneStr ) {
if ( sys.isNull( str ) ) {
return '';
}
str = String( str );
//noinspection JSHint
length = ~~length;
pruneStr = !sys.isEmpty( pruneStr ) ? String( pruneStr ) : '...';
if ( str.length <= length ) {
return str;
}
var tmpl = function ( c ) {
return c.toUpperCase() !== c.toLowerCase() ? 'A' : ' ';
}, template = str.slice( 0, length + 1 ).replace( /.(?=\W*\w*$)/g, tmpl ); // 'Hello, world' -> 'HellAA
// AAAAA'
if ( template.slice( template.length - 2 ).match( /\w\w/ ) ) {
template = template.replace( /\s*\S+$/, '' );
} else {
template = exports.rtrim( template.slice( 0, template.length - 1 ) );
}
return ( template + pruneStr ).length > str.length ? str : str.slice( 0, template.length ) + pruneStr;
};
/**
* Split string by delimiter (String or RegExp), /\s+/ by default.
*
* @param {string}
* str The string to split
* @param {string=}
* delimiter An optional delimiter to use
* @returns {array.<string>}
*/
exports.words = function ( str, delimiter ) {
if ( sys.isEmpty( str ) ) {
return [];
}
return exports.trim( str, delimiter ).split( delimiter || /\s+/ );
};
/**
* Join an array into a human readable sentence.
*
* @param {array}
* array The array to join
* @param {string=}
* separator The separator to use, defaults to ","
* @param {string=}
* lastSeparator The last seperator to use, defaults to "and"
* @param {boolean=}
* serial Serial commas? defaults to <code>false</code>
* @returns {string}
*/
exports.toSentence = function ( array, separator, lastSeparator, serial ) {
separator = separator || ', ';
lastSeparator = lastSeparator || ' and ';
var a = array.slice(), lastMember = a.pop();
if ( array.length > 2 && serial ) {
lastSeparator = exports.rtrim( separator ) + lastSeparator;
}
return a.length ? a.join( separator ) + lastSeparator + lastMember : lastMember;
};
/**
* The same as toSentence, but adjusts delimeters to use Serial comma.
*
* @param {array}
* array The array to join
* @param {string=}
* separator The separator to use, defaults to ","
* @param {string=}
* lastSeparator The last seperator to use, defaults to "and"
* @returns {string}
*/
exports.toSentenceSerial = function () {
var args = [].slice.call( arguments );
args[ 3 ] = true;
return exports.toSentence.apply( exports, args );
};
/**
* Transform text into a URL slug. Replaces whitespaces, accentuated, and special characters with a dash.
*
* @param {string}str
* The string to slugify
* @returns {string}
*/
exports.slugify = function ( str ) {
if ( sys.isNull( str ) ) {
return '';
}
var from = "ąàáäâãåæćęèéëêìíïîłńòóöôõøùúüûñçżź", to = "aaaaaaaaceeeeeiiiilnoooooouuuunczz", regex = new RegExp(
defaultToWhiteSpace( from ), 'g' );
str = String( str ).toLowerCase().replace( regex, function ( c ) {
var index = from.indexOf( c );
return to.charAt( index ) || '-';
} );
return exports.dasherize( str.replace( /[^\w\s-]/g, '' ) );
};
/**
* Surround a string with another string.
*
* @param {string}
* str The string to surround
* @param {string}
* wrapper The string to wrap it with
* @returns {string}
*/
exports.surround = function ( str, wrapper ) {
return [ wrapper, str, wrapper ].join( '' );
};
/**
* Quotes a string.
*
* @param {string}
* str The string to quote
* @returns {string}
*/
exports.quote = function ( str ) {
return exports.surround( str, '"' );
};
var strRepeat = function ( str, qty ) {
if ( qty < 1 ) {return '';}
var result = '';
while ( qty > 0 ) {
//noinspection JSHint
if ( qty & 1 ) {result += str;}
//noinspection JSHint
qty >>= 1, str += str;
}
return result;
};
/**
* Pads the striong with characters until the total string length is equal to the passed length parameter. By
* default, pads on the left with the space char (" "). padStr is truncated to a single character if necessary
*
* @param {string}
* str The string to pad
* @param {integer}length
* The length to pad out
* @param {string=}
* padStr The string to pad with
* @param {string=}
* type How to pad the string, choices are "right", "left", "both"
* @returns {string}
*/
exports.pad = function ( str, length, padStr, type ) {
str = str === null ? '' : String( str );
//noinspection JSHint
length = ~~length;
var padlen = 0;
if ( !padStr ) {
padStr = ' ';
} else if ( padStr.length > 1 ) {
padStr = padStr.charAt( 0 );
}
switch ( type ) {
case 'right':
padlen = length - str.length;
return str + strRepeat( padStr, padlen );
case 'both':
padlen = length - str.length;
return strRepeat( padStr, Math.ceil( padlen / 2 ) ) + str +
strRepeat( padStr, Math.floor( padlen / 2 ) );
default: // 'left'
padlen = length - str.length;
return strRepeat( padStr, padlen ) + str;
}
};
/**
* left-pad a string. Alias for pad(str, length, padStr, 'left').
*
* @param {string}
* str The string to pad
* @param {integer}length
* The length to pad out
* @param {string=}
* padStr The string to pad with
* @returns {string}
*/
exports.lpad = function ( str, length, padStr ) {
return exports.pad( str, length, padStr );
};
/**
* right-pad a string. Alias for pad(str, length, padStr, 'right')
*
* @param {string}
* str The string to pad
* @param {integer}length
* The length to pad out
* @param {string=}
* padStr The string to pad with
* @returns {string}
*/
exports.rpad = function ( str, length, padStr ) {
return exports.pad( str, length, padStr, 'right' );
};
/**
* left/right-pad a string. Alias for pad(str, length, padStr, 'both')
*
* @param {string}
* str The string to pad
* @param {integer}length
* The length to pad out
* @param {string=}
* padStr The string to pad with
* @returns {string}
*/
exports.lrpad = function ( str, length, padStr ) {
return exports.pad( str, length, padStr, 'both' );
};
var parseNumber = function ( source ) {
var res = ( source * 1 ) || 0;
return res;
};
/**
* Parse string to number. Returns NaN if string can't be parsed to number.
*
* @param {string}
* str The string to parse
* @param {integer=}
* decimals The number of decimals to return
* @returns {number}
*/
exports.toNumber = function ( str, decimals ) {
if ( str === null || str === '' ) {return 0;}
str = String( str );
//noinspection JSHint
var num = parseNumber( parseNumber( str ).toFixed( ~~decimals ) );
return num === 0 && !str.match( /^0+$/ ) ? Number.NaN : num;
};
/**
* Joins strings together with given separator
*
* @param {...string}
* str The strings you want to join.
* @returns {string}
*/
exports.join = function () {
var args = [].slice.call( arguments ), separator = args.shift();
if ( separator === null ) {separator = '';}
return args.join( separator );
};
/**
* Returns the string with each first letter capitalized
*
* @param {string}
* str The string to capitalize
* @returns {string}
*/
exports.titleize = function ( str ) {
if ( str === null ) {return '';}
return String( str ).replace( /(?:^|\s)\S/g, function ( c ) {
return c.toUpperCase();
} );
};
/**
* Converts first letter of the string to uppercase.
*
* @param {string}
* str The string to check
* @returns {string}
*/
exports.capitalize = function ( str ) {
str = str === null ? '' : String( str );
return str.charAt( 0 ).toUpperCase() + str.slice( 1 );
};
/**
* Formats a string with {} placeholders like .net's format method. This is a very simple and fast replacement and not an interpolation.
* It is only appropriate for values and not object unless they format well with `toString()`.
*
* @example
* var res = strings.format( "Well, this is {0} how do yo do!", "fine" );
* -> "Well, this is fine how do yo do!"
*
* @param {string}
* format The format string
* @param {...object}
* The contents to replace
* @return {string}
*/
exports.format = function() {
var s = arguments[ 0 ];
for ( var i = 0; i < arguments.length - 1; i++ ) {
var reg = new RegExp( "\\{" + i + "\\}", "gm" );
s = s.replace( reg, arguments[ i + 1 ] );
}
return s;
};