diff --git a/benchmark/string/truncate.bench.ts b/benchmark/string/truncate.bench.ts index c6a3bd44..a7338070 100644 --- a/benchmark/string/truncate.bench.ts +++ b/benchmark/string/truncate.bench.ts @@ -5,17 +5,17 @@ import { bench, describe } from "vitest"; import { randomStringArray } from "../testData.js"; describe("truncate", () => { - const stringArray = randomStringArray(200); + const stringArray = randomStringArray(500); bench("moderndash", () => { for (const str of stringArray) { - truncate(str); + truncate(str, { length: 8 }); } }); bench("lodash", () => { for (const str of stringArray) { - lodashVersion(str); + lodashVersion(str, { length: 8 }); } }); }); diff --git a/package/src/string/truncate.ts b/package/src/string/truncate.ts index 35c4be69..ba7c6a91 100644 --- a/package/src/string/truncate.ts +++ b/package/src/string/truncate.ts @@ -1,61 +1,44 @@ -type Options = { - /** - * The maximum string length. - * - * Default: 30 - */ - length?: number; - - /** - * The string to indicate text is omitted. - * - * Also named [ellipsis](https://developer.mozilla.org/en-US/docs/Web/CSS/text-overflow) - * - * Default: "...", you might want to use "…" (… U+02026) instead - */ - omission?: string; - - /** - * The separator pattern to truncate to. - * - * Default: none - */ - separator?: string; -}; - /** * Truncates a string if it's longer than the given maximum length. - * The last characters of the truncated string are replaced with the omission + * The last characters of the truncated string are replaced with the ellipsis * string which defaults to "...". * + * @example + * truncate("Hello, world!", { length: 5 }) + * // => "Hello..." + * + * truncate("Hello, world!", { length: 5, ellipsis: " [...]" }) + * // => "Hello [...]" + * + * truncate("Hello, world!", { length: 5, separator: " " }) + * // => "Hello, ..." + * * @param str The string to truncate * @param options The options object + * @param options.length The maximum string length (default: 30) + * @param options.ellipsis The string to indicate text is omitted (default: "...") + * @param options.separator The separator pattern to truncate to (default: none) * @returns The truncated string */ -export function truncate(str: string, options?: Options) { - // https://stackoverflow.com/q/1199352 - // https://github.com/Maggi64/moderndash/issues/155 - // https://lodash.com/docs/4.17.15#truncate - const { length = 30, omission = "...", separator } = options ?? {}; +export function truncate(str: string, options?: { length?: number; ellipsis?: string; separator?: string }): string { + const { length = 30, ellipsis = "...", separator } = options ?? {}; + if (str.length <= length) return str; - if (str.length <= length) { - return str; - } + const end = length - ellipsis.length; + + if (end < 1) + return ellipsis; + + // Actually long enough to truncate the string + let truncated = str.slice(0, end); - let maxLength = length - omission.length; - if (maxLength < 0) { - maxLength = 0; + if (separator) { + const sepIndex = truncated.lastIndexOf(separator); + if (sepIndex > -1) { + truncated = truncated.slice(0, sepIndex); + } } - const subString = str.slice( - 0, - // FYI .slice() is OK if maxLength > text.length - maxLength - ); - return ( - (separator - ? subString.slice(0, subString.lastIndexOf(separator)) - : subString) + omission - ); + return truncated + ellipsis; } diff --git a/package/test/string/truncate.test.ts b/package/test/string/truncate.test.ts index 37d6eff4..6f6d6855 100644 --- a/package/test/string/truncate.test.ts +++ b/package/test/string/truncate.test.ts @@ -19,13 +19,13 @@ it("should truncate string the given length", () => { }); it("should support a `omission` option", () => { - expect(truncate(string, { omission: " [...]" })).toBe( + expect(truncate(string, { ellipsis: " [...]" })).toBe( "hi-diddly-ho there, neig [...]" ); }); it("should support empty `omission` option", () => { - expect(truncate(string, { omission: "" })).toBe( + expect(truncate(string, { ellipsis: "" })).toBe( "hi-diddly-ho there, neighborin" ); }); @@ -41,9 +41,9 @@ it("should support a `separator` option", () => { }); it("should treat negative `length` as `0`", () => { - [0, -2].forEach((length) => { + for (const length of [0, -2]) { expect(truncate(string, { length })).toBe("..."); - }); + } }); it("should work as an iteratee for methods like `_.map`", () => {