doc: improvements to console.markdown copy
[platform/upstream/nodejs.git] / lib / path.js
1 'use strict';
2
3 const util = require('util');
4 const isWindows = process.platform === 'win32';
5
6 function assertPath(path) {
7   if (typeof path !== 'string') {
8     throw new TypeError('Path must be a string. Received ' +
9                         util.inspect(path));
10   }
11 }
12
13 // resolves . and .. elements in a path array with directory names there
14 // must be no slashes or device names (c:\) in the array
15 // (so also no leading and trailing slashes - it does not distinguish
16 // relative and absolute paths)
17 function normalizeArray(parts, allowAboveRoot) {
18   var res = [];
19   for (var i = 0; i < parts.length; i++) {
20     var p = parts[i];
21
22     // ignore empty parts
23     if (!p || p === '.')
24       continue;
25
26     if (p === '..') {
27       if (res.length && res[res.length - 1] !== '..') {
28         res.pop();
29       } else if (allowAboveRoot) {
30         res.push('..');
31       }
32     } else {
33       res.push(p);
34     }
35   }
36
37   return res;
38 }
39
40 // Returns an array with empty elements removed from either end of the input
41 // array or the original array if no elements need to be removed
42 function trimArray(arr) {
43   var lastIndex = arr.length - 1;
44   var start = 0;
45   for (; start <= lastIndex; start++) {
46     if (arr[start])
47       break;
48   }
49
50   var end = lastIndex;
51   for (; end >= 0; end--) {
52     if (arr[end])
53       break;
54   }
55
56   if (start === 0 && end === lastIndex)
57     return arr;
58   if (start > end)
59     return [];
60   return arr.slice(start, end + 1);
61 }
62
63 // Regex to split a windows path into three parts: [*, device, slash,
64 // tail] windows-only
65 const splitDeviceRe =
66     /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/;
67
68 // Regex to split the tail part of the above into [*, dir, basename, ext]
69 const splitTailRe =
70     /^([\s\S]*?)((?:\.{1,2}|[^\\\/]+?|)(\.[^.\/\\]*|))(?:[\\\/]*)$/;
71
72 var win32 = {};
73
74 // Function to split a filename into [root, dir, basename, ext]
75 function win32SplitPath(filename) {
76   // Separate device+slash from tail
77   var result = splitDeviceRe.exec(filename),
78       device = (result[1] || '') + (result[2] || ''),
79       tail = result[3];
80   // Split the tail into dir, basename and extension
81   var result2 = splitTailRe.exec(tail),
82       dir = result2[1],
83       basename = result2[2],
84       ext = result2[3];
85   return [device, dir, basename, ext];
86 }
87
88 function win32StatPath(path) {
89   var result = splitDeviceRe.exec(path),
90       device = result[1] || '',
91       isUnc = !!device && device[1] !== ':';
92   return {
93     device,
94     isUnc,
95     isAbsolute: isUnc || !!result[2], // UNC paths are always absolute
96     tail: result[3]
97   };
98 }
99
100 function normalizeUNCRoot(device) {
101   return '\\\\' + device.replace(/^[\\\/]+/, '').replace(/[\\\/]+/g, '\\');
102 }
103
104 // path.resolve([from ...], to)
105 win32.resolve = function() {
106   var resolvedDevice = '',
107       resolvedTail = '',
108       resolvedAbsolute = false;
109
110   for (var i = arguments.length - 1; i >= -1; i--) {
111     var path;
112     if (i >= 0) {
113       path = arguments[i];
114     } else if (!resolvedDevice) {
115       path = process.cwd();
116     } else {
117       // Windows has the concept of drive-specific current working
118       // directories. If we've resolved a drive letter but not yet an
119       // absolute path, get cwd for that drive. We're sure the device is not
120       // an unc path at this points, because unc paths are always absolute.
121       path = process.env['=' + resolvedDevice];
122       // Verify that a drive-local cwd was found and that it actually points
123       // to our drive. If not, default to the drive's root.
124       if (!path || path.substr(0, 3).toLowerCase() !==
125           resolvedDevice.toLowerCase() + '\\') {
126         path = resolvedDevice + '\\';
127       }
128     }
129
130     assertPath(path);
131
132     // Skip empty entries
133     if (path === '') {
134       continue;
135     }
136
137     var result = win32StatPath(path),
138         device = result.device,
139         isUnc = result.isUnc,
140         isAbsolute = result.isAbsolute,
141         tail = result.tail;
142
143     if (device &&
144         resolvedDevice &&
145         device.toLowerCase() !== resolvedDevice.toLowerCase()) {
146       // This path points to another device so it is not applicable
147       continue;
148     }
149
150     if (!resolvedDevice) {
151       resolvedDevice = device;
152     }
153     if (!resolvedAbsolute) {
154       resolvedTail = tail + '\\' + resolvedTail;
155       resolvedAbsolute = isAbsolute;
156     }
157
158     if (resolvedDevice && resolvedAbsolute) {
159       break;
160     }
161   }
162
163   // Convert slashes to backslashes when `resolvedDevice` points to an UNC
164   // root. Also squash multiple slashes into a single one where appropriate.
165   if (isUnc) {
166     resolvedDevice = normalizeUNCRoot(resolvedDevice);
167   }
168
169   // At this point the path should be resolved to a full absolute path,
170   // but handle relative paths to be safe (might happen when process.cwd()
171   // fails)
172
173   // Normalize the tail path
174   resolvedTail = normalizeArray(resolvedTail.split(/[\\\/]+/),
175                                 !resolvedAbsolute).join('\\');
176
177   return (resolvedDevice + (resolvedAbsolute ? '\\' : '') + resolvedTail) ||
178          '.';
179 };
180
181
182 win32.normalize = function(path) {
183   assertPath(path);
184
185   var result = win32StatPath(path),
186       device = result.device,
187       isUnc = result.isUnc,
188       isAbsolute = result.isAbsolute,
189       tail = result.tail,
190       trailingSlash = /[\\\/]$/.test(tail);
191
192   // Normalize the tail path
193   tail = normalizeArray(tail.split(/[\\\/]+/), !isAbsolute).join('\\');
194
195   if (!tail && !isAbsolute) {
196     tail = '.';
197   }
198   if (tail && trailingSlash) {
199     tail += '\\';
200   }
201
202   // Convert slashes to backslashes when `device` points to an UNC root.
203   // Also squash multiple slashes into a single one where appropriate.
204   if (isUnc) {
205     device = normalizeUNCRoot(device);
206   }
207
208   return device + (isAbsolute ? '\\' : '') + tail;
209 };
210
211
212 win32.isAbsolute = function(path) {
213   assertPath(path);
214   return win32StatPath(path).isAbsolute;
215 };
216
217 win32.join = function() {
218   var paths = [];
219   for (var i = 0; i < arguments.length; i++) {
220     var arg = arguments[i];
221     assertPath(arg);
222     if (arg) {
223       paths.push(arg);
224     }
225   }
226
227   var joined = paths.join('\\');
228
229   // Make sure that the joined path doesn't start with two slashes, because
230   // normalize() will mistake it for an UNC path then.
231   //
232   // This step is skipped when it is very clear that the user actually
233   // intended to point at an UNC path. This is assumed when the first
234   // non-empty string arguments starts with exactly two slashes followed by
235   // at least one more non-slash character.
236   //
237   // Note that for normalize() to treat a path as an UNC path it needs to
238   // have at least 2 components, so we don't filter for that here.
239   // This means that the user can use join to construct UNC paths from
240   // a server name and a share name; for example:
241   //   path.join('//server', 'share') -> '\\\\server\\share\')
242   if (!/^[\\\/]{2}[^\\\/]/.test(paths[0])) {
243     joined = joined.replace(/^[\\\/]{2,}/, '\\');
244   }
245
246   return win32.normalize(joined);
247 };
248
249
250 // path.relative(from, to)
251 // it will solve the relative path from 'from' to 'to', for instance:
252 // from = 'C:\\orandea\\test\\aaa'
253 // to = 'C:\\orandea\\impl\\bbb'
254 // The output of the function should be: '..\\..\\impl\\bbb'
255 win32.relative = function(from, to) {
256   assertPath(from);
257   assertPath(to);
258
259   from = win32.resolve(from);
260   to = win32.resolve(to);
261
262   // windows is not case sensitive
263   var lowerFrom = from.toLowerCase();
264   var lowerTo = to.toLowerCase();
265
266   var toParts = trimArray(to.split('\\'));
267
268   var lowerFromParts = trimArray(lowerFrom.split('\\'));
269   var lowerToParts = trimArray(lowerTo.split('\\'));
270
271   var length = Math.min(lowerFromParts.length, lowerToParts.length);
272   var samePartsLength = length;
273   for (var i = 0; i < length; i++) {
274     if (lowerFromParts[i] !== lowerToParts[i]) {
275       samePartsLength = i;
276       break;
277     }
278   }
279
280   if (samePartsLength === 0) {
281     return to;
282   }
283
284   var outputParts = [];
285   for (var i = samePartsLength; i < lowerFromParts.length; i++) {
286     outputParts.push('..');
287   }
288
289   outputParts = outputParts.concat(toParts.slice(samePartsLength));
290
291   return outputParts.join('\\');
292 };
293
294
295 win32._makeLong = function(path) {
296   // Note: this will *probably* throw somewhere.
297   if (typeof path !== 'string')
298     return path;
299
300   if (!path) {
301     return '';
302   }
303
304   var resolvedPath = win32.resolve(path);
305
306   if (/^[a-zA-Z]\:\\/.test(resolvedPath)) {
307     // path is local filesystem path, which needs to be converted
308     // to long UNC path.
309     return '\\\\?\\' + resolvedPath;
310   } else if (/^\\\\[^?.]/.test(resolvedPath)) {
311     // path is network UNC path, which needs to be converted
312     // to long UNC path.
313     return '\\\\?\\UNC\\' + resolvedPath.substring(2);
314   }
315
316   return path;
317 };
318
319
320 win32.dirname = function(path) {
321   var result = win32SplitPath(path),
322       root = result[0],
323       dir = result[1];
324
325   if (!root && !dir) {
326     // No dirname whatsoever
327     return '.';
328   }
329
330   if (dir) {
331     // It has a dirname, strip trailing slash
332     dir = dir.substr(0, dir.length - 1);
333   }
334
335   return root + dir;
336 };
337
338
339 win32.basename = function(path, ext) {
340   if (ext !== undefined && typeof ext !== 'string')
341     throw new TypeError('ext must be a string');
342
343   var f = win32SplitPath(path)[2];
344   // TODO: make this comparison case-insensitive on windows?
345   if (ext && f.substr(-1 * ext.length) === ext) {
346     f = f.substr(0, f.length - ext.length);
347   }
348   return f;
349 };
350
351
352 win32.extname = function(path) {
353   return win32SplitPath(path)[3];
354 };
355
356
357 win32.format = function(pathObject) {
358   if (pathObject === null || typeof pathObject !== 'object') {
359     throw new TypeError(
360         "Parameter 'pathObject' must be an object, not " + typeof pathObject
361     );
362   }
363
364   var root = pathObject.root || '';
365
366   if (typeof root !== 'string') {
367     throw new TypeError(
368         "'pathObject.root' must be a string or undefined, not " +
369         typeof pathObject.root
370     );
371   }
372
373   var dir = pathObject.dir;
374   var base = pathObject.base || '';
375   if (!dir) {
376     return base;
377   }
378   if (dir[dir.length - 1] === win32.sep) {
379     return dir + base;
380   }
381   return dir + win32.sep + base;
382 };
383
384
385 win32.parse = function(pathString) {
386   assertPath(pathString);
387
388   var allParts = win32SplitPath(pathString);
389   return {
390     root: allParts[0],
391     dir: allParts[0] + allParts[1].slice(0, -1),
392     base: allParts[2],
393     ext: allParts[3],
394     name: allParts[2].slice(0, allParts[2].length - allParts[3].length)
395   };
396 };
397
398
399 win32.sep = '\\';
400 win32.delimiter = ';';
401
402
403 // Split a filename into [root, dir, basename, ext], unix version
404 // 'root' is just a slash, or nothing.
405 const splitPathRe =
406     /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;
407 var posix = {};
408
409
410 function posixSplitPath(filename) {
411   const out = splitPathRe.exec(filename);
412   out.shift();
413   return out;
414 }
415
416
417 // path.resolve([from ...], to)
418 // posix version
419 posix.resolve = function() {
420   var resolvedPath = '',
421       resolvedAbsolute = false;
422
423   for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
424     var path = (i >= 0) ? arguments[i] : process.cwd();
425
426     assertPath(path);
427
428     // Skip empty entries
429     if (path === '') {
430       continue;
431     }
432
433     resolvedPath = path + '/' + resolvedPath;
434     resolvedAbsolute = path[0] === '/';
435   }
436
437   // At this point the path should be resolved to a full absolute path, but
438   // handle relative paths to be safe (might happen when process.cwd() fails)
439
440   // Normalize the path
441   resolvedPath = normalizeArray(resolvedPath.split('/'),
442                                 !resolvedAbsolute).join('/');
443
444   return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
445 };
446
447 // path.normalize(path)
448 // posix version
449 posix.normalize = function(path) {
450   assertPath(path);
451
452   var isAbsolute = posix.isAbsolute(path),
453       trailingSlash = path && path[path.length - 1] === '/';
454
455   // Normalize the path
456   path = normalizeArray(path.split('/'), !isAbsolute).join('/');
457
458   if (!path && !isAbsolute) {
459     path = '.';
460   }
461   if (path && trailingSlash) {
462     path += '/';
463   }
464
465   return (isAbsolute ? '/' : '') + path;
466 };
467
468 // posix version
469 posix.isAbsolute = function(path) {
470   assertPath(path);
471   return !!path && path[0] === '/';
472 };
473
474 // posix version
475 posix.join = function() {
476   var path = '';
477   for (var i = 0; i < arguments.length; i++) {
478     var segment = arguments[i];
479     assertPath(segment);
480     if (segment) {
481       if (!path) {
482         path += segment;
483       } else {
484         path += '/' + segment;
485       }
486     }
487   }
488   return posix.normalize(path);
489 };
490
491
492 // path.relative(from, to)
493 // posix version
494 posix.relative = function(from, to) {
495   assertPath(from);
496   assertPath(to);
497
498   from = posix.resolve(from).substr(1);
499   to = posix.resolve(to).substr(1);
500
501   var fromParts = trimArray(from.split('/'));
502   var toParts = trimArray(to.split('/'));
503
504   var length = Math.min(fromParts.length, toParts.length);
505   var samePartsLength = length;
506   for (var i = 0; i < length; i++) {
507     if (fromParts[i] !== toParts[i]) {
508       samePartsLength = i;
509       break;
510     }
511   }
512
513   var outputParts = [];
514   for (var i = samePartsLength; i < fromParts.length; i++) {
515     outputParts.push('..');
516   }
517
518   outputParts = outputParts.concat(toParts.slice(samePartsLength));
519
520   return outputParts.join('/');
521 };
522
523
524 posix._makeLong = function(path) {
525   return path;
526 };
527
528
529 posix.dirname = function(path) {
530   var result = posixSplitPath(path),
531       root = result[0],
532       dir = result[1];
533
534   if (!root && !dir) {
535     // No dirname whatsoever
536     return '.';
537   }
538
539   if (dir) {
540     // It has a dirname, strip trailing slash
541     dir = dir.substr(0, dir.length - 1);
542   }
543
544   return root + dir;
545 };
546
547
548 posix.basename = function(path, ext) {
549   if (ext !== undefined && typeof ext !== 'string')
550     throw new TypeError('ext must be a string');
551
552   var f = posixSplitPath(path)[2];
553
554   if (ext && f.substr(-1 * ext.length) === ext) {
555     f = f.substr(0, f.length - ext.length);
556   }
557   return f;
558 };
559
560
561 posix.extname = function(path) {
562   return posixSplitPath(path)[3];
563 };
564
565
566 posix.format = function(pathObject) {
567   if (pathObject === null || typeof pathObject !== 'object') {
568     throw new TypeError(
569         "Parameter 'pathObject' must be an object, not " + typeof pathObject
570     );
571   }
572
573   var root = pathObject.root || '';
574
575   if (typeof root !== 'string') {
576     throw new TypeError(
577         "'pathObject.root' must be a string or undefined, not " +
578         typeof pathObject.root
579     );
580   }
581
582   var dir = pathObject.dir ? pathObject.dir + posix.sep : '';
583   var base = pathObject.base || '';
584   return dir + base;
585 };
586
587
588 posix.parse = function(pathString) {
589   assertPath(pathString);
590
591   var allParts = posixSplitPath(pathString);
592   return {
593     root: allParts[0],
594     dir: allParts[0] + allParts[1].slice(0, -1),
595     base: allParts[2],
596     ext: allParts[3],
597     name: allParts[2].slice(0, allParts[2].length - allParts[3].length)
598   };
599 };
600
601
602 posix.sep = '/';
603 posix.delimiter = ':';
604
605
606 if (isWindows)
607   module.exports = win32;
608 else /* posix */
609   module.exports = posix;
610
611 module.exports.posix = posix;
612 module.exports.win32 = win32;