From 6bf9d07e5f462a80355f82a0d8d20ac3ff8208bd Mon Sep 17 00:00:00 2001 From: Dominic Baur Date: Tue, 26 Aug 2025 09:10:35 +0200 Subject: [PATCH 1/6] Add string utility functions: ltrim, rtrim, trim (neolution-ch#29) --- src/lib/string.spec.ts | 62 +++++++++++++++++++++++++++++++++++++- src/lib/string.ts | 68 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 1 deletion(-) diff --git a/src/lib/string.spec.ts b/src/lib/string.spec.ts index 94fc71e..ae26d18 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 } from "./string"; describe("string tests", () => { test.each([ @@ -120,4 +120,64 @@ describe("string tests", () => { ])("truncate without suffix parameter", (value, maxLength, expected) => { expect(truncate(value, maxLength)).toBe(expected); }); + + test.each([ + [null as unknown as string, " ", null], + [undefined as unknown as string, " ", undefined], + [" hello world", " ", "hello world"], + [" hello world", " ", "hello world"], + [" hello world", " ", "hello world"], + ])("left trim", (haystack, needle, expected) => { + expect(ltrim(haystack, needle)).toBe(expected); + }); + + test.each([ + [null as unknown as string, " ", null], + [undefined as unknown as string, " ", undefined], + [" hello world", "", " hello world"], + [" hello world", "", " hello world"], + [" hello world", "", " hello world"], + ])("left trim without needle", (haystack, needle, expected) => { + expect(ltrim(haystack, needle)).toBe(expected); + }); + + test.each([ + [null as unknown as string, " ", null], + [undefined as unknown as string, " ", undefined], + ["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([ + [null as unknown as string, " ", null], + [undefined as unknown as string, " ", undefined], + ["hello world ", "", "hello world "], + ["hello world ", "", "hello world "], + ["hello world ", "", "hello world "], + ])("right trim without needle", (haystack, needle, expected) => { + expect(rtrim(haystack, needle)).toBe(expected); + }); + + test.each([ + [null as unknown as string, " ", null], + [undefined as unknown as string, " ", undefined], + [" hello world ", " ", "hello world"], + [" hello world ", " ", "hello world"], + [" hello world ", " ", "hello world"], + ])("trim", (haystack, needle, expected) => { + expect(trim(haystack, needle)).toBe(expected); + }); + + test.each([ + [null as unknown as string, " ", null], + [undefined as unknown as string, " ", undefined], + [" hello world ", "", " hello world "], + [" hello world ", "", " hello world "], + [" hello world ", "", " hello world "], + ])("trim without needle", (haystack, needle, expected) => { + expect(trim(haystack, needle)).toBe(expected); + }); }); diff --git a/src/lib/string.ts b/src/lib/string.ts index c0666b3..6ec2bac 100644 --- a/src/lib/string.ts +++ b/src/lib/string.ts @@ -64,3 +64,71 @@ 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 { + if (!haystack || !needle) return haystack; + + 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 { + if (!haystack || !needle) { + return haystack; + } + + const needleLength = needle.length, + haystackLen = haystack.length; + + if (needleLength === 0 || haystackLen === 0) { + return haystack; + } + + let offset = haystackLen, + 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); +} From f50f03c9fb59524f59e46471ea22d1a7ea693d51 Mon Sep 17 00:00:00 2001 From: Dominic Baur Date: Tue, 26 Aug 2025 10:25:15 +0200 Subject: [PATCH 2/6] Add string utility functions: splitLine (neolution-ch#29) --- src/lib/string.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/lib/string.ts b/src/lib/string.ts index 6ec2bac..6c56f49 100644 --- a/src/lib/string.ts +++ b/src/lib/string.ts @@ -132,3 +132,12 @@ 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/); +} From feddcf06a62c5a4f3b8b940e9f743d0e0bf80913 Mon Sep 17 00:00:00 2001 From: Dominic Baur Date: Tue, 26 Aug 2025 10:27:03 +0200 Subject: [PATCH 3/6] Add tests for string utility functions: splitLine --- src/lib/string.spec.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/lib/string.spec.ts b/src/lib/string.spec.ts index ae26d18..395ee1d 100644 --- a/src/lib/string.spec.ts +++ b/src/lib/string.spec.ts @@ -1,4 +1,4 @@ -import { isNullOrEmpty, isNullOrWhitespace, capitalize, uncapitalize, truncate, ltrim, rtrim, trim } from "./string"; +import { isNullOrEmpty, isNullOrWhitespace, capitalize, uncapitalize, truncate, ltrim, rtrim, trim, splitLine } from "./string"; describe("string tests", () => { test.each([ @@ -180,4 +180,21 @@ describe("string tests", () => { ])("trim without needle", (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); + }); }); From 6fa20ae138ec01723a074634fb32c9d27bfbddbb Mon Sep 17 00:00:00 2001 From: Dominic Baur Date: Tue, 26 Aug 2025 10:35:51 +0200 Subject: [PATCH 4/6] Add changelog entry --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c15e1c6..9fb25f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.2.0] - 2025-08-26 + +### 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 From 7adb32088baf75bbd44a2b9afbb945c7d5dfdb9d Mon Sep 17 00:00:00 2001 From: Dominic Baur Date: Tue, 26 Aug 2025 13:48:55 +0200 Subject: [PATCH 5/6] Fix string utility functions ltrim, rtrim null check --- src/lib/string.spec.ts | 52 ++++++++---------------------------------- src/lib/string.ts | 12 +++------- 2 files changed, 13 insertions(+), 51 deletions(-) diff --git a/src/lib/string.spec.ts b/src/lib/string.spec.ts index 395ee1d..feac236 100644 --- a/src/lib/string.spec.ts +++ b/src/lib/string.spec.ts @@ -122,65 +122,33 @@ describe("string tests", () => { }); test.each([ - [null as unknown as string, " ", null], - [undefined as unknown as string, " ", undefined], + ["", " ", ""], + ["", "", ""], + ["hello world", "", "hello world"], [" hello world", " ", "hello world"], - [" hello world", " ", "hello world"], - [" hello world", " ", "hello world"], ])("left trim", (haystack, needle, expected) => { expect(ltrim(haystack, needle)).toBe(expected); }); test.each([ - [null as unknown as string, " ", null], - [undefined as unknown as string, " ", undefined], - [" hello world", "", " hello world"], - [" hello world", "", " hello world"], - [" hello world", "", " hello world"], - ])("left trim without needle", (haystack, needle, expected) => { - expect(ltrim(haystack, needle)).toBe(expected); - }); - - test.each([ - [null as unknown as string, " ", null], - [undefined as unknown as string, " ", undefined], + ["", " ", ""], + ["", "", ""], + ["hello world", "hello world", ""], + ["hello world", "", "hello world"], ["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([ - [null as unknown as string, " ", null], - [undefined as unknown as string, " ", undefined], - ["hello world ", "", "hello world "], - ["hello world ", "", "hello world "], - ["hello world ", "", "hello world "], - ])("right trim without needle", (haystack, needle, expected) => { - expect(rtrim(haystack, needle)).toBe(expected); - }); - - test.each([ - [null as unknown as string, " ", null], - [undefined as unknown as string, " ", undefined], + ["", " ", ""], + ["", "", ""], + ["hello world", "", "hello world"], [" hello world ", " ", "hello world"], - [" hello world ", " ", "hello world"], - [" hello world ", " ", "hello world"], ])("trim", (haystack, needle, expected) => { expect(trim(haystack, needle)).toBe(expected); }); - test.each([ - [null as unknown as string, " ", null], - [undefined as unknown as string, " ", undefined], - [" hello world ", "", " hello world "], - [" hello world ", "", " hello world "], - [" hello world ", "", " hello world "], - ])("trim without needle", (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); }); diff --git a/src/lib/string.ts b/src/lib/string.ts index 6c56f49..5126678 100644 --- a/src/lib/string.ts +++ b/src/lib/string.ts @@ -72,8 +72,6 @@ export function truncate(value: string | undefined, maxLength: number, suffix = * @returns the string trimmed from the left side */ export function ltrim(haystack: string, needle: string): string { - if (!haystack || !needle) return haystack; - const needleLength = needle.length; if (needleLength === 0 || haystack.length === 0) { return haystack; @@ -94,18 +92,14 @@ export function ltrim(haystack: string, needle: string): string { * @returns the string trimmed from the right side */ export function rtrim(haystack: string, needle: string): string { - if (!haystack || !needle) { - return haystack; - } - const needleLength = needle.length, - haystackLen = haystack.length; + haystackLength = haystack.length; - if (needleLength === 0 || haystackLen === 0) { + if (needleLength === 0 || haystackLength === 0) { return haystack; } - let offset = haystackLen, + let offset = haystackLength, idx = -1; while (true) { From 82a094ecd53239b22bb5102f0cbeb966d3101ac0 Mon Sep 17 00:00:00 2001 From: Dominic Baur Date: Tue, 26 Aug 2025 14:24:40 +0200 Subject: [PATCH 6/6] Fix changelog --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fb25f3..c9e5912 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [2.2.0] - 2025-08-26 - ### Added - `splitLine` string utility functions