From 67257e4e4005be42457feba1a0d2e0d8ca53c950 Mon Sep 17 00:00:00 2001 From: David Knaack Date: Wed, 13 May 2026 17:19:20 +0200 Subject: [PATCH 1/2] chore: Reduce dependency footprint --- .github/actions/check-public-api/index.js | 4435 +---------------- packages/connectivity/package.json | 3 +- ...destination-accessor-failure-cases.spec.ts | 7 +- .../destination/destination-service.spec.ts | 22 +- .../scp-cf/destination/destination-service.ts | 70 +- packages/connectivity/src/scp-cf/jwt/jwt.ts | 79 +- packages/generator-common/package.json | 2 - .../generator-common/src/sdk-metadata/util.ts | 2 +- packages/generator-common/src/util.ts | 5 +- packages/generator/package.json | 3 - .../src/edmx-parser/v4/edmx-parser.ts | 10 +- .../common/operation-return-type.ts | 7 +- .../service/class.ts | 9 +- packages/generator/src/generator.ts | 15 +- .../src/name-formatting-strategies.ts | 6 +- packages/generator/src/operations/import.ts | 4 +- .../src/sdk-metadata/code-samples.ts | 9 +- .../sdk-metadata/generation-and-usage.spec.ts | 7 +- .../generator/src/service-name-formatter.ts | 22 +- packages/odata-common/package.json | 3 +- .../batch/batch-request-serializer.ts | 4 +- packages/util/package.json | 1 - packages/util/src/string-formatter.ts | 149 +- pnpm-lock.yaml | 50 +- 24 files changed, 464 insertions(+), 4460 deletions(-) diff --git a/.github/actions/check-public-api/index.js b/.github/actions/check-public-api/index.js index b0517e1ee6..e7e848a9c8 100644 --- a/.github/actions/check-public-api/index.js +++ b/.github/actions/check-public-api/index.js @@ -57831,4303 +57831,6 @@ module.exports = { module.exports = __nccwpck_require__(9023).deprecate; -/***/ }), - -/***/ 5231: -/***/ (function(module) { - -/*! - * Voca string library 1.4.1 - * https://vocajs.pages.dev - * - * Copyright Dmitri Pavlutin and other contributors - * Released under the MIT license - */ - -(function (global, factory) { - true ? module.exports = factory() : - 0; -}(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 camel case. - * - * @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 kebab case, - * also called spinal case or lisp case. - * - * @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 snake case. - * - * @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`.
- * If a valid UTF-16 - * surrogate pair starts at `position`, the - * astral code point - * 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 - * surrogate pairs and - * combining marks. - * - * @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`.
- * - * @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 - * surrogate pairs and - * combining marks. - * - * @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(integer)) { - integer = 0; - } - - integer = integer >>> 0; - - switch (conversion.typeSpecifier) { - case TYPE_INTEGER_ASCII_CHARACTER: - integer = String.fromCharCode(integer); - break; - - case TYPE_INTEGER_BINARY: - integer = integer.toString(RADIX_BINARY); - break; - - case TYPE_INTEGER_OCTAL: - integer = integer.toString(RADIX_OCTAL); - break; - - case TYPE_INTEGER_HEXADECIMAL: - integer = integer.toString(RADIX_HEXADECIMAL); - break; - - case TYPE_INTEGER_HEXADECIMAL_UPPERCASE: - integer = integer.toString(RADIX_HEXADECIMAL).toUpperCase(); - break; - } - - return coerceToString(integer); - } - - /** - * Formats a decimal 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 integerDecimal(replacement, conversion) { - var integer = parseInt(replacement); - - if (isNaN(integer)) { - integer = 0; - } - - return addSignToFormattedNumber(integer, toString(integer), conversion); - } - - /** - * Formats a string 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 stringFormat(replacement, conversion) { - var formattedReplacement = replacement; - var precision = conversion.precision; - - if (!isNil(precision) && formattedReplacement.length > precision) { - formattedReplacement = truncate(formattedReplacement, precision, ''); - } - - return formattedReplacement; - } - - /** - * Returns the computed string based on format specifiers. - * - * @ignore - * @name computeReplacement - * @param {string} replacement The replacement value. - * @param {ConversionSpecification} conversion The conversion specification object. - * @return {string} Returns the computed string. - */ - - function compute(replacement, conversion) { - var formatFunction; - - switch (conversion.typeSpecifier) { - case TYPE_STRING: - formatFunction = stringFormat; - break; - - case TYPE_INTEGER_DECIMAL: - case TYPE_INTEGER: - formatFunction = integerDecimal; - break; - - case TYPE_INTEGER_ASCII_CHARACTER: - case TYPE_INTEGER_BINARY: - case TYPE_INTEGER_OCTAL: - case TYPE_INTEGER_HEXADECIMAL: - case TYPE_INTEGER_HEXADECIMAL_UPPERCASE: - case TYPE_INTEGER_UNSIGNED_DECIMAL: - formatFunction = integerBase; - break; - - case TYPE_FLOAT: - case TYPE_FLOAT_SCIENTIFIC: - case TYPE_FLOAT_SCIENTIFIC_UPPERCASE: - case TYPE_FLOAT_SHORT: - case TYPE_FLOAT_SHORT_UPPERCASE: - formatFunction = float; - break; - } - - var formattedString = formatFunction(replacement, conversion); - return alignAndPad(formattedString, conversion); - } - - /** - * Construct the new conversion specification object. - * - * @ignore - * @param {Object} properties An object with properties to initialize. - * @return {ConversionSpecification} ConversionSpecification instance. - */ - - function ConversionSpecification(properties) { - /** - * The percent characters from conversion specification. - * - * @ignore - * @name ConversionSpecification#percent - * @type {string} - */ - this.percent = properties.percent; - /** - * The sign specifier to force a sign to be used on a number. - * - * @ignore - * @name ConversionSpecification#signSpecifier - * @type {string} - */ - - this.signSpecifier = properties.signSpecifier; - /** - * The padding specifier that says what padding character will be used. - * - * @ignore - * @name ConversionSpecification#paddingSpecifier - * @type {string} - */ - - this.paddingSpecifier = properties.paddingSpecifier; - /** - * The alignment specifier that says if the result should be left-justified or right-justified. - * - * @ignore - * @name ConversionSpecification#alignmentSpecifier - * @type {string} - */ - - this.alignmentSpecifier = properties.alignmentSpecifier; - /** - * The width specifier how many characters this conversion should result in. - * - * @ignore - * @name ConversionSpecification#width - * @type {number} - */ - - this.width = properties.width; - /** - * The precision specifier says how many decimal digits should be displayed for floating-point numbers. - * - * @ignore - * @name ConversionSpecification#precision - * @type {number} - */ - - this.precision = properties.precision; - /** - * The type specifier says what type the argument data should be treated as. - * - * @ignore - * @name ConversionSpecification#typeSpecifier - * @type {string} - */ - - this.typeSpecifier = properties.typeSpecifier; - } - /** - * Check if the conversion specification is a percent literal "%%". - * - * @ignore - * @return {boolean} Returns true if the conversion is a percent literal, false otherwise. - */ - - - ConversionSpecification.prototype.isPercentLiteral = function () { - return LITERAL_PERCENT_SPECIFIER === this.percent; - }; - /** - * Get the padding character from padding specifier. - * - * @ignore - * @returns {string} Returns the padding character. - */ - - - ConversionSpecification.prototype.getPaddingCharacter = function () { - var paddingCharacter = nilDefault(this.paddingSpecifier, ' '); - - if (paddingCharacter.length === 2 && paddingCharacter[0] === LITERAL_SINGLE_QUOTE) { - paddingCharacter = paddingCharacter[1]; - } - - return paddingCharacter; - }; - - /** - * Validates the specifier type and replacement position. - * - * @ignore - * @throws {Error} Throws an exception on insufficient arguments or unknown specifier. - * @param {number} index The index of the matched specifier. - * @param {number} replacementsLength The number of replacements. - * @param {ConversionSpecification} conversion The conversion specification object. - * @return {undefined} - */ - - function validate(index, replacementsLength, conversion) { - if (isNil(conversion.typeSpecifier)) { - throw new Error('sprintf(): Unknown type specifier'); - } - - if (index > replacementsLength - 1) { - throw new Error('sprintf(): Too few arguments'); - } - - if (index < 0) { - throw new Error('sprintf(): Argument number must be greater than zero'); - } - } - - /** - * Return the replacement for regular expression match of the conversion specification. - * - * @ignore - * @name matchReplacement - * @param {ReplacementIndex} replacementIndex The replacement index object. - * @param {string[]} replacements The array of replacements. - * @param {string} conversionSpecification The conversion specification. - * @param {string} percent The percent characters from conversion specification. - * @param {string} position The position to insert the replacement. - * @param {string} signSpecifier The sign specifier to force a sign to be used on a number. - * @param {string} paddingSpecifier The padding specifier that says what padding character will be used. - * @param {string} alignmentSpecifier The alignment specifier that says if the result should be left-justified or right-justified. - * @param {string} widthSpecifier The width specifier how many characters this conversion should result in. - * @param {string} precisionSpecifier The precision specifier says how many decimal digits should be displayed for floating-point numbers. - * @param {string} typeSpecifier The type specifier says what type the argument data should be treated as. - * @return {string} Returns the computed replacement. - */ - - function match(replacementIndex, replacements, conversionSpecification, percent, position, signSpecifier, paddingSpecifier, alignmentSpecifier, widthSpecifier, precisionSpecifier, typeSpecifier) { - var conversion = new ConversionSpecification({ - percent: percent, - signSpecifier: signSpecifier, - paddingSpecifier: paddingSpecifier, - alignmentSpecifier: alignmentSpecifier, - width: coerceToNumber(widthSpecifier, null), - precision: coerceToNumber(precisionSpecifier, null), - typeSpecifier: typeSpecifier - }); - - if (conversion.isPercentLiteral()) { - return conversionSpecification.slice(1); - } - - var actualReplacementIndex = replacementIndex.getIndexByPosition(position); - replacementIndex.incrementOnEmptyPosition(position); - validate(actualReplacementIndex, replacements.length, conversion); - return compute(replacements[actualReplacementIndex], conversion); - } - - /** - * Produces a string according to `format`. - * - *
- * `format` string is composed of zero or more directives: ordinary characters (not %), which are copied unchanged - * to the output string and conversion specifications, each of which results in fetching zero or more subsequent - * arguments.

- * - * Each conversion specification is introduced by the character %, and ends with a conversion - * specifier. In between there may be (in this order) zero or more flags, an optional minimum field width - * and an optional precision.
- * The syntax is: ConversionSpecification = "%" { Flags } - * [ MinimumFieldWidth ] [ Precision ] ConversionSpecifier, where curly braces { } denote repetition - * and square brackets [ ] optionality.

- * - * By default, the arguments are used in the given order.
- * For argument numbering and swapping, `%m$` (where `m` is a number indicating the argument order) - * is used instead of `%` to specify explicitly which argument is taken. For instance `%1$s` fetches the 1st argument, - * `%2$s` the 2nd and so on, no matter what position the conversion specification has in `format`. - *

- * - * The flags
- * The character % is followed by zero or more of the following flags:
- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
+ - * A sign (+ or -) should always be placed before a number produced by a - * signed conversion. By default a sign is used only for negative numbers. - *
0The value should be zero padded.
(a space) The value should be space padded.
'Indicates alternate padding character, specified by prefixing it with a single quote '.
-The converted value is to be left adjusted on the field boundary (the default is right justification).
- * - * The minimum field width
- * An optional decimal digit string (with nonzero first digit) specifying a minimum field width. If the converted - * value has fewer characters than the field width, it will be padded with spaces on the left (or right, if the - * left-adjustment flag has been given).

- * - * The precision
- * An optional precision, in the form of a period `.` followed by an optional decimal digit string.
- * This gives the number of digits to appear after the radix character for `e`, `E`, `f` and `F` conversions, the - * maximum number of significant digits for `g` and `G` conversions or the maximum number of characters to be printed - * from a string for `s` conversion.

- * - * The conversion specifier
- * A specifier that mentions what type the argument should be treated as: - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
`s`The string argument is treated as and presented as a string.
`d` `i`The integer argument is converted to signed decimal notation.
`b`The unsigned integer argument is converted to unsigned binary.
`c`The unsigned integer argument is converted to an ASCII character with that number.
`o`The unsigned integer argument is converted to unsigned octal.
`u`The unsigned integer argument is converted to unsigned decimal.
`x` `X`The unsigned integer argument is converted to unsigned hexadecimal. The letters `abcdef` are used for `x` - * conversions; the letters `ABCDEF` are used for `X` conversions.
`f` - * The float argument is rounded and converted to decimal notation in the style `[-]ddd.ddd`, where the number of - * digits after the decimal-point character is equal to the precision specification. If the precision is missing, - * it is taken as 6; if the precision is explicitly zero, no decimal-point character appears. - * If a decimal point appears, at least one digit appears before it. - *
`e` `E` - * The float argument is rounded and converted in the style `[-]d.ddde±dd`, where there is one digit - * before the decimal-point character and the number of digits after it is equal to the precision. If - * the precision is missing, it is taken as `6`; if the precision is zero, no decimal-point character - * appears. An `E` conversion uses the letter `E` (rather than `e`) to introduce the exponent. - *
`g` `G` - * The float argument is converted in style `f` or `e` (or `F` or `E` for `G` conversions). The precision specifies - * the number of significant digits. If the precision is missing, `6` digits are given; if the - * precision is zero, it is treated as `1`. Style `e` is used if the exponent from its conversion is less - * than `-6` or greater than or equal to the precision. Trailing zeros are removed from the fractional - * part of the result; a decimal point appears only if it is followed by at least one digit. - *
`%`A literal `%` is written. No argument is converted. The complete conversion specification is `%%`.
- *
- * - * @function sprintf - * @static - * @since 1.0.0 - * @memberOf Format - * @param {string} [format=''] The format string. - * @param {...*} replacements The replacements to produce the string. - * @return {string} Returns the produced string. - * @example - * v.sprintf('%s, %s!', 'Hello', 'World'); - * // => 'Hello World!' - * - * v.sprintf('%s costs $%d', 'coffee', 2); - * // => 'coffee costs $2' - * - * v.sprintf('%1$s %2$s %1$s %2$s, watcha gonna %3$s', 'bad', 'boys', 'do') - * // => 'bad boys bad boys, watcha gonna do' - * - * v.sprintf('% 6s', 'bird'); - * // => ' bird' - * - * v.sprintf('% -6s', 'crab'); - * // => 'crab ' - * - * v.sprintf("%'*5s", 'cat'); - * // => '**cat' - * - * v.sprintf("%'*-6s", 'duck'); - * // => 'duck**' - * - * v.sprintf('%d %i %+d', 15, -2, 25); - * // => '15 -2 +25' - * - * v.sprintf("%06d", 15); - * // => '000015' - * - * v.sprintf('0b%b 0o%o 0x%X', 12, 9, 155); - * // => '0b1100 0o11 0x9B' - * - * v.sprintf('%.2f', 10.469); - * // => '10.47' - * - * v.sprintf('%.2e %g', 100.5, 0.455); - * // => '1.01e+2 0.455' - * - */ - - function sprintf(format) { - var formatString = coerceToString(format); - - if (formatString === '') { - return formatString; - } - - for (var _len = arguments.length, replacements = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - replacements[_key - 1] = arguments[_key]; - } - - var boundReplacementMatch = match.bind(undefined, new ReplacementIndex(), replacements); - return formatString.replace(REGEXP_CONVERSION_SPECIFICATION, boundReplacementMatch); - } - - /** - * Produces a string according to `format`. Works exactly like sprintf(), - * with the only difference that accepts the formatting arguments in an array `values`.
- * See here `format` string specifications. - * - * @function vprintf - * @static - * @since 1.0.0 - * @memberOf Format - * @param {string} format=''] The format string. - * @param {Array} replacements The array of replacements to produce the string. - * @return {string} Returns the produced string. - * @example - * v.vprintf('%s', ['Welcome']) - * // => 'Welcome' - * - * v.vprintf('%s has %d apples', ['Alexandra', 3]); - * // => 'Alexandra has 3 apples' - */ - - function vprintf(format, replacements) { - return sprintf.apply(void 0, [format].concat(_toConsumableArray(nilDefault(replacements, [])))); - } - - var escapeCharactersMap = { - '<': '<', - '>': '>', - '&': '&', - '"': '"', - "'": ''', - '`': '`' - }; - /** - * Return the escaped version of `character`. - * - * @ignore - * @param {string} character The character to be escape. - * @return {string} The escaped version of character. - */ - - function replaceSpecialCharacter(character) { - return escapeCharactersMap[character]; - } - /** - * Escapes HTML special characters < > & ' " ` in subject. - * - * @function escapeHtml - * @static - * @since 1.0.0 - * @memberOf Escape - * @param {string} [subject=''] The string to escape. - * @return {string} Returns the escaped string. - * @example - * v.escapeHtml('

wonderful world

'); - * // => '<p>wonderful world</p>' - */ - - - function escapeHtml(subject) { - return coerceToString(subject).replace(REGEXP_HTML_SPECIAL_CHARACTERS, replaceSpecialCharacter); - } - - /** - * Escapes the regular expression special characters `- [ ] / { } ( ) * + ? . \ ^ $ |` in `subject`. - * - * @function escapeRegExp - * @static - * @since 1.0.0 - * @memberOf Escape - * @param {string} [subject=''] The string to escape. - * @return {string} Returns the escaped string. - * @example - * v.escapeRegExp('(hours)[minutes]{seconds}'); - * // => '\(hours\)\[minutes\]\{seconds\}' - */ - - function escapeRegExp(subject) { - return coerceToString(subject).replace(REGEXP_SPECIAL_CHARACTERS, '\\$&'); - } - - var unescapeCharactersMap = { - '<': /(<)|(�*3c;)|(�*60;)/gi, - '>': /(>)|(�*3e;)|(�*62;)/gi, - '&': /(&)|(�*26;)|(�*38;)/gi, - '"': /(")|(�*22;)|(�*34;)/gi, - "'": /(�*27;)|(�*39;)/gi, - '`': /(�*60;)|(�*96;)/gi - }; - var characters = Object.keys(unescapeCharactersMap); - /** - * Replaces the HTML entities with corresponding characters. - * - * @ignore - * @param {string} string The accumulator string. - * @param {string} key The character. - * @return {string} The string with replaced HTML entity - */ - - function reduceUnescapedString(string, key) { - return string.replace(unescapeCharactersMap[key], key); - } - /** - * Unescapes HTML special characters from &lt; &gt; &amp; &quot; &#x27; &#x60; - * to corresponding < > & ' " ` in subject. - * - * @function unescapeHtml - * @static - * @since 1.0.0 - * @memberOf Escape - * @param {string} [subject=''] The string to unescape. - * @return {string} Returns the unescaped string. - * @example - * v.unescapeHtml('<p>wonderful world</p>'); - * // => '

wonderful world

' - */ - - - function unescapeHtml(subject) { - var subjectString = coerceToString(subject); - return characters.reduce(reduceUnescapedString, subjectString); - } - - /** - * Returns the first occurrence index of `search` in `subject`. - * - * @function indexOf - * @static - * @since 1.0.0 - * @memberOf Index - * @param {string} [subject=''] The string where to search. - * @param {string} search The string to search. - * @param {number} [fromIndex=0] The index to start searching. - * @return {number} Returns the first occurrence index or `-1` if not found. - * @example - * v.indexOf('morning', 'n'); - * // => 3 - * - * v.indexOf('evening', 'o'); - * // => -1 - */ - - function indexOf(subject, search, fromIndex) { - var subjectString = coerceToString(subject); - return subjectString.indexOf(search, fromIndex); - } - - /** - * Returns the last occurrence index of `search` in `subject`. - * - * @function lastIndexOf - * @static - * @since 1.0.0 - * @memberOf Index - * @param {string} [subject=''] The string where to search. - * @param {string} search The string to search. - * @param {number} [fromIndex=subject.length - 1] The index to start searching backward in the string. - * @return {number} Returns the last occurrence index or `-1` if not found. - * @example - * v.lastIndexOf('morning', 'n'); - * // => 5 - * - * v.lastIndexOf('evening', 'o'); - * // => -1 - */ - - function lastIndexOf(subject, search, fromIndex) { - var subjectString = coerceToString(subject); - return subjectString.lastIndexOf(search, fromIndex); - } - - /** - * Returns the first index of a `pattern` match in `subject`. - * - * @function search - * @static - * @since 1.0.0 - * @memberOf Index - * @param {string} [subject=''] The string where to search. - * @param {string|RegExp} pattern The pattern to match. If `pattern` is not RegExp, it is transformed to `new RegExp(pattern)`. - * @param {number} [fromIndex=0] The index to start searching. - * @return {number} Returns the first match index or `-1` if not found. - * @example - * v.search('morning', /rn/); - * // => 2 - * - * v.search('evening', '/\d/'); - * // => -1 - */ - - function search(subject, pattern, fromIndex) { - var subjectString = coerceToString(subject); - var fromIndexNumber = isNil(fromIndex) ? 0 : clipNumber(toInteger(fromIndex), 0, subjectString.length); - var matchIndex = subjectString.substr(fromIndexNumber).search(pattern); - - if (matchIndex !== -1 && !isNaN(fromIndexNumber)) { - matchIndex += fromIndexNumber; - } - - return matchIndex; - } - - /** - * Inserts into `subject` a string `toInsert` at specified `position`. - * - * @function insert - * @static - * @since 1.0.0 - * @memberOf Manipulate - * @param {string} [subject=''] The string where to insert. - * @param {string} [toInsert=''] The string to be inserted. - * @param {number} [position=0] The position to insert. - * @return {string} Returns the string after insertion. - * @example - * v.insert('ct', 'a', 1); - * // => 'cat' - * - * v.insert('sunny', ' day', 5); - * // => 'sunny day' - */ - - function insert(subject, toInsert, position) { - var subjectString = coerceToString(subject); - var toInsertString = coerceToString(toInsert); - var positionNumber = coerceToNumber(position); - - if (positionNumber < 0 || positionNumber > subjectString.length || toInsertString === '') { - return subjectString; - } - - return subjectString.slice(0, positionNumber) + toInsertString + subjectString.slice(positionNumber); - } - - /** - * Generated diacritics map. See bellow the base code. - * @ignore - * @type Object - */ - var diacritics = { - '3': '\u039e\u03be', - '8': '\u0398\u03b8', - A: '\x41\xc0\xc1\xc2\xc3\xc4\xc5\u0100\u0102\u0104\u01cd\u01de\u01e0\u01fa\u0200\u0202\u0226\u023a\u0386\u0391\u0410', - B: '\x42\u0181\u0182\u0243\u0392\u0411', - C: '\x43\xc7\u0106\u0108\u010a\u010c\u0187\u023b\u0426', - D: '\x44\u010e\u0110\u0189\u018a\u018b\xd0\u0394\u0414', - E: '\x45\xc8\xc9\xca\xcb\u0112\u0114\u0116\u0118\u011a\u018e\u0190\u0204\u0206\u0228\u0388\u0395\u0415\u042d', - F: '\x46\u0191\u03a6\u0424', - G: '\x47\u011c\u011e\u0120\u0122\u0193\u01e4\u01e6\u01f4\u0393\u0413\u0490', - H: '\x48\u0124\u0126\u021e\u0389\u0397\u0425', - I: '\x49\xcc\xcd\xce\xcf\u0128\u012a\u012c\u012e\u0130\u0197\u01cf\u0208\u020a\u038a\u0399\u03aa\u0406\u0418', - J: '\x4a\u0134\u0248\u0419', - K: '\x4b\u0136\u0198\u01e8\u039a\u041a', - L: '\x4c\u0139\u013b\u013d\u013f\u0141\u023d\u039b\u041b', - M: '\x4d\u019c\u039c\u041c', - N: '\x4e\xd1\u0143\u0145\u0147\u019d\u01f8\u0220\u039d\u041d', - O: '\x4f\xd2\xd3\xd4\xd5\xd6\xd8\u014c\u014e\u0150\u0186\u019f\u01a0\u01d1\u01ea\u01ec\u01fe\u020c\u020e\u022a\u022c\u022e\u0230\u038c\u039f\u041e', - P: '\x50\u01a4\u03a0\u041f', - Q: '\x51\u024a', - R: '\x52\u0154\u0156\u0158\u0210\u0212\u024c\u03a1\u0420', - S: '\x53\u015a\u015c\u015e\u0160\u0218\u03a3\u0421', - T: '\x54\u0162\u0164\u0166\u01ac\u01ae\u021a\u023e\u03a4\u0422', - U: '\x55\xd9\xda\xdb\xdc\u0168\u016a\u016c\u016e\u0170\u0172\u01af\u01d3\u01d5\u01d7\u01d9\u01db\u0214\u0216\u0244\u0423\u042a', - V: '\x56\u01b2\u0245\u0412', - W: '\x57\u0174\u038f\u03a9', - X: '\x58\u03a7', - Y: '\x59\xdd\u0176\u0178\u01b3\u0232\u024e\u038e\u03a5\u03ab\u042b', - Z: '\x5a\u0179\u017b\u017d\u01b5\u0224\u0396\u0417', - a: '\x61\xe0\xe1\xe2\xe3\xe4\xe5\u0101\u0103\u0105\u01ce\u01df\u01e1\u01fb\u0201\u0203\u0227\u0250\u03ac\u03b1\u0430', - b: '\x62\u0180\u0183\u0253\u03b2\u0431', - c: '\x63\xe7\u0107\u0109\u010b\u010d\u0188\u023c\u0446', - d: '\x64\u010f\u0111\u018c\u0256\u0257\xf0\u03b4\u0434', - e: '\x65\xe8\xe9\xea\xeb\u0113\u0115\u0117\u0119\u011b\u01dd\u0205\u0207\u0229\u0247\u025b\u03ad\u03b5\u0435\u044d', - f: '\x66\u0192\u03c6\u0444', - g: '\x67\u011d\u011f\u0121\u0123\u01e5\u01e7\u01f5\u0260\u03b3\u0433\u0491', - h: '\x68\u0125\u0127\u021f\u0265\u03ae\u03b7\u0445', - i: '\x69\xec\xed\xee\xef\u0129\u012b\u012d\u012f\u0131\u01d0\u0209\u020b\u0268\u0390\u03af\u03b9\u03ca\u0438\u0456', - j: '\x6a\u0135\u01f0\u0249\u0439', - k: '\x6b\u0137\u0199\u01e9\u03ba\u043a', - l: '\x6c\u013a\u013c\u013e\u0140\u0142\u017f\u019a\u026b\u03bb\u043b', - m: '\x6d\u026f\u0271\u03bc\u043c', - n: '\x6e\xf1\u0144\u0146\u0148\u0149\u019e\u01f9\u0272\u03bd\u043d', - o: '\x6f\xf2\xf3\xf4\xf5\xf6\xf8\u014d\u014f\u0151\u01a1\u01d2\u01eb\u01ed\u01ff\u020d\u020f\u022b\u022d\u022f\u0231\u0254\u0275\u03bf\u03cc\u043e', - p: '\x70\u01a5\u03c0\u043f', - q: '\x71\u024b', - r: '\x72\u0155\u0157\u0159\u0211\u0213\u024d\u027d\u03c1\u0440', - s: '\x73\xdf\u015b\u015d\u015f\u0161\u0219\u023f\u03c2\u03c3\u0441', - t: '\x74\u0163\u0165\u0167\u01ad\u021b\u0288\u03c4\u0442', - u: '\x75\xf9\xfa\xfb\xfc\u0169\u016b\u016d\u016f\u0171\u0173\u01b0\u01d4\u01d6\u01d8\u01da\u01dc\u0215\u0217\u0289\u0443\u044a', - v: '\x76\u028b\u028c\u0432', - w: '\x77\u0175\u03c9\u03ce', - x: '\x78\u03c7', - y: '\x79\xfd\xff\u0177\u01b4\u0233\u024f\u03b0\u03c5\u03cb\u03cd\u044b', - z: '\x7a\u017a\u017c\u017e\u01b6\u0225\u0240\u03b6\u0437', - OE: '\x8c\u0152', - oe: '\x9c\u0153', - AE: '\xc6\u01e2\u01fc', - ae: '\xe6\u01e3\u01fd', - hv: '\u0195', - OI: '\u01a2', - oi: '\u01a3', - DZ: '\u01c4\u01f1', - Dz: '\u01c5\u01f2', - dz: '\u01c6\u01f3', - LJ: '\u01c7', - Lj: '\u01c8', - lj: '\u01c9', - NJ: '\u01ca', - Nj: '\u01cb', - nj: '\u01cc', - OU: '\u0222', - ou: '\u0223', - TH: '\xde', - th: '\xfe', - PS: '\u03a8', - ps: '\u03c8', - Yo: '\u0401', - Ye: '\u0404', - Yi: '\u0407', - Zh: '\u0416', - Ch: '\u0427', - Sh: '\u0428\u0429', - '': '\u042a\u042c\u044c', - Yu: '\u042e', - Ya: '\u042f', - zh: '\u0436', - ch: '\u0447', - sh: '\u0448\u0449', - yu: '\u044e', - ya: '\u044f', - yo: '\u0451', - ye: '\u0454', - yi: '\u0457' - }; - var diacriticsMap = null; - /** - * Creates a map of the diacritics. - * - * @ignore - * @returns {Object} Returns the diacritics map. - */ - - function getDiacriticsMap() { - if (diacriticsMap !== null) { - return diacriticsMap; - } - - diacriticsMap = {}; - Object.keys(diacritics).forEach(function (key) { - var characters = diacritics[key]; - - for (var index = 0; index < characters.length; index++) { - var character = characters[index]; - diacriticsMap[character] = key; - } - }); - return diacriticsMap; - } - /** - * Get the latin character from character with diacritics. - * - * @ignore - * @param {string} character The character with diacritics. - * @returns {string} Returns the character without diacritics. - */ - - - function getLatinCharacter(character) { - var characterWithoutDiacritic = getDiacriticsMap()[character]; - return characterWithoutDiacritic ? characterWithoutDiacritic : character; - } - - /** - * Returns the `cleanCharacter` from combining marks regular expression match. - * - * @ignore - * @param {string} character The character with combining marks - * @param {string} cleanCharacter The character without combining marks. - * @return {string} The character without combining marks. - */ - - function removeCombiningMarks(character, cleanCharacter) { - return cleanCharacter; - } - /** - * Latinises the `subject` by removing diacritic characters. - * - * @function latinise - * @static - * @since 1.0.0 - * @memberOf Manipulate - * @param {string} [subject=''] The string to latinise. - * @return {string} Returns the latinised string. - * @example - * v.latinise('cafe\u0301'); // or 'café' - * // => 'cafe' - * - * v.latinise('août décembre'); - * // => 'aout decembre' - * - * v.latinise('как прекрасен этот мир'); - * // => 'kak prekrasen etot mir' - */ - - - function latinise(subject) { - var subjectString = coerceToString(subject); - - if (subjectString === '') { - return ''; - } - - return subjectString.replace(REGEXP_NON_LATIN, getLatinCharacter).replace(REGEXP_COMBINING_MARKS, removeCombiningMarks); - } - - /** - * Pads `subject` to a new `length`. - * - * @function pad - * @static - * @since 1.0.0 - * @memberOf Manipulate - * @param {string} [subject=''] The string to pad. - * @param {int} [length=0] The length to 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 padded string. - * @example - * v.pad('dog', 5); - * // => ' dog ' - * - * v.pad('bird', 6, '-'); - * // => '-bird-' - * - * v.pad('cat', 6, '-='); - * // => '-cat-=' - */ - - function pad(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; - } - - var paddingLength = lengthInt - subjectString.length; - var paddingSideLength = toInteger(paddingLength / 2); - var paddingSideRemainingLength = paddingLength % 2; - return buildPadding(padString, paddingSideLength) + subjectString + buildPadding(padString, paddingSideLength + paddingSideRemainingLength); - } - - /** - * Replaces the matches of `search` with `replace`.
- * - * @function replace - * @static - * @since 1.0.0 - * @memberOf Manipulate - * @param {string} [subject=''] The string to verify. - * @param {string|RegExp} search The search pattern to replace. If `search` is a string, - * a simple string match is evaluated and only the first occurrence replaced. - * @param {string|Function} replace The string or function which invocation result replaces `search` match. - * @return {string} Returns the replacement result. - * @example - * v.replace('swan', 'wa', 'u'); - * // => 'sun' - * - * v.replace('domestic duck', /domestic\s/, ''); - * // => 'duck' - * - * v.replace('nice duck', /(nice)(duck)/, function(match, nice, duck) { - * return 'the ' + duck + ' is ' + nice; - * }); - * // => 'the duck is nice' - */ - - function replace(subject, search, replace) { - var subjectString = coerceToString(subject); - return subjectString.replace(search, replace); - } - - /** - * Replaces all occurrences of `search` with `replace`.
- * - * @function replaceAll - * @static - * @since 1.0.0 - * @memberOf Manipulate - * @param {string} [subject=''] The string to verify. - * @param {string|RegExp} search The search pattern to replace. If `search` is a string, a simple string match is evaluated. - * All matches are replaced. - * @param {string|Function} replace The string or function which invocation result replaces all `search` matches. - * @return {string} Returns the replacement result. - * @example - * v.replaceAll('good morning', 'o', '*'); - * // => 'g**d m*rning' - * v.replaceAll('evening', /n/g, 's'); - * // => 'evesisg' - * - */ - - function replaceAll(subject, search, replace) { - var subjectString = coerceToString(subject); - - if (search instanceof RegExp) { - if (search.flags.indexOf('g') === -1) { - throw new TypeError('search argument is a non-global regular expression'); - } - - return subjectString.replace(search, replace); - } - - var searchString = coerceToString(search); - var isFunctionalReplace = typeof replace === 'function'; - - if (!isFunctionalReplace) { - replace = coerceToString(replace); - } - - var searchLength = searchString.length; - - if (searchLength === 0) { - return replaceAll(subject, /(?:)/g, replace); - } - - var advanceBy = searchLength > 1 ? searchLength : 1; - var matchPositions = []; - var position = subjectString.indexOf(searchString, 0); - - while (position !== -1) { - matchPositions.push(position); - position = subjectString.indexOf(searchString, position + advanceBy); - } - - var endOfLastMatch = 0; - var result = ''; - - for (var i = 0; i < matchPositions.length; i++) { - var _position = matchPositions[i]; - var replacement = replace; - - if (isFunctionalReplace) { - replacement = coerceToString(replace.call(undefined, searchString, _position, subjectString)); - } - - result += subjectString.slice(endOfLastMatch, _position) + replacement; - endOfLastMatch = _position + searchLength; - } - - if (endOfLastMatch < subjectString.length) { - result += subjectString.slice(endOfLastMatch); - } - - return result; - } - - /** - * Reverses the `subject`. - * - * @function reverse - * @static - * @since 1.0.0 - * @memberOf Manipulate - * @param {string} [subject=''] The string to reverse. - * @return {string} Returns the reversed string. - * @example - * v.reverse('winter'); - * // => 'retniw' - */ - - function reverse(subject) { - var subjectString = coerceToString(subject); - return subjectString.split('').reverse().join(''); - } - - /** - * Reverses the `subject` taking care of - * surrogate pairs and - * combining marks. - * - * @function reverseGrapheme - * @static - * @since 1.0.0 - * @memberOf Manipulate - * @param {string} [subject=''] The string to reverse. - * @return {string} Returns the reversed string. - * @example - * v.reverseGrapheme('summer'); - * // => 'remmus' - * - * v.reverseGrapheme('𝌆 bar mañana mañana'); - * // => 'anañam anañam rab 𝌆' - */ - - function reverseGrapheme(subject) { - var subjectString = coerceToString(subject); - /** - * @see https://github.com/mathiasbynens/esrever - */ - - subjectString = subjectString.replace(REGEXP_COMBINING_MARKS, function ($0, $1, $2) { - return reverseGrapheme($2) + $1; - }).replace(REGEXP_SURROGATE_PAIRS, '$2$1'); - var reversedString = ''; - var index = subjectString.length; - - while (index--) { - reversedString += subjectString.charAt(index); - } - - return reversedString; - } - - /** - * Slugifies the `subject`. Cleans the `subject` by replacing diacritics with corresponding latin characters. - * - * @function slugify - * @static - * @since 1.0.0 - * @memberOf Manipulate - * @param {string} [subject=''] The string to slugify. - * @return {string} Returns the slugified string. - * @example - * v.slugify('Italian cappuccino drink'); - * // => 'italian-cappuccino-drink' - * - * v.slugify('caffé latté'); - * // => 'caffe-latte' - * - * v.slugify('хорошая погода'); - * // => 'horoshaya-pogoda' - */ - - function slugify(subject) { - var subjectString = coerceToString(subject); - - if (subjectString === '') { - return ''; - } - - var cleanSubjectString = latinise(subjectString).replace(REGEXP_NON_LATIN, '-'); - return kebabCase(cleanSubjectString); - } - - /** - * Changes `subject` by deleting `deleteCount` of characters starting at position `start`. Places a new string - * `toAdd` instead of deleted characters. - * - * @function splice - * @static - * @since 1.0.0 - * @memberOf Manipulate - * @param {string} [subject=''] The string where to insert. - * @param {string} start The position to start changing the string. For a negative position will start from the end of - * the string. - * @param {number} [deleteCount=subject.length-start] The number of characters to delete from string. - * @param {string} [toAdd=''] The string to be added instead of deleted characters. - * @return {string} Returns the modified string. - * @example - * v.splice('new year', 0, 4); - * // => 'year' - * - * v.splice('new year', 0, 3, 'happy'); - * // => 'happy year' - * - * v.splice('new year', -4, 4, 'day'); - * // => 'new day' - */ - - function splice(subject, start, deleteCount, toAdd) { - var subjectString = coerceToString(subject); - var toAddString = coerceToString(toAdd); - var startPosition = coerceToNumber(start); - - if (startPosition < 0) { - startPosition = subjectString.length + startPosition; - - if (startPosition < 0) { - startPosition = 0; - } - } else if (startPosition > subjectString.length) { - startPosition = subjectString.length; - } - - var deleteCountNumber = coerceToNumber(deleteCount, subjectString.length - startPosition); - - if (deleteCountNumber < 0) { - deleteCountNumber = 0; - } - - return subjectString.slice(0, startPosition) + toAddString + subjectString.slice(startPosition + deleteCountNumber); - } - - /** - * Translates characters or replaces substrings in `subject`. - * - * @function tr - * @static - * @since 1.3.0 - * @memberOf Manipulate - * @param {string} [subject=''] The string to translate. - * @param {string|Object} from The string of characters to translate from. Or an object, then the object keys are replaced with corresponding values (longest keys are tried first). - * @param {string} to The string of characters to translate to. Ignored when `from` is an object. - * @return {string} Returns the translated string. - * @example - * v.tr('hello', 'el', 'ip'); - * // => 'hippo' - * - * v.tr('légèreté', 'éè', 'ee'); - * // => 'legerete' - * - * v.tr('Yes. The fire rises.', { - * 'Yes': 'Awesome', - * 'fire': 'flame' - * }) - * // => 'Awesome. The flame rises.' - * - * v.tr(':where is the birthplace of :what', { - * ':where': 'Africa', - * ':what': 'Humanity' - * }); - * // => 'Africa is the birthplace of Humanity' - * - */ - - function tr(subject, from, to) { - var subjectString = coerceToString(subject); - var keys; - var values; - - if (isString(from) && isString(to)) { - keys = from.split(''); - values = to.split(''); - } else { - var _extractKeysAndValues = extractKeysAndValues(nilDefault(from, {})); - - var _extractKeysAndValues2 = _slicedToArray(_extractKeysAndValues, 2); - - keys = _extractKeysAndValues2[0]; - values = _extractKeysAndValues2[1]; - } - - var keysLength = keys.length; - - if (keysLength === 0) { - return subjectString; - } - - var result = ''; - var valuesLength = values.length; - - for (var index = 0; index < subjectString.length; index++) { - var isMatch = false; - var matchValue = void 0; - - for (var keyIndex = 0; keyIndex < keysLength && keyIndex < valuesLength; keyIndex++) { - var key = keys[keyIndex]; - - if (subjectString.substr(index, key.length) === key) { - isMatch = true; - matchValue = values[keyIndex]; - index = index + key.length - 1; - break; - } - } - - result += isMatch ? matchValue : subjectString[index]; - } - - return result; - } - - function extractKeysAndValues(object) { - var keys = Object.keys(object); - var values = keys.sort(sortStringByLength).map(function (key) { - return object[key]; - }); - return [keys, values]; - } - - function sortStringByLength(str1, str2) { - if (str1.length === str2.length) { - return 0; - } - - return str1.length < str2.length ? 1 : -1; - } - - /** - * Checks whether `subject` includes `search` starting from `position`. - * - * @function includes - * @static - * @since 1.0.0 - * @memberOf Query - * @param {string} [subject=''] The string where to search. - * @param {string} search The string to search. - * @param {number} [position=0] The position to start searching. - * @return {boolean} Returns `true` if `subject` includes `search` or `false` otherwise. - * @example - * v.includes('starship', 'star'); - * // => true - * - * v.includes('galaxy', 'g', 1); - * // => false - */ - - function includes(subject, search, position) { - var subjectString = coerceToString(subject); - var searchString = toString(search); - - if (searchString === null) { - return false; - } - - if (searchString === '') { - return true; - } - - position = isNil(position) ? 0 : clipNumber(toInteger(position), 0, subjectString.length); - return subjectString.indexOf(searchString, position) !== -1; - } - - var reduce$1 = Array.prototype.reduce; - /** - * Removes whitespaces from the left side of the `subject`. - * - * @function trimLeft - * @static - * @since 1.0.0 - * @memberOf Manipulate - * @param {string} [subject=''] The string to trim. - * @param {string} [whitespace=whitespace] The whitespace characters to trim. List all characters that you want to be stripped. - * @return {string} Returns the trimmed string. - * @example - * v.trimLeft(' Starship Troopers'); - * // => 'Starship Troopers' - * - * v.trimLeft('***Mobile Infantry', '*'); - * // => 'Mobile Infantry' - */ - - function trimLeft(subject, whitespace) { - var subjectString = coerceToString(subject); - - if (whitespace === '' || subjectString === '') { - return subjectString; - } - - var whitespaceString = toString(whitespace); - - if (isNil(whitespaceString)) { - return subjectString.replace(REGEXP_TRIM_LEFT, ''); - } - - var matchWhitespace = true; - return reduce$1.call(subjectString, function (trimmed, character) { - if (matchWhitespace && includes(whitespaceString, character)) { - return trimmed; - } - - matchWhitespace = false; - return trimmed + character; - }, ''); - } - - var reduceRight = Array.prototype.reduceRight; - /** - * Removes whitespaces from the right side of the `subject`. - * - * @function trimRight - * @static - * @since 1.0.0 - * @memberOf Manipulate - * @param {string} [subject=''] The string to trim. - * @param {string} [whitespace=whitespace] The whitespace characters to trim. List all characters that you want to be stripped. - * @return {string} Returns the trimmed string. - * @example - * v.trimRight('the fire rises '); - * // => 'the fire rises' - * - * v.trimRight('do you feel in charge?!!!', '!'); - * // => 'do you feel in charge?' - */ - - function trimRight(subject, whitespace) { - var subjectString = coerceToString(subject); - - if (whitespace === '' || subjectString === '') { - return subjectString; - } - - var whitespaceString = toString(whitespace); - - if (isNil(whitespaceString)) { - return subjectString.replace(REGEXP_TRIM_RIGHT, ''); - } - - var matchWhitespace = true; - return reduceRight.call(subjectString, function (trimmed, character) { - if (matchWhitespace && includes(whitespaceString, character)) { - return trimmed; - } - - matchWhitespace = false; - return character + trimmed; - }, ''); - } - - /** - * Removes whitespaces from left and right sides of the `subject`. - * - * @function trim - * @static - * @since 1.0.0 - * @memberOf Manipulate - * @param {string} [subject=''] The string to trim. - * @param {string} [whitespace=whitespace] The whitespace characters to trim. List all characters that you want to be stripped. - * @return {string} Returns the trimmed string. - * @example - * v.trim(' Mother nature '); - * // => 'Mother nature' - * - * v.trim('--Earth--', '-'); - * // => 'Earth' - */ - - function trim(subject, whitespace) { - var subjectString = coerceToString(subject); - - if (whitespace === '' || subjectString === '') { - return subjectString; - } - - var whitespaceString = toString(whitespace); - - if (isNil(whitespaceString)) { - return subjectString.trim(); - } - - return trimRight(trimLeft(subjectString, whitespaceString), whitespaceString); - } - - var OPTION_WIDTH = 'width'; - var OPTION_NEW_LINE = 'newLine'; - var OPTION_INDENT = 'indent'; - var OPTION_CUT = 'cut'; - /** - * Wraps `subject` to a given number of characters using a string break character. - * - * @function wordWrap - * @static - * @since 1.0.0 - * @memberOf Manipulate - * @param {string} [subject=''] The string to wrap. - * @param {Object} [options={}] The wrap options. - * @param {number} [options.width=75] The number of characters at which to wrap. - * @param {string} [options.newLine='\n'] The string to add at the end of line. - * @param {string} [options.indent=''] The string to intend the line. - * @param {boolean} [options.cut=false] When `false` (default) does not split the word even if word length is bigger than `width`.
- * When `true` breaks the word that has length bigger than `width`. - * - * @return {string} Returns wrapped string. - * @example - * v.wordWrap('Hello world', { - * width: 5 - * }); - * // => 'Hello\nworld' - * - * v.wordWrap('Hello world', { - * width: 5, - * newLine: '
', - * indent: '__' - * }); - * // => '__Hello
__world' - * - * v.wordWrap('Wonderful world', { - * width: 5, - * cut: true - * }); - * // => 'Wonde\nrful\nworld' - * - */ - - function wordWrap(subject) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - var subjectString = coerceToString(subject); - - var _determineOptions = determineOptions(options), - width = _determineOptions.width, - newLine = _determineOptions.newLine, - indent = _determineOptions.indent, - cut = _determineOptions.cut; - - if (subjectString === '' || width <= 0) { - return indent; - } - - var subjectLength = subjectString.length; - var substring = subjectString.substring.bind(subjectString); - var offset = 0; - var wrappedLine = ''; - - while (subjectLength - offset > width) { - if (subjectString[offset] === ' ') { - offset++; - continue; - } - - var spaceToWrapAt = subjectString.lastIndexOf(' ', width + offset); - - if (spaceToWrapAt >= offset) { - wrappedLine += indent + substring(offset, spaceToWrapAt) + newLine; - offset = spaceToWrapAt + 1; - } else { - if (cut) { - wrappedLine += indent + substring(offset, width + offset) + newLine; - offset += width; - } else { - spaceToWrapAt = subjectString.indexOf(' ', width + offset); - - if (spaceToWrapAt >= 0) { - wrappedLine += indent + substring(offset, spaceToWrapAt) + newLine; - offset = spaceToWrapAt + 1; - } else { - wrappedLine += indent + substring(offset); - offset = subjectLength; - } - } - } - } - - if (offset < subjectLength) { - wrappedLine += indent + substring(offset); - } - - return wrappedLine; - } - /** - * Determine the word wrap options. The missing values are filled with defaults. - * - * @param {Object} options The options object. - * @return {Object} The word wrap options, with default settings if necessary. - * @ignore - */ - - function determineOptions(options) { - return { - width: coerceToNumber(options[OPTION_WIDTH], 75), - newLine: coerceToString(options[OPTION_NEW_LINE], '\n'), - indent: coerceToString(options[OPTION_INDENT], ''), - cut: coerceToBoolean(options[OPTION_CUT], false) - }; - } - - /** - * Checks whether `subject` ends with `end`. - * - * @function endsWith - * @static - * @since 1.0.0 - * @memberOf Query - * @param {string} [subject=''] The string to verify. - * @param {string} end The ending string. - * @param {number} [position=subject.length] Search within `subject` as if the string were only `position` long. - * @return {boolean} Returns `true` if `subject` ends with `end` or `false` otherwise. - * @example - * v.endsWith('red alert', 'alert'); - * // => true - * - * v.endsWith('metro south', 'metro'); - * // => false - * - * v.endsWith('Murphy', 'ph', 5); - * // => true - */ - - function endsWith(subject, end, position) { - if (isNil(end)) { - return false; - } - - var subjectString = coerceToString(subject); - var endString = coerceToString(end); - - if (endString === '') { - return true; - } - - position = isNil(position) ? subjectString.length : clipNumber(toInteger(position), 0, subjectString.length); - position -= endString.length; - var lastIndex = subjectString.indexOf(endString, position); - return lastIndex !== -1 && lastIndex === position; - } - - /** - * Checks whether `subject` contains only alpha characters. - * - * @function isAlpha - * @static - * @since 1.0.0 - * @memberOf Query - * @param {string} [subject=''] The string to verify. - * @return {boolean} Returns `true` if `subject` contains only alpha characters or `false` otherwise. - * @example - * v.isAlpha('bart'); - * // => true - * - * v.isAlpha('lisa!'); - * // => false - * - * v.isAlpha('lisa and bart'); - * // => false - */ - - function isAlpha(subject) { - var subjectString = coerceToString(subject); - return REGEXP_ALPHA.test(subjectString); - } - - /** - * Checks whether `subject` contains only alpha and digit characters. - * - * @function isAlphaDigit - * @static - * @since 1.0.0 - * @memberOf Query - * @param {string} [subject=''] The string to verify. - * @return {boolean} Returns `true` if `subject` contains only alpha and digit characters or `false` otherwise. - * @example - * v.isAlphaDigit('year2020'); - * // => true - * - * v.isAlphaDigit('1448'); - * // => true - * - * v.isAlphaDigit('40-20'); - * // => false - */ - - function isAlphaDigit(subject) { - var subjectString = coerceToString(subject); - return REGEXP_ALPHA_DIGIT.test(subjectString); - } - - /** - * Checks whether `subject` is empty or contains only whitespaces. - * - * @function isBlank - * @static - * @since 1.0.0 - * @memberOf Query - * @param {string} [subject=''] The string to verify. - * @return {boolean} Returns `true` if `subject` is empty or contains only whitespaces or `false` otherwise. - * @example - * v.isBlank(''); - * // => true - * - * v.isBlank(' '); - * // => true - * - * v.isBlank('World'); - * // => false - */ - - function isBlank(subject) { - var subjectString = coerceToString(subject); - return subjectString.trim().length === 0; - } - - /** - * Checks whether `subject` contains only digit characters. - * - * @function isDigit - * @static - * @since 1.0.0 - * @memberOf Query - * @param {string} [subject=''] The string to verify. - * @return {boolean} Returns `true` if `subject` contains only digit characters or `false` otherwise. - * @example - * v.isDigit('35'); - * // => true - * - * v.isDigit('1.5'); - * // => false - * - * v.isDigit('ten'); - * // => false - */ - - function isDigit(subject) { - var subjectString = coerceToString(subject); - return REGEXP_DIGIT.test(subjectString); - } - - /** - * Checks whether `subject` is empty. - * - * @function isEmpty - * @static - * @since 1.0.0 - * @memberOf Query - * @param {string} [subject=''] The string to verify. - * @return {boolean} Returns `true` if `subject` is empty or `false` otherwise - * @example - * v.isEmpty(''); - * // => true - * - * v.isEmpty(' '); - * // => false - * - * v.isEmpty('sun'); - * // => false - */ - - function isEmpty(subject) { - var subjectString = coerceToString(subject); - return subjectString.length === 0; - } - - /** - * Checks whether `subject` has only lower case characters. - * - * @function isLowerCase - * @static - * @since 1.0.0 - * @memberOf Query - * @param {string} [subject=''] The string to verify. - * @return {boolean} Returns `true` if `subject` is lower case or `false` otherwise. - * @example - * v.isLowerCase('motorcycle'); - * // => true - * - * v.isLowerCase('John'); - * // => false - * - * v.isLowerCase('T1000'); - * // => false - */ - - function isLowerCase(subject) { - var valueString = coerceToString(subject); - return isAlpha(valueString) && valueString.toLowerCase() === valueString; - } - - /** - * Checks whether `subject` is numeric. - * - * @function isNumeric - * @static - * @since 1.0.0 - * @memberOf Query - * @param {string} [subject=''] The string to verify. - * @return {boolean} Returns `true` if `subject` is numeric or `false` otherwise. - * @example - * v.isNumeric('350'); - * // => true - * - * v.isNumeric('-20.5'); - * // => true - * - * v.isNumeric('1.5E+2'); - * // => true - * - * v.isNumeric('five'); - * // => false - */ - - function isNumeric(subject) { - var valueNumeric = typeof subject === 'object' && !isNil(subject) ? Number(subject) : subject; - return (typeof valueNumeric === 'number' || typeof valueNumeric === 'string') && !isNaN(valueNumeric - parseFloat(valueNumeric)); - } - - /** - * Checks whether `subject` contains only upper case characters. - * - * @function isUpperCase - * @static - * @since 1.0.0 - * @memberOf Query - * @param {string} [subject=''] The string to verify. - * @return {boolean} Returns `true` if `subject` is upper case or `false` otherwise. - * @example - * v.isUpperCase('ACDC'); - * // => true - * - * v.isUpperCase('Morning'); - * // => false - */ - - function isUpperCase(subject) { - var subjectString = coerceToString(subject); - return isAlpha(subjectString) && subjectString.toUpperCase() === subjectString; - } - - /** - * Checks whether `subject` matches the regular expression `pattern`. - * - * @function matches - * @static - * @since 1.0.0 - * @memberOf Query - * @param {string} [subject=''] The string to verify. - * @param {RegExp|string} pattern The pattern to match. 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 {boolean} Returns `true` if `subject` matches `pattern` or `false` otherwise. - * @example - * v.matches('pluto', /plu.{2}/); - * // => true - * - * v.matches('sun', 'S', 'i'); - * // => true - * - * v.matches('apollo 11', '\\d{3}'); - * // => false - */ - - function matches(subject, pattern, flags) { - var subjectString = coerceToString(subject); - var flagsString = coerceToString(flags); - var patternString; - - if (!(pattern instanceof RegExp)) { - patternString = toString(pattern); - - if (patternString === null) { - return false; - } - - pattern = new RegExp(patternString, flagsString); - } - - return pattern.test(subjectString); - } - - /** - * Checks whether `subject` starts with `start`. - * - * @function startsWith - * @static - * @since 1.0.0 - * @memberOf Query - * @param {string} [subject=''] The string to verify. - * @param {string} start The starting string. - * @param {number} [position=0] The position to start searching. - * @return {boolean} Returns `true` if `subject` starts with `start` or `false` otherwise. - * @example - * v.startsWith('say hello to my little friend', 'say hello'); - * // => true - * - * v.startsWith('tony', 'on', 1); - * // => true - * - * v.startsWith('the world is yours', 'world'); - * // => false - */ - - function startsWith(subject, start, position) { - var subjectString = coerceToString(subject); - var startString = toString(start); - - if (startString === null) { - return false; - } - - if (startString === '') { - return true; - } - - position = isNil(position) ? 0 : clipNumber(toInteger(position), 0, subjectString.length); - return subjectString.substr(position, startString.length) === startString; - } - - /** - * Splits `subject` into an array of characters. - * - * @function chars - * @static - * @since 1.0.0 - * @memberOf Split - * @param {string} [subject=''] The string to split into characters. - * @return {Array} Returns the array of characters. - * @example - * v.chars('cloud'); - * // => ['c', 'l', 'o', 'u', 'd'] - */ - - function chars(subject) { - var subjectString = coerceToString(subject); - return subjectString.split(''); - } - - /** - * Returns an array of Unicode code point values from characters of `subject`. - * - * @function codePoints - * @static - * @since 1.0.0 - * @memberOf Split - * @param {string} [subject=''] The string to extract from. - * @return {Array} Returns an array of non-negative numbers less than or equal to `0x10FFFF`. - * @example - * v.codePoints('rain'); - * // => [114, 97, 105, 110], or - * // [0x72, 0x61, 0x69, 0x6E] - * - * v.codePoints('\uD83D\uDE00 smile'); // or '😀 smile' - * // => [128512, 32, 115, 109, 105, 108, 101], or - * // [0x1F600, 0x20, 0x73, 0x6D, 0x69, 0x6C, 0x65] - */ - - function codePoints(subject) { - var subjectString = coerceToString(subject); - var subjectStringLength = subjectString.length; - var codePointArray = []; - var index = 0; - var codePointNumber; - - while (index < subjectStringLength) { - codePointNumber = codePointAt(subjectString, index); - codePointArray.push(codePointNumber); - index += codePointNumber > 0xffff ? 2 : 1; - } - - return codePointArray; - } - - /** - * Splits `subject` into an array of graphemes taking care of - * surrogate pairs and - * combining marks. - * - * @function graphemes - * @static - * @since 1.0.0 - * @memberOf Split - * @param {string} [subject=''] The string to split into characters. - * @return {Array} Returns the array of graphemes. - * @example - * v.graphemes('\uD835\uDC00\uD835\uDC01'); // or '𝐀𝐁' - * // => ['\uD835\uDC00', '\uD835\uDC01'], or - * // ['𝐀', '𝐁'] - * - * v.graphemes('cafe\u0301'); // or 'café' - * // => ['c', 'a', 'f', 'e\u0301'], or - * // ['c', 'a', 'f', 'é'] - */ - - function graphemes(subject) { - var subjectString = coerceToString(subject); - return nilDefault(subjectString.match(REGEXP_UNICODE_CHARACTER), []); - } - - /** - * Splits `subject` into an array of chunks by `separator`. - * - * @function split - * @static - * @since 1.0.0 - * @memberOf Split - * @param {string} [subject=''] The string to split into characters. - * @param {string|RegExp} [separator] The pattern to match the separator. - * @param {number} [limit] Limit the number of chunks to be found. - * @return {Array} Returns the array of chunks. - * @example - * v.split('rage against the dying of the light', ' '); - * // => ['rage', 'against', 'the', 'dying', 'of', 'the', 'light'] - * - * v.split('the dying of the light', /\s/, 3); - * // => ['the', 'dying', 'of'] - */ - - function split(subject, separator, limit) { - var subjectString = coerceToString(subject); - return subjectString.split(separator, limit); - } - - var BYRE_ORDER_MARK = '\uFEFF'; - /** - * Strips the byte order mark (BOM) from the beginning of `subject`. - * - * @function stripBom - * @static - * @since 1.2.0 - * @memberOf Strip - * @param {string} [subject=''] The string to strip from. - * @return {string} Returns the stripped string. - * @example - * - * v.stripBom('\uFEFFsummertime sadness'); - * // => 'summertime sadness' - * - * v.stripBom('summertime happiness'); - * // => 'summertime happiness' - * - */ - - function trim$1(subject) { - var subjectString = coerceToString(subject); - - if (subjectString === '') { - return ''; - } - - if (subjectString[0] === BYRE_ORDER_MARK) { - return subjectString.substring(1); - } - - return subjectString; - } - - /** - * Checks whether `subject` contains substring at specific `index`. - * - * @ignore - * @param {string} subject The subject to search in. - * @param {string} substring The substring to search/ - * @param {number} index The index to search substring. - * @param {boolean} lookBehind Whether to look behind (true) or ahead (false). - * @return {boolean} Returns a boolean whether the substring exists. - */ - function hasSubstringAtIndex(subject, substring, index) { - var lookBehind = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; - var indexOffset = 0; - - if (lookBehind) { - indexOffset = -substring.length + 1; - } - - var extractedSubstring = subject.substr(index + indexOffset, substring.length); - return extractedSubstring.toLowerCase() === substring; - } - - /** - * Parses the tags from the string '...'. - * - * @ignore - * @param {string} tags The string that contains the tags. - * @return {string[]} Returns the array of tag names. - */ - - function parseTagList(tags) { - var tagsList = []; - var match; - - while ((match = REGEXP_TAG_LIST.exec(tags)) !== null) { - tagsList.push(match[1]); - } - - return tagsList; - } - - var STATE_START_TAG = 0; - var STATE_NON_WHITESPACE = 1; - var STATE_DONE = 2; - /** - * Parses the tag name from html content. - * - * @ignore - * @param {string} tagContent The tag content. - * @return {string} Returns the tag name. - */ - - function parseTagName(tagContent) { - var state = STATE_START_TAG; - var tagName = ''; - var index = 0; - - while (state !== STATE_DONE) { - var char = tagContent[index++].toLowerCase(); - - switch (char) { - case '<': - break; - - case '>': - state = STATE_DONE; - break; - - default: - if (REGEXP_WHITESPACE.test(char)) { - if (state === STATE_NON_WHITESPACE) { - state = STATE_DONE; - } - } else { - if (state === STATE_START_TAG) { - state = STATE_NON_WHITESPACE; - } - - if (char !== '/') { - tagName += char; - } - } - - break; - } - } - - return tagName; - } - - var STATE_OUTPUT = 0; - var STATE_HTML = 1; - var STATE_EXCLAMATION = 2; - var STATE_COMMENT = 3; - /** - * Strips HTML tags from `subject`. - * - * @function stripTags - * @static - * @since 1.1.0 - * @memberOf Strip - * @param {string} [subject=''] The string to strip from. - * @param {string|Array} [allowableTags] The string `''` or array `['tag1', 'tag2']` of tags that should not be stripped. - * @param {string} [replacement=''] The string to replace the stripped tag. - * @return {string} Returns the stripped string. - * @example - * - * v.stripTags('Summer is nice'); - * // => 'Summer is nice' - * - * v.stripTags('Winter is cold', ['b', 'i']); - * // => 'Winter is cold' - * - * v.stripTags('Sun
set', '', '-'); - * // => 'Sun-set' - */ - - function trim$2(subject, allowableTags, replacement) { - subject = coerceToString(subject); - - if (subject === '') { - return ''; - } - - if (!Array.isArray(allowableTags)) { - var allowableTagsString = coerceToString(allowableTags); - allowableTags = allowableTagsString === '' ? [] : parseTagList(allowableTagsString); - } - - var replacementString = coerceToString(replacement); - var length = subject.length; - var hasAllowableTags = allowableTags.length > 0; - var hasSubstring = hasSubstringAtIndex.bind(null, subject); - var state = STATE_OUTPUT; - var depth = 0; - var output = ''; - var tagContent = ''; - var quote = null; - - for (var index = 0; index < length; index++) { - var char = subject[index]; - var advance = false; - - switch (char) { - case '<': - if (quote) { - break; - } - - if (hasSubstring('< ', index, false)) { - advance = true; - break; - } - - if (state === STATE_OUTPUT) { - advance = true; - state = STATE_HTML; - break; - } - - if (state === STATE_HTML) { - depth++; - break; - } - - advance = true; - break; - - case '!': - if (state === STATE_HTML && hasSubstring('': - if (depth > 0) { - depth--; - break; - } - - if (quote) { - break; - } - - if (state === STATE_HTML) { - quote = null; - state = STATE_OUTPUT; - - if (hasAllowableTags) { - tagContent += '>'; - var tagName = parseTagName(tagContent); - - if (allowableTags.indexOf(tagName.toLowerCase()) !== -1) { - output += tagContent; - } else { - output += replacementString; - } - - tagContent = ''; - } else { - output += replacementString; - } - - break; - } - - if (state === STATE_EXCLAMATION || state === STATE_COMMENT && hasSubstring('-->', index)) { - quote = null; - state = STATE_OUTPUT; - tagContent = ''; - break; - } - - advance = true; - break; - - default: - advance = true; - } - - if (advance) { - switch (state) { - case STATE_OUTPUT: - output += char; - break; - - case STATE_HTML: - if (hasAllowableTags) { - tagContent += char; - } - - break; - } - } - } - - return output; - } - - var globalObject = null; - - function getGlobalObject() { - if (globalObject !== null) { - return globalObject; - } - /* istanbul ignore next */ - // It's hard to mock the global variables. This code surely works fine. I hope :) - - - if (typeof global === 'object' && global.Object === Object) { - // NodeJS global object - globalObject = global; - } else if (typeof self === 'object' && self.Object === Object) { - // self property from Window object - globalObject = self; - } else { - // Other cases. Function constructor always has the context as global object - globalObject = new Function('return this')(); - } - - return globalObject; - } - - var globalObject$1 = getGlobalObject(); - var previousV = globalObject$1.v; - /** - * Restores `v` variable to previous value and returns Voca library instance. - * - * @function noConflict - * @static - * @since 1.0.0 - * @memberOf Util - * @return {Object} Returns Voca library instance. - * @example - * var voca = v.noConflict(); - * voca.isAlpha('Hello'); - * // => true - */ - - function noConflict() { - if (this === globalObject$1.v) { - globalObject$1.v = previousV; - } - - return this; - } - - /** - * A property that contains the library semantic version number. - * @name version - * @static - * @since 1.0.0 - * @memberOf Util - * @type string - * @example - * v.version - * // => '1.4.0' - */ - var version = '1.4.0'; - - /* eslint sort-imports: "off" */ - var functions = { - camelCase: camelCase, - capitalize: capitalize, - decapitalize: decapitalize, - kebabCase: kebabCase, - lowerCase: lowerCase, - snakeCase: snakeCase, - swapCase: swapCase, - titleCase: titleCase, - upperCase: upperCase, - count: count, - countGraphemes: countGrapheme, - countSubstrings: countSubstrings, - countWhere: countWhere, - countWords: countWords, - escapeHtml: escapeHtml, - escapeRegExp: escapeRegExp, - unescapeHtml: unescapeHtml, - sprintf: sprintf, - vprintf: vprintf, - indexOf: indexOf, - lastIndexOf: lastIndexOf, - search: search, - charAt: charAt, - codePointAt: codePointAt, - first: first, - graphemeAt: graphemeAt, - last: last, - prune: prune, - slice: slice, - substr: substr, - substring: substring, - truncate: truncate, - insert: insert, - latinise: latinise, - pad: pad, - padLeft: padLeft, - padRight: padRight, - repeat: repeat, - replace: replace, - replaceAll: replaceAll, - reverse: reverse, - reverseGrapheme: reverseGrapheme, - slugify: slugify, - splice: splice, - tr: tr, - trim: trim, - trimLeft: trimLeft, - trimRight: trimRight, - wordWrap: wordWrap, - endsWith: endsWith, - includes: includes, - isAlpha: isAlpha, - isAlphaDigit: isAlphaDigit, - isBlank: isBlank, - isDigit: isDigit, - isEmpty: isEmpty, - isLowerCase: isLowerCase, - isNumeric: isNumeric, - isString: isString, - isUpperCase: isUpperCase, - matches: matches, - startsWith: startsWith, - chars: chars, - codePoints: codePoints, - graphemes: graphemes, - split: split, - words: words, - stripBom: trim$1, - stripTags: trim$2, - noConflict: noConflict, - version: version - }; - - /** - * The chain wrapper constructor. - * - * @ignore - * @param {string} subject The string to be wrapped. - * @param {boolean} [explicitChain=false] A boolean that indicates if the chain sequence is explicit or implicit. - * @return {ChainWrapper} Returns a new instance of `ChainWrapper` - * @constructor - */ - - function ChainWrapper(subject, explicitChain) { - this._wrappedValue = subject; - this._explicitChain = explicitChain; - } - /** - * Unwraps the chain sequence wrapped value. - * - * @memberof Chain - * @since 1.0.0 - * @function __proto__value - * @return {*} Returns the unwrapped value. - * @example - * v - * .chain('Hello world') - * .replace('Hello', 'Hi') - * .lowerCase() - * .slugify() - * .value() - * // => 'hi-world' - * - * v(' Space travel ') - * .trim() - * .truncate(8) - * .value() - * // => 'Space...' - */ - - - ChainWrapper.prototype.value = function () { - return this._wrappedValue; - }; - /** - * Override the default object valueOf(). - * - * @ignore - * @return {*} Returns the wrapped value. - */ - - - ChainWrapper.prototype.valueOf = function () { - return this.value(); - }; - /** - * Returns the wrapped value to be used in JSON.stringify(). - * - * @ignore - * @return {*} Returns the wrapped value. - */ - - - ChainWrapper.prototype.toJSON = function () { - return this.value(); - }; - /** - * Returns the string representation of the wrapped value. - * - * @ignore - * @return {string} Returns the string representation. - */ - - - ChainWrapper.prototype.toString = function () { - return String(this.value()); - }; - /** - * Creates a new chain object that enables explicit chain sequences. - * Use `v.prototype.value()` to unwrap the result.
- * Does not modify the wrapped value. - * - * @memberof Chain - * @since 1.0.0 - * @function __proto__chain - * @return {Object} Returns the wrapper in explicit mode. - * @example - * v('Back to School') - * .chain() - * .lowerCase() - * .words() - * .value() - * // => ['back', 'to', 'school'] - * - * v(" Back to School ") - * .chain() - * .trim() - * .truncate(7) - * .value() - * // => 'Back...' - */ - - - ChainWrapper.prototype.chain = function () { - return new ChainWrapper(this._wrappedValue, true); - }; - /** - * Modifies the wrapped value with the invocation result of `changer` function. The current wrapped value is the - * argument of `changer` invocation. - * - * @memberof Chain - * @since 1.0.0 - * @function __proto__thru - * @param {Function} changer The function to invoke. - * @return {Object} Returns the new wrapper that wraps the invocation result of `changer`. - * @example - * v - * .chain('sun is shining') - * .words() - * .thru(function(words) { - * return words[0]; - * }) - * .value() - * // => 'sun' - * - */ - - - ChainWrapper.prototype.thru = function (changer) { - if (typeof changer === 'function') { - return new ChainWrapper(changer(this._wrappedValue), this._explicitChain); - } - - return this; - }; - /** - * A boolean that indicates if the chain sequence is explicit or implicit. - * @ignore - * @type {boolean} - * @private - */ - - - ChainWrapper.prototype._explicitChain = true; - /** - * Make a voca function chainable. - * - * @ignore - * @param {Function} functionInstance The function to make chainable - * @return {Function} Returns the chainable function - */ - - function makeFunctionChainable(functionInstance) { - return function () { - for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { - args[_key] = arguments[_key]; - } - - var result = functionInstance.apply(void 0, [this._wrappedValue].concat(args)); - - if (this._explicitChain || typeof result === 'string') { - return new ChainWrapper(result, this._explicitChain); - } else { - return result; - } - }; - } - - Object.keys(functions).forEach(function (name) { - ChainWrapper.prototype[name] = makeFunctionChainable(functions[name]); - }); - - /** - * Creates a chain object that wraps `subject`, enabling explicit chain sequences.
- * Use `v.prototype.value()` to unwrap the result. - * - * @memberOf Chain - * @since 1.0.0 - * @function chain - * @param {string} subject The string to wrap. - * @return {Object} Returns the new wrapper object. - * @example - * v - * .chain('Back to School') - * .lowerCase() - * .words() - * .value() - * // => ['back', 'to', 'school'] - */ - - function chain(subject) { - return new ChainWrapper(subject, true); - } - - /** - * Creates a chain object that wraps `subject`, enabling implicit chain sequences.
- * A function that returns `number`, `boolean` or `array` type terminates the chain sequence and returns the unwrapped value. - * Otherwise use `v.prototype.value()` to unwrap the result. - * - * @memberOf Chain - * @since 1.0.0 - * @function v - * @param {string} subject The string to wrap. - * @return {Object} Returns the new wrapper object. - * @example - * v('Back to School') - * .lowerCase() - * .words() - * // => ['back', 'to', 'school'] - * - * v(" Back to School ") - * .trim() - * .truncate(7) - * .value() - * // => 'Back...' - */ - - function Voca(subject) { - return new ChainWrapper(subject, false); - } - - _extends(Voca, functions, { - chain: chain - }); - - return Voca; - -}))); - - /***/ }), /***/ 3766: @@ -66333,12 +62036,9 @@ function packageJsonBase(options) { /***/ }), /***/ 1490: -/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.getCopyrightHeader = getCopyrightHeader; exports.validateNpmCompliance = validateNpmCompliance; @@ -66346,7 +62046,6 @@ exports.npmCompliantName = npmCompliantName; exports.directoryToSpeakingModuleName = directoryToSpeakingModuleName; exports.directoryToServiceName = directoryToServiceName; const util_1 = __nccwpck_require__(2343); -const voca_1 = __importDefault(__nccwpck_require__(5231)); /** * @returns A copyright header * @internal @@ -66412,7 +62111,7 @@ function transformUnscopedName(packageName) { * @internal */ function directoryToSpeakingModuleName(packageName) { - return voca_1.default.titleCase(packageName.replace(/[-,_]/g, ' ')); + return (0, util_1.titleFormat)(packageName.replace(/[-,_]/g, ' ')); } /** * This is taken from version 2.0 @@ -67636,21 +63335,20 @@ function removeLeadingSlashes(path) { /***/ }), /***/ 7841: -/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { +/***/ ((__unused_webpack_module, exports) => { -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.webEOL = exports.unixEOL = void 0; +exports.capitalize = capitalize; +exports.decapitalize = decapitalize; exports.upperCaseSnakeCase = upperCaseSnakeCase; exports.camelCase = camelCase; exports.titleFormat = titleFormat; exports.pascalCase = pascalCase; exports.kebabCase = kebabCase; +exports.titleCase = titleCase; exports.formatJson = formatJson; -const voca_1 = __importDefault(__nccwpck_require__(5231)); /** * Within all files generated by the SDK we use the unix style end of line delimiter. * We do not consider if the generator is executed on windows or unix systems. @@ -67663,13 +63361,108 @@ exports.unixEOL = '\n'; * @deprecated Since v4.6.0. Use '\r\n' directly instead. */ exports.webEOL = '\r\n'; +// Uses Unicode property escapes to correctly classify non-ASCII letters and digits +// (e.g. accented uppercase Ü, Arabic-Indic digits ٣). Caseless letters (CJK, Arabic, +// titlecase like Dž) are treated as lowercase so they are included in words rather than dropped. +function charKind(c) { + if (/^\p{Lu}$/u.test(c)) + return 'upper'; + if (/^\p{L}$/u.test(c)) + return 'lower'; // covers Ll, Lt, Lm, Lo + if (/^\p{N}$/u.test(c)) + return 'digit'; + return 'separator'; +} +// Splits an identifier string into words, handling common case conventions: +// camelCase → ['camel', 'Case'] +// XMLParser → ['XML', 'Parser'] +// field_name → ['field', 'name'] +// Field13Name → ['Field', '13', 'Name'] +function words(str) { + const result = []; + let word = ''; + let state = 'none'; + const pushWord = () => { + if (word) { + result.push(word); + } + word = ''; + }; + for (const char of str) { + const kind = charKind(char); + if (state === 'none' || state === 'separator') { + if (kind !== 'separator') { + word = char; + state = kind; + } + } + else if (kind === 'separator') { + pushWord(); + state = 'separator'; + } + else if (kind === 'lower' && state === 'upper') { + // Transition into a lowercase run: if we accumulated multiple uppers, + // the last one belongs to this new word (e.g. XMLParser → XML + Parser) + if (word.length > 1) { + const last = word.slice(-1); + word = word.slice(0, -1); + pushWord(); + word = last; + } + word += char; + state = 'lower'; + } + else if (kind !== state) { + // Any other kind change (lower→digit, digit→upper, etc.) starts a new word + pushWord(); + word = char; + state = kind; + } + else { + word += char; + } + } + pushWord(); + return result; +} +// Applies transforms to the first Unicode code point and the remainder. +// Uses codePointAt to correctly handle surrogate pairs (e.g. emoji, some CJK). +function transformInitialLetter(str, headTransform, tailTransform = s => s) { + if (!str.length) { + return str; + } + // A surrogate pair occupies 2 UTF-16 code units; all BMP chars occupy 1. + const firstCharLength = str.codePointAt(0) > 0xffff ? 2 : 1; + return (headTransform(str.slice(0, firstCharLength)) + + tailTransform(str.slice(firstCharLength))); +} +/** + * Uppercase the first character of a string, leaving the rest unchanged. + * @param str - The string to capitalize. + * @returns The string with the first character uppercased. + */ +function capitalize(str) { + return transformInitialLetter(str, c => c.toUpperCase()); +} +/** + * Lowercase the first character of a string, leaving the rest unchanged. + * @param str - The string to decapitalize. + * @returns The string with the first character lowercased. + * @internal + */ +function decapitalize(str) { + return transformInitialLetter(str, c => c.toLowerCase()); +} +function capitalizeWord(str) { + return transformInitialLetter(str, c => c.toUpperCase(), s => s.toLowerCase()); +} /** * Convert a string to the uppercase snake case. This format is used e.g. for static properties on entity classes. * @param str - The string to be transformed. * @returns The input string in the case used by static methods on entity-classes. */ function upperCaseSnakeCase(str) { - return voca_1.default.upperCase(voca_1.default.snakeCase(str)); + return words(str).join('_').toUpperCase(); } /** * Convert a string to camelCase. This format used e.g. for properties on entity class instances. @@ -67677,7 +63470,11 @@ function upperCaseSnakeCase(str) { * @returns The transformed string. */ function camelCase(str) { - return voca_1.default.camelCase(str); + const parts = words(str); + if (!parts.length) { + return ''; + } + return parts[0].toLowerCase() + parts.slice(1).map(capitalizeWord).join(''); } /** * Convert a string to a human readable format, e.g. it transforms `to_BusinessPartner` to `To Business Partner`. @@ -67685,7 +63482,7 @@ function camelCase(str) { * @returns The transformed string. */ function titleFormat(str) { - return voca_1.default.titleCase(voca_1.default.words(str).join(' ')); + return words(str).map(capitalizeWord).join(' '); } /** * Convert a string to pascal case. This format is used e.g. for types. @@ -67693,10 +63490,7 @@ function titleFormat(str) { * @returns The transformed string. */ function pascalCase(str) { - return voca_1.default - .words(str) - .map(word => voca_1.default.capitalize(word)) - .join(''); + return words(str).map(capitalizeWord).join(''); } /** * Convert a string to kebab case. This format is used e.g. for file names. @@ -67704,7 +63498,16 @@ function pascalCase(str) { * @returns The transformed string. */ function kebabCase(str) { - return voca_1.default.kebabCase(str); + return words(str).join('-').toLowerCase(); +} +/** + * Uppercase the first character after each word boundary, leaving separators intact. + * E.g. `content-type` → `Content-Type`, `if-match` → `If-Match`. + * @param str - The string to transform. + * @returns The string with each word's first character uppercased. + */ +function titleCase(str) { + return str.replace(/\b\w/g, c => c.toUpperCase()); } /** * Convert a JSON object to a string using formatting in line with the prettier with indentation and new line at the end. diff --git a/packages/connectivity/package.json b/packages/connectivity/package.json index c61436c3d8..1b1616c038 100644 --- a/packages/connectivity/package.json +++ b/packages/connectivity/package.json @@ -44,10 +44,9 @@ "@sap-cloud-sdk/util": "workspace:^", "@sap/xsenv": "^6.2.0", "@sap/xssec": "^4.13.0", - "async-retry": "^1.3.3", "axios": "^1.15.0", "jks-js": "^1.1.6", - "jsonwebtoken": "^9.0.3", + "jwt-decode": "^4.0.0", "safe-stable-stringify": "^2.5.0" }, "devDependencies": { diff --git a/packages/connectivity/src/scp-cf/destination/destination-accessor-failure-cases.spec.ts b/packages/connectivity/src/scp-cf/destination/destination-accessor-failure-cases.spec.ts index 437d2ba53e..ee21368d08 100644 --- a/packages/connectivity/src/scp-cf/destination/destination-accessor-failure-cases.spec.ts +++ b/packages/connectivity/src/scp-cf/destination/destination-accessor-failure-cases.spec.ts @@ -51,9 +51,10 @@ describe('Failure cases', () => { jwt: 'fails', cacheVerificationKeys: false }) - ).rejects.toThrowErrorMatchingInlineSnapshot( - '"JwtError: The given jwt payload does not encode valid JSON."' - ); + ).rejects.toThrowErrorMatchingInlineSnapshot(` +"JwtError: The given jwt payload does not encode valid JSON. +Cause: Invalid JWT format." +`); }); it('throws an error if the subaccount/instance destinations call fails', async () => { diff --git a/packages/connectivity/src/scp-cf/destination/destination-service.spec.ts b/packages/connectivity/src/scp-cf/destination/destination-service.spec.ts index 6101921d95..93870e2ad1 100644 --- a/packages/connectivity/src/scp-cf/destination/destination-service.spec.ts +++ b/packages/connectivity/src/scp-cf/destination/destination-service.spec.ts @@ -355,6 +355,7 @@ describe('destination service', () => { describe('fetchDestinationByToken', () => { afterEach(() => { + jest.useRealTimers(); jest.restoreAllMocks(); }); @@ -619,7 +620,22 @@ describe('destination service', () => { } ); expect(actual).toEqual(parseDestination(response)); - }); + }, 10000); + + it('stops retrying after the configured number of attempts for 500 errors', async () => { + const mock = nock(destinationServiceUri) + .get('/destination-configuration/v1/destinations/HTTP-BASIC') + .times(3) + .reply(500); + + await expect( + fetchDestinationWithTokenRetrieval(destinationServiceUri, jwt, { + destinationName: 'HTTP-BASIC', + retry: true + }) + ).rejects.toThrow(); + expect(mock.isDone()).toBe(true); + }, 15000); it('does no retry if request fails with 401 error', async () => { const response = { @@ -688,7 +704,7 @@ describe('destination service', () => { } ); expect(actual).toMatchObject(parseDestination(responseValidToken)); - }); + }, 10000); it('does a retry if auth tokens are failing but returns the destination with errors in the end', async () => { const response = { @@ -723,7 +739,7 @@ describe('destination service', () => { ); expect(actual.authTokens![0].error).toEqual('ERROR'); expect(mock.isDone()).toBe(true); - }, 10000); + }, 15000); it('fetches a destination and returns 200 but authTokens are failing', async () => { const destinationName = 'FINAL-DESTINATION'; diff --git a/packages/connectivity/src/scp-cf/destination/destination-service.ts b/packages/connectivity/src/scp-cf/destination/destination-service.ts index c6f007e269..0dcfbbc1a9 100644 --- a/packages/connectivity/src/scp-cf/destination/destination-service.ts +++ b/packages/connectivity/src/scp-cf/destination/destination-service.ts @@ -7,7 +7,6 @@ import { import axios from 'axios'; import { executeWithMiddleware } from '@sap-cloud-sdk/resilience/internal'; import { resilience } from '@sap-cloud-sdk/resilience'; -import asyncRetry from 'async-retry'; import { decodeJwt, getTenantId, wrapJwtInHeader } from '../jwt'; import { urlAndAgent } from '../../http-agent'; import { buildAuthorizationHeaders } from '../authorization-header'; @@ -303,6 +302,58 @@ function errorMessageFromResponse( : ''; } +/** + * @internal + * Retries a function with exponential backoff. + * @param fn - The function to retry. + * @param options - Options for retrying. + * @param options.retries - The maximum number of retries. Default is 3. + * @param options.onRetry - A callback function that is called before each retry with the error and the current attempt number. + * @param options.randomize - Whether to randomize the backoff time by a factor of 1-2. Default is true. + * @returns The result of the function. + */ +const sleep = (ms: number): Promise => + new Promise(resolve => setTimeout(resolve, ms)); + +async function withRetry( + fn: (bail: (err: Error) => never, attempt: number) => Promise, + options: { + retries?: number; + onRetry?: (err: Error, attempt: number) => void; + randomize?: boolean; + } = {} +): Promise { + const maxRetries = options.retries ?? 3; + const randomize = Number(options.randomize ?? true); + + class BailError extends Error { + constructor(readonly cause: Error) { + super(cause.message); + } + } + + for (let attempt = 0; attempt < maxRetries - 1; attempt++) { + try { + return await fn(err => { + throw new BailError(err); + }, attempt); + } catch (error) { + if (error instanceof BailError) { + throw error.cause; + } + options.onRetry?.(error as Error, attempt + 1); + // Exponential backoff with optional randomization (factor 1-2x) + const scalingFactor = 1 + randomize * Math.random(); + const backoff = Math.round(scalingFactor * 1000 * 2 ** attempt); + await sleep(backoff); + } + } + + return fn(err => { + throw new BailError(err); + }, maxRetries - 1); +} + function retryDestination( destinationName: string ): Middleware< @@ -311,20 +362,21 @@ function retryDestination( MiddlewareContext > { return options => arg => { - let retryCount = 1; - return asyncRetry( - async bail => { + const maxRetries = 3; + return withRetry( + async (bail, attempt) => { try { const destination = await options.fn(arg); - if (retryCount < 3) { - retryCount++; + if (attempt < maxRetries - 1) { // this will throw if the destination does not contain valid auth headers and a second try is done to get a destination with valid tokens. await buildAuthorizationHeaders(parseDestination(destination.data)); } return destination; } catch (error) { - const status = error?.response?.status; - if (status.toString().startsWith('4')) { + const status = axios.isAxiosError(error) + ? error.response?.status + : undefined; + if (status?.toString().startsWith('4')) { bail( new ErrorWithCause( `Request failed with status code ${status}`, @@ -338,7 +390,7 @@ function retryDestination( } }, { - retries: 3, + retries: maxRetries, onRetry: (err: Error) => logger.warn( `Failed to retrieve destination ${destinationName} - doing a retry. Original Error ${err.message}` diff --git a/packages/connectivity/src/scp-cf/jwt/jwt.ts b/packages/connectivity/src/scp-cf/jwt/jwt.ts index 069f415db9..35899e56c9 100644 --- a/packages/connectivity/src/scp-cf/jwt/jwt.ts +++ b/packages/connectivity/src/scp-cf/jwt/jwt.ts @@ -1,9 +1,9 @@ import { createLogger, pickValueIgnoreCase } from '@sap-cloud-sdk/util'; -import { decode } from 'jsonwebtoken'; +import { jwtDecode } from 'jwt-decode'; import { Cache } from '../cache'; import { getIssuerSubdomain } from '../subdomain-replacer'; import type { - Jwt, + JwtHeader, JwtPayload, JwtWithPayloadObject } from '../jsonwebtoken-type'; @@ -136,7 +136,7 @@ function audiencesFromAud({ aud }: JwtPayload): string[] { } function audiencesFromScope({ scope }: JwtPayload): string[] { - return makeArray(scope).reduce( + return makeArray(scope).reduce( (aud, s) => (s.includes('.') ? [...aud, s.split('.')[0]] : aud), [] ); @@ -148,7 +148,20 @@ function audiencesFromScope({ scope }: JwtPayload): string[] { * @returns Decoded payload. */ export function decodeJwt(token: string | JwtPayload): JwtPayload { - return typeof token === 'string' ? decodeJwtComplete(token).payload : token; + if (typeof token !== 'string') { + return token; + } + try { + validateJwtFormat(token); + return decodeJwtPart(token); + } catch (error) { + throw new Error( + 'JwtError: The given jwt payload does not encode valid JSON.', + { + cause: error + } + ); + } } /** @@ -158,13 +171,21 @@ export function decodeJwt(token: string | JwtPayload): JwtPayload { * @internal */ export function decodeJwtComplete(token: string): JwtWithPayloadObject { - const decodedToken = decode(token, { complete: true, json: true }); - if (decodedToken !== null && isJwtWithPayloadObject(decodedToken)) { - return decodedToken; + try { + const signature = validateJwtFormat(token); + return { + header: decodeJwtPart(token, { header: true }), + payload: decodeJwtPart(token), + signature + }; + } catch (error) { + throw new Error( + 'JwtError: The given jwt payload does not encode valid JSON.', + { + cause: error + } + ); } - throw new Error( - 'JwtError: The given jwt payload does not encode valid JSON.' - ); } /** @@ -274,6 +295,40 @@ export function isUserToken(token: JwtPair | undefined): token is JwtPair { return !(keys.length === 1 && keys[0] === 'iss'); } -function isJwtWithPayloadObject(decoded: Jwt): decoded is JwtWithPayloadObject { - return typeof decoded.payload !== 'string'; +/** + * Validate the format of the given JWT and return the signature part if valid. + * @returns The signature part of the JWT if the format is valid. + * @throws An error if the JWT format is invalid. + * @internal + */ +function validateJwtFormat(token: string): string { + const [encodedHeader, encodedPayload, signature, ...rest] = token.split('.'); + + if (!encodedHeader || !encodedPayload || rest.length > 0) { + throw new Error('Invalid JWT format.'); + } + + return signature; +} + +/** + * Decodes part of a JWT (header or payload) and ensures that the decoded value is an object. + * @param token - The JWT to decode. + * @param options - Options for decoding, e.g. whether to decode the header or payload. + * @param options.header - If true, decodes the header; otherwise, decodes the payload. + * @returns The decoded JWT part as an object. + * @throws An error if the decoded value is not an object. + * @internal + */ +function decodeJwtPart( + token: string, + options?: { header?: boolean } +): T { + const decoded = jwtDecode(token, options); + + if (typeof decoded !== 'object' || decoded === null) { + throw new Error('Invalid JWT content.'); + } + + return decoded; } diff --git a/packages/generator-common/package.json b/packages/generator-common/package.json index a65beb3b7f..1f2971030a 100644 --- a/packages/generator-common/package.json +++ b/packages/generator-common/package.json @@ -42,10 +42,8 @@ "dependencies": { "@sap-cloud-sdk/util": "workspace:^", "fast-levenshtein": "~3.0.0", - "fs-extra": "^11.3.4", "glob": "^13.0.6", "prettier": "^3.8.1", - "voca": "^1.4.1", "yargs": "^17.7.2" }, "devDependencies": { diff --git a/packages/generator-common/src/sdk-metadata/util.ts b/packages/generator-common/src/sdk-metadata/util.ts index 89b1c42de6..29aa5f696e 100644 --- a/packages/generator-common/src/sdk-metadata/util.ts +++ b/packages/generator-common/src/sdk-metadata/util.ts @@ -1,5 +1,5 @@ import { resolve } from 'path'; -import { readFile } from 'fs-extra'; +import { readFile } from 'node:fs/promises'; /** * Get the current SDK version from the package json. diff --git a/packages/generator-common/src/util.ts b/packages/generator-common/src/util.ts index 78bd0ee0aa..dd3c71c6cb 100644 --- a/packages/generator-common/src/util.ts +++ b/packages/generator-common/src/util.ts @@ -1,5 +1,4 @@ -import { codeBlock, createLogger } from '@sap-cloud-sdk/util'; -import voca from 'voca'; +import { codeBlock, createLogger, titleFormat } from '@sap-cloud-sdk/util'; /** * @returns A copyright header @@ -80,7 +79,7 @@ function transformUnscopedName(packageName: string) { * @internal */ export function directoryToSpeakingModuleName(packageName: string): string { - return voca.titleCase(packageName.replace(/[-,_]/g, ' ')); + return titleFormat(packageName.replace(/[-,_]/g, ' ')); } /** diff --git a/packages/generator/package.json b/packages/generator/package.json index d05102d43f..a4850ba2f1 100644 --- a/packages/generator/package.json +++ b/packages/generator/package.json @@ -48,12 +48,9 @@ "@sap-cloud-sdk/odata-v2": "workspace:^", "@sap-cloud-sdk/odata-v4": "workspace:^", "@sap-cloud-sdk/util": "workspace:^", - "@types/fs-extra": "^11.0.4", "fast-xml-parser": "^5.5.9", - "fs-extra": "^11.3.4", "ts-morph": "^28.0.0", "typescript": "~5.9.3", - "voca": "^1.4.1", "winston": "^3.19.0" }, "devDependencies": { diff --git a/packages/generator/src/edmx-parser/v4/edmx-parser.ts b/packages/generator/src/edmx-parser/v4/edmx-parser.ts index 57f356f7e6..8725c98f66 100644 --- a/packages/generator/src/edmx-parser/v4/edmx-parser.ts +++ b/packages/generator/src/edmx-parser/v4/edmx-parser.ts @@ -1,4 +1,4 @@ -import voca from 'voca'; +import { capitalize } from '@sap-cloud-sdk/util'; import { getMergedPropertyWithNamespace, getPropertyFromEntityContainer, @@ -133,13 +133,11 @@ export function parseOperationImports( root: any, operationType: 'function' | 'action' ): EdmxOperationImport[] { - const operations = getPropertyFromEntityContainer( - root, - `${voca.capitalize(operationType)}Import` - ); + const capType = capitalize(operationType); + const operations = getPropertyFromEntityContainer(root, `${capType}Import`); return operations.map(operation => ({ ...operation, - operationName: operation[voca.capitalize(operationType)], + operationName: operation[capType], operationType })); } diff --git a/packages/generator/src/edmx-to-vdm/common/operation-return-type.ts b/packages/generator/src/edmx-to-vdm/common/operation-return-type.ts index be382f6032..0dd81b5315 100644 --- a/packages/generator/src/edmx-to-vdm/common/operation-return-type.ts +++ b/packages/generator/src/edmx-to-vdm/common/operation-return-type.ts @@ -1,5 +1,4 @@ -import { first } from '@sap-cloud-sdk/util'; -import voca from 'voca'; +import { first, decapitalize } from '@sap-cloud-sdk/util'; import { isNullableProperty } from '../../generator-utils'; // eslint-disable-next-line import-x/no-internal-modules import { getApiName } from '../../generator-without-ts-morph/service'; @@ -134,9 +133,7 @@ function getEntityReturnType( ? { returnTypeCategory: 'entity', returnType: first(entities)!.className, - builderFunction: `${voca.decapitalize( - serviceName - )}(deSerializers).${getApiName(first(entities)!.className)}`, + builderFunction: `${decapitalize(serviceName)}(deSerializers).${getApiName(first(entities)!.className)}`, isNullable, isCollection } diff --git a/packages/generator/src/generator-without-ts-morph/service/class.ts b/packages/generator/src/generator-without-ts-morph/service/class.ts index e7cdd6e036..77f094fb5b 100644 --- a/packages/generator/src/generator-without-ts-morph/service/class.ts +++ b/packages/generator/src/generator-without-ts-morph/service/class.ts @@ -1,5 +1,4 @@ -import { codeBlock } from '@sap-cloud-sdk/util'; -import voca from 'voca'; +import { codeBlock, decapitalize } from '@sap-cloud-sdk/util'; // eslint-disable-next-line import-x/no-internal-modules import { matchEntity } from '../entity-api/match-entity'; import { @@ -18,9 +17,7 @@ export function serviceBuilder( oDataVersion: ODataVersion ): string { return codeBlock` - export function ${voca.decapitalize( - serviceName - )}<${getGenericTypesWithDefault(oDataVersion)}>( + export function ${decapitalize(serviceName)}<${getGenericTypesWithDefault(oDataVersion)}>( deSerializers: Partial> = defaultDeSerializers as any @@ -142,5 +139,5 @@ function getApiInitializer(entityClassName: string): string { * @returns apiName e.g. testEntityApi if the entity is called TestEntity. */ export function getApiName(entityName: string): string { - return `${voca.decapitalize(entityName)}Api`; + return `${decapitalize(entityName)}Api`; } diff --git a/packages/generator/src/generator.ts b/packages/generator/src/generator.ts index d2d50ef5cd..b6bf47f70b 100644 --- a/packages/generator/src/generator.ts +++ b/packages/generator/src/generator.ts @@ -1,4 +1,9 @@ -import { existsSync, promises as fsPromises } from 'fs'; +import { + existsSync, + readdirSync, + rmSync, + promises as fsPromises +} from 'node:fs'; import { dirname, join, resolve } from 'path'; import { copyFiles, @@ -20,7 +25,6 @@ import { setLogLevel, splitInChunks } from '@sap-cloud-sdk/util'; -import { emptyDirSync } from 'fs-extra'; import { IndentationText, ModuleKind, @@ -178,7 +182,12 @@ export async function generateProject( const services = await parseServices(options); if (options.clearOutputDir) { - emptyDirSync(options.outputDir.toString()); + for (const entry of readdirSync(options.outputDir.toString())) { + rmSync(join(options.outputDir.toString(), entry), { + recursive: true, + force: true + }); + } } const project = new Project( diff --git a/packages/generator/src/name-formatting-strategies.ts b/packages/generator/src/name-formatting-strategies.ts index 93c3f2e2b8..d01171d365 100644 --- a/packages/generator/src/name-formatting-strategies.ts +++ b/packages/generator/src/name-formatting-strategies.ts @@ -1,12 +1,10 @@ -import voca from 'voca'; +import { capitalize } from '@sap-cloud-sdk/util'; import { reservedJsKeywords } from '@sap-cloud-sdk/generator-common/internal'; const applyPrefixOnJSReservedWords = (prefix: string) => (param: string): string => - reservedJsKeywords.includes(param) - ? prefix + voca.capitalize(param) - : param; + reservedJsKeywords.includes(param) ? prefix + capitalize(param) : param; /** * @internal diff --git a/packages/generator/src/operations/import.ts b/packages/generator/src/operations/import.ts index 5c9c50bf40..43dfba6911 100644 --- a/packages/generator/src/operations/import.ts +++ b/packages/generator/src/operations/import.ts @@ -1,5 +1,5 @@ import { StructureKind } from 'ts-morph'; -import voca from 'voca'; +import { decapitalize } from '@sap-cloud-sdk/util'; import { odataImportDeclarationTsMorph, propertyTypeImportNames, @@ -121,7 +121,7 @@ export function operationDeclarations( : [ { kind: StructureKind.ImportDeclaration, - namedImports: [voca.decapitalize(className)], + namedImports: [decapitalize(className)], moduleSpecifier: `./service${extension}` } ]; diff --git a/packages/generator/src/sdk-metadata/code-samples.ts b/packages/generator/src/sdk-metadata/code-samples.ts index eab9a3dc35..1ae078ed65 100644 --- a/packages/generator/src/sdk-metadata/code-samples.ts +++ b/packages/generator/src/sdk-metadata/code-samples.ts @@ -1,5 +1,4 @@ -import { codeBlock } from '@sap-cloud-sdk/util'; -import voca from 'voca'; +import { codeBlock, decapitalize } from '@sap-cloud-sdk/util'; import { getApiName } from '../generator-without-ts-morph'; import { getOperationParams } from './code-sample-util'; import type { VdmOperation } from '../vdm-types'; @@ -14,11 +13,9 @@ export function entityCodeSample( directoryName: string ): MultiLineText { return codeBlock` -import { ${voca.decapitalize( - serviceName - )} } from './generated/${directoryName}'; +import { ${decapitalize(serviceName)} } from './generated/${directoryName}'; -const { ${getApiName(entityName)} } = ${voca.decapitalize(serviceName)}(); +const { ${getApiName(entityName)} } = ${decapitalize(serviceName)}(); const resultPromise = ${getApiName( entityName )}.requestBuilder().getAll().top(5).execute({ destinationName: 'myDestinationName' }); diff --git a/packages/generator/src/sdk-metadata/generation-and-usage.spec.ts b/packages/generator/src/sdk-metadata/generation-and-usage.spec.ts index f032183287..f106460ab0 100644 --- a/packages/generator/src/sdk-metadata/generation-and-usage.spec.ts +++ b/packages/generator/src/sdk-metadata/generation-and-usage.spec.ts @@ -1,5 +1,6 @@ import { resolve } from 'path'; -import { writeFile, readFile, removeSync } from 'fs-extra'; +import { writeFile, readFile } from 'node:fs/promises'; +import { rmSync } from 'node:fs'; import execa from 'execa'; import { getApiSpecificUsage } from './generation-and-usage'; import { entityCodeSample } from './code-samples'; @@ -110,6 +111,8 @@ describe('generation-and-usage', () => { 'commonjs' ]); await expect(readFile(resolve(__dirname, jsFile))).resolves.toBeDefined(); - [tsFile, jsFile].map(file => removeSync(resolve(__dirname, file))); + [tsFile, jsFile].map(file => + rmSync(resolve(__dirname, file), { recursive: true, force: true }) + ); }, 60000); }); diff --git a/packages/generator/src/service-name-formatter.ts b/packages/generator/src/service-name-formatter.ts index ed948350db..f05e187805 100644 --- a/packages/generator/src/service-name-formatter.ts +++ b/packages/generator/src/service-name-formatter.ts @@ -1,10 +1,11 @@ import { camelCase, createLogger, + kebabCase, + pascalCase, UniqueNameGenerator, upperCaseSnakeCase } from '@sap-cloud-sdk/util'; -import voca from 'voca'; import { stripPrefix } from './internal-prefix'; import { applyPrefixOnJsConflictFunctionImports } from './name-formatting-strategies'; import { @@ -21,7 +22,7 @@ export class ServiceNameFormatter { let formattedName = name.replace(/\.|\//g, '_'); formattedName = stripAPIUnderscore(formattedName); formattedName = stripUnderscoreSrv(formattedName); - formattedName = voca.kebabCase(formattedName); + formattedName = kebabCase(formattedName); return formattedName.endsWith('service') ? formattedName : `${formattedName}-service`; @@ -151,7 +152,7 @@ If you are ok with this change execute the generator with the '--skipValidation' } originalToOperationName(originalName: string): string { - const transformedName = voca.camelCase(stripPrefix(originalName)); + const transformedName = camelCase(stripPrefix(originalName)); const newName = this.serviceWideNameGenerator.generateAndSaveUniqueName(transformedName); @@ -197,7 +198,7 @@ If you are ok with this change execute the generator with the '--skipValidation' entitySetName: string, originalPropertyName: string ): string { - const transformedName = voca.camelCase(originalPropertyName); + const transformedName = camelCase(originalPropertyName); const generator = this.getOrInitGenerator( this.instancePropertyNameGenerators, @@ -218,7 +219,7 @@ If you are ok with this change execute the generator with the '--skipValidation' originalFunctionImportName: string, originalParameterName: string ): string { - const transformedName = voca.camelCase(originalParameterName); + const transformedName = camelCase(originalParameterName); const generator = this.getOrInitGenerator( this.parameterNameGenerators, @@ -241,7 +242,7 @@ If you are ok with this change execute the generator with the '--skipValidation' originalFunctionImportName: string, originalParameterName: string ): string { - const transformedName = voca.camelCase(originalParameterName); + const transformedName = camelCase(originalParameterName); const generator = this.getOrInitGenerator( this.parameterNameGenerators, @@ -265,7 +266,7 @@ If you are ok with this change execute the generator with the '--skipValidation' transformedName = stripCollection(entitySetName); } - transformedName = stripAUnderscore(voca.titleCase(transformedName)); + transformedName = pascalCase(stripAUnderscore(transformedName)); const uniqueName = this.serviceWideNameGenerator.generateAndSaveUniqueNamesWithSuffixes( @@ -287,9 +288,10 @@ If you are ok with this change execute the generator with the '--skipValidation' originalName: string, originalContainerTypeName: string ): string { - const transformedName = stripAUnderscore( - voca.titleCase(originalName) - ).replace('_', ''); + const transformedName = pascalCase(stripAUnderscore(originalName)).replace( + '_', + '' + ); const uniqueName = this.serviceWideNameGenerator.generateAndSaveUniqueName( transformedName, diff --git a/packages/odata-common/package.json b/packages/odata-common/package.json index 2080fb14d5..1e1894b29a 100644 --- a/packages/odata-common/package.json +++ b/packages/odata-common/package.json @@ -44,8 +44,7 @@ "@sap-cloud-sdk/http-client": "workspace:^", "@sap-cloud-sdk/util": "workspace:^", "bignumber.js": "^11.0.0", - "moment": "^2.30.1", - "voca": "^1.4.1" + "moment": "^2.30.1" }, "devDependencies": { "@sap-cloud-sdk/test-services-odata-common": "workspace:^", diff --git a/packages/odata-common/src/request-builder/batch/batch-request-serializer.ts b/packages/odata-common/src/request-builder/batch/batch-request-serializer.ts index 43157c2da4..37dfc4948e 100644 --- a/packages/odata-common/src/request-builder/batch/batch-request-serializer.ts +++ b/packages/odata-common/src/request-builder/batch/batch-request-serializer.ts @@ -1,4 +1,4 @@ -import voca from 'voca'; +import { titleCase } from '@sap-cloud-sdk/util'; import { ODataRequest } from '../../request'; import { BatchChangeSet } from './batch-change-set'; import type { ODataRequestConfig, WithBatchReference } from '../../request'; @@ -55,7 +55,7 @@ export function serializeRequest( ...odataRequest.customHeaders() }; const requestHeaders = Object.entries(headers).map( - ([key, value]) => `${voca.titleCase(key)}: ${value}` + ([key, value]) => `${titleCase(key)}: ${value}` ); const method = request.requestConfig.method.toUpperCase(); diff --git a/packages/util/package.json b/packages/util/package.json index 8aa5e04bd0..8338d510b4 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -40,7 +40,6 @@ "dependencies": { "axios": "^1.15.0", "logform": "^2.7.0", - "voca": "^1.4.1", "winston": "^3.19.0", "winston-transport": "^4.9.0" }, diff --git a/packages/util/src/string-formatter.ts b/packages/util/src/string-formatter.ts index 117bcd0efb..3ff9f74307 100644 --- a/packages/util/src/string-formatter.ts +++ b/packages/util/src/string-formatter.ts @@ -1,5 +1,3 @@ -import voca from 'voca'; - /** * Within all files generated by the SDK we use the unix style end of line delimiter. * We do not consider if the generator is executed on windows or unix systems. @@ -14,13 +12,133 @@ export const unixEOL = '\n'; */ export const webEOL = '\r\n'; +type CharKind = 'upper' | 'lower' | 'digit' | 'separator'; + +// Uses Unicode property escapes to correctly classify non-ASCII letters and digits +// (e.g. accented uppercase Ü, Arabic-Indic digits ٣). Caseless letters (CJK, Arabic, +// titlecase like Dž) are treated as lowercase so they are included in words rather than dropped. +function charKind(c: string): CharKind { + if (/^\p{Lu}$/u.test(c)) { + return 'upper'; + } + if (/^\p{L}$/u.test(c)) { + return 'lower'; + } // covers Ll, Lt, Lm, Lo + if (/^\p{N}$/u.test(c)) { + return 'digit'; + } + return 'separator'; +} + +// Splits an identifier string into words, handling common case conventions: +// camelCase → ['camel', 'Case'] +// XMLParser → ['XML', 'Parser'] +// field_name → ['field', 'name'] +// Field13Name → ['Field', '13', 'Name'] +function words(str: string): string[] { + if (!str) { + return []; + } + const result: string[] = []; + let word = ''; + let state: CharKind | 'none' = 'none'; + + const pushWord = () => { + if (word) { + result.push(word); + } + word = ''; + }; + + for (const char of str) { + const kind = charKind(char); + + if (state === 'none' || state === 'separator') { + if (kind !== 'separator') { + word = char; + state = kind; + } + } else if (kind === 'separator') { + pushWord(); + state = 'separator'; + } else if (kind === 'lower' && state === 'upper') { + // Transition into a lowercase run: if we accumulated multiple uppers, + // the last one belongs to this new word (e.g. XMLParser → XML + Parser) + if (word.length > 1) { + const last = word.slice(-1); + word = word.slice(0, -1); + pushWord(); + word = last; + } + word += char; + state = 'lower'; + } else if (kind !== state) { + // Any other kind change (lower→digit, digit→upper, etc.) starts a new word + pushWord(); + word = char; + state = kind; + } else { + word += char; + } + } + + pushWord(); + + return result; +} + +// Applies transforms to the first Unicode code point and the remainder. +// Uses codePointAt to correctly handle surrogate pairs (e.g. emoji, some CJK). +function transformInitialLetter( + str: string, + headTransform: (c: string) => string, + tailTransform: (s: string) => string = s => s +): string { + if (!str?.length) { + return str ?? ''; + } + // A surrogate pair occupies 2 UTF-16 code units; all BMP chars occupy 1. + const firstCharLength = str.codePointAt(0)! > 0xffff ? 2 : 1; + return ( + headTransform(str.slice(0, firstCharLength)) + + tailTransform(str.slice(firstCharLength)) + ); +} + +/** + * Uppercase the first character of a string, leaving the rest unchanged. + * @param str - The string to capitalize. + * @returns The string with the first character uppercased. + */ +export function capitalize(str: string): string { + return transformInitialLetter(str, c => c.toUpperCase()); +} + +/** + * Lowercase the first character of a string, leaving the rest unchanged. + * @param str - The string to decapitalize. + * @returns The string with the first character lowercased. + * @internal + */ +export function decapitalize(str: string): string { + return transformInitialLetter(str, c => c.toLowerCase()); +} + +function capitalizeWord(str: string): string { + return transformInitialLetter( + str, + c => c.toUpperCase(), + s => s.toLowerCase() + ); +} + /** * Convert a string to the uppercase snake case. This format is used e.g. for static properties on entity classes. * @param str - The string to be transformed. * @returns The input string in the case used by static methods on entity-classes. */ export function upperCaseSnakeCase(str: string): string { - return voca.upperCase(voca.snakeCase(str)); + return words(str).join('_').toUpperCase(); } /** @@ -29,7 +147,11 @@ export function upperCaseSnakeCase(str: string): string { * @returns The transformed string. */ export function camelCase(str: string): string { - return voca.camelCase(str); + const parts = words(str); + if (!parts.length) { + return ''; + } + return parts[0].toLowerCase() + parts.slice(1).map(capitalizeWord).join(''); } /** @@ -38,7 +160,7 @@ export function camelCase(str: string): string { * @returns The transformed string. */ export function titleFormat(str: string): string { - return voca.titleCase(voca.words(str).join(' ')); + return words(str).map(capitalizeWord).join(' '); } /** @@ -47,10 +169,7 @@ export function titleFormat(str: string): string { * @returns The transformed string. */ export function pascalCase(str: string): string { - return voca - .words(str) - .map(word => voca.capitalize(word)) - .join(''); + return words(str).map(capitalize).join(''); } /** @@ -59,7 +178,17 @@ export function pascalCase(str: string): string { * @returns The transformed string. */ export function kebabCase(str: string): string { - return voca.kebabCase(str); + return words(str).join('-').toLowerCase(); +} + +/** + * Uppercase the first character after each word boundary, leaving separators intact. + * E.g. `content-type` → `Content-Type`, `if-match` → `If-Match`. + * @param str - The string to transform. + * @returns The string with each word's first character uppercased. + */ +export function titleCase(str: string): string { + return str.replace(/\b\w/g, c => c.toUpperCase()); } /** diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 36ffe92679..4b10ca266a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -321,18 +321,15 @@ importers: '@sap/xssec': specifier: ^4.13.0 version: 4.13.0 - async-retry: - specifier: ^1.3.3 - version: 1.3.3 axios: specifier: ^1.15.0 version: 1.16.0 jks-js: specifier: ^1.1.6 version: 1.1.7 - jsonwebtoken: - specifier: ^9.0.3 - version: 9.0.3 + jwt-decode: + specifier: ^4.0.0 + version: 4.0.0 safe-stable-stringify: specifier: ^2.5.0 version: 2.5.0 @@ -412,24 +409,15 @@ importers: '@sap-cloud-sdk/util': specifier: workspace:^ version: link:../util - '@types/fs-extra': - specifier: ^11.0.4 - version: 11.0.4 fast-xml-parser: specifier: ^5.5.9 version: 5.7.3 - fs-extra: - specifier: ^11.3.4 - version: 11.3.5 ts-morph: specifier: ^28.0.0 version: 28.0.0 typescript: specifier: ~5.9.3 version: 5.9.3 - voca: - specifier: ^1.4.1 - version: 1.4.1 winston: specifier: ^3.19.0 version: 3.19.0 @@ -461,18 +449,12 @@ importers: fast-levenshtein: specifier: ~3.0.0 version: 3.0.0 - fs-extra: - specifier: ^11.3.4 - version: 11.3.5 glob: specifier: ^13.0.6 version: 13.0.6 prettier: specifier: ^3.8.1 version: 3.8.3 - voca: - specifier: ^1.4.1 - version: 1.4.1 yargs: specifier: ^17.7.2 version: 17.7.2 @@ -550,9 +532,6 @@ importers: moment: specifier: ^2.30.1 version: 2.30.1 - voca: - specifier: ^1.4.1 - version: 1.4.1 devDependencies: '@sap-cloud-sdk/test-services-odata-common': specifier: workspace:^ @@ -825,9 +804,6 @@ importers: logform: specifier: ^2.7.0 version: 2.7.0 - voca: - specifier: ^1.4.1 - version: 1.4.1 winston: specifier: ^3.19.0 version: 3.19.0 @@ -2210,9 +2186,6 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - '@types/fs-extra@11.0.4': - resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} - '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} @@ -2240,9 +2213,6 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - '@types/jsonfile@6.1.4': - resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} - '@types/jsonwebtoken@9.0.10': resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} @@ -6081,9 +6051,6 @@ packages: resolution: {integrity: sha512-sfAcO2yeSU0CSPFI/DmZp3FsFE9T+8913nv1xWBOyzODv13fwkn6Vl7HqxGpkr9F608M+8SuFId3s+BlZqfXww==} engines: {node: '>=4.0'} - voca@1.4.1: - resolution: {integrity: sha512-NJC/BzESaHT1p4B5k4JykxedeltmNbau4cummStd4RjFojgq/kLew5TzYge9N2geeWyI2w8T30wUET5v+F7ZHA==} - walkdir@0.4.1: resolution: {integrity: sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==} engines: {node: '>=6.0.0'} @@ -7573,11 +7540,6 @@ snapshots: '@types/estree@1.0.8': {} - '@types/fs-extra@11.0.4': - dependencies: - '@types/jsonfile': 6.1.4 - '@types/node': 22.19.15 - '@types/hast@3.0.4': dependencies: '@types/unist': 3.0.3 @@ -7608,10 +7570,6 @@ snapshots: '@types/json5@0.0.29': optional: true - '@types/jsonfile@6.1.4': - dependencies: - '@types/node': 22.19.15 - '@types/jsonwebtoken@9.0.10': dependencies: '@types/ms': 2.1.0 @@ -12126,8 +12084,6 @@ snapshots: ini: 1.3.8 js-git: 0.7.8 - voca@1.4.1: {} - walkdir@0.4.1: {} walker@1.0.8: From f041523b85dd2e9a41b54a1dbc2c96811ccd55c3 Mon Sep 17 00:00:00 2001 From: "sap-cloud-sdk-bot[bot]" <274190970+sap-cloud-sdk-bot[bot]@users.noreply.github.com> Date: Wed, 13 May 2026 17:03:52 +0000 Subject: [PATCH 2/2] Changes from lint:fix --- .github/actions/check-public-api/index.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/actions/check-public-api/index.js b/.github/actions/check-public-api/index.js index e7e848a9c8..7de7f06375 100644 --- a/.github/actions/check-public-api/index.js +++ b/.github/actions/check-public-api/index.js @@ -63365,12 +63365,15 @@ exports.webEOL = '\r\n'; // (e.g. accented uppercase Ü, Arabic-Indic digits ٣). Caseless letters (CJK, Arabic, // titlecase like Dž) are treated as lowercase so they are included in words rather than dropped. function charKind(c) { - if (/^\p{Lu}$/u.test(c)) + if (/^\p{Lu}$/u.test(c)) { return 'upper'; - if (/^\p{L}$/u.test(c)) - return 'lower'; // covers Ll, Lt, Lm, Lo - if (/^\p{N}$/u.test(c)) + } + if (/^\p{L}$/u.test(c)) { + return 'lower'; + } // covers Ll, Lt, Lm, Lo + if (/^\p{N}$/u.test(c)) { return 'digit'; + } return 'separator'; } // Splits an identifier string into words, handling common case conventions: @@ -63379,6 +63382,9 @@ function charKind(c) { // field_name → ['field', 'name'] // Field13Name → ['Field', '13', 'Name'] function words(str) { + if (!str) { + return []; + } const result = []; let word = ''; let state = 'none'; @@ -63428,8 +63434,8 @@ function words(str) { // Applies transforms to the first Unicode code point and the remainder. // Uses codePointAt to correctly handle surrogate pairs (e.g. emoji, some CJK). function transformInitialLetter(str, headTransform, tailTransform = s => s) { - if (!str.length) { - return str; + if (!str?.length) { + return str ?? ''; } // A surrogate pair occupies 2 UTF-16 code units; all BMP chars occupy 1. const firstCharLength = str.codePointAt(0) > 0xffff ? 2 : 1; @@ -63490,7 +63496,7 @@ function titleFormat(str) { * @returns The transformed string. */ function pascalCase(str) { - return words(str).map(capitalizeWord).join(''); + return words(str).map(capitalize).join(''); } /** * Convert a string to kebab case. This format is used e.g. for file names.