UNPKG

@oxog/string

Version:

Comprehensive string manipulation utilities with zero dependencies

327 lines (326 loc) 11.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.similarity = similarity; exports.fuzzyMatch = fuzzyMatch; exports.soundsLike = soundsLike; exports.findPatterns = findPatterns; exports.isRepeating = isRepeating; exports.extractEmails = extractEmails; exports.extractUrls = extractUrls; exports.extractNumbers = extractNumbers; exports.random = random; exports.generatePronounceable = generatePronounceable; exports.generateFromPattern = generateFromPattern; exports.mask = mask; exports.maskEmail = maskEmail; exports.maskCreditCard = maskCreditCard; exports.hash = hash; exports.toTable = toTable; exports.boxify = boxify; exports.progressBar = progressBar; const algorithms_1 = require("../utils/algorithms"); // String Similarity & Matching function similarity(str1, str2, algorithm = 'levenshtein') { switch (algorithm) { case 'levenshtein': const maxLength = Math.max(str1.length, str2.length); if (maxLength === 0) return 1; return 1 - (0, algorithms_1.levenshteinDistance)(str1, str2) / maxLength; case 'jaro': return (0, algorithms_1.jaroDistance)(str1, str2); case 'cosine': return (0, algorithms_1.cosineDistance)(str1, str2); default: throw new Error(`Unknown similarity algorithm: ${algorithm}`); } } function fuzzyMatch(str, pattern, threshold = 0.6) { return similarity(str, pattern, 'levenshtein') >= threshold; } function soundsLike(str1, str2, algorithm = 'soundex') { switch (algorithm) { case 'soundex': return (0, algorithms_1.soundex)(str1) === (0, algorithms_1.soundex)(str2); case 'metaphone': return (0, algorithms_1.metaphone)(str1) === (0, algorithms_1.metaphone)(str2); default: throw new Error(`Unknown sounds-like algorithm: ${algorithm}`); } } // Pattern Detection function findPatterns(str, minLength = 2) { const patterns = new Map(); for (let length = minLength; length <= str.length / 2; length++) { for (let i = 0; i <= str.length - length; i++) { const pattern = str.slice(i, i + length); const existing = patterns.get(pattern); if (existing) { existing.indices.push(i); existing.frequency++; } else { patterns.set(pattern, { pattern, indices: [i], length, frequency: 1 }); } } } return Array.from(patterns.values()) .filter(p => p.frequency > 1) .sort((a, b) => b.frequency - a.frequency || b.length - a.length); } function isRepeating(str) { if (str.length === 0) return false; for (let len = 1; len <= str.length / 2; len++) { if (str.length % len === 0) { const pattern = str.slice(0, len); const repeated = pattern.repeat(str.length / len); if (repeated === str) return true; } } return false; } function extractEmails(str) { const emailRegex = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g; return str.match(emailRegex) || []; } function extractUrls(str) { const urlRegex = /https?:\/\/[^\s<>"{}|\\^`[\]]+/g; return str.match(urlRegex) || []; } function extractNumbers(str) { const numberRegex = /-?\d+(?:\.\d+)?/g; return str.match(numberRegex) || []; } // String Generation function random(length, options = {}) { const { uppercase = true, lowercase = true, numbers = true, symbols = false, excludeSimilar = false, customCharset } = options; if (customCharset) { let result = ''; for (let i = 0; i < length; i++) { result += customCharset[Math.floor(Math.random() * customCharset.length)]; } return result; } let charset = ''; if (uppercase) charset += excludeSimilar ? 'ABCDEFGHJKLMNPQRSTUVWXYZ' : 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; if (lowercase) charset += excludeSimilar ? 'abcdefghjkmnpqrstuvwxyz' : 'abcdefghijklmnopqrstuvwxyz'; if (numbers) charset += excludeSimilar ? '23456789' : '0123456789'; if (symbols) charset += '!@#$%^&*()_+-=[]{}|;:,.<>?'; if (!charset) { throw new Error('At least one character type must be enabled'); } let result = ''; for (let i = 0; i < length; i++) { result += charset[Math.floor(Math.random() * charset.length)]; } return result; } function generatePronounceable(length) { const consonants = 'bcdfghjklmnpqrstvwxyz'; const vowels = 'aeiou'; let result = ''; let useConsonant = Math.random() > 0.5; for (let i = 0; i < length; i++) { if (useConsonant) { result += consonants[Math.floor(Math.random() * consonants.length)]; } else { result += vowels[Math.floor(Math.random() * vowels.length)]; } useConsonant = !useConsonant; } return result; } function generateFromPattern(pattern) { return pattern.replace(/[#X]/g, char => { if (char === '#') { return Math.floor(Math.random() * 10).toString(); } else if (char === 'X') { return String.fromCharCode(65 + Math.floor(Math.random() * 26)); } return char; }); } // Masking & Security function mask(str, options = {}) { const { maskChar = '*', unmaskedStart = 0, unmaskedEnd = 0 } = options; if (unmaskedStart + unmaskedEnd >= str.length) { return str; } const start = str.slice(0, unmaskedStart); const end = str.slice(-unmaskedEnd || str.length); const middle = maskChar.repeat(str.length - unmaskedStart - unmaskedEnd); return start + middle + end; } function maskEmail(email) { const atIndex = email.indexOf('@'); if (atIndex === -1) return mask(email); const local = email.slice(0, atIndex); const domain = email.slice(atIndex); const maskedLocal = mask(local, { unmaskedStart: 1, unmaskedEnd: 1 }); return maskedLocal + domain; } function maskCreditCard(cc) { const digits = cc.replace(/\D/g, ''); return mask(digits, { unmaskedEnd: 4 }); } function hash(str, algorithm) { switch (algorithm) { case 'md5': return md5(str); case 'sha1': return sha1(str); case 'sha256': return sha256(str); default: throw new Error(`Unknown hash algorithm: ${algorithm}`); } } // Visual Formatting function toTable(data, options = {}) { const { headers = false, border = true, padding = 1, align = 'left' } = options; if (!data.length) return ''; const maxCols = Math.max(...data.map(row => row.length)); const colWidths = new Array(maxCols).fill(0); // Calculate column widths data.forEach(row => { row.forEach((cell, col) => { colWidths[col] = Math.max(colWidths[col], cell.length); }); }); const lines = []; if (border) { const borderLine = '+' + colWidths.map(w => '-'.repeat(w + padding * 2)).join('+') + '+'; lines.push(borderLine); } data.forEach((row, rowIndex) => { const cells = row.map((cell, col) => { const width = colWidths[col]; let aligned = cell; if (align === 'center') { const spaces = width - cell.length; const left = Math.floor(spaces / 2); const right = spaces - left; aligned = ' '.repeat(left) + cell + ' '.repeat(right); } else if (align === 'right') { aligned = cell.padStart(width); } else { aligned = cell.padEnd(width); } return ' '.repeat(padding) + aligned + ' '.repeat(padding); }); const line = border ? '|' + cells.join('|') + '|' : cells.join(' '); lines.push(line); if (headers && rowIndex === 0 && border) { const borderLine = '+' + colWidths.map(w => '-'.repeat(w + padding * 2)).join('+') + '+'; lines.push(borderLine); } }); if (border) { const borderLine = '+' + colWidths.map(w => '-'.repeat(w + padding * 2)).join('+') + '+'; lines.push(borderLine); } return lines.join('\n'); } function boxify(str, options = {}) { const { style = 'single', padding = 1, margin = 0, title } = options; const styles = { single: { tl: '┌', tr: '┐', bl: '└', br: '┘', h: '─', v: '│' }, double: { tl: '╔', tr: '╗', bl: '╚', br: '╝', h: '═', v: '║' }, rounded: { tl: '╭', tr: '╮', bl: '╰', br: '╯', h: '─', v: '│' }, thick: { tl: '┏', tr: '┓', bl: '┗', br: '┛', h: '━', v: '┃' } }; const chars = styles[style]; const lines = str.split('\n'); const maxWidth = Math.max(...lines.map(line => line.length)); const innerWidth = maxWidth + padding * 2; const result = []; // Add top margin for (let i = 0; i < margin; i++) { result.push(''); } // Top border const topBorder = chars.tl + chars.h.repeat(innerWidth) + chars.tr; result.push(' '.repeat(margin) + topBorder); // Title if (title) { const titlePadding = Math.max(0, Math.floor((innerWidth - title.length) / 2)); const titleLine = chars.v + ' '.repeat(titlePadding) + title + ' '.repeat(innerWidth - titlePadding - title.length) + chars.v; result.push(' '.repeat(margin) + titleLine); const titleSeparator = chars.tl + chars.h.repeat(innerWidth) + chars.tr; result.push(' '.repeat(margin) + titleSeparator); } // Content lines lines.forEach(line => { const paddedLine = ' '.repeat(padding) + line.padEnd(maxWidth) + ' '.repeat(padding); result.push(' '.repeat(margin) + chars.v + paddedLine + chars.v); }); // Bottom border const bottomBorder = chars.bl + chars.h.repeat(innerWidth) + chars.br; result.push(' '.repeat(margin) + bottomBorder); // Add bottom margin for (let i = 0; i < margin; i++) { result.push(''); } return result.join('\n'); } function progressBar(value, options = {}) { const { width = 20, complete = '█', incomplete = '░', showPercent = true } = options; const percentage = Math.max(0, Math.min(100, value)); const completed = Math.round((percentage / 100) * width); const remaining = width - completed; const bar = complete.repeat(completed) + incomplete.repeat(remaining); if (showPercent) { return `${bar} ${percentage.toFixed(1)}%`; } return bar; } // Pure JavaScript hash implementations function md5(str) { // Simplified MD5 implementation - in production, use a proper crypto library let hash = 0; if (str.length === 0) return hash.toString(16); for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // Convert to 32-bit integer } return Math.abs(hash).toString(16); } function sha1(str) { // Simplified SHA1 implementation - in production, use a proper crypto library let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; } return Math.abs(hash).toString(16).padStart(8, '0'); } function sha256(str) { // Simplified SHA256 implementation - in production, use a proper crypto library let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; } return Math.abs(hash).toString(16).padStart(16, '0'); }