UNPKG

11.6 kBJavaScriptView Raw
1"use strict";
2// *****************************************************************************
3// Copyright (C) 2017 TypeFox and others.
4//
5// This program and the accompanying materials are made available under the
6// terms of the Eclipse Public License v. 2.0 which is available at
7// http://www.eclipse.org/legal/epl-2.0.
8//
9// This Source Code may also be made available under the following Secondary
10// Licenses when the conditions for such availability set forth in the Eclipse
11// Public License v. 2.0 are satisfied: GNU General Public License, version 2
12// with the GNU Classpath Exception which is available at
13// https://www.gnu.org/software/classpath/license.html.
14//
15// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
16// *****************************************************************************
17Object.defineProperty(exports, "__esModule", { value: true });
18exports.Path = void 0;
19/**
20 * On POSIX:
21 * ┌──────────────────────┬────────────┐
22 * │ dir │ base │
23 * ├──────┬ ├──────┬─────┤
24 * │ root │ │ name │ ext │
25 * " / home/user/dir / file .txt "
26 * └──────┴───────────────┴──────┴─────┘
27 *
28 * On Windows:
29 * ┌──────────────────────┬────────────┐
30 * │ dir │ base │
31 * ├──────┬ ├──────┬─────┤
32 * │ root │ │ name │ ext │
33 * " /c: / home/user/dir / file .txt "
34 * └──────┴───────────────┴──────┴─────┘
35 */
36const os_1 = require("./os");
37class Path {
38 /**
39 * The raw should be normalized, meaning that only '/' is allowed as a path separator.
40 */
41 constructor(raw) {
42 raw = Path.normalizePathSeparator(raw);
43 this.raw = Path.normalizeDrive(raw);
44 const firstIndex = this.raw.indexOf(Path.separator);
45 const lastIndex = this.raw.lastIndexOf(Path.separator);
46 this.isAbsolute = firstIndex === 0;
47 this.base = lastIndex === -1 ? this.raw : this.raw.substr(lastIndex + 1);
48 this.isRoot = this.isAbsolute && firstIndex === lastIndex && (!this.base || Path.isDrive(this.base));
49 this.root = this.computeRoot();
50 const extIndex = this.base.lastIndexOf('.');
51 this.name = extIndex === -1 ? this.base : this.base.substr(0, extIndex);
52 this.ext = extIndex === -1 ? '' : this.base.substr(extIndex);
53 }
54 static isDrive(segment) {
55 return segment.endsWith(':');
56 }
57 /**
58 * vscode-uri always normalizes drive letters to lower case:
59 * https://github.com/Microsoft/vscode-uri/blob/b1d3221579f97f28a839b6f996d76fc45e9964d8/src/index.ts#L1025
60 * Theia path should be adjusted to this.
61 */
62 static normalizeDrive(path) {
63 // lower-case windows drive letters in /C:/fff or C:/fff
64 if (path.length >= 3 && path.charCodeAt(0) === 47 /* '/' */ && path.charCodeAt(2) === 58 /* ':' */) {
65 const code = path.charCodeAt(1);
66 if (code >= 65 /* A */ && code <= 90 /* Z */) {
67 path = `/${String.fromCharCode(code + 32)}:${path.substr(3)}`; // "/c:".length === 3
68 }
69 }
70 else if (path.length >= 2 && path.charCodeAt(1) === 58 /* ':' */) {
71 const code = path.charCodeAt(0);
72 if (code >= 65 /* A */ && code <= 90 /* Z */) {
73 path = `${String.fromCharCode(code + 32)}:${path.substr(2)}`; // "c:".length === 2
74 }
75 if (path.charCodeAt(0) !== 47 /* '/' */) {
76 path = `${String.fromCharCode(47)}${path}`;
77 }
78 }
79 return path;
80 }
81 /**
82 * Normalize path separator to use Path.separator
83 * @param Path candidate to normalize
84 * @returns Normalized string path
85 */
86 static normalizePathSeparator(path) {
87 return path.split(/[\\]/).join(Path.separator);
88 }
89 /**
90 * Creates a windows path from the given path string.
91 * A windows path uses an upper case drive letter and backwards slashes.
92 * @param path The input path
93 * @returns Windows style path
94 */
95 static windowsPath(path) {
96 const offset = path.charAt(0) === '/' ? 1 : 0;
97 if (path.charAt(offset + 1) === ':') {
98 const driveLetter = path.charAt(offset).toUpperCase();
99 const substring = path.substring(offset + 2).replace(/\//g, '\\');
100 return `${driveLetter}:${substring || '\\'}`;
101 }
102 return path.replace(/\//g, '\\');
103 }
104 /**
105 * Tildify path, replacing `home` with `~` if user's `home` is present at the beginning of the path.
106 * This is a non-operation for Windows.
107 *
108 * @param resourcePath
109 * @param home
110 */
111 static tildify(resourcePath, home) {
112 const path = new Path(resourcePath);
113 const isWindows = path.root && Path.isDrive(path.root.base);
114 if (!isWindows && home && resourcePath.indexOf(`${home}/`) === 0) {
115 return resourcePath.replace(`${home}/`, '~/');
116 }
117 return resourcePath;
118 }
119 /**
120 * Untildify path, replacing `~` with `home` if `~` present at the beginning of the path.
121 * This is a non-operation for Windows.
122 *
123 * @param resourcePath
124 * @param home
125 */
126 static untildify(resourcePath, home) {
127 if (resourcePath.startsWith('~')) {
128 const untildifiedResource = resourcePath.replace(/^~/, home);
129 const untildifiedPath = new Path(untildifiedResource);
130 const isWindows = untildifiedPath.root && Path.isDrive(untildifiedPath.root.base);
131 if (!isWindows && home && untildifiedResource.startsWith(`${home}`)) {
132 return untildifiedResource;
133 }
134 }
135 return resourcePath;
136 }
137 computeRoot() {
138 // '/' -> '/'
139 // '/c:' -> '/c:'
140 if (this.isRoot) {
141 return this;
142 }
143 // 'foo/bar' -> `undefined`
144 if (!this.isAbsolute) {
145 return undefined;
146 }
147 const index = this.raw.indexOf(Path.separator, Path.separator.length);
148 if (index === -1) {
149 // '/foo/bar' -> '/'
150 return new Path(Path.separator);
151 }
152 // '/c:/foo/bar' -> '/c:'
153 // '/foo/bar' -> '/'
154 return new Path(this.raw.substr(0, index)).root;
155 }
156 /**
157 * Returns the parent directory if it exists (`hasDir === true`) or `this` otherwise.
158 */
159 get dir() {
160 if (this._dir === undefined) {
161 this._dir = this.computeDir();
162 }
163 return this._dir;
164 }
165 /**
166 * Returns `true` if this has a parent directory, `false` otherwise.
167 *
168 * _This implementation returns `true` if and only if this is not the root dir and
169 * there is a path separator in the raw path._
170 */
171 get hasDir() {
172 return !this.isRoot && this.raw.lastIndexOf(Path.separator) !== -1;
173 }
174 computeDir() {
175 if (!this.hasDir) {
176 return this;
177 }
178 const lastIndex = this.raw.lastIndexOf(Path.separator);
179 if (this.isAbsolute) {
180 const firstIndex = this.raw.indexOf(Path.separator);
181 if (firstIndex === lastIndex) {
182 return new Path(this.raw.substr(0, firstIndex + 1));
183 }
184 }
185 return new Path(this.raw.substr(0, lastIndex));
186 }
187 join(...paths) {
188 const relativePath = paths.filter(s => !!s).join(Path.separator);
189 if (!relativePath) {
190 return this;
191 }
192 if (this.raw.endsWith(Path.separator)) {
193 return new Path(this.raw + relativePath);
194 }
195 return new Path(this.raw + Path.separator + relativePath);
196 }
197 /**
198 *
199 * @param paths portions of a path
200 * @returns a new Path if an absolute path can be computed from the segments passed in + this.raw
201 * If no absolute path can be computed, returns undefined.
202 *
203 * Processes the path segments passed in from right to left (reverse order) concatenating until an
204 * absolute path is found.
205 */
206 resolve(...paths) {
207 const segments = paths.slice().reverse(); // Don't mutate the caller's array.
208 segments.push(this.raw);
209 let result = new Path('');
210 for (const segment of segments) {
211 if (segment) {
212 const next = new Path(segment).join(result.raw);
213 if (next.isAbsolute) {
214 return next.normalize();
215 }
216 result = next;
217 }
218 }
219 }
220 toString() {
221 return this.raw;
222 }
223 /**
224 * Converts the current path into a file system path.
225 * @param format Determines the format of the path.
226 * If `undefined`, the format will be determined by the `OS.backend.type` value.
227 * @returns A file system path.
228 */
229 fsPath(format) {
230 if (format === Path.Format.Windows || (format === undefined && os_1.OS.backend.isWindows)) {
231 return Path.windowsPath(this.raw);
232 }
233 else {
234 return this.raw;
235 }
236 }
237 relative(path) {
238 if (this.raw === path.raw) {
239 return new Path('');
240 }
241 if (!this.raw || !path.raw) {
242 return undefined;
243 }
244 const raw = this.base ? this.raw + Path.separator : this.raw;
245 if (!path.raw.startsWith(raw)) {
246 return undefined;
247 }
248 const relativePath = path.raw.substr(raw.length);
249 return new Path(relativePath);
250 }
251 isEqualOrParent(path) {
252 return !!this.relative(path);
253 }
254 relativity(path) {
255 const relative = this.relative(path);
256 if (relative) {
257 const relativeStr = relative.toString();
258 if (relativeStr === '') {
259 return 0;
260 }
261 return relativeStr.split(Path.separator).length;
262 }
263 return -1;
264 }
265 /*
266 * return a normalized Path, resolving '..' and '.' segments
267 */
268 normalize() {
269 const trailingSlash = this.raw.endsWith('/');
270 const pathArray = this.toString().split('/');
271 const resultArray = [];
272 pathArray.forEach((value, index) => {
273 if (!value || value === '.') {
274 return;
275 }
276 if (value === '..') {
277 if (resultArray.length && resultArray[resultArray.length - 1] !== '..') {
278 resultArray.pop();
279 }
280 else if (!this.isAbsolute) {
281 resultArray.push('..');
282 }
283 }
284 else {
285 resultArray.push(value);
286 }
287 });
288 if (resultArray.length === 0) {
289 if (this.isRoot) {
290 return new Path('/');
291 }
292 else {
293 return new Path('.');
294 }
295 }
296 return new Path((this.isAbsolute ? '/' : '') + resultArray.join('/') + (trailingSlash ? '/' : ''));
297 }
298}
299exports.Path = Path;
300Path.separator = '/';
301(function (Path) {
302 let Format;
303 (function (Format) {
304 Format[Format["Posix"] = 0] = "Posix";
305 Format[Format["Windows"] = 1] = "Windows";
306 })(Format = Path.Format || (Path.Format = {}));
307})(Path = exports.Path || (exports.Path = {}));
308//# sourceMappingURL=path.js.map
\No newline at end of file