UNPKG

topkat-utils

Version:

A comprehensive collection of TypeScript/JavaScript utility functions for common programming tasks. Includes validation, object manipulation, date handling, string formatting, and more. Zero dependencies, fully typed, and optimized for performance.

296 lines 13.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseStringAsNumber = exports.parseStringAsBoolean = exports.parseStringVariable = exports.upperCase = exports.lowerCase = exports.nbOccurenceInString = exports.cln = exports.miniTemplater = exports.pathJoinSafe = exports.urlPathJoin = exports.generateObjectId = exports.generateToken = exports.convertAccentedCharacters = exports.getValuesBetweenStrings = exports.getValuesBetweenSeparator = exports.camelCaseToWords = exports.capitalize1st = exports.titleCase = exports.pascalCase = exports.kebabCase = exports.snakeCase = exports.camelCaseify = exports.camelCase = void 0; //---------------------------------------- // STRING UTILS //---------------------------------------- const error_utils_1 = require("./error-utils"); const isset_1 = require("./isset"); const getWordBits = (wb) => Array.isArray(wb[0]) ? wb[0] : wb; /**Eg: camelCase('hello', 'world') => 'helloWorld' */ function camelCase(...wordBits) { const wordBitsReal = getWordBits(wordBits); return wordBitsReal.filter(e => e).map((w, i) => i === 0 ? w.toLowerCase() : capitalize1st(w, true)).join(''); } exports.camelCase = camelCase; /** Replace 'hello-world', 'hello World', 'hello_World', 'helloWorld' => 'helloWorld' */ function camelCaseify(word) { const wordBitsReal = word.replace(/([A-Z])/g, ' $1').split(/[- _]/g); return camelCase(wordBitsReal); } exports.camelCaseify = camelCaseify; /**Eg: snake_case * trimmed but not lowerCased */ function snakeCase(...wordBits) { const wordBitsReal = getWordBits(wordBits); return wordBitsReal.filter(e => e).map(w => w.trim()).join('_'); } exports.snakeCase = snakeCase; /**Eg: kebab-case * trimmed AND lowerCased * undefined, null are removed */ function kebabCase(...wordBits) { const wordBitsReal = getWordBits(wordBits); return wordBitsReal.filter(e => e).map(w => w.trim().toLowerCase()).join('-'); } exports.kebabCase = kebabCase; /**Eg: PascalCase * undefined, null are removed */ function pascalCase(...wordBits) { const wordBitsReal = getWordBits(wordBits); return wordBitsReal.filter(e => e).map(w => capitalize1st(w, true)).join(''); } exports.pascalCase = pascalCase; /**Eg: Titlecase * undefined, null are removed */ function titleCase(...wordBits) { const wordBitsReal = getWordBits(wordBits); return capitalize1st(wordBitsReal.filter(e => e).map(w => w.trim()).join(''), true); } exports.titleCase = titleCase; function capitalize1st(str = '', lowercaseTheRest = false) { return str ? str[0].toUpperCase() + (lowercaseTheRest ? str.toLowerCase() : str).slice(1) : str; } exports.capitalize1st = capitalize1st; function camelCaseToWords(str) { return str ? str.trim().replace(/([A-Z])/g, '-$1').toLowerCase().split('-') : []; } exports.camelCaseToWords = camelCaseToWords; /** GIVEN A STRING '{ blah;2}, ['nested,(what,ever)']' AND A SEPARATOR ","" * This will return the content separated by first level of separators * @return ["{ blah;2}", "['nested,(what,ever)']"] */ function getValuesBetweenSeparator(str, separator, removeTrailingSpaces = true) { (0, error_utils_1.err500IfEmptyOrNotSet)({ separator, str }); const { outer } = getValuesBetweenStrings(str, separator, undefined, undefined, undefined, removeTrailingSpaces); return outer; } exports.getValuesBetweenSeparator = getValuesBetweenSeparator; /** GIVEN A STRING "a: [ 'str', /[^]]/, '[aa]]]str', () => [ nestedArray ] ], b: ['arr']" * @return matching: [ "'str', /[^]]/, '[aa]]]str', () => [ nestedArray ]", "'arr'" ], between: [ "a:", ", b: " ] * @param str base string * @param openingOrSeparator opening character OR separator if closing not set * @param closing * @param ignoreBetweenOpen default ['\'', '`', '"', '/'], when reaching an opening char, it will ignore all until it find the corresponding closing char * @param ignoreBetweenClose default ['\'', '`', '"', '/'] list of corresponding closing chars */ function getValuesBetweenStrings(str, openingOrSeparator, closing, ignoreBetweenOpen = ['\'', '`', '"', '/'], ignoreBetweenClose = ['\'', '`', '"', '/'], removeTrailingSpaces = true) { (0, error_utils_1.err500IfEmptyOrNotSet)({ openingOrSeparator, str }); str = str.replace(/<</g, '§§"').replace(/>>/g, '"§§'); const arrayValues = []; const betweenArray = []; let level = 0; let ignoreUntil = false; let actualValue = ''; let precedingChar = ''; let separatorMode = false; if (!closing) separatorMode = true; const separator = separatorMode ? openingOrSeparator : false; const openingChars = separatorMode ? ['(', '{', '['] : [openingOrSeparator]; const closingChars = separatorMode ? [')', '}', ']'] : [closing]; const pushActualValue = () => { if (level === 0) betweenArray.push(removeTrailingSpaces ? actualValue.replace(/(?:^\s+|\s+$)/g, '') : actualValue); else arrayValues.push(removeTrailingSpaces ? actualValue.replace(/(?:^\s+|\s+$)/g, '') : actualValue); actualValue = ''; }; str.split('').forEach(char => { // handle unwanted nested structure like characters in a strings that may be a unmatched closing / opening character // Eg: {'azer}aze'} if (ignoreUntil && char === ignoreUntil && precedingChar !== '\\') ignoreUntil = false; else if (ignoreUntil && char !== ignoreUntil) ignoreUntil = true; else if (ignoreBetweenOpen.includes(char)) { const indexChar = ignoreBetweenOpen.findIndex(char2 => char2 === char); ignoreUntil = ignoreBetweenClose[indexChar]; } else if (openingChars.includes(char)) { // handle nested structures if (!separatorMode && level === 0) pushActualValue(); level++; if (!separatorMode) return; } else if (closingChars.includes(char)) { // handle nested structures if (!separatorMode && level === 1) pushActualValue(); level--; } else if (separatorMode && level === 0 && char === separator) { // SEPARATOR MODE pushActualValue(); return; } actualValue += char; precedingChar = char; }); pushActualValue(); const replaceValz = arr => arr.map(v => v.replace(/§§"/g, '<<').replace(/"§§/g, '>>')).filter(v => v); return { inner: replaceValz(arrayValues), outer: replaceValz(betweenArray) }; } exports.getValuesBetweenStrings = getValuesBetweenStrings; /** Remove accentued character from string and eventually special chars and numbers * @param {String} str input string * @param {Object} config { removeSpecialChars: false, removeNumbers: false, removeSpaces: false } * @returns String with all accentued char replaced by their non accentued version + config formattting */ function convertAccentedCharacters(str, config = {}) { let output = str .trim() .replace(/[àáâãäå]/g, 'a') .replace(/[ÀÁÂÃÄÅ]/g, 'A') .replace(/ç/g, 'c') .replace(/Ç/g, 'C') .replace(/[èéêë]/g, 'e') .replace(/[ÈÉÊË]/g, 'E') .replace(/[ìíîï]/g, 'i') .replace(/[ÌÍÎÏ]/g, 'I') .replace(/[ôö]/g, 'o') .replace(/[ÔÖ]/g, 'O') .replace(/[ùúû]/g, 'u') .replace(/[ÙÚÛ]/g, 'U'); if (config.removeNumbers === true) output = output.replace(/\d+/g, ''); if (config.removeSpecialChars === true) output = output.replace(/[^A-Za-z0-9 ]/g, ''); if (config.removeSpaces === true) output = output.replace(/\s+/g, ''); return output; } exports.convertAccentedCharacters = convertAccentedCharacters; let generatedTokens = []; // cache to avoid collision let lastTs = Date.now(); /** minLength 8 if unique * @param {Number} length default: 20 * @param {Boolean} unique default: true. Generate a real unique token base on the date. min length will be min 8 in this case * @param {string} mode one of ['alphanumeric', 'hexadecimal'] * NOTE: to generate a mongoDB Random Id, use the params: 24, true, 'hexadecimal' */ function generateToken(length = 20, unique = true, mode = 'alphanumeric') { const charConvNumeric = mode === 'alphanumeric' ? 36 : 16; if (unique && length < 8) throw new Error('generateToken can not be used with less than 8 characters when unique is set to true'); let token; let tokenTs; do { tokenTs = Date.now(); token = unique ? tokenTs.toString(charConvNumeric) : ''; while (token.length < length) token += Math.random().toString(charConvNumeric).substr(2, 1); // char alphaNumeric aléatoire } while (generatedTokens.includes(token)); if (lastTs < tokenTs) { generatedTokens = []; // reset generated token on new timestamp because cannot collide lastTs = Date.now(); } generatedTokens.push(token); return token; } exports.generateToken = generateToken; function generateObjectId() { return generateToken(24, true, 'hexadecimal'); } exports.generateObjectId = generateObjectId; /** Useful to join differents bits of url with normalizing slashes * * urlPathJoin('https://', 'www.kikou.lol/', '/user', '//2//') => https://www.kikou.lol/user/2 * * urlPathJoin('http:/', 'kikou.lol') => https://www.kikou.lol */ function urlPathJoin(...bits) { return bits .join('/') .replace(/\/+/g, '/') // replace double slash .replace(/(^\/|\/$)/g, '') // remove starting and trailing slash .replace(/(https?:)\/\/?/, '$1//'); // make sure there is 2 slashes after http(s) } exports.urlPathJoin = urlPathJoin; /** @deprecated use urlPathJoin instead // file path using ONLY SLASH and not antislash on windows. Remove also starting and trailing slashes */ function pathJoinSafe(...pathBits) { return pathBits .join('/') .replace(/\/+/g, '/') // replace double slash .replace(/(^\/|\/$)/g, ''); // remove starting and trailing slash } exports.pathJoinSafe = pathJoinSafe; /** Replace variables in a string like: `Hello {{userName}}!` * @param {String} content * @param {Object} varz object with key => value === toReplace => replacer * @param {Object} options * * valueWhenNotSet => replacer for undefined values. Default: '' * * regexp => must be 'g' and first capturing group matching the value to replace. Default: /{{\s*([^}]*)\s*}}/g */ function miniTemplater(content, varz, options = {}) { const options2 = { valueWhenNotSet: '', regexp: /{{\s*([^}]*)\s*}}/g, valueWhenContentUndefined: '', ...options, }; return (0, isset_1.isset)(content) ? content.replace(options2.regexp, (_, $1) => (0, isset_1.isset)(varz[$1]) ? varz[$1] : options2.valueWhenNotSet) : options2.valueWhenContentUndefined; } exports.miniTemplater = miniTemplater; /** Clean output for outside world. All undefined / null / NaN / Infinity values are changed to '-' */ function cln(val, replacerInCaseItIsUndefinNaN = '-') { return ['undefined', undefined, 'indéfini', 'NaN', NaN, Infinity, null].includes(val) ? replacerInCaseItIsUndefinNaN : val; } exports.cln = cln; function nbOccurenceInString(baseString, searchedString, allowOverlapping = false) { if (searchedString.length === 0) return baseString.length + 1; let n = 0; let pos = 0; const step = allowOverlapping ? 1 : searchedString.length; // eslint-disable-next-line no-constant-condition while (true) { pos = baseString.indexOf(searchedString, pos); if (pos >= 0) { ++n; pos += step; } else break; } return n; } exports.nbOccurenceInString = nbOccurenceInString; /** typed lower case. Eg: if you pass 'A' | 'B', the resulting type will be 'a' | 'b' */ function lowerCase(string) { return string?.toLocaleLowerCase(); } exports.lowerCase = lowerCase; /** typed upper case. Eg: if you pass 'a' | 'b', the resulting type will be 'A' | 'B' */ function upperCase(string) { return string?.toLocaleUpperCase(); } exports.upperCase = upperCase; /** Parse strings like 'true', 'false', '123', 'null' to their real type equivalent. Actual string is returned if nothing matches. * * /!\ for typing please profide a type parameter like `parseStringVariable<boolean>('true')` */ function parseStringVariable(val) { if (val === 'undefined') return undefined; else if (val === 'true') return true; else if (val === 'false') return false; else if (val === 'null') return null; else if (/^[0-9]+$/.test(val)) return Number(val); else return val; } exports.parseStringVariable = parseStringVariable; /** return val === 'true' || val === true */ function parseStringAsBoolean(val) { return val === 'true' || val === true; } exports.parseStringAsBoolean = parseStringAsBoolean; /** return Number(val) */ function parseStringAsNumber(val) { return Number(val); } exports.parseStringAsNumber = parseStringAsNumber; //# sourceMappingURL=string-utils.js.map