locutus
Version:
Locutus other languages' standard libraries to JavaScript for fun and educational purposes
77 lines (76 loc) • 2.76 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Rel = Rel;
const cleanPath = (path) => {
if (path === '') {
return '.';
}
const rooted = path.startsWith('/');
const segments = path.split('/');
const stack = [];
for (const segment of segments) {
if (segment === '' || segment === '.') {
continue;
}
if (segment === '..') {
const top = stack.at(-1);
if (top && top !== '..') {
stack.pop();
}
else if (!rooted) {
stack.push('..');
}
continue;
}
stack.push(segment);
}
const cleaned = `${rooted ? '/' : ''}${stack.join('/')}`;
if (cleaned === '') {
return rooted ? '/' : '.';
}
return cleaned;
};
const toSegments = (cleanedPath) => {
if (cleanedPath === '.' || cleanedPath === '/') {
return [];
}
return cleanedPath.split('/').filter(Boolean);
};
function Rel(basePath, targetPath) {
// discuss at: https://locutus.io/golang/filepath/Rel
// parity verified: Go 1.23
// original by: Kevin van Zonneveld (https://kvz.io)
// note 1: Computes a lexical relative path for slash-separated inputs without consulting the current working directory.
// note 2: Throws when one path is rooted and the other is relative, matching Go's error case.
// example 1: Rel('a/b', 'a/b/c')
// returns 1: 'c'
// example 2: Rel('/a/b', '/a/c')
// returns 2: '../c'
// example 3: Rel('a/.', 'a/b/..')
// returns 3: '.'
const baseValue = String(basePath);
const targetValue = String(targetPath);
const cleanBase = cleanPath(baseValue);
const cleanTarget = cleanPath(targetValue);
const baseRooted = cleanBase.startsWith('/');
const targetRooted = cleanTarget.startsWith('/');
if (baseRooted !== targetRooted) {
throw new TypeError(`Rel(): can't make ${targetValue} relative to ${baseValue}`);
}
const baseSegments = toSegments(cleanBase);
const targetSegments = toSegments(cleanTarget);
let shared = 0;
while (shared < baseSegments.length &&
shared < targetSegments.length &&
baseSegments[shared] === targetSegments[shared]) {
shared += 1;
}
if (baseSegments.slice(shared).includes('..')) {
throw new TypeError(`Rel(): can't make ${targetValue} relative to ${baseValue}`);
}
const relativeSegments = [
...Array.from({ length: baseSegments.length - shared }, () => '..'),
...targetSegments.slice(shared),
];
return relativeSegments.length > 0 ? relativeSegments.join('/') : '.';
}