/** * ------------------------------------------------------------ * StringUtils * ------------------------------------------------------------ * Centralized string manipulation utility. * * Features: * - 🛡️ Null-safe (handles null/undefined gracefully) * - ⛓️ Fluent and immutable API (chainable methods) * - 🛠️ Common formatting utilities (Capitalization, Truncation) * - 🎲 Generator utilities (Random string, Slugs) * * @example * // Basic Chaining * StringUtils.of(" hello world ") * .trim() * .capitalizeEachWord() * .value(); // "Hello World" * ------------------------------------------------------------ */ /* ------------------------------------------------------------------ * Types * ------------------------------------------------------------------ */ export type StringInput = string | number | null | undefined; /* ------------------------------------------------------------------ * Interfaces * ------------------------------------------------------------------ */ interface IStringUtils { // --- Output & Conversion --- value(): string; toString(): string; toJSON(): string; // --- Manipulations (Chainable) --- upperCase(): IStringUtils; lowerCase(): IStringUtils; capitalizeFirst(): IStringUtils; capitalizeEachWord(): IStringUtils; limit(maxLength: number, suffix?: string): IStringUtils; trim(): IStringUtils; slugify(): IStringUtils; mask(visibleStart: number, visibleEnd: number, maskChar?: string): IStringUtils; // --- Inspection & Fallback --- isEmpty(): boolean; orElse(defaultValue: string): string; } /* ------------------------------------------------------------------ * StringUtils * ------------------------------------------------------------------ */ export class StringUtils implements IStringUtils { private readonly _value: string; /* ---------------------------------------------------------------- * Constructor & Static Initializers * ---------------------------------------------------------------- */ constructor(value?: StringInput) { // Defensive coding: Convert null/undefined/number to string immediately if (value === null || value === undefined) { this._value = ''; } else { this._value = String(value); } } /** * Static factory method for cleaner chaining. * * @param value - The input string, number, or null/undefined. * @example * StringUtils.of("hello").upperCase().value() */ static of(value?: StringInput): StringUtils { return new StringUtils(value); } /** * Generates a random alphanumeric string. * Useful for IDs, tokens, or temporary passwords. * * @param length - Length of the generated string (default: 10). * @example * StringUtils.random(8).value() // "aB9x2Z1m" */ static random(length: number = 10): StringUtils { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let result = ''; for (let i = 0; i < length; i++) { result += chars.charAt(Math.floor(Math.random() * chars.length)); } return new StringUtils(result); } /* ---------------------------------------------------------------- * Core Manipulations * ---------------------------------------------------------------- */ /** * Converts string to UPPERCASE. * * @example * StringUtils.of("hello").upperCase().value() // "HELLO" */ upperCase(): StringUtils { return new StringUtils(this._value.toUpperCase()); } /** * Converts string to lowercase. * * @example * StringUtils.of("HELLO").lowerCase().value() // "hello" */ lowerCase(): StringUtils { return new StringUtils(this._value.toLowerCase()); } /** * Capitalizes only the first letter of the entire string. * Remainder is forced to lowercase (Sentence case). * * @example * StringUtils.of("HELLO world").capitalizeFirst().value() // "Hello world" */ capitalizeFirst(): StringUtils { if (!this._value) return this; const lower = this._value.toLowerCase(); return new StringUtils(lower.charAt(0).toUpperCase() + lower.slice(1)); } /** * Capitalizes the first letter of every word (Title Case). * Automatically handles multiple spaces. * * @example * StringUtils.of("hello world").capitalizeEachWord().value() // "Hello World" * StringUtils.of("hello world").capitalizeEachWord().value() // "Hello World" */ capitalizeEachWord(): StringUtils { if (!this._value) return this; const transformed = this._value .toLowerCase() .split(/\s+/) // Split by any whitespace regex to handle double spaces .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(' '); return new StringUtils(transformed); } /** * Truncates string to a specific length and adds a suffix. * * @param maxLength - The character limit. * @param suffix - The string to append if truncated (default: "..."). * @example * StringUtils.of("Lorem Ipsum").limit(5).value() // "Lorem..." * StringUtils.of("Lorem Ipsum").limit(5, "").value() // "Lorem" */ limit(maxLength: number, suffix: string = '...'): StringUtils { if (this._value.length <= maxLength) return this; return new StringUtils(this._value.substring(0, maxLength) + suffix); } /* ---------------------------------------------------------------- * Utility Extensions * ---------------------------------------------------------------- */ /** * Removes whitespace from both ends of the string. * * @example * StringUtils.of(" data ").trim().value() // "data" */ trim(): StringUtils { return new StringUtils(this._value.trim()); } /** * Converts string to URL-friendly slug. * Removes special characters and replaces spaces with dashes. * * @example * StringUtils.of("Hello World!").slugify().value() // "hello-world" * StringUtils.of("C# & .NET").slugify().value() // "c-net" */ slugify(): StringUtils { const slug = this._value .toLowerCase() .trim() .replace(/[^\w\s-]/g, '') // Remove non-word chars .replace(/[\s_-]+/g, '-') // Replace spaces and underscores with - .replace(/^-+|-+$/g, ''); // Remove leading/trailing - return new StringUtils(slug); } /** * Masks part of the string. Useful for PII (emails/phones). * * @param visibleStart - Number of characters to show at the start. * @param visibleEnd - Number of characters to show at the end. * @param maskChar - The character to use for masking (default: "*"). * @example * StringUtils.of("08123456789").mask(4, 3).value() // "0812****789" */ mask(visibleStart: number = 0, visibleEnd: number = 0, maskChar: string = '*'): StringUtils { if (this._value.length <= visibleStart + visibleEnd) return this; const start = this._value.slice(0, visibleStart); const end = this._value.slice(-visibleEnd); const middle = maskChar.repeat(this._value.length - visibleStart - visibleEnd); return new StringUtils(start + middle + end); } /* ---------------------------------------------------------------- * Inspection & Output * ---------------------------------------------------------------- */ /** * Checks if the string is empty or contains only whitespace. * * @returns true if string is empty or whitespace-only. * @example * StringUtils.of("").isEmpty() // true * StringUtils.of(" ").isEmpty() // true */ isEmpty(): boolean { return this._value.trim().length === 0; } /** * Returns the raw string value. */ value(): string { return this._value; } /** * Returns default value if current string is empty. * * @param defaultValue - The fallback string. * @example * StringUtils.of(null).orElse("N/A") // "N/A" */ orElse(defaultValue: string): string { return this.isEmpty() ? defaultValue : this._value; } /** * Allows default JS string interpolation to work. * * @example * const name = StringUtils.of("John"); * console.log(`Hello ${name}`); // "Hello John" */ toString(): string { return this._value; } /** * Allows JSON.stringify to serialize just the string, not the object wrapper. * * @example * JSON.stringify({ name: StringUtils.of("John") }) // '{"name":"John"}' */ toJSON(): string { return this._value; } }