std/path/windows/dirname.ts

118 lines
3.2 KiB
TypeScript

// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
import { assertArg } from "../_common/dirname.ts";
import { CHAR_COLON } from "../_common/constants.ts";
import { stripTrailingSeparators } from "../_common/strip_trailing_separators.ts";
import {
isPathSeparator,
isPosixPathSeparator,
isWindowsDeviceRoot,
} from "./_util.ts";
/**
* Return the directory path of a `path`.
*
* @example Usage
* ```ts
* import { dirname } from "@std/path/windows/dirname";
* import { assertEquals } from "@std/assert";
*
* const dir = dirname("C:\\foo\\bar\\baz.ext");
* assertEquals(dir, "C:\\foo\\bar");
* ```
*
* Note: If you are working with file URLs,
* use the new version of `dirname` from `@std/path/windows/unstable-dirname`.
*
* @param path The path to get the directory from.
* @returns The directory path.
*/
export function dirname(path: string): string {
assertArg(path);
const len = path.length;
let rootEnd = -1;
let end = -1;
let matchedSlash = true;
let offset = 0;
const code = path.charCodeAt(0);
// Try to match a root
if (len > 1) {
if (isPathSeparator(code)) {
// Possible UNC root
rootEnd = offset = 1;
if (isPathSeparator(path.charCodeAt(1))) {
// Matched double path separator at beginning
let j = 2;
let last = j;
// Match 1 or more non-path separators
for (; j < len; ++j) {
if (isPathSeparator(path.charCodeAt(j))) break;
}
if (j < len && j !== last) {
// Matched!
last = j;
// Match 1 or more path separators
for (; j < len; ++j) {
if (!isPathSeparator(path.charCodeAt(j))) break;
}
if (j < len && j !== last) {
// Matched!
last = j;
// Match 1 or more non-path separators
for (; j < len; ++j) {
if (isPathSeparator(path.charCodeAt(j))) break;
}
if (j === len) {
// We matched a UNC root only
return path;
}
if (j !== last) {
// We matched a UNC root with leftovers
// Offset by 1 to include the separator after the UNC root to
// treat it as a "normal root" on top of a (UNC) root
rootEnd = offset = j + 1;
}
}
}
}
} else if (isWindowsDeviceRoot(code)) {
// Possible device root
if (path.charCodeAt(1) === CHAR_COLON) {
rootEnd = offset = 2;
if (len > 2) {
if (isPathSeparator(path.charCodeAt(2))) rootEnd = offset = 3;
}
}
}
} else if (isPathSeparator(code)) {
// `path` contains just a path separator, exit early to avoid
// unnecessary work
return path;
}
for (let i = len - 1; i >= offset; --i) {
if (isPathSeparator(path.charCodeAt(i))) {
if (!matchedSlash) {
end = i;
break;
}
} else {
// We saw the first non-path separator
matchedSlash = false;
}
}
if (end === -1) {
if (rootEnd === -1) return ".";
else end = rootEnd;
}
return stripTrailingSeparators(path.slice(0, end), isPosixPathSeparator);
}