Skip to content

Commit

Permalink
feat(text/unstable): add strip function (denoland#6265)
Browse files Browse the repository at this point in the history
  • Loading branch information
lionel-rowe committed Dec 20, 2024
1 parent 4989ba7 commit f4cc4f2
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 0 deletions.
1 change: 1 addition & 0 deletions text/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
102 changes: 102 additions & 0 deletions text/unstable_strip.ts
Original file line number Diff line number Diff line change
@@ -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("");
}
31 changes: 31 additions & 0 deletions text/unstable_strip_test.ts
Original file line number Diff line number Diff line change
@@ -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");
});
});

0 comments on commit f4cc4f2

Please sign in to comment.