diff --git a/CHANGELOG.md b/CHANGELOG.md index c15e1c6..c9e5912 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- `splitLine` string utility functions + +## [2.1.0] - 2025-08-26 + +### Added + +- `ltrim`, `ltrim` and `ltrim` string type utility functions + ## [2.0.0] - 2025-07-29 ### Added diff --git a/src/lib/string.spec.ts b/src/lib/string.spec.ts index 94fc71e..feac236 100644 --- a/src/lib/string.spec.ts +++ b/src/lib/string.spec.ts @@ -1,4 +1,4 @@ -import { isNullOrEmpty, isNullOrWhitespace, capitalize, uncapitalize, truncate } from "./string"; +import { isNullOrEmpty, isNullOrWhitespace, capitalize, uncapitalize, truncate, ltrim, rtrim, trim, splitLine } from "./string"; describe("string tests", () => { test.each([ @@ -120,4 +120,49 @@ describe("string tests", () => { ])("truncate without suffix parameter", (value, maxLength, expected) => { expect(truncate(value, maxLength)).toBe(expected); }); + + test.each([ + ["", " ", ""], + ["", "", ""], + ["hello world", "", "hello world"], + [" hello world", " ", "hello world"], + ])("left trim", (haystack, needle, expected) => { + expect(ltrim(haystack, needle)).toBe(expected); + }); + + test.each([ + ["", " ", ""], + ["", "", ""], + ["hello world", "hello world", ""], + ["hello world", "", "hello world"], + ["hello world ", " ", "hello world"], + ])("right trim", (haystack, needle, expected) => { + expect(rtrim(haystack, needle)).toBe(expected); + }); + + test.each([ + ["", " ", ""], + ["", "", ""], + ["hello world", "", "hello world"], + [" hello world ", " ", "hello world"], + ])("trim", (haystack, needle, expected) => { + expect(trim(haystack, needle)).toBe(expected); + }); + + test.each([["hello world", ["hello world"]]])("splitLine with single line", (str, expected) => { + expect(splitLine(str)).toStrictEqual(expected); + }); + + test.each([ + ["hello world", ["hello world"]], + ["hello world\nhello world\nhello world", ["hello world", "hello world", "hello world"]], + ["hello world\rhello world\rhello world", ["hello world", "hello world", "hello world"]], + ["hello world\r\nhello world\r\nhello world", ["hello world", "hello world", "hello world"]], + ])("splitLine", (str, expected) => { + expect(splitLine(str)).toStrictEqual(expected); + }); + + test.each([["", [""]]])("splitLine with empty strings", (str, expected) => { + expect(splitLine(str)).toStrictEqual(expected); + }); }); diff --git a/src/lib/string.ts b/src/lib/string.ts index c0666b3..5126678 100644 --- a/src/lib/string.ts +++ b/src/lib/string.ts @@ -64,3 +64,74 @@ export function truncate(value: string | undefined, maxLength: number, suffix = return `${value.slice(0, maxLength)}${suffix}`; } + +/** + * Removes all occurrences of needle from the start of haystack + * @param haystack string to trim + * @param needle the thing to trim + * @returns the string trimmed from the left side + */ +export function ltrim(haystack: string, needle: string): string { + const needleLength = needle.length; + if (needleLength === 0 || haystack.length === 0) { + return haystack; + } + + let offset = 0; + + while (haystack.indexOf(needle, offset) === offset) { + offset = offset + needleLength; + } + return haystack.slice(offset); +} + +/** + * Removes all occurrences of needle from the end of haystack + * @param haystack string to trim + * @param needle the thing to trim + * @returns the string trimmed from the right side + */ +export function rtrim(haystack: string, needle: string): string { + const needleLength = needle.length, + haystackLength = haystack.length; + + if (needleLength === 0 || haystackLength === 0) { + return haystack; + } + + let offset = haystackLength, + idx = -1; + + while (true) { + idx = haystack.lastIndexOf(needle, offset - 1); + if (idx === -1 || idx + needleLength !== offset) { + break; + } + if (idx === 0) { + return ""; + } + offset = idx; + } + + return haystack.slice(0, offset); +} + +/** + * Removes all occurrences of needle from the start and the end of haystack + * @param haystack string to trim + * @param needle the thing to trim + * @returns the string trimmed from the right and left side + */ +export function trim(haystack: string, needle: string): string { + const trimmed = ltrim(haystack, needle); + return rtrim(trimmed, needle); +} + +/** + * Splits the string at line breaks + * @param str the string to split + * @returns the individual lines as an array + */ +export function splitLine(str: string): string[] { + return str.split(/\r\n|\r|\n/); +}