UNPKG

shelving

Version:

Toolkit for using data in JavaScript.

87 lines (86 loc) 3.65 kB
/** * 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("/")); }