shelving
Version:
Toolkit for using data in JavaScript.
87 lines (86 loc) • 3.65 kB
JavaScript
/**
* Reuseable utilities for dealing with filesystem paths.
*/
import { RequiredError } from "../error/RequiredError.js";
import { isNullish, splitString } from "./index.js";
/** Is a string path an absolute path? */
export function isAbsolutePath(path) {
return typeof path === "string" && path.startsWith("/");
}
/** Is a string path an relative path? */
export function isRelativePath(path) {
return typeof path === "string" && (path === "." || path.startsWith("./"));
}
/**
* Resolve a relative or absolute path and return the absolute path, or `undefined` if not a valid path.
* - Normalises runs of `//` more than one slash.
* - Normalises `\` windows paths.
* - Strips trailing slashes.
*
* @param path Absolute path e.g. `/a/b/c`, relative path e.g. `./a` or `b` or `../c`, URL string e.g. `http://shax.com/a/b/c`, or `URL` instance.
* @param base Absolute path used for resolving relative paths in `possible`
* @return Absolute path with a leading slash but no trailing slash, e.g. `/a/c/b`
*/
export function getPath(inputPath, inputBase = "/") {
if (isNullish(inputPath))
return;
if (isAbsolutePath(inputPath))
return cleanPath(inputPath);
return joinPath(inputBase, isRelativePath(inputPath) ? inputPath.slice(2) : inputPath);
}
export function cleanPath(path) {
const clean = path.replace(/[\\/]+(?:\.(?:[\\/]+|$))*/g, "/");
return clean.length > 1 && clean.endsWith("/") ? clean.slice(0, -1) : clean;
}
/**
* Resolve a relative or absolute path and return the path, or throw `RequiredError` if not a valid path.
* - Internally uses `new URL` to do path processing but shouldn't ever reveal that fact.
* - Returned paths are cleaned with `cleanPath()` so runs of slashes and trailing slashes are removed.
*
* @param path Absolute path e.g. `/a/b/c`, relative path e.g. `./a` or `b` or `../c`, URL string e.g. `http://shax.com/a/b/c`, or `URL` instance.
* @param base Absolute path used for resolving relative paths in `possible`
* @return Absolute path with a leading trailing slash, e.g. `/a/c/b`
*/
export function requirePath(path, base, caller = requirePath) {
const output = getPath(path, base);
if (!output)
throw new RequiredError("Invalid path", { received: path, caller });
return output;
}
/**
* Match and strip a base path prefix from a path using segment-aware pathname rules.
* - Both inputs must be absolute paths that begin with `/`.
* - Returns `/` when the paths are an exact match.
*/
export function matchPathPrefix(target, base, caller = matchPathPrefix) {
const basePath = requirePath(base, undefined, caller);
const targetPath = requirePath(target, basePath, caller);
if (basePath === "/")
return targetPath;
if (targetPath === basePath)
return "/";
if (targetPath.startsWith(`${basePath}/`))
return targetPath.slice(basePath.length);
}
/** Is a target path active? */
export function isPathActive(target, current) {
return target === current;
}
/** Is a target path proud (i.e. is the current path, or is a child of the current path)? */
export function isPathProud(target, current) {
return target === current || (target !== "/" && target.startsWith(`${current}/`));
}
/**
* Get the "segments" in an absolute path.
* - `splitPath("/")` returns `[]` — the root has no segments.
*/
export function splitPath(path) {
if (typeof path !== "string")
return path;
if (path === "/")
return [];
return splitString(path.slice(1), "/", 1, undefined, splitPath);
}
export function joinPath(...parts) {
return cleanPath(parts.flat().join("/"));
}