diff --git a/.changeset/four-socks-relate.md b/.changeset/four-socks-relate.md new file mode 100644 index 0000000000..98eb8c526e --- /dev/null +++ b/.changeset/four-socks-relate.md @@ -0,0 +1,10 @@ +--- +"effect": minor +--- + +added String.truncate + +```ts +import { truncate } from "effect/String" +truncate("Hello World!", { length: 5 }) // "Hello..." +``` diff --git a/packages/effect/src/String.ts b/packages/effect/src/String.ts index ce8e0e424c..a4b092ca23 100644 --- a/packages/effect/src/String.ts +++ b/packages/effect/src/String.ts @@ -747,3 +747,78 @@ const isLineBreak = (char: string): boolean => { const isLineBreak2 = (char0: string, char1: string): boolean => char0.charCodeAt(0) === CR && char1.charCodeAt(0) === LF const linesSeparated = (self: string, stripped: boolean): LinesIterator => new LinesIterator(self, stripped) + +/** + * Truncates a string to a specified length and by default adds the omission string "..." to the end of the string. + * @since 3.12.0 + * + * @example + * import { truncate } from "effect/String"; + * truncate("Hello World!", { length: 5 }); // "Hello..." + */ +export const truncate: { + ( + str: string, + options: { + /** + * The length to truncate the string to. + */ + readonly length: number + + /** + * The separator to use to truncate the string. Use " " to truncate evenly on a word boundary. + */ + readonly separator?: string + + /** + * The omission string to add to the end of the truncated string. Defaults to "...". + */ + readonly omission?: string + } + ): string + + ( + options: { + /** + * The length to truncate the string to. + */ + readonly length: number + + /** + * The separator to use to truncate the string. Use " " to truncate evenly on a word boundary. + */ + readonly separator?: string + + /** + * The omission string to add to the end of the truncated string. Defaults to "...". + */ + readonly omission?: string + } + ): (str: string) => string +} = dual( + 2, + ( + str: string, + options: { + readonly length: number + readonly separator?: string + readonly omission?: string + } + ) => { + if (str.length <= options.length) { + return str + } + + let subString = str.slice(0, options.length) + + if (typeof options.separator === "string") { + subString = subString.slice( + 0, + subString.lastIndexOf(options.separator) + ) + } + + const omission = options.omission ?? "..." + return subString + omission + } +) diff --git a/packages/effect/test/String.test.ts b/packages/effect/test/String.test.ts index 0d50cd135f..dc289f683f 100644 --- a/packages/effect/test/String.test.ts +++ b/packages/effect/test/String.test.ts @@ -280,4 +280,13 @@ describe("String", () => { deepStrictEqual(Array.from(result), ["", "$", " $Hello,", " $World!", " $"]) }) }) + + it("truncate", () => { + expect(S.truncate("Hello World!", { length: 5 })).toBe("Hello...") + expect(S.truncate("Hello World!", { length: 5, omission: "" })).toBe("Hello") + expect(S.truncate("Hello World!", { length: 7 })).toBe("Hello W...") + expect(S.truncate("Hello World!", { length: 7, separator: " " })).toBe( + "Hello..." + ) + }) })