From f4cc4f249fd18d3dff83a64c95c875ec788662ee Mon Sep 17 00:00:00 2001 From: lionel-rowe Date: Fri, 20 Dec 2024 20:42:55 +0800 Subject: [PATCH] feat(text/unstable): add `strip` function (#6265) --- text/deno.json | 1 + text/unstable_strip.ts | 102 ++++++++++++++++++++++++++++++++++++ text/unstable_strip_test.ts | 31 +++++++++++ 3 files changed, 134 insertions(+) create mode 100644 text/unstable_strip.ts create mode 100644 text/unstable_strip_test.ts diff --git a/text/deno.json b/text/deno.json index 9c475257d44d..2da7549271d6 100644 --- a/text/deno.json +++ b/text/deno.json @@ -7,6 +7,7 @@ "./compare-similarity": "./compare_similarity.ts", "./levenshtein-distance": "./levenshtein_distance.ts", "./unstable-slugify": "./unstable_slugify.ts", + "./unstable-strip": "./unstable_strip.ts", "./to-camel-case": "./to_camel_case.ts", "./unstable-to-constant-case": "./unstable_to_constant_case.ts", "./to-kebab-case": "./to_kebab_case.ts", diff --git a/text/unstable_strip.ts b/text/unstable_strip.ts new file mode 100644 index 000000000000..8c012322d99c --- /dev/null +++ b/text/unstable_strip.ts @@ -0,0 +1,102 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { escape } from "@std/regexp"; + +export type StripOptions = { + /** + * If `true`, all occurrences will be stripped from the start of the string. + * If a number, the specified number of occurrences will be stripped from the start. + * + * @default {true} // if `end` option is omitted + * @default {false} // if `end` option is specified + */ + start?: boolean | number; + /** + * If `true`, all occurrences will be stripped from the end of the string. + * If a number, the specified number of occurrences will be stripped from the end. + * + * @default {true} // if `start` option is omitted + * @default {false} // if `start` option is specified + */ + end?: boolean | number; +}; + +/** + * Strips the specified pattern from the start and/or end of the string. + * + * @param str The string to strip from. + * @param pattern The pattern to strip from the string. + * @param options An object containing options for the strip operation. + * + * @example Strip both start and end + * ```ts + * import { strip } from "@std/text/unstable-strip"; + * import { assertEquals } from "@std/assert"; + * assertEquals(strip("---x---", "-"), "x"); + * ``` + * + * @example Strip start only + * ```ts + * import { strip } from "@std/text/unstable-strip"; + * import { assertEquals } from "@std/assert"; + * assertEquals(strip("---x---", "-", { start: true }), "x---"); + * ``` + * + * @example Strip end only + * ```ts + * import { strip } from "@std/text/unstable-strip"; + * import { assertEquals } from "@std/assert"; + * assertEquals(strip("---x---", "-", { end: true }), "---x"); + * ``` + * + * @example Strip a given number of occurrences + * ```ts + * import { strip } from "@std/text/unstable-strip"; + * import { assertEquals } from "@std/assert"; + * assertEquals(strip("---x---", "-", { start: 2, end: 1 }), "-x--"); + * ``` + * + * @example Strip using a regexp + * ```ts + * import { strip } from "@std/text/unstable-strip"; + * import { assertEquals } from "@std/assert"; + * assertEquals(strip("-_-x-_-", /[-_]/), "x"); + * ``` + */ +export function strip( + str: string, + pattern: string | RegExp, + options?: StripOptions, +): string { + const source = typeof pattern === "string" ? escape(pattern) : pattern.source; + const flags = typeof pattern === "string" ? "" : getFlags(pattern); + + const start = options?.start ?? (options?.end == null ? true : false); + const end = options?.end ?? (options?.start == null ? true : false); + + let prev = str; + + for ( + const [option, regex] of [ + [start, new RegExp(`^${source}`, flags)], + [end, new RegExp(`${source}$`, flags)], + ] as const + ) { + const count = typeof option === "number" ? option : option ? Infinity : 0; + + for (let i = 0; i < count; ++i) { + str = str.replace(regex, ""); + if (str === prev) break; + prev = str; + } + } + + return str; +} + +function getFlags(re: RegExp): string { + const flags = new Set(re.flags); + flags.delete("g"); + flags.delete("y"); + + return [...flags.keys()].join(""); +} diff --git a/text/unstable_strip_test.ts b/text/unstable_strip_test.ts new file mode 100644 index 000000000000..18d454fb1c09 --- /dev/null +++ b/text/unstable_strip_test.ts @@ -0,0 +1,31 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { assertEquals } from "@std/assert"; +import { strip } from "./unstable_strip.ts"; + +Deno.test("strip()", async (t) => { + await t.step("strips single-char prefixes/suffixes", () => { + assertEquals(strip("..x.x..", "."), "x.x"); + + assertEquals(strip("..x.x..", ".", { start: true }), "x.x.."); + assertEquals(strip("..x.x..", ".", { end: true }), "..x.x"); + + assertEquals(strip("..x.x..", ".", { end: 0 }), "..x.x.."); + assertEquals(strip("..x.x..", ".", { end: 1 }), "..x.x."); + assertEquals(strip("..x.x..", ".", { end: 2 }), "..x.x"); + assertEquals(strip("..x.x..", ".", { end: 3 }), "..x.x"); + + assertEquals(strip("..x.x..", ".", { start: 0 }), "..x.x.."); + assertEquals(strip("..x.x..", ".", { start: 1 }), ".x.x.."); + assertEquals(strip("..x.x..", ".", { start: 2 }), "x.x.."); + assertEquals(strip("..x.x..", ".", { start: 3 }), "x.x.."); + }); + + await t.step("strips mult-char prefixes/suffixes", () => { + assertEquals(strip("._.._._.x.x._._.._.", "._."), "_.x.x._"); + }); + + await t.step("strips prefixes/suffixes by regex pattern", () => { + assertEquals(strip("!@#$%x.x!@#$%", /./), ""); + assertEquals(strip("!@#$%x.x!@#$%", /[^\p{L}\p{M}\p{N}]+/u), "x.x"); + }); +});