mirror of
https://github.com/denoland/std.git
synced 2024-11-22 04:59:05 +00:00
d102a10235
* refactor: import from `@std/assert` * update
129 lines
4.2 KiB
TypeScript
129 lines
4.2 KiB
TypeScript
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
|
// This module is browser compatible.
|
|
|
|
import { CHAR_BACKWARD_SLASH } from "../_common/constants.ts";
|
|
import { resolve } from "./resolve.ts";
|
|
import { assertArgs } from "../_common/relative.ts";
|
|
|
|
/**
|
|
* Return the relative path from `from` to `to` based on current working directory.
|
|
*
|
|
* An example in windws, for instance:
|
|
* from = 'C:\\orandea\\test\\aaa'
|
|
* to = 'C:\\orandea\\impl\\bbb'
|
|
* The output of the function should be: '..\\..\\impl\\bbb'
|
|
*
|
|
* @example Usage
|
|
* ```ts
|
|
* import { relative } from "@std/path/windows/relative";
|
|
* import { assertEquals } from "@std/assert";
|
|
*
|
|
* const relativePath = relative("C:\\foobar\\test\\aaa", "C:\\foobar\\impl\\bbb");
|
|
* assertEquals(relativePath, "..\\..\\impl\\bbb");
|
|
* ```
|
|
*
|
|
* @param from The path from which to calculate the relative path
|
|
* @param to The path to which to calculate the relative path
|
|
* @returns The relative path from `from` to `to`
|
|
*/
|
|
export function relative(from: string, to: string): string {
|
|
assertArgs(from, to);
|
|
|
|
const fromOrig = resolve(from);
|
|
const toOrig = resolve(to);
|
|
|
|
if (fromOrig === toOrig) return "";
|
|
|
|
from = fromOrig.toLowerCase();
|
|
to = toOrig.toLowerCase();
|
|
|
|
if (from === to) return "";
|
|
|
|
// Trim any leading backslashes
|
|
let fromStart = 0;
|
|
let fromEnd = from.length;
|
|
for (; fromStart < fromEnd; ++fromStart) {
|
|
if (from.charCodeAt(fromStart) !== CHAR_BACKWARD_SLASH) break;
|
|
}
|
|
// Trim trailing backslashes (applicable to UNC paths only)
|
|
for (; fromEnd - 1 > fromStart; --fromEnd) {
|
|
if (from.charCodeAt(fromEnd - 1) !== CHAR_BACKWARD_SLASH) break;
|
|
}
|
|
const fromLen = fromEnd - fromStart;
|
|
|
|
// Trim any leading backslashes
|
|
let toStart = 0;
|
|
let toEnd = to.length;
|
|
for (; toStart < toEnd; ++toStart) {
|
|
if (to.charCodeAt(toStart) !== CHAR_BACKWARD_SLASH) break;
|
|
}
|
|
// Trim trailing backslashes (applicable to UNC paths only)
|
|
for (; toEnd - 1 > toStart; --toEnd) {
|
|
if (to.charCodeAt(toEnd - 1) !== CHAR_BACKWARD_SLASH) break;
|
|
}
|
|
const toLen = toEnd - toStart;
|
|
|
|
// Compare paths to find the longest common path from root
|
|
const length = fromLen < toLen ? fromLen : toLen;
|
|
let lastCommonSep = -1;
|
|
let i = 0;
|
|
for (; i <= length; ++i) {
|
|
if (i === length) {
|
|
if (toLen > length) {
|
|
if (to.charCodeAt(toStart + i) === CHAR_BACKWARD_SLASH) {
|
|
// We get here if `from` is the exact base path for `to`.
|
|
// For example: from='C:\\foo\\bar'; to='C:\\foo\\bar\\baz'
|
|
return toOrig.slice(toStart + i + 1);
|
|
} else if (i === 2) {
|
|
// We get here if `from` is the device root.
|
|
// For example: from='C:\\'; to='C:\\foo'
|
|
return toOrig.slice(toStart + i);
|
|
}
|
|
}
|
|
if (fromLen > length) {
|
|
if (from.charCodeAt(fromStart + i) === CHAR_BACKWARD_SLASH) {
|
|
// We get here if `to` is the exact base path for `from`.
|
|
// For example: from='C:\\foo\\bar'; to='C:\\foo'
|
|
lastCommonSep = i;
|
|
} else if (i === 2) {
|
|
// We get here if `to` is the device root.
|
|
// For example: from='C:\\foo\\bar'; to='C:\\'
|
|
lastCommonSep = 3;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
const fromCode = from.charCodeAt(fromStart + i);
|
|
const toCode = to.charCodeAt(toStart + i);
|
|
if (fromCode !== toCode) break;
|
|
else if (fromCode === CHAR_BACKWARD_SLASH) lastCommonSep = i;
|
|
}
|
|
|
|
// We found a mismatch before the first common path separator was seen, so
|
|
// return the original `to`.
|
|
if (i !== length && lastCommonSep === -1) {
|
|
return toOrig;
|
|
}
|
|
|
|
let out = "";
|
|
if (lastCommonSep === -1) lastCommonSep = 0;
|
|
// Generate the relative path based on the path difference between `to` and
|
|
// `from`
|
|
for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
|
|
if (i === fromEnd || from.charCodeAt(i) === CHAR_BACKWARD_SLASH) {
|
|
if (out.length === 0) out += "..";
|
|
else out += "\\..";
|
|
}
|
|
}
|
|
|
|
// Lastly, append the rest of the destination (`to`) path that comes after
|
|
// the common path parts
|
|
if (out.length > 0) {
|
|
return out + toOrig.slice(toStart + lastCommonSep, toEnd);
|
|
} else {
|
|
toStart += lastCommonSep;
|
|
if (toOrig.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) ++toStart;
|
|
return toOrig.slice(toStart, toEnd);
|
|
}
|
|
}
|