diff --git a/text/deno.json b/text/deno.json index 848ec9215325..892ec81f24b5 100644 --- a/text/deno.json +++ b/text/deno.json @@ -6,6 +6,7 @@ "./closest-string": "./closest_string.ts", "./compare-similarity": "./compare_similarity.ts", "./levenshtein-distance": "./levenshtein_distance.ts", + "./slugify": "./slugify.ts", "./to-camel-case": "./to_camel_case.ts", "./to-constant-case": "./to_constant_case.ts", "./to-kebab-case": "./to_kebab_case.ts", diff --git a/text/mod.ts b/text/mod.ts index 5ca47c00d43f..80a485cfb644 100644 --- a/text/mod.ts +++ b/text/mod.ts @@ -28,3 +28,4 @@ export * from "./to_constant_case.ts"; export * from "./to_kebab_case.ts"; export * from "./to_pascal_case.ts"; export * from "./to_snake_case.ts"; +export * from "./slugify.ts"; diff --git a/text/slugify.ts b/text/slugify.ts new file mode 100644 index 000000000000..f93feb6e5198 --- /dev/null +++ b/text/slugify.ts @@ -0,0 +1,30 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +/** + * **UNSTABLE**: New API, yet to be vetted. + * + * Converts a string into {@link https://en.wikipedia.org/wiki/Clean_URL#Slug a slug}. + * + * @example Usage + * ```ts + * import { slugify } from "@std/text/slugify"; + * import { assertEquals } from "@std/assert"; + * + * assertEquals(slugify("hello world"), "hello-world"); + * assertEquals(slugify("déjà vu"), "deja-vu"); + * ``` + * + * @param input The string that is going to be converted into a slug + * @returns The string as a slug + * + * @experimental + */ +export function slugify(input: string): string { + return input + .trim() + .normalize("NFD") + .replaceAll(/[^a-zA-Z0-9\s-]/g, "") + .replaceAll(/\s+|-+/g, "-") + .replaceAll(/^-+|-+$/g, "") + .toLowerCase(); +} diff --git a/text/slugify_test.ts b/text/slugify_test.ts new file mode 100644 index 000000000000..e93313cd1bc9 --- /dev/null +++ b/text/slugify_test.ts @@ -0,0 +1,58 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { assertEquals } from "@std/assert/equals"; +import { slugify } from "./slugify.ts"; + +Deno.test("slugify() returns kebabcase", () => { + assertEquals(slugify("hello world"), "hello-world"); +}); +Deno.test("slugify() returns lowercase", () => { + assertEquals(slugify("Hello World"), "hello-world"); +}); + +Deno.test("slugify() handles whitespaces", () => { + assertEquals(slugify(" Hello World "), "hello-world"); + assertEquals(slugify("Hello\tWorld"), "hello-world"); + assertEquals(slugify("Hello\nWorld"), "hello-world"); + assertEquals(slugify("Hello\r\nWorld"), "hello-world"); +}); + +Deno.test("slugify() replaces diacritic characters", () => { + assertEquals(slugify("déjà vu"), "deja-vu"); + assertEquals(slugify("Cliché"), "cliche"); + assertEquals(slugify("façade"), "facade"); + assertEquals(slugify("résumé"), "resume"); +}); + +Deno.test("slugify() handles dashes", () => { + assertEquals(slugify("-Hello-World-"), "hello-world"); + assertEquals(slugify("--Hello--World--"), "hello-world"); +}); + +Deno.test("slugify() handles empty String", () => { + assertEquals(slugify(""), ""); +}); + +Deno.test("slugify() removes unknown special characters", () => { + assertEquals(slugify("hello ~ world"), "hello-world"); + + assertEquals( + slugify("Elon Musk considers move to Mars"), + "elon-musk-considers-move-to-mars", + ); + assertEquals( + slugify("Fintech startups raised $34B in 2019"), + "fintech-startups-raised-34b-in-2019", + ); + assertEquals( + slugify("Shopify joins Facebook’s cryptocurrency Libra Association"), + "shopify-joins-facebooks-cryptocurrency-libra-association", + ); + assertEquals( + slugify("What is a slug and how to optimize it?"), + "what-is-a-slug-and-how-to-optimize-it", + ); + assertEquals( + slugify("Bitcoin soars past $33,000, its highest ever"), + "bitcoin-soars-past-33000-its-highest-ever", + ); +});