Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(path/unstable): support URL as 1st arg of basename() #5900

Merged
merged 2 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 32 additions & 3 deletions path/basename.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,37 @@
*
* @returns The basename of the path.
*/
export function basename(path: string, suffix = ""): string {
export function basename(path: string, suffix?: string): string;
/**
* Return the last portion of a path.
*
* @experimental **UNSTABLE**: New API, yet to be vetted.
*
* The trailing directory separators are ignored, and optional suffix is
* removed.
*
* @example Usage
* ```ts
* import { basename } from "@std/path/basename";
* import { assertEquals } from "@std/assert";
*
* if (Deno.build.os === "windows") {
* assertEquals(basename(new URL("file:///C:/user/Documents/image.png")), "image.png");
* } else {
* assertEquals(basename(new URL("file:///home/user/Documents/image.png")), "image.png");
* }
* ```
*
* @param path Path to extract the name from.
* @param suffix Suffix to remove from extracted name.
*
* @returns The basename of the path.
*/
export function basename(path: string | URL, suffix?: string): string;
export function basename(path: string | URL, suffix = ""): string {
return isWindows
? windowsBasename(path, suffix)
: posixBasename(path, suffix);
// deno-lint-ignore no-explicit-any
? windowsBasename(path as any, suffix)
// deno-lint-ignore no-explicit-any
: posixBasename(path as any, suffix);

Check warning on line 63 in path/basename.ts

View check run for this annotation

Codecov / codecov/patch

path/basename.ts#L63

Added line #L63 was not covered by tests
}
43 changes: 38 additions & 5 deletions path/basename_test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// Copyright the Browserify authors. MIT License.
// Ported from https://github.com/browserify/path-browserify/
import { assertEquals } from "@std/assert";
import { assertEquals, assertThrows } from "@std/assert";
import { basename } from "./basename.ts";
import * as posix from "./posix/mod.ts";
import * as windows from "./windows/mod.ts";
Expand Down Expand Up @@ -58,6 +58,15 @@ const POSIX_TESTSUITE = [
[["///"], "/"],
[["///", "bbb"], "/"],
[["//", "bbb"], "/"],
[[new URL("file:///dir/basename.ext")], "basename.ext"],
[[new URL("file:///basename.ext"), ".ext"], "basename"],
[[new URL("file:///dir/basename.ext")], "basename.ext"],
[[new URL("file:///aaa/bbb/")], "bbb"],
[[new URL("file:///aaa/bbb"), "b"], "bb"],
[[new URL("file:///aaa/bbb"), "bb"], "b"],
[[new URL("file:///aaa/bbb"), "bbb"], "bbb"],
[[new URL("file:///aaa/bbb"), "a/bbb"], "bbb"],
[[new URL("file://///a")], "a"],
] as const;

const WIN32_TESTSUITE = [
Expand All @@ -84,6 +93,10 @@ const WIN32_TESTSUITE = [
[["C:basename.ext\\\\"], "basename.ext"],
[["C:foo"], "foo"],
[["file:stream"], "file:stream"],
[[new URL("file:///")], "\\"],
[[new URL("file:///C:/")], "\\"],
[[new URL("file:///C:/aaa")], "aaa"],
[[new URL("file://///")], "\\"],
] as const;

Deno.test("posix.basename()", function () {
Expand All @@ -92,7 +105,8 @@ Deno.test("posix.basename()", function () {
}

for (const [[name, suffix], expected] of POSIX_TESTSUITE) {
assertEquals(posix.basename(name, suffix), expected);
// deno-lint-ignore no-explicit-any
assertEquals(posix.basename(name as any, suffix), expected);
}

// On unix a backslash is just treated as any other character.
Expand All @@ -114,17 +128,36 @@ Deno.test("posix.basename()", function () {
);
});

Deno.test("posix.basename() throws with non-file URL", () => {
assertThrows(
() => posix.basename(new URL("https://deno.land/")),
TypeError,
'URL must be a file URL: received "https:"',
);
});

Deno.test("windows.basename()", function () {
for (const [[name, suffix], expected] of WIN32_TESTSUITE) {
assertEquals(windows.basename(name, suffix), expected);
// deno-lint-ignore no-explicit-any
assertEquals(windows.basename(name as any, suffix), expected);
}

// windows should pass all "forward slash" posix tests as well.
for (const [[name, suffix], expected] of COREUTILS_TESTSUITE) {
assertEquals(windows.basename(name, suffix), expected);
// deno-lint-ignore no-explicit-any
assertEquals(windows.basename(name as any, suffix), expected);
}

for (const [[name, suffix], expected] of POSIX_TESTSUITE) {
assertEquals(windows.basename(name, suffix), expected);
// deno-lint-ignore no-explicit-any
assertEquals(windows.basename(name as any, suffix), expected);
}
});

Deno.test("windows.basename() throws with non-file URL", () => {
assertThrows(
() => windows.basename(new URL("https://deno.land/")),
TypeError,
'URL must be a file URL: received "https:"',
);
});
26 changes: 16 additions & 10 deletions path/posix/basename.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from "../_common/basename.ts";
import { stripTrailingSeparators } from "../_common/strip_trailing_separators.ts";
import { isPosixPathSeparator } from "./_util.ts";
import { fromFileUrl } from "./from_file_url.ts";

/**
* Return the last portion of a `path`.
Expand All @@ -23,28 +24,33 @@ import { isPosixPathSeparator } from "./_util.ts";
* assertEquals(basename("/home/user/Documents/image.png", ".png"), "image");
* ```
*
* @example Working with URLs
* @param path The path to extract the name from.
* @param suffix The suffix to remove from extracted name.
* @returns The extracted name.
*/
export function basename(path: string, suffix?: string): string;
/**
* Return the last portion of a `path`.
* Trailing directory separators are ignored, and optional suffix is removed.
*
* Note: This function doesn't automatically strip hash and query parts from
* URLs. If your URL contains a hash or query, remove them before passing the
* URL to the function. This can be done by passing the URL to `new URL(url)`,
* and setting the `hash` and `search` properties to empty strings.
* @experimental **UNSTABLE**: New API, yet to be vetted.
*
* @example Usage
* ```ts
* import { basename } from "@std/path/posix/basename";
* import { assertEquals } from "@std/assert";
*
* assertEquals(basename("https://deno.land/std/path/mod.ts"), "mod.ts");
* assertEquals(basename("https://deno.land/std/path/mod.ts", ".ts"), "mod");
* assertEquals(basename("https://deno.land/std/path/mod.ts?a=b"), "mod.ts?a=b");
* assertEquals(basename("https://deno.land/std/path/mod.ts#header"), "mod.ts#header");
* assertEquals(basename(new URL("file:///home/user/Documents/image.png")), "image.png");
* assertEquals(basename(new URL("file:///home/user/Documents/image.png"), ".png"), "image");
* ```
*
* @param path The path to extract the name from.
* @param suffix The suffix to remove from extracted name.
* @returns The extracted name.
*/
export function basename(path: string, suffix = ""): string {
export function basename(path: URL, suffix?: string): string;
export function basename(path: string | URL, suffix = ""): string {
path = path instanceof URL ? fromFileUrl(path) : path;
assertArgs(path, suffix);

const lastSegment = lastPathSegment(path, isPosixPathSeparator);
Expand Down
25 changes: 24 additions & 1 deletion path/windows/basename.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import { CHAR_COLON } from "../_common/constants.ts";
import { stripTrailingSeparators } from "../_common/strip_trailing_separators.ts";
import { isPathSeparator, isWindowsDeviceRoot } from "./_util.ts";
import { fromFileUrl } from "./from_file_url.ts";

/**
* Return the last portion of a `path`.
Expand All @@ -28,7 +29,29 @@ import { isPathSeparator, isWindowsDeviceRoot } from "./_util.ts";
* @param suffix The suffix to remove from extracted name.
* @returns The extracted name.
*/
export function basename(path: string, suffix = ""): string {
export function basename(path: string, suffix?: string): string;
/**
* Return the last portion of a `path`.
* Trailing directory separators are ignored, and optional suffix is removed.
*
* @experimental **UNSTABLE**: New API, yet to be vetted.
*
* @example Usage
* ```ts
* import { basename } from "@std/path/windows/basename";
* import { assertEquals } from "@std/assert";
*
* assertEquals(basename(new URL("file:///C:/user/Documents/image.png")), "image.png");
* assertEquals(basename(new URL("file:///C:/user/Documents/image.png"), ".png"), "image");
* ```
*
* @param path The path to extract the name from.
* @param suffix The suffix to remove from extracted name.
* @returns The extracted name.
*/
export function basename(path: URL, suffix?: string): string;
export function basename(path: string | URL, suffix = ""): string {
path = path instanceof URL ? fromFileUrl(path) : path;
assertArgs(path, suffix);

// Check for a drive letter prefix so as not to mistake the following
Expand Down