295 lines
8.6 KiB
Plaintext
295 lines
8.6 KiB
Plaintext
import { last } from "./utils.js";
|
|
function joinPaths(paths) {
|
|
return cleanPath(
|
|
paths.filter((val) => {
|
|
return val !== void 0;
|
|
}).join("/")
|
|
);
|
|
}
|
|
function cleanPath(path) {
|
|
return path.replace(/\/{2,}/g, "/");
|
|
}
|
|
function trimPathLeft(path) {
|
|
return path === "/" ? path : path.replace(/^\/{1,}/, "");
|
|
}
|
|
function trimPathRight(path) {
|
|
return path === "/" ? path : path.replace(/\/{1,}$/, "");
|
|
}
|
|
function trimPath(path) {
|
|
return trimPathRight(trimPathLeft(path));
|
|
}
|
|
function removeTrailingSlash(value, basepath) {
|
|
if ((value == null ? void 0 : value.endsWith("/")) && value !== "/" && value !== `${basepath}/`) {
|
|
return value.slice(0, -1);
|
|
}
|
|
return value;
|
|
}
|
|
function exactPathTest(pathName1, pathName2, basepath) {
|
|
return removeTrailingSlash(pathName1, basepath) === removeTrailingSlash(pathName2, basepath);
|
|
}
|
|
function resolvePath({
|
|
basepath,
|
|
base,
|
|
to,
|
|
trailingSlash = "never",
|
|
caseSensitive
|
|
}) {
|
|
var _a, _b;
|
|
base = removeBasepath(basepath, base, caseSensitive);
|
|
to = removeBasepath(basepath, to, caseSensitive);
|
|
let baseSegments = parsePathname(base);
|
|
const toSegments = parsePathname(to);
|
|
if (baseSegments.length > 1 && ((_a = last(baseSegments)) == null ? void 0 : _a.value) === "/") {
|
|
baseSegments.pop();
|
|
}
|
|
toSegments.forEach((toSegment, index) => {
|
|
if (toSegment.value === "/") {
|
|
if (!index) {
|
|
baseSegments = [toSegment];
|
|
} else if (index === toSegments.length - 1) {
|
|
baseSegments.push(toSegment);
|
|
} else ;
|
|
} else if (toSegment.value === "..") {
|
|
baseSegments.pop();
|
|
} else if (toSegment.value === ".") ;
|
|
else {
|
|
baseSegments.push(toSegment);
|
|
}
|
|
});
|
|
if (baseSegments.length > 1) {
|
|
if (((_b = last(baseSegments)) == null ? void 0 : _b.value) === "/") {
|
|
if (trailingSlash === "never") {
|
|
baseSegments.pop();
|
|
}
|
|
} else if (trailingSlash === "always") {
|
|
baseSegments.push({ type: "pathname", value: "/" });
|
|
}
|
|
}
|
|
const joined = joinPaths([basepath, ...baseSegments.map((d) => d.value)]);
|
|
return cleanPath(joined);
|
|
}
|
|
function parsePathname(pathname) {
|
|
if (!pathname) {
|
|
return [];
|
|
}
|
|
pathname = cleanPath(pathname);
|
|
const segments = [];
|
|
if (pathname.slice(0, 1) === "/") {
|
|
pathname = pathname.substring(1);
|
|
segments.push({
|
|
type: "pathname",
|
|
value: "/"
|
|
});
|
|
}
|
|
if (!pathname) {
|
|
return segments;
|
|
}
|
|
const split = pathname.split("/").filter(Boolean);
|
|
segments.push(
|
|
...split.map((part) => {
|
|
if (part === "$" || part === "*") {
|
|
return {
|
|
type: "wildcard",
|
|
value: part
|
|
};
|
|
}
|
|
if (part.charAt(0) === "$") {
|
|
return {
|
|
type: "param",
|
|
value: part
|
|
};
|
|
}
|
|
return {
|
|
type: "pathname",
|
|
value: part.includes("%25") ? part.split("%25").map((segment) => decodeURI(segment)).join("%25") : decodeURI(part)
|
|
};
|
|
})
|
|
);
|
|
if (pathname.slice(-1) === "/") {
|
|
pathname = pathname.substring(1);
|
|
segments.push({
|
|
type: "pathname",
|
|
value: "/"
|
|
});
|
|
}
|
|
return segments;
|
|
}
|
|
function interpolatePath({
|
|
path,
|
|
params,
|
|
leaveWildcards,
|
|
leaveParams,
|
|
decodeCharMap
|
|
}) {
|
|
const interpolatedPathSegments = parsePathname(path);
|
|
function encodeParam(key) {
|
|
const value = params[key];
|
|
const isValueString = typeof value === "string";
|
|
if (["*", "_splat"].includes(key)) {
|
|
return isValueString ? encodeURI(value) : value;
|
|
} else {
|
|
return isValueString ? encodePathParam(value, decodeCharMap) : value;
|
|
}
|
|
}
|
|
const usedParams = {};
|
|
const interpolatedPath = joinPaths(
|
|
interpolatedPathSegments.map((segment) => {
|
|
if (segment.type === "wildcard") {
|
|
usedParams._splat = params._splat;
|
|
const value = encodeParam("_splat");
|
|
if (leaveWildcards) return `${segment.value}${value ?? ""}`;
|
|
return value;
|
|
}
|
|
if (segment.type === "param") {
|
|
const key = segment.value.substring(1);
|
|
usedParams[key] = params[key];
|
|
if (leaveParams) {
|
|
const value = encodeParam(segment.value);
|
|
return `${segment.value}${value ?? ""}`;
|
|
}
|
|
return encodeParam(key) ?? "undefined";
|
|
}
|
|
return segment.value;
|
|
})
|
|
);
|
|
return { usedParams, interpolatedPath };
|
|
}
|
|
function encodePathParam(value, decodeCharMap) {
|
|
let encoded = encodeURIComponent(value);
|
|
if (decodeCharMap) {
|
|
for (const [encodedChar, char] of decodeCharMap) {
|
|
encoded = encoded.replaceAll(encodedChar, char);
|
|
}
|
|
}
|
|
return encoded;
|
|
}
|
|
function matchPathname(basepath, currentPathname, matchLocation) {
|
|
const pathParams = matchByPath(basepath, currentPathname, matchLocation);
|
|
if (matchLocation.to && !pathParams) {
|
|
return;
|
|
}
|
|
return pathParams ?? {};
|
|
}
|
|
function removeBasepath(basepath, pathname, caseSensitive = false) {
|
|
const normalizedBasepath = caseSensitive ? basepath : basepath.toLowerCase();
|
|
const normalizedPathname = caseSensitive ? pathname : pathname.toLowerCase();
|
|
switch (true) {
|
|
// default behaviour is to serve app from the root - pathname
|
|
// left untouched
|
|
case normalizedBasepath === "/":
|
|
return pathname;
|
|
// shortcut for removing the basepath if it matches the pathname
|
|
case normalizedPathname === normalizedBasepath:
|
|
return "";
|
|
// in case pathname is shorter than basepath - there is
|
|
// nothing to remove
|
|
case pathname.length < basepath.length:
|
|
return pathname;
|
|
// avoid matching partial segments - strict equality handled
|
|
// earlier, otherwise, basepath separated from pathname with
|
|
// separator, therefore lack of separator means partial
|
|
// segment match (`/app` should not match `/application`)
|
|
case normalizedPathname[normalizedBasepath.length] !== "/":
|
|
return pathname;
|
|
// remove the basepath from the pathname if it starts with it
|
|
case normalizedPathname.startsWith(normalizedBasepath):
|
|
return pathname.slice(basepath.length);
|
|
// otherwise, return the pathname as is
|
|
default:
|
|
return pathname;
|
|
}
|
|
}
|
|
function matchByPath(basepath, from, matchLocation) {
|
|
if (basepath !== "/" && !from.startsWith(basepath)) {
|
|
return void 0;
|
|
}
|
|
from = removeBasepath(basepath, from, matchLocation.caseSensitive);
|
|
const to = removeBasepath(
|
|
basepath,
|
|
`${matchLocation.to ?? "$"}`,
|
|
matchLocation.caseSensitive
|
|
);
|
|
const baseSegments = parsePathname(from);
|
|
const routeSegments = parsePathname(to);
|
|
if (!from.startsWith("/")) {
|
|
baseSegments.unshift({
|
|
type: "pathname",
|
|
value: "/"
|
|
});
|
|
}
|
|
if (!to.startsWith("/")) {
|
|
routeSegments.unshift({
|
|
type: "pathname",
|
|
value: "/"
|
|
});
|
|
}
|
|
const params = {};
|
|
const isMatch = (() => {
|
|
for (let i = 0; i < Math.max(baseSegments.length, routeSegments.length); i++) {
|
|
const baseSegment = baseSegments[i];
|
|
const routeSegment = routeSegments[i];
|
|
const isLastBaseSegment = i >= baseSegments.length - 1;
|
|
const isLastRouteSegment = i >= routeSegments.length - 1;
|
|
if (routeSegment) {
|
|
if (routeSegment.type === "wildcard") {
|
|
const _splat = decodeURI(
|
|
joinPaths(baseSegments.slice(i).map((d) => d.value))
|
|
);
|
|
params["*"] = _splat;
|
|
params["_splat"] = _splat;
|
|
return true;
|
|
}
|
|
if (routeSegment.type === "pathname") {
|
|
if (routeSegment.value === "/" && !(baseSegment == null ? void 0 : baseSegment.value)) {
|
|
return true;
|
|
}
|
|
if (baseSegment) {
|
|
if (matchLocation.caseSensitive) {
|
|
if (routeSegment.value !== baseSegment.value) {
|
|
return false;
|
|
}
|
|
} else if (routeSegment.value.toLowerCase() !== baseSegment.value.toLowerCase()) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
if (!baseSegment) {
|
|
return false;
|
|
}
|
|
if (routeSegment.type === "param") {
|
|
if (baseSegment.value === "/") {
|
|
return false;
|
|
}
|
|
if (baseSegment.value.charAt(0) !== "$") {
|
|
params[routeSegment.value.substring(1)] = decodeURIComponent(
|
|
baseSegment.value
|
|
);
|
|
}
|
|
}
|
|
}
|
|
if (!isLastBaseSegment && isLastRouteSegment) {
|
|
params["**"] = joinPaths(baseSegments.slice(i + 1).map((d) => d.value));
|
|
return !!matchLocation.fuzzy && (routeSegment == null ? void 0 : routeSegment.value) !== "/";
|
|
}
|
|
}
|
|
return true;
|
|
})();
|
|
return isMatch ? params : void 0;
|
|
}
|
|
export {
|
|
cleanPath,
|
|
exactPathTest,
|
|
interpolatePath,
|
|
joinPaths,
|
|
matchByPath,
|
|
matchPathname,
|
|
parsePathname,
|
|
removeBasepath,
|
|
removeTrailingSlash,
|
|
resolvePath,
|
|
trimPath,
|
|
trimPathLeft,
|
|
trimPathRight
|
|
};
|
|
//# sourceMappingURL=path.js.map
|