diff --git a/ext/node/lib.rs b/ext/node/lib.rs index b73c3366ff..f4b4d0a39b 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -439,8 +439,9 @@ deno_core::extension!(deno_node, "path/_constants.ts", "path/_interface.ts", "path/_util.ts", + "path/_posix.ts", + "path/_win32.ts", "path/common.ts", - "path/glob.ts", "path/mod.ts", "path/posix.ts", "path/separator.ts", diff --git a/ext/node/polyfills/path.ts b/ext/node/polyfills/path.ts index e5a6ca905b..d2b6e673fe 100644 --- a/ext/node/polyfills/path.ts +++ b/ext/node/polyfills/path.ts @@ -1,4 +1,4 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. export * from "ext:deno_node/path/mod.ts"; -import * as m from "ext:deno_node/path/mod.ts"; -export default { ...m }; +import m from "ext:deno_node/path/mod.ts"; +export default m; diff --git a/ext/node/polyfills/path/_posix.ts b/ext/node/polyfills/path/_posix.ts new file mode 100644 index 0000000000..3f42bbe866 --- /dev/null +++ b/ext/node/polyfills/path/_posix.ts @@ -0,0 +1,533 @@ +// Copyright the Browserify authors. MIT License. +// Ported from https://github.com/browserify/path-browserify/ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import type { + FormatInputPathObject, + ParsedPath, +} from "ext:deno_node/path/_interface.ts"; +import { CHAR_DOT, CHAR_FORWARD_SLASH } from "ext:deno_node/path/_constants.ts"; +import { ERR_INVALID_ARG_TYPE } from "ext:deno_node/internal/errors.ts"; + +import { + _format, + assertPath, + encodeWhitespace, + isPosixPathSeparator, + normalizeString, +} from "ext:deno_node/path/_util.ts"; + +export const sep = "/"; +export const delimiter = ":"; + +// path.resolve([from ...], to) +/** + * Resolves `pathSegments` into an absolute path. + * @param pathSegments an array of path segments + */ +export function resolve(...pathSegments: string[]): string { + let resolvedPath = ""; + let resolvedAbsolute = false; + + for (let i = pathSegments.length - 1; i >= -1 && !resolvedAbsolute; i--) { + let path: string; + + if (i >= 0) path = pathSegments[i]; + else { + // deno-lint-ignore no-explicit-any + const { Deno } = globalThis as any; + if (typeof Deno?.cwd !== "function") { + throw new TypeError("Resolved a relative path without a CWD."); + } + path = Deno.cwd(); + } + + assertPath(path); + + // Skip empty entries + if (path.length === 0) { + continue; + } + + resolvedPath = `${path}/${resolvedPath}`; + resolvedAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; + } + + // At this point the path should be resolved to a full absolute path, but + // handle relative paths to be safe (might happen when process.cwd() fails) + + // Normalize the path + resolvedPath = normalizeString( + resolvedPath, + !resolvedAbsolute, + "/", + isPosixPathSeparator, + ); + + if (resolvedAbsolute) { + if (resolvedPath.length > 0) return `/${resolvedPath}`; + else return "/"; + } else if (resolvedPath.length > 0) return resolvedPath; + else return "."; +} + +/** + * Normalize the `path`, resolving `'..'` and `'.'` segments. + * @param path to be normalized + */ +export function normalize(path: string): string { + assertPath(path); + + if (path.length === 0) return "."; + + const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; + const trailingSeparator = + path.charCodeAt(path.length - 1) === CHAR_FORWARD_SLASH; + + // Normalize the path + path = normalizeString(path, !isAbsolute, "/", isPosixPathSeparator); + + if (path.length === 0 && !isAbsolute) path = "."; + if (path.length > 0 && trailingSeparator) path += "/"; + + if (isAbsolute) return `/${path}`; + return path; +} + +/** + * Verifies whether provided path is absolute + * @param path to be verified as absolute + */ +export function isAbsolute(path: string): boolean { + assertPath(path); + return path.length > 0 && path.charCodeAt(0) === CHAR_FORWARD_SLASH; +} + +/** + * Join all given a sequence of `paths`,then normalizes the resulting path. + * @param paths to be joined and normalized + */ +export function join(...paths: string[]): string { + if (paths.length === 0) return "."; + let joined: string | undefined; + for (let i = 0, len = paths.length; i < len; ++i) { + const path = paths[i]; + assertPath(path); + if (path.length > 0) { + if (!joined) joined = path; + else joined += `/${path}`; + } + } + if (!joined) return "."; + return normalize(joined); +} + +/** + * Return the relative path from `from` to `to` based on current working directory. + * @param from path in current working directory + * @param to path in current working directory + */ +export function relative(from: string, to: string): string { + assertPath(from); + assertPath(to); + + if (from === to) return ""; + + from = resolve(from); + to = resolve(to); + + if (from === to) return ""; + + // Trim any leading backslashes + let fromStart = 1; + const fromEnd = from.length; + for (; fromStart < fromEnd; ++fromStart) { + if (from.charCodeAt(fromStart) !== CHAR_FORWARD_SLASH) break; + } + const fromLen = fromEnd - fromStart; + + // Trim any leading backslashes + let toStart = 1; + const toEnd = to.length; + for (; toStart < toEnd; ++toStart) { + if (to.charCodeAt(toStart) !== CHAR_FORWARD_SLASH) break; + } + const toLen = toEnd - toStart; + + // Compare paths to find the longest common path from root + const length = fromLen < toLen ? fromLen : toLen; + let lastCommonSep = -1; + let i = 0; + for (; i <= length; ++i) { + if (i === length) { + if (toLen > length) { + if (to.charCodeAt(toStart + i) === CHAR_FORWARD_SLASH) { + // We get here if `from` is the exact base path for `to`. + // For example: from='/foo/bar'; to='/foo/bar/baz' + return to.slice(toStart + i + 1); + } else if (i === 0) { + // We get here if `from` is the root + // For example: from='/'; to='/foo' + return to.slice(toStart + i); + } + } else if (fromLen > length) { + if (from.charCodeAt(fromStart + i) === CHAR_FORWARD_SLASH) { + // We get here if `to` is the exact base path for `from`. + // For example: from='/foo/bar/baz'; to='/foo/bar' + lastCommonSep = i; + } else if (i === 0) { + // We get here if `to` is the root. + // For example: from='/foo'; to='/' + lastCommonSep = 0; + } + } + break; + } + const fromCode = from.charCodeAt(fromStart + i); + const toCode = to.charCodeAt(toStart + i); + if (fromCode !== toCode) break; + else if (fromCode === CHAR_FORWARD_SLASH) lastCommonSep = i; + } + + let out = ""; + // Generate the relative path based on the path difference between `to` + // and `from` + for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) { + if (i === fromEnd || from.charCodeAt(i) === CHAR_FORWARD_SLASH) { + if (out.length === 0) out += ".."; + else out += "/.."; + } + } + + // Lastly, append the rest of the destination (`to`) path that comes after + // the common path parts + if (out.length > 0) return out + to.slice(toStart + lastCommonSep); + else { + toStart += lastCommonSep; + if (to.charCodeAt(toStart) === CHAR_FORWARD_SLASH) ++toStart; + return to.slice(toStart); + } +} + +/** + * Resolves path to a namespace path + * @param path to resolve to namespace + */ +export function toNamespacedPath(path: string): string { + // Non-op on posix systems + return path; +} + +/** + * Return the directory name of a `path`. + * @param path to determine name for + */ +export function dirname(path: string): string { + assertPath(path); + if (path.length === 0) return "."; + const hasRoot = path.charCodeAt(0) === CHAR_FORWARD_SLASH; + let end = -1; + let matchedSlash = true; + for (let i = path.length - 1; i >= 1; --i) { + if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) { + if (!matchedSlash) { + end = i; + break; + } + } else { + // We saw the first non-path separator + matchedSlash = false; + } + } + + if (end === -1) return hasRoot ? "/" : "."; + if (hasRoot && end === 1) return "//"; + return path.slice(0, end); +} + +/** + * Return the last portion of a `path`. Trailing directory separators are ignored. + * @param path to process + * @param ext of path directory + */ +export function basename(path: string, ext = ""): string { + if (ext !== undefined && typeof ext !== "string") { + throw new ERR_INVALID_ARG_TYPE("ext", ["string"], ext); + } + assertPath(path); + + let start = 0; + let end = -1; + let matchedSlash = true; + let i: number; + + if (ext !== undefined && ext.length > 0 && ext.length <= path.length) { + if (ext.length === path.length && ext === path) return ""; + let extIdx = ext.length - 1; + let firstNonSlashEnd = -1; + for (i = path.length - 1; i >= 0; --i) { + const code = path.charCodeAt(i); + if (code === CHAR_FORWARD_SLASH) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else { + if (firstNonSlashEnd === -1) { + // We saw the first non-path separator, remember this index in case + // we need it if the extension ends up not matching + matchedSlash = false; + firstNonSlashEnd = i + 1; + } + if (extIdx >= 0) { + // Try to match the explicit extension + if (code === ext.charCodeAt(extIdx)) { + if (--extIdx === -1) { + // We matched the extension, so mark this as the end of our path + // component + end = i; + } + } else { + // Extension does not match, so our result is the entire path + // component + extIdx = -1; + end = firstNonSlashEnd; + } + } + } + } + + if (start === end) end = firstNonSlashEnd; + else if (end === -1) end = path.length; + return path.slice(start, end); + } else { + for (i = path.length - 1; i >= 0; --i) { + if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // path component + matchedSlash = false; + end = i + 1; + } + } + + if (end === -1) return ""; + return path.slice(start, end); + } +} + +/** + * Return the extension of the `path`. + * @param path with extension + */ +export function extname(path: string): string { + assertPath(path); + let startDot = -1; + let startPart = 0; + let end = -1; + let matchedSlash = true; + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0; + for (let i = path.length - 1; i >= 0; --i) { + const code = path.charCodeAt(i); + if (code === CHAR_FORWARD_SLASH) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === CHAR_DOT) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) startDot = i; + else if (preDotState !== 1) preDotState = 1; + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if ( + startDot === -1 || + end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) + ) { + return ""; + } + return path.slice(startDot, end); +} + +/** + * Generate a path from `FormatInputPathObject` object. + * @param pathObject with path + */ +export function format(pathObject: FormatInputPathObject): string { + if (pathObject === null || typeof pathObject !== "object") { + throw new ERR_INVALID_ARG_TYPE("pathObject", ["Object"], pathObject); + } + return _format("/", pathObject); +} + +/** + * Return a `ParsedPath` object of the `path`. + * @param path to process + */ +export function parse(path: string): ParsedPath { + assertPath(path); + + const ret: ParsedPath = { root: "", dir: "", base: "", ext: "", name: "" }; + if (path.length === 0) return ret; + const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; + let start: number; + if (isAbsolute) { + ret.root = "/"; + start = 1; + } else { + start = 0; + } + let startDot = -1; + let startPart = 0; + let end = -1; + let matchedSlash = true; + let i = path.length - 1; + + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0; + + // Get non-dir info + for (; i >= start; --i) { + const code = path.charCodeAt(i); + if (code === CHAR_FORWARD_SLASH) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === CHAR_DOT) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) startDot = i; + else if (preDotState !== 1) preDotState = 1; + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if ( + startDot === -1 || + end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) + ) { + if (end !== -1) { + if (startPart === 0 && isAbsolute) { + ret.base = ret.name = path.slice(1, end); + } else { + ret.base = ret.name = path.slice(startPart, end); + } + } + } else { + if (startPart === 0 && isAbsolute) { + ret.name = path.slice(1, startDot); + ret.base = path.slice(1, end); + } else { + ret.name = path.slice(startPart, startDot); + ret.base = path.slice(startPart, end); + } + ret.ext = path.slice(startDot, end); + } + + if (startPart > 0) ret.dir = path.slice(0, startPart - 1); + else if (isAbsolute) ret.dir = "/"; + + return ret; +} + +/** + * Converts a file URL to a path string. + * + * ```ts + * fromFileUrl("file:///home/foo"); // "/home/foo" + * ``` + * @param url of a file URL + */ +export function fromFileUrl(url: string | URL): string { + url = url instanceof URL ? url : new URL(url); + if (url.protocol != "file:") { + throw new TypeError("Must be a file URL."); + } + return decodeURIComponent( + url.pathname.replace(/%(?![0-9A-Fa-f]{2})/g, "%25"), + ); +} + +/** + * Converts a path string to a file URL. + * + * ```ts + * toFileUrl("/home/foo"); // new URL("file:///home/foo") + * ``` + * @param path to convert to file URL + */ +export function toFileUrl(path: string): URL { + if (!isAbsolute(path)) { + throw new TypeError("Must be an absolute path."); + } + const url = new URL("file:///"); + url.pathname = encodeWhitespace( + path.replace(/%/g, "%25").replace(/\\/g, "%5C"), + ); + return url; +} + +export default { + basename, + delimiter, + dirname, + extname, + format, + fromFileUrl, + isAbsolute, + join, + normalize, + parse, + relative, + resolve, + sep, + toFileUrl, + toNamespacedPath, +}; diff --git a/ext/node/polyfills/path/_win32.ts b/ext/node/polyfills/path/_win32.ts new file mode 100644 index 0000000000..73321e7090 --- /dev/null +++ b/ext/node/polyfills/path/_win32.ts @@ -0,0 +1,1025 @@ +// Copyright the Browserify authors. MIT License. +// Ported from https://github.com/browserify/path-browserify/ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import type { + FormatInputPathObject, + ParsedPath, +} from "ext:deno_node/path/_interface.ts"; +import { + CHAR_BACKWARD_SLASH, + CHAR_COLON, + CHAR_DOT, + CHAR_QUESTION_MARK, +} from "ext:deno_node/path/_constants.ts"; +import { ERR_INVALID_ARG_TYPE } from "ext:deno_node/internal/errors.ts"; + +import { + _format, + assertPath, + encodeWhitespace, + isPathSeparator, + isWindowsDeviceRoot, + normalizeString, +} from "ext:deno_node/path/_util.ts"; +import { assert } from "ext:deno_node/_util/asserts.ts"; + +export const sep = "\\"; +export const delimiter = ";"; + +/** + * Resolves path segments into a `path` + * @param pathSegments to process to path + */ +export function resolve(...pathSegments: string[]): string { + let resolvedDevice = ""; + let resolvedTail = ""; + let resolvedAbsolute = false; + + for (let i = pathSegments.length - 1; i >= -1; i--) { + let path: string; + // deno-lint-ignore no-explicit-any + const { Deno } = globalThis as any; + if (i >= 0) { + path = pathSegments[i]; + } else if (!resolvedDevice) { + if (typeof Deno?.cwd !== "function") { + throw new TypeError("Resolved a drive-letter-less path without a CWD."); + } + path = Deno.cwd(); + } else { + if ( + typeof Deno?.env?.get !== "function" || typeof Deno?.cwd !== "function" + ) { + throw new TypeError("Resolved a relative path without a CWD."); + } + path = Deno.cwd(); + + // Verify that a cwd was found and that it actually points + // to our drive. If not, default to the drive's root. + if ( + path === undefined || + path.slice(0, 3).toLowerCase() !== `${resolvedDevice.toLowerCase()}\\` + ) { + path = `${resolvedDevice}\\`; + } + } + + assertPath(path); + + const len = path.length; + + // Skip empty entries + if (len === 0) continue; + + let rootEnd = 0; + let device = ""; + 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 + device = `\\\\${firstPart}\\${path.slice(last)}`; + rootEnd = j; + } 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 + rootEnd = 1; + isAbsolute = true; + } + + if ( + device.length > 0 && + resolvedDevice.length > 0 && + device.toLowerCase() !== resolvedDevice.toLowerCase() + ) { + // This path points to another device so it is not applicable + continue; + } + + if (resolvedDevice.length === 0 && device.length > 0) { + resolvedDevice = device; + } + if (!resolvedAbsolute) { + resolvedTail = `${path.slice(rootEnd)}\\${resolvedTail}`; + resolvedAbsolute = isAbsolute; + } + + if (resolvedAbsolute && resolvedDevice.length > 0) break; + } + + // At this point the path should be resolved to a full absolute path, + // but handle relative paths to be safe (might happen when process.cwd() + // fails) + + // Normalize the tail path + resolvedTail = normalizeString( + resolvedTail, + !resolvedAbsolute, + "\\", + isPathSeparator, + ); + + return resolvedDevice + (resolvedAbsolute ? "\\" : "") + resolvedTail || "."; +} + +/** + * Normalizes a `path` + * @param path to normalize + */ +export function normalize(path: string): string { + assertPath(path); + const len = path.length; + if (len === 0) return "."; + 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 "\\"; + } else if (tail.length > 0) { + return tail; + } else { + return ""; + } + } else if (isAbsolute) { + if (tail.length > 0) return `${device}\\${tail}`; + else return `${device}\\`; + } else if (tail.length > 0) { + return device + tail; + } else { + return device; + } +} + +/** + * Verifies whether path is absolute + * @param path to verify + */ +export function isAbsolute(path: string): boolean { + assertPath(path); + const len = path.length; + if (len === 0) return false; + + const code = path.charCodeAt(0); + if (isPathSeparator(code)) { + return true; + } else if (isWindowsDeviceRoot(code)) { + // Possible device root + + if (len > 2 && path.charCodeAt(1) === CHAR_COLON) { + if (isPathSeparator(path.charCodeAt(2))) return true; + } + } + return false; +} + +/** + * Join all given a sequence of `paths`,then normalizes the resulting path. + * @param paths to be joined and normalized + */ +export function join(...paths: string[]): string { + const pathsCount = paths.length; + if (pathsCount === 0) return "."; + + let joined: string | undefined; + let firstPart: string | null = null; + for (let i = 0; i < pathsCount; ++i) { + const path = paths[i]; + assertPath(path); + if (path.length > 0) { + if (joined === undefined) joined = firstPart = path; + else joined += `\\${path}`; + } + } + + if (joined === undefined) return "."; + + // Make sure that the joined path doesn't start with two slashes, because + // normalize() will mistake it for an UNC path then. + // + // This step is skipped when it is very clear that the user actually + // intended to point at an UNC path. This is assumed when the first + // non-empty string arguments starts with exactly two slashes followed by + // at least one more non-slash character. + // + // Note that for normalize() to treat a path as an UNC path it needs to + // have at least 2 components, so we don't filter for that here. + // This means that the user can use join to construct UNC paths from + // a server name and a share name; for example: + // path.join('//server', 'share') -> '\\\\server\\share\\') + let needsReplace = true; + let slashCount = 0; + assert(firstPart != null); + if (isPathSeparator(firstPart.charCodeAt(0))) { + ++slashCount; + const firstLen = firstPart.length; + if (firstLen > 1) { + if (isPathSeparator(firstPart.charCodeAt(1))) { + ++slashCount; + if (firstLen > 2) { + if (isPathSeparator(firstPart.charCodeAt(2))) ++slashCount; + else { + // We matched a UNC path in the first part + needsReplace = false; + } + } + } + } + } + if (needsReplace) { + // Find any more consecutive slashes we need to replace + for (; slashCount < joined.length; ++slashCount) { + if (!isPathSeparator(joined.charCodeAt(slashCount))) break; + } + + // Replace the slashes if needed + if (slashCount >= 2) joined = `\\${joined.slice(slashCount)}`; + } + + return normalize(joined); +} + +/** + * It will solve the relative path from `from` to `to`, for instance: + * from = 'C:\\orandea\\test\\aaa' + * to = 'C:\\orandea\\impl\\bbb' + * The output of the function should be: '..\\..\\impl\\bbb' + * @param from relative path + * @param to relative path + */ +export function relative(from: string, to: string): string { + assertPath(from); + assertPath(to); + + if (from === to) return ""; + + const fromOrig = resolve(from); + const toOrig = resolve(to); + + if (fromOrig === toOrig) return ""; + + from = fromOrig.toLowerCase(); + to = toOrig.toLowerCase(); + + if (from === to) return ""; + + // Trim any leading backslashes + let fromStart = 0; + let fromEnd = from.length; + for (; fromStart < fromEnd; ++fromStart) { + if (from.charCodeAt(fromStart) !== CHAR_BACKWARD_SLASH) break; + } + // Trim trailing backslashes (applicable to UNC paths only) + for (; fromEnd - 1 > fromStart; --fromEnd) { + if (from.charCodeAt(fromEnd - 1) !== CHAR_BACKWARD_SLASH) break; + } + const fromLen = fromEnd - fromStart; + + // Trim any leading backslashes + let toStart = 0; + let toEnd = to.length; + for (; toStart < toEnd; ++toStart) { + if (to.charCodeAt(toStart) !== CHAR_BACKWARD_SLASH) break; + } + // Trim trailing backslashes (applicable to UNC paths only) + for (; toEnd - 1 > toStart; --toEnd) { + if (to.charCodeAt(toEnd - 1) !== CHAR_BACKWARD_SLASH) break; + } + const toLen = toEnd - toStart; + + // Compare paths to find the longest common path from root + const length = fromLen < toLen ? fromLen : toLen; + let lastCommonSep = -1; + let i = 0; + for (; i <= length; ++i) { + if (i === length) { + if (toLen > length) { + if (to.charCodeAt(toStart + i) === CHAR_BACKWARD_SLASH) { + // We get here if `from` is the exact base path for `to`. + // For example: from='C:\\foo\\bar'; to='C:\\foo\\bar\\baz' + return toOrig.slice(toStart + i + 1); + } else if (i === 2) { + // We get here if `from` is the device root. + // For example: from='C:\\'; to='C:\\foo' + return toOrig.slice(toStart + i); + } + } + if (fromLen > length) { + if (from.charCodeAt(fromStart + i) === CHAR_BACKWARD_SLASH) { + // We get here if `to` is the exact base path for `from`. + // For example: from='C:\\foo\\bar'; to='C:\\foo' + lastCommonSep = i; + } else if (i === 2) { + // We get here if `to` is the device root. + // For example: from='C:\\foo\\bar'; to='C:\\' + lastCommonSep = 3; + } + } + break; + } + const fromCode = from.charCodeAt(fromStart + i); + const toCode = to.charCodeAt(toStart + i); + if (fromCode !== toCode) break; + else if (fromCode === CHAR_BACKWARD_SLASH) lastCommonSep = i; + } + + // We found a mismatch before the first common path separator was seen, so + // return the original `to`. + if (i !== length && lastCommonSep === -1) { + return toOrig; + } + + let out = ""; + if (lastCommonSep === -1) lastCommonSep = 0; + // Generate the relative path based on the path difference between `to` and + // `from` + for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) { + if (i === fromEnd || from.charCodeAt(i) === CHAR_BACKWARD_SLASH) { + if (out.length === 0) out += ".."; + else out += "\\.."; + } + } + + // Lastly, append the rest of the destination (`to`) path that comes after + // the common path parts + if (out.length > 0) { + return out + toOrig.slice(toStart + lastCommonSep, toEnd); + } else { + toStart += lastCommonSep; + if (toOrig.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) ++toStart; + return toOrig.slice(toStart, toEnd); + } +} + +/** + * Resolves path to a namespace path + * @param path to resolve to namespace + */ +export function toNamespacedPath(path: string): string { + // Note: this will *probably* throw somewhere. + if (typeof path !== "string") return path; + if (path.length === 0) return ""; + + const resolvedPath = resolve(path); + + if (resolvedPath.length >= 3) { + if (resolvedPath.charCodeAt(0) === CHAR_BACKWARD_SLASH) { + // Possible UNC root + + if (resolvedPath.charCodeAt(1) === CHAR_BACKWARD_SLASH) { + const code = resolvedPath.charCodeAt(2); + if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) { + // Matched non-long UNC root, convert the path to a long UNC path + return `\\\\?\\UNC\\${resolvedPath.slice(2)}`; + } + } + } else if (isWindowsDeviceRoot(resolvedPath.charCodeAt(0))) { + // Possible device root + + if ( + resolvedPath.charCodeAt(1) === CHAR_COLON && + resolvedPath.charCodeAt(2) === CHAR_BACKWARD_SLASH + ) { + // Matched device root, convert the path to a long UNC path + return `\\\\?\\${resolvedPath}`; + } + } + } + + return path; +} + +/** + * Return the directory name of a `path`. + * @param path to determine name for + */ +export function dirname(path: string): string { + assertPath(path); + const len = path.length; + if (len === 0) return "."; + 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 path.slice(0, end); +} + +/** + * Return the last portion of a `path`. Trailing directory separators are ignored. + * @param path to process + * @param ext of path directory + */ +export function basename(path: string, ext = ""): string { + if (ext !== undefined && typeof ext !== "string") { + throw new ERR_INVALID_ARG_TYPE("ext", ["string"], ext); + } + + assertPath(path); + + let start = 0; + let end = -1; + let matchedSlash = true; + let i: number; + + // Check for a drive letter prefix so as not to mistake the following + // path separator as an extra separator at the end of the path that can be + // disregarded + if (path.length >= 2) { + const drive = path.charCodeAt(0); + if (isWindowsDeviceRoot(drive)) { + if (path.charCodeAt(1) === CHAR_COLON) start = 2; + } + } + + if (ext !== undefined && ext.length > 0 && ext.length <= path.length) { + if (ext.length === path.length && ext === path) return ""; + let extIdx = ext.length - 1; + let firstNonSlashEnd = -1; + for (i = path.length - 1; i >= start; --i) { + const code = path.charCodeAt(i); + if (isPathSeparator(code)) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else { + if (firstNonSlashEnd === -1) { + // We saw the first non-path separator, remember this index in case + // we need it if the extension ends up not matching + matchedSlash = false; + firstNonSlashEnd = i + 1; + } + if (extIdx >= 0) { + // Try to match the explicit extension + if (code === ext.charCodeAt(extIdx)) { + if (--extIdx === -1) { + // We matched the extension, so mark this as the end of our path + // component + end = i; + } + } else { + // Extension does not match, so our result is the entire path + // component + extIdx = -1; + end = firstNonSlashEnd; + } + } + } + } + + if (start === end) end = firstNonSlashEnd; + else if (end === -1) end = path.length; + return path.slice(start, end); + } else { + for (i = path.length - 1; i >= start; --i) { + if (isPathSeparator(path.charCodeAt(i))) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // path component + matchedSlash = false; + end = i + 1; + } + } + + if (end === -1) return ""; + return path.slice(start, end); + } +} + +/** + * Return the extension of the `path`. + * @param path with extension + */ +export function extname(path: string): string { + assertPath(path); + let start = 0; + let startDot = -1; + let startPart = 0; + let end = -1; + let matchedSlash = true; + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0; + + // Check for a drive letter prefix so as not to mistake the following + // path separator as an extra separator at the end of the path that can be + // disregarded + + if ( + path.length >= 2 && + path.charCodeAt(1) === CHAR_COLON && + isWindowsDeviceRoot(path.charCodeAt(0)) + ) { + start = startPart = 2; + } + + for (let i = path.length - 1; i >= start; --i) { + const code = path.charCodeAt(i); + if (isPathSeparator(code)) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === CHAR_DOT) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) startDot = i; + else if (preDotState !== 1) preDotState = 1; + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if ( + startDot === -1 || + end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) + ) { + return ""; + } + return path.slice(startDot, end); +} + +/** + * Generate a path from `FormatInputPathObject` object. + * @param pathObject with path + */ +export function format(pathObject: FormatInputPathObject): string { + if (pathObject === null || typeof pathObject !== "object") { + throw new ERR_INVALID_ARG_TYPE("pathObject", ["Object"], pathObject); + } + return _format("\\", pathObject); +} + +/** + * Return a `ParsedPath` object of the `path`. + * @param path to process + */ +export function parse(path: string): ParsedPath { + assertPath(path); + + const ret: ParsedPath = { root: "", dir: "", base: "", ext: "", name: "" }; + + const len = path.length; + if (len === 0) return ret; + + let rootEnd = 0; + let code = path.charCodeAt(0); + + // Try to match a root + if (len > 1) { + if (isPathSeparator(code)) { + // Possible UNC root + + rootEnd = 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 + + rootEnd = j; + } else if (j !== last) { + // We matched a UNC root with leftovers + + rootEnd = j + 1; + } + } + } + } + } else if (isWindowsDeviceRoot(code)) { + // Possible device root + + if (path.charCodeAt(1) === CHAR_COLON) { + rootEnd = 2; + if (len > 2) { + if (isPathSeparator(path.charCodeAt(2))) { + if (len === 3) { + // `path` contains just a drive root, exit early to avoid + // unnecessary work + ret.root = ret.dir = path; + return ret; + } + rootEnd = 3; + } + } else { + // `path` contains just a drive root, exit early to avoid + // unnecessary work + ret.root = ret.dir = path; + return ret; + } + } + } + } else if (isPathSeparator(code)) { + // `path` contains just a path separator, exit early to avoid + // unnecessary work + ret.root = ret.dir = path; + return ret; + } + + if (rootEnd > 0) ret.root = path.slice(0, rootEnd); + + let startDot = -1; + let startPart = rootEnd; + let end = -1; + let matchedSlash = true; + let i = path.length - 1; + + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0; + + // Get non-dir info + for (; i >= rootEnd; --i) { + code = path.charCodeAt(i); + if (isPathSeparator(code)) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === CHAR_DOT) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) startDot = i; + else if (preDotState !== 1) preDotState = 1; + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if ( + startDot === -1 || + end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) + ) { + if (end !== -1) { + ret.base = ret.name = path.slice(startPart, end); + } + } else { + ret.name = path.slice(startPart, startDot); + ret.base = path.slice(startPart, end); + ret.ext = path.slice(startDot, end); + } + + // If the directory is the root, use the entire root as the `dir` including + // the trailing slash if any (`C:\abc` -> `C:\`). Otherwise, strip out the + // trailing slash (`C:\abc\def` -> `C:\abc`). + if (startPart > 0 && startPart !== rootEnd) { + ret.dir = path.slice(0, startPart - 1); + } else ret.dir = ret.root; + + return ret; +} + +/** + * Converts a file URL to a path string. + * + * ```ts + * fromFileUrl("file:///home/foo"); // "\\home\\foo" + * fromFileUrl("file:///C:/Users/foo"); // "C:\\Users\\foo" + * fromFileUrl("file://localhost/home/foo"); // "\\\\localhost\\home\\foo" + * ``` + * @param url of a file URL + */ +export function fromFileUrl(url: string | URL): string { + url = url instanceof URL ? url : new URL(url); + if (url.protocol != "file:") { + throw new TypeError("Must be a file URL."); + } + let path = decodeURIComponent( + url.pathname.replace(/\//g, "\\").replace(/%(?![0-9A-Fa-f]{2})/g, "%25"), + ).replace(/^\\*([A-Za-z]:)(\\|$)/, "$1\\"); + if (url.hostname != "") { + // Note: The `URL` implementation guarantees that the drive letter and + // hostname are mutually exclusive. Otherwise it would not have been valid + // to append the hostname and path like this. + path = `\\\\${url.hostname}${path}`; + } + return path; +} + +/** + * Converts a path string to a file URL. + * + * ```ts + * toFileUrl("\\home\\foo"); // new URL("file:///home/foo") + * toFileUrl("C:\\Users\\foo"); // new URL("file:///C:/Users/foo") + * toFileUrl("\\\\127.0.0.1\\home\\foo"); // new URL("file://127.0.0.1/home/foo") + * ``` + * @param path to convert to file URL + */ +export function toFileUrl(path: string): URL { + if (!isAbsolute(path)) { + throw new TypeError("Must be an absolute path."); + } + const [, hostname, pathname] = path.match( + /^(?:[/\\]{2}([^/\\]+)(?=[/\\](?:[^/\\]|$)))?(.*)/, + )!; + const url = new URL("file:///"); + url.pathname = encodeWhitespace(pathname.replace(/%/g, "%25")); + if (hostname != null && hostname != "localhost") { + url.hostname = hostname; + if (!url.hostname) { + throw new TypeError("Invalid hostname."); + } + } + return url; +} + +export default { + basename, + delimiter, + dirname, + extname, + format, + fromFileUrl, + isAbsolute, + join, + normalize, + parse, + relative, + resolve, + sep, + toFileUrl, + toNamespacedPath, +}; diff --git a/ext/node/polyfills/path/glob.ts b/ext/node/polyfills/path/glob.ts deleted file mode 100644 index de6c752d54..0000000000 --- a/ext/node/polyfills/path/glob.ts +++ /dev/null @@ -1,419 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { isWindows, osType } from "ext:deno_node/_util/os.ts"; -import { SEP, SEP_PATTERN } from "ext:deno_node/path/separator.ts"; -import * as _win32 from "ext:deno_node/path/win32.ts"; -import * as _posix from "ext:deno_node/path/posix.ts"; -import type { OSType } from "ext:deno_node/_util/os.ts"; - -const path = isWindows ? _win32 : _posix; -const { join, normalize } = path; - -export interface GlobOptions { - /** Extended glob syntax. - * See https://www.linuxjournal.com/content/bash-extended-globbing. - * - * @default {true} - */ - extended?: boolean; - /** Globstar syntax. - * See https://www.linuxjournal.com/content/globstar-new-bash-globbing-option. - * If false, `**` is treated like `*`. - * - * @default {true} - */ - globstar?: boolean; - /** Whether globstar should be case-insensitive. */ - caseInsensitive?: boolean; - /** Operating system. Defaults to the native OS. */ - os?: OSType; -} - -export type GlobToRegExpOptions = GlobOptions; - -const regExpEscapeChars = [ - "!", - "$", - "(", - ")", - "*", - "+", - ".", - "=", - "?", - "[", - "\\", - "^", - "{", - "|", -]; -const rangeEscapeChars = ["-", "\\", "]"]; - -/** Convert a glob string to a regular expression. - * - * Tries to match bash glob expansion as closely as possible. - * - * Basic glob syntax: - * - `*` - Matches everything without leaving the path segment. - * - `?` - Matches any single character. - * - `{foo,bar}` - Matches `foo` or `bar`. - * - `[abcd]` - Matches `a`, `b`, `c` or `d`. - * - `[a-d]` - Matches `a`, `b`, `c` or `d`. - * - `[!abcd]` - Matches any single character besides `a`, `b`, `c` or `d`. - * - `[[::]]` - Matches any character belonging to ``. - * - `[[:alnum:]]` - Matches any digit or letter. - * - `[[:digit:]abc]` - Matches any digit, `a`, `b` or `c`. - * - See https://facelessuser.github.io/wcmatch/glob/#posix-character-classes - * for a complete list of supported character classes. - * - `\` - Escapes the next character for an `os` other than `"windows"`. - * - \` - Escapes the next character for `os` set to `"windows"`. - * - `/` - Path separator. - * - `\` - Additional path separator only for `os` set to `"windows"`. - * - * Extended syntax: - * - Requires `{ extended: true }`. - * - `?(foo|bar)` - Matches 0 or 1 instance of `{foo,bar}`. - * - `@(foo|bar)` - Matches 1 instance of `{foo,bar}`. They behave the same. - * - `*(foo|bar)` - Matches _n_ instances of `{foo,bar}`. - * - `+(foo|bar)` - Matches _n > 0_ instances of `{foo,bar}`. - * - `!(foo|bar)` - Matches anything other than `{foo,bar}`. - * - See https://www.linuxjournal.com/content/bash-extended-globbing. - * - * Globstar syntax: - * - Requires `{ globstar: true }`. - * - `**` - Matches any number of any path segments. - * - Must comprise its entire path segment in the provided glob. - * - See https://www.linuxjournal.com/content/globstar-new-bash-globbing-option. - * - * Note the following properties: - * - The generated `RegExp` is anchored at both start and end. - * - Repeating and trailing separators are tolerated. Trailing separators in the - * provided glob have no meaning and are discarded. - * - Absolute globs will only match absolute paths, etc. - * - Empty globs will match nothing. - * - Any special glob syntax must be contained to one path segment. For example, - * `?(foo|bar/baz)` is invalid. The separator will take precedence and the - * first segment ends with an unclosed group. - * - If a path segment ends with unclosed groups or a dangling escape prefix, a - * parse error has occurred. Every character for that segment is taken - * literally in this event. - * - * Limitations: - * - A negative group like `!(foo|bar)` will wrongly be converted to a negative - * look-ahead followed by a wildcard. This means that `!(foo).js` will wrongly - * fail to match `foobar.js`, even though `foobar` is not `foo`. Effectively, - * `!(foo|bar)` is treated like `!(@(foo|bar)*)`. This will work correctly if - * the group occurs not nested at the end of the segment. */ -export function globToRegExp( - glob: string, - { - extended = true, - globstar: globstarOption = true, - os = osType, - caseInsensitive = false, - }: GlobToRegExpOptions = {}, -): RegExp { - if (glob == "") { - return /(?!)/; - } - - const sep = os == "windows" ? "(?:\\\\|/)+" : "/+"; - const sepMaybe = os == "windows" ? "(?:\\\\|/)*" : "/*"; - const seps = os == "windows" ? ["\\", "/"] : ["/"]; - const globstar = os == "windows" - ? "(?:[^\\\\/]*(?:\\\\|/|$)+)*" - : "(?:[^/]*(?:/|$)+)*"; - const wildcard = os == "windows" ? "[^\\\\/]*" : "[^/]*"; - const escapePrefix = os == "windows" ? "`" : "\\"; - - // Remove trailing separators. - let newLength = glob.length; - for (; newLength > 1 && seps.includes(glob[newLength - 1]); newLength--); - glob = glob.slice(0, newLength); - - let regExpString = ""; - - // Terminates correctly. Trust that `j` is incremented every iteration. - for (let j = 0; j < glob.length;) { - let segment = ""; - const groupStack: string[] = []; - let inRange = false; - let inEscape = false; - let endsWithSep = false; - let i = j; - - // Terminates with `i` at the non-inclusive end of the current segment. - for (; i < glob.length && !seps.includes(glob[i]); i++) { - if (inEscape) { - inEscape = false; - const escapeChars = inRange ? rangeEscapeChars : regExpEscapeChars; - segment += escapeChars.includes(glob[i]) ? `\\${glob[i]}` : glob[i]; - continue; - } - - if (glob[i] == escapePrefix) { - inEscape = true; - continue; - } - - if (glob[i] == "[") { - if (!inRange) { - inRange = true; - segment += "["; - if (glob[i + 1] == "!") { - i++; - segment += "^"; - } else if (glob[i + 1] == "^") { - i++; - segment += "\\^"; - } - continue; - } else if (glob[i + 1] == ":") { - let k = i + 1; - let value = ""; - while (glob[k + 1] != null && glob[k + 1] != ":") { - value += glob[k + 1]; - k++; - } - if (glob[k + 1] == ":" && glob[k + 2] == "]") { - i = k + 2; - if (value == "alnum") segment += "\\dA-Za-z"; - else if (value == "alpha") segment += "A-Za-z"; - else if (value == "ascii") segment += "\x00-\x7F"; - else if (value == "blank") segment += "\t "; - else if (value == "cntrl") segment += "\x00-\x1F\x7F"; - else if (value == "digit") segment += "\\d"; - else if (value == "graph") segment += "\x21-\x7E"; - else if (value == "lower") segment += "a-z"; - else if (value == "print") segment += "\x20-\x7E"; - else if (value == "punct") { - segment += "!\"#$%&'()*+,\\-./:;<=>?@[\\\\\\]^_"; - segment += "\u2018"; - segment += "{|}~"; - } else if (value == "space") segment += "\\s\v"; - else if (value == "upper") segment += "A-Z"; - else if (value == "word") segment += "\\w"; - else if (value == "xdigit") segment += "\\dA-Fa-f"; - continue; - } - } - } - - if (glob[i] == "]" && inRange) { - inRange = false; - segment += "]"; - continue; - } - - if (inRange) { - if (glob[i] == "\\") { - segment += `\\\\`; - } else { - segment += glob[i]; - } - continue; - } - - if ( - glob[i] == ")" && groupStack.length > 0 && - groupStack[groupStack.length - 1] != "BRACE" - ) { - segment += ")"; - const type = groupStack.pop()!; - if (type == "!") { - segment += wildcard; - } else if (type != "@") { - segment += type; - } - continue; - } - - if ( - glob[i] == "|" && groupStack.length > 0 && - groupStack[groupStack.length - 1] != "BRACE" - ) { - segment += "|"; - continue; - } - - if (glob[i] == "+" && extended && glob[i + 1] == "(") { - i++; - groupStack.push("+"); - segment += "(?:"; - continue; - } - - if (glob[i] == "@" && extended && glob[i + 1] == "(") { - i++; - groupStack.push("@"); - segment += "(?:"; - continue; - } - - if (glob[i] == "?") { - if (extended && glob[i + 1] == "(") { - i++; - groupStack.push("?"); - segment += "(?:"; - } else { - segment += "."; - } - continue; - } - - if (glob[i] == "!" && extended && glob[i + 1] == "(") { - i++; - groupStack.push("!"); - segment += "(?!"; - continue; - } - - if (glob[i] == "{") { - groupStack.push("BRACE"); - segment += "(?:"; - continue; - } - - if (glob[i] == "}" && groupStack[groupStack.length - 1] == "BRACE") { - groupStack.pop(); - segment += ")"; - continue; - } - - if (glob[i] == "," && groupStack[groupStack.length - 1] == "BRACE") { - segment += "|"; - continue; - } - - if (glob[i] == "*") { - if (extended && glob[i + 1] == "(") { - i++; - groupStack.push("*"); - segment += "(?:"; - } else { - const prevChar = glob[i - 1]; - let numStars = 1; - while (glob[i + 1] == "*") { - i++; - numStars++; - } - const nextChar = glob[i + 1]; - if ( - globstarOption && numStars == 2 && - [...seps, undefined].includes(prevChar) && - [...seps, undefined].includes(nextChar) - ) { - segment += globstar; - endsWithSep = true; - } else { - segment += wildcard; - } - } - continue; - } - - segment += regExpEscapeChars.includes(glob[i]) ? `\\${glob[i]}` : glob[i]; - } - - // Check for unclosed groups or a dangling backslash. - if (groupStack.length > 0 || inRange || inEscape) { - // Parse failure. Take all characters from this segment literally. - segment = ""; - for (const c of glob.slice(j, i)) { - segment += regExpEscapeChars.includes(c) ? `\\${c}` : c; - endsWithSep = false; - } - } - - regExpString += segment; - if (!endsWithSep) { - regExpString += i < glob.length ? sep : sepMaybe; - endsWithSep = true; - } - - // Terminates with `i` at the start of the next segment. - while (seps.includes(glob[i])) i++; - - // Check that the next value of `j` is indeed higher than the current value. - if (!(i > j)) { - throw new Error("Assertion failure: i > j (potential infinite loop)"); - } - j = i; - } - - regExpString = `^${regExpString}$`; - return new RegExp(regExpString, caseInsensitive ? "i" : ""); -} - -/** Test whether the given string is a glob */ -export function isGlob(str: string): boolean { - const chars: Record = { "{": "}", "(": ")", "[": "]" }; - const regex = - /\\(.)|(^!|\*|\?|[\].+)]\?|\[[^\\\]]+\]|\{[^\\}]+\}|\(\?[:!=][^\\)]+\)|\([^|]+\|[^\\)]+\))/; - - if (str === "") { - return false; - } - - let match: RegExpExecArray | null; - - while ((match = regex.exec(str))) { - if (match[2]) return true; - let idx = match.index + match[0].length; - - // if an open bracket/brace/paren is escaped, - // set the index to the next closing character - const open = match[1]; - const close = open ? chars[open] : null; - if (open && close) { - const n = str.indexOf(close, idx); - if (n !== -1) { - idx = n + 1; - } - } - - str = str.slice(idx); - } - - return false; -} - -/** Like normalize(), but doesn't collapse "**\/.." when `globstar` is true. */ -export function normalizeGlob( - glob: string, - { globstar = false }: GlobOptions = {}, -): string { - if (glob.match(/\0/g)) { - throw new Error(`Glob contains invalid characters: "${glob}"`); - } - if (!globstar) { - return normalize(glob); - } - const s = SEP_PATTERN.source; - const badParentPattern = new RegExp( - `(?<=(${s}|^)\\*\\*${s})\\.\\.(?=${s}|$)`, - "g", - ); - return normalize(glob.replace(badParentPattern, "\0")).replace(/\0/g, ".."); -} - -/** Like join(), but doesn't collapse "**\/.." when `globstar` is true. */ -export function joinGlobs( - globs: string[], - { extended = true, globstar = false }: GlobOptions = {}, -): string { - if (!globstar || globs.length == 0) { - return join(...globs); - } - if (globs.length === 0) return "."; - let joined: string | undefined; - for (const glob of globs) { - const path = glob; - if (path.length > 0) { - if (!joined) joined = path; - else joined += `${SEP}${path}`; - } - } - if (!joined) return "."; - return normalizeGlob(joined, { extended, globstar }); -} diff --git a/ext/node/polyfills/path/mod.ts b/ext/node/polyfills/path/mod.ts index c4346b6b3a..ee231e17d7 100644 --- a/ext/node/polyfills/path/mod.ts +++ b/ext/node/polyfills/path/mod.ts @@ -3,13 +3,25 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. import { isWindows } from "ext:deno_node/_util/os.ts"; -import _win32 from "ext:deno_node/path/win32.ts"; -import _posix from "ext:deno_node/path/posix.ts"; +import _win32 from "ext:deno_node/path/_win32.ts"; +import _posix from "ext:deno_node/path/_posix.ts"; -const path = isWindows ? _win32 : _posix; +export const win32 = { + ..._win32, + win32: null as unknown as typeof _win32, + posix: null as unknown as typeof _posix, +}; -export const win32 = _win32; -export const posix = _posix; +export const posix = { + ..._posix, + win32: null as unknown as typeof _win32, + posix: null as unknown as typeof _posix, +}; + +posix.win32 = win32.win32 = win32; +posix.posix = win32.posix = posix; + +const path = isWindows ? win32 : posix; export const { basename, delimiter, @@ -27,8 +39,6 @@ export const { toFileUrl, toNamespacedPath, } = path; - +export default path; export * from "ext:deno_node/path/common.ts"; -export { SEP, SEP_PATTERN } from "ext:deno_node/path/separator.ts"; export * from "ext:deno_node/path/_interface.ts"; -export * from "ext:deno_node/path/glob.ts"; diff --git a/ext/node/polyfills/path/posix.ts b/ext/node/polyfills/path/posix.ts index 3f42bbe866..40e291f270 100644 --- a/ext/node/polyfills/path/posix.ts +++ b/ext/node/polyfills/path/posix.ts @@ -2,519 +2,9 @@ // Ported from https://github.com/browserify/path-browserify/ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -import type { - FormatInputPathObject, - ParsedPath, -} from "ext:deno_node/path/_interface.ts"; -import { CHAR_DOT, CHAR_FORWARD_SLASH } from "ext:deno_node/path/_constants.ts"; -import { ERR_INVALID_ARG_TYPE } from "ext:deno_node/internal/errors.ts"; +import path from "ext:deno_node/path/mod.ts"; -import { - _format, - assertPath, - encodeWhitespace, - isPosixPathSeparator, - normalizeString, -} from "ext:deno_node/path/_util.ts"; - -export const sep = "/"; -export const delimiter = ":"; - -// path.resolve([from ...], to) -/** - * Resolves `pathSegments` into an absolute path. - * @param pathSegments an array of path segments - */ -export function resolve(...pathSegments: string[]): string { - let resolvedPath = ""; - let resolvedAbsolute = false; - - for (let i = pathSegments.length - 1; i >= -1 && !resolvedAbsolute; i--) { - let path: string; - - if (i >= 0) path = pathSegments[i]; - else { - // deno-lint-ignore no-explicit-any - const { Deno } = globalThis as any; - if (typeof Deno?.cwd !== "function") { - throw new TypeError("Resolved a relative path without a CWD."); - } - path = Deno.cwd(); - } - - assertPath(path); - - // Skip empty entries - if (path.length === 0) { - continue; - } - - resolvedPath = `${path}/${resolvedPath}`; - resolvedAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; - } - - // At this point the path should be resolved to a full absolute path, but - // handle relative paths to be safe (might happen when process.cwd() fails) - - // Normalize the path - resolvedPath = normalizeString( - resolvedPath, - !resolvedAbsolute, - "/", - isPosixPathSeparator, - ); - - if (resolvedAbsolute) { - if (resolvedPath.length > 0) return `/${resolvedPath}`; - else return "/"; - } else if (resolvedPath.length > 0) return resolvedPath; - else return "."; -} - -/** - * Normalize the `path`, resolving `'..'` and `'.'` segments. - * @param path to be normalized - */ -export function normalize(path: string): string { - assertPath(path); - - if (path.length === 0) return "."; - - const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; - const trailingSeparator = - path.charCodeAt(path.length - 1) === CHAR_FORWARD_SLASH; - - // Normalize the path - path = normalizeString(path, !isAbsolute, "/", isPosixPathSeparator); - - if (path.length === 0 && !isAbsolute) path = "."; - if (path.length > 0 && trailingSeparator) path += "/"; - - if (isAbsolute) return `/${path}`; - return path; -} - -/** - * Verifies whether provided path is absolute - * @param path to be verified as absolute - */ -export function isAbsolute(path: string): boolean { - assertPath(path); - return path.length > 0 && path.charCodeAt(0) === CHAR_FORWARD_SLASH; -} - -/** - * Join all given a sequence of `paths`,then normalizes the resulting path. - * @param paths to be joined and normalized - */ -export function join(...paths: string[]): string { - if (paths.length === 0) return "."; - let joined: string | undefined; - for (let i = 0, len = paths.length; i < len; ++i) { - const path = paths[i]; - assertPath(path); - if (path.length > 0) { - if (!joined) joined = path; - else joined += `/${path}`; - } - } - if (!joined) return "."; - return normalize(joined); -} - -/** - * Return the relative path from `from` to `to` based on current working directory. - * @param from path in current working directory - * @param to path in current working directory - */ -export function relative(from: string, to: string): string { - assertPath(from); - assertPath(to); - - if (from === to) return ""; - - from = resolve(from); - to = resolve(to); - - if (from === to) return ""; - - // Trim any leading backslashes - let fromStart = 1; - const fromEnd = from.length; - for (; fromStart < fromEnd; ++fromStart) { - if (from.charCodeAt(fromStart) !== CHAR_FORWARD_SLASH) break; - } - const fromLen = fromEnd - fromStart; - - // Trim any leading backslashes - let toStart = 1; - const toEnd = to.length; - for (; toStart < toEnd; ++toStart) { - if (to.charCodeAt(toStart) !== CHAR_FORWARD_SLASH) break; - } - const toLen = toEnd - toStart; - - // Compare paths to find the longest common path from root - const length = fromLen < toLen ? fromLen : toLen; - let lastCommonSep = -1; - let i = 0; - for (; i <= length; ++i) { - if (i === length) { - if (toLen > length) { - if (to.charCodeAt(toStart + i) === CHAR_FORWARD_SLASH) { - // We get here if `from` is the exact base path for `to`. - // For example: from='/foo/bar'; to='/foo/bar/baz' - return to.slice(toStart + i + 1); - } else if (i === 0) { - // We get here if `from` is the root - // For example: from='/'; to='/foo' - return to.slice(toStart + i); - } - } else if (fromLen > length) { - if (from.charCodeAt(fromStart + i) === CHAR_FORWARD_SLASH) { - // We get here if `to` is the exact base path for `from`. - // For example: from='/foo/bar/baz'; to='/foo/bar' - lastCommonSep = i; - } else if (i === 0) { - // We get here if `to` is the root. - // For example: from='/foo'; to='/' - lastCommonSep = 0; - } - } - break; - } - const fromCode = from.charCodeAt(fromStart + i); - const toCode = to.charCodeAt(toStart + i); - if (fromCode !== toCode) break; - else if (fromCode === CHAR_FORWARD_SLASH) lastCommonSep = i; - } - - let out = ""; - // Generate the relative path based on the path difference between `to` - // and `from` - for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) { - if (i === fromEnd || from.charCodeAt(i) === CHAR_FORWARD_SLASH) { - if (out.length === 0) out += ".."; - else out += "/.."; - } - } - - // Lastly, append the rest of the destination (`to`) path that comes after - // the common path parts - if (out.length > 0) return out + to.slice(toStart + lastCommonSep); - else { - toStart += lastCommonSep; - if (to.charCodeAt(toStart) === CHAR_FORWARD_SLASH) ++toStart; - return to.slice(toStart); - } -} - -/** - * Resolves path to a namespace path - * @param path to resolve to namespace - */ -export function toNamespacedPath(path: string): string { - // Non-op on posix systems - return path; -} - -/** - * Return the directory name of a `path`. - * @param path to determine name for - */ -export function dirname(path: string): string { - assertPath(path); - if (path.length === 0) return "."; - const hasRoot = path.charCodeAt(0) === CHAR_FORWARD_SLASH; - let end = -1; - let matchedSlash = true; - for (let i = path.length - 1; i >= 1; --i) { - if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) { - if (!matchedSlash) { - end = i; - break; - } - } else { - // We saw the first non-path separator - matchedSlash = false; - } - } - - if (end === -1) return hasRoot ? "/" : "."; - if (hasRoot && end === 1) return "//"; - return path.slice(0, end); -} - -/** - * Return the last portion of a `path`. Trailing directory separators are ignored. - * @param path to process - * @param ext of path directory - */ -export function basename(path: string, ext = ""): string { - if (ext !== undefined && typeof ext !== "string") { - throw new ERR_INVALID_ARG_TYPE("ext", ["string"], ext); - } - assertPath(path); - - let start = 0; - let end = -1; - let matchedSlash = true; - let i: number; - - if (ext !== undefined && ext.length > 0 && ext.length <= path.length) { - if (ext.length === path.length && ext === path) return ""; - let extIdx = ext.length - 1; - let firstNonSlashEnd = -1; - for (i = path.length - 1; i >= 0; --i) { - const code = path.charCodeAt(i); - if (code === CHAR_FORWARD_SLASH) { - // If we reached a path separator that was not part of a set of path - // separators at the end of the string, stop now - if (!matchedSlash) { - start = i + 1; - break; - } - } else { - if (firstNonSlashEnd === -1) { - // We saw the first non-path separator, remember this index in case - // we need it if the extension ends up not matching - matchedSlash = false; - firstNonSlashEnd = i + 1; - } - if (extIdx >= 0) { - // Try to match the explicit extension - if (code === ext.charCodeAt(extIdx)) { - if (--extIdx === -1) { - // We matched the extension, so mark this as the end of our path - // component - end = i; - } - } else { - // Extension does not match, so our result is the entire path - // component - extIdx = -1; - end = firstNonSlashEnd; - } - } - } - } - - if (start === end) end = firstNonSlashEnd; - else if (end === -1) end = path.length; - return path.slice(start, end); - } else { - for (i = path.length - 1; i >= 0; --i) { - if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) { - // If we reached a path separator that was not part of a set of path - // separators at the end of the string, stop now - if (!matchedSlash) { - start = i + 1; - break; - } - } else if (end === -1) { - // We saw the first non-path separator, mark this as the end of our - // path component - matchedSlash = false; - end = i + 1; - } - } - - if (end === -1) return ""; - return path.slice(start, end); - } -} - -/** - * Return the extension of the `path`. - * @param path with extension - */ -export function extname(path: string): string { - assertPath(path); - let startDot = -1; - let startPart = 0; - let end = -1; - let matchedSlash = true; - // Track the state of characters (if any) we see before our first dot and - // after any path separator we find - let preDotState = 0; - for (let i = path.length - 1; i >= 0; --i) { - const code = path.charCodeAt(i); - if (code === CHAR_FORWARD_SLASH) { - // If we reached a path separator that was not part of a set of path - // separators at the end of the string, stop now - if (!matchedSlash) { - startPart = i + 1; - break; - } - continue; - } - if (end === -1) { - // We saw the first non-path separator, mark this as the end of our - // extension - matchedSlash = false; - end = i + 1; - } - if (code === CHAR_DOT) { - // If this is our first dot, mark it as the start of our extension - if (startDot === -1) startDot = i; - else if (preDotState !== 1) preDotState = 1; - } else if (startDot !== -1) { - // We saw a non-dot and non-path separator before our dot, so we should - // have a good chance at having a non-empty extension - preDotState = -1; - } - } - - if ( - startDot === -1 || - end === -1 || - // We saw a non-dot character immediately before the dot - preDotState === 0 || - // The (right-most) trimmed path component is exactly '..' - (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) - ) { - return ""; - } - return path.slice(startDot, end); -} - -/** - * Generate a path from `FormatInputPathObject` object. - * @param pathObject with path - */ -export function format(pathObject: FormatInputPathObject): string { - if (pathObject === null || typeof pathObject !== "object") { - throw new ERR_INVALID_ARG_TYPE("pathObject", ["Object"], pathObject); - } - return _format("/", pathObject); -} - -/** - * Return a `ParsedPath` object of the `path`. - * @param path to process - */ -export function parse(path: string): ParsedPath { - assertPath(path); - - const ret: ParsedPath = { root: "", dir: "", base: "", ext: "", name: "" }; - if (path.length === 0) return ret; - const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; - let start: number; - if (isAbsolute) { - ret.root = "/"; - start = 1; - } else { - start = 0; - } - let startDot = -1; - let startPart = 0; - let end = -1; - let matchedSlash = true; - let i = path.length - 1; - - // Track the state of characters (if any) we see before our first dot and - // after any path separator we find - let preDotState = 0; - - // Get non-dir info - for (; i >= start; --i) { - const code = path.charCodeAt(i); - if (code === CHAR_FORWARD_SLASH) { - // If we reached a path separator that was not part of a set of path - // separators at the end of the string, stop now - if (!matchedSlash) { - startPart = i + 1; - break; - } - continue; - } - if (end === -1) { - // We saw the first non-path separator, mark this as the end of our - // extension - matchedSlash = false; - end = i + 1; - } - if (code === CHAR_DOT) { - // If this is our first dot, mark it as the start of our extension - if (startDot === -1) startDot = i; - else if (preDotState !== 1) preDotState = 1; - } else if (startDot !== -1) { - // We saw a non-dot and non-path separator before our dot, so we should - // have a good chance at having a non-empty extension - preDotState = -1; - } - } - - if ( - startDot === -1 || - end === -1 || - // We saw a non-dot character immediately before the dot - preDotState === 0 || - // The (right-most) trimmed path component is exactly '..' - (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) - ) { - if (end !== -1) { - if (startPart === 0 && isAbsolute) { - ret.base = ret.name = path.slice(1, end); - } else { - ret.base = ret.name = path.slice(startPart, end); - } - } - } else { - if (startPart === 0 && isAbsolute) { - ret.name = path.slice(1, startDot); - ret.base = path.slice(1, end); - } else { - ret.name = path.slice(startPart, startDot); - ret.base = path.slice(startPart, end); - } - ret.ext = path.slice(startDot, end); - } - - if (startPart > 0) ret.dir = path.slice(0, startPart - 1); - else if (isAbsolute) ret.dir = "/"; - - return ret; -} - -/** - * Converts a file URL to a path string. - * - * ```ts - * fromFileUrl("file:///home/foo"); // "/home/foo" - * ``` - * @param url of a file URL - */ -export function fromFileUrl(url: string | URL): string { - url = url instanceof URL ? url : new URL(url); - if (url.protocol != "file:") { - throw new TypeError("Must be a file URL."); - } - return decodeURIComponent( - url.pathname.replace(/%(?![0-9A-Fa-f]{2})/g, "%25"), - ); -} - -/** - * Converts a path string to a file URL. - * - * ```ts - * toFileUrl("/home/foo"); // new URL("file:///home/foo") - * ``` - * @param path to convert to file URL - */ -export function toFileUrl(path: string): URL { - if (!isAbsolute(path)) { - throw new TypeError("Must be an absolute path."); - } - const url = new URL("file:///"); - url.pathname = encodeWhitespace( - path.replace(/%/g, "%25").replace(/\\/g, "%5C"), - ); - return url; -} - -export default { +export const { basename, delimiter, dirname, @@ -530,4 +20,6 @@ export default { sep, toFileUrl, toNamespacedPath, -}; +} = path.posix; + +export default path.posix; diff --git a/ext/node/polyfills/path/win32.ts b/ext/node/polyfills/path/win32.ts index 73321e7090..e847b0a3bb 100644 --- a/ext/node/polyfills/path/win32.ts +++ b/ext/node/polyfills/path/win32.ts @@ -2,1011 +2,9 @@ // Ported from https://github.com/browserify/path-browserify/ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -import type { - FormatInputPathObject, - ParsedPath, -} from "ext:deno_node/path/_interface.ts"; -import { - CHAR_BACKWARD_SLASH, - CHAR_COLON, - CHAR_DOT, - CHAR_QUESTION_MARK, -} from "ext:deno_node/path/_constants.ts"; -import { ERR_INVALID_ARG_TYPE } from "ext:deno_node/internal/errors.ts"; +import path from "ext:deno_node/path/mod.ts"; -import { - _format, - assertPath, - encodeWhitespace, - isPathSeparator, - isWindowsDeviceRoot, - normalizeString, -} from "ext:deno_node/path/_util.ts"; -import { assert } from "ext:deno_node/_util/asserts.ts"; - -export const sep = "\\"; -export const delimiter = ";"; - -/** - * Resolves path segments into a `path` - * @param pathSegments to process to path - */ -export function resolve(...pathSegments: string[]): string { - let resolvedDevice = ""; - let resolvedTail = ""; - let resolvedAbsolute = false; - - for (let i = pathSegments.length - 1; i >= -1; i--) { - let path: string; - // deno-lint-ignore no-explicit-any - const { Deno } = globalThis as any; - if (i >= 0) { - path = pathSegments[i]; - } else if (!resolvedDevice) { - if (typeof Deno?.cwd !== "function") { - throw new TypeError("Resolved a drive-letter-less path without a CWD."); - } - path = Deno.cwd(); - } else { - if ( - typeof Deno?.env?.get !== "function" || typeof Deno?.cwd !== "function" - ) { - throw new TypeError("Resolved a relative path without a CWD."); - } - path = Deno.cwd(); - - // Verify that a cwd was found and that it actually points - // to our drive. If not, default to the drive's root. - if ( - path === undefined || - path.slice(0, 3).toLowerCase() !== `${resolvedDevice.toLowerCase()}\\` - ) { - path = `${resolvedDevice}\\`; - } - } - - assertPath(path); - - const len = path.length; - - // Skip empty entries - if (len === 0) continue; - - let rootEnd = 0; - let device = ""; - 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 - device = `\\\\${firstPart}\\${path.slice(last)}`; - rootEnd = j; - } 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 - rootEnd = 1; - isAbsolute = true; - } - - if ( - device.length > 0 && - resolvedDevice.length > 0 && - device.toLowerCase() !== resolvedDevice.toLowerCase() - ) { - // This path points to another device so it is not applicable - continue; - } - - if (resolvedDevice.length === 0 && device.length > 0) { - resolvedDevice = device; - } - if (!resolvedAbsolute) { - resolvedTail = `${path.slice(rootEnd)}\\${resolvedTail}`; - resolvedAbsolute = isAbsolute; - } - - if (resolvedAbsolute && resolvedDevice.length > 0) break; - } - - // At this point the path should be resolved to a full absolute path, - // but handle relative paths to be safe (might happen when process.cwd() - // fails) - - // Normalize the tail path - resolvedTail = normalizeString( - resolvedTail, - !resolvedAbsolute, - "\\", - isPathSeparator, - ); - - return resolvedDevice + (resolvedAbsolute ? "\\" : "") + resolvedTail || "."; -} - -/** - * Normalizes a `path` - * @param path to normalize - */ -export function normalize(path: string): string { - assertPath(path); - const len = path.length; - if (len === 0) return "."; - 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 "\\"; - } else if (tail.length > 0) { - return tail; - } else { - return ""; - } - } else if (isAbsolute) { - if (tail.length > 0) return `${device}\\${tail}`; - else return `${device}\\`; - } else if (tail.length > 0) { - return device + tail; - } else { - return device; - } -} - -/** - * Verifies whether path is absolute - * @param path to verify - */ -export function isAbsolute(path: string): boolean { - assertPath(path); - const len = path.length; - if (len === 0) return false; - - const code = path.charCodeAt(0); - if (isPathSeparator(code)) { - return true; - } else if (isWindowsDeviceRoot(code)) { - // Possible device root - - if (len > 2 && path.charCodeAt(1) === CHAR_COLON) { - if (isPathSeparator(path.charCodeAt(2))) return true; - } - } - return false; -} - -/** - * Join all given a sequence of `paths`,then normalizes the resulting path. - * @param paths to be joined and normalized - */ -export function join(...paths: string[]): string { - const pathsCount = paths.length; - if (pathsCount === 0) return "."; - - let joined: string | undefined; - let firstPart: string | null = null; - for (let i = 0; i < pathsCount; ++i) { - const path = paths[i]; - assertPath(path); - if (path.length > 0) { - if (joined === undefined) joined = firstPart = path; - else joined += `\\${path}`; - } - } - - if (joined === undefined) return "."; - - // Make sure that the joined path doesn't start with two slashes, because - // normalize() will mistake it for an UNC path then. - // - // This step is skipped when it is very clear that the user actually - // intended to point at an UNC path. This is assumed when the first - // non-empty string arguments starts with exactly two slashes followed by - // at least one more non-slash character. - // - // Note that for normalize() to treat a path as an UNC path it needs to - // have at least 2 components, so we don't filter for that here. - // This means that the user can use join to construct UNC paths from - // a server name and a share name; for example: - // path.join('//server', 'share') -> '\\\\server\\share\\') - let needsReplace = true; - let slashCount = 0; - assert(firstPart != null); - if (isPathSeparator(firstPart.charCodeAt(0))) { - ++slashCount; - const firstLen = firstPart.length; - if (firstLen > 1) { - if (isPathSeparator(firstPart.charCodeAt(1))) { - ++slashCount; - if (firstLen > 2) { - if (isPathSeparator(firstPart.charCodeAt(2))) ++slashCount; - else { - // We matched a UNC path in the first part - needsReplace = false; - } - } - } - } - } - if (needsReplace) { - // Find any more consecutive slashes we need to replace - for (; slashCount < joined.length; ++slashCount) { - if (!isPathSeparator(joined.charCodeAt(slashCount))) break; - } - - // Replace the slashes if needed - if (slashCount >= 2) joined = `\\${joined.slice(slashCount)}`; - } - - return normalize(joined); -} - -/** - * It will solve the relative path from `from` to `to`, for instance: - * from = 'C:\\orandea\\test\\aaa' - * to = 'C:\\orandea\\impl\\bbb' - * The output of the function should be: '..\\..\\impl\\bbb' - * @param from relative path - * @param to relative path - */ -export function relative(from: string, to: string): string { - assertPath(from); - assertPath(to); - - if (from === to) return ""; - - const fromOrig = resolve(from); - const toOrig = resolve(to); - - if (fromOrig === toOrig) return ""; - - from = fromOrig.toLowerCase(); - to = toOrig.toLowerCase(); - - if (from === to) return ""; - - // Trim any leading backslashes - let fromStart = 0; - let fromEnd = from.length; - for (; fromStart < fromEnd; ++fromStart) { - if (from.charCodeAt(fromStart) !== CHAR_BACKWARD_SLASH) break; - } - // Trim trailing backslashes (applicable to UNC paths only) - for (; fromEnd - 1 > fromStart; --fromEnd) { - if (from.charCodeAt(fromEnd - 1) !== CHAR_BACKWARD_SLASH) break; - } - const fromLen = fromEnd - fromStart; - - // Trim any leading backslashes - let toStart = 0; - let toEnd = to.length; - for (; toStart < toEnd; ++toStart) { - if (to.charCodeAt(toStart) !== CHAR_BACKWARD_SLASH) break; - } - // Trim trailing backslashes (applicable to UNC paths only) - for (; toEnd - 1 > toStart; --toEnd) { - if (to.charCodeAt(toEnd - 1) !== CHAR_BACKWARD_SLASH) break; - } - const toLen = toEnd - toStart; - - // Compare paths to find the longest common path from root - const length = fromLen < toLen ? fromLen : toLen; - let lastCommonSep = -1; - let i = 0; - for (; i <= length; ++i) { - if (i === length) { - if (toLen > length) { - if (to.charCodeAt(toStart + i) === CHAR_BACKWARD_SLASH) { - // We get here if `from` is the exact base path for `to`. - // For example: from='C:\\foo\\bar'; to='C:\\foo\\bar\\baz' - return toOrig.slice(toStart + i + 1); - } else if (i === 2) { - // We get here if `from` is the device root. - // For example: from='C:\\'; to='C:\\foo' - return toOrig.slice(toStart + i); - } - } - if (fromLen > length) { - if (from.charCodeAt(fromStart + i) === CHAR_BACKWARD_SLASH) { - // We get here if `to` is the exact base path for `from`. - // For example: from='C:\\foo\\bar'; to='C:\\foo' - lastCommonSep = i; - } else if (i === 2) { - // We get here if `to` is the device root. - // For example: from='C:\\foo\\bar'; to='C:\\' - lastCommonSep = 3; - } - } - break; - } - const fromCode = from.charCodeAt(fromStart + i); - const toCode = to.charCodeAt(toStart + i); - if (fromCode !== toCode) break; - else if (fromCode === CHAR_BACKWARD_SLASH) lastCommonSep = i; - } - - // We found a mismatch before the first common path separator was seen, so - // return the original `to`. - if (i !== length && lastCommonSep === -1) { - return toOrig; - } - - let out = ""; - if (lastCommonSep === -1) lastCommonSep = 0; - // Generate the relative path based on the path difference between `to` and - // `from` - for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) { - if (i === fromEnd || from.charCodeAt(i) === CHAR_BACKWARD_SLASH) { - if (out.length === 0) out += ".."; - else out += "\\.."; - } - } - - // Lastly, append the rest of the destination (`to`) path that comes after - // the common path parts - if (out.length > 0) { - return out + toOrig.slice(toStart + lastCommonSep, toEnd); - } else { - toStart += lastCommonSep; - if (toOrig.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) ++toStart; - return toOrig.slice(toStart, toEnd); - } -} - -/** - * Resolves path to a namespace path - * @param path to resolve to namespace - */ -export function toNamespacedPath(path: string): string { - // Note: this will *probably* throw somewhere. - if (typeof path !== "string") return path; - if (path.length === 0) return ""; - - const resolvedPath = resolve(path); - - if (resolvedPath.length >= 3) { - if (resolvedPath.charCodeAt(0) === CHAR_BACKWARD_SLASH) { - // Possible UNC root - - if (resolvedPath.charCodeAt(1) === CHAR_BACKWARD_SLASH) { - const code = resolvedPath.charCodeAt(2); - if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) { - // Matched non-long UNC root, convert the path to a long UNC path - return `\\\\?\\UNC\\${resolvedPath.slice(2)}`; - } - } - } else if (isWindowsDeviceRoot(resolvedPath.charCodeAt(0))) { - // Possible device root - - if ( - resolvedPath.charCodeAt(1) === CHAR_COLON && - resolvedPath.charCodeAt(2) === CHAR_BACKWARD_SLASH - ) { - // Matched device root, convert the path to a long UNC path - return `\\\\?\\${resolvedPath}`; - } - } - } - - return path; -} - -/** - * Return the directory name of a `path`. - * @param path to determine name for - */ -export function dirname(path: string): string { - assertPath(path); - const len = path.length; - if (len === 0) return "."; - 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 path.slice(0, end); -} - -/** - * Return the last portion of a `path`. Trailing directory separators are ignored. - * @param path to process - * @param ext of path directory - */ -export function basename(path: string, ext = ""): string { - if (ext !== undefined && typeof ext !== "string") { - throw new ERR_INVALID_ARG_TYPE("ext", ["string"], ext); - } - - assertPath(path); - - let start = 0; - let end = -1; - let matchedSlash = true; - let i: number; - - // Check for a drive letter prefix so as not to mistake the following - // path separator as an extra separator at the end of the path that can be - // disregarded - if (path.length >= 2) { - const drive = path.charCodeAt(0); - if (isWindowsDeviceRoot(drive)) { - if (path.charCodeAt(1) === CHAR_COLON) start = 2; - } - } - - if (ext !== undefined && ext.length > 0 && ext.length <= path.length) { - if (ext.length === path.length && ext === path) return ""; - let extIdx = ext.length - 1; - let firstNonSlashEnd = -1; - for (i = path.length - 1; i >= start; --i) { - const code = path.charCodeAt(i); - if (isPathSeparator(code)) { - // If we reached a path separator that was not part of a set of path - // separators at the end of the string, stop now - if (!matchedSlash) { - start = i + 1; - break; - } - } else { - if (firstNonSlashEnd === -1) { - // We saw the first non-path separator, remember this index in case - // we need it if the extension ends up not matching - matchedSlash = false; - firstNonSlashEnd = i + 1; - } - if (extIdx >= 0) { - // Try to match the explicit extension - if (code === ext.charCodeAt(extIdx)) { - if (--extIdx === -1) { - // We matched the extension, so mark this as the end of our path - // component - end = i; - } - } else { - // Extension does not match, so our result is the entire path - // component - extIdx = -1; - end = firstNonSlashEnd; - } - } - } - } - - if (start === end) end = firstNonSlashEnd; - else if (end === -1) end = path.length; - return path.slice(start, end); - } else { - for (i = path.length - 1; i >= start; --i) { - if (isPathSeparator(path.charCodeAt(i))) { - // If we reached a path separator that was not part of a set of path - // separators at the end of the string, stop now - if (!matchedSlash) { - start = i + 1; - break; - } - } else if (end === -1) { - // We saw the first non-path separator, mark this as the end of our - // path component - matchedSlash = false; - end = i + 1; - } - } - - if (end === -1) return ""; - return path.slice(start, end); - } -} - -/** - * Return the extension of the `path`. - * @param path with extension - */ -export function extname(path: string): string { - assertPath(path); - let start = 0; - let startDot = -1; - let startPart = 0; - let end = -1; - let matchedSlash = true; - // Track the state of characters (if any) we see before our first dot and - // after any path separator we find - let preDotState = 0; - - // Check for a drive letter prefix so as not to mistake the following - // path separator as an extra separator at the end of the path that can be - // disregarded - - if ( - path.length >= 2 && - path.charCodeAt(1) === CHAR_COLON && - isWindowsDeviceRoot(path.charCodeAt(0)) - ) { - start = startPart = 2; - } - - for (let i = path.length - 1; i >= start; --i) { - const code = path.charCodeAt(i); - if (isPathSeparator(code)) { - // If we reached a path separator that was not part of a set of path - // separators at the end of the string, stop now - if (!matchedSlash) { - startPart = i + 1; - break; - } - continue; - } - if (end === -1) { - // We saw the first non-path separator, mark this as the end of our - // extension - matchedSlash = false; - end = i + 1; - } - if (code === CHAR_DOT) { - // If this is our first dot, mark it as the start of our extension - if (startDot === -1) startDot = i; - else if (preDotState !== 1) preDotState = 1; - } else if (startDot !== -1) { - // We saw a non-dot and non-path separator before our dot, so we should - // have a good chance at having a non-empty extension - preDotState = -1; - } - } - - if ( - startDot === -1 || - end === -1 || - // We saw a non-dot character immediately before the dot - preDotState === 0 || - // The (right-most) trimmed path component is exactly '..' - (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) - ) { - return ""; - } - return path.slice(startDot, end); -} - -/** - * Generate a path from `FormatInputPathObject` object. - * @param pathObject with path - */ -export function format(pathObject: FormatInputPathObject): string { - if (pathObject === null || typeof pathObject !== "object") { - throw new ERR_INVALID_ARG_TYPE("pathObject", ["Object"], pathObject); - } - return _format("\\", pathObject); -} - -/** - * Return a `ParsedPath` object of the `path`. - * @param path to process - */ -export function parse(path: string): ParsedPath { - assertPath(path); - - const ret: ParsedPath = { root: "", dir: "", base: "", ext: "", name: "" }; - - const len = path.length; - if (len === 0) return ret; - - let rootEnd = 0; - let code = path.charCodeAt(0); - - // Try to match a root - if (len > 1) { - if (isPathSeparator(code)) { - // Possible UNC root - - rootEnd = 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 - - rootEnd = j; - } else if (j !== last) { - // We matched a UNC root with leftovers - - rootEnd = j + 1; - } - } - } - } - } else if (isWindowsDeviceRoot(code)) { - // Possible device root - - if (path.charCodeAt(1) === CHAR_COLON) { - rootEnd = 2; - if (len > 2) { - if (isPathSeparator(path.charCodeAt(2))) { - if (len === 3) { - // `path` contains just a drive root, exit early to avoid - // unnecessary work - ret.root = ret.dir = path; - return ret; - } - rootEnd = 3; - } - } else { - // `path` contains just a drive root, exit early to avoid - // unnecessary work - ret.root = ret.dir = path; - return ret; - } - } - } - } else if (isPathSeparator(code)) { - // `path` contains just a path separator, exit early to avoid - // unnecessary work - ret.root = ret.dir = path; - return ret; - } - - if (rootEnd > 0) ret.root = path.slice(0, rootEnd); - - let startDot = -1; - let startPart = rootEnd; - let end = -1; - let matchedSlash = true; - let i = path.length - 1; - - // Track the state of characters (if any) we see before our first dot and - // after any path separator we find - let preDotState = 0; - - // Get non-dir info - for (; i >= rootEnd; --i) { - code = path.charCodeAt(i); - if (isPathSeparator(code)) { - // If we reached a path separator that was not part of a set of path - // separators at the end of the string, stop now - if (!matchedSlash) { - startPart = i + 1; - break; - } - continue; - } - if (end === -1) { - // We saw the first non-path separator, mark this as the end of our - // extension - matchedSlash = false; - end = i + 1; - } - if (code === CHAR_DOT) { - // If this is our first dot, mark it as the start of our extension - if (startDot === -1) startDot = i; - else if (preDotState !== 1) preDotState = 1; - } else if (startDot !== -1) { - // We saw a non-dot and non-path separator before our dot, so we should - // have a good chance at having a non-empty extension - preDotState = -1; - } - } - - if ( - startDot === -1 || - end === -1 || - // We saw a non-dot character immediately before the dot - preDotState === 0 || - // The (right-most) trimmed path component is exactly '..' - (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) - ) { - if (end !== -1) { - ret.base = ret.name = path.slice(startPart, end); - } - } else { - ret.name = path.slice(startPart, startDot); - ret.base = path.slice(startPart, end); - ret.ext = path.slice(startDot, end); - } - - // If the directory is the root, use the entire root as the `dir` including - // the trailing slash if any (`C:\abc` -> `C:\`). Otherwise, strip out the - // trailing slash (`C:\abc\def` -> `C:\abc`). - if (startPart > 0 && startPart !== rootEnd) { - ret.dir = path.slice(0, startPart - 1); - } else ret.dir = ret.root; - - return ret; -} - -/** - * Converts a file URL to a path string. - * - * ```ts - * fromFileUrl("file:///home/foo"); // "\\home\\foo" - * fromFileUrl("file:///C:/Users/foo"); // "C:\\Users\\foo" - * fromFileUrl("file://localhost/home/foo"); // "\\\\localhost\\home\\foo" - * ``` - * @param url of a file URL - */ -export function fromFileUrl(url: string | URL): string { - url = url instanceof URL ? url : new URL(url); - if (url.protocol != "file:") { - throw new TypeError("Must be a file URL."); - } - let path = decodeURIComponent( - url.pathname.replace(/\//g, "\\").replace(/%(?![0-9A-Fa-f]{2})/g, "%25"), - ).replace(/^\\*([A-Za-z]:)(\\|$)/, "$1\\"); - if (url.hostname != "") { - // Note: The `URL` implementation guarantees that the drive letter and - // hostname are mutually exclusive. Otherwise it would not have been valid - // to append the hostname and path like this. - path = `\\\\${url.hostname}${path}`; - } - return path; -} - -/** - * Converts a path string to a file URL. - * - * ```ts - * toFileUrl("\\home\\foo"); // new URL("file:///home/foo") - * toFileUrl("C:\\Users\\foo"); // new URL("file:///C:/Users/foo") - * toFileUrl("\\\\127.0.0.1\\home\\foo"); // new URL("file://127.0.0.1/home/foo") - * ``` - * @param path to convert to file URL - */ -export function toFileUrl(path: string): URL { - if (!isAbsolute(path)) { - throw new TypeError("Must be an absolute path."); - } - const [, hostname, pathname] = path.match( - /^(?:[/\\]{2}([^/\\]+)(?=[/\\](?:[^/\\]|$)))?(.*)/, - )!; - const url = new URL("file:///"); - url.pathname = encodeWhitespace(pathname.replace(/%/g, "%25")); - if (hostname != null && hostname != "localhost") { - url.hostname = hostname; - if (!url.hostname) { - throw new TypeError("Invalid hostname."); - } - } - return url; -} - -export default { +export const { basename, delimiter, dirname, @@ -1022,4 +20,6 @@ export default { sep, toFileUrl, toNamespacedPath, -}; +} = path.win32; + +export default path.win32;