UNPKG

@ayonli/jsext

Version:

A JavaScript extension package for building strong and modern applications.

366 lines (362 loc) 10.8 kB
'use strict'; var env = require('./env.js'); var string = require('./string.js'); var path_util = require('./path/util.js'); /** * Platform-independent utility functions for dealing with file system paths and * URLs. * * The functions in this module are designed to be generic and work in any * runtime, whether server-side or browsers. They can be used for both system * paths and URLs. * @module */ /** * Platform-specific path segment separator. The value is `\` in Windows * server-side environments, and `/` elsewhere. */ const sep = (() => { if (env.isDeno) { if (Deno.build.os === "windows") { return "\\"; } } else if (env.isNodeLike) { if (process.platform === "win32") { return "\\"; } } return "/"; })(); /** * Returns the current working directory. * * **NOTE:** In the browser, this function returns the current origin and pathname. * * This function may fail in unsupported environments or being rejected by the * permission system of the runtime. */ function cwd() { if (env.isDeno) { return Deno.cwd(); } else if (env.isNodeLike) { return process.cwd(); } else if (typeof location === "object" && location.origin) { return location.origin + (location.pathname === "/" ? "" : location.pathname); } else { throw new Error("Unable to determine the current working directory."); } } /** * Concatenates all given `segments` into a well-formed path. * @experimental * * @example * ```ts * import { join } from "@ayonli/jsext/path"; * * console.log(join("foo", "bar")); // "foo/bar" or "foo\\bar" on Windows * console.log(join("/", "foo", "bar")); // "/foo/bar" * console.log(join("C:\\", "foo", "bar")); // "C:\\foo\\bar" * console.log(join("file:///foo", "bar", "..")) // "file:///foo" * * console.log(join("http://example.com", "foo", "bar", "?query")); * // "http://example.com/foo/bar?query" * ``` */ function join(...segments) { let _paths = []; for (let i = 0; i < segments.length; i++) { const path = segments[i]; if (path) { if (path_util.isAbsolute(path)) { _paths = []; } _paths.push(path); } } const paths = []; for (let i = 0; i < _paths.length; i++) { let segment = _paths[i]; for (const _segment of path_util.split(segment)) { if (_segment === "..") { if (!paths.length || paths.every(p => p === "..")) { paths.push(".."); } else if (paths.length > 2 || (paths.length === 2 && !path_util.isAbsolute(paths[1])) || (paths.length === 1 && !path_util.isAbsolute(paths[0]))) { paths.pop(); } } else if (_segment && _segment !== ".") { paths.push(_segment); } } } if (!paths.length) { return "."; } const start = paths[0]; const _sep = path_util.isUrl(start) || path_util.isPosixPath(start) ? "/" : path_util.isWindowsPath(start) ? "\\" : sep; let path = ""; for (let i = 0; i < paths.length; i++) { const segment = paths[i]; if (!path || segment[0] === "?" || segment[0] === "#") { path += segment; } else if (path_util.isVolume(segment)) { if (path) { path += segment + "/"; } else { path = segment; } } else { path += (path.endsWith(_sep) ? "" : _sep) + string.trim(segment, "/\\"); } } if (/^file:\/\/\/[a-z]:$/i.test(path)) { return path + "/"; } else { return path; } } /** * This function is similar to Node.js implementation, but does not preserve * trailing slashes. * * Since Node.js implementation is not well-designed and this function is * identical as calling `join(path)`, so it is deprecated. * @experimental * * @deprecated use {@link join} or {@link sanitize} instead. */ function normalize(path) { return join(path); } /** * Similar to {@link normalize}, but also remove the search string and hash * string if present. * @experimental * * @example * ```ts * import { sanitize } from "@ayonli/jsext/path"; * * console.log(sanitize("foo/bar?query")); // "foo/bar" * console.log(sanitize("foo/bar#hash")); // "foo/bar" * console.log(sanitize("foo/bar/..?query#hash")); // "foo" * console.log(sanitize("foo/./bar/..?query#hash")); // "foo" * ``` */ function sanitize(path) { return join(...path_util.split(path).filter(path_util.isNotQuery)); } /** * Resolves path `segments` into a well-formed path. * * This function is similar to {@link join}, except it always returns an * absolute path based on the current working directory if the input segments * are not absolute by themselves. * @experimental */ function resolve(...segments) { segments = segments.filter(s => s !== ""); const _cwd = cwd(); if (!segments.length) { return _cwd; } segments = path_util.isAbsolute(segments[0]) ? segments : [_cwd, ...segments]; return join(...segments); } /** * Returns the parent path of the given `path`. * @experimental * * @example * ```ts * import { dirname } from "@ayonli/jsext/path"; * * console.log(dirname("foo/bar")); // "foo" * console.log(dirname("/foo/bar")); // "/foo" * console.log(dirname("C:\\foo\\bar")); // "C:\\foo" * console.log(dirname("file:///foo/bar")); // "file:///foo" * console.log(dirname("http://example.com/foo/bar")); // "http://example.com/foo" * console.log(dirname("http://example.com/foo")); // "http://example.com" * console.log(dirname("http://example.com/foo/bar?foo=bar#baz")); // "http://example.com/foo" * ``` */ function dirname(path) { if (path_util.isUrl(path)) { const { protocol, host, pathname } = new URL(path); const origin = protocol + "//" + host; const _dirname = dirname(decodeURI(pathname)); if (_dirname === "/") { return path_util.isFileProtocol(origin) ? origin + "/" : origin; } else { return origin + _dirname; } } else { const segments = path_util.split(path).filter(path_util.isNotQuery); const last = segments.pop(); if (segments.length) { return join(...segments); } else if (last === "/") { return "/"; } else if (path_util.isVolume(last, true)) { return last + "\\"; } else if (path_util.isVolume(last)) { return last; } else { return "."; } } } /** * Return the last portion of the given `path`. Trailing directory separators * are ignored, and optional `suffix` is removed. * @experimental * * @example * ```ts * import { basename } from "@ayonli/jsext/path"; * * console.log(basename("/foo/bar")); // "bar" * console.log(basename("c:\\foo\\bar")); // "bar" * console.log(basename("file:///foo/bar")); // "bar" * console.log(basename("http://example.com/foo/bar")); // "bar" * console.log(basename("http://example.com/foo/bar?foo=bar#baz")); // "bar" * console.log(basename("http://example.com/foo/bar.txt?foo=bar#baz", ".txt")); // "bar" * ``` */ function basename(path, suffix = "") { if (path_util.isUrl(path)) { const { pathname } = new URL(path); return basename(decodeURI(pathname), suffix); } else { const segments = path_util.split(path).filter(path_util.isNotQuery); const _basename = segments.pop(); if (!_basename || _basename === "/" || path_util.isVolume(_basename)) { return ""; } else if (suffix) { return string.stripEnd(_basename, suffix); } else { return _basename; } } } /** * Returns the extension of the `path` with leading period. * @experimental * * @example * ```ts * import { extname } from "@ayonli/jsext/path"; * * console.log(extname("/foo/bar.txt")); // ".txt" * console.log(extname("c:\\foo\\bar.txt")); // ".txt" * console.log(extname("file:///foo/bar.txt")); // ".txt" * console.log(extname("http://example.com/foo/bar.txt")); // ".txt" * console.log(extname("http://example.com/foo/bar.txt?foo=bar#baz")); // ".txt" * ``` */ function extname(path) { const base = basename(path); const index = base.lastIndexOf("."); if (index === -1) { return ""; } else { return base.slice(index); } } /** * Converts the given path to a file URL if it's not one already. * @experimental * * @example * ```ts * import { toFileUrl } from "@ayonli/jsext/path"; * * console.log(toFileUrl("foo/bar")); // "file:///foo/bar" * console.log(toFileUrl("c:\\foo\\bar")); // "file:///c:/foo/bar" * ``` */ function toFileUrl(path) { if (path_util.isFileUrl(path)) { return path; } else if (!path_util.isUrl(path)) { let _path = resolve(path).replace(/\\/g, "/"); _path = _path[0] === "/" ? _path : "/" + _path; return new URL("file://" + _path).href; } else { throw new Error("Cannot convert a URL to a file URL."); } } /** * Converts the given URL to a file system path if it's not one already. * @experimental * * @example * ```ts * import { toFsPath } from "@ayonli/jsext/path"; * * console.log(toFsPath("file:///foo/bar")); // "/foo/bar" * console.log(toFsPath("file:///c:/foo/bar")); // "c:\\foo\\bar" * ``` */ function toFsPath(url) { if (path_util.isFsPath(url)) { return url; } else if (path_util.isFileUrl(url)) { url = url.replace(/^file:(\/\/)?/i, "").replace(/^\/([a-z]):/i, "$1:"); return join(url); } else if (!path_util.isUrl(url)) { return resolve(url); } else { throw new Error("Cannot convert a URL to a file system path."); } } exports.contains = path_util.contains; exports.endsWith = path_util.endsWith; exports.equals = path_util.equals; exports.isAbsolute = path_util.isAbsolute; exports.isFileUrl = path_util.isFileUrl; exports.isFsPath = path_util.isFsPath; exports.isPosixPath = path_util.isPosixPath; exports.isUrl = path_util.isUrl; exports.isWindowsPath = path_util.isWindowsPath; exports.split = path_util.split; exports.startsWith = path_util.startsWith; exports.basename = basename; exports.cwd = cwd; exports.dirname = dirname; exports.extname = extname; exports.join = join; exports.normalize = normalize; exports.resolve = resolve; exports.sanitize = sanitize; exports.sep = sep; exports.toFileUrl = toFileUrl; exports.toFsPath = toFsPath; //# sourceMappingURL=path.js.map