| /** | |
| * Provides path operations through the Effect environment. | |
| * | |
| * The `Path` service works with file system paths without tying code to one | |
| * concrete platform module. It exposes common operations such as joining, | |
| * normalizing, parsing, formatting, resolving, and converting paths to or from | |
| * file URLs. This module includes the service interface, parsed path type, | |
| * service tag, runtime marker, and built-in POSIX path layer. | |
| * | |
| * @since 4.0.0 | |
| */ | |
| import * as Context from "./Context.ts" | |
| import * as Effect from "./Effect.ts" | |
| import { identity } from "./Function.ts" | |
| import * as Layer from "./Layer.ts" | |
| import { BadArgument } from "./PlatformError.ts" | |
| /** | |
| * Runtime type identifier used to mark implementations of the `Path` service. | |
| * | |
| * **Details** | |
| * | |
| * The marker is the exact string stored on `Path` service implementations. | |
| * Most code should depend on the `Path` service instead of inspecting this | |
| * value directly. | |
| * | |
| * @see {@link layer} for the built-in POSIX `Path` service layer | |
| * | |
| * @category type IDs | |
| * @since 4.0.0 | |
| */ | |
| export const TypeId = "~effect/platform/Path" | |
| /** | |
| * Defines the service interface for platform-specific path manipulation. | |
| * | |
| * **When to use** | |
| * | |
| * Use to depend on path operations through the Effect environment instead of a | |
| * concrete host path module. | |
| * | |
| * **Details** | |
| * | |
| * The service exposes operations for joining, normalizing, parsing, | |
| * formatting, and converting file system paths. URL conversion methods return | |
| * `Effect`s because invalid file URLs or paths can fail with `BadArgument`. | |
| * | |
| * **Example** (Using path operations) | |
| * | |
| * ```ts | |
| * import { Effect, Path } from "effect" | |
| * | |
| * const program = Effect.gen(function*() { | |
| * const path = yield* Path.Path | |
| * | |
| * // Use various path operations | |
| * const joined = path.join("home", "user", "documents") | |
| * const normalized = path.normalize("./path/../to/file.txt") | |
| * const basename = path.basename("/path/to/file.txt") | |
| * const dirname = path.dirname("/path/to/file.txt") | |
| * const extname = path.extname("file.txt") | |
| * const isAbs = path.isAbsolute("/absolute/path") | |
| * const parsed = path.parse("/path/to/file.txt") | |
| * const relative = path.relative("/from/path", "/to/path") | |
| * const resolved = path.resolve("relative", "path") | |
| * | |
| * console.log({ | |
| * joined, | |
| * normalized, | |
| * basename, | |
| * dirname, | |
| * extname, | |
| * isAbs, | |
| * parsed, | |
| * relative, | |
| * resolved | |
| * }) | |
| * }) | |
| * ``` | |
| * | |
| * @category models | |
| * @since 4.0.0 | |
| */ | |
| export interface Path { | |
| readonly [TypeId]: typeof TypeId | |
| readonly sep: string | |
| readonly basename: (path: string, suffix?: string) => string | |
| readonly dirname: (path: string) => string | |
| readonly extname: (path: string) => string | |
| readonly format: (pathObject: Partial<Path.Parsed>) => string | |
| readonly fromFileUrl: (url: URL) => Effect.Effect<string, BadArgument> | |
| readonly isAbsolute: (path: string) => boolean | |
| readonly join: (...paths: ReadonlyArray<string>) => string | |
| readonly normalize: (path: string) => string | |
| readonly parse: (path: string) => Path.Parsed | |
| readonly relative: (from: string, to: string) => string | |
| readonly resolve: (...pathSegments: ReadonlyArray<string>) => string | |
| readonly toFileUrl: (path: string) => Effect.Effect<URL, BadArgument> | |
| readonly toNamespacedPath: (path: string) => string | |
| } | |
| /** | |
| * Namespace containing types associated with the `Path` service. | |
| * | |
| * **When to use** | |
| * | |
| * Use to reference types associated with path parsing and formatting. | |
| * | |
| * **Example** (Working with parsed paths) | |
| * | |
| * ```ts | |
| * import { Effect, Path } from "effect" | |
| * | |
| * // Access types and utilities in the Path namespace | |
| * const program = Effect.gen(function*() { | |
| * const path = yield* Path.Path | |
| * | |
| * // Parse a path and get a Path.Parsed object | |
| * const parsed = path.parse("/home/user/file.txt") | |
| * | |
| * // The parsed object conforms to the Path.Parsed interface | |
| * const exampleParsed = { | |
| * root: "/", | |
| * dir: "/home/user", | |
| * base: "file.txt", | |
| * ext: ".txt", | |
| * name: "file" | |
| * } | |
| * | |
| * console.log(parsed, exampleParsed) | |
| * }) | |
| * ``` | |
| * | |
| * @since 4.0.0 | |
| */ | |
| export declare namespace Path { | |
| /** | |
| * Structured representation of a parsed file system path. | |
| * | |
| * **When to use** | |
| * | |
| * Use to model the object form produced by `Path.parse` and consumed by | |
| * `Path.format`. | |
| * | |
| * **Details** | |
| * | |
| * The fields correspond to the path root, directory, base filename, | |
| * extension, and filename without extension, matching the shape consumed by | |
| * `Path.format`. | |
| * | |
| * **Example** (Parsing and formatting paths) | |
| * | |
| * ```ts | |
| * import { Effect, Path } from "effect" | |
| * | |
| * const program = Effect.gen(function*() { | |
| * const path = yield* Path.Path | |
| * | |
| * // Parse a path into its components | |
| * const parsed = path.parse("/home/user/documents/file.txt") | |
| * console.log(parsed) | |
| * // { | |
| * // root: "/", | |
| * // dir: "/home/user/documents", | |
| * // base: "file.txt", | |
| * // ext: ".txt", | |
| * // name: "file" | |
| * // } | |
| * | |
| * // Format a path from its components | |
| * const formatted = path.format({ | |
| * dir: "/home/user", | |
| * name: "newfile", | |
| * ext: ".ts" | |
| * }) | |
| * console.log(formatted) // "/home/user/newfile.ts" | |
| * }) | |
| * ``` | |
| * | |
| * @category models | |
| * @since 4.0.0 | |
| */ | |
| export interface Parsed { | |
| readonly root: string | |
| readonly dir: string | |
| readonly base: string | |
| readonly ext: string | |
| readonly name: string | |
| } | |
| } | |
| /** | |
| * Service tag for accessing the current `Path` implementation. | |
| * | |
| * **When to use** | |
| * | |
| * Use when you need path operations supplied by an effect's environment. | |
| * | |
| * **Example** (Providing a custom Path service) | |
| * | |
| * ```ts | |
| * import { Effect, Layer, Path } from "effect" | |
| * | |
| * // Create a custom path implementation | |
| * const customPath: Path.Path = { | |
| * [Path.TypeId]: Path.TypeId, | |
| * sep: "/", | |
| * basename: (path: string, suffix?: string) => { | |
| * const base = path.split("/").pop() || "" | |
| * return suffix && base.endsWith(suffix) | |
| * ? base.slice(0, -suffix.length) | |
| * : base | |
| * }, | |
| * dirname: (path: string) => path.split("/").slice(0, -1).join("/") || "/", | |
| * extname: (path: string) => { | |
| * const match = path.match(/\.[^.]*$/) | |
| * return match ? match[0] : "" | |
| * }, | |
| * format: (pathObject) => { | |
| * const dir = pathObject.dir || "" | |
| * const name = pathObject.name || "" | |
| * const ext = pathObject.ext || "" | |
| * return dir ? `${dir}/${name}${ext}` : `${name}${ext}` | |
| * }, | |
| * fromFileUrl: (url: URL) => Effect.succeed(url.pathname), | |
| * isAbsolute: (path: string) => path.startsWith("/"), | |
| * join: (...paths: ReadonlyArray<string>) => paths.join("/"), | |
| * normalize: (path: string) => path.replace(/\/+/g, "/"), | |
| * parse: (path: string) => ({ | |
| * root: path.startsWith("/") ? "/" : "", | |
| * dir: path.split("/").slice(0, -1).join("/") || "/", | |
| * base: path.split("/").pop() || "", | |
| * ext: path.match(/\.[^.]*$/)?.[0] || "", | |
| * name: path.split("/").pop()?.replace(/\.[^.]*$/, "") || "" | |
| * }), | |
| * relative: (from: string, to: string) => to.replace(from, ""), | |
| * resolve: (...pathSegments: ReadonlyArray<string>) => pathSegments.join("/"), | |
| * toFileUrl: (path: string) => Effect.succeed(new URL(`file://${path}`)), | |
| * toNamespacedPath: (path: string) => path | |
| * } | |
| * | |
| * // Provide the path service | |
| * const customPathLayer = Layer.succeed(Path.Path)(customPath) | |
| * | |
| * const program = Effect.gen(function*() { | |
| * const path = yield* Path.Path | |
| * const joined = path.join("home", "user", "file.txt") | |
| * console.log(joined) // "home/user/file.txt" | |
| * }) | |
| * | |
| * // Run with custom path implementation | |
| * const result = Effect.provide(program, customPathLayer) | |
| * ``` | |
| * | |
| * @category services | |
| * @since 4.0.0 | |
| */ | |
| export const Path: Context.Service<Path, Path> = Context.Service("effect/Path") | |
| /** | |
| * The following functions are adapted from the Node.js source code: | |
| * https://github.com/nodejs/node/blob/main/lib/internal/url.js | |
| * | |
| * The following license applies to these functions: | |
| * - MIT | |
| */ | |
| // Resolves . and .. elements in a path with directory names | |
| function normalizeStringPosix(path: string, allowAboveRoot: boolean) { | |
| let res = "" | |
| let lastSegmentLength = 0 | |
| let lastSlash = -1 | |
| let dots = 0 | |
| let code | |
| for (let i = 0; i <= path.length; ++i) { | |
| if (i < path.length) { | |
| code = path.charCodeAt(i) | |
| } else if (code === 47 /*/*/) { | |
| break | |
| } else { | |
| code = 47 /*/*/ | |
| } | |
| if (code === 47 /*/*/) { | |
| if (lastSlash === i - 1 || dots === 1) { | |
| // NOOP | |
| } else if (lastSlash !== i - 1 && dots === 2) { | |
| if ( | |
| res.length < 2 || lastSegmentLength !== 2 || res.charCodeAt(res.length - 1) !== 46 /*.*/ || | |
| res.charCodeAt(res.length - 2) !== 46 /*.*/ | |
| ) { | |
| if (res.length > 2) { | |
| const lastSlashIndex = res.lastIndexOf("/") | |
| if (lastSlashIndex !== res.length - 1) { | |
| if (lastSlashIndex === -1) { | |
| res = "" | |
| lastSegmentLength = 0 | |
| } else { | |
| res = res.slice(0, lastSlashIndex) | |
| lastSegmentLength = res.length - 1 - res.lastIndexOf("/") | |
| } | |
| lastSlash = i | |
| dots = 0 | |
| continue | |
| } | |
| } else if (res.length === 2 || res.length === 1) { | |
| res = "" | |
| lastSegmentLength = 0 | |
| lastSlash = i | |
| dots = 0 | |
| continue | |
| } | |
| } | |
| if (allowAboveRoot) { | |
| if (res.length > 0) { | |
| res += "/.." | |
| } else { | |
| res = ".." | |
| } | |
| lastSegmentLength = 2 | |
| } | |
| } else { | |
| if (res.length > 0) { | |
| res += "/" + path.slice(lastSlash + 1, i) | |
| } else { | |
| res = path.slice(lastSlash + 1, i) | |
| } | |
| lastSegmentLength = i - lastSlash - 1 | |
| } | |
| lastSlash = i | |
| dots = 0 | |
| } else if (code === 46 /*.*/ && dots !== -1) { | |
| ++dots | |
| } else { | |
| dots = -1 | |
| } | |
| } | |
| return res | |
| } | |
| function _format(sep: string, pathObject: Partial<Path.Parsed>) { | |
| const dir = pathObject.dir || pathObject.root | |
| const base = pathObject.base || (pathObject.name || "") + (pathObject.ext || "") | |
| if (!dir) { | |
| return base | |
| } | |
| if (dir === pathObject.root) { | |
| return dir + base | |
| } | |
| return dir + sep + base | |
| } | |
| function fromFileUrl(url: URL): Effect.Effect<string, BadArgument> { | |
| if (url.protocol !== "file:") { | |
| return Effect.fail( | |
| new BadArgument({ | |
| module: "Path", | |
| method: "fromFileUrl", | |
| description: "URL must be of scheme file" | |
| }) | |
| ) | |
| } else if (url.hostname !== "") { | |
| return Effect.fail( | |
| new BadArgument({ | |
| module: "Path", | |
| method: "fromFileUrl", | |
| description: "Invalid file URL host" | |
| }) | |
| ) | |
| } | |
| const pathname = url.pathname | |
| for (let n = 0; n < pathname.length; n++) { | |
| if (pathname[n] === "%") { | |
| const third = pathname.codePointAt(n + 2)! | 0x20 | |
| if (pathname[n + 1] === "2" && third === 102) { | |
| return Effect.fail( | |
| new BadArgument({ | |
| module: "Path", | |
| method: "fromFileUrl", | |
| description: "must not include encoded / characters" | |
| }) | |
| ) | |
| } | |
| } | |
| } | |
| return Effect.succeed(decodeURIComponent(pathname)) | |
| } | |
| const resolve: Path["resolve"] = function resolve() { | |
| let resolvedPath = "" | |
| let resolvedAbsolute = false | |
| let cwd: string | undefined = undefined | |
| for (let i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { | |
| let path: string | |
| if (i >= 0) { | |
| path = arguments[i] | |
| } else { | |
| const process = (globalThis as any).process | |
| if ( | |
| cwd === undefined && "process" in globalThis && | |
| typeof process === "object" && | |
| process !== null && | |
| typeof process.cwd === "function" | |
| ) { | |
| cwd = process.cwd() | |
| } | |
| path = cwd! | |
| } | |
| // Skip empty entries | |
| if (path.length === 0) { | |
| continue | |
| } | |
| resolvedPath = path + "/" + resolvedPath | |
| resolvedAbsolute = path.charCodeAt(0) === 47 /*/*/ | |
| } | |
| // 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 = normalizeStringPosix(resolvedPath, !resolvedAbsolute) | |
| if (resolvedAbsolute) { | |
| if (resolvedPath.length > 0) { | |
| return "/" + resolvedPath | |
| } else { | |
| return "/" | |
| } | |
| } else if (resolvedPath.length > 0) { | |
| return resolvedPath | |
| } else { | |
| return "." | |
| } | |
| } | |
| const CHAR_FORWARD_SLASH = 47 | |
| function toFileUrl(filepath: string) { | |
| const outURL = new URL("file://") | |
| let resolved = resolve(filepath) | |
| // path.resolve strips trailing slashes so we must add them back | |
| const filePathLast = filepath.charCodeAt(filepath.length - 1) | |
| if ( | |
| (filePathLast === CHAR_FORWARD_SLASH) && | |
| resolved[resolved.length - 1] !== "/" | |
| ) { | |
| resolved += "/" | |
| } | |
| outURL.pathname = encodePathChars(resolved) | |
| return Effect.succeed(outURL) | |
| } | |
| const percentRegExp = /%/g | |
| const backslashRegExp = /\\/g | |
| const newlineRegExp = /\n/g | |
| const carriageReturnRegExp = /\r/g | |
| const tabRegExp = /\t/g | |
| function encodePathChars(filepath: string) { | |
| if (filepath.includes("%")) { | |
| filepath = filepath.replace(percentRegExp, "%25") | |
| } | |
| if (filepath.includes("\\")) { | |
| filepath = filepath.replace(backslashRegExp, "%5C") | |
| } | |
| if (filepath.includes("\n")) { | |
| filepath = filepath.replace(newlineRegExp, "%0A") | |
| } | |
| if (filepath.includes("\r")) { | |
| filepath = filepath.replace(carriageReturnRegExp, "%0D") | |
| } | |
| if (filepath.includes("\t")) { | |
| filepath = filepath.replace(tabRegExp, "%09") | |
| } | |
| return filepath | |
| } | |
| const posixImpl = Path.of({ | |
| [TypeId]: TypeId, | |
| resolve, | |
| normalize(path) { | |
| if (path.length === 0) return "." | |
| const isAbsolute = path.charCodeAt(0) === 47 /*/*/ | |
| const trailingSeparator = path.charCodeAt(path.length - 1) === 47 /*/*/ | |
| // Normalize the path | |
| path = normalizeStringPosix(path, !isAbsolute) | |
| if (path.length === 0 && !isAbsolute) path = "." | |
| if (path.length > 0 && trailingSeparator) path += "/" | |
| if (isAbsolute) return "/" + path | |
| return path | |
| }, | |
| isAbsolute(path) { | |
| return path.length > 0 && path.charCodeAt(0) === 47 /*/*/ | |
| }, | |
| join() { | |
| if (arguments.length === 0) { | |
| return "." | |
| } | |
| let joined | |
| for (let i = 0; i < arguments.length; ++i) { | |
| const arg = arguments[i] | |
| if (arg.length > 0) { | |
| if (joined === undefined) { | |
| joined = arg | |
| } else { | |
| joined += "/" + arg | |
| } | |
| } | |
| } | |
| if (joined === undefined) { | |
| return "." | |
| } | |
| return posixImpl.normalize(joined) | |
| }, | |
| relative(from, to) { | |
| if (from === to) return "" | |
| from = posixImpl.resolve(from) | |
| to = posixImpl.resolve(to) | |
| if (from === to) return "" | |
| // Trim any leading backslashes | |
| let fromStart = 1 | |
| for (; fromStart < from.length; ++fromStart) { | |
| if (from.charCodeAt(fromStart) !== 47 /*/*/) { | |
| break | |
| } | |
| } | |
| const fromEnd = from.length | |
| const fromLen = fromEnd - fromStart | |
| // Trim any leading backslashes | |
| let toStart = 1 | |
| for (; toStart < to.length; ++toStart) { | |
| if (to.charCodeAt(toStart) !== 47 /*/*/) { | |
| break | |
| } | |
| } | |
| const toEnd = to.length | |
| 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) === 47 /*/*/) { | |
| // 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) === 47 /*/*/) { | |
| // 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 === 47 /*/*/) { | |
| 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) === 47 /*/*/) { | |
| 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) === 47 /*/*/) { | |
| ++toStart | |
| } | |
| return to.slice(toStart) | |
| } | |
| }, | |
| dirname(path) { | |
| if (path.length === 0) return "." | |
| let code = path.charCodeAt(0) | |
| const hasRoot = code === 47 /*/*/ | |
| let end = -1 | |
| let matchedSlash = true | |
| for (let i = path.length - 1; i >= 1; --i) { | |
| code = path.charCodeAt(i) | |
| if (code === 47 /*/*/) { | |
| 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) | |
| }, | |
| basename(path, ext) { | |
| let start = 0 | |
| let end = -1 | |
| let matchedSlash = true | |
| let i | |
| 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 === 47 /*/*/) { | |
| // 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) === 47 /*/*/) { | |
| // 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) | |
| } | |
| }, | |
| extname(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 === 47 /*/*/) { | |
| // 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 === 46 /*.*/) { | |
| // 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) | |
| }, | |
| format: function format(pathObject) { | |
| if (pathObject === null || typeof pathObject !== "object") { | |
| throw new TypeError("The \"pathObject\" argument must be of type Object. Received type " + typeof pathObject) | |
| } | |
| return _format("/", pathObject) | |
| }, | |
| parse(path) { | |
| const ret = { root: "", dir: "", base: "", ext: "", name: "" } | |
| if (path.length === 0) return ret | |
| let code = path.charCodeAt(0) | |
| const isAbsolute = code === 47 /*/*/ | |
| let start | |
| 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) { | |
| code = path.charCodeAt(i) | |
| if (code === 47 /*/*/) { | |
| // 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 === 46 /*.*/) { | |
| // 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 | |
| }, | |
| sep: "/", | |
| fromFileUrl, | |
| toFileUrl, | |
| toNamespacedPath: identity | |
| }) | |
| /** | |
| * Layer that provides the built-in POSIX `Path` implementation. | |
| * | |
| * **When to use** | |
| * | |
| * Use when you need an effect that requires the `Path` service to run with the | |
| * built-in POSIX path implementation. | |
| * | |
| * **Details** | |
| * | |
| * The layer provides a static service whose separator is `/` and whose | |
| * operations use POSIX path semantics. | |
| * | |
| * @see {@link Path} for accessing the `Path` service from an effect | |
| * | |
| * @category layers | |
| * @since 4.0.0 | |
| */ | |
| export const layer: Layer.Layer<Path> = Layer.succeed(Path)(posixImpl) | |
Xet Storage Details
- Size:
- 25.3 kB
- Xet hash:
- 10b6b34aae52edbc7de6dbca7b90a109c363e44e04d6d3bf803b5ea8fed843b3
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.