@theia/core
Version:
Theia is a cloud & desktop IDE framework implemented in TypeScript.
308 lines • 11.7 kB
JavaScript
"use strict";
// *****************************************************************************
// Copyright (C) 2017 TypeFox and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
Object.defineProperty(exports, "__esModule", { value: true });
exports.Path = void 0;
/**
* On POSIX:
* ┌──────────────────────┬────────────┐
* │ dir │ base │
* ├──────┬ ├──────┬─────┤
* │ root │ │ name │ ext │
* " / home/user/dir / file .txt "
* └──────┴───────────────┴──────┴─────┘
*
* On Windows:
* ┌──────────────────────┬────────────┐
* │ dir │ base │
* ├──────┬ ├──────┬─────┤
* │ root │ │ name │ ext │
* " /c: / home/user/dir / file .txt "
* └──────┴───────────────┴──────┴─────┘
*/
const os_1 = require("./os");
class Path {
/**
* The raw should be normalized, meaning that only '/' is allowed as a path separator.
*/
constructor(raw) {
raw = Path.normalizePathSeparator(raw);
this.raw = Path.normalizeDrive(raw);
const firstIndex = this.raw.indexOf(Path.separator);
const lastIndex = this.raw.lastIndexOf(Path.separator);
this.isAbsolute = firstIndex === 0;
this.base = lastIndex === -1 ? this.raw : this.raw.substring(lastIndex + 1);
this.isRoot = this.isAbsolute && firstIndex === lastIndex && (!this.base || Path.isDrive(this.base));
this.root = this.computeRoot();
const extIndex = this.base.lastIndexOf('.');
this.name = extIndex === -1 ? this.base : this.base.substring(0, extIndex);
this.ext = extIndex === -1 ? '' : this.base.substring(extIndex);
}
static isDrive(segment) {
return segment.endsWith(':');
}
/**
* vscode-uri always normalizes drive letters to lower case:
* https://github.com/Microsoft/vscode-uri/blob/b1d3221579f97f28a839b6f996d76fc45e9964d8/src/index.ts#L1025
* Theia path should be adjusted to this.
*/
static normalizeDrive(path) {
// lower-case windows drive letters in /C:/fff or C:/fff
if (path.length >= 3 && path.charCodeAt(0) === 47 /* '/' */ && path.charCodeAt(2) === 58 /* ':' */) {
const code = path.charCodeAt(1);
if (code >= 65 /* A */ && code <= 90 /* Z */) {
path = `/${String.fromCharCode(code + 32)}:${path.substring(3)}`; // "/c:".length === 3
}
}
else if (path.length >= 2 && path.charCodeAt(1) === 58 /* ':' */) {
const code = path.charCodeAt(0);
if (code >= 65 /* A */ && code <= 90 /* Z */) {
path = `${String.fromCharCode(code + 32)}:${path.substring(2)}`; // "c:".length === 2
}
if (path.charCodeAt(0) !== 47 /* '/' */) {
path = `${String.fromCharCode(47)}${path}`;
}
}
return path;
}
/**
* Normalize path separator to use Path.separator
* @param Path candidate to normalize
* @returns Normalized string path
*/
static normalizePathSeparator(path) {
return path.split(/[\\]/).join(Path.separator);
}
/**
* Creates a windows path from the given path string.
* A windows path uses an upper case drive letter and backwards slashes.
* @param path The input path
* @returns Windows style path
*/
static windowsPath(path) {
const offset = path.charAt(0) === '/' ? 1 : 0;
if (path.charAt(offset + 1) === ':') {
const driveLetter = path.charAt(offset).toUpperCase();
const substring = path.substring(offset + 2).replace(/\//g, '\\');
return `${driveLetter}:${substring || '\\'}`;
}
return path.replace(/\//g, '\\');
}
/**
* Tildify path, replacing `home` with `~` if user's `home` is present at the beginning of the path.
* This is a non-operation for Windows.
*
* @param resourcePath
* @param home
*/
static tildify(resourcePath, home) {
const path = new Path(resourcePath);
const isWindows = path.root && Path.isDrive(path.root.base);
if (!isWindows && home && resourcePath.indexOf(`${home}/`) === 0) {
return resourcePath.replace(`${home}/`, '~/');
}
return resourcePath;
}
/**
* Untildify path, replacing `~` with `home` if `~` present at the beginning of the path.
* This is a non-operation for Windows.
*
* @param resourcePath
* @param home
*/
static untildify(resourcePath, home) {
if (resourcePath.startsWith('~')) {
const untildifiedResource = resourcePath.replace(/^~/, home);
const untildifiedPath = new Path(untildifiedResource);
const isWindows = untildifiedPath.root && Path.isDrive(untildifiedPath.root.base);
if (!isWindows && home && untildifiedResource.startsWith(`${home}`)) {
return untildifiedResource;
}
}
return resourcePath;
}
computeRoot() {
// '/' -> '/'
// '/c:' -> '/c:'
if (this.isRoot) {
return this;
}
// 'foo/bar' -> `undefined`
if (!this.isAbsolute) {
return undefined;
}
const index = this.raw.indexOf(Path.separator, Path.separator.length);
if (index === -1) {
// '/foo/bar' -> '/'
return new Path(Path.separator);
}
// '/c:/foo/bar' -> '/c:'
// '/foo/bar' -> '/'
return new Path(this.raw.substring(0, index)).root;
}
/**
* Returns the parent directory if it exists (`hasDir === true`) or `this` otherwise.
*/
get dir() {
if (this._dir === undefined) {
this._dir = this.computeDir();
}
return this._dir;
}
/**
* Returns `true` if this has a parent directory, `false` otherwise.
*
* _This implementation returns `true` if and only if this is not the root dir and
* there is a path separator in the raw path._
*/
get hasDir() {
return !this.isRoot && this.raw.lastIndexOf(Path.separator) !== -1;
}
computeDir() {
if (!this.hasDir) {
return this;
}
const lastIndex = this.raw.lastIndexOf(Path.separator);
if (this.isAbsolute) {
const firstIndex = this.raw.indexOf(Path.separator);
if (firstIndex === lastIndex) {
return new Path(this.raw.substring(0, firstIndex + 1));
}
}
return new Path(this.raw.substring(0, lastIndex));
}
join(...paths) {
const relativePath = paths.filter(s => !!s).join(Path.separator);
if (!relativePath) {
return this;
}
if (this.raw.endsWith(Path.separator)) {
return new Path(this.raw + relativePath);
}
return new Path(this.raw + Path.separator + relativePath);
}
/**
*
* @param paths portions of a path
* @returns a new Path if an absolute path can be computed from the segments passed in + this.raw
* If no absolute path can be computed, returns undefined.
*
* Processes the path segments passed in from right to left (reverse order) concatenating until an
* absolute path is found.
*/
resolve(...paths) {
const segments = paths.slice().reverse(); // Don't mutate the caller's array.
segments.push(this.raw);
let result = new Path('');
for (const segment of segments) {
if (segment) {
const next = new Path(segment).join(result.raw);
if (next.isAbsolute) {
return next.normalize();
}
result = next;
}
}
}
toString() {
return this.raw;
}
/**
* Converts the current path into a file system path.
* @param format Determines the format of the path.
* If `undefined`, the format will be determined by the `OS.backend.type` value.
* @returns A file system path.
*/
fsPath(format) {
if (format === Path.Format.Windows || (format === undefined && os_1.OS.backend.isWindows)) {
return Path.windowsPath(this.raw);
}
else {
return this.raw;
}
}
relative(path) {
if (this.raw === path.raw) {
return new Path('');
}
if (!this.raw || !path.raw) {
return undefined;
}
const raw = this.base ? this.raw + Path.separator : this.raw;
if (!path.raw.startsWith(raw)) {
return undefined;
}
const relativePath = path.raw.substring(raw.length);
return new Path(relativePath);
}
isEqualOrParent(path) {
return !!this.relative(path);
}
relativity(path) {
const relative = this.relative(path);
if (relative) {
const relativeStr = relative.toString();
if (relativeStr === '') {
return 0;
}
return relativeStr.split(Path.separator).length;
}
return -1;
}
/*
* return a normalized Path, resolving '..' and '.' segments
*/
normalize() {
const trailingSlash = this.raw.endsWith('/');
const pathArray = this.toString().split('/');
const resultArray = [];
pathArray.forEach((value, index) => {
if (!value || value === '.') {
return;
}
if (value === '..') {
if (resultArray.length && resultArray[resultArray.length - 1] !== '..') {
resultArray.pop();
}
else if (!this.isAbsolute) {
resultArray.push('..');
}
}
else {
resultArray.push(value);
}
});
if (resultArray.length === 0) {
if (this.isRoot) {
return new Path('/');
}
else {
return new Path('.');
}
}
return new Path((this.isAbsolute ? '/' : '') + resultArray.join('/') + (trailingSlash ? '/' : ''));
}
}
exports.Path = Path;
Path.separator = '/';
(function (Path) {
let Format;
(function (Format) {
Format[Format["Posix"] = 0] = "Posix";
Format[Format["Windows"] = 1] = "Windows";
})(Format = Path.Format || (Path.Format = {}));
})(Path = exports.Path || (exports.Path = {}));
//# sourceMappingURL=path.js.map