diff --git a/CHANGELOG.md b/CHANGELOG.md index 3703841..3ec93da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `splitLines` string utility function +- `trimStart`, `trimEnd` and `trim` string type utility functions ### Changed diff --git a/src/lib/string.spec.ts b/src/lib/string.spec.ts index f9bb4ce..c76adcc 100644 --- a/src/lib/string.spec.ts +++ b/src/lib/string.spec.ts @@ -1,4 +1,4 @@ -import { isNullOrEmpty, isNullOrWhitespace, capitalize, uncapitalize, truncate, splitLines } from "./string"; +import { isNullOrEmpty, isNullOrWhitespace, capitalize, uncapitalize, truncate, splitLines, trimStart, trimEnd, trim } from "./string"; describe("string tests", () => { test.each([ @@ -121,6 +121,42 @@ describe("string tests", () => { expect(truncate(value, maxLength)).toBe(expected); }); + test.each([ + [null as unknown as string, " ", null as unknown as string], + [undefined as unknown as string, " ", undefined as unknown as string], + ["hello world", "hello world", ""], + ["hello world", " ", "hello world"], + [" hello world", " ", "hello world"], + ["hello world hello world", "hello world", " hello world"], + ["hello worldhello world", "hello world", ""], + ])("left trim", (haystack, needle, expected) => { + expect(trimStart(haystack, needle)).toBe(expected); + }); + + test.each([ + [null as unknown as string, " ", null as unknown as string], + [undefined as unknown as string, " ", undefined as unknown as string], + ["hello world", "hello world", ""], + ["hello world ", " ", "hello world"], + ["hello world", " ", "hello world"], + ["hello world hello world", "hello world", "hello world "], + ["hello worldhello world", "hello world", ""], + ])("right trim", (haystack, needle, expected) => { + expect(trimEnd(haystack, needle)).toBe(expected); + }); + + test.each([ + [null as unknown as string, " ", null as unknown as string], + [undefined as unknown as string, " ", undefined as unknown as string], + ["hello world", "", "hello world"], + [" hello world ", " ", "hello world"], + ["hello world ", " ", "hello world"], + [" hello world", " ", "hello world"], + ["hello worldhello world", "hello world", ""], + ])("trim", (haystack, needle, expected) => { + expect(trim(haystack, needle)).toBe(expected); + }); + test.each([ ["", false, false, []], [null as unknown as string, false, false, []], diff --git a/src/lib/string.ts b/src/lib/string.ts index 3b9e07e..57e13a4 100644 --- a/src/lib/string.ts +++ b/src/lib/string.ts @@ -65,6 +65,64 @@ 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 trimStart(haystack: string, needle: string): string { + if (isNullOrEmpty(haystack) || isNullOrEmpty(needle)) { + return haystack; + } + + let offset = 0; + + while (haystack.indexOf(needle, offset) === offset) { + offset = offset + needle.length; + } + 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 trimEnd(haystack: string, needle: string): string { + if (isNullOrEmpty(haystack) || isNullOrEmpty(needle)) { + return haystack; + } + + let offset = haystack.length, + idx = -1; + + while (true) { + idx = haystack.lastIndexOf(needle, offset - 1); + if (idx === -1 || idx + needle.length !== 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 = trimStart(haystack, needle); + return trimEnd(trimmed, needle); +} + /** * Splits the string at line breaks * @param str the string to split