UNPKG

voca

Version:

The ultimate JavaScript string library

1,676 lines (1,456 loc) 127 kB
/*! * Voca string library 1.4.1 * https://vocajs.pages.dev * * Copyright Dmitri Pavlutin and other contributors * Released under the MIT license */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = global || self, global.v = factory()); }(this, (function () { 'use strict'; function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); } function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); } function _iterableToArrayLimit(arr, i) { if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === "[object Arguments]")) { return; } var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } /** * Checks if `value` is `null` or `undefined` * * @ignore * @function isNil * @param {*} value The object to check * @return {boolean} Returns `true` is `value` is `undefined` or `null`, `false` otherwise */ function isNil(value) { return value === undefined || value === null; } /** * Converts the `value` to a boolean. If `value` is `undefined` or `null`, returns `defaultValue`. * * @ignore * @function toBoolean * @param {*} value The value to convert. * @param {boolean} [defaultValue=false] The default value. * @return {boolean} Returns the coercion to boolean. */ function coerceToBoolean(value) { var defaultValue = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; if (isNil(value)) { return defaultValue; } return Boolean(value); } /** * Checks whether `subject` is a string primitive type. * * @function isString * @static * @since 1.0.0 * @memberOf Query * @param {string} subject The value to verify. * @return {boolean} Returns `true` if `subject` is string primitive type or `false` otherwise. * @example * v.isString('vacation'); * // => true * * v.isString(560); * // => false */ function isString(subject) { return typeof subject === 'string'; } /** * Get the string representation of the `value`. * Converts the `value` to string. * If `value` is `null` or `undefined`, return `defaultValue`. * * @ignore * @function toString * @param {*} value The value to convert. * @param {*} [defaultValue=''] The default value to return. * @return {string|null} Returns the string representation of `value`. Returns `defaultValue` if `value` is * `null` or `undefined`. */ function coerceToString(value) { var defaultValue = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; if (isNil(value)) { return defaultValue; } if (isString(value)) { return value; } return String(value); } /** * Converts the first character of `subject` to upper case. If `restToLower` is `true`, convert the rest of * `subject` to lower case. * * @function capitalize * @static * @since 1.0.0 * @memberOf Case * @param {string} [subject=''] The string to capitalize. * @param {boolean} [restToLower=false] Convert the rest of `subject` to lower case. * @return {string} Returns the capitalized string. * @example * v.capitalize('apple'); * // => 'Apple' * * v.capitalize('aPPle', true); * // => 'Apple' */ function capitalize(subject, restToLower) { var subjectString = coerceToString(subject); var restToLowerCaseBoolean = coerceToBoolean(restToLower); if (subjectString === '') { return ''; } if (restToLowerCaseBoolean) { subjectString = subjectString.toLowerCase(); } return subjectString.substr(0, 1).toUpperCase() + subjectString.substr(1); } /** * Converts the `subject` to lower case. * * @function lowerCase * @static * @since 1.0.0 * @memberOf Case * @param {string} [subject=''] The string to convert to lower case. * @return {string} Returns the lower case string. * @example * v.lowerCase('Green'); * // => 'green' * * v.lowerCase('BLUE'); * // => 'blue' */ function lowerCase(subject) { var subjectString = coerceToString(subject, ''); return subjectString.toLowerCase(); } /** * A regular expression string matching digits * * @type {string} * @ignore */ var digit = '\\d'; /** * A regular expression string matching whitespace * * @type {string} * @ignore */ var whitespace = '\\s\\uFEFF\\xA0'; /** * A regular expression string matching high surrogate * * @type {string} * @ignore */ var highSurrogate = '\\uD800-\\uDBFF'; /** * A regular expression string matching low surrogate * * @type {string} * @ignore */ var lowSurrogate = '\\uDC00-\\uDFFF'; /** * A regular expression string matching diacritical mark * * @type {string} * @ignore */ var diacriticalMark = '\\u0300-\\u036F\\u1AB0-\\u1AFF\\u1DC0-\\u1DFF\\u20D0-\\u20FF\\uFE20-\\uFE2F'; /** * A regular expression to match the base character for a combining mark * * @type {string} * @ignore */ var base = '\\0-\\u02FF\\u0370-\\u1AAF\\u1B00-\\u1DBF\\u1E00-\\u20CF\\u2100-\\uD7FF\\uE000-\\uFE1F\\uFE30-\\uFFFF'; /** * Regular expression to match combining marks * * @see http://unicode.org/faq/char_combmark.html * @type {RegExp} * @ignore */ var REGEXP_COMBINING_MARKS = new RegExp('([' + base + ']|[' + highSurrogate + '][' + lowSurrogate + ']|[' + highSurrogate + '](?![' + lowSurrogate + '])|(?:[^' + highSurrogate + ']|^)[' + lowSurrogate + '])([' + diacriticalMark + ']+)', 'g'); /** * Regular expression to match surrogate pairs * * @see http://www.unicode.org/faq/utf_bom.html#utf16-2 * @type {RegExp} * @ignore */ var REGEXP_SURROGATE_PAIRS = new RegExp('([' + highSurrogate + '])([' + lowSurrogate + '])', 'g'); /** * Regular expression to match a unicode character * * @type {RegExp} * @ignore */ var REGEXP_UNICODE_CHARACTER = new RegExp('((?:[' + base + ']|[' + highSurrogate + '][' + lowSurrogate + ']|[' + highSurrogate + '](?![' + lowSurrogate + '])|(?:[^' + highSurrogate + ']|^)[' + lowSurrogate + '])(?:[' + diacriticalMark + ']+))|\ ([' + highSurrogate + '][' + lowSurrogate + '])|\ ([\\n\\r\\u2028\\u2029])|\ (.)', 'g'); /** * Regular expression to match whitespaces * * @type {RegExp} * @ignore */ var REGEXP_WHITESPACE = new RegExp('[' + whitespace + ']'); /** * Regular expression to match whitespaces from the left side * * @type {RegExp} * @ignore */ var REGEXP_TRIM_LEFT = new RegExp('^[' + whitespace + ']+'); /** * Regular expression to match whitespaces from the right side * * @type {RegExp} * @ignore */ var REGEXP_TRIM_RIGHT = new RegExp('[' + whitespace + ']+$'); /** * Regular expression to match digit characters * * @type {RegExp} * @ignore */ var REGEXP_DIGIT = new RegExp('^' + digit + '+$'); /** * Regular expression to match regular expression special characters * * @type {RegExp} * @ignore */ var REGEXP_SPECIAL_CHARACTERS = /[-[\]{}()*+!<=:?./\\^$|#,]/g; /** * Regular expression to match not latin characters * * @type {RegExp} * @ignore */ var REGEXP_NON_LATIN = /[^A-Za-z0-9]/g; /** * Regular expression to match HTML special characters. * * @type {RegExp} * @ignore */ var REGEXP_HTML_SPECIAL_CHARACTERS = /[<>&"'`]/g; /** * Regular expression to match sprintf format string * * @type {RegExp} * @ignore */ var REGEXP_CONVERSION_SPECIFICATION = /(%{1,2})(?:(\d+)\$)?(\+)?([ 0]|'.{1})?(-)?(\d+)?(?:\.(\d+))?([bcdiouxXeEfgGs])?/g; /** * Regular expression to match trailing zeros in a number * * @type {RegExp} * @ignore */ var REGEXP_TRAILING_ZEROS = /\.?0+$/g; /** * Regular expression to match a list of tags. * * @see https://html.spec.whatwg.org/multipage/syntax.html#syntax-tag-name * @type {RegExp} * @ignore */ var REGEXP_TAG_LIST = /<([A-Za-z0-9]+)>/g; /** * A regular expression to match the General Punctuation Unicode block * * @type {string} * @ignore */ var generalPunctuationBlock = '\\u2000-\\u206F'; /** * A regular expression to match non characters from from Basic Latin and Latin-1 Supplement Unicode blocks * * @type {string} * @ignore */ var nonCharacter = '\\x00-\\x2F\\x3A-\\x40\\x5B-\\x60\\x7b-\\xBF\\xD7\\xF7'; /** * A regular expression to match the dingbat Unicode block * * @type {string} * @ignore */ var dingbatBlock = '\\u2700-\\u27BF'; /** * A regular expression string that matches lower case letters: LATIN * * @type {string} * @ignore */ var lowerCaseLetter = 'a-z\\xB5\\xDF-\\xF6\\xF8-\\xFF\\u0101\\u0103\\u0105\\u0107\\u0109\\u010B\\u010D\\u010F\\u0111\\u0113\\u0115\\u0117\\u0119\\u011B\\u011D\\u011F\\u0121\\u0123\\u0125\\u0127\\u0129\\u012B\\u012D\\u012F\\u0131\\u0133\\u0135\\u0137\\u0138\\u013A\\u013C\\u013E\\u0140\\u0142\\u0144\\u0146\\u0148\\u0149\\u014B\\u014D\\u014F\\u0151\\u0153\\u0155\\u0157\\u0159\\u015B\\u015D\\u015F\\u0161\\u0163\\u0165\\u0167\\u0169\\u016B\\u016D\\u016F\\u0171\\u0173\\u0175\\u0177\\u017A\\u017C\\u017E-\\u0180\\u0183\\u0185\\u0188\\u018C\\u018D\\u0192\\u0195\\u0199-\\u019B\\u019E\\u01A1\\u01A3\\u01A5\\u01A8\\u01AA\\u01AB\\u01AD\\u01B0\\u01B4\\u01B6\\u01B9\\u01BA\\u01BD-\\u01BF\\u01C6\\u01C9\\u01CC\\u01CE\\u01D0\\u01D2\\u01D4\\u01D6\\u01D8\\u01DA\\u01DC\\u01DD\\u01DF\\u01E1\\u01E3\\u01E5\\u01E7\\u01E9\\u01EB\\u01ED\\u01EF\\u01F0\\u01F3\\u01F5\\u01F9\\u01FB\\u01FD\\u01FF\\u0201\\u0203\\u0205\\u0207\\u0209\\u020B\\u020D\\u020F\\u0211\\u0213\\u0215\\u0217\\u0219\\u021B\\u021D\\u021F\\u0221\\u0223\\u0225\\u0227\\u0229\\u022B\\u022D\\u022F\\u0231\\u0233-\\u0239\\u023C\\u023F\\u0240\\u0242\\u0247\\u0249\\u024B\\u024D\\u024F'; /** * A regular expression string that matches upper case letters: LATIN * * @type {string} * @ignore */ var upperCaseLetter = '\\x41-\\x5a\\xc0-\\xd6\\xd8-\\xde\\u0100\\u0102\\u0104\\u0106\\u0108\\u010a\\u010c\\u010e\\u0110\\u0112\\u0114\\u0116\\u0118\\u011a\\u011c\\u011e\\u0120\\u0122\\u0124\\u0126\\u0128\\u012a\\u012c\\u012e\\u0130\\u0132\\u0134\\u0136\\u0139\\u013b\\u013d\\u013f\\u0141\\u0143\\u0145\\u0147\\u014a\\u014c\\u014e\\u0150\\u0152\\u0154\\u0156\\u0158\\u015a\\u015c\\u015e\\u0160\\u0162\\u0164\\u0166\\u0168\\u016a\\u016c\\u016e\\u0170\\u0172\\u0174\\u0176\\u0178\\u0179\\u017b\\u017d\\u0181\\u0182\\u0184\\u0186\\u0187\\u0189-\\u018b\\u018e-\\u0191\\u0193\\u0194\\u0196-\\u0198\\u019c\\u019d\\u019f\\u01a0\\u01a2\\u01a4\\u01a6\\u01a7\\u01a9\\u01ac\\u01ae\\u01af\\u01b1-\\u01b3\\u01b5\\u01b7\\u01b8\\u01bc\\u01c4\\u01c5\\u01c7\\u01c8\\u01ca\\u01cb\\u01cd\\u01cf\\u01d1\\u01d3\\u01d5\\u01d7\\u01d9\\u01db\\u01de\\u01e0\\u01e2\\u01e4\\u01e6\\u01e8\\u01ea\\u01ec\\u01ee\\u01f1\\u01f2\\u01f4\\u01f6-\\u01f8\\u01fa\\u01fc\\u01fe\\u0200\\u0202\\u0204\\u0206\\u0208\\u020a\\u020c\\u020e\\u0210\\u0212\\u0214\\u0216\\u0218\\u021a\\u021c\\u021e\\u0220\\u0222\\u0224\\u0226\\u0228\\u022a\\u022c\\u022e\\u0230\\u0232\\u023a\\u023b\\u023d\\u023e\\u0241\\u0243-\\u0246\\u0248\\u024a\\u024c\\u024e'; /** * Regular expression to match Unicode words * * @type {RegExp} * @ignore */ var REGEXP_WORD = new RegExp('(?:[' + upperCaseLetter + '][' + diacriticalMark + ']*)?(?:[' + lowerCaseLetter + '][' + diacriticalMark + ']*)+|\ (?:[' + upperCaseLetter + '][' + diacriticalMark + ']*)+(?![' + lowerCaseLetter + '])|\ [' + digit + ']+|\ [' + dingbatBlock + ']|\ [^' + nonCharacter + generalPunctuationBlock + whitespace + ']+', 'g'); /** * Regular expression to match words from Basic Latin and Latin-1 Supplement blocks * * @type {RegExp} * @ignore */ var REGEXP_LATIN_WORD = /[A-Z\xC0-\xD6\xD8-\xDE]?[a-z\xDF-\xF6\xF8-\xFF]+|[A-Z\xC0-\xD6\xD8-\xDE]+(?![a-z\xDF-\xF6\xF8-\xFF])|\d+/g; /** * Regular expression to match alpha characters * * @see http://stackoverflow.com/a/22075070/1894471 * @type {RegExp} * @ignore */ var REGEXP_ALPHA = new RegExp('^(?:[' + lowerCaseLetter + upperCaseLetter + '][' + diacriticalMark + ']*)+$'); /** * Regular expression to match alpha and digit characters * * @see http://stackoverflow.com/a/22075070/1894471 * @type {RegExp} * @ignore */ var REGEXP_ALPHA_DIGIT = new RegExp('^((?:[' + lowerCaseLetter + upperCaseLetter + '][' + diacriticalMark + ']*)|[' + digit + '])+$'); /** * Regular expression to match Extended ASCII characters, i.e. the first 255 * * @type {RegExp} * @ignore */ var REGEXP_EXTENDED_ASCII = /^[\x01-\xFF]*$/; /** * Verifies if `value` is `undefined` or `null` and returns `defaultValue`. In other case returns `value`. * * @ignore * @function nilDefault * @param {*} value The value to verify. * @param {*} defaultValue The default value. * @return {*} Returns `defaultValue` if `value` is `undefined` or `null`, otherwise `defaultValue`. */ function nilDefault(value, defaultValue) { return value == null ? defaultValue : value; } /** * Get the string representation of the `value`. * Converts the `value` to string. * * @ignore * @function toString * @param {*} value The value to convert. * @return {string|null} Returns the string representation of `value`. */ function toString(value) { if (isNil(value)) { return null; } if (isString(value)) { return value; } return String(value); } /** * Splits `subject` into an array of words. * * @function words * @static * @since 1.0.0 * @memberOf Split * @param {string} [subject=''] The string to split into words. * @param {string|RegExp} [pattern] The pattern to watch words. If `pattern` is not RegExp, it is transformed to `new RegExp(pattern, flags)`. * @param {string} [flags=''] The regular expression flags. Applies when `pattern` is string type. * @return {Array} Returns the array of words. * @example * v.words('gravity can cross dimensions'); * // => ['gravity', 'can', 'cross', 'dimensions'] * * v.words('GravityCanCrossDimensions'); * // => ['Gravity', 'Can', 'Cross', 'Dimensions'] * * v.words('Gravity - can cross dimensions!'); * // => ['Gravity', 'can', 'cross', 'dimensions'] * * v.words('Earth gravity', /[^\s]+/g); * // => ['Earth', 'gravity'] */ function words(subject, pattern, flags) { var subjectString = coerceToString(subject); var patternRegExp; if (isNil(pattern)) { patternRegExp = REGEXP_EXTENDED_ASCII.test(subjectString) ? REGEXP_LATIN_WORD : REGEXP_WORD; } else if (pattern instanceof RegExp) { patternRegExp = pattern; } else { var flagsString = toString(nilDefault(flags, '')); patternRegExp = new RegExp(toString(pattern), flagsString); } return nilDefault(subjectString.match(patternRegExp), []); } /** * Transforms the `word` into camel case chunk. * * @param {string} word The word string * @param {number} index The index of the word in phrase. * @return {string} The transformed word. * @ignore */ function wordToCamel(word, index) { return index === 0 ? lowerCase(word) : capitalize(word, true); } /** * Converts the `subject` to <a href="https://en.wikipedia.org/wiki/CamelCase">camel case</a>. * * @function camelCase * @static * @since 1.0.0 * @memberOf Case * @param {string} [subject=''] The string to convert to camel case. * @return {string} The camel case string. * @example * v.camelCase('bird flight'); * // => 'birdFlight' * * v.camelCase('BirdFlight'); * // => 'birdFlight' * * v.camelCase('-BIRD-FLIGHT-'); * // => 'birdFlight' */ function camelCase(subject) { var subjectString = coerceToString(subject); if (subjectString === '') { return ''; } return words(subjectString).map(wordToCamel).join(''); } /** * Converts the first character of `subject` to lower case. * * @function decapitalize * @static * @since 1.0.0 * @memberOf Case * @param {string} [subject=''] The string to decapitalize. * @return {string} Returns the decapitalized string. * @example * v.decapitalize('Sun'); * // => 'sun' * * v.decapitalize('moon'); * // => 'moon' */ function decapitalize(subject) { var subjectString = coerceToString(subject); if (subjectString === '') { return ''; } return subjectString.substr(0, 1).toLowerCase() + subjectString.substr(1); } /** * Converts the `subject` to <a href="https://en.wikipedia.org/wiki/Letter_case#cite_ref-13">kebab case</a>, * also called <i>spinal case</i> or <i>lisp case</i>. * * @function kebabCase * @static * @since 1.0.0 * @memberOf Case * @param {string} [subject=''] The string to convert to kebab case. * @return {string} Returns the kebab case string. * @example * v.kebabCase('goodbye blue sky'); * // => 'goodbye-blue-sky' * * v.kebabCase('GoodbyeBlueSky'); * // => 'goodbye-blue-sky' * * v.kebabCase('-Goodbye-Blue-Sky-'); * // => 'goodbye-blue-sky' */ function kebabCase(subject) { var subjectString = coerceToString(subject); if (subjectString === '') { return ''; } return words(subjectString).map(lowerCase).join('-'); } /** * Converts the `subject` to <a href="https://en.wikipedia.org/wiki/Snake_case">snake case</a>. * * @function snakeCase * @static * @since 1.0.0 * @memberOf Case * @param {string} [subject=''] The string to convert to snake case. * @return {string} Returns the snake case string. * @example * v.snakeCase('learning to fly'); * // => 'learning_to_fly' * * v.snakeCase('LearningToFly'); * // => 'learning_to_fly' * * v.snakeCase('-Learning-To-Fly-'); * // => 'learning_to_fly' */ function snakeCase(subject) { var subjectString = coerceToString(subject); if (subjectString === '') { return ''; } return words(subjectString).map(lowerCase).join('_'); } /** * Converts the `subject` to upper case. * * @function upperCase * @static * @since 1.0.0 * @memberOf Case * @param {string} [subject=''] The string to convert to upper case. * @return {string} Returns the upper case string. * @example * v.upperCase('school'); * // => 'SCHOOL' */ function upperCase(subject) { var subjectString = coerceToString(subject); return subjectString.toUpperCase(); } /** * Converts the uppercase alpha characters of `subject` to lowercase and lowercase * characters to uppercase. * * @function swapCase * @static * @since 1.3.0 * @memberOf Case * @param {string} [subject=''] The string to swap the case. * @return {string} Returns the converted string. * @example * v.swapCase('League of Shadows'); * // => 'lEAGUE OF sHADOWS' * * v.swapCase('2 Bees'); * // => '2 bEES' */ function swapCase(subject) { var subjectString = coerceToString(subject); return subjectString.split('').reduce(swapAndConcat, ''); } function swapAndConcat(swapped, character) { var lowerCase = character.toLowerCase(); var upperCase = character.toUpperCase(); return swapped + (character === lowerCase ? upperCase : lowerCase); } /** * Converts the subject to title case. * * @function titleCase * @static * @since 1.4.0 * @memberOf Case * @param {string} [subject=''] The string to convert to title case. * @param {Array} [noSplit] Do not split words at the specified characters. * @return {string} Returns the title case string. * @example * v.titleCase('learning to fly'); * // => 'Learning To Fly' * * v.titleCase('jean-luc is good-looking', ['-']); * // => 'Jean-luc Is Good-looking' */ function titleCase(subject, noSplit) { var subjectString = coerceToString(subject); var noSplitArray = Array.isArray(noSplit) ? noSplit : []; var wordsRegExp = REGEXP_EXTENDED_ASCII.test(subjectString) ? REGEXP_LATIN_WORD : REGEXP_WORD; return subjectString.replace(wordsRegExp, function (word, index) { var isNoSplit = index > 0 && noSplitArray.indexOf(subjectString[index - 1]) >= 0; return isNoSplit ? word.toLowerCase() : capitalize(word, true); }); } /** * Clip the number to interval `downLimit` to `upLimit`. * * @ignore * @function clipNumber * @param {number} value The number to clip * @param {number} downLimit The down limit * @param {number} upLimit The upper limit * @return {number} The clipped number */ function clipNumber(value, downLimit, upLimit) { if (value <= downLimit) { return downLimit; } if (value >= upLimit) { return upLimit; } return value; } /** * Max save integer value * * @ignore * @type {number} */ var MAX_SAFE_INTEGER = 0x1fffffffffffff; /** * Transforms `value` to an integer. * * @ignore * @function toInteger * @param {number} value The number to transform. * @returns {number} Returns the transformed integer. */ function toInteger(value) { if (value === Infinity) { return MAX_SAFE_INTEGER; } if (value === -Infinity) { return -MAX_SAFE_INTEGER; } return ~~value; } /** * Truncates `subject` to a new `length`. * * @function truncate * @static * @since 1.0.0 * @memberOf Chop * @param {string} [subject=''] The string to truncate. * @param {int} length The length to truncate the string. * @param {string} [end='...'] The string to be added at the end. * @return {string} Returns the truncated string. * @example * v.truncate('Once upon a time', 7); * // => 'Once...' * * v.truncate('Good day, Little Red Riding Hood', 14, ' (...)'); * // => 'Good day (...)' * * v.truncate('Once upon', 10); * // => 'Once upon' */ function truncate(subject, length, end) { var subjectString = coerceToString(subject); var lengthInt = isNil(length) ? subjectString.length : clipNumber(toInteger(length), 0, MAX_SAFE_INTEGER); var endString = coerceToString(end, '...'); if (lengthInt >= subjectString.length) { return subjectString; } return subjectString.substr(0, length - endString.length) + endString; } /** * Access a character from `subject` at specified `position`. * * @function charAt * @static * @since 1.0.0 * @memberOf Chop * @param {string} [subject=''] The string to extract from. * @param {numbers} position The position to get the character. * @return {string} Returns the character at specified position. * @example * v.charAt('helicopter', 0); * // => 'h' * * v.charAt('helicopter', 1); * // => 'e' */ function charAt(subject, position) { var subjectString = coerceToString(subject); return subjectString.charAt(position); } var HIGH_SURROGATE_START = 0xd800; var HIGH_SURROGATE_END = 0xdbff; var LOW_SURROGATE_START = 0xdc00; var LOW_SURROGATE_END = 0xdfff; /** * Checks if `codePoint` is a high-surrogate number from range 0xD800 to 0xDBFF. * * @ignore * @param {number} codePoint The code point number to be verified * @return {boolean} Returns a boolean whether `codePoint` is a high-surrogate number. */ function isHighSurrogate(codePoint) { return codePoint >= HIGH_SURROGATE_START && codePoint <= HIGH_SURROGATE_END; } /** * Checks if `codePoint` is a low-surrogate number from range 0xDC00 to 0xDFFF. * * @ignore * @param {number} codePoint The code point number to be verified * @return {boolean} Returns a boolean whether `codePoint` is a low-surrogate number. */ function isLowSurrogate(codePoint) { return codePoint >= LOW_SURROGATE_START && codePoint <= LOW_SURROGATE_END; } /** * Get the astral code point number based on surrogate pair numbers. * * @ignore * @param {number} highSurrogate The high-surrogate code point number. * @param {number} lowSurrogate The low-surrogate code point number. * @return {number} Returns the astral symbol number. */ function getAstralNumberFromSurrogatePair(highSurrogate, lowSurrogate) { return (highSurrogate - HIGH_SURROGATE_START) * 0x400 + lowSurrogate - LOW_SURROGATE_START + 0x10000; } /** * Get the number representation of the `value`. * Converts the `value` to number. * If `value` is `null` or `undefined`, return `defaultValue`. * * @ignore * @function toString * @param {*} value The value to convert. * @param {*} [defaultValue=''] The default value to return. * @return {number|null} Returns the number representation of `value`. Returns `defaultValue` if `value` is * `null` or `undefined`. */ function coerceToNumber(value) { var defaultValue = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; if (isNil(value)) { return defaultValue; } if (typeof value === 'number') { return value; } return Number(value); } /** * If `value` is `NaN`, return `defaultValue`. In other case returns `value`. * * @ignore * @function nanDefault * @param {*} value The value to verify. * @param {*} defaultValue The default value. * @return {*} Returns `defaultValue` if `value` is `NaN`, otherwise `defaultValue`. */ function nanDefault(value, defaultValue) { return value !== value ? defaultValue : value; } /** * Get the Unicode code point value of the character at `position`. <br/> * If a valid UTF-16 <a href="https://rainsoft.io/what-every-javascript-developer-should-know-about-unicode/#24surrogatepairs"> * surrogate pair</a> starts at `position`, the * <a href="https://rainsoft.io/what-every-javascript-developer-should-know-about-unicode/#astralplanes">astral code point</a> * value at `position` is returned. * * @function codePointAt * @static * @since 1.0.0 * @memberOf Chop * @param {string} [subject=''] The string to extract from. * @param {number} position The position to get the code point number. * @return {number} Returns a non-negative number less than or equal to `0x10FFFF`. * @example * v.codePointAt('rain', 1); * // => 97, or 0x0061 * * v.codePointAt('\uD83D\uDE00 is smile', 0); // or '😀 is smile' * // => 128512, or 0x1F600 */ function codePointAt(subject, position) { var subjectString = coerceToString(subject); var subjectStringLength = subjectString.length; var positionNumber = coerceToNumber(position); positionNumber = nanDefault(positionNumber, 0); if (positionNumber < 0 || positionNumber >= subjectStringLength) { return undefined; } var firstCodePoint = subjectString.charCodeAt(positionNumber); var secondCodePoint; if (isHighSurrogate(firstCodePoint) && subjectStringLength > positionNumber + 1) { secondCodePoint = subjectString.charCodeAt(positionNumber + 1); if (isLowSurrogate(secondCodePoint)) { return getAstralNumberFromSurrogatePair(firstCodePoint, secondCodePoint); } } return firstCodePoint; } /** * Extracts the first `length` characters from `subject`. * * @function first * @static * @since 1.0.0 * @memberOf Chop * @param {string} [subject=''] The string to extract from. * @param {int} [length=1] The number of characters to extract. * @return {string} Returns the first characters string. * @example * v.first('helicopter'); * // => 'h' * * v.first('vehicle', 2); * // => 've' * * v.first('car', 5); * // => 'car' */ function first(subject, length) { var subjectString = coerceToString(subject); var lengthInt = isNil(length) ? 1 : clipNumber(toInteger(length), 0, MAX_SAFE_INTEGER); if (subjectString.length <= lengthInt) { return subjectString; } return subjectString.substr(0, lengthInt); } /** * Get a grapheme from `subject` at specified `position` taking care of * <a href="https://rainsoft.io/what-every-javascript-developer-should-know-about-unicode/#24surrogatepairs">surrogate pairs</a> and * <a href="https://rainsoft.io/what-every-javascript-developer-should-know-about-unicode/#25combiningmarks">combining marks</a>. * * @function graphemeAt * @static * @since 1.0.0 * @memberOf Chop * @param {string} [subject=''] The string to extract from. * @param {number} position The position to get the grapheme. * @return {string} Returns the grapheme at specified position. * @example * v.graphemeAt('\uD835\uDC00\uD835\uDC01', 0); // or '𝐀𝐁' * // => 'A' * * v.graphemeAt('cafe\u0301', 3); // or 'café' * // => 'é' */ function graphemeAt(subject, position) { var subjectString = coerceToString(subject); var positionNumber = coerceToNumber(position); var graphemeMatch; var graphemeMatchIndex = 0; positionNumber = nanDefault(positionNumber, 0); while ((graphemeMatch = REGEXP_UNICODE_CHARACTER.exec(subjectString)) !== null) { if (graphemeMatchIndex === positionNumber) { REGEXP_UNICODE_CHARACTER.lastIndex = 0; return graphemeMatch[0]; } graphemeMatchIndex++; } return ''; } /** * Extracts the last `length` characters from `subject`. * * @function last * @static * @since 1.0.0 * @memberOf Chop * @param {string} [subject=''] The string to extract from. * @param {int} [length=1] The number of characters to extract. * @return {string} Returns the last characters string. * @example * v.last('helicopter'); * // => 'r' * * v.last('vehicle', 2); * // => 'le' * * v.last('car', 5); * // => 'car' */ function last(subject, length) { var subjectString = coerceToString(subject); var lengthInt = isNil(length) ? 1 : clipNumber(toInteger(length), 0, MAX_SAFE_INTEGER); if (subjectString.length <= lengthInt) { return subjectString; } return subjectString.substr(subjectString.length - lengthInt, lengthInt); } /** * Truncates `subject` to a new `length` and does not break the words. Guarantees that the truncated string is no longer * than `length`. * * @static * @function prune * @since 1.0.0 * @memberOf Chop * @param {string} [subject=''] The string to prune. * @param {int} length The length to prune the string. * @param {string} [end='...'] The string to be added at the end. * @return {string} Returns the pruned string. * @example * v.prune('Once upon a time', 7); * // => 'Once...' * * v.prune('Good day, Little Red Riding Hood', 16, ' (more)'); * // => 'Good day (more)' * * v.prune('Once upon', 10); * // => 'Once upon' */ function prune(subject, length, end) { var subjectString = coerceToString(subject); var lengthInt = isNil(length) ? subjectString.length : clipNumber(toInteger(length), 0, MAX_SAFE_INTEGER); var endString = coerceToString(end, '...'); if (lengthInt >= subjectString.length) { return subjectString; } var pattern = REGEXP_EXTENDED_ASCII.test(subjectString) ? REGEXP_LATIN_WORD : REGEXP_WORD; var truncatedLength = 0; subjectString.replace(pattern, function (word, offset) { var wordInsertLength = offset + word.length; if (wordInsertLength <= lengthInt - endString.length) { truncatedLength = wordInsertLength; } }); return subjectString.substr(0, truncatedLength) + endString; } /** * Extracts from `subject` a string from `start` position up to `end` position. The character at `end` position is not * included. * * @function slice * @static * @since 1.0.0 * @memberOf Chop * @param {string} [subject=''] The string to extract from. * @param {number} start The position to start extraction. If negative use `subject.length + start`. * @param {number} [end=subject.length] The position to end extraction. If negative use `subject.length + end`. * @return {string} Returns the extracted string. * @note Uses native `String.prototype.slice()` * @example * v.slice('miami', 1); * // => 'iami' * * v.slice('florida', -4); * // => 'rida' * * v.slice('florida', 1, 4); * // => "lor" */ function slice(subject, start, end) { return coerceToString(subject).slice(start, end); } /** * Extracts from `subject` a string from `start` position a number of `length` characters. * * @function substr * @static * @since 1.0.0 * @memberOf Chop * @param {string} [subject=''] The string to extract from. * @param {number} start The position to start extraction. * @param {number} [length=subject.endOfString] The number of characters to extract. If omitted, extract to the end of `subject`. * @return {string} Returns the extracted string. * @note Uses native `String.prototype.substr()` * @example * v.substr('infinite loop', 9); * // => 'loop' * * v.substr('dreams', 2, 2); * // => 'ea' */ function substr(subject, start, length) { return coerceToString(subject).substr(start, length); } /** * Extracts from `subject` a string from `start` position up to `end` position. The character at `end` position is not * included. * * @function substring * @static * @since 1.0.0 * @memberOf Chop * @param {string} [subject=''] The string to extract from. * @param {number} start The position to start extraction. * @param {number} [end=subject.length] The position to end extraction. * @return {string} Returns the extracted string. * @note Uses native `String.prototype.substring()` * @example * v.substring('beach', 1); * // => 'each' * * v.substring('ocean', 1, 3); * // => 'ea' */ function substring(subject, start, end) { return coerceToString(subject).substring(start, end); } /** * Counts the characters in `subject`.<br/> * * @function count * @static * @since 1.0.0 * @memberOf Count * @param {string} [subject=''] The string to count characters. * @return {number} Returns the number of characters in `subject`. * @example * v.count('rain'); * // => 4 */ function count(subject) { return coerceToString(subject).length; } /** * Counts the graphemes in `subject` taking care of * <a href="https://rainsoft.io/what-every-javascript-developer-should-know-about-unicode/#24surrogatepairs">surrogate pairs</a> and * <a href="https://rainsoft.io/what-every-javascript-developer-should-know-about-unicode/#25combiningmarks">combining marks</a>. * * @function countGraphemes * @static * @since 1.0.0 * @memberOf Count * @param {string} [subject=''] The string to count graphemes. * @return {number} Returns the number of graphemes in `subject`. * @example * v.countGraphemes('cafe\u0301'); // or 'café' * // => 4 * * v.countGraphemes('\uD835\uDC00\uD835\uDC01'); // or '𝐀𝐁' * // => 2 * * v.countGraphemes('rain'); * // => 4 */ function countGrapheme(subject) { return coerceToString(subject).replace(REGEXP_COMBINING_MARKS, '*').replace(REGEXP_SURROGATE_PAIRS, '*').length; } /** * Counts the number of `substring` appearances in `subject`. * * @function countSubstrings * @static * @since 1.0.0 * @memberOf Count * @param {string} [subject=''] The string where to count. * @param {string} substring The substring to be counted. * @return {number} Returns the number of `substring` appearances. * @example * v.countSubstrings('bad boys, bad boys whatcha gonna do?', 'boys'); * // => 2 * * v.countSubstrings('every dog has its day', 'cat'); * // => 0 */ function countSubstrings(subject, substring) { var subjectString = coerceToString(subject); var substringString = coerceToString(substring); var substringLength = substringString.length; var count = 0; var matchIndex = 0; if (subjectString === '' || substringString === '') { return count; } do { matchIndex = subjectString.indexOf(substringString, matchIndex); if (matchIndex !== -1) { count++; matchIndex += substringLength; } } while (matchIndex !== -1); return count; } var reduce = Array.prototype.reduce; /** * Counts the characters in `subject` for which `predicate` returns truthy. * * @function countWhere * @static * @since 1.0.0 * @memberOf Count * @param {string} [subject=''] The string to count characters. * @param {Function} predicate The predicate function invoked on each character with parameters `(character, index, string)`. * @param {Object} [context] The context to invoke the `predicate`. * @return {number} Returns the number of characters for which `predicate` returns truthy. * @example * v.countWhere('hola!', v.isAlpha); * // => 4 * * v.countWhere('2022', function(character, index, str) { * return character === '2'; * }); * // => 3 */ function countWhere(subject, predicate, context) { var subjectString = coerceToString(subject); if (subjectString === '' || typeof predicate !== 'function') { return 0; } var predicateWithContext = predicate.bind(context); return reduce.call(subjectString, function (countTruthy, character, index) { return predicateWithContext(character, index, subjectString) ? countTruthy + 1 : countTruthy; }, 0); } /** * Counts the number of words in `subject`. * * @function countWords * @static * @since 1.0.0 * @memberOf Count * @param {string} [subject=''] The string to split into words. * @param {string|RegExp} [pattern] The pattern to watch words. If `pattern` is not RegExp, it is transformed to `new RegExp(pattern, flags)`. * @param {string} [flags=''] The regular expression flags. Applies when `pattern` is string type. * @return {number} Returns the number of words. * @example * v.countWords('gravity can cross dimensions'); * // => 4 * * v.countWords('GravityCanCrossDimensions'); * // => 4 * * v.countWords('Gravity - can cross dimensions!'); * // => 4 * * v.words('Earth gravity', /[^\s]+/g); * // => 2 */ function countWords(subject, pattern, flags) { return words(subject, pattern, flags).length; } /** * The current index. * * @ignore * @name ReplacementIndex#index * @type {number} * @return {ReplacementIndex} ReplacementIndex instance. */ function ReplacementIndex() { this.index = 0; } /** * Increment the current index. * * @ignore * @return {undefined} */ ReplacementIndex.prototype.increment = function () { this.index++; }; /** * Increment the current index by position. * * @ignore * @param {number} [position] The replacement position. * @return {undefined} */ ReplacementIndex.prototype.incrementOnEmptyPosition = function (position) { if (isNil(position)) { this.increment(); } }; /** * Get the replacement index by position. * * @ignore * @param {number} [position] The replacement position. * @return {number} The replacement index. */ ReplacementIndex.prototype.getIndexByPosition = function (position) { return isNil(position) ? this.index : position - 1; }; // Type specifiers var TYPE_INTEGER = 'i'; var TYPE_INTEGER_BINARY = 'b'; var TYPE_INTEGER_ASCII_CHARACTER = 'c'; var TYPE_INTEGER_DECIMAL = 'd'; var TYPE_INTEGER_OCTAL = 'o'; var TYPE_INTEGER_UNSIGNED_DECIMAL = 'u'; var TYPE_INTEGER_HEXADECIMAL = 'x'; var TYPE_INTEGER_HEXADECIMAL_UPPERCASE = 'X'; var TYPE_FLOAT_SCIENTIFIC = 'e'; var TYPE_FLOAT_SCIENTIFIC_UPPERCASE = 'E'; var TYPE_FLOAT = 'f'; var TYPE_FLOAT_SHORT = 'g'; var TYPE_FLOAT_SHORT_UPPERCASE = 'G'; var TYPE_STRING = 's'; // Simple literals var LITERAL_SINGLE_QUOTE = "'"; var LITERAL_PLUS = '+'; var LITERAL_MINUS = '-'; var LITERAL_PERCENT_SPECIFIER = '%%'; // Radix constants to format numbers var RADIX_BINARY = 2; var RADIX_OCTAL = 8; var RADIX_HEXADECIMAL = 16; /** * Repeats the `subject` number of `times`. * * @function repeat * @static * @since 1.0.0 * @memberOf Manipulate * @param {string} [subject=''] The string to repeat. * @param {number} [times=1] The number of times to repeat. * @return {string} Returns the repeated string. * @example * v.repeat('w', 3); * // => 'www' * * v.repeat('world', 0); * // => '' */ function repeat(subject, times) { var subjectString = coerceToString(subject); var timesInt = isNil(times) ? 1 : clipNumber(toInteger(times), 0, MAX_SAFE_INTEGER); var repeatString = ''; while (timesInt) { if (timesInt & 1) { repeatString += subjectString; } if (timesInt > 1) { subjectString += subjectString; } timesInt >>= 1; } return repeatString; } /** * Creates the padding string. * * @ignore * @param {string} padCharacters The characters to create padding string. * @param {number} length The padding string length. * @return {string} The padding string. */ function buildPadding(padCharacters, length) { var padStringRepeat = toInteger(length / padCharacters.length); var padStringRest = length % padCharacters.length; return repeat(padCharacters, padStringRepeat + padStringRest).substr(0, length); } /** * Pads `subject` from left to a new `length`. * * @function padLeft * @static * @since 1.0.0 * @memberOf Manipulate * @param {string} [subject=''] The string to pad. * @param {int} [length=0] The length to left pad the string. No changes are made if `length` is less than `subject.length`. * @param {string} [pad=' '] The string to be used for padding. * @return {string} Returns the left padded string. * @example * v.padLeft('dog', 5); * // => ' dog' * * v.padLeft('bird', 6, '-'); * // => '--bird' * * v.padLeft('cat', 6, '-='); * // => '-=-cat' */ function padLeft(subject, length, pad) { var subjectString = coerceToString(subject); var lengthInt = isNil(length) ? 0 : clipNumber(toInteger(length), 0, MAX_SAFE_INTEGER); var padString = coerceToString(pad, ' '); if (lengthInt <= subjectString.length) { return subjectString; } return buildPadding(padString, lengthInt - subjectString.length) + subjectString; } /** * Pads `subject` from right to a new `length`. * * @function padRight * @static * @since 1.0.0 * @memberOf Manipulate * @param {string} [subject=''] The string to pad. * @param {int} [length=0] The length to right pad the string. No changes are made if `length` is less than `subject.length`. * @param {string} [pad=' '] The string to be used for padding. * @return {string} Returns the right padded string. * @example * v.padRight('dog', 5); * // => 'dog ' * * v.padRight('bird', 6, '-'); * // => 'bird--' * * v.padRight('cat', 6, '-='); * // => 'cat-=-' */ function padRight(subject, length, pad) { var subjectString = coerceToString(subject); var lengthInt = isNil(length) ? 0 : clipNumber(toInteger(length), 0, MAX_SAFE_INTEGER); var padString = coerceToString(pad, ' '); if (lengthInt <= subjectString.length) { return subjectString; } return subjectString + buildPadding(padString, lengthInt - subjectString.length); } /** * Aligns and pads `subject` string. * * @ignore * @param {string} subject The subject string. * @param {ConversionSpecification} conversion The conversion specification object. * @return {string} Returns the aligned and padded string. */ function alignAndPad(subject, conversion) { var width = conversion.width; if (isNil(width) || subject.length >= width) { return subject; } var padType = conversion.alignmentSpecifier === LITERAL_MINUS ? padRight : padLeft; return padType(subject, width, conversion.getPaddingCharacter()); } /** * Add sign to the formatted number. * * @ignore * @name addSignToFormattedNumber * @param {number} replacementNumber The number to be replaced. * @param {string} formattedReplacement The formatted version of number. * @param {ConversionSpecification} conversion The conversion specification object. * @return {string} Returns the formatted number string with a sign. */ function addSignToFormattedNumber(replacementNumber, formattedReplacement, conversion) { if (conversion.signSpecifier === LITERAL_PLUS && replacementNumber >= 0) { formattedReplacement = LITERAL_PLUS + formattedReplacement; } return formattedReplacement; } /** * Formats a float type according to specifiers. * * @ignore * @param {string} replacement The string to be formatted. * @param {ConversionSpecification} conversion The conversion specification object. * @return {string} Returns the formatted string. */ function float(replacement, conversion) { var replacementNumber = parseFloat(replacement); var formattedReplacement; if (isNaN(replacementNumber)) { replacementNumber = 0; } var precision = coerceToNumber(conversion.precision, 6); switch (conversion.typeSpecifier) { case TYPE_FLOAT: formattedReplacement = replacementNumber.toFixed(precision); break; case TYPE_FLOAT_SCIENTIFIC: formattedReplacement = replacementNumber.toExponential(precision); break; case TYPE_FLOAT_SCIENTIFIC_UPPERCASE: formattedReplacement = replacementNumber.toExponential(precision).toUpperCase(); break; case TYPE_FLOAT_SHORT: case TYPE_FLOAT_SHORT_UPPERCASE: formattedReplacement = formatFloatAsShort(replacementNumber, precision, conversion); break; } formattedReplacement = addSignToFormattedNumber(replacementNumber, formattedReplacement, conversion); return coerceToString(formattedReplacement); } /** * Formats the short float. * * @ignore * @param {number} replacementNumber The number to format. * @param {number} precision The precision to format the float. * @param {ConversionSpecification} conversion The conversion specification object. * @return {string} Returns the formatted short float. */ function formatFloatAsShort(replacementNumber, precision, conversion) { if (replacementNumber === 0) { return '0'; } var nonZeroPrecision = precision === 0 ? 1 : precision; var formattedReplacement = replacementNumber.toPrecision(nonZeroPrecision).replace(REGEXP_TRAILING_ZEROS, ''); if (conversion.typeSpecifier === TYPE_FLOAT_SHORT_UPPERCASE) { formattedReplacement = formattedReplacement.toUpperCase(); } return formattedReplacement; } /** * Formats an integer type according to specifiers. * * @ignore * @param {string} replacement The string to be formatted. * @param {ConversionSpecification} conversion The conversion specification object. * @return {string} Returns the formatted string. */ function integerBase(replacement, conversion) { var integer = parseInt(replacement); if (isNaN(integ