mirror of
https://github.com/denoland/std.git
synced 2024-11-22 04:59:05 +00:00
d102a10235
* refactor: import from `@std/assert` * update
133 lines
3.8 KiB
TypeScript
133 lines
3.8 KiB
TypeScript
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
|
// This module is browser compatible.
|
|
|
|
import { assertArg } from "../_common/normalize.ts";
|
|
import { CHAR_COLON } from "../_common/constants.ts";
|
|
import { normalizeString } from "../_common/normalize_string.ts";
|
|
import { isPathSeparator, isWindowsDeviceRoot } from "./_util.ts";
|
|
|
|
/**
|
|
* Normalize the `path`, resolving `'..'` and `'.'` segments.
|
|
* Note that resolving these segments does not necessarily mean that all will be eliminated.
|
|
* A `'..'` at the top-level will be preserved, and an empty path is canonically `'.'`.
|
|
*
|
|
* @example Usage
|
|
* ```ts
|
|
* import { normalize } from "@std/path/windows/normalize";
|
|
* import { assertEquals } from "@std/assert";
|
|
*
|
|
* const normalized = normalize("C:\\foo\\..\\bar");
|
|
* assertEquals(normalized, "C:\\bar");
|
|
* ```
|
|
*
|
|
* @param path The path to normalize
|
|
* @returns The normalized path
|
|
*/
|
|
export function normalize(path: string): string {
|
|
assertArg(path);
|
|
|
|
const len = path.length;
|
|
let rootEnd = 0;
|
|
let device: string | undefined;
|
|
let isAbsolute = false;
|
|
const code = path.charCodeAt(0);
|
|
|
|
// Try to match a root
|
|
if (len > 1) {
|
|
if (isPathSeparator(code)) {
|
|
// Possible UNC root
|
|
|
|
// If we started with a separator, we know we at least have an absolute
|
|
// path of some kind (UNC or otherwise)
|
|
isAbsolute = true;
|
|
|
|
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) {
|
|
const firstPart = path.slice(last, j);
|
|
// 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 the normalized version of the UNC root since there
|
|
// is nothing left to process
|
|
|
|
return `\\\\${firstPart}\\${path.slice(last)}\\`;
|
|
} else if (j !== last) {
|
|
// We matched a UNC root with leftovers
|
|
|
|
device = `\\\\${firstPart}\\${path.slice(last, j)}`;
|
|
rootEnd = j;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
rootEnd = 1;
|
|
}
|
|
} else if (isWindowsDeviceRoot(code)) {
|
|
// Possible device root
|
|
|
|
if (path.charCodeAt(1) === CHAR_COLON) {
|
|
device = path.slice(0, 2);
|
|
rootEnd = 2;
|
|
if (len > 2) {
|
|
if (isPathSeparator(path.charCodeAt(2))) {
|
|
// Treat separator following drive name as an absolute path
|
|
// indicator
|
|
isAbsolute = true;
|
|
rootEnd = 3;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if (isPathSeparator(code)) {
|
|
// `path` contains just a path separator, exit early to avoid unnecessary
|
|
// work
|
|
return "\\";
|
|
}
|
|
|
|
let tail: string;
|
|
if (rootEnd < len) {
|
|
tail = normalizeString(
|
|
path.slice(rootEnd),
|
|
!isAbsolute,
|
|
"\\",
|
|
isPathSeparator,
|
|
);
|
|
} else {
|
|
tail = "";
|
|
}
|
|
if (tail.length === 0 && !isAbsolute) tail = ".";
|
|
if (tail.length > 0 && isPathSeparator(path.charCodeAt(len - 1))) {
|
|
tail += "\\";
|
|
}
|
|
if (device === undefined) {
|
|
if (isAbsolute) {
|
|
if (tail.length > 0) return `\\${tail}`;
|
|
else return "\\";
|
|
}
|
|
return tail;
|
|
} else if (isAbsolute) {
|
|
if (tail.length > 0) return `${device}\\${tail}`;
|
|
else return `${device}\\`;
|
|
}
|
|
return device + tail;
|
|
}
|